From: Siratul Islam <siratul.islam@linux.dev>
To: Stefan Popa <stefan.popa@analog.com>, linux-iio@vger.kernel.org
Cc: linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, jic23@kernel.org, andy@kernel.org,
nuno.sa@analog.com, linux@roeck-us.net, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, dlechner@baylibre.com,
ciprian.hegbeli@analog.com, u.kleine-koenig@baylibre.com
Subject: Re: [PATCH v5 2/2] iio: adc: add MAX40080 current-sense amplifier driver
Date: Thu, 23 Jul 2026 23:21:16 +0600 [thread overview]
Message-ID: <7b986215fe644da12c4bdab1abefbf43dbb3c3eb.camel@linux.dev> (raw)
In-Reply-To: <20260723065036.2683075-3-stefan.popa@analog.com>
On Thu, 2026-07-23 at 09:50 +0300, Stefan Popa wrote:
> The MAX40080 is a bidirectional current-sense amplifier with an
> integrated 12-bit ADC and an I2C/SMBus interface. It measures the
> voltage across an external shunt resistor and the input bus voltage,
> storing the results in an internal FIFO.
>
...
>
> +MAXIM MAX40080 CURRENT SENSE AMPLIFIER DRIVER
> +M: Ciprian Hegbeli <ciprian.hegbeli@analog.com>
> +M: Stefan Popa <stefan.popa@analog.com>
> +L: linux-iio@vger.kernel.org
> +S: Supported
> +W: https://ez.analog.com/linux-software-drivers
> +F: Documentation/devicetree/bindings/iio/adc/maxim,max40080.yaml
MAINTAINERS file entry for the binding should be added with the binding patch.
> +F: drivers/iio/adc/max40080.c
> +
>
...
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * MAX40080 Digital Current-Sense Amplifier driver
> + *
> + * Copyright 2026 Analog Devices, Inc.
I'd also add the datasheet link here.
/* Datasheet: <https://www.analog.com/media/en/technical-documentation/data-sheets/MAX40080.pdf> */
> + */
> +
...
> +
> +/* CFG.mode field */
> +#define MAX40080_STDBY_MODE 0x00
> +#define MAX40080_SINGLE_MODE 0x02 /* one conversion per Quick Command */
Make "o" in "One" uppercase to maintain consistency with your other comments.
> +
> +/* FIFO_CFG.store_iv field */
> +#define MAX40080_STORE_I_V 0x02
Why I_V and not IV?
> +
> +#define MAX40080_ADC_RES 4096
> +#define MAX40080_INTER_VREF_MV 1250
Andy would usually suggest "mV"
> +#define MAX40080_V_BUFF_GAIN 30
> +#define MAX40080_CSA_50MV_GAIN 25
> +#define MAX40080_CSA_10MV_GAIN 125
Ditto.
> +
> +/*
...
> +
> +static int max40080_update_bits(struct max40080_state *st, u8 reg,
> + u16 mask, u16 val)
> +{
> + int ret;
> + int tmp;
> +
> + guard(mutex)(&st->lock);
> +
> + tmp = i2c_smbus_read_word_data(st->client, reg);
> + if (tmp < 0)
> + return tmp;
> +
> + tmp &= ~mask;
> + tmp |= val & mask;
Could be done in one step? "tmp = (tmp & ~mask) | (val & mask)"
> +
> + ret = i2c_smbus_write_word_data(st->client, reg, tmp);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int max40080_read_iv(struct max40080_state *st, u32 *iv)
> +{
> + u32 tmp = 0;
> + int ret, io_ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = max40080_trigger_measurement(st);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * Wait for the conversion to complete by polling the FIFO valid bit
> + * (or bail out on an I2C error). Polling the device's own status makes
> + * this independent of the actual conversion time, which varies with the
> + * oversampling ratio and the bus speed. The timeout is only a safety
> + * ceiling: the worst case is the maximum 128x averaging on both the
> + * current and voltage channels at the slowest 15 ksps base rate plus the
> + * inter-channel switching time, i.e. roughly 20 ms; 50 ms leaves ample
> + * margin.
> + */
> + ret = read_poll_timeout(max40080_read_iv_once, io_ret,
> + io_ret || (tmp & MAX40080_IV_VALID_MSK),
> + 500, 50000, false, st, &tmp);
> + *iv = tmp;
Is "iv" intentionally being written even in error path? Ideally it would be
assigned after the check.
> + if (ret)
> + return ret;
> +
> + return io_ret;
> +}
> +
> +/*
> + * max40080_oversampling_avail[] is ordered so that its index is the FILTER
> + * field value (index 0 = no averaging, index 1 = 8x, ...). Return that index
> + * for an exact match, or -EINVAL for a value that is not on the list.
> + */
> +static int max40080_oversampling_to_filter(int val)
> +{
> + int i;
> +
Just use for(unsigned int i = 0; i < ARRAY_SIZE(max40080_oversampling_avail); i++);
No need to have a separate declaration of 'i' for "for"
> + for (i = 0; i < ARRAY_SIZE(max40080_oversampling_avail); i++)
> + if (max40080_oversampling_avail[i] == val)
> + return i;
I'd use scope here as it's technically multiline. "for(...) { }"
> +
> + return -EINVAL;
> +}
> +
> +static int max40080_set_oversampling_ratio(struct max40080_state *st, int val)
> +{
> + int ret, filter = max40080_oversampling_to_filter(val);
Separate initialization from declaration.
int ret, filter;
filter = max40080_oversampling_to_filter(val);
> +
> + if (filter < 0)
> + return filter;
> +
> + ret = max40080_update_bits(st, MAX40080_REG_CFG, MAX40080_FILTER_MSK,
> + FIELD_PREP(MAX40080_FILTER_MSK, filter));
> + if (ret)
> + return ret;
> +
> + st->oversampling_ratio = val;
> +
> + return 0;
> +}
> +
> +static int max40080_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val,
> + int *val2,
> + long mask)
Put "int *val, int *val2, long mask" on the same line. i.e,
static int max40080_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
> +{
> + struct max40080_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + if (chan->type == IIO_CURRENT) {
> + ret = max40080_get_current(st, val);
> + if (ret)
> + return ret;
> + } else if (chan->type == IIO_VOLTAGE) {
> + ret = max40080_get_voltage(st, val);
> + if (ret)
> + return ret;
> + }
> +
Either have this space before return on all of the paths in this "switch"
or none of them for consistency.
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_SCALE:
> + if (chan->type == IIO_CURRENT) {
> + /*
> + * The selectable current-sense range is exposed through
> + * scale: each RANGE setting has its own precomputed
> + * mA-per-code value. Userspace picks the range by writing
> + * the matching scale.
> + */
> + *val = st->current_scale[st->range][0];
> + *val2 = st->current_scale[st->range][1];
> + return IIO_VAL_INT_PLUS_NANO;
> + }
> + /* voltage[mV] = raw * Vref[mV] * buffer_gain / ADC_RES */
> + *val = MAX40080_INTER_VREF_MV * MAX40080_V_BUFF_GAIN;
> + *val2 = MAX40080_ADC_RES;
> + return IIO_VAL_FRACTIONAL;
> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> + ret = max40080_get_oversampling_ratio(st, val);
> + if (ret)
> + return ret;
> + return IIO_VAL_INT;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int max40080_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + struct max40080_state *st = iio_priv(indio_dev);
> + unsigned int i;
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SCALE:
> + /* Only the current channel has a selectable range/scale. */
> + if (chan->type != IIO_CURRENT)
> + return -EINVAL;
> +
> + for (i = 0; i < MAX40080_NUM_RANGES; i++)
> + if (val == st->current_scale[i][0] &&
> + val2 == st->current_scale[i][1])
> + return max40080_set_range(st, i);
I'd add brackets to this "for" too like above.
> +
> + return -EINVAL;
> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> + ret = max40080_set_oversampling_ratio(st, val);
> + if (ret)
> + return ret;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int max40080_reg_access(struct iio_dev *indio_dev,
> + unsigned int reg,
> + unsigned int write_val,
> + unsigned int *read_val)
> +{
> + struct max40080_state *st = iio_priv(indio_dev);
> +
> + if (read_val) {
> + int val = i2c_smbus_read_word_data(st->client, reg);
Declare this "val" at the top.
> +
> + if (val < 0)
> + return val;
> + *read_val = val;
Add blank lines above and below
"*read_val = val;"
> + return 0;
> + }
> +
> + return i2c_smbus_write_word_data(st->client, reg, write_val);
> +}
> +
> +/*
> + * Configure the device from the cached state. The device powers up in standby
> + * with PEC enabled (CFG POR = 0x0060), so PEC is kept enabled throughout.
> + */
> +static int max40080_init(struct max40080_state *st)
> +{
> + u16 fifo_cfg, cfg;
> + int ret, filter;
> +
> + filter = max40080_oversampling_to_filter(st->oversampling_ratio);
> + if (filter < 0)
> + return filter;
> +
> + /*
> + * Put the device in standby before (re)configuring the FIFO: the FIFO
> + * configuration register can only be written while the device is not
> + * converting.
> + */
> + cfg = FIELD_PREP(MAX40080_MODE_MSK, MAX40080_STDBY_MODE) |
> + FIELD_PREP(MAX40080_PEC_EN_MSK, 1);
I'd have a blank line before the "ret" assignments.
> + ret = i2c_smbus_write_word_data(st->client, MAX40080_REG_CFG, cfg);
> + if (ret)
> + return ret;
> +
> + /* Store a matched current+voltage pair per conversion. */
> + fifo_cfg = FIELD_PREP(MAX40080_STORE_IV_MSK, MAX40080_STORE_I_V);
Here too.
> + ret = i2c_smbus_write_word_data(st->client, MAX40080_REG_FIFO_CFG,
> + fifo_cfg);
> + if (ret)
> + return ret;
> +
> + /*
> + * Use single-measurement mode: the device stays idle and converts once
> + * per SMBus Quick Command (see max40080_trigger_measurement()), so each
> + * read returns a fresh sample rather than a queued FIFO entry.
> + */
> + cfg = FIELD_PREP(MAX40080_MODE_MSK, MAX40080_SINGLE_MODE) |
> + FIELD_PREP(MAX40080_PEC_EN_MSK, 1) |
> + FIELD_PREP(MAX40080_RANGE_MSK, st->range) |
> + FIELD_PREP(MAX40080_FILTER_MSK, filter);
> +
Like you have here.
> + ret = i2c_smbus_write_word_data(st->client, MAX40080_REG_CFG, cfg);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int max40080_probe(struct i2c_client *client)
> +{
> +
...
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + i2c_set_clientdata(client, indio_dev);
> +
Is it used?
> + st = iio_priv(indio_dev);
> + st->client = client;
> +
> + ret = devm_mutex_init(dev, &st->lock);
> + if (ret)
> + return ret;
> +
> + ret = device_property_read_u32(dev, propname, &st->shunt_resistor_uohm);
> + if (ret)
> + return dev_err_probe(dev, ret, "can't read %s\n", propname);
Add a space here.
> + if (!st->shunt_resistor_uohm)
> + return dev_err_probe(dev, -EINVAL, "%s must be non-zero\n",
> + propname);
> +
> + max40080_calc_current_scale(st);
> +
> + /* Defaults: 50 mV range (index 0), no averaging. */
> + st->range = 0;
> + st->oversampling_ratio = 1;
> +
> + indio_dev->name = "max40080";
> + indio_dev->info = &max40080_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->channels = max40080_channels;
> + indio_dev->num_channels = ARRAY_SIZE(max40080_channels);
> +
> + ret = max40080_init(st);
> + if (ret)
> + return ret;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
> +
> +static const struct i2c_device_id max40080_i2c_ids[] = {
> + { "max40080" },
use named initializer. i.e,
{ .name = "max40080" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, max40080_i2c_ids);
> +
>
...
> +MODULE_AUTHOR("Ciprian Hegbeli <ciprian.hegbeli@analog.com>");
> +MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
> +MODULE_DESCRIPTION("Analog Devices MAX40080 current-sense amplifier driver");
> +MODULE_LICENSE("GPL");
prev parent reply other threads:[~2026-07-23 17:22 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-23 6:50 [PATCH v5 0/2] iio: adc: add MAX40080 current-sense amplifier driver Stefan Popa
2026-07-23 6:50 ` [PATCH v5 1/2] dt-bindings: iio: adc: add maxim,max40080 Stefan Popa
2026-07-23 7:04 ` sashiko-bot
2026-07-23 16:23 ` Siratul Islam
2026-07-23 16:40 ` Conor Dooley
2026-07-23 6:50 ` [PATCH v5 2/2] iio: adc: add MAX40080 current-sense amplifier driver Stefan Popa
2026-07-23 7:06 ` sashiko-bot
2026-07-23 17:21 ` Siratul Islam [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=7b986215fe644da12c4bdab1abefbf43dbb3c3eb.camel@linux.dev \
--to=siratul.islam@linux.dev \
--cc=andy@kernel.org \
--cc=ciprian.hegbeli@analog.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dlechner@baylibre.com \
--cc=jic23@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=nuno.sa@analog.com \
--cc=robh@kernel.org \
--cc=stefan.popa@analog.com \
--cc=u.kleine-koenig@baylibre.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.