* [PATCH v2 0/2] iio: adc: add support for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
@ 2026-09-24 13:14 Neil Armstrong
2026-09-24 13:14 ` [PATCH v2 1/2] dt-bindings: iio: add: document " Neil Armstrong
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Neil Armstrong @ 2026-09-24 13:14 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, devicetree, linux-kernel, Neil Armstrong
The MAX34417 is a specialized current and voltage monitor used to determine
power consumption of portable systems. The driver support getting the channel
voltage and accumulated average power over an I2C/SMBUS serial interface.
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
---
Changes in v2:
- switch to shunt-resistor-micro-ohms no more required
- removed gpio.h from example
- Fixed max34417->MAX34417 in Kconfig and comments
- Added missing includes and remove unneeded
- Fixed typos in comments
- Switched to fsleep()
- Better aligned max34417_read_power declararation
- Handled 0 acc_count
- Switched to GENMASK_ULL() for 32bits systems
- Added missing empty lines
- Moved the input correction into a helper
- Set default input correction for all channels
- Switched to dev_err_probe() to return from probe
- Switched to device_for_each_child_node_scoped()
- Handled invalid shunt-resistor-micro-ohms value
- Link to v1: https://patch.msgid.link/20260923-topic-sm8x50-iio-max34417-adc-v1-0-41d4ba1bfc41@linaro.org
---
Neil Armstrong (2):
dt-bindings: iio: add: document the MAX34417 Four-Channel High Dynamic Range Power Accumulator
iio: adc: add driver for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
.../bindings/iio/adc/maxim,max34417.yaml | 100 ++++++
drivers/iio/adc/Kconfig | 11 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/max34417.c | 374 +++++++++++++++++++++
4 files changed, 486 insertions(+)
---
base-commit: fd73f4a6659897191fa0d40695fe370925dd3780
change-id: 20260923-topic-sm8x50-iio-max34417-adc-209e880533fb
Best regards,
--
Neil Armstrong <neil.armstrong@linaro.org>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 1/2] dt-bindings: iio: add: document the MAX34417 Four-Channel High Dynamic Range Power Accumulator
2026-09-24 13:14 [PATCH v2 0/2] iio: adc: add support for the MAX34417 Four-Channel High Dynamic Range Power Accumulator Neil Armstrong
@ 2026-09-24 13:14 ` Neil Armstrong
2026-09-24 16:43 ` Conor Dooley
2026-09-24 13:14 ` [PATCH v2 2/2] iio: adc: add driver for " Neil Armstrong
2026-09-25 3:13 ` [PATCH v2 0/2] iio: adc: add support " Jonathan Cameron
2 siblings, 1 reply; 9+ messages in thread
From: Neil Armstrong @ 2026-09-24 13:14 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, devicetree, linux-kernel, Neil Armstrong
Document the Maxim MAX34417 Four-Channel High Dynamic Range Power
Accumulator used to monitor power consumption of portable systems.
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
---
.../bindings/iio/adc/maxim,max34417.yaml | 100 +++++++++++++++++++++
1 file changed, 100 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/maxim,max34417.yaml b/Documentation/devicetree/bindings/iio/adc/maxim,max34417.yaml
new file mode 100644
index 000000000000..058af61cc04e
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/maxim,max34417.yaml
@@ -0,0 +1,100 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/maxim,max34417.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX34417 Four-Channel High Dynamic Range Power Accumulator
+
+maintainers:
+ - Neil Armstrong <neil.armstrong@linaro.org>
+
+description: |
+ The MAX34417 is a specialized current and voltage monitor used to determine
+ power consumption of portable systems. The device has a very wide dynamic
+ range (20,000:1) that allows for the accurate measurement of power in such
+ systems. The device is configured and monitored with a standard I2C/SMBus
+ serial interface. The unidirectional current sensor offers precision
+ high-side operation with a low full-scale sense voltage.
+
+ Specifications about the device can be found at:
+ https://www.analog.com/media/en/technical-documentation/data-sheets/max34417.pdf
+
+properties:
+ compatible:
+ const: maxim,max34417
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ reg:
+ maxItems: 1
+
+ vdd-supply: true
+ vio-supply: true
+
+patternProperties:
+ "^channel@[0-3]$":
+ $ref: adc.yaml
+ type: object
+ description:
+ Represents the internal channels of the sensor.
+
+ properties:
+ reg:
+ items:
+ - minimum: 0
+ maximum: 3
+
+ label: true
+
+ shunt-resistor-micro-ohms:
+ description:
+ Value in micro Ohms of the shunt resistor connected between the RS+ and RS- inputs.
+ minimum: 1000
+ maximum: 100000
+ default: 1000
+
+ required:
+ - reg
+
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ sensor@10 {
+ compatible = "maxim,max34417";
+ reg = <0x10>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ channel@0 {
+ reg = <0>;
+ label = "ch0";
+ shunt-resistor-micro-ohms = <5000>;
+ };
+
+ channel@1 {
+ reg = <1>;
+ label = "ch1";
+ };
+
+ channel@2 {
+ reg = <2>;
+ };
+ };
+ };
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 2/2] iio: adc: add driver for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
2026-09-24 13:14 [PATCH v2 0/2] iio: adc: add support for the MAX34417 Four-Channel High Dynamic Range Power Accumulator Neil Armstrong
2026-09-24 13:14 ` [PATCH v2 1/2] dt-bindings: iio: add: document " Neil Armstrong
@ 2026-09-24 13:14 ` Neil Armstrong
2026-09-24 15:13 ` Joshua Crofts
2026-09-25 3:26 ` Jonathan Cameron
2026-09-25 3:13 ` [PATCH v2 0/2] iio: adc: add support " Jonathan Cameron
2 siblings, 2 replies; 9+ messages in thread
From: Neil Armstrong @ 2026-09-24 13:14 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, devicetree, linux-kernel, Neil Armstrong
The MAX34417 is a specialized current and voltage monitor used to
determine power consumption of portable systems. The driver support
getting the channels voltage and accumulated average power over an
I2C/SMBUS serial interface.
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
---
drivers/iio/adc/Kconfig | 11 ++
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/max34417.c | 374 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 386 insertions(+)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 415e519ad4eb..15360c78d3e5 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1116,6 +1116,17 @@ config MAX34408
To compile this driver as a module, choose M here: the module will be
called max34408.
+config MAX34417
+ tristate "Maxim MAX34417 ADC driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say yes here to build ADC support for Maxim MAX34417 Four-Channel High
+ Dynamic Range Power Accumulator.
+
+ To compile this driver as a module, choose M here: the module will be
+ called max34417.
+
config MAX77541_ADC
tristate "Analog Devices MAX77541 ADC driver"
depends on MFD_MAX77541
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index dcec0abb03b7..c667e7ecbc53 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -95,6 +95,7 @@ obj-$(CONFIG_MAX1241) += max1241.o
obj-$(CONFIG_MAX1363) += max1363.o
obj-$(CONFIG_MAX14001) += max14001.o
obj-$(CONFIG_MAX34408) += max34408.o
+obj-$(CONFIG_MAX34417) += max34417.o
obj-$(CONFIG_MAX77541_ADC) += max77541-adc.o
obj-$(CONFIG_MAX9611) += max9611.o
obj-$(CONFIG_MCP320X) += mcp320x.o
diff --git a/drivers/iio/adc/max34417.c b/drivers/iio/adc/max34417.c
new file mode 100644
index 000000000000..98d961c5ecee
--- /dev/null
+++ b/drivers/iio/adc/max34417.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * IIO driver for Maxim MAX34417 ADC, 4-Channels High Dynamic Range Power Accumulator
+ *
+ * Datasheet: https://www.analog.com/en/products/max34417.html
+ *
+ * TODO: Slow Mode, Continuous Accumulate Mode, Park Feature, Bulk Update, Perr_Verr Correction
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/init.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/units.h>
+
+#define MAX34417_UPDATE_REG 0x0
+#define MAX34417_CONTROL_REG 0x1
+#define MAX34417_ACC_COUNT_REG 0x2
+
+#define MAX34417_PWR_ACC_1_REG 0x3
+#define MAX34417_PWR_ACC_2_REG 0x4
+#define MAX34417_PWR_ACC_3_REG 0x5
+#define MAX34417_PWR_ACC_4_REG 0x6
+
+#define MAX34417_V_CH1_REG 0x7
+#define MAX34417_V_CH2_REG 0x8
+#define MAX34417_V_CH3_REG 0x9
+#define MAX34417_V_CH4_REG 0xa
+
+#define MAX34417_DID_REG 0xf
+
+#define MAX34417_BULK_POWER_READOUT_REG 0x10
+#define MAX34417_BULK_VOLTAGE_READOUT_REG 0x11
+
+#define MAX34417_BULK_UPDATE_ADDRESS 0x2c
+#define MAX34417_BULK_UPDATE_REG 0x0
+
+/* Bit masks for control register */
+#define MAX34417_CONTROL_OVF BIT(0)
+#define MAX34417_CONTROL_SLOW BIT(1)
+#define MAX34417_CONTROL_PARK0 BIT(2)
+#define MAX34417_CONTROL_PARK1 BIT(3)
+#define MAX34417_CONTROL_PARK_EN BIT(4)
+#define MAX34417_CONTROL_SMM BIT(5)
+#define MAX34417_CONTROL_CAM BIT(6)
+#define MAX34417_CONTROL_MODE BIT(7)
+
+#define MAX34417_DEFAULT_CMM_WIDE (MAX34417_CONTROL_MODE | MAX34417_CONTROL_SMM)
+
+#define MAX34417_DEFAULT_RSENSE 1000
+
+#define MAX34417_PWR_CORRECTION_SCALE 24
+#define MAX34417_PWR_AVG_FULL_SCALE_BITS 30
+
+#define MAX34417_VOLTAGE_CORRECTION_SCALE 24
+#define MAX34417_VOLTAGE_FULL_SCALE_BITS 14
+
+#define MAX34417_CHANNEL_COUNT 4
+
+/**
+ * struct max34417_data - MAX34417 specific data.
+ * @regmap: Device register map.
+ * @dev: MAX34417 device.
+ * @lock: Lock for protecting access to device hardware registers, mostly
+ * for reading common accumulator count and control register.
+ * @input_correction: Correction based on the Rsense value from channel nodes.
+ * @input_label: Channel label from channel nodes.
+ */
+struct max34417_data {
+ struct regmap *regmap;
+ struct device *dev;
+ struct mutex lock;
+ u32 input_correction[MAX34417_CHANNEL_COUNT];
+ const char *input_label[MAX34417_CHANNEL_COUNT];
+};
+
+static const struct regmap_config max34417_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX34417_DID_REG,
+};
+
+#define MAX34417_CHANNEL(_index, _v_address, _power_address) \
+ { \
+ .type = IIO_VOLTAGE, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = (_index), \
+ .address = (_v_address), \
+ .indexed = 1, \
+ }, \
+ { \
+ .type = IIO_POWER, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = (_index), \
+ .address = (_power_address), \
+ .indexed = 1, \
+ }
+
+static const struct iio_chan_spec max34417_channels[] = {
+ MAX34417_CHANNEL(0, MAX34417_V_CH1_REG, MAX34417_PWR_ACC_1_REG),
+ MAX34417_CHANNEL(1, MAX34417_V_CH2_REG, MAX34417_PWR_ACC_2_REG),
+ MAX34417_CHANNEL(2, MAX34417_V_CH3_REG, MAX34417_PWR_ACC_3_REG),
+ MAX34417_CHANNEL(3, MAX34417_V_CH4_REG, MAX34417_PWR_ACC_4_REG),
+};
+
+/* TODO Implement trigger to update accumulator once and get all channels at once */
+
+static int max34417_accumulator_update(struct max34417_data *max34417)
+{
+ int rc;
+
+ rc = regmap_write(max34417->regmap, MAX34417_UPDATE_REG, 1);
+ if (rc) {
+ dev_err(max34417->dev, "Error (%d) writing update register\n", rc);
+ return rc;
+ }
+
+ /* Wait for accumulator update */
+ fsleep(1000);
+
+ return 0;
+}
+
+static int max34417_read_voltage(struct max34417_data *max34417,
+ const struct iio_chan_spec *chan, int *val)
+{
+ uint16_t voltage;
+ uint8_t buf[3];
+ int rc;
+
+ guard(mutex)(&max34417->lock);
+
+ rc = max34417_accumulator_update(max34417);
+ if (rc)
+ return rc;
+
+ rc = regmap_noinc_read(max34417->regmap, chan->address, &buf, 3);
+ if (rc)
+ return rc;
+
+ voltage = buf[2] | ((uint64_t)buf[1] << 8);
+ voltage >>= 2;
+
+ *val = voltage;
+
+ return IIO_VAL_INT;
+}
+
+static int max34417_read_power(struct max34417_data *max34417,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2)
+{
+ uint32_t acc_count;
+ uint64_t power;
+ uint8_t buf[8];
+ int rc;
+
+ guard(mutex)(&max34417->lock);
+
+ rc = max34417_accumulator_update(max34417);
+ if (rc)
+ return rc;
+
+ rc = regmap_noinc_read(max34417->regmap, MAX34417_ACC_COUNT_REG,
+ &buf, 4);
+ if (rc)
+ return rc;
+
+ acc_count = buf[3] | ((uint64_t)buf[2] << 8) | ((uint64_t)buf[1] << 16);
+ if (!acc_count)
+ return -EIO;
+
+ rc = regmap_noinc_read(max34417->regmap, chan->address, &buf, 8);
+ if (rc)
+ return rc;
+
+ power = buf[7];
+ power |= ((uint64_t)buf[6] << 8UL);
+ power |= ((uint64_t)buf[5] << 16UL);
+ power |= ((uint64_t)buf[4] << 24UL);
+ power |= ((uint64_t)buf[3] << 32UL);
+ power |= ((uint64_t)buf[2] << 40UL);
+ power |= ((uint64_t)buf[1] << 48UL);
+
+ power = div_u64(power, acc_count);
+
+ *val = FIELD_GET(GENMASK_ULL(31, 0), power);
+ *val2 = FIELD_GET(GENMASK_ULL(55, 32), power);
+
+ return IIO_VAL_INT_64;
+}
+
+static int max34417_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max34417_data *max34417 = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type == IIO_VOLTAGE)
+ return max34417_read_voltage(max34417, chan, val);
+
+ return -EINVAL;
+ case IIO_CHAN_INFO_AVERAGE_RAW:
+ if (chan->type == IIO_POWER)
+ return max34417_read_power(max34417, chan, val, val2);
+
+ return -EINVAL;
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_VOLTAGE) {
+ /* Scale to mA */
+ *val = MAX34417_VOLTAGE_CORRECTION_SCALE * MILLI;
+ *val2 = MAX34417_VOLTAGE_FULL_SCALE_BITS;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+ } else if (chan->type == IIO_POWER) {
+ /* Scale to mW */
+ *val = max34417->input_correction[chan->channel] * MILLI;
+ *val2 = MAX34417_PWR_AVG_FULL_SCALE_BITS;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+ }
+
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max34417_read_label(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ char *label)
+{
+ struct max34417_data *max34417 = iio_priv(indio_dev);
+ const char *input_label = max34417->input_label[chan->channel];
+
+ if (chan->type == IIO_VOLTAGE) {
+ if (input_label)
+ return sysfs_emit(label, "%s-voltage\n", input_label);
+
+ return sysfs_emit(label, "channel%d-voltage\n", chan->channel);
+ }
+
+ if (chan->type == IIO_POWER) {
+ if (input_label)
+ return sysfs_emit(label, "%s-power\n", input_label);
+
+ return sysfs_emit(label, "channel%d-power\n", chan->channel);
+ }
+
+ return 0;
+}
+
+static const struct iio_info max34417_info = {
+ .read_raw = max34417_read_raw,
+ .read_label = max34417_read_label,
+};
+
+static unsigned int max34417_calc_input_correction(u32 rsense)
+{
+ /* (100 milliOhm / rsense) * MAX34417_PWR_CORRECTION_SCALE */
+ return (100 * MILLI * MAX34417_PWR_CORRECTION_SCALE) / rsense;
+}
+
+static int max34417_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct max34417_data *max34417;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ int rc, i;
+
+ regmap = devm_regmap_init_i2c(client, &max34417_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "regmap_init failed\n");
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*max34417));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ rc = devm_regulator_get_enable(dev, "vdd");
+ if (rc)
+ return dev_err_probe(dev, rc, "failed to get vdd regulator\n");
+
+ rc = devm_regulator_get_enable(dev, "vio");
+ if (rc)
+ return dev_err_probe(dev, rc, "failed to get vio regulator\n");
+
+ max34417 = iio_priv(indio_dev);
+ max34417->regmap = regmap;
+ max34417->dev = dev;
+ mutex_init(&max34417->lock);
+
+ /* Set default input correction for all channels */
+ for (i = 0; i < MAX34417_CHANNEL_COUNT; ++i)
+ max34417->input_correction[i] =
+ max34417_calc_input_correction(MAX34417_DEFAULT_RSENSE);
+
+ device_for_each_child_node_scoped(dev, node) {
+ u32 rsense, index;
+
+ if (fwnode_property_read_u32(node, "reg", &index))
+ return dev_err_probe(dev, -EINVAL, "missing reg property of %pfwP\n",
+ node);
+ else if (index >= MAX34417_CHANNEL_COUNT)
+ return dev_err_probe(dev, -EINVAL, "invalid reg %d of %pfwP\n",
+ index, node);
+
+ fwnode_property_read_string(node, "label", &max34417->input_label[index]);
+
+ rc = fwnode_property_read_u32(node, "shunt-resistor-micro-ohms", &rsense);
+ if (!rc) {
+ if (!rsense || rsense < 1000 || rsense > 100000)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid shunt value %d of %pfwP\n",
+ rsense, node);
+
+ max34417->input_correction[index] =
+ max34417_calc_input_correction(rsense);
+ }
+ }
+
+ indio_dev->channels = max34417_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max34417_channels);
+ indio_dev->name = "max34417";
+ indio_dev->info = &max34417_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ /* Set as default Manual Mode & Wide ADC */
+ rc = regmap_write(max34417->regmap, MAX34417_CONTROL_REG, MAX34417_DEFAULT_CMM_WIDE);
+ if (rc)
+ return dev_err_probe(max34417->dev, rc, "Error writing control register\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id max34417_of_match[] = {
+ { .compatible = "maxim,max34417" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max34417_of_match);
+
+static const struct i2c_device_id max34417_id[] = {
+ { .name = "max34417" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max34417_id);
+
+static struct i2c_driver max34417_driver = {
+ .driver = {
+ .name = "max34417",
+ .of_match_table = max34417_of_match,
+ },
+ .probe = max34417_probe,
+ .id_table = max34417_id,
+};
+module_i2c_driver(max34417_driver);
+
+MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>");
+MODULE_DESCRIPTION("Maxim MAX34417 ADC driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 2/2] iio: adc: add driver for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
2026-09-24 13:14 ` [PATCH v2 2/2] iio: adc: add driver for " Neil Armstrong
@ 2026-09-24 15:13 ` Joshua Crofts
2026-09-25 3:26 ` Jonathan Cameron
1 sibling, 0 replies; 9+ messages in thread
From: Joshua Crofts @ 2026-09-24 15:13 UTC (permalink / raw)
To: Neil Armstrong
Cc: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-iio, devicetree, linux-kernel
On Thu, 24 Sep 2026 15:14:16 +0200
Neil Armstrong <neil.armstrong@linaro.org> wrote:
> The MAX34417 is a specialized current and voltage monitor used to
> determine power consumption of portable systems. The driver support
> getting the channels voltage and accumulated average power over an
> I2C/SMBUS serial interface.
>
> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
> ---
Unfortunately you were too fast and there was another reply on v1 :(
--
Kind regards,
Joshua Crofts
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: iio: add: document the MAX34417 Four-Channel High Dynamic Range Power Accumulator
2026-09-24 13:14 ` [PATCH v2 1/2] dt-bindings: iio: add: document " Neil Armstrong
@ 2026-09-24 16:43 ` Conor Dooley
0 siblings, 0 replies; 9+ messages in thread
From: Conor Dooley @ 2026-09-24 16:43 UTC (permalink / raw)
To: Neil Armstrong
Cc: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-iio, devicetree, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 3931 bytes --]
On Thu, Sep 24, 2026 at 03:14:15PM +0200, Neil Armstrong wrote:
> Document the Maxim MAX34417 Four-Channel High Dynamic Range Power
> Accumulator used to monitor power consumption of portable systems.
>
> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
> ---
> .../bindings/iio/adc/maxim,max34417.yaml | 100 +++++++++++++++++++++
> 1 file changed, 100 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/maxim,max34417.yaml b/Documentation/devicetree/bindings/iio/adc/maxim,max34417.yaml
> new file mode 100644
> index 000000000000..058af61cc04e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/maxim,max34417.yaml
> @@ -0,0 +1,100 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/maxim,max34417.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Maxim MAX34417 Four-Channel High Dynamic Range Power Accumulator
> +
> +maintainers:
> + - Neil Armstrong <neil.armstrong@linaro.org>
> +
> +description: |
> + The MAX34417 is a specialized current and voltage monitor used to determine
> + power consumption of portable systems. The device has a very wide dynamic
> + range (20,000:1) that allows for the accurate measurement of power in such
> + systems. The device is configured and monitored with a standard I2C/SMBus
> + serial interface. The unidirectional current sensor offers precision
> + high-side operation with a low full-scale sense voltage.
> +
> + Specifications about the device can be found at:
> + https://www.analog.com/media/en/technical-documentation/data-sheets/max34417.pdf
> +
> +properties:
> + compatible:
> + const: maxim,max34417
ADI wants people to use their prefix for newly added devices,
particularly if their release is after the maxim purchase.
> +
> + "#address-cells":
> + const: 1
> +
> + "#size-cells":
> + const: 0
> +
> + reg:
> + maxItems: 1
Atypical ordering, usually reg is before these cells props.
> +
> + vdd-supply: true
> + vio-supply: true
> +
> +patternProperties:
> + "^channel@[0-3]$":
> + $ref: adc.yaml
> + type: object
> + description:
> + Represents the internal channels of the sensor.
> +
> + properties:
> + reg:
> + items:
> + - minimum: 0
> + maximum: 3
> +
> + label: true
This doesn't do anything, when you have unevalatedProperties: false.
If these are the only valid properties, use additionalProperties: false
instead. Otherwise, you can just remove this line.
> +
> + shunt-resistor-micro-ohms:
> + description:
> + Value in micro Ohms of the shunt resistor connected between the RS+ and RS- inputs.
> + minimum: 1000
> + maximum: 100000
> + default: 1000
> +
> + required:
> + - reg
> +
> + unevaluatedProperties: false
> +
> +required:
> + - compatible
> + - reg
Typically in IIO, supplies are made required unless the device can
operate without them.
Cheers,
Conor.
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + sensor@10 {
> + compatible = "maxim,max34417";
> + reg = <0x10>;
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + channel@0 {
> + reg = <0>;
> + label = "ch0";
> + shunt-resistor-micro-ohms = <5000>;
> + };
> +
> + channel@1 {
> + reg = <1>;
> + label = "ch1";
> + };
> +
> + channel@2 {
> + reg = <2>;
> + };
> + };
> + };
>
> --
> 2.34.1
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 0/2] iio: adc: add support for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
2026-09-24 13:14 [PATCH v2 0/2] iio: adc: add support for the MAX34417 Four-Channel High Dynamic Range Power Accumulator Neil Armstrong
2026-09-24 13:14 ` [PATCH v2 1/2] dt-bindings: iio: add: document " Neil Armstrong
2026-09-24 13:14 ` [PATCH v2 2/2] iio: adc: add driver for " Neil Armstrong
@ 2026-09-25 3:13 ` Jonathan Cameron
2026-09-25 8:12 ` Neil Armstrong
2 siblings, 1 reply; 9+ messages in thread
From: Jonathan Cameron @ 2026-09-25 3:13 UTC (permalink / raw)
To: Neil Armstrong
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-iio, devicetree, linux-kernel
On Thu, 24 Sep 2026 15:14:14 +0200
Neil Armstrong <neil.armstrong@linaro.org> wrote:
> The MAX34417 is a specialized current and voltage monitor used to determine
> power consumption of portable systems. The driver support getting the channel
> voltage and accumulated average power over an I2C/SMBUS serial interface.
>
> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Hi Neil,
As pointed out too fast for a v2. You aren't new upstream so
that shouldn't come as a surprise!
Secondly for devices that are all about monitoring power supplies
etc we always ask for a clear statement of why IIO rather than
hwmon + to CC at least the maintainer and often the hwmon list.
There are various valid reasons for that choice, but it is good
if they are clearly stated for discussion.
Thanks,
Jonathan
> ---
> Changes in v2:
> - switch to shunt-resistor-micro-ohms no more required
> - removed gpio.h from example
> - Fixed max34417->MAX34417 in Kconfig and comments
> - Added missing includes and remove unneeded
> - Fixed typos in comments
> - Switched to fsleep()
> - Better aligned max34417_read_power declararation
> - Handled 0 acc_count
> - Switched to GENMASK_ULL() for 32bits systems
> - Added missing empty lines
> - Moved the input correction into a helper
> - Set default input correction for all channels
> - Switched to dev_err_probe() to return from probe
> - Switched to device_for_each_child_node_scoped()
> - Handled invalid shunt-resistor-micro-ohms value
> - Link to v1: https://patch.msgid.link/20260923-topic-sm8x50-iio-max34417-adc-v1-0-41d4ba1bfc41@linaro.org
>
> ---
> Neil Armstrong (2):
> dt-bindings: iio: add: document the MAX34417 Four-Channel High Dynamic Range Power Accumulator
> iio: adc: add driver for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
>
> .../bindings/iio/adc/maxim,max34417.yaml | 100 ++++++
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/max34417.c | 374 +++++++++++++++++++++
> 4 files changed, 486 insertions(+)
> ---
> base-commit: fd73f4a6659897191fa0d40695fe370925dd3780
> change-id: 20260923-topic-sm8x50-iio-max34417-adc-209e880533fb
>
> Best regards,
> --
> Neil Armstrong <neil.armstrong@linaro.org>
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 2/2] iio: adc: add driver for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
2026-09-24 13:14 ` [PATCH v2 2/2] iio: adc: add driver for " Neil Armstrong
2026-09-24 15:13 ` Joshua Crofts
@ 2026-09-25 3:26 ` Jonathan Cameron
2026-09-25 8:46 ` Neil Armstrong
1 sibling, 1 reply; 9+ messages in thread
From: Jonathan Cameron @ 2026-09-25 3:26 UTC (permalink / raw)
To: Neil Armstrong
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-iio, devicetree, linux-kernel
On Thu, 24 Sep 2026 15:14:16 +0200
Neil Armstrong <neil.armstrong@linaro.org> wrote:
> The MAX34417 is a specialized current and voltage monitor used to
> determine power consumption of portable systems. The driver support
> getting the channels voltage and accumulated average power over an
> I2C/SMBUS serial interface.
>
> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
A few comments inline. For a new driver I'd wait a week before sending an
update. Whilst you've gotten quite a few reviews already it is good to make
sure any discussion has died down before moving on to the next version.
Thanks,
Jonathan
> diff --git a/drivers/iio/adc/max34417.c b/drivers/iio/adc/max34417.c
> new file mode 100644
> index 000000000000..98d961c5ecee
> --- /dev/null
> +++ b/drivers/iio/adc/max34417.c
> @@ -0,0 +1,374 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * IIO driver for Maxim MAX34417 ADC, 4-Channels High Dynamic Range Power Accumulator
> + *
> + * Datasheet: https://www.analog.com/en/products/max34417.html
> + *
> + * TODO: Slow Mode, Continuous Accumulate Mode, Park Feature, Bulk Update, Perr_Verr Correction
> + */
> +#define MAX34417_CHANNEL(_index, _v_address, _power_address) \
> + { \
> + .type = IIO_VOLTAGE, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_SCALE), \
> + .channel = (_index), \
> + .address = (_v_address), \
> + .indexed = 1, \
> + }, \
> + { \
> + .type = IIO_POWER, \
As below. This smells like it might not be an actual power channel if it
is accumulated on a fixed frequency. It'll be some sort of scaled IIO_ENERGY
channel. If you want to present it as power (which may make sense) then
it may need a little maths.
> + .info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \
> + BIT(IIO_CHAN_INFO_SCALE), \
> + .channel = (_index), \
> + .address = (_power_address), \
> + .indexed = 1, \
> + }
> +
> +static const struct iio_chan_spec max34417_channels[] = {
> + MAX34417_CHANNEL(0, MAX34417_V_CH1_REG, MAX34417_PWR_ACC_1_REG),
> + MAX34417_CHANNEL(1, MAX34417_V_CH2_REG, MAX34417_PWR_ACC_2_REG),
> + MAX34417_CHANNEL(2, MAX34417_V_CH3_REG, MAX34417_PWR_ACC_3_REG),
> + MAX34417_CHANNEL(3, MAX34417_V_CH4_REG, MAX34417_PWR_ACC_4_REG),
> +};
> +
> +/* TODO Implement trigger to update accumulator once and get all channels at once */
> +
> +static int max34417_accumulator_update(struct max34417_data *max34417)
> +{
> + int rc;
> +
> + rc = regmap_write(max34417->regmap, MAX34417_UPDATE_REG, 1);
> + if (rc) {
> + dev_err(max34417->dev, "Error (%d) writing update register\n", rc);
> + return rc;
> + }
> +
> + /* Wait for accumulator update */
> + fsleep(1000);
> +
> + return 0;
> +}
> +
> +static int max34417_read_voltage(struct max34417_data *max34417,
> + const struct iio_chan_spec *chan, int *val)
> +{
> + uint16_t voltage;
> + uint8_t buf[3];
> + int rc;
> +
> + guard(mutex)(&max34417->lock);
> +
> + rc = max34417_accumulator_update(max34417);
> + if (rc)
> + return rc;
> +
> + rc = regmap_noinc_read(max34417->regmap, chan->address, &buf, 3);
> + if (rc)
> + return rc;
> +
> + voltage = buf[2] | ((uint64_t)buf[1] << 8);
get_unaligned_be16();
> + voltage >>= 2;
> +
> + *val = voltage;
> +
> + return IIO_VAL_INT;
> +}
> +
> +static int max34417_read_power(struct max34417_data *max34417,
> + const struct iio_chan_spec *chan,
> + int *val, int *val2)
> +{
> + uint32_t acc_count;
> + uint64_t power;
> + uint8_t buf[8];
Kernel types so u32, u64, u8
> + int rc;
> +
> + guard(mutex)(&max34417->lock);
> +
> + rc = max34417_accumulator_update(max34417);
> + if (rc)
> + return rc;
> +
> + rc = regmap_noinc_read(max34417->regmap, MAX34417_ACC_COUNT_REG,
> + &buf, 4);
> + if (rc)
> + return rc;
> +
> + acc_count = buf[3] | ((uint64_t)buf[2] << 8) | ((uint64_t)buf[1] << 16);
get_unaligned_be24(buf);
> + if (!acc_count)
> + return -EIO;
> +
> + rc = regmap_noinc_read(max34417->regmap, chan->address, &buf, 8);
> + if (rc)
> + return rc;
> +
> + power = buf[7];
> + power |= ((uint64_t)buf[6] << 8UL);
> + power |= ((uint64_t)buf[5] << 16UL);
> + power |= ((uint64_t)buf[4] << 24UL);
> + power |= ((uint64_t)buf[3] << 32UL);
> + power |= ((uint64_t)buf[2] << 40UL);
> + power |= ((uint64_t)buf[1] << 48UL);
Hmm. i think this is the second 56 bit endian reader we've had
recently. Time to add get_unaligned_be56()
> +
> + power = div_u64(power, acc_count);
> +
> + *val = FIELD_GET(GENMASK_ULL(31, 0), power);
> + *val2 = FIELD_GET(GENMASK_ULL(55, 32), power);
> +
> + return IIO_VAL_INT_64;
> +}
> +
> +static int max34417_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + struct max34417_data *max34417 = iio_priv(indio_dev);
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + if (chan->type == IIO_VOLTAGE)
To reduce indent I'd flip it
if (chan->type != IIO_VOLTAGE)
return -EINVAL;
> + return max34417_read_voltage(max34417, chan, val);
> +
> + return -EINVAL;
> + case IIO_CHAN_INFO_AVERAGE_RAW:
> + if (chan->type == IIO_POWER)
> + return max34417_read_power(max34417, chan, val, val2);
> +
> + return -EINVAL;
> + case IIO_CHAN_INFO_SCALE:
> + if (chan->type == IIO_VOLTAGE) {
> + /* Scale to mA */
On a voltage channel? That is unlikely to be correct.
> + *val = MAX34417_VOLTAGE_CORRECTION_SCALE * MILLI;
> + *val2 = MAX34417_VOLTAGE_FULL_SCALE_BITS;
> +
> + return IIO_VAL_FRACTIONAL_LOG2;
> + } else if (chan->type == IIO_POWER) {
Actually power or accumulated power (otherwise known as energy!)
> + /* Scale to mW */
> + *val = max34417->input_correction[chan->channel] * MILLI;
> + *val2 = MAX34417_PWR_AVG_FULL_SCALE_BITS;
> +
> + return IIO_VAL_FRACTIONAL_LOG2;
> + }
> +
> + return -EINVAL;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static unsigned int max34417_calc_input_correction(u32 rsense)
> +{
> + /* (100 milliOhm / rsense) * MAX34417_PWR_CORRECTION_SCALE */
> + return (100 * MILLI * MAX34417_PWR_CORRECTION_SCALE) / rsense;
> +}
> +
> +static int max34417_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct max34417_data *max34417;
> + struct iio_dev *indio_dev;
> + struct regmap *regmap;
> + int rc, i;
> +
> + regmap = devm_regmap_init_i2c(client, &max34417_regmap_config);
> + if (IS_ERR(regmap))
> + return dev_err_probe(dev, PTR_ERR(regmap), "regmap_init failed\n");
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*max34417));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + rc = devm_regulator_get_enable(dev, "vdd");
> + if (rc)
> + return dev_err_probe(dev, rc, "failed to get vdd regulator\n");
> +
> + rc = devm_regulator_get_enable(dev, "vio");
> + if (rc)
> + return dev_err_probe(dev, rc, "failed to get vio regulator\n");
> +
> + max34417 = iio_priv(indio_dev);
> + max34417->regmap = regmap;
> + max34417->dev = dev;
> + mutex_init(&max34417->lock);
For new code
ret = devm_mutex_init(...)
if (ret)
return ret;
Brings some debug logic in which might be a little bit useful to someone
and it's cheap to do.
> +
> + /* Set default input correction for all channels */
> + for (i = 0; i < MAX34417_CHANNEL_COUNT; ++i)
for (unsigned int i = 0; .... i++)
> + max34417->input_correction[i] =
> + max34417_calc_input_correction(MAX34417_DEFAULT_RSENSE);
> +
> + device_for_each_child_node_scoped(dev, node) {
> + u32 rsense, index;
> +
> + if (fwnode_property_read_u32(node, "reg", &index))
> + return dev_err_probe(dev, -EINVAL, "missing reg property of %pfwP\n",
> + node);
returned, so no need to chase with an else.
> + else if (index >= MAX34417_CHANNEL_COUNT)
> + return dev_err_probe(dev, -EINVAL, "invalid reg %d of %pfwP\n",
> + index, node);
> +
> + fwnode_property_read_string(node, "label", &max34417->input_label[index]);
> +
> + rc = fwnode_property_read_u32(node, "shunt-resistor-micro-ohms", &rsense);
For optional properties, we generally now check for them first then if the property is
there can make errors reasons to fail
if (fwnode_property_present()) {
rc = fwnode_property_read_u32();
if (rc)
return dev_err_probe();
etc
> + if (!rc) {
> + if (!rsense || rsense < 1000 || rsense > 100000)
> + return dev_err_probe(dev, -EINVAL,
> + "invalid shunt value %d of %pfwP\n",
> + rsense, node);
> +
> + max34417->input_correction[index] =
> + max34417_calc_input_correction(rsense);
> + }
> + }
> +
> + indio_dev->channels = max34417_channels;
> + indio_dev->num_channels = ARRAY_SIZE(max34417_channels);
> + indio_dev->name = "max34417";
> + indio_dev->info = &max34417_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + /* Set as default Manual Mode & Wide ADC */
> + rc = regmap_write(max34417->regmap, MAX34417_CONTROL_REG, MAX34417_DEFAULT_CMM_WIDE);
> + if (rc)
> + return dev_err_probe(max34417->dev, rc, "Error writing control register\n");
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 0/2] iio: adc: add support for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
2026-09-25 3:13 ` [PATCH v2 0/2] iio: adc: add support " Jonathan Cameron
@ 2026-09-25 8:12 ` Neil Armstrong
0 siblings, 0 replies; 9+ messages in thread
From: Neil Armstrong @ 2026-09-25 8:12 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-iio, devicetree, linux-kernel
Hi Jonathan,
On 9/25/26 05:13, Jonathan Cameron wrote:
> On Thu, 24 Sep 2026 15:14:14 +0200
> Neil Armstrong <neil.armstrong@linaro.org> wrote:
>
>> The MAX34417 is a specialized current and voltage monitor used to determine
>> power consumption of portable systems. The driver support getting the channel
>> voltage and accumulated average power over an I2C/SMBUS serial interface.
>>
>> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
>
> Hi Neil,
>
> As pointed out too fast for a v2. You aren't new upstream so
> that shouldn't come as a surprise!
Yeah sorry, but with the initial feedback v1 was far form beeing
acceptable, will adjust timings for next versions.
>
> Secondly for devices that are all about monitoring power supplies
> etc we always ask for a clear statement of why IIO rather than
> hwmon + to CC at least the maintainer and often the hwmon list.
Sure, thanks for the suggestion.
>
> There are various valid reasons for that choice, but it is good
> if they are clearly stated for discussion.
I don't honestly have an strong opinion on that, for me IIO offers
much more options to retrieve data from the sensor and adding the
complex feature offered. The IIO triggers for example would
perfectly match with the bulk readout we coulnd't implement with
the hwmon API.
Thanks,
Neil
>
> Thanks,
>
> Jonathan
>
>> ---
>> Changes in v2:
>> - switch to shunt-resistor-micro-ohms no more required
>> - removed gpio.h from example
>> - Fixed max34417->MAX34417 in Kconfig and comments
>> - Added missing includes and remove unneeded
>> - Fixed typos in comments
>> - Switched to fsleep()
>> - Better aligned max34417_read_power declararation
>> - Handled 0 acc_count
>> - Switched to GENMASK_ULL() for 32bits systems
>> - Added missing empty lines
>> - Moved the input correction into a helper
>> - Set default input correction for all channels
>> - Switched to dev_err_probe() to return from probe
>> - Switched to device_for_each_child_node_scoped()
>> - Handled invalid shunt-resistor-micro-ohms value
>> - Link to v1: https://patch.msgid.link/20260923-topic-sm8x50-iio-max34417-adc-v1-0-41d4ba1bfc41@linaro.org
>>
>> ---
>> Neil Armstrong (2):
>> dt-bindings: iio: add: document the MAX34417 Four-Channel High Dynamic Range Power Accumulator
>> iio: adc: add driver for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
>>
>> .../bindings/iio/adc/maxim,max34417.yaml | 100 ++++++
>> drivers/iio/adc/Kconfig | 11 +
>> drivers/iio/adc/Makefile | 1 +
>> drivers/iio/adc/max34417.c | 374 +++++++++++++++++++++
>> 4 files changed, 486 insertions(+)
>> ---
>> base-commit: fd73f4a6659897191fa0d40695fe370925dd3780
>> change-id: 20260923-topic-sm8x50-iio-max34417-adc-209e880533fb
>>
>> Best regards,
>> --
>> Neil Armstrong <neil.armstrong@linaro.org>
>>
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 2/2] iio: adc: add driver for the MAX34417 Four-Channel High Dynamic Range Power Accumulator
2026-09-25 3:26 ` Jonathan Cameron
@ 2026-09-25 8:46 ` Neil Armstrong
0 siblings, 0 replies; 9+ messages in thread
From: Neil Armstrong @ 2026-09-25 8:46 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-iio, devicetree, linux-kernel
Hi,
On 9/25/26 05:26, Jonathan Cameron wrote:
> On Thu, 24 Sep 2026 15:14:16 +0200
> Neil Armstrong <neil.armstrong@linaro.org> wrote:
>
>> The MAX34417 is a specialized current and voltage monitor used to
>> determine power consumption of portable systems. The driver support
>> getting the channels voltage and accumulated average power over an
>> I2C/SMBUS serial interface.
>>
>> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
>
> A few comments inline. For a new driver I'd wait a week before sending an
> update. Whilst you've gotten quite a few reviews already it is good to make
> sure any discussion has died down before moving on to the next version.
>
>
> Thanks,
>
> Jonathan
>
>> diff --git a/drivers/iio/adc/max34417.c b/drivers/iio/adc/max34417.c
>> new file mode 100644
>> index 000000000000..98d961c5ecee
>> --- /dev/null
>> +++ b/drivers/iio/adc/max34417.c
>> @@ -0,0 +1,374 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * IIO driver for Maxim MAX34417 ADC, 4-Channels High Dynamic Range Power Accumulator
>> + *
>> + * Datasheet: https://www.analog.com/en/products/max34417.html
>> + *
>> + * TODO: Slow Mode, Continuous Accumulate Mode, Park Feature, Bulk Update, Perr_Verr Correction
>> + */
>
>> +#define MAX34417_CHANNEL(_index, _v_address, _power_address) \
>> + { \
>> + .type = IIO_VOLTAGE, \
>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
>> + BIT(IIO_CHAN_INFO_SCALE), \
>> + .channel = (_index), \
>> + .address = (_v_address), \
>> + .indexed = 1, \
>> + }, \
>> + { \
>> + .type = IIO_POWER, \
>
> As below. This smells like it might not be an actual power channel if it
> is accumulated on a fixed frequency. It'll be some sort of scaled IIO_ENERGY
> channel. If you want to present it as power (which may make sense) then
> it may need a little maths.
So as I understand the ENERGY would need to provide Joules. Which would
be doable if we take in account the accumulator sample rate (1024sps) from the
datasheet.
But this implementation tries to provide an initial support following
the MAX34417 datasheet which provides calculation for Average Power (page 18),
this is why I sticked to POWER and IIO_CHAN_INFO_AVERAGE_RAW.
But you're right, knowing the sample rate we could indeed calculate the energy.
I can try to do the math, but with manual updates it may no be very accurate so
the Continuous Accumulate Mode should be implemented to provide
accurate Energy measurements over time and would be enabled via an
IIO_CHAN_INFO_ENABLE.
>
>> + .info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \
>> + BIT(IIO_CHAN_INFO_SCALE), \
>> + .channel = (_index), \
>> + .address = (_power_address), \
>> + .indexed = 1, \
>> + }
>> +
>> +static const struct iio_chan_spec max34417_channels[] = {
>> + MAX34417_CHANNEL(0, MAX34417_V_CH1_REG, MAX34417_PWR_ACC_1_REG),
>> + MAX34417_CHANNEL(1, MAX34417_V_CH2_REG, MAX34417_PWR_ACC_2_REG),
>> + MAX34417_CHANNEL(2, MAX34417_V_CH3_REG, MAX34417_PWR_ACC_3_REG),
>> + MAX34417_CHANNEL(3, MAX34417_V_CH4_REG, MAX34417_PWR_ACC_4_REG),
>> +};
>> +
>> +/* TODO Implement trigger to update accumulator once and get all channels at once */
>> +
>> +static int max34417_accumulator_update(struct max34417_data *max34417)
>> +{
>> + int rc;
>> +
>> + rc = regmap_write(max34417->regmap, MAX34417_UPDATE_REG, 1);
>> + if (rc) {
>> + dev_err(max34417->dev, "Error (%d) writing update register\n", rc);
>> + return rc;
>> + }
>> +
>> + /* Wait for accumulator update */
>> + fsleep(1000);
>> +
>> + return 0;
>> +}
>> +
>> +static int max34417_read_voltage(struct max34417_data *max34417,
>> + const struct iio_chan_spec *chan, int *val)
>> +{
>> + uint16_t voltage;
>> + uint8_t buf[3];
>> + int rc;
>> +
>> + guard(mutex)(&max34417->lock);
>> +
>> + rc = max34417_accumulator_update(max34417);
>> + if (rc)
>> + return rc;
>> +
>> + rc = regmap_noinc_read(max34417->regmap, chan->address, &buf, 3);
>> + if (rc)
>> + return rc;
>> +
>> + voltage = buf[2] | ((uint64_t)buf[1] << 8);
>
> get_unaligned_be16();
>
>> + voltage >>= 2;
>> +
>> + *val = voltage;
>> +
>> + return IIO_VAL_INT;
>> +}
>> +
>> +static int max34417_read_power(struct max34417_data *max34417,
>> + const struct iio_chan_spec *chan,
>> + int *val, int *val2)
>> +{
>> + uint32_t acc_count;
>> + uint64_t power;
>> + uint8_t buf[8];
> Kernel types so u32, u64, u8
>
>> + int rc;
>> +
>> + guard(mutex)(&max34417->lock);
>> +
>> + rc = max34417_accumulator_update(max34417);
>> + if (rc)
>> + return rc;
>> +
>> + rc = regmap_noinc_read(max34417->regmap, MAX34417_ACC_COUNT_REG,
>> + &buf, 4);
>> + if (rc)
>> + return rc;
>> +
>> + acc_count = buf[3] | ((uint64_t)buf[2] << 8) | ((uint64_t)buf[1] << 16);
>
> get_unaligned_be24(buf);
>
>> + if (!acc_count)
>> + return -EIO;
>> +
>> + rc = regmap_noinc_read(max34417->regmap, chan->address, &buf, 8);
>> + if (rc)
>> + return rc;
>> +
>> + power = buf[7];
>> + power |= ((uint64_t)buf[6] << 8UL);
>> + power |= ((uint64_t)buf[5] << 16UL);
>> + power |= ((uint64_t)buf[4] << 24UL);
>> + power |= ((uint64_t)buf[3] << 32UL);
>> + power |= ((uint64_t)buf[2] << 40UL);
>> + power |= ((uint64_t)buf[1] << 48UL);
>
> Hmm. i think this is the second 56 bit endian reader we've had
> recently. Time to add get_unaligned_be56()
Indeed
>
>> +
>> + power = div_u64(power, acc_count);
>> +
>> + *val = FIELD_GET(GENMASK_ULL(31, 0), power);
>> + *val2 = FIELD_GET(GENMASK_ULL(55, 32), power);
>> +
>> + return IIO_VAL_INT_64;
>> +}
>> +
>> +static int max34417_read_raw(struct iio_dev *indio_dev,
>> + struct iio_chan_spec const *chan,
>> + int *val, int *val2, long mask)
>> +{
>> + struct max34417_data *max34417 = iio_priv(indio_dev);
>> +
>> + switch (mask) {
>> + case IIO_CHAN_INFO_RAW:
>> + if (chan->type == IIO_VOLTAGE)
> To reduce indent I'd flip it
> if (chan->type != IIO_VOLTAGE)
> return -EINVAL;
>
>> + return max34417_read_voltage(max34417, chan, val);
>> +
>> + return -EINVAL;
>> + case IIO_CHAN_INFO_AVERAGE_RAW:
>> + if (chan->type == IIO_POWER)
>> + return max34417_read_power(max34417, chan, val, val2);
>> +
>> + return -EINVAL;
>> + case IIO_CHAN_INFO_SCALE:
>> + if (chan->type == IIO_VOLTAGE) {
>> + /* Scale to mA */
>
> On a voltage channel? That is unlikely to be correct.
Indeed
>
>> + *val = MAX34417_VOLTAGE_CORRECTION_SCALE * MILLI;
>> + *val2 = MAX34417_VOLTAGE_FULL_SCALE_BITS;
>> +
>> + return IIO_VAL_FRACTIONAL_LOG2;
>> + } else if (chan->type == IIO_POWER) {
>
> Actually power or accumulated power (otherwise known as energy!)
>
>> + /* Scale to mW */
>> + *val = max34417->input_correction[chan->channel] * MILLI;
>> + *val2 = MAX34417_PWR_AVG_FULL_SCALE_BITS;
>> +
>> + return IIO_VAL_FRACTIONAL_LOG2;
>> + }
>> +
>> + return -EINVAL;
>> + default:
>> + return -EINVAL;
>> + }
>> +}
>
>
>> +
>> +static unsigned int max34417_calc_input_correction(u32 rsense)
>> +{
>> + /* (100 milliOhm / rsense) * MAX34417_PWR_CORRECTION_SCALE */
>> + return (100 * MILLI * MAX34417_PWR_CORRECTION_SCALE) / rsense;
>> +}
>> +
>> +static int max34417_probe(struct i2c_client *client)
>> +{
>> + struct device *dev = &client->dev;
>> + struct max34417_data *max34417;
>> + struct iio_dev *indio_dev;
>> + struct regmap *regmap;
>> + int rc, i;
>> +
>> + regmap = devm_regmap_init_i2c(client, &max34417_regmap_config);
>> + if (IS_ERR(regmap))
>> + return dev_err_probe(dev, PTR_ERR(regmap), "regmap_init failed\n");
>> +
>> + indio_dev = devm_iio_device_alloc(dev, sizeof(*max34417));
>> + if (!indio_dev)
>> + return -ENOMEM;
>> +
>> + rc = devm_regulator_get_enable(dev, "vdd");
>> + if (rc)
>> + return dev_err_probe(dev, rc, "failed to get vdd regulator\n");
>> +
>> + rc = devm_regulator_get_enable(dev, "vio");
>> + if (rc)
>> + return dev_err_probe(dev, rc, "failed to get vio regulator\n");
>> +
>> + max34417 = iio_priv(indio_dev);
>> + max34417->regmap = regmap;
>> + max34417->dev = dev;
>> + mutex_init(&max34417->lock);
> For new code
> ret = devm_mutex_init(...)
> if (ret)
> return ret;
>
> Brings some debug logic in which might be a little bit useful to someone
> and it's cheap to do.
>
>> +
>> + /* Set default input correction for all channels */
>> + for (i = 0; i < MAX34417_CHANNEL_COUNT; ++i)
> for (unsigned int i = 0; .... i++)
>
>> + max34417->input_correction[i] =
>> + max34417_calc_input_correction(MAX34417_DEFAULT_RSENSE);
>> +
>> + device_for_each_child_node_scoped(dev, node) {
>> + u32 rsense, index;
>> +
>> + if (fwnode_property_read_u32(node, "reg", &index))
>> + return dev_err_probe(dev, -EINVAL, "missing reg property of %pfwP\n",
>> + node);
>
> returned, so no need to chase with an else.
>
>> + else if (index >= MAX34417_CHANNEL_COUNT)
>> + return dev_err_probe(dev, -EINVAL, "invalid reg %d of %pfwP\n",
>> + index, node);
>> +
>> + fwnode_property_read_string(node, "label", &max34417->input_label[index]);
>> +
>> + rc = fwnode_property_read_u32(node, "shunt-resistor-micro-ohms", &rsense);
> For optional properties, we generally now check for them first then if the property is
> there can make errors reasons to fail
Will switch to that
>
> if (fwnode_property_present()) {
> rc = fwnode_property_read_u32();
> if (rc)
> return dev_err_probe();
>
> etc
>
>> + if (!rc) {
>> + if (!rsense || rsense < 1000 || rsense > 100000)
>> + return dev_err_probe(dev, -EINVAL,
>> + "invalid shunt value %d of %pfwP\n",
>> + rsense, node);
>> +
>> + max34417->input_correction[index] =
>> + max34417_calc_input_correction(rsense);
>> + }
>> + }
>> +
>> + indio_dev->channels = max34417_channels;
>> + indio_dev->num_channels = ARRAY_SIZE(max34417_channels);
>> + indio_dev->name = "max34417";
>> + indio_dev->info = &max34417_info;
>> + indio_dev->modes = INDIO_DIRECT_MODE;
>> +
>> + /* Set as default Manual Mode & Wide ADC */
>> + rc = regmap_write(max34417->regmap, MAX34417_CONTROL_REG, MAX34417_DEFAULT_CMM_WIDE);
>> + if (rc)
>> + return dev_err_probe(max34417->dev, rc, "Error writing control register\n");
>> +
>> + return devm_iio_device_register(dev, indio_dev);
>> +}
>
Thanks,
Neil
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-09-25 8:46 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-24 13:14 [PATCH v2 0/2] iio: adc: add support for the MAX34417 Four-Channel High Dynamic Range Power Accumulator Neil Armstrong
2026-09-24 13:14 ` [PATCH v2 1/2] dt-bindings: iio: add: document " Neil Armstrong
2026-09-24 16:43 ` Conor Dooley
2026-09-24 13:14 ` [PATCH v2 2/2] iio: adc: add driver for " Neil Armstrong
2026-09-24 15:13 ` Joshua Crofts
2026-09-25 3:26 ` Jonathan Cameron
2026-09-25 8:46 ` Neil Armstrong
2026-09-25 3:13 ` [PATCH v2 0/2] iio: adc: add support " Jonathan Cameron
2026-09-25 8:12 ` Neil Armstrong
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®