1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2024 Inochi Amaoto <inochiama@outlook.com>
4  *
5  * Sophgo power control mcu for SG2042
6  */
7 
8 #include <linux/cleanup.h>
9 #include <linux/debugfs.h>
10 #include <linux/err.h>
11 #include <linux/hwmon.h>
12 #include <linux/i2c.h>
13 #include <linux/kernel.h>
14 #include <linux/module.h>
15 #include <linux/mutex.h>
16 
17 /* fixed MCU registers */
18 #define REG_BOARD_TYPE				0x00
19 #define REG_MCU_FIRMWARE_VERSION		0x01
20 #define REG_PCB_VERSION				0x02
21 #define REG_PWR_CTRL				0x03
22 #define REG_SOC_TEMP				0x04
23 #define REG_BOARD_TEMP				0x05
24 #define REG_RST_COUNT				0x0a
25 #define REG_UPTIME				0x0b
26 #define REG_RESET_REASON			0x0d
27 #define REG_MCU_TYPE				0x18
28 #define REG_REPOWER_POLICY			0x65
29 #define REG_CRITICAL_TEMP			0x66
30 #define REG_REPOWER_TEMP			0x67
31 
32 #define REPOWER_POLICY_REBOOT			1
33 #define REPOWER_POLICY_KEEP_OFF			2
34 
35 #define MCU_POWER_MAX				0xff
36 
37 #define DEFINE_MCU_DEBUG_ATTR(_name, _reg, _format)			\
38 	static int _name##_show(struct seq_file *seqf,			\
39 				    void *unused)			\
40 	{								\
41 		struct sg2042_mcu_data *mcu = seqf->private;		\
42 		int ret;						\
43 		ret = i2c_smbus_read_byte_data(mcu->client, (_reg));	\
44 		if (ret < 0)						\
45 			return ret;					\
46 		seq_printf(seqf, _format "\n", ret);			\
47 		return 0;						\
48 	}								\
49 	DEFINE_SHOW_ATTRIBUTE(_name)					\
50 
51 struct sg2042_mcu_data {
52 	struct i2c_client	*client;
53 	struct dentry		*debugfs;
54 	struct mutex		mutex;
55 };
56 
57 static struct dentry *sgmcu_debugfs;
58 
reset_count_show(struct device * dev,struct device_attribute * attr,char * buf)59 static ssize_t reset_count_show(struct device *dev,
60 				struct device_attribute *attr,
61 				char *buf)
62 {
63 	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
64 	int ret;
65 
66 	ret = i2c_smbus_read_byte_data(mcu->client, REG_RST_COUNT);
67 	if (ret < 0)
68 		return ret;
69 
70 	return sprintf(buf, "%d\n", ret);
71 }
72 
uptime_show(struct device * dev,struct device_attribute * attr,char * buf)73 static ssize_t uptime_show(struct device *dev,
74 			   struct device_attribute *attr,
75 			   char *buf)
76 {
77 	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
78 	u8 time_val[2];
79 	int ret;
80 
81 	ret = i2c_smbus_read_i2c_block_data(mcu->client, REG_UPTIME,
82 					    sizeof(time_val), time_val);
83 	if (ret < 0)
84 		return ret;
85 
86 	return sprintf(buf, "%d\n",
87 		       (time_val[0]) | (time_val[1] << 8));
88 }
89 
reset_reason_show(struct device * dev,struct device_attribute * attr,char * buf)90 static ssize_t reset_reason_show(struct device *dev,
91 				 struct device_attribute *attr,
92 				 char *buf)
93 {
94 	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
95 	int ret;
96 
97 	ret = i2c_smbus_read_byte_data(mcu->client, REG_RESET_REASON);
98 	if (ret < 0)
99 		return ret;
100 
101 	return sprintf(buf, "0x%02x\n", ret);
102 }
103 
repower_policy_show(struct device * dev,struct device_attribute * attr,char * buf)104 static ssize_t repower_policy_show(struct device *dev,
105 				   struct device_attribute *attr,
106 				   char *buf)
107 {
108 	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
109 	int ret;
110 	const char *action;
111 
112 	ret = i2c_smbus_read_byte_data(mcu->client, REG_REPOWER_POLICY);
113 	if (ret < 0)
114 		return ret;
115 
116 	if (ret == REPOWER_POLICY_REBOOT)
117 		action = "repower";
118 	else if (ret == REPOWER_POLICY_KEEP_OFF)
119 		action = "keep";
120 	else
121 		action = "unknown";
122 
123 	return sprintf(buf, "%s\n", action);
124 }
125 
repower_policy_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)126 static ssize_t repower_policy_store(struct device *dev,
127 				    struct device_attribute *attr,
128 				    const char *buf, size_t count)
129 {
130 	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
131 	u8 value;
132 	int ret;
133 
134 	if (sysfs_streq("repower", buf))
135 		value = REPOWER_POLICY_REBOOT;
136 	else if (sysfs_streq("keep", buf))
137 		value = REPOWER_POLICY_KEEP_OFF;
138 	else
139 		return -EINVAL;
140 
141 	ret = i2c_smbus_write_byte_data(mcu->client,
142 					REG_REPOWER_POLICY, value);
143 	if (ret < 0)
144 		return ret;
145 
146 	return count;
147 }
148 
149 static DEVICE_ATTR_RO(reset_count);
150 static DEVICE_ATTR_RO(uptime);
151 static DEVICE_ATTR_RO(reset_reason);
152 static DEVICE_ATTR_RW(repower_policy);
153 
154 DEFINE_MCU_DEBUG_ATTR(firmware_version, REG_MCU_FIRMWARE_VERSION, "0x%02x");
155 DEFINE_MCU_DEBUG_ATTR(pcb_version, REG_PCB_VERSION, "0x%02x");
156 DEFINE_MCU_DEBUG_ATTR(board_type, REG_BOARD_TYPE, "0x%02x");
157 DEFINE_MCU_DEBUG_ATTR(mcu_type, REG_MCU_TYPE, "%d");
158 
159 static struct attribute *sg2042_mcu_attrs[] = {
160 	&dev_attr_reset_count.attr,
161 	&dev_attr_uptime.attr,
162 	&dev_attr_reset_reason.attr,
163 	&dev_attr_repower_policy.attr,
164 	NULL
165 };
166 
167 static const struct attribute_group sg2042_mcu_attr_group = {
168 	.attrs	= sg2042_mcu_attrs,
169 };
170 
171 static const struct attribute_group *sg2042_mcu_groups[] = {
172 	&sg2042_mcu_attr_group,
173 	NULL
174 };
175 
176 static const struct hwmon_channel_info * const sg2042_mcu_info[] = {
177 	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
178 	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_CRIT |
179 					HWMON_T_CRIT_HYST,
180 				 HWMON_T_INPUT),
181 	NULL
182 };
183 
sg2042_mcu_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)184 static int sg2042_mcu_read(struct device *dev,
185 			   enum hwmon_sensor_types type,
186 			   u32 attr, int channel, long *val)
187 {
188 	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
189 	int tmp;
190 	u8 reg;
191 
192 	switch (attr) {
193 	case hwmon_temp_input:
194 		reg = channel ? REG_BOARD_TEMP : REG_SOC_TEMP;
195 		break;
196 	case hwmon_temp_crit:
197 		reg = REG_CRITICAL_TEMP;
198 		break;
199 	case hwmon_temp_crit_hyst:
200 		reg = REG_REPOWER_TEMP;
201 		break;
202 	default:
203 		return -EOPNOTSUPP;
204 	}
205 
206 	tmp = i2c_smbus_read_byte_data(mcu->client, reg);
207 	if (tmp < 0)
208 		return tmp;
209 	*val = tmp * 1000;
210 
211 	return 0;
212 }
213 
sg2042_mcu_write(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long val)214 static int sg2042_mcu_write(struct device *dev,
215 			    enum hwmon_sensor_types type,
216 			    u32 attr, int channel, long val)
217 {
218 	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
219 	int temp = val / 1000;
220 	int hyst_temp, crit_temp;
221 	u8 reg;
222 
223 	temp = clamp_val(temp, 0, MCU_POWER_MAX);
224 
225 	guard(mutex)(&mcu->mutex);
226 
227 	switch (attr) {
228 	case hwmon_temp_crit:
229 		hyst_temp = i2c_smbus_read_byte_data(mcu->client,
230 						     REG_REPOWER_TEMP);
231 		if (hyst_temp < 0)
232 			return hyst_temp;
233 
234 		crit_temp = temp;
235 		reg = REG_CRITICAL_TEMP;
236 		break;
237 	case hwmon_temp_crit_hyst:
238 		crit_temp = i2c_smbus_read_byte_data(mcu->client,
239 						     REG_CRITICAL_TEMP);
240 		if (crit_temp < 0)
241 			return crit_temp;
242 
243 		hyst_temp = temp;
244 		reg = REG_REPOWER_TEMP;
245 		break;
246 	default:
247 		return -EOPNOTSUPP;
248 	}
249 
250 	/*
251 	 * ensure hyst_temp is smaller to avoid MCU from
252 	 * keeping triggering repower event.
253 	 */
254 	if (crit_temp < hyst_temp)
255 		return -EINVAL;
256 
257 	return i2c_smbus_write_byte_data(mcu->client, reg, temp);
258 }
259 
sg2042_mcu_is_visible(const void * _data,enum hwmon_sensor_types type,u32 attr,int channel)260 static umode_t sg2042_mcu_is_visible(const void *_data,
261 				     enum hwmon_sensor_types type,
262 				     u32 attr, int channel)
263 {
264 	switch (type) {
265 	case hwmon_temp:
266 		switch (attr) {
267 		case hwmon_temp_input:
268 			return 0444;
269 		case hwmon_temp_crit:
270 		case hwmon_temp_crit_hyst:
271 			if (channel == 0)
272 				return 0644;
273 			break;
274 		default:
275 			break;
276 		}
277 		break;
278 	default:
279 			break;
280 	}
281 	return 0;
282 }
283 
284 static const struct hwmon_ops sg2042_mcu_ops = {
285 	.is_visible = sg2042_mcu_is_visible,
286 	.read = sg2042_mcu_read,
287 	.write = sg2042_mcu_write,
288 };
289 
290 static const struct hwmon_chip_info sg2042_mcu_chip_info = {
291 	.ops = &sg2042_mcu_ops,
292 	.info = sg2042_mcu_info,
293 };
294 
sg2042_mcu_debugfs_init(struct sg2042_mcu_data * mcu,struct device * dev)295 static void sg2042_mcu_debugfs_init(struct sg2042_mcu_data *mcu,
296 				    struct device *dev)
297 {
298 	mcu->debugfs = debugfs_create_dir(dev_name(dev), sgmcu_debugfs);
299 
300 	debugfs_create_file("firmware_version", 0444, mcu->debugfs,
301 			    mcu, &firmware_version_fops);
302 	debugfs_create_file("pcb_version", 0444, mcu->debugfs, mcu,
303 			    &pcb_version_fops);
304 	debugfs_create_file("mcu_type", 0444, mcu->debugfs, mcu,
305 			    &mcu_type_fops);
306 	debugfs_create_file("board_type", 0444, mcu->debugfs, mcu,
307 			    &board_type_fops);
308 }
309 
sg2042_mcu_i2c_probe(struct i2c_client * client)310 static int sg2042_mcu_i2c_probe(struct i2c_client *client)
311 {
312 	struct device *dev = &client->dev;
313 	struct sg2042_mcu_data *mcu;
314 	struct device *hwmon_dev;
315 
316 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
317 						I2C_FUNC_SMBUS_BLOCK_DATA))
318 		return -ENODEV;
319 
320 	mcu = devm_kmalloc(dev, sizeof(*mcu), GFP_KERNEL);
321 	if (!mcu)
322 		return -ENOMEM;
323 
324 	mutex_init(&mcu->mutex);
325 	mcu->client = client;
326 
327 	i2c_set_clientdata(client, mcu);
328 
329 	hwmon_dev = devm_hwmon_device_register_with_info(dev, "sg2042_mcu",
330 							 mcu,
331 							 &sg2042_mcu_chip_info,
332 							 NULL);
333 	if (IS_ERR(hwmon_dev))
334 		return PTR_ERR(hwmon_dev);
335 
336 	sg2042_mcu_debugfs_init(mcu, dev);
337 
338 	return 0;
339 }
340 
sg2042_mcu_i2c_remove(struct i2c_client * client)341 static void sg2042_mcu_i2c_remove(struct i2c_client *client)
342 {
343 	struct sg2042_mcu_data *mcu = i2c_get_clientdata(client);
344 
345 	debugfs_remove_recursive(mcu->debugfs);
346 }
347 
348 static const struct i2c_device_id sg2042_mcu_id[] = {
349 	{ "sg2042-hwmon-mcu", 0 },
350 	{},
351 };
352 MODULE_DEVICE_TABLE(i2c, sg2042_mcu_id);
353 
354 static const struct of_device_id sg2042_mcu_of_id[] = {
355 	{ .compatible = "sophgo,sg2042-hwmon-mcu" },
356 	{},
357 };
358 MODULE_DEVICE_TABLE(of, sg2042_mcu_of_id);
359 
360 static struct i2c_driver sg2042_mcu_driver = {
361 	.driver = {
362 		.name = "sg2042-mcu",
363 		.of_match_table = sg2042_mcu_of_id,
364 		.dev_groups = sg2042_mcu_groups,
365 	},
366 	.probe = sg2042_mcu_i2c_probe,
367 	.remove = sg2042_mcu_i2c_remove,
368 	.id_table = sg2042_mcu_id,
369 };
370 
sg2042_mcu_init(void)371 static int __init sg2042_mcu_init(void)
372 {
373 	sgmcu_debugfs = debugfs_create_dir("sg2042-mcu", NULL);
374 	return i2c_add_driver(&sg2042_mcu_driver);
375 }
376 
sg2042_mcu_exit(void)377 static void __exit sg2042_mcu_exit(void)
378 {
379 	debugfs_remove_recursive(sgmcu_debugfs);
380 	i2c_del_driver(&sg2042_mcu_driver);
381 }
382 
383 module_init(sg2042_mcu_init);
384 module_exit(sg2042_mcu_exit);
385 
386 MODULE_AUTHOR("Inochi Amaoto <inochiama@outlook.com>");
387 MODULE_DESCRIPTION("MCU I2C driver for SG2042 soc platform");
388 MODULE_LICENSE("GPL");
389