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