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