Linux Audio

Check our new training course

Linux debugging, profiling, tracing and performance analysis training

Mar 24-27, 2025, special US time zones
Register
Loading...
Note: File does not exist in v3.1.
  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
 51struct sg2042_mcu_data {
 52	struct i2c_client	*client;
 53	struct dentry		*debugfs;
 54	struct mutex		mutex;
 55};
 56
 57static struct dentry *sgmcu_debugfs;
 58
 59static 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
 73static 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
 90static 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
104static 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
126static 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
149static DEVICE_ATTR_RO(reset_count);
150static DEVICE_ATTR_RO(uptime);
151static DEVICE_ATTR_RO(reset_reason);
152static DEVICE_ATTR_RW(repower_policy);
153
154DEFINE_MCU_DEBUG_ATTR(firmware_version, REG_MCU_FIRMWARE_VERSION, "0x%02x");
155DEFINE_MCU_DEBUG_ATTR(pcb_version, REG_PCB_VERSION, "0x%02x");
156DEFINE_MCU_DEBUG_ATTR(board_type, REG_BOARD_TYPE, "0x%02x");
157DEFINE_MCU_DEBUG_ATTR(mcu_type, REG_MCU_TYPE, "%d");
158
159static 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
167static const struct attribute_group sg2042_mcu_attr_group = {
168	.attrs	= sg2042_mcu_attrs,
169};
170
171static const struct attribute_group *sg2042_mcu_groups[] = {
172	&sg2042_mcu_attr_group,
173	NULL
174};
175
176static 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
184static 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
214static 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
260static 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
284static 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
290static const struct hwmon_chip_info sg2042_mcu_chip_info = {
291	.ops = &sg2042_mcu_ops,
292	.info = sg2042_mcu_info,
293};
294
295static 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
310static 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
341static 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
348static const struct i2c_device_id sg2042_mcu_id[] = {
349	{ "sg2042-hwmon-mcu" },
350	{ }
351};
352MODULE_DEVICE_TABLE(i2c, sg2042_mcu_id);
353
354static const struct of_device_id sg2042_mcu_of_id[] = {
355	{ .compatible = "sophgo,sg2042-hwmon-mcu" },
356	{},
357};
358MODULE_DEVICE_TABLE(of, sg2042_mcu_of_id);
359
360static 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
371static 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
377static void __exit sg2042_mcu_exit(void)
378{
379	debugfs_remove_recursive(sgmcu_debugfs);
380	i2c_del_driver(&sg2042_mcu_driver);
381}
382
383module_init(sg2042_mcu_init);
384module_exit(sg2042_mcu_exit);
385
386MODULE_AUTHOR("Inochi Amaoto <inochiama@outlook.com>");
387MODULE_DESCRIPTION("MCU I2C driver for SG2042 soc platform");
388MODULE_LICENSE("GPL");