1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright (c) 2024 Inochi Amaoto <inochiama@outlook.com>
5 * Sophgo power control mcu for SG2042
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, \
41 struct sg2042_mcu_data *mcu = seqf->private; \
43 ret = i2c_smbus_read_byte_data(mcu->client, (_reg)); \
46 seq_printf(seqf, _format "\n", ret); \
49 DEFINE_SHOW_ATTRIBUTE(_name) \
51 struct sg2042_mcu_data {
52 struct i2c_client
*client
;
53 struct dentry
*debugfs
;
57 static struct dentry
*sgmcu_debugfs
;
59 static ssize_t
reset_count_show(struct device
*dev
,
60 struct device_attribute
*attr
,
63 struct sg2042_mcu_data
*mcu
= dev_get_drvdata(dev
);
66 ret
= i2c_smbus_read_byte_data(mcu
->client
, REG_RST_COUNT
);
70 return sprintf(buf
, "%d\n", ret
);
73 static ssize_t
uptime_show(struct device
*dev
,
74 struct device_attribute
*attr
,
77 struct sg2042_mcu_data
*mcu
= dev_get_drvdata(dev
);
81 ret
= i2c_smbus_read_i2c_block_data(mcu
->client
, REG_UPTIME
,
82 sizeof(time_val
), time_val
);
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
,
94 struct sg2042_mcu_data
*mcu
= dev_get_drvdata(dev
);
97 ret
= i2c_smbus_read_byte_data(mcu
->client
, REG_RESET_REASON
);
101 return sprintf(buf
, "0x%02x\n", ret
);
104 static ssize_t
repower_policy_show(struct device
*dev
,
105 struct device_attribute
*attr
,
108 struct sg2042_mcu_data
*mcu
= dev_get_drvdata(dev
);
112 ret
= i2c_smbus_read_byte_data(mcu
->client
, REG_REPOWER_POLICY
);
116 if (ret
== REPOWER_POLICY_REBOOT
)
118 else if (ret
== REPOWER_POLICY_KEEP_OFF
)
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
);
134 if (sysfs_streq("repower", buf
))
135 value
= REPOWER_POLICY_REBOOT
;
136 else if (sysfs_streq("keep", buf
))
137 value
= REPOWER_POLICY_KEEP_OFF
;
141 ret
= i2c_smbus_write_byte_data(mcu
->client
,
142 REG_REPOWER_POLICY
, value
);
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
,
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
,
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
|
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
);
193 case hwmon_temp_input
:
194 reg
= channel
? REG_BOARD_TEMP
: REG_SOC_TEMP
;
196 case hwmon_temp_crit
:
197 reg
= REG_CRITICAL_TEMP
;
199 case hwmon_temp_crit_hyst
:
200 reg
= REG_REPOWER_TEMP
;
206 tmp
= i2c_smbus_read_byte_data(mcu
->client
, reg
);
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
;
223 temp
= clamp_val(temp
, 0, MCU_POWER_MAX
);
225 guard(mutex
)(&mcu
->mutex
);
228 case hwmon_temp_crit
:
229 hyst_temp
= i2c_smbus_read_byte_data(mcu
->client
,
235 reg
= REG_CRITICAL_TEMP
;
237 case hwmon_temp_crit_hyst
:
238 crit_temp
= i2c_smbus_read_byte_data(mcu
->client
,
244 reg
= REG_REPOWER_TEMP
;
251 * ensure hyst_temp is smaller to avoid MCU from
252 * keeping triggering repower event.
254 if (crit_temp
< hyst_temp
)
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
)
267 case hwmon_temp_input
:
269 case hwmon_temp_crit
:
270 case hwmon_temp_crit_hyst
:
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
,
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
,
304 debugfs_create_file("mcu_type", 0444, mcu
->debugfs
, mcu
,
306 debugfs_create_file("board_type", 0444, mcu
->debugfs
, mcu
,
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
))
320 mcu
= devm_kmalloc(dev
, sizeof(*mcu
), GFP_KERNEL
);
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",
331 &sg2042_mcu_chip_info
,
333 if (IS_ERR(hwmon_dev
))
334 return PTR_ERR(hwmon_dev
);
336 sg2042_mcu_debugfs_init(mcu
, dev
);
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
= {
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");