hwmon: (ina238) Add support for current limits

Since the shunt voltage register and the current register now report the
same values, use the shunt voltage limit registers to report and adjust
current limits, using the same LSB as the LSB used for the actual current
register.

Handle current register accuracy differences in separate function to
improve code readability.

Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # INA780
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
This commit is contained in:
Guenter Roeck 2025-08-31 09:25:16 -07:00
parent b7bce92f28
commit a1d5f8ecb9
2 changed files with 87 additions and 22 deletions

View File

@ -82,6 +82,10 @@ power1_input_highest Peak Power (uW)
(SQ52206 only)
curr1_input Current measurement (mA)
curr1_min Minimum current threshold (mA)
curr1_min_alarm Minimum current alarm
curr1_max Maximum current threshold (mA)
curr1_max_alarm Maximum current alarm
energy1_input Energy measurement (uJ)
(SQ52206 and INA237 only)

View File

@ -335,40 +335,92 @@ static int ina238_write_in(struct device *dev, u32 attr, int channel, long val)
}
}
static int ina238_read_current(struct device *dev, u32 attr, long *val)
static int __ina238_read_curr(struct ina238_data *data, long *val)
{
u32 lsb = data->current_lsb;
int err, regval;
if (data->config->has_20bit_voltage_current) {
err = ina238_read_field_s20(data->client, INA238_CURRENT, &regval);
if (err)
return err;
lsb /= 16; /* Adjust accuracy */
} else {
err = regmap_read(data->regmap, INA238_CURRENT, &regval);
if (err)
return err;
regval = (s16)regval;
}
*val = DIV_S64_ROUND_CLOSEST((s64)regval * lsb, 1000);
return 0;
}
static int ina238_read_curr(struct device *dev, u32 attr, long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
int reg, mask = 0;
int regval;
int err;
if (attr == hwmon_curr_input)
return __ina238_read_curr(data, val);
switch (attr) {
case hwmon_curr_input:
if (data->config->has_20bit_voltage_current) {
err = ina238_read_field_s20(data->client, INA238_CURRENT, &regval);
if (err)
return err;
} else {
err = regmap_read(data->regmap, INA238_CURRENT, &regval);
if (err < 0)
return err;
/* sign-extend */
regval = (s16)regval;
}
/* Signed register. Result in mA */
*val = DIV_S64_ROUND_CLOSEST((s64)regval * data->current_lsb, 1000);
/* Account for 4 bit offset */
if (data->config->has_20bit_voltage_current)
*val /= 16;
case hwmon_curr_min:
reg = INA238_SHUNT_UNDER_VOLTAGE;
break;
case hwmon_curr_min_alarm:
reg = INA238_DIAG_ALERT;
mask = INA238_DIAG_ALERT_SHNTUL;
break;
case hwmon_curr_max:
reg = INA238_SHUNT_OVER_VOLTAGE;
break;
case hwmon_curr_max_alarm:
reg = INA238_DIAG_ALERT;
mask = INA238_DIAG_ALERT_SHNTOL;
break;
default:
return -EOPNOTSUPP;
}
err = regmap_read(data->regmap, reg, &regval);
if (err < 0)
return err;
if (mask)
*val = !!(regval & mask);
else
*val = DIV_S64_ROUND_CLOSEST((s64)(s16)regval * data->current_lsb, 1000);
return 0;
}
static int ina238_write_curr(struct device *dev, u32 attr, long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
int regval;
/* Set baseline range to avoid over/underflows */
val = clamp_val(val, -1000000, 1000000);
/* Scale */
val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb);
/* Clamp to register size */
regval = clamp_val(val, S16_MIN, S16_MAX) & 0xffff;
switch (attr) {
case hwmon_curr_min:
return regmap_write(data->regmap, INA238_SHUNT_UNDER_VOLTAGE,
regval);
case hwmon_curr_max:
return regmap_write(data->regmap, INA238_SHUNT_OVER_VOLTAGE,
regval);
default:
return -EOPNOTSUPP;
}
}
static int ina238_read_power(struct device *dev, u32 attr, long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
@ -521,7 +573,7 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
case hwmon_in:
return ina238_read_in(dev, attr, channel, val);
case hwmon_curr:
return ina238_read_current(dev, attr, val);
return ina238_read_curr(dev, attr, val);
case hwmon_power:
return ina238_read_power(dev, attr, val);
case hwmon_temp:
@ -544,6 +596,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 = ina238_write_curr(dev, attr, val);
break;
case hwmon_power:
err = ina238_write_power_max(dev, val);
break;
@ -582,7 +637,12 @@ static umode_t ina238_is_visible(const void *drvdata,
case hwmon_curr:
switch (attr) {
case hwmon_curr_input:
case hwmon_curr_max_alarm:
case hwmon_curr_min_alarm:
return 0444;
case hwmon_curr_max:
case hwmon_curr_min:
return 0644;
default:
return 0;
}
@ -627,7 +687,8 @@ static const struct hwmon_channel_info * const ina238_info[] = {
INA238_HWMON_IN_CONFIG),
HWMON_CHANNEL_INFO(curr,
/* 0: current through shunt */
HWMON_C_INPUT),
HWMON_C_INPUT | HWMON_C_MIN | HWMON_C_MIN_ALARM |
HWMON_C_MAX | HWMON_C_MAX_ALARM),
HWMON_CHANNEL_INFO(power,
/* 0: power */
HWMON_P_INPUT | HWMON_P_MAX |