brintos

brintos / linux-shallow public Read only

0
0
Text · 8.8 KiB · 1410457 Raw
389 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (c) 2024 Inochi Amaoto <inochiama@outlook.com>4 *5 * Sophgo power control mcu for SG20426 */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				0x0019#define REG_MCU_FIRMWARE_VERSION		0x0120#define REG_PCB_VERSION				0x0221#define REG_PWR_CTRL				0x0322#define REG_SOC_TEMP				0x0423#define REG_BOARD_TEMP				0x0524#define REG_RST_COUNT				0x0a25#define REG_UPTIME				0x0b26#define REG_RESET_REASON			0x0d27#define REG_MCU_TYPE				0x1828#define REG_REPOWER_POLICY			0x6529#define REG_CRITICAL_TEMP			0x6630#define REG_REPOWER_TEMP			0x6731 32#define REPOWER_POLICY_REBOOT			133#define REPOWER_POLICY_KEEP_OFF			234 35#define MCU_POWER_MAX				0xff36 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	else121		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	else139		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	NULL165};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	NULL174};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	NULL182};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 from252	 * 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", 0 },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");389