| // 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[] = { |
| { "tsc1641", 0 }, |
| { } |
| }; |
| 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"); |