| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Driver for ST Microelectronics TSC1641 I2C power monitor | |
| * | |
| * 60 V, 16-bit high-precision power monitor with I2C and MIPI I3C interface | |
| * Datasheet: https://www.st.com/resource/en/datasheet/tsc1641.pdf | |
| * | |
| * Copyright (C) 2025 Igor Reznichenko <igor@reznichenko.net> | |
| */ | |
| #include <linux/bitfield.h> | |
| #include <linux/bits.h> | |
| #include <linux/device.h> | |
| #include <linux/err.h> | |
| #include <linux/hwmon.h> | |
| #include <linux/i2c.h> | |
| #include <linux/module.h> | |
| #include <linux/regmap.h> | |
| #include <linux/sysfs.h> | |
| #include <linux/util_macros.h> | |
| /* I2C registers */ | |
| #define TSC1641_CONFIG 0x00 | |
| #define TSC1641_SHUNT_VOLTAGE 0x01 | |
| #define TSC1641_LOAD_VOLTAGE 0x02 | |
| #define TSC1641_POWER 0x03 | |
| #define TSC1641_CURRENT 0x04 | |
| #define TSC1641_TEMP 0x05 | |
| #define TSC1641_MASK 0x06 | |
| #define TSC1641_FLAG 0x07 | |
| #define TSC1641_RSHUNT 0x08 /* Shunt resistance */ | |
| #define TSC1641_SOL 0x09 | |
| #define TSC1641_SUL 0x0A | |
| #define TSC1641_LOL 0x0B | |
| #define TSC1641_LUL 0x0C | |
| #define TSC1641_POL 0x0D | |
| #define TSC1641_TOL 0x0E | |
| #define TSC1641_MANUF_ID 0xFE /* 0x0006 */ | |
| #define TSC1641_DIE_ID 0xFF /* 0x1000 */ | |
| #define TSC1641_MAX_REG 0xFF | |
| #define TSC1641_RSHUNT_DEFAULT 1000 /* 1mOhm */ | |
| #define TSC1641_CONFIG_DEFAULT 0x003F /* Default mode and temperature sensor */ | |
| #define TSC1641_MASK_DEFAULT 0xFC00 /* Unmask all alerts */ | |
| /* Bit mask for conversion time in the configuration register */ | |
| #define TSC1641_CONV_TIME_MASK GENMASK(7, 4) | |
| #define TSC1641_CONV_TIME_DEFAULT 1024 | |
| #define TSC1641_MIN_UPDATE_INTERVAL 1024 | |
| /* LSB value of different registers */ | |
| #define TSC1641_VLOAD_LSB_MVOLT 2 | |
| #define TSC1641_POWER_LSB_UWATT 25000 | |
| #define TSC1641_VSHUNT_LSB_NVOLT 2500 /* Use nanovolts to make it integer */ | |
| #define TSC1641_RSHUNT_LSB_UOHM 10 | |
| #define TSC1641_TEMP_LSB_MDEGC 500 | |
| /* Limits based on datasheet */ | |
| #define TSC1641_RSHUNT_MIN_UOHM 100 | |
| #define TSC1641_RSHUNT_MAX_UOHM 655350 | |
| #define TSC1641_CURR_ABS_MAX_MAMP 819200 /* Max current at 100uOhm*/ | |
| #define TSC1641_ALERT_POL_MASK BIT(1) | |
| #define TSC1641_ALERT_LATCH_EN_MASK BIT(0) | |
| /* Flags indicating alerts in TSC1641_FLAG register*/ | |
| #define TSC1641_SAT_FLAG BIT(13) | |
| #define TSC1641_SHUNT_OV_FLAG BIT(6) | |
| #define TSC1641_SHUNT_UV_FLAG BIT(5) | |
| #define TSC1641_LOAD_OV_FLAG BIT(4) | |
| #define TSC1641_LOAD_UV_FLAG BIT(3) | |
| #define TSC1641_POWER_OVER_FLAG BIT(2) | |
| #define TSC1641_TEMP_OVER_FLAG BIT(1) | |
| static bool tsc1641_writeable_reg(struct device *dev, unsigned int reg) | |
| { | |
| switch (reg) { | |
| case TSC1641_CONFIG: | |
| case TSC1641_MASK: | |
| case TSC1641_RSHUNT: | |
| case TSC1641_SOL: | |
| case TSC1641_SUL: | |
| case TSC1641_LOL: | |
| case TSC1641_LUL: | |
| case TSC1641_POL: | |
| case TSC1641_TOL: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| static bool tsc1641_volatile_reg(struct device *dev, unsigned int reg) | |
| { | |
| switch (reg) { | |
| case TSC1641_SHUNT_VOLTAGE: | |
| case TSC1641_LOAD_VOLTAGE: | |
| case TSC1641_POWER: | |
| case TSC1641_CURRENT: | |
| case TSC1641_TEMP: | |
| case TSC1641_FLAG: | |
| case TSC1641_MANUF_ID: | |
| case TSC1641_DIE_ID: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| static const struct regmap_config tsc1641_regmap_config = { | |
| .reg_bits = 8, | |
| .val_bits = 16, | |
| .use_single_write = true, | |
| .use_single_read = true, | |
| .max_register = TSC1641_MAX_REG, | |
| .cache_type = REGCACHE_MAPLE, | |
| .volatile_reg = tsc1641_volatile_reg, | |
| .writeable_reg = tsc1641_writeable_reg, | |
| }; | |
| struct tsc1641_data { | |
| long rshunt_uohm; | |
| long current_lsb_ua; | |
| struct regmap *regmap; | |
| }; | |
| /* | |
| * Upper limit due to chip 16-bit shunt register, lower limit to | |
| * prevent current and power registers overflow | |
| */ | |
| static inline int tsc1641_validate_shunt(u32 val) | |
| { | |
| if (val < TSC1641_RSHUNT_MIN_UOHM || val > TSC1641_RSHUNT_MAX_UOHM) | |
| return -EINVAL; | |
| return 0; | |
| } | |
| static int tsc1641_set_shunt(struct tsc1641_data *data, u32 val) | |
| { | |
| struct regmap *regmap = data->regmap; | |
| long rshunt_reg; | |
| /* RSHUNT register LSB is 10uOhm so need to divide further */ | |
| rshunt_reg = DIV_ROUND_CLOSEST(val, TSC1641_RSHUNT_LSB_UOHM); | |
| /* | |
| * Clamp value to the nearest multiple of TSC1641_RSHUNT_LSB_UOHM | |
| * in case shunt value provided was not a multiple | |
| */ | |
| data->rshunt_uohm = rshunt_reg * TSC1641_RSHUNT_LSB_UOHM; | |
| data->current_lsb_ua = DIV_ROUND_CLOSEST(TSC1641_VSHUNT_LSB_NVOLT * 1000, | |
| data->rshunt_uohm); | |
| return regmap_write(regmap, TSC1641_RSHUNT, rshunt_reg); | |
| } | |
| /* | |
| * Conversion times in uS, value in CONFIG[CT3:CT0] corresponds to index in this array | |
| * See "Table 14. CT3 to CT0: conversion time" in: | |
| * https://www.st.com/resource/en/datasheet/tsc1641.pdf | |
| */ | |
| static const int tsc1641_conv_times[] = { 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 }; | |
| static int tsc1641_reg_to_upd_interval(u16 config) | |
| { | |
| int idx = FIELD_GET(TSC1641_CONV_TIME_MASK, config); | |
| idx = clamp_val(idx, 0, ARRAY_SIZE(tsc1641_conv_times) - 1); | |
| int conv_time = tsc1641_conv_times[idx]; | |
| /* Don't support sub-millisecond update interval as it's not supported in hwmon */ | |
| conv_time = max(conv_time, TSC1641_MIN_UPDATE_INTERVAL); | |
| /* Return nearest value in milliseconds */ | |
| return DIV_ROUND_CLOSEST(conv_time, 1000); | |
| } | |
| static u16 tsc1641_upd_interval_to_reg(long interval) | |
| { | |
| /* Supported interval is 1ms - 33ms */ | |
| interval = clamp_val(interval, 1, 33); | |
| int conv = interval * 1000; | |
| int conv_bits = find_closest(conv, tsc1641_conv_times, | |
| ARRAY_SIZE(tsc1641_conv_times)); | |
| return FIELD_PREP(TSC1641_CONV_TIME_MASK, conv_bits); | |
| } | |
| static int tsc1641_chip_write(struct device *dev, u32 attr, long val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| switch (attr) { | |
| case hwmon_chip_update_interval: | |
| return regmap_update_bits(data->regmap, TSC1641_CONFIG, | |
| TSC1641_CONV_TIME_MASK, | |
| tsc1641_upd_interval_to_reg(val)); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| static int tsc1641_chip_read(struct device *dev, u32 attr, long *val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| u32 regval; | |
| int ret; | |
| switch (attr) { | |
| case hwmon_chip_update_interval: | |
| ret = regmap_read(data->regmap, TSC1641_CONFIG, ®val); | |
| if (ret) | |
| return ret; | |
| *val = tsc1641_reg_to_upd_interval(regval); | |
| return 0; | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| static int tsc1641_flag_read(struct regmap *regmap, u32 flag, long *val) | |
| { | |
| unsigned int regval; | |
| int ret; | |
| ret = regmap_read_bypassed(regmap, TSC1641_FLAG, ®val); | |
| if (ret) | |
| return ret; | |
| *val = !!(regval & flag); | |
| return 0; | |
| } | |
| static int tsc1641_in_read(struct device *dev, u32 attr, long *val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| struct regmap *regmap = data->regmap; | |
| unsigned int regval; | |
| int ret, reg; | |
| long sat_flag; | |
| switch (attr) { | |
| case hwmon_in_input: | |
| reg = TSC1641_LOAD_VOLTAGE; | |
| break; | |
| case hwmon_in_min: | |
| reg = TSC1641_LUL; | |
| break; | |
| case hwmon_in_max: | |
| reg = TSC1641_LOL; | |
| break; | |
| case hwmon_in_min_alarm: | |
| return tsc1641_flag_read(regmap, TSC1641_LOAD_UV_FLAG, val); | |
| case hwmon_in_max_alarm: | |
| return tsc1641_flag_read(regmap, TSC1641_LOAD_OV_FLAG, val); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| ret = regmap_read(regmap, reg, ®val); | |
| if (ret) | |
| return ret; | |
| /* Check if load voltage is out of range */ | |
| if (reg == TSC1641_LOAD_VOLTAGE) { | |
| /* Register is 15-bit max */ | |
| if (regval & 0x8000) | |
| return -ENODATA; | |
| ret = tsc1641_flag_read(regmap, TSC1641_SAT_FLAG, &sat_flag); | |
| if (ret) | |
| return ret; | |
| /* Out of range conditions per datasheet */ | |
| if (sat_flag && (regval == 0x7FFF || !regval)) | |
| return -ENODATA; | |
| } | |
| *val = regval * TSC1641_VLOAD_LSB_MVOLT; | |
| return 0; | |
| } | |
| /* Chip supports bidirectional (positive or negative) current */ | |
| static int tsc1641_curr_read(struct device *dev, u32 attr, long *val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| struct regmap *regmap = data->regmap; | |
| int regval; | |
| int ret, reg; | |
| long sat_flag; | |
| /* Current limits are the shunt under/over voltage limits */ | |
| switch (attr) { | |
| case hwmon_curr_input: | |
| reg = TSC1641_CURRENT; | |
| break; | |
| case hwmon_curr_min: | |
| reg = TSC1641_SUL; | |
| break; | |
| case hwmon_curr_max: | |
| reg = TSC1641_SOL; | |
| break; | |
| case hwmon_curr_min_alarm: | |
| return tsc1641_flag_read(regmap, TSC1641_SHUNT_UV_FLAG, val); | |
| case hwmon_curr_max_alarm: | |
| return tsc1641_flag_read(regmap, TSC1641_SHUNT_OV_FLAG, val); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| /* | |
| * Current uses shunt voltage, so check if it's out of range. | |
| * We report current register in sysfs to stay consistent with internal | |
| * power calculations which use current register values | |
| */ | |
| if (reg == TSC1641_CURRENT) { | |
| ret = regmap_read(regmap, TSC1641_SHUNT_VOLTAGE, ®val); | |
| if (ret) | |
| return ret; | |
| ret = tsc1641_flag_read(regmap, TSC1641_SAT_FLAG, &sat_flag); | |
| if (ret) | |
| return ret; | |
| if (sat_flag && (regval == 0x7FFF || regval == 0x8000)) | |
| return -ENODATA; | |
| } | |
| ret = regmap_read(regmap, reg, ®val); | |
| if (ret) | |
| return ret; | |
| /* Current in milliamps, signed */ | |
| *val = DIV_ROUND_CLOSEST((s16)regval * data->current_lsb_ua, 1000); | |
| return 0; | |
| } | |
| static int tsc1641_power_read(struct device *dev, u32 attr, long *val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| struct regmap *regmap = data->regmap; | |
| unsigned int regval; | |
| int ret, reg; | |
| switch (attr) { | |
| case hwmon_power_input: | |
| reg = TSC1641_POWER; | |
| break; | |
| case hwmon_power_max: | |
| reg = TSC1641_POL; | |
| break; | |
| case hwmon_power_max_alarm: | |
| return tsc1641_flag_read(regmap, TSC1641_POWER_OVER_FLAG, val); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| ret = regmap_read(regmap, reg, ®val); | |
| if (ret) | |
| return ret; | |
| *val = regval * TSC1641_POWER_LSB_UWATT; | |
| return 0; | |
| } | |
| static int tsc1641_temp_read(struct device *dev, u32 attr, long *val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| struct regmap *regmap = data->regmap; | |
| unsigned int regval; | |
| int ret, reg; | |
| switch (attr) { | |
| case hwmon_temp_input: | |
| reg = TSC1641_TEMP; | |
| break; | |
| case hwmon_temp_max: | |
| reg = TSC1641_TOL; | |
| break; | |
| case hwmon_temp_max_alarm: | |
| return tsc1641_flag_read(regmap, TSC1641_TEMP_OVER_FLAG, val); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| ret = regmap_read(regmap, reg, ®val); | |
| if (ret) | |
| return ret; | |
| /* 0x8000 means that TEMP measurement not enabled */ | |
| if (reg == TSC1641_TEMP && regval == 0x8000) | |
| return -ENODATA; | |
| /* Both temperature and limit registers are signed */ | |
| *val = (s16)regval * TSC1641_TEMP_LSB_MDEGC; | |
| return 0; | |
| } | |
| static int tsc1641_in_write(struct device *dev, u32 attr, long val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| struct regmap *regmap = data->regmap; | |
| unsigned int regval; | |
| int reg; | |
| switch (attr) { | |
| case hwmon_in_min: | |
| reg = TSC1641_LUL; | |
| break; | |
| case hwmon_in_max: | |
| reg = TSC1641_LOL; | |
| break; | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| /* Clamp to full register range */ | |
| val = clamp_val(val, 0, TSC1641_VLOAD_LSB_MVOLT * USHRT_MAX); | |
| regval = DIV_ROUND_CLOSEST(val, TSC1641_VLOAD_LSB_MVOLT); | |
| return regmap_write(regmap, reg, regval); | |
| } | |
| static int tsc1641_curr_write(struct device *dev, u32 attr, long val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| struct regmap *regmap = data->regmap; | |
| int reg, regval; | |
| switch (attr) { | |
| case hwmon_curr_min: | |
| reg = TSC1641_SUL; | |
| break; | |
| case hwmon_curr_max: | |
| reg = TSC1641_SOL; | |
| break; | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| /* Clamp to prevent over/underflow below */ | |
| val = clamp_val(val, -TSC1641_CURR_ABS_MAX_MAMP, TSC1641_CURR_ABS_MAX_MAMP); | |
| /* Convert val in milliamps to register */ | |
| regval = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb_ua); | |
| /* | |
| * Prevent signed 16-bit overflow. | |
| * Integer arithmetic and shunt scaling can quantize values near 0x7FFF/0x8000, | |
| * so reading and writing back may not preserve the exact original register value. | |
| */ | |
| regval = clamp_val(regval, SHRT_MIN, SHRT_MAX); | |
| /* SUL and SOL registers are signed */ | |
| return regmap_write(regmap, reg, regval & 0xFFFF); | |
| } | |
| static int tsc1641_power_write(struct device *dev, u32 attr, long val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| struct regmap *regmap = data->regmap; | |
| unsigned int regval; | |
| switch (attr) { | |
| case hwmon_power_max: | |
| /* Clamp to full register range */ | |
| val = clamp_val(val, 0, TSC1641_POWER_LSB_UWATT * USHRT_MAX); | |
| regval = DIV_ROUND_CLOSEST(val, TSC1641_POWER_LSB_UWATT); | |
| return regmap_write(regmap, TSC1641_POL, regval); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| static int tsc1641_temp_write(struct device *dev, u32 attr, long val) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| struct regmap *regmap = data->regmap; | |
| int regval; | |
| switch (attr) { | |
| case hwmon_temp_max: | |
| /* Clamp to full register range */ | |
| val = clamp_val(val, TSC1641_TEMP_LSB_MDEGC * SHRT_MIN, | |
| TSC1641_TEMP_LSB_MDEGC * SHRT_MAX); | |
| regval = DIV_ROUND_CLOSEST(val, TSC1641_TEMP_LSB_MDEGC); | |
| /* TOL register is signed */ | |
| return regmap_write(regmap, TSC1641_TOL, regval & 0xFFFF); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| static umode_t tsc1641_is_visible(const void *data, enum hwmon_sensor_types type, | |
| u32 attr, int channel) | |
| { | |
| switch (type) { | |
| case hwmon_chip: | |
| switch (attr) { | |
| case hwmon_chip_update_interval: | |
| return 0644; | |
| default: | |
| break; | |
| } | |
| break; | |
| case hwmon_in: | |
| switch (attr) { | |
| case hwmon_in_input: | |
| return 0444; | |
| case hwmon_in_min: | |
| case hwmon_in_max: | |
| return 0644; | |
| case hwmon_in_min_alarm: | |
| case hwmon_in_max_alarm: | |
| return 0444; | |
| default: | |
| break; | |
| } | |
| break; | |
| case hwmon_curr: | |
| switch (attr) { | |
| case hwmon_curr_input: | |
| return 0444; | |
| case hwmon_curr_min: | |
| case hwmon_curr_max: | |
| return 0644; | |
| case hwmon_curr_min_alarm: | |
| case hwmon_curr_max_alarm: | |
| return 0444; | |
| default: | |
| break; | |
| } | |
| break; | |
| case hwmon_power: | |
| switch (attr) { | |
| case hwmon_power_input: | |
| return 0444; | |
| case hwmon_power_max: | |
| return 0644; | |
| case hwmon_power_max_alarm: | |
| return 0444; | |
| default: | |
| break; | |
| } | |
| break; | |
| case hwmon_temp: | |
| switch (attr) { | |
| case hwmon_temp_input: | |
| return 0444; | |
| case hwmon_temp_max: | |
| return 0644; | |
| case hwmon_temp_max_alarm: | |
| return 0444; | |
| default: | |
| break; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| return 0; | |
| } | |
| static int tsc1641_read(struct device *dev, enum hwmon_sensor_types type, | |
| u32 attr, int channel, long *val) | |
| { | |
| switch (type) { | |
| case hwmon_chip: | |
| return tsc1641_chip_read(dev, attr, val); | |
| case hwmon_in: | |
| return tsc1641_in_read(dev, attr, val); | |
| case hwmon_curr: | |
| return tsc1641_curr_read(dev, attr, val); | |
| case hwmon_power: | |
| return tsc1641_power_read(dev, attr, val); | |
| case hwmon_temp: | |
| return tsc1641_temp_read(dev, attr, val); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| static int tsc1641_write(struct device *dev, enum hwmon_sensor_types type, | |
| u32 attr, int channel, long val) | |
| { | |
| switch (type) { | |
| case hwmon_chip: | |
| return tsc1641_chip_write(dev, attr, val); | |
| case hwmon_in: | |
| return tsc1641_in_write(dev, attr, val); | |
| case hwmon_curr: | |
| return tsc1641_curr_write(dev, attr, val); | |
| case hwmon_power: | |
| return tsc1641_power_write(dev, attr, val); | |
| case hwmon_temp: | |
| return tsc1641_temp_write(dev, attr, val); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| static const struct hwmon_channel_info * const tsc1641_info[] = { | |
| HWMON_CHANNEL_INFO(chip, | |
| HWMON_C_UPDATE_INTERVAL), | |
| HWMON_CHANNEL_INFO(in, | |
| HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MAX_ALARM | | |
| HWMON_I_MIN | HWMON_I_MIN_ALARM), | |
| HWMON_CHANNEL_INFO(curr, | |
| HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM | | |
| HWMON_C_MIN | HWMON_C_MIN_ALARM), | |
| HWMON_CHANNEL_INFO(power, | |
| HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM), | |
| HWMON_CHANNEL_INFO(temp, | |
| HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM), | |
| NULL | |
| }; | |
| static ssize_t shunt_resistor_show(struct device *dev, | |
| struct device_attribute *da, char *buf) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%li\n", data->rshunt_uohm); | |
| } | |
| static ssize_t shunt_resistor_store(struct device *dev, | |
| struct device_attribute *da, | |
| const char *buf, size_t count) | |
| { | |
| struct tsc1641_data *data = dev_get_drvdata(dev); | |
| unsigned int val; | |
| int ret; | |
| ret = kstrtouint(buf, 10, &val); | |
| if (ret < 0) | |
| return ret; | |
| ret = tsc1641_validate_shunt(val); | |
| if (ret < 0) | |
| return ret; | |
| ret = tsc1641_set_shunt(data, val); | |
| if (ret < 0) | |
| return ret; | |
| return count; | |
| } | |
| static const struct hwmon_ops tsc1641_hwmon_ops = { | |
| .is_visible = tsc1641_is_visible, | |
| .read = tsc1641_read, | |
| .write = tsc1641_write, | |
| }; | |
| static const struct hwmon_chip_info tsc1641_chip_info = { | |
| .ops = &tsc1641_hwmon_ops, | |
| .info = tsc1641_info, | |
| }; | |
| static DEVICE_ATTR_RW(shunt_resistor); | |
| /* Shunt resistor value is exposed via sysfs attribute */ | |
| static struct attribute *tsc1641_attrs[] = { | |
| &dev_attr_shunt_resistor.attr, | |
| NULL, | |
| }; | |
| ATTRIBUTE_GROUPS(tsc1641); | |
| static int tsc1641_init(struct device *dev, struct tsc1641_data *data) | |
| { | |
| struct regmap *regmap = data->regmap; | |
| bool active_high; | |
| u32 shunt; | |
| int ret; | |
| if (device_property_read_u32(dev, "shunt-resistor-micro-ohms", &shunt) < 0) | |
| shunt = TSC1641_RSHUNT_DEFAULT; | |
| if (tsc1641_validate_shunt(shunt) < 0) { | |
| dev_err(dev, "invalid shunt resistor value %u\n", shunt); | |
| return -EINVAL; | |
| } | |
| ret = tsc1641_set_shunt(data, shunt); | |
| if (ret < 0) | |
| return ret; | |
| ret = regmap_write(regmap, TSC1641_CONFIG, TSC1641_CONFIG_DEFAULT); | |
| if (ret < 0) | |
| return ret; | |
| active_high = device_property_read_bool(dev, "st,alert-polarity-active-high"); | |
| return regmap_write(regmap, TSC1641_MASK, TSC1641_MASK_DEFAULT | | |
| FIELD_PREP(TSC1641_ALERT_POL_MASK, active_high)); | |
| } | |
| static int tsc1641_probe(struct i2c_client *client) | |
| { | |
| struct device *dev = &client->dev; | |
| struct tsc1641_data *data; | |
| struct device *hwmon_dev; | |
| int ret; | |
| data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | |
| if (!data) | |
| return -ENOMEM; | |
| data->regmap = devm_regmap_init_i2c(client, &tsc1641_regmap_config); | |
| if (IS_ERR(data->regmap)) | |
| return dev_err_probe(dev, PTR_ERR(data->regmap), | |
| "failed to allocate register map\n"); | |
| ret = tsc1641_init(dev, data); | |
| if (ret < 0) | |
| return dev_err_probe(dev, ret, "failed to configure device\n"); | |
| hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, | |
| data, &tsc1641_chip_info, tsc1641_groups); | |
| if (IS_ERR(hwmon_dev)) | |
| return PTR_ERR(hwmon_dev); | |
| dev_info(dev, "power monitor %s (Rshunt = %li uOhm)\n", | |
| client->name, data->rshunt_uohm); | |
| return 0; | |
| } | |
| static const struct i2c_device_id tsc1641_id[] = { | |
| { .name = "tsc1641" }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(i2c, tsc1641_id); | |
| static const struct of_device_id __maybe_unused tsc1641_of_match[] = { | |
| { .compatible = "st,tsc1641" }, | |
| { }, | |
| }; | |
| MODULE_DEVICE_TABLE(of, tsc1641_of_match); | |
| static struct i2c_driver tsc1641_driver = { | |
| .driver = { | |
| .name = "tsc1641", | |
| .of_match_table = of_match_ptr(tsc1641_of_match), | |
| }, | |
| .probe = tsc1641_probe, | |
| .id_table = tsc1641_id, | |
| }; | |
| module_i2c_driver(tsc1641_driver); | |
| MODULE_AUTHOR("Igor Reznichenko <igor@reznichenko.net>"); | |
| MODULE_DESCRIPTION("tsc1641 driver"); | |
| MODULE_LICENSE("GPL"); |