Merge tag 'trace-printf-v6.13' of git://git.kernel.org/pub/scm/linux/kernel/git/trace...
[drm/drm-misc.git] / drivers / hwmon / sg2042-mcu.c
blobaa3fb773602c9e9deac379767b5aaade5b301a11
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2024 Inochi Amaoto <inochiama@outlook.com>
5 * Sophgo power control mcu for SG2042
6 */
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>
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
32 #define REPOWER_POLICY_REBOOT 1
33 #define REPOWER_POLICY_KEEP_OFF 2
35 #define MCU_POWER_MAX 0xff
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) \
51 struct sg2042_mcu_data {
52 struct i2c_client *client;
53 struct dentry *debugfs;
54 struct mutex mutex;
57 static struct dentry *sgmcu_debugfs;
59 static ssize_t reset_count_show(struct device *dev,
60 struct device_attribute *attr,
61 char *buf)
63 struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
64 int ret;
66 ret = i2c_smbus_read_byte_data(mcu->client, REG_RST_COUNT);
67 if (ret < 0)
68 return ret;
70 return sprintf(buf, "%d\n", ret);
73 static ssize_t uptime_show(struct device *dev,
74 struct device_attribute *attr,
75 char *buf)
77 struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
78 u8 time_val[2];
79 int ret;
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;
86 return sprintf(buf, "%d\n",
87 (time_val[0]) | (time_val[1] << 8));
90 static ssize_t reset_reason_show(struct device *dev,
91 struct device_attribute *attr,
92 char *buf)
94 struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
95 int ret;
97 ret = i2c_smbus_read_byte_data(mcu->client, REG_RESET_REASON);
98 if (ret < 0)
99 return ret;
101 return sprintf(buf, "0x%02x\n", ret);
104 static ssize_t repower_policy_show(struct device *dev,
105 struct device_attribute *attr,
106 char *buf)
108 struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
109 int ret;
110 const char *action;
112 ret = i2c_smbus_read_byte_data(mcu->client, REG_REPOWER_POLICY);
113 if (ret < 0)
114 return ret;
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";
123 return sprintf(buf, "%s\n", action);
126 static ssize_t repower_policy_store(struct device *dev,
127 struct device_attribute *attr,
128 const char *buf, size_t count)
130 struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
131 u8 value;
132 int ret;
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;
141 ret = i2c_smbus_write_byte_data(mcu->client,
142 REG_REPOWER_POLICY, value);
143 if (ret < 0)
144 return ret;
146 return count;
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);
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");
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
167 static const struct attribute_group sg2042_mcu_attr_group = {
168 .attrs = sg2042_mcu_attrs,
171 static const struct attribute_group *sg2042_mcu_groups[] = {
172 &sg2042_mcu_attr_group,
173 NULL
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
184 static int sg2042_mcu_read(struct device *dev,
185 enum hwmon_sensor_types type,
186 u32 attr, int channel, long *val)
188 struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
189 int tmp;
190 u8 reg;
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;
206 tmp = i2c_smbus_read_byte_data(mcu->client, reg);
207 if (tmp < 0)
208 return tmp;
209 *val = tmp * 1000;
211 return 0;
214 static int sg2042_mcu_write(struct device *dev,
215 enum hwmon_sensor_types type,
216 u32 attr, int channel, long val)
218 struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
219 int temp = val / 1000;
220 int hyst_temp, crit_temp;
221 u8 reg;
223 temp = clamp_val(temp, 0, MCU_POWER_MAX);
225 guard(mutex)(&mcu->mutex);
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;
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;
243 hyst_temp = temp;
244 reg = REG_REPOWER_TEMP;
245 break;
246 default:
247 return -EOPNOTSUPP;
251 * ensure hyst_temp is smaller to avoid MCU from
252 * keeping triggering repower event.
254 if (crit_temp < hyst_temp)
255 return -EINVAL;
257 return i2c_smbus_write_byte_data(mcu->client, reg, temp);
260 static umode_t sg2042_mcu_is_visible(const void *_data,
261 enum hwmon_sensor_types type,
262 u32 attr, int channel)
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;
277 break;
278 default:
279 break;
281 return 0;
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,
290 static const struct hwmon_chip_info sg2042_mcu_chip_info = {
291 .ops = &sg2042_mcu_ops,
292 .info = sg2042_mcu_info,
295 static void sg2042_mcu_debugfs_init(struct sg2042_mcu_data *mcu,
296 struct device *dev)
298 mcu->debugfs = debugfs_create_dir(dev_name(dev), sgmcu_debugfs);
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);
310 static int sg2042_mcu_i2c_probe(struct i2c_client *client)
312 struct device *dev = &client->dev;
313 struct sg2042_mcu_data *mcu;
314 struct device *hwmon_dev;
316 if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
317 I2C_FUNC_SMBUS_BLOCK_DATA))
318 return -ENODEV;
320 mcu = devm_kmalloc(dev, sizeof(*mcu), GFP_KERNEL);
321 if (!mcu)
322 return -ENOMEM;
324 mutex_init(&mcu->mutex);
325 mcu->client = client;
327 i2c_set_clientdata(client, mcu);
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);
336 sg2042_mcu_debugfs_init(mcu, dev);
338 return 0;
341 static void sg2042_mcu_i2c_remove(struct i2c_client *client)
343 struct sg2042_mcu_data *mcu = i2c_get_clientdata(client);
345 debugfs_remove_recursive(mcu->debugfs);
348 static const struct i2c_device_id sg2042_mcu_id[] = {
349 { "sg2042-hwmon-mcu" },
352 MODULE_DEVICE_TABLE(i2c, sg2042_mcu_id);
354 static const struct of_device_id sg2042_mcu_of_id[] = {
355 { .compatible = "sophgo,sg2042-hwmon-mcu" },
358 MODULE_DEVICE_TABLE(of, sg2042_mcu_of_id);
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,
366 .probe = sg2042_mcu_i2c_probe,
367 .remove = sg2042_mcu_i2c_remove,
368 .id_table = sg2042_mcu_id,
371 static int __init sg2042_mcu_init(void)
373 sgmcu_debugfs = debugfs_create_dir("sg2042-mcu", NULL);
374 return i2c_add_driver(&sg2042_mcu_driver);
377 static void __exit sg2042_mcu_exit(void)
379 debugfs_remove_recursive(sgmcu_debugfs);
380 i2c_del_driver(&sg2042_mcu_driver);
383 module_init(sg2042_mcu_init);
384 module_exit(sg2042_mcu_exit);
386 MODULE_AUTHOR("Inochi Amaoto <inochiama@outlook.com>");
387 MODULE_DESCRIPTION("MCU I2C driver for SG2042 soc platform");
388 MODULE_LICENSE("GPL");