File Index
Files and directories
Sign in
FILE

sg2042-mcu.c

File preview with syntax highlighting.

linux-6.19.14/drivers/hwmon/sg2042-mcu.c C
  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 mutex		mutex;
 54};
 55
 56static ssize_t reset_count_show(struct device *dev,
 57				struct device_attribute *attr,
 58				char *buf)
 59{
 60	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
 61	int ret;
 62
 63	ret = i2c_smbus_read_byte_data(mcu->client, REG_RST_COUNT);
 64	if (ret < 0)
 65		return ret;
 66
 67	return sprintf(buf, "%d\n", ret);
 68}
 69
 70static ssize_t uptime_show(struct device *dev,
 71			   struct device_attribute *attr,
 72			   char *buf)
 73{
 74	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
 75	u8 time_val[2];
 76	int ret;
 77
 78	ret = i2c_smbus_read_i2c_block_data(mcu->client, REG_UPTIME,
 79					    sizeof(time_val), time_val);
 80	if (ret < 0)
 81		return ret;
 82
 83	return sprintf(buf, "%d\n",
 84		       (time_val[0]) | (time_val[1] << 8));
 85}
 86
 87static ssize_t reset_reason_show(struct device *dev,
 88				 struct device_attribute *attr,
 89				 char *buf)
 90{
 91	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
 92	int ret;
 93
 94	ret = i2c_smbus_read_byte_data(mcu->client, REG_RESET_REASON);
 95	if (ret < 0)
 96		return ret;
 97
 98	return sprintf(buf, "0x%02x\n", ret);
 99}
100
101static ssize_t repower_policy_show(struct device *dev,
102				   struct device_attribute *attr,
103				   char *buf)
104{
105	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
106	int ret;
107	const char *action;
108
109	ret = i2c_smbus_read_byte_data(mcu->client, REG_REPOWER_POLICY);
110	if (ret < 0)
111		return ret;
112
113	if (ret == REPOWER_POLICY_REBOOT)
114		action = "repower";
115	else if (ret == REPOWER_POLICY_KEEP_OFF)
116		action = "keep";
117	else
118		action = "unknown";
119
120	return sprintf(buf, "%s\n", action);
121}
122
123static ssize_t repower_policy_store(struct device *dev,
124				    struct device_attribute *attr,
125				    const char *buf, size_t count)
126{
127	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
128	u8 value;
129	int ret;
130
131	if (sysfs_streq("repower", buf))
132		value = REPOWER_POLICY_REBOOT;
133	else if (sysfs_streq("keep", buf))
134		value = REPOWER_POLICY_KEEP_OFF;
135	else
136		return -EINVAL;
137
138	ret = i2c_smbus_write_byte_data(mcu->client,
139					REG_REPOWER_POLICY, value);
140	if (ret < 0)
141		return ret;
142
143	return count;
144}
145
146static DEVICE_ATTR_RO(reset_count);
147static DEVICE_ATTR_RO(uptime);
148static DEVICE_ATTR_RO(reset_reason);
149static DEVICE_ATTR_RW(repower_policy);
150
151DEFINE_MCU_DEBUG_ATTR(firmware_version, REG_MCU_FIRMWARE_VERSION, "0x%02x");
152DEFINE_MCU_DEBUG_ATTR(pcb_version, REG_PCB_VERSION, "0x%02x");
153DEFINE_MCU_DEBUG_ATTR(board_type, REG_BOARD_TYPE, "0x%02x");
154DEFINE_MCU_DEBUG_ATTR(mcu_type, REG_MCU_TYPE, "%d");
155
156static struct attribute *sg2042_mcu_attrs[] = {
157	&dev_attr_reset_count.attr,
158	&dev_attr_uptime.attr,
159	&dev_attr_reset_reason.attr,
160	&dev_attr_repower_policy.attr,
161	NULL
162};
163
164static const struct attribute_group sg2042_mcu_attr_group = {
165	.attrs	= sg2042_mcu_attrs,
166};
167
168static const struct attribute_group *sg2042_mcu_groups[] = {
169	&sg2042_mcu_attr_group,
170	NULL
171};
172
173static const struct hwmon_channel_info * const sg2042_mcu_info[] = {
174	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
175	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_CRIT |
176					HWMON_T_CRIT_HYST,
177				 HWMON_T_INPUT),
178	NULL
179};
180
181static int sg2042_mcu_read(struct device *dev,
182			   enum hwmon_sensor_types type,
183			   u32 attr, int channel, long *val)
184{
185	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
186	int tmp;
187	u8 reg;
188
189	switch (attr) {
190	case hwmon_temp_input:
191		reg = channel ? REG_BOARD_TEMP : REG_SOC_TEMP;
192		break;
193	case hwmon_temp_crit:
194		reg = REG_CRITICAL_TEMP;
195		break;
196	case hwmon_temp_crit_hyst:
197		reg = REG_REPOWER_TEMP;
198		break;
199	default:
200		return -EOPNOTSUPP;
201	}
202
203	tmp = i2c_smbus_read_byte_data(mcu->client, reg);
204	if (tmp < 0)
205		return tmp;
206	*val = tmp * 1000;
207
208	return 0;
209}
210
211static int sg2042_mcu_write(struct device *dev,
212			    enum hwmon_sensor_types type,
213			    u32 attr, int channel, long val)
214{
215	struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
216	int temp = val / 1000;
217	int hyst_temp, crit_temp;
218	u8 reg;
219
220	temp = clamp_val(temp, 0, MCU_POWER_MAX);
221
222	guard(mutex)(&mcu->mutex);
223
224	switch (attr) {
225	case hwmon_temp_crit:
226		hyst_temp = i2c_smbus_read_byte_data(mcu->client,
227						     REG_REPOWER_TEMP);
228		if (hyst_temp < 0)
229			return hyst_temp;
230
231		crit_temp = temp;
232		reg = REG_CRITICAL_TEMP;
233		break;
234	case hwmon_temp_crit_hyst:
235		crit_temp = i2c_smbus_read_byte_data(mcu->client,
236						     REG_CRITICAL_TEMP);
237		if (crit_temp < 0)
238			return crit_temp;
239
240		hyst_temp = temp;
241		reg = REG_REPOWER_TEMP;
242		break;
243	default:
244		return -EOPNOTSUPP;
245	}
246
247	/*
248	 * ensure hyst_temp is smaller to avoid MCU from
249	 * keeping triggering repower event.
250	 */
251	if (crit_temp < hyst_temp)
252		return -EINVAL;
253
254	return i2c_smbus_write_byte_data(mcu->client, reg, temp);
255}
256
257static umode_t sg2042_mcu_is_visible(const void *_data,
258				     enum hwmon_sensor_types type,
259				     u32 attr, int channel)
260{
261	switch (type) {
262	case hwmon_temp:
263		switch (attr) {
264		case hwmon_temp_input:
265			return 0444;
266		case hwmon_temp_crit:
267		case hwmon_temp_crit_hyst:
268			if (channel == 0)
269				return 0644;
270			break;
271		default:
272			break;
273		}
274		break;
275	default:
276			break;
277	}
278	return 0;
279}
280
281static const struct hwmon_ops sg2042_mcu_ops = {
282	.is_visible = sg2042_mcu_is_visible,
283	.read = sg2042_mcu_read,
284	.write = sg2042_mcu_write,
285};
286
287static const struct hwmon_chip_info sg2042_mcu_chip_info = {
288	.ops = &sg2042_mcu_ops,
289	.info = sg2042_mcu_info,
290};
291
292static void sg2042_mcu_debugfs_init(struct sg2042_mcu_data *mcu)
293{
294	debugfs_create_file("firmware_version", 0444, mcu->client->debugfs,
295			    mcu, &firmware_version_fops);
296	debugfs_create_file("pcb_version", 0444, mcu->client->debugfs, mcu,
297			    &pcb_version_fops);
298	debugfs_create_file("mcu_type", 0444, mcu->client->debugfs, mcu,
299			    &mcu_type_fops);
300	debugfs_create_file("board_type", 0444, mcu->client->debugfs, mcu,
301			    &board_type_fops);
302}
303
304static int sg2042_mcu_i2c_probe(struct i2c_client *client)
305{
306	struct device *dev = &client->dev;
307	struct sg2042_mcu_data *mcu;
308	struct device *hwmon_dev;
309
310	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
311						I2C_FUNC_SMBUS_BLOCK_DATA))
312		return -ENODEV;
313
314	mcu = devm_kmalloc(dev, sizeof(*mcu), GFP_KERNEL);
315	if (!mcu)
316		return -ENOMEM;
317
318	mutex_init(&mcu->mutex);
319	mcu->client = client;
320
321	i2c_set_clientdata(client, mcu);
322
323	hwmon_dev = devm_hwmon_device_register_with_info(dev, "sg2042_mcu",
324							 mcu,
325							 &sg2042_mcu_chip_info,
326							 NULL);
327	if (IS_ERR(hwmon_dev))
328		return PTR_ERR(hwmon_dev);
329
330	sg2042_mcu_debugfs_init(mcu);
331
332	return 0;
333}
334
335static const struct i2c_device_id sg2042_mcu_id[] = {
336	{ "sg2042-hwmon-mcu" },
337	{ }
338};
339MODULE_DEVICE_TABLE(i2c, sg2042_mcu_id);
340
341static const struct of_device_id sg2042_mcu_of_id[] = {
342	{ .compatible = "sophgo,sg2042-hwmon-mcu" },
343	{},
344};
345MODULE_DEVICE_TABLE(of, sg2042_mcu_of_id);
346
347static struct i2c_driver sg2042_mcu_driver = {
348	.driver = {
349		.name = "sg2042-mcu",
350		.of_match_table = sg2042_mcu_of_id,
351		.dev_groups = sg2042_mcu_groups,
352	},
353	.probe = sg2042_mcu_i2c_probe,
354	.id_table = sg2042_mcu_id,
355};
356module_i2c_driver(sg2042_mcu_driver);
357
358MODULE_AUTHOR("Inochi Amaoto <inochiama@outlook.com>");
359MODULE_DESCRIPTION("MCU I2C driver for SG2042 soc platform");
360MODULE_LICENSE("GPL");
~/ Read-only file preview.