* [PATCH v2 2/2] hwmon: (ina238) Add support for INA780
2025-08-08 3:05 [PATCH v2 0/2] hwmon: Add support for INA780 Chris Packham
2025-08-08 3:05 ` [PATCH v2 1/2] dt-bindings: hwmon: ti,ina2xx: Add INA780 device Chris Packham
@ 2025-08-08 3:05 ` Chris Packham
2025-08-27 16:04 ` Guenter Roeck
2025-08-28 12:09 ` Guenter Roeck
1 sibling, 2 replies; 10+ messages in thread
From: Chris Packham @ 2025-08-08 3:05 UTC (permalink / raw)
To: jdelvare, linux, robh, krzk+dt, conor+dt
Cc: linux-hwmon, devicetree, linux-kernel, Chris Packham
Add support for the TI INA780 Digital Power Monitor. The INA780 uses
EZShunt(tm) technology, which means there are fixed LSB conversions for
a number of fields rather than needing to be calibrated.
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
---
Documentation/hwmon/ina238.rst | 20 +++
drivers/hwmon/ina238.c | 258 ++++++++++++++++++++++++++-------
2 files changed, 226 insertions(+), 52 deletions(-)
diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst
index 9a24da4786a4..220d36d5c947 100644
--- a/Documentation/hwmon/ina238.rst
+++ b/Documentation/hwmon/ina238.rst
@@ -14,6 +14,11 @@ Supported chips:
Datasheet:
https://www.ti.com/lit/gpn/ina238
+ * Texas Instruments INA780
+
+ Datasheet:
+ https://www.ti.com/product/ina780a
+
* Silergy SQ52206
Prefix: 'SQ52206'
@@ -69,3 +74,18 @@ energy1_input Energy measurement (uJ)
power1_input_highest Peak Power (uW)
======================= =======================================================
+
+Sysfs differences for ina780
+----------------------------
+
+======================= =======================================================
+in0_input Bus voltage (mV)
+in0_min Minimum bus voltage threshold (mV)
+in0_min_alarm Minimum shunt voltage alarm
+in0_max Maximum bus voltage threshold (mV)
+in0_max_alarm Maximum shunt voltage alarm
+curr1_max Maximum current threshold
+curr1_max_alarm Maximum current alarm
+curr1_min Minimum current threshold
+curr1_min_alarm Minimum current alarm
+======================= =======================================================
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c
index a2cb615fa278..424efa99c541 100644
--- a/drivers/hwmon/ina238.c
+++ b/drivers/hwmon/ina238.c
@@ -2,6 +2,7 @@
/*
* Driver for Texas Instruments INA238 power monitor chip
* Datasheet: https://www.ti.com/product/ina238
+ * https://www.ti.com/product/ina780a
*
* Copyright (C) 2021 Nathan Rossi <nathan.rossi@digi.com>
*/
@@ -32,6 +33,8 @@
#define INA238_DIAG_ALERT 0xb
#define INA238_SHUNT_OVER_VOLTAGE 0xc
#define INA238_SHUNT_UNDER_VOLTAGE 0xd
+#define INA780_COL 0xc
+#define INA780_CUL 0xd
#define INA238_BUS_OVER_VOLTAGE 0xe
#define INA238_BUS_UNDER_VOLTAGE 0xf
#define INA238_TEMP_LIMIT 0x10
@@ -49,6 +52,8 @@
#define INA238_DIAG_ALERT_BUSOL BIT(4)
#define INA238_DIAG_ALERT_BUSUL BIT(3)
#define INA238_DIAG_ALERT_POL BIT(2)
+#define INA780_DIAG_ALERT_CURRENTOL BIT(6)
+#define INA780_DIAG_ALERT_CURRENTUL BIT(5)
#define INA238_REGISTERS 0x20
@@ -108,22 +113,29 @@
#define SQ52206_BUS_VOLTAGE_LSB 3750 /* 3.75 mV/lsb */
#define SQ52206_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
+#define INA780_CURRENT_LSB 2400 /* 2.4 mA/lsb */
+#define INA780_ENERGY_LSB 7680 /* 7.68 mJ/lsb */
+
static const struct regmap_config ina238_regmap_config = {
.max_register = INA238_REGISTERS,
.reg_bits = 8,
.val_bits = 16,
};
-enum ina238_ids { ina238, ina237, sq52206 };
+enum ina238_ids { ina238, ina237, sq52206, ina780 };
struct ina238_config {
+ bool has_shunt; /* has shunt resistor */
bool has_power_highest; /* chip detection power peak */
bool has_energy; /* chip detection energy */
+ bool has_curr_min_max; /* supports COL/CUL */
u8 temp_shift; /* fixed parameters for temp calculate */
u32 power_calculate_factor; /* fixed parameters for power calculate */
u16 config_default; /* Power-on default state */
int bus_voltage_lsb; /* use for temperature calculate, uV/lsb */
int temp_lsb; /* use for temperature calculate */
+ int temp_max; /* maximum configurable temp limit in mC */
+ int fixed_power_lsb;
};
struct ina238_data {
@@ -137,6 +149,7 @@ struct ina238_data {
static const struct ina238_config ina238_config[] = {
[ina238] = {
+ .has_shunt = true,
.has_energy = false,
.has_power_highest = false,
.temp_shift = 4,
@@ -144,8 +157,10 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_lsb = INA238_DIE_TEMP_LSB,
+ .temp_max = 125000,
},
[ina237] = {
+ .has_shunt = true,
.has_energy = false,
.has_power_highest = false,
.temp_shift = 4,
@@ -153,8 +168,10 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_lsb = INA238_DIE_TEMP_LSB,
+ .temp_max = 125000,
},
[sq52206] = {
+ .has_shunt = true,
.has_energy = true,
.has_power_highest = true,
.temp_shift = 0,
@@ -162,7 +179,20 @@ static const struct ina238_config ina238_config[] = {
.config_default = SQ52206_CONFIG_DEFAULT,
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
.temp_lsb = SQ52206_DIE_TEMP_LSB,
+ .temp_max = 125000,
},
+ [ina780] = {
+ .has_shunt = false,
+ .has_energy = true,
+ .has_power_highest = false,
+ .temp_shift = 4,
+ .config_default = INA238_CONFIG_DEFAULT,
+ .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
+ .temp_lsb = INA238_DIE_TEMP_LSB,
+ .temp_max = 150000,
+ .fixed_power_lsb = 480,
+ .has_curr_min_max = true,
+ }
};
static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val)
@@ -203,12 +233,12 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
+ bool has_shunt = data->config->has_shunt;
int reg, mask;
int regval;
int err;
- switch (channel) {
- case 0:
+ if (has_shunt && channel == 0) {
switch (attr) {
case hwmon_in_input:
reg = INA238_SHUNT_VOLTAGE;
@@ -230,8 +260,7 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
default:
return -EOPNOTSUPP;
}
- break;
- case 1:
+ } else {
switch (attr) {
case hwmon_in_input:
reg = INA238_BUS_VOLTAGE;
@@ -253,9 +282,6 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
default:
return -EOPNOTSUPP;
}
- break;
- default:
- return -EOPNOTSUPP;
}
err = regmap_read(data->regmap, reg, ®val);
@@ -268,7 +294,7 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
case hwmon_in_min:
/* signed register, value in mV */
regval = (s16)regval;
- if (channel == 0)
+ if (has_shunt && channel == 0)
/* gain of 1 -> LSB / 4 */
*val = (regval * INA238_SHUNT_VOLTAGE_LSB) *
data->gain / (1000 * 4);
@@ -288,14 +314,14 @@ static int ina238_write_in(struct device *dev, u32 attr, int channel,
long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
+ bool has_shunt = data->config->has_shunt;
int regval;
if (attr != hwmon_in_max && attr != hwmon_in_min)
return -EOPNOTSUPP;
/* convert decimal to register value */
- switch (channel) {
- case 0:
+ if (has_shunt && channel == 0) {
/* signed value, clamp to max range +/-163 mV */
regval = clamp_val(val, -163, 163);
regval = (regval * 1000 * 4) /
@@ -312,7 +338,7 @@ static int ina238_write_in(struct device *dev, u32 attr, int channel,
default:
return -EOPNOTSUPP;
}
- case 1:
+ } else {
/* signed value, positive values only. Clamp to max 102.396 V */
regval = clamp_val(val, 0, 102396);
regval = (regval * 1000) / data->config->bus_voltage_lsb;
@@ -328,8 +354,6 @@ static int ina238_write_in(struct device *dev, u32 attr, int channel,
default:
return -EOPNOTSUPP;
}
- default:
- return -EOPNOTSUPP;
}
}
@@ -356,9 +380,86 @@ static int ina238_read_current(struct device *dev, u32 attr, long *val)
return 0;
}
+static int ina780_read_current(struct device *dev, u32 attr, long *val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ unsigned int regval;
+ int reg, mask;
+ int err;
+
+ switch (attr) {
+ case hwmon_curr_input:
+ reg = INA238_CURRENT;
+ break;
+ case hwmon_curr_max:
+ reg = INA780_COL;
+ break;
+ case hwmon_curr_min:
+ reg = INA780_CUL;
+ break;
+ case hwmon_curr_max_alarm:
+ reg = INA238_DIAG_ALERT;
+ mask = INA780_DIAG_ALERT_CURRENTOL;
+ break;
+ case hwmon_curr_min_alarm:
+ reg = INA238_DIAG_ALERT;
+ mask = INA780_DIAG_ALERT_CURRENTUL;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ switch (attr) {
+ case hwmon_curr_input:
+ case hwmon_curr_max:
+ case hwmon_curr_min:
+ err = regmap_read(data->regmap, reg, ®val);
+ if (err)
+ return err;
+ *val = div_s64((s16)regval * INA780_CURRENT_LSB, 1000);
+ break;
+ case hwmon_curr_max_alarm:
+ case hwmon_curr_min_alarm:
+ err = regmap_read(data->regmap, reg, ®val);
+ if (err)
+ return err;
+ *val = !!(regval & mask);
+ break;
+ }
+
+ return 0;
+}
+
+static int ina780_write_current(struct device *dev, u32 attr, long val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ bool has_curr_min_max = data->config->has_curr_min_max;
+ unsigned int regval;
+ int reg;
+
+ if (!has_curr_min_max)
+ return -EOPNOTSUPP;
+
+ switch (attr) {
+ case hwmon_curr_max:
+ reg = INA780_COL;
+ break;
+ case hwmon_curr_min:
+ reg = INA780_CUL;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ clamp_val(val, -78643, 78640);
+ regval = div_s64(val * 1000ULL, INA780_CURRENT_LSB);
+
+ return regmap_write(data->regmap, reg, regval);
+}
+
static int ina238_read_power(struct device *dev, u32 attr, long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
+ long long fixed_power_lsb = data->config->fixed_power_lsb;
long long power;
int regval;
int err;
@@ -369,9 +470,14 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
if (err)
return err;
- /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
- power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain *
- data->config->power_calculate_factor, 4 * 100 * data->rshunt);
+ if (fixed_power_lsb)
+ power = div_u64(regval * fixed_power_lsb, 1000);
+ else
+ /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
+ power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain *
+ data->config->power_calculate_factor,
+ 4 * 100 * data->rshunt);
+
/* Clamp value to maximum value of long */
*val = clamp_val(power, 0, LONG_MAX);
break;
@@ -395,8 +501,12 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
* Truncated 24-bit compare register, lower 8-bits are
* truncated. Same conversion to/from uW as POWER register.
*/
- power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * data->gain *
- data->config->power_calculate_factor, 4 * 100 * data->rshunt);
+ if (fixed_power_lsb)
+ power = div_u64((regval << 8) * fixed_power_lsb * 256, 1000);
+ else
+ power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * data->gain *
+ data->config->power_calculate_factor,
+ 4 * 100 * data->rshunt);
/* Clamp value to maximum value of long */
*val = clamp_val(power, 0, LONG_MAX);
break;
@@ -417,6 +527,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
static int ina238_write_power(struct device *dev, u32 attr, long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
+ int fixed_power_lsb = data->config->fixed_power_lsb;
long regval;
if (attr != hwmon_power_max)
@@ -428,8 +539,12 @@ static int ina238_write_power(struct device *dev, u32 attr, long val)
* register.
*/
regval = clamp_val(val, 0, LONG_MAX);
- regval = div_u64(val * 4 * 100 * data->rshunt, data->config->power_calculate_factor *
- 1000ULL * INA238_FIXED_SHUNT * data->gain);
+ if (fixed_power_lsb)
+ regval = div_u64(val * 1000ULL, fixed_power_lsb);
+ else
+ regval = div_u64(val * 4 * 100 * data->rshunt,
+ data->config->power_calculate_factor *
+ 1000ULL * INA238_FIXED_SHUNT * data->gain);
regval = clamp_val(regval >> 8, 0, U16_MAX);
return regmap_write(data->regmap, INA238_POWER_LIMIT, regval);
@@ -481,7 +596,7 @@ static int ina238_write_temp(struct device *dev, u32 attr, long val)
return -EOPNOTSUPP;
/* Signed */
- val = clamp_val(val, -40000, 125000);
+ val = clamp_val(val, -40000, data->config->temp_max);
regval = div_s64(val * 10000, data->config->temp_lsb) << data->config->temp_shift;
regval = clamp_val(regval, S16_MIN, S16_MAX) & (0xffff << data->config->temp_shift);
@@ -492,6 +607,7 @@ static ssize_t energy1_input_show(struct device *dev,
struct device_attribute *da, char *buf)
{
struct ina238_data *data = dev_get_drvdata(dev);
+ bool has_shunt = data->config->has_shunt;
int ret;
u64 regval;
u64 energy;
@@ -501,8 +617,11 @@ static ssize_t energy1_input_show(struct device *dev,
return ret;
/* result in uJ */
- energy = div_u64(regval * INA238_FIXED_SHUNT * data->gain * 16 * 10 *
- data->config->power_calculate_factor, 4 * data->rshunt);
+ if (has_shunt)
+ energy = div_u64(regval * INA238_FIXED_SHUNT * data->gain * 16 * 10 *
+ data->config->power_calculate_factor, 4 * data->rshunt);
+ else
+ energy = div_u64(regval * INA780_ENERGY_LSB, 1000);
return sysfs_emit(buf, "%llu\n", energy);
}
@@ -510,11 +629,17 @@ static ssize_t energy1_input_show(struct device *dev,
static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ bool has_curr_min_max = data->config->has_curr_min_max;
+
switch (type) {
case hwmon_in:
return ina238_read_in(dev, attr, channel, val);
case hwmon_curr:
- return ina238_read_current(dev, attr, val);
+ if (has_curr_min_max)
+ return ina780_read_current(dev, attr, val);
+ else
+ return ina238_read_current(dev, attr, val);
case hwmon_power:
return ina238_read_power(dev, attr, val);
case hwmon_temp:
@@ -537,6 +662,9 @@ static int ina238_write(struct device *dev, enum hwmon_sensor_types type,
case hwmon_in:
err = ina238_write_in(dev, attr, channel, val);
break;
+ case hwmon_curr:
+ err = ina780_write_current(dev, attr, val);
+ break;
case hwmon_power:
err = ina238_write_power(dev, attr, val);
break;
@@ -558,6 +686,8 @@ static umode_t ina238_is_visible(const void *drvdata,
{
const struct ina238_data *data = drvdata;
bool has_power_highest = data->config->has_power_highest;
+ bool has_curr_min_max = data->config->has_curr_min_max;
+ bool has_shunt = data->config->has_shunt;
switch (type) {
case hwmon_in:
@@ -565,10 +695,14 @@ static umode_t ina238_is_visible(const void *drvdata,
case hwmon_in_input:
case hwmon_in_max_alarm:
case hwmon_in_min_alarm:
- return 0444;
+ if (channel == 0 || has_shunt)
+ return 0444;
+ return 0;
case hwmon_in_max:
case hwmon_in_min:
- return 0644;
+ if (channel == 0 || has_shunt)
+ return 0644;
+ return 0;
default:
return 0;
}
@@ -576,6 +710,13 @@ static umode_t ina238_is_visible(const void *drvdata,
switch (attr) {
case hwmon_curr_input:
return 0444;
+ case hwmon_curr_max:
+ case hwmon_curr_min:
+ case hwmon_curr_max_alarm:
+ case hwmon_curr_min_alarm:
+ if (has_curr_min_max)
+ return 0644;
+ return 0;
default:
return 0;
}
@@ -614,13 +755,16 @@ static umode_t ina238_is_visible(const void *drvdata,
static const struct hwmon_channel_info * const ina238_info[] = {
HWMON_CHANNEL_INFO(in,
- /* 0: shunt voltage */
+ /* 0: shunt voltage (bus voltage for ina780)*/
INA238_HWMON_IN_CONFIG,
- /* 1: bus voltage */
+ /* 1: bus voltage (not present on ina780) */
INA238_HWMON_IN_CONFIG),
HWMON_CHANNEL_INFO(curr,
/* 0: current through shunt */
- HWMON_C_INPUT),
+ HWMON_C_INPUT |
+ /* current limits avialble on ina780*/
+ HWMON_C_MAX | HWMON_C_MAX_ALARM |
+ HWMON_C_MIN | HWMON_C_MIN_ALARM),
HWMON_CHANNEL_INFO(power,
/* 0: power */
HWMON_P_INPUT | HWMON_P_MAX |
@@ -679,21 +823,23 @@ static int ina238_probe(struct i2c_client *client)
return PTR_ERR(data->regmap);
}
- /* load shunt value */
- data->rshunt = INA238_RSHUNT_DEFAULT;
- if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0 && pdata)
- data->rshunt = pdata->shunt_uohms;
- if (data->rshunt == 0) {
- dev_err(dev, "invalid shunt resister value %u\n", data->rshunt);
- return -EINVAL;
- }
+ if (data->config->has_shunt) {
+ /* load shunt value */
+ data->rshunt = INA238_RSHUNT_DEFAULT;
+ if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0 && pdata)
+ data->rshunt = pdata->shunt_uohms;
+ if (data->rshunt == 0) {
+ dev_err(dev, "invalid shunt resister value %u\n", data->rshunt);
+ return -EINVAL;
+ }
- /* load shunt gain value */
- if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
- data->gain = 4; /* Default of ADCRANGE = 0 */
- if (data->gain != 1 && data->gain != 2 && data->gain != 4) {
- dev_err(dev, "invalid shunt gain value %u\n", data->gain);
- return -EINVAL;
+ /* load shunt gain value */
+ if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
+ data->gain = 4; /* Default of ADCRANGE = 0 */
+ if (data->gain != 1 && data->gain != 2 && data->gain != 4) {
+ dev_err(dev, "invalid shunt gain value %u\n", data->gain);
+ return -EINVAL;
+ }
}
/* Setup CONFIG register */
@@ -720,12 +866,14 @@ static int ina238_probe(struct i2c_client *client)
return -ENODEV;
}
- /* Setup SHUNT_CALIBRATION register with fixed value */
- ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION,
- INA238_CALIBRATION_VALUE);
- if (ret < 0) {
- dev_err(dev, "error configuring the device: %d\n", ret);
- return -ENODEV;
+ if (data->config->has_shunt) {
+ /* Setup SHUNT_CALIBRATION register with fixed value */
+ ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION,
+ INA238_CALIBRATION_VALUE);
+ if (ret < 0) {
+ dev_err(dev, "error configuring the device: %d\n", ret);
+ return -ENODEV;
+ }
}
/* Setup alert/alarm configuration */
@@ -743,8 +891,9 @@ static int ina238_probe(struct i2c_client *client)
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
- dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n",
- client->name, data->rshunt, data->gain);
+ if (data->config->has_shunt)
+ dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n",
+ client->name, data->rshunt, data->gain);
return 0;
}
@@ -753,6 +902,7 @@ static const struct i2c_device_id ina238_id[] = {
{ "ina237", ina237 },
{ "ina238", ina238 },
{ "sq52206", sq52206 },
+ { "ina780", ina780 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ina238_id);
@@ -770,6 +920,10 @@ static const struct of_device_id __maybe_unused ina238_of_match[] = {
.compatible = "silergy,sq52206",
.data = (void *)sq52206
},
+ {
+ .compatible = "ti,ina780",
+ .data = (void *)ina780
+ },
{ }
};
MODULE_DEVICE_TABLE(of, ina238_of_match);
--
2.50.1
^ permalink raw reply related [flat|nested] 10+ messages in thread