summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/hwmon/index.rst1
-rw-r--r--Documentation/hwmon/tsc1641.rst87
-rw-r--r--drivers/hwmon/Kconfig12
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/tsc1641.c748
5 files changed, 849 insertions, 0 deletions
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 45a26927e64f..d1b80bfd0fd1 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -256,6 +256,7 @@ Hardware Monitoring Kernel Drivers
tps40422
tps53679
tps546d24
+ tsc1641
twl4030-madc-hwmon
ucd9000
ucd9200
diff --git a/Documentation/hwmon/tsc1641.rst b/Documentation/hwmon/tsc1641.rst
new file mode 100644
index 000000000000..425e25f7a7d1
--- /dev/null
+++ b/Documentation/hwmon/tsc1641.rst
@@ -0,0 +1,87 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+Kernel driver tsc1641
+=====================
+
+Supported chips:
+
+ * ST TSC1641
+
+ Prefix: 'tsc1641'
+
+ Addresses scanned: -
+
+ Datasheet:
+ https://www.st.com/resource/en/datasheet/tsc1641.pdf
+
+Author:
+ - Igor Reznichenko <igor@reznichenko.net>
+
+
+Description
+-----------
+
+The TSC1641 is a high-precision current, voltage, power, and temperature
+monitoring analog front-end (AFE). It monitors bidirectional current into a
+shunt resistor and load voltage up to 60 V in a synchronized way. Digital bus
+interface is I2C/SMbus. The TSC1641 allows the assertion of several alerts
+regarding the voltage, current, power and temperature.
+
+Usage Notes
+-----------
+
+The TSC1641 driver requires the value of the external shunt resistor to
+correctly compute current and power measurements. The resistor value, in
+micro-ohms, should be provided either through the device tree property
+"shunt-resistor-micro-ohms" or via writable sysfs attribute "shunt_resistor".
+Please refer to the Documentation/devicetree/bindings/hwmon/st,tsc1641.yaml
+for bindings if the device tree is used.
+
+Supported range of shunt resistor values is from 100 uOhm to 655.35 mOhm, in
+10 uOhm steps.
+When selecting the value keep in mind device maximum DC power measurement is
+1600W. See datasheet p.22 for ST recommendations on selecting shunt value.
+
+If the shunt resistor value is not specified in the device tree, the driver
+initializes it to 1000 uOhm by default. Users may configure the correct shunt
+resistor value at runtime by writing to the "shunt_resistor" sysfs attribute.
+
+The driver only supports continuous operating mode.
+Measurement ranges:
+
+================ ===============================================================
+Current Bidirectional, dependent on shunt
+Bus voltage 0-60V
+Maximum DC power 1600W
+Temperature -40C to +125C
+================ ===============================================================
+
+Sysfs entries
+-------------
+
+==================== ===========================================================
+in0_input bus voltage (mV)
+in0_max bus voltage max alarm limit (mV)
+in0_max_alarm bus voltage max alarm limit exceeded
+in0_min bus voltage min alarm limit (mV)
+in0_min_alarm bus voltage min alarm limit exceeded
+
+curr1_input current measurement (mA)
+curr1_max current max alarm limit (mA)
+curr1_max_alarm current max alarm limit exceeded
+curr1_min current min alarm limit (mA)
+curr1_min_alarm current min alarm limit exceeded
+
+power1_input power measurement (uW)
+power1_max power max alarm limit (uW)
+power1_max_alarm power max alarm limit exceeded
+
+shunt_resistor shunt resistor value (uOhms)
+
+temp1_input temperature measurement (mdegC)
+temp1_max temperature max alarm limit (mdegC)
+temp1_max_alarm temperature max alarm limit exceeded
+
+update_interval data conversion time (1 - 33ms), longer conversion time
+ corresponds to higher effective resolution in bits
+==================== =========================================================== \ No newline at end of file
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index adf957540050..8c852bcac26f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2434,6 +2434,18 @@ config SENSORS_TMP513
This driver can also be built as a module. If so, the module
will be called tmp513.
+config SENSORS_TSC1641
+ tristate "ST Microelectronics TSC1641 Power Monitor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for TSC1641 power monitor chip.
+ The TSC1641 driver is configured for the default configuration of
+ the part except temperature is enabled by default.
+
+ This driver can also be built as a module. If so, the module
+ will be called tsc1641.
+
config SENSORS_VEXPRESS
tristate "Versatile Express"
depends on VEXPRESS_CONFIG
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 73b2abdcc6dd..a8de5bc69f2a 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -233,6 +233,7 @@ obj-$(CONFIG_SENSORS_TMP401) += tmp401.o
obj-$(CONFIG_SENSORS_TMP421) += tmp421.o
obj-$(CONFIG_SENSORS_TMP464) += tmp464.o
obj-$(CONFIG_SENSORS_TMP513) += tmp513.o
+obj-$(CONFIG_SENSORS_TSC1641) += tsc1641.o
obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress-hwmon.o
obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o
obj-$(CONFIG_SENSORS_VIA686A) += via686a.o
diff --git a/drivers/hwmon/tsc1641.c b/drivers/hwmon/tsc1641.c
new file mode 100644
index 000000000000..2b5d34bab146
--- /dev/null
+++ b/drivers/hwmon/tsc1641.c
@@ -0,0 +1,748 @@
+// 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, &regval);
+ 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, &regval);
+ 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, &regval);
+ 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, &regval);
+ 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, &regval);
+ 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, &regval);
+ 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, &regval);
+ 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");