* [PATCH v3 0/3] hwmon: ina238: add samples and update_interval_us support
@ 2026-06-09 19:43 ` Ferdinand Schwenk
0 siblings, 0 replies; 14+ messages in thread
From: Ferdinand Schwenk via B4 Relay @ 2026-06-09 19:43 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
Ferdinand Schwenk
The INA238 family exposes ADC averaging and conversion timing controls
through ADC_CONFIG. Add support for the samples and update_interval
chip attributes and introduce a generic update_interval_us companion
attribute for devices that need sub-millisecond resolution.
The shortest INA238 conversion time steps are below 1 ms, so several
valid hardware settings collapse to the same update_interval value when
reported only in milliseconds. Keep update_interval in milliseconds as
required by the existing hwmon ABI and provide update_interval_us to
report and program the same total conversion cycle time with microsecond
resolution.
Patch 1 adds samples and update_interval support for the INA238 family.
Patch 2 adds the generic hwmon update_interval_us attribute and
documents it.
Patch 3 wires the new attribute up in the INA238 driver.
Link: https://lore.kernel.org/all/20260522-hwmon-ina238-add-samples-update-interval-v1-0-e1acfceb447e@advastore.com/
---
v2:
- keep update_interval in milliseconds to preserve the existing ABI
- add the generic update_interval_us hwmon chip attribute and documentation
- implement update_interval_us for ina238
- report and program intervals using the active averaging count
v3:
- add missing Signed-off-by trailer to all patches
- address truncation concern by tightening update_interval input clamping in patch 1/3
- preserve equivalent clamping in patch 3/3 after introducing update_interval_us
- Link to v2: https://lore.kernel.org/r/20260608-hwmon-ina238-update-interval-us-v2-v2-0-2d939fbb2ea1@advastore.com
---
Ferdinand Schwenk (3):
hwmon: ina238: add support for samples and update_interval
hwmon: Add update_interval_us chip attribute
hwmon: ina238: add update_interval_us attribute
Documentation/ABI/testing/sysfs-class-hwmon | 14 +++
Documentation/hwmon/ina238.rst | 4 +
Documentation/hwmon/sysfs-interface.rst | 4 +
drivers/hwmon/hwmon.c | 1 +
drivers/hwmon/ina238.c | 162 +++++++++++++++++++++++++++-
include/linux/hwmon.h | 2 +
6 files changed, 185 insertions(+), 2 deletions(-)
---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: 20260608-hwmon-ina238-update-interval-us-v2-13448de0a083
Best regards,
--
Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 0/3] hwmon: ina238: add samples and update_interval_us support
@ 2026-06-09 19:43 ` Ferdinand Schwenk
0 siblings, 0 replies; 14+ messages in thread
From: Ferdinand Schwenk @ 2026-06-09 19:43 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
Ferdinand Schwenk
The INA238 family exposes ADC averaging and conversion timing controls
through ADC_CONFIG. Add support for the samples and update_interval
chip attributes and introduce a generic update_interval_us companion
attribute for devices that need sub-millisecond resolution.
The shortest INA238 conversion time steps are below 1 ms, so several
valid hardware settings collapse to the same update_interval value when
reported only in milliseconds. Keep update_interval in milliseconds as
required by the existing hwmon ABI and provide update_interval_us to
report and program the same total conversion cycle time with microsecond
resolution.
Patch 1 adds samples and update_interval support for the INA238 family.
Patch 2 adds the generic hwmon update_interval_us attribute and
documents it.
Patch 3 wires the new attribute up in the INA238 driver.
Link: https://lore.kernel.org/all/20260522-hwmon-ina238-add-samples-update-interval-v1-0-e1acfceb447e@advastore.com/
---
v2:
- keep update_interval in milliseconds to preserve the existing ABI
- add the generic update_interval_us hwmon chip attribute and documentation
- implement update_interval_us for ina238
- report and program intervals using the active averaging count
v3:
- add missing Signed-off-by trailer to all patches
- address truncation concern by tightening update_interval input clamping in patch 1/3
- preserve equivalent clamping in patch 3/3 after introducing update_interval_us
- Link to v2: https://lore.kernel.org/r/20260608-hwmon-ina238-update-interval-us-v2-v2-0-2d939fbb2ea1@advastore.com
---
Ferdinand Schwenk (3):
hwmon: ina238: add support for samples and update_interval
hwmon: Add update_interval_us chip attribute
hwmon: ina238: add update_interval_us attribute
Documentation/ABI/testing/sysfs-class-hwmon | 14 +++
Documentation/hwmon/ina238.rst | 4 +
Documentation/hwmon/sysfs-interface.rst | 4 +
drivers/hwmon/hwmon.c | 1 +
drivers/hwmon/ina238.c | 162 +++++++++++++++++++++++++++-
include/linux/hwmon.h | 2 +
6 files changed, 185 insertions(+), 2 deletions(-)
---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: 20260608-hwmon-ina238-update-interval-us-v2-13448de0a083
Best regards,
--
Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 1/3] hwmon: ina238: add support for samples and update_interval
2026-06-09 19:43 ` Ferdinand Schwenk
@ 2026-06-09 19:43 ` Ferdinand Schwenk
-1 siblings, 0 replies; 14+ messages in thread
From: Ferdinand Schwenk via B4 Relay @ 2026-06-09 19:43 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
Ferdinand Schwenk
From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Expose INA238 ADC averaging count (AVG) and conversion timing
(VBUSCT/VSHCT/VTCT) through chip-level hwmon attributes:
chip/samples
chip/update_interval
Use per-chip conversion-time lookup tables so the same helpers work
for INA228/INA237/INA238/INA700/INA780 and SQ52206. Cache ADC_CONFIG
in driver data and update it on writes to avoid extra register reads
during read-modify-write updates.
Report update_interval in milliseconds as required by the hwmon ABI.
Compute it from raw ADC cycle time multiplied by the active averaging
count, and apply the inverse mapping on writes so programmed conversion
time tracks the selected sample count.
Clamp user-provided update_interval before unit scaling to prevent
overflow in arithmetic conversions.
Also combine chip attributes in HWMON_CHANNEL_INFO using a bitwise OR
for a single logical chip channel.
Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
---
drivers/hwmon/ina238.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 142 insertions(+), 2 deletions(-)
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c
index ff67b03189f7..dc5dd3ad2557 100644
--- a/drivers/hwmon/ina238.c
+++ b/drivers/hwmon/ina238.c
@@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
+#include <linux/util_macros.h>
/* INA238 register definitions */
#define INA238_CONFIG 0x0
@@ -49,6 +50,17 @@
#define INA238_DIAG_ALERT_BUSUL BIT(3)
#define INA238_DIAG_ALERT_POL BIT(2)
+/* INA238_ADC_CONFIG register field masks and shifts */
+#define INA238_ADC_CONFIG_MODE_MASK GENMASK(15, 12)
+#define INA238_ADC_CONFIG_VBUSCT_SHIFT 9
+#define INA238_ADC_CONFIG_VBUSCT_MASK GENMASK(11, 9)
+#define INA238_ADC_CONFIG_VSHCT_SHIFT 6
+#define INA238_ADC_CONFIG_VSHCT_MASK GENMASK(8, 6)
+#define INA238_ADC_CONFIG_VTCT_SHIFT 3
+#define INA238_ADC_CONFIG_VTCT_MASK GENMASK(5, 3)
+#define INA238_ADC_CONFIG_AVG_SHIFT 0
+#define INA238_ADC_CONFIG_AVG_MASK GENMASK(2, 0)
+
#define INA238_REGISTERS 0x20
#define INA238_RSHUNT_DEFAULT 2500 /* uOhm */
@@ -101,6 +113,21 @@ static const struct regmap_config ina238_regmap_config = {
.val_bits = 16,
};
+/* Lookup table for conversion times in usec for INA238 family */
+static const u16 ina238_conv_time[] = {
+ 50, 84, 150, 280, 540, 1052, 2074, 4120,
+};
+
+/* Lookup table for conversion times in usec for SQ52206 */
+static const u16 sq52206_conv_time[] = {
+ 66, 118, 310, 566, 1070, 2090, 4140, 8230,
+};
+
+/* Lookup table for number of samples used in averaging mode */
+static const int ina238_avg_samples[] = {
+ 1, 4, 16, 64, 128, 256, 512, 1024,
+};
+
enum ina238_ids { ina228, ina237, ina238, ina700, ina780, sq52206 };
struct ina238_config {
@@ -112,6 +139,7 @@ struct ina238_config {
u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */
u32 bus_voltage_lsb; /* bus voltage LSB, in nV */
int current_lsb; /* current LSB, in uA */
+ const u16 *conv_time; /* conversion time lookup table */
};
struct ina238_data {
@@ -124,6 +152,7 @@ struct ina238_data {
int current_lsb; /* current LSB, in uA */
int power_lsb; /* power LSB, in uW */
int energy_lsb; /* energy LSB, in uJ */
+ u16 adc_config; /* cached ADC_CONFIG register value */
};
static const struct ina238_config ina238_config[] = {
@@ -135,6 +164,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 16,
+ .conv_time = ina238_conv_time,
},
[ina237] = {
.has_20bit_voltage_current = false,
@@ -144,6 +174,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
+ .conv_time = ina238_conv_time,
},
[ina238] = {
.has_20bit_voltage_current = false,
@@ -153,6 +184,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
+ .conv_time = ina238_conv_time,
},
[ina700] = {
.has_20bit_voltage_current = false,
@@ -163,6 +195,7 @@ static const struct ina238_config ina238_config[] = {
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 480,
+ .conv_time = ina238_conv_time,
},
[ina780] = {
.has_20bit_voltage_current = false,
@@ -173,6 +206,7 @@ static const struct ina238_config ina238_config[] = {
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 2400,
+ .conv_time = ina238_conv_time,
},
[sq52206] = {
.has_20bit_voltage_current = false,
@@ -182,6 +216,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = SQ52206_CONFIG_DEFAULT,
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
.temp_resolution = 16,
+ .conv_time = sq52206_conv_time,
},
};
@@ -261,6 +296,97 @@ static int ina228_read_voltage(struct ina238_data *data, int channel, long *val)
return 0;
}
+/* Converting ADC_CONFIG register value to update_interval in usec */
+static inline u32 ina238_reg_to_interval_us(struct ina238_data *data)
+{
+ const u16 *ct = data->config->conv_time;
+ u32 vbusct = ct[(data->adc_config & INA238_ADC_CONFIG_VBUSCT_MASK) >>
+ INA238_ADC_CONFIG_VBUSCT_SHIFT];
+ u32 vshct = ct[(data->adc_config & INA238_ADC_CONFIG_VSHCT_MASK) >>
+ INA238_ADC_CONFIG_VSHCT_SHIFT];
+ u32 vtct = ct[(data->adc_config & INA238_ADC_CONFIG_VTCT_MASK) >>
+ INA238_ADC_CONFIG_VTCT_SHIFT];
+
+ return vbusct + vshct + vtct;
+}
+
+static inline u32 ina238_samples(struct ina238_data *data)
+{
+ return ina238_avg_samples[(data->adc_config & INA238_ADC_CONFIG_AVG_MASK) >>
+ INA238_ADC_CONFIG_AVG_SHIFT];
+}
+
+/* Converting update_interval in msec to a single conversion time in usec */
+static inline u32 ina238_interval_ms_to_conv_time(long interval, u32 samples)
+{
+ u64 interval_us;
+
+ interval = clamp_val(interval, 0, INT_MAX / 1000);
+ interval_us = (u64)interval * 1000;
+
+ /*
+ * update_interval reports the ADC cycle time including averaging.
+ * The target per-field conversion time is interval_us / (samples * 3).
+ */
+ return DIV_ROUND_CLOSEST_ULL(interval_us, samples * 3);
+}
+
+static int ina238_read_chip(struct device *dev, u32 attr, long *val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_chip_samples:
+ *val = ina238_samples(data);
+ return 0;
+ case hwmon_chip_update_interval:
+ /* Return in msec */
+ *val = DIV_ROUND_CLOSEST(ina238_reg_to_interval_us(data) *
+ ina238_samples(data), 1000);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ina238_write_chip(struct device *dev, u32 attr, long val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ u16 adc_config;
+ int idx, ret;
+
+ switch (attr) {
+ case hwmon_chip_samples:
+ idx = find_closest(val, ina238_avg_samples,
+ ARRAY_SIZE(ina238_avg_samples));
+ adc_config = (data->adc_config & ~INA238_ADC_CONFIG_AVG_MASK) |
+ (idx << INA238_ADC_CONFIG_AVG_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
+ case hwmon_chip_update_interval:
+ val = ina238_interval_ms_to_conv_time(val, ina238_samples(data));
+ idx = find_closest(val, data->config->conv_time,
+ ARRAY_SIZE(ina238_conv_time));
+ adc_config = (data->adc_config &
+ ~(INA238_ADC_CONFIG_VBUSCT_MASK |
+ INA238_ADC_CONFIG_VSHCT_MASK |
+ INA238_ADC_CONFIG_VTCT_MASK)) |
+ ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static int ina238_read_in(struct device *dev, u32 attr, int channel,
long *val)
{
@@ -587,6 +713,8 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
switch (type) {
+ case hwmon_chip:
+ return ina238_read_chip(dev, attr, val);
case hwmon_in:
return ina238_read_in(dev, attr, channel, val);
case hwmon_curr:
@@ -607,6 +735,8 @@ static int ina238_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
switch (type) {
+ case hwmon_chip:
+ return ina238_write_chip(dev, attr, val);
case hwmon_in:
return ina238_write_in(dev, attr, channel, val);
case hwmon_curr:
@@ -629,6 +759,14 @@ static umode_t ina238_is_visible(const void *drvdata,
bool has_energy = data->config->has_energy;
switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_samples:
+ case hwmon_chip_update_interval:
+ return 0644;
+ default:
+ return 0;
+ }
case hwmon_in:
switch (attr) {
case hwmon_in_input:
@@ -692,6 +830,8 @@ static umode_t ina238_is_visible(const void *drvdata,
HWMON_I_MIN | HWMON_I_MIN_ALARM)
static const struct hwmon_channel_info * const ina238_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(in,
/* 0: shunt voltage */
INA238_HWMON_IN_CONFIG,
@@ -798,8 +938,8 @@ static int ina238_probe(struct i2c_client *client)
}
/* Setup ADC_CONFIG register */
- ret = regmap_write(data->regmap, INA238_ADC_CONFIG,
- INA238_ADC_CONFIG_DEFAULT);
+ data->adc_config = INA238_ADC_CONFIG_DEFAULT;
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, data->adc_config);
if (ret < 0) {
dev_err(dev, "error configuring the device: %d\n", ret);
return -ENODEV;
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 1/3] hwmon: ina238: add support for samples and update_interval
@ 2026-06-09 19:43 ` Ferdinand Schwenk
0 siblings, 0 replies; 14+ messages in thread
From: Ferdinand Schwenk @ 2026-06-09 19:43 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
Ferdinand Schwenk
Expose INA238 ADC averaging count (AVG) and conversion timing
(VBUSCT/VSHCT/VTCT) through chip-level hwmon attributes:
chip/samples
chip/update_interval
Use per-chip conversion-time lookup tables so the same helpers work
for INA228/INA237/INA238/INA700/INA780 and SQ52206. Cache ADC_CONFIG
in driver data and update it on writes to avoid extra register reads
during read-modify-write updates.
Report update_interval in milliseconds as required by the hwmon ABI.
Compute it from raw ADC cycle time multiplied by the active averaging
count, and apply the inverse mapping on writes so programmed conversion
time tracks the selected sample count.
Clamp user-provided update_interval before unit scaling to prevent
overflow in arithmetic conversions.
Also combine chip attributes in HWMON_CHANNEL_INFO using a bitwise OR
for a single logical chip channel.
Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
---
drivers/hwmon/ina238.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 142 insertions(+), 2 deletions(-)
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c
index ff67b03189f7..dc5dd3ad2557 100644
--- a/drivers/hwmon/ina238.c
+++ b/drivers/hwmon/ina238.c
@@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
+#include <linux/util_macros.h>
/* INA238 register definitions */
#define INA238_CONFIG 0x0
@@ -49,6 +50,17 @@
#define INA238_DIAG_ALERT_BUSUL BIT(3)
#define INA238_DIAG_ALERT_POL BIT(2)
+/* INA238_ADC_CONFIG register field masks and shifts */
+#define INA238_ADC_CONFIG_MODE_MASK GENMASK(15, 12)
+#define INA238_ADC_CONFIG_VBUSCT_SHIFT 9
+#define INA238_ADC_CONFIG_VBUSCT_MASK GENMASK(11, 9)
+#define INA238_ADC_CONFIG_VSHCT_SHIFT 6
+#define INA238_ADC_CONFIG_VSHCT_MASK GENMASK(8, 6)
+#define INA238_ADC_CONFIG_VTCT_SHIFT 3
+#define INA238_ADC_CONFIG_VTCT_MASK GENMASK(5, 3)
+#define INA238_ADC_CONFIG_AVG_SHIFT 0
+#define INA238_ADC_CONFIG_AVG_MASK GENMASK(2, 0)
+
#define INA238_REGISTERS 0x20
#define INA238_RSHUNT_DEFAULT 2500 /* uOhm */
@@ -101,6 +113,21 @@ static const struct regmap_config ina238_regmap_config = {
.val_bits = 16,
};
+/* Lookup table for conversion times in usec for INA238 family */
+static const u16 ina238_conv_time[] = {
+ 50, 84, 150, 280, 540, 1052, 2074, 4120,
+};
+
+/* Lookup table for conversion times in usec for SQ52206 */
+static const u16 sq52206_conv_time[] = {
+ 66, 118, 310, 566, 1070, 2090, 4140, 8230,
+};
+
+/* Lookup table for number of samples used in averaging mode */
+static const int ina238_avg_samples[] = {
+ 1, 4, 16, 64, 128, 256, 512, 1024,
+};
+
enum ina238_ids { ina228, ina237, ina238, ina700, ina780, sq52206 };
struct ina238_config {
@@ -112,6 +139,7 @@ struct ina238_config {
u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */
u32 bus_voltage_lsb; /* bus voltage LSB, in nV */
int current_lsb; /* current LSB, in uA */
+ const u16 *conv_time; /* conversion time lookup table */
};
struct ina238_data {
@@ -124,6 +152,7 @@ struct ina238_data {
int current_lsb; /* current LSB, in uA */
int power_lsb; /* power LSB, in uW */
int energy_lsb; /* energy LSB, in uJ */
+ u16 adc_config; /* cached ADC_CONFIG register value */
};
static const struct ina238_config ina238_config[] = {
@@ -135,6 +164,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 16,
+ .conv_time = ina238_conv_time,
},
[ina237] = {
.has_20bit_voltage_current = false,
@@ -144,6 +174,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
+ .conv_time = ina238_conv_time,
},
[ina238] = {
.has_20bit_voltage_current = false,
@@ -153,6 +184,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
+ .conv_time = ina238_conv_time,
},
[ina700] = {
.has_20bit_voltage_current = false,
@@ -163,6 +195,7 @@ static const struct ina238_config ina238_config[] = {
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 480,
+ .conv_time = ina238_conv_time,
},
[ina780] = {
.has_20bit_voltage_current = false,
@@ -173,6 +206,7 @@ static const struct ina238_config ina238_config[] = {
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 2400,
+ .conv_time = ina238_conv_time,
},
[sq52206] = {
.has_20bit_voltage_current = false,
@@ -182,6 +216,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = SQ52206_CONFIG_DEFAULT,
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
.temp_resolution = 16,
+ .conv_time = sq52206_conv_time,
},
};
@@ -261,6 +296,97 @@ static int ina228_read_voltage(struct ina238_data *data, int channel, long *val)
return 0;
}
+/* Converting ADC_CONFIG register value to update_interval in usec */
+static inline u32 ina238_reg_to_interval_us(struct ina238_data *data)
+{
+ const u16 *ct = data->config->conv_time;
+ u32 vbusct = ct[(data->adc_config & INA238_ADC_CONFIG_VBUSCT_MASK) >>
+ INA238_ADC_CONFIG_VBUSCT_SHIFT];
+ u32 vshct = ct[(data->adc_config & INA238_ADC_CONFIG_VSHCT_MASK) >>
+ INA238_ADC_CONFIG_VSHCT_SHIFT];
+ u32 vtct = ct[(data->adc_config & INA238_ADC_CONFIG_VTCT_MASK) >>
+ INA238_ADC_CONFIG_VTCT_SHIFT];
+
+ return vbusct + vshct + vtct;
+}
+
+static inline u32 ina238_samples(struct ina238_data *data)
+{
+ return ina238_avg_samples[(data->adc_config & INA238_ADC_CONFIG_AVG_MASK) >>
+ INA238_ADC_CONFIG_AVG_SHIFT];
+}
+
+/* Converting update_interval in msec to a single conversion time in usec */
+static inline u32 ina238_interval_ms_to_conv_time(long interval, u32 samples)
+{
+ u64 interval_us;
+
+ interval = clamp_val(interval, 0, INT_MAX / 1000);
+ interval_us = (u64)interval * 1000;
+
+ /*
+ * update_interval reports the ADC cycle time including averaging.
+ * The target per-field conversion time is interval_us / (samples * 3).
+ */
+ return DIV_ROUND_CLOSEST_ULL(interval_us, samples * 3);
+}
+
+static int ina238_read_chip(struct device *dev, u32 attr, long *val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_chip_samples:
+ *val = ina238_samples(data);
+ return 0;
+ case hwmon_chip_update_interval:
+ /* Return in msec */
+ *val = DIV_ROUND_CLOSEST(ina238_reg_to_interval_us(data) *
+ ina238_samples(data), 1000);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ina238_write_chip(struct device *dev, u32 attr, long val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ u16 adc_config;
+ int idx, ret;
+
+ switch (attr) {
+ case hwmon_chip_samples:
+ idx = find_closest(val, ina238_avg_samples,
+ ARRAY_SIZE(ina238_avg_samples));
+ adc_config = (data->adc_config & ~INA238_ADC_CONFIG_AVG_MASK) |
+ (idx << INA238_ADC_CONFIG_AVG_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
+ case hwmon_chip_update_interval:
+ val = ina238_interval_ms_to_conv_time(val, ina238_samples(data));
+ idx = find_closest(val, data->config->conv_time,
+ ARRAY_SIZE(ina238_conv_time));
+ adc_config = (data->adc_config &
+ ~(INA238_ADC_CONFIG_VBUSCT_MASK |
+ INA238_ADC_CONFIG_VSHCT_MASK |
+ INA238_ADC_CONFIG_VTCT_MASK)) |
+ ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static int ina238_read_in(struct device *dev, u32 attr, int channel,
long *val)
{
@@ -587,6 +713,8 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
switch (type) {
+ case hwmon_chip:
+ return ina238_read_chip(dev, attr, val);
case hwmon_in:
return ina238_read_in(dev, attr, channel, val);
case hwmon_curr:
@@ -607,6 +735,8 @@ static int ina238_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
switch (type) {
+ case hwmon_chip:
+ return ina238_write_chip(dev, attr, val);
case hwmon_in:
return ina238_write_in(dev, attr, channel, val);
case hwmon_curr:
@@ -629,6 +759,14 @@ static umode_t ina238_is_visible(const void *drvdata,
bool has_energy = data->config->has_energy;
switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_samples:
+ case hwmon_chip_update_interval:
+ return 0644;
+ default:
+ return 0;
+ }
case hwmon_in:
switch (attr) {
case hwmon_in_input:
@@ -692,6 +830,8 @@ static umode_t ina238_is_visible(const void *drvdata,
HWMON_I_MIN | HWMON_I_MIN_ALARM)
static const struct hwmon_channel_info * const ina238_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(in,
/* 0: shunt voltage */
INA238_HWMON_IN_CONFIG,
@@ -798,8 +938,8 @@ static int ina238_probe(struct i2c_client *client)
}
/* Setup ADC_CONFIG register */
- ret = regmap_write(data->regmap, INA238_ADC_CONFIG,
- INA238_ADC_CONFIG_DEFAULT);
+ data->adc_config = INA238_ADC_CONFIG_DEFAULT;
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, data->adc_config);
if (ret < 0) {
dev_err(dev, "error configuring the device: %d\n", ret);
return -ENODEV;
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 2/3] hwmon: Add update_interval_us chip attribute
2026-06-09 19:43 ` Ferdinand Schwenk
@ 2026-06-09 19:43 ` Ferdinand Schwenk
-1 siblings, 0 replies; 14+ messages in thread
From: Ferdinand Schwenk via B4 Relay @ 2026-06-09 19:43 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
Ferdinand Schwenk
From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Some hardware monitoring chips support update intervals below one
millisecond. The existing update_interval attribute uses millisecond
granularity, which causes sub-millisecond steps to round to the same
value and become inaccessible from userspace.
Introduce update_interval_us, a companion chip-level attribute that
expresses the same update interval in microseconds. Drivers
implementing this attribute should also implement update_interval for
compatibility with millisecond-based userspace interfaces.
Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
---
Documentation/ABI/testing/sysfs-class-hwmon | 14 ++++++++++++++
Documentation/hwmon/sysfs-interface.rst | 4 ++++
drivers/hwmon/hwmon.c | 1 +
include/linux/hwmon.h | 2 ++
4 files changed, 21 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-class-hwmon b/Documentation/ABI/testing/sysfs-class-hwmon
index cfd0d0bab483..b185bdfc7186 100644
--- a/Documentation/ABI/testing/sysfs-class-hwmon
+++ b/Documentation/ABI/testing/sysfs-class-hwmon
@@ -27,6 +27,20 @@ Description:
Some devices have a variable update rate or interval.
This attribute can be used to change it to the desired value.
+What: /sys/class/hwmon/hwmonX/update_interval_us
+Description:
+ The interval at which the chip will update readings,
+ expressed in microseconds.
+ Unit: microsecond
+
+ RW
+
+ Some devices have a variable update rate or interval and
+ require finer-than-millisecond control.
+ This attribute can be used to change it to the desired value.
+ Drivers implementing this attribute should also implement
+ update_interval for millisecond-based userspace interfaces.
+
What: /sys/class/hwmon/hwmonX/inY_min
Description:
Voltage min value.
diff --git a/Documentation/hwmon/sysfs-interface.rst b/Documentation/hwmon/sysfs-interface.rst
index f76e9f8cc1ad..94e1bbce172a 100644
--- a/Documentation/hwmon/sysfs-interface.rst
+++ b/Documentation/hwmon/sysfs-interface.rst
@@ -106,6 +106,10 @@ Global attributes
`update_interval`
The interval at which the chip will update readings.
+`update_interval_us`
+ The interval at which the chip will update readings,
+ expressed in microseconds for finer resolution.
+
********
Voltages
diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c
index 9695dca62a7e..c481bb535e35 100644
--- a/drivers/hwmon/hwmon.c
+++ b/drivers/hwmon/hwmon.c
@@ -572,6 +572,7 @@ static const char * const hwmon_chip_attrs[] = {
[hwmon_chip_curr_reset_history] = "curr_reset_history",
[hwmon_chip_power_reset_history] = "power_reset_history",
[hwmon_chip_update_interval] = "update_interval",
+ [hwmon_chip_update_interval_us] = "update_interval_us",
[hwmon_chip_alarms] = "alarms",
[hwmon_chip_samples] = "samples",
[hwmon_chip_curr_samples] = "curr_samples",
diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h
index 301a83afbd66..5a5882fee299 100644
--- a/include/linux/hwmon.h
+++ b/include/linux/hwmon.h
@@ -39,6 +39,7 @@ enum hwmon_chip_attributes {
hwmon_chip_power_reset_history,
hwmon_chip_register_tz,
hwmon_chip_update_interval,
+ hwmon_chip_update_interval_us,
hwmon_chip_alarms,
hwmon_chip_samples,
hwmon_chip_curr_samples,
@@ -55,6 +56,7 @@ enum hwmon_chip_attributes {
#define HWMON_C_POWER_RESET_HISTORY BIT(hwmon_chip_power_reset_history)
#define HWMON_C_REGISTER_TZ BIT(hwmon_chip_register_tz)
#define HWMON_C_UPDATE_INTERVAL BIT(hwmon_chip_update_interval)
+#define HWMON_C_UPDATE_INTERVAL_US BIT(hwmon_chip_update_interval_us)
#define HWMON_C_ALARMS BIT(hwmon_chip_alarms)
#define HWMON_C_SAMPLES BIT(hwmon_chip_samples)
#define HWMON_C_CURR_SAMPLES BIT(hwmon_chip_curr_samples)
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 2/3] hwmon: Add update_interval_us chip attribute
@ 2026-06-09 19:43 ` Ferdinand Schwenk
0 siblings, 0 replies; 14+ messages in thread
From: Ferdinand Schwenk @ 2026-06-09 19:43 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
Ferdinand Schwenk
Some hardware monitoring chips support update intervals below one
millisecond. The existing update_interval attribute uses millisecond
granularity, which causes sub-millisecond steps to round to the same
value and become inaccessible from userspace.
Introduce update_interval_us, a companion chip-level attribute that
expresses the same update interval in microseconds. Drivers
implementing this attribute should also implement update_interval for
compatibility with millisecond-based userspace interfaces.
Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
---
Documentation/ABI/testing/sysfs-class-hwmon | 14 ++++++++++++++
Documentation/hwmon/sysfs-interface.rst | 4 ++++
drivers/hwmon/hwmon.c | 1 +
include/linux/hwmon.h | 2 ++
4 files changed, 21 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-class-hwmon b/Documentation/ABI/testing/sysfs-class-hwmon
index cfd0d0bab483..b185bdfc7186 100644
--- a/Documentation/ABI/testing/sysfs-class-hwmon
+++ b/Documentation/ABI/testing/sysfs-class-hwmon
@@ -27,6 +27,20 @@ Description:
Some devices have a variable update rate or interval.
This attribute can be used to change it to the desired value.
+What: /sys/class/hwmon/hwmonX/update_interval_us
+Description:
+ The interval at which the chip will update readings,
+ expressed in microseconds.
+ Unit: microsecond
+
+ RW
+
+ Some devices have a variable update rate or interval and
+ require finer-than-millisecond control.
+ This attribute can be used to change it to the desired value.
+ Drivers implementing this attribute should also implement
+ update_interval for millisecond-based userspace interfaces.
+
What: /sys/class/hwmon/hwmonX/inY_min
Description:
Voltage min value.
diff --git a/Documentation/hwmon/sysfs-interface.rst b/Documentation/hwmon/sysfs-interface.rst
index f76e9f8cc1ad..94e1bbce172a 100644
--- a/Documentation/hwmon/sysfs-interface.rst
+++ b/Documentation/hwmon/sysfs-interface.rst
@@ -106,6 +106,10 @@ Global attributes
`update_interval`
The interval at which the chip will update readings.
+`update_interval_us`
+ The interval at which the chip will update readings,
+ expressed in microseconds for finer resolution.
+
********
Voltages
diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c
index 9695dca62a7e..c481bb535e35 100644
--- a/drivers/hwmon/hwmon.c
+++ b/drivers/hwmon/hwmon.c
@@ -572,6 +572,7 @@ static const char * const hwmon_chip_attrs[] = {
[hwmon_chip_curr_reset_history] = "curr_reset_history",
[hwmon_chip_power_reset_history] = "power_reset_history",
[hwmon_chip_update_interval] = "update_interval",
+ [hwmon_chip_update_interval_us] = "update_interval_us",
[hwmon_chip_alarms] = "alarms",
[hwmon_chip_samples] = "samples",
[hwmon_chip_curr_samples] = "curr_samples",
diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h
index 301a83afbd66..5a5882fee299 100644
--- a/include/linux/hwmon.h
+++ b/include/linux/hwmon.h
@@ -39,6 +39,7 @@ enum hwmon_chip_attributes {
hwmon_chip_power_reset_history,
hwmon_chip_register_tz,
hwmon_chip_update_interval,
+ hwmon_chip_update_interval_us,
hwmon_chip_alarms,
hwmon_chip_samples,
hwmon_chip_curr_samples,
@@ -55,6 +56,7 @@ enum hwmon_chip_attributes {
#define HWMON_C_POWER_RESET_HISTORY BIT(hwmon_chip_power_reset_history)
#define HWMON_C_REGISTER_TZ BIT(hwmon_chip_register_tz)
#define HWMON_C_UPDATE_INTERVAL BIT(hwmon_chip_update_interval)
+#define HWMON_C_UPDATE_INTERVAL_US BIT(hwmon_chip_update_interval_us)
#define HWMON_C_ALARMS BIT(hwmon_chip_alarms)
#define HWMON_C_SAMPLES BIT(hwmon_chip_samples)
#define HWMON_C_CURR_SAMPLES BIT(hwmon_chip_curr_samples)
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 3/3] hwmon: ina238: add update_interval_us attribute
2026-06-09 19:43 ` Ferdinand Schwenk
@ 2026-06-09 19:43 ` Ferdinand Schwenk
-1 siblings, 0 replies; 14+ messages in thread
From: Ferdinand Schwenk via B4 Relay @ 2026-06-09 19:43 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
Ferdinand Schwenk
From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
The INA238 family supports eight conversion time steps from 50 us to
4120 us (SQ52206: 66 us to 8230 us). At the millisecond granularity of
update_interval, the four shortest steps (50, 84, 150, 280 us) all
round to the same value and cannot be individually selected.
Add support for the generic update_interval_us attribute, which reports
and programs the same ADC cycle time as update_interval but in
microseconds, giving userspace full access to all conversion time steps.
Both attributes reflect the total cycle time including the active
averaging count: the reported value is the raw conversion time
multiplied by the number of averaged samples, and writes apply the
inverse mapping.
Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
---
Documentation/hwmon/ina238.rst | 4 +++
drivers/hwmon/ina238.c | 70 ++++++++++++++++++++++++++----------------
2 files changed, 48 insertions(+), 26 deletions(-)
diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst
index 43950d1ec551..a75b79e17d9d 100644
--- a/Documentation/hwmon/ina238.rst
+++ b/Documentation/hwmon/ina238.rst
@@ -106,4 +106,8 @@ energy1_input Energy measurement (uJ)
temp1_input Die temperature measurement (mC)
temp1_max Maximum die temperature threshold (mC)
temp1_max_alarm Maximum die temperature alarm
+
+samples ADC averaging count (1, 4, 16, 64, 128, 256, 512, 1024)
+update_interval Total ADC conversion cycle time including averaging (ms)
+update_interval_us Total ADC conversion cycle time including averaging (us)
======================= =======================================================
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c
index dc5dd3ad2557..080a93fcc9f7 100644
--- a/drivers/hwmon/ina238.c
+++ b/drivers/hwmon/ina238.c
@@ -316,19 +316,36 @@ static inline u32 ina238_samples(struct ina238_data *data)
INA238_ADC_CONFIG_AVG_SHIFT];
}
-/* Converting update_interval in msec to a single conversion time in usec */
-static inline u32 ina238_interval_ms_to_conv_time(long interval, u32 samples)
+/* Converting update_interval(_us) to a per-field conversion time in usec.
+ * interval_us is the total ADC cycle time including averaging in microseconds.
+ * All three conversion fields (VBUSCT, VSHCT, VTCT) are set equal, so the
+ * per-field time is interval_us / (samples * 3).
+ */
+static inline u32 ina238_interval_us_to_conv_time(u32 interval_us, u32 samples)
{
- u64 interval_us;
+ return DIV_ROUND_CLOSEST_ULL(interval_us, samples * 3);
+}
- interval = clamp_val(interval, 0, INT_MAX / 1000);
- interval_us = (u64)interval * 1000;
+/* Write a per-field conversion time (in usec) to the ADC_CONFIG register */
+static int ina238_write_conv_time(struct ina238_data *data, u32 conv_time_us)
+{
+ u16 adc_config;
+ int idx, ret;
- /*
- * update_interval reports the ADC cycle time including averaging.
- * The target per-field conversion time is interval_us / (samples * 3).
- */
- return DIV_ROUND_CLOSEST_ULL(interval_us, samples * 3);
+ idx = find_closest(conv_time_us, data->config->conv_time,
+ ARRAY_SIZE(ina238_conv_time));
+ adc_config = (data->adc_config &
+ ~(INA238_ADC_CONFIG_VBUSCT_MASK |
+ INA238_ADC_CONFIG_VSHCT_MASK |
+ INA238_ADC_CONFIG_VTCT_MASK)) |
+ ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
}
static int ina238_read_chip(struct device *dev, u32 attr, long *val)
@@ -344,6 +361,10 @@ static int ina238_read_chip(struct device *dev, u32 attr, long *val)
*val = DIV_ROUND_CLOSEST(ina238_reg_to_interval_us(data) *
ina238_samples(data), 1000);
return 0;
+ case hwmon_chip_update_interval_us:
+ /* Return in usec */
+ *val = ina238_reg_to_interval_us(data) * ina238_samples(data);
+ return 0;
default:
return -EOPNOTSUPP;
}
@@ -367,21 +388,16 @@ static int ina238_write_chip(struct device *dev, u32 attr, long val)
data->adc_config = adc_config;
return 0;
case hwmon_chip_update_interval:
- val = ina238_interval_ms_to_conv_time(val, ina238_samples(data));
- idx = find_closest(val, data->config->conv_time,
- ARRAY_SIZE(ina238_conv_time));
- adc_config = (data->adc_config &
- ~(INA238_ADC_CONFIG_VBUSCT_MASK |
- INA238_ADC_CONFIG_VSHCT_MASK |
- INA238_ADC_CONFIG_VTCT_MASK)) |
- ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
- ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
- ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
- ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
- if (ret)
- return ret;
- data->adc_config = adc_config;
- return 0;
+ /* Convert ms to us before passing to the shared helper */
+ val = clamp_val(val, 0, INT_MAX / 1000) * 1000;
+ return ina238_write_conv_time(data,
+ ina238_interval_us_to_conv_time((u32)val,
+ ina238_samples(data)));
+ case hwmon_chip_update_interval_us:
+ val = clamp_val(val, 0, INT_MAX);
+ return ina238_write_conv_time(data,
+ ina238_interval_us_to_conv_time((u32)val,
+ ina238_samples(data)));
default:
return -EOPNOTSUPP;
}
@@ -763,6 +779,7 @@ static umode_t ina238_is_visible(const void *drvdata,
switch (attr) {
case hwmon_chip_samples:
case hwmon_chip_update_interval:
+ case hwmon_chip_update_interval_us:
return 0644;
default:
return 0;
@@ -831,7 +848,8 @@ static umode_t ina238_is_visible(const void *drvdata,
static const struct hwmon_channel_info * const ina238_info[] = {
HWMON_CHANNEL_INFO(chip,
- HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL),
+ HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL |
+ HWMON_C_UPDATE_INTERVAL_US),
HWMON_CHANNEL_INFO(in,
/* 0: shunt voltage */
INA238_HWMON_IN_CONFIG,
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 3/3] hwmon: ina238: add update_interval_us attribute
@ 2026-06-09 19:43 ` Ferdinand Schwenk
0 siblings, 0 replies; 14+ messages in thread
From: Ferdinand Schwenk @ 2026-06-09 19:43 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
Ferdinand Schwenk
The INA238 family supports eight conversion time steps from 50 us to
4120 us (SQ52206: 66 us to 8230 us). At the millisecond granularity of
update_interval, the four shortest steps (50, 84, 150, 280 us) all
round to the same value and cannot be individually selected.
Add support for the generic update_interval_us attribute, which reports
and programs the same ADC cycle time as update_interval but in
microseconds, giving userspace full access to all conversion time steps.
Both attributes reflect the total cycle time including the active
averaging count: the reported value is the raw conversion time
multiplied by the number of averaged samples, and writes apply the
inverse mapping.
Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
---
Documentation/hwmon/ina238.rst | 4 +++
drivers/hwmon/ina238.c | 70 ++++++++++++++++++++++++++----------------
2 files changed, 48 insertions(+), 26 deletions(-)
diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst
index 43950d1ec551..a75b79e17d9d 100644
--- a/Documentation/hwmon/ina238.rst
+++ b/Documentation/hwmon/ina238.rst
@@ -106,4 +106,8 @@ energy1_input Energy measurement (uJ)
temp1_input Die temperature measurement (mC)
temp1_max Maximum die temperature threshold (mC)
temp1_max_alarm Maximum die temperature alarm
+
+samples ADC averaging count (1, 4, 16, 64, 128, 256, 512, 1024)
+update_interval Total ADC conversion cycle time including averaging (ms)
+update_interval_us Total ADC conversion cycle time including averaging (us)
======================= =======================================================
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c
index dc5dd3ad2557..080a93fcc9f7 100644
--- a/drivers/hwmon/ina238.c
+++ b/drivers/hwmon/ina238.c
@@ -316,19 +316,36 @@ static inline u32 ina238_samples(struct ina238_data *data)
INA238_ADC_CONFIG_AVG_SHIFT];
}
-/* Converting update_interval in msec to a single conversion time in usec */
-static inline u32 ina238_interval_ms_to_conv_time(long interval, u32 samples)
+/* Converting update_interval(_us) to a per-field conversion time in usec.
+ * interval_us is the total ADC cycle time including averaging in microseconds.
+ * All three conversion fields (VBUSCT, VSHCT, VTCT) are set equal, so the
+ * per-field time is interval_us / (samples * 3).
+ */
+static inline u32 ina238_interval_us_to_conv_time(u32 interval_us, u32 samples)
{
- u64 interval_us;
+ return DIV_ROUND_CLOSEST_ULL(interval_us, samples * 3);
+}
- interval = clamp_val(interval, 0, INT_MAX / 1000);
- interval_us = (u64)interval * 1000;
+/* Write a per-field conversion time (in usec) to the ADC_CONFIG register */
+static int ina238_write_conv_time(struct ina238_data *data, u32 conv_time_us)
+{
+ u16 adc_config;
+ int idx, ret;
- /*
- * update_interval reports the ADC cycle time including averaging.
- * The target per-field conversion time is interval_us / (samples * 3).
- */
- return DIV_ROUND_CLOSEST_ULL(interval_us, samples * 3);
+ idx = find_closest(conv_time_us, data->config->conv_time,
+ ARRAY_SIZE(ina238_conv_time));
+ adc_config = (data->adc_config &
+ ~(INA238_ADC_CONFIG_VBUSCT_MASK |
+ INA238_ADC_CONFIG_VSHCT_MASK |
+ INA238_ADC_CONFIG_VTCT_MASK)) |
+ ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
}
static int ina238_read_chip(struct device *dev, u32 attr, long *val)
@@ -344,6 +361,10 @@ static int ina238_read_chip(struct device *dev, u32 attr, long *val)
*val = DIV_ROUND_CLOSEST(ina238_reg_to_interval_us(data) *
ina238_samples(data), 1000);
return 0;
+ case hwmon_chip_update_interval_us:
+ /* Return in usec */
+ *val = ina238_reg_to_interval_us(data) * ina238_samples(data);
+ return 0;
default:
return -EOPNOTSUPP;
}
@@ -367,21 +388,16 @@ static int ina238_write_chip(struct device *dev, u32 attr, long val)
data->adc_config = adc_config;
return 0;
case hwmon_chip_update_interval:
- val = ina238_interval_ms_to_conv_time(val, ina238_samples(data));
- idx = find_closest(val, data->config->conv_time,
- ARRAY_SIZE(ina238_conv_time));
- adc_config = (data->adc_config &
- ~(INA238_ADC_CONFIG_VBUSCT_MASK |
- INA238_ADC_CONFIG_VSHCT_MASK |
- INA238_ADC_CONFIG_VTCT_MASK)) |
- ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
- ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
- ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
- ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
- if (ret)
- return ret;
- data->adc_config = adc_config;
- return 0;
+ /* Convert ms to us before passing to the shared helper */
+ val = clamp_val(val, 0, INT_MAX / 1000) * 1000;
+ return ina238_write_conv_time(data,
+ ina238_interval_us_to_conv_time((u32)val,
+ ina238_samples(data)));
+ case hwmon_chip_update_interval_us:
+ val = clamp_val(val, 0, INT_MAX);
+ return ina238_write_conv_time(data,
+ ina238_interval_us_to_conv_time((u32)val,
+ ina238_samples(data)));
default:
return -EOPNOTSUPP;
}
@@ -763,6 +779,7 @@ static umode_t ina238_is_visible(const void *drvdata,
switch (attr) {
case hwmon_chip_samples:
case hwmon_chip_update_interval:
+ case hwmon_chip_update_interval_us:
return 0644;
default:
return 0;
@@ -831,7 +848,8 @@ static umode_t ina238_is_visible(const void *drvdata,
static const struct hwmon_channel_info * const ina238_info[] = {
HWMON_CHANNEL_INFO(chip,
- HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL),
+ HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL |
+ HWMON_C_UPDATE_INTERVAL_US),
HWMON_CHANNEL_INFO(in,
/* 0: shunt voltage */
INA238_HWMON_IN_CONFIG,
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/3] hwmon: Add update_interval_us chip attribute
2026-06-09 19:43 ` Ferdinand Schwenk
(?)
@ 2026-06-09 19:48 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-06-09 19:48 UTC (permalink / raw)
To: Ferdinand Schwenk via B4 Relay; +Cc: linux-hwmon
> From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
>
> Some hardware monitoring chips support update intervals below one
> millisecond. The existing update_interval attribute uses millisecond
> granularity, which causes sub-millisecond steps to round to the same
> value and become inaccessible from userspace.
>
> Introduce update_interval_us, a companion chip-level attribute that
> expresses the same update interval in microseconds. Drivers
> implementing this attribute should also implement update_interval for
> compatibility with millisecond-based userspace interfaces.
>
> Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260609-hwmon-ina238-update-interval-us-v2-v3-0-016b55567950@advastore.com?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/3] hwmon: ina238: add update_interval_us attribute
2026-06-09 19:43 ` Ferdinand Schwenk
(?)
@ 2026-06-09 19:49 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-06-09 19:49 UTC (permalink / raw)
To: Ferdinand Schwenk via B4 Relay; +Cc: linux-hwmon
> From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
>
> The INA238 family supports eight conversion time steps from 50 us to
> 4120 us (SQ52206: 66 us to 8230 us). At the millisecond granularity of
> update_interval, the four shortest steps (50, 84, 150, 280 us) all
> round to the same value and cannot be individually selected.
>
> Add support for the generic update_interval_us attribute, which reports
> and programs the same ADC cycle time as update_interval but in
> microseconds, giving userspace full access to all conversion time steps.
>
> Both attributes reflect the total cycle time including the active
> averaging count: the reported value is the raw conversion time
> multiplied by the number of averaged samples, and writes apply the
> inverse mapping.
>
> Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260609-hwmon-ina238-update-interval-us-v2-v3-0-016b55567950@advastore.com?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 1/3] hwmon: ina238: add support for samples and update_interval
2026-06-09 19:43 ` Ferdinand Schwenk
(?)
@ 2026-06-09 19:50 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-06-09 19:50 UTC (permalink / raw)
To: Ferdinand Schwenk via B4 Relay; +Cc: linux-hwmon
> From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
>
> Expose INA238 ADC averaging count (AVG) and conversion timing
> (VBUSCT/VSHCT/VTCT) through chip-level hwmon attributes:
>
> chip/samples
> chip/update_interval
>
> Use per-chip conversion-time lookup tables so the same helpers work
> for INA228/INA237/INA238/INA700/INA780 and SQ52206. Cache ADC_CONFIG
> in driver data and update it on writes to avoid extra register reads
> during read-modify-write updates.
>
> Report update_interval in milliseconds as required by the hwmon ABI.
> Compute it from raw ADC cycle time multiplied by the active averaging
> count, and apply the inverse mapping on writes so programmed conversion
> time tracks the selected sample count.
>
> Clamp user-provided update_interval before unit scaling to prevent
> overflow in arithmetic conversions.
>
> Also combine chip attributes in HWMON_CHANNEL_INFO using a bitwise OR
> for a single logical chip channel.
>
> Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260609-hwmon-ina238-update-interval-us-v2-v3-0-016b55567950@advastore.com?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 1/3] hwmon: ina238: add support for samples and update_interval
2026-06-09 19:43 ` Ferdinand Schwenk
(?)
(?)
@ 2026-06-09 20:44 ` Guenter Roeck
-1 siblings, 0 replies; 14+ messages in thread
From: Guenter Roeck @ 2026-06-09 20:44 UTC (permalink / raw)
To: Ferdinand Schwenk
Cc: Jonathan Corbet, Shuah Khan, linux-hwmon, linux-kernel, linux-doc,
richard.leitner
On Tue, Jun 09, 2026 at 09:43:10PM +0200, Ferdinand Schwenk wrote:
> From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
>
> Expose INA238 ADC averaging count (AVG) and conversion timing
> (VBUSCT/VSHCT/VTCT) through chip-level hwmon attributes:
>
> chip/samples
> chip/update_interval
>
> Use per-chip conversion-time lookup tables so the same helpers work
> for INA228/INA237/INA238/INA700/INA780 and SQ52206. Cache ADC_CONFIG
> in driver data and update it on writes to avoid extra register reads
> during read-modify-write updates.
>
> Report update_interval in milliseconds as required by the hwmon ABI.
> Compute it from raw ADC cycle time multiplied by the active averaging
> count, and apply the inverse mapping on writes so programmed conversion
> time tracks the selected sample count.
>
> Clamp user-provided update_interval before unit scaling to prevent
> overflow in arithmetic conversions.
>
> Also combine chip attributes in HWMON_CHANNEL_INFO using a bitwise OR
> for a single logical chip channel.
>
> Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Applied.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/3] hwmon: Add update_interval_us chip attribute
2026-06-09 19:43 ` Ferdinand Schwenk
(?)
(?)
@ 2026-06-09 20:45 ` Guenter Roeck
-1 siblings, 0 replies; 14+ messages in thread
From: Guenter Roeck @ 2026-06-09 20:45 UTC (permalink / raw)
To: Ferdinand Schwenk
Cc: Jonathan Corbet, Shuah Khan, linux-hwmon, linux-kernel, linux-doc,
richard.leitner
On Tue, Jun 09, 2026 at 09:43:11PM +0200, Ferdinand Schwenk wrote:
> From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
>
> Some hardware monitoring chips support update intervals below one
> millisecond. The existing update_interval attribute uses millisecond
> granularity, which causes sub-millisecond steps to round to the same
> value and become inaccessible from userspace.
>
> Introduce update_interval_us, a companion chip-level attribute that
> expresses the same update interval in microseconds. Drivers
> implementing this attribute should also implement update_interval for
> compatibility with millisecond-based userspace interfaces.
>
> Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Applied.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/3] hwmon: ina238: add update_interval_us attribute
2026-06-09 19:43 ` Ferdinand Schwenk
(?)
(?)
@ 2026-06-09 20:49 ` Guenter Roeck
-1 siblings, 0 replies; 14+ messages in thread
From: Guenter Roeck @ 2026-06-09 20:49 UTC (permalink / raw)
To: Ferdinand Schwenk
Cc: Jonathan Corbet, Shuah Khan, linux-hwmon, linux-kernel, linux-doc,
richard.leitner
On Tue, Jun 09, 2026 at 09:43:12PM +0200, Ferdinand Schwenk wrote:
> From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
>
> The INA238 family supports eight conversion time steps from 50 us to
> 4120 us (SQ52206: 66 us to 8230 us). At the millisecond granularity of
> update_interval, the four shortest steps (50, 84, 150, 280 us) all
> round to the same value and cannot be individually selected.
>
> Add support for the generic update_interval_us attribute, which reports
> and programs the same ADC cycle time as update_interval but in
> microseconds, giving userspace full access to all conversion time steps.
>
> Both attributes reflect the total cycle time including the active
> averaging count: the reported value is the raw conversion time
> multiplied by the number of averaged samples, and writes apply the
> inverse mapping.
>
> Signed-off-by: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Applied.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-06-09 20:49 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-09 19:43 [PATCH v3 0/3] hwmon: ina238: add samples and update_interval_us support Ferdinand Schwenk via B4 Relay
2026-06-09 19:43 ` Ferdinand Schwenk
2026-06-09 19:43 ` [PATCH v3 1/3] hwmon: ina238: add support for samples and update_interval Ferdinand Schwenk via B4 Relay
2026-06-09 19:43 ` Ferdinand Schwenk
2026-06-09 19:50 ` sashiko-bot
2026-06-09 20:44 ` Guenter Roeck
2026-06-09 19:43 ` [PATCH v3 2/3] hwmon: Add update_interval_us chip attribute Ferdinand Schwenk via B4 Relay
2026-06-09 19:43 ` Ferdinand Schwenk
2026-06-09 19:48 ` sashiko-bot
2026-06-09 20:45 ` Guenter Roeck
2026-06-09 19:43 ` [PATCH v3 3/3] hwmon: ina238: add update_interval_us attribute Ferdinand Schwenk via B4 Relay
2026-06-09 19:43 ` Ferdinand Schwenk
2026-06-09 19:49 ` sashiko-bot
2026-06-09 20:49 ` Guenter Roeck
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.