Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "John Erasmus Mari Geronimo"
	<johnerasmusmari.geronimo@analog.com>,
	devicetree@vger.kernel.org
Cc: robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v5 2/2] iio: temperature: add support for Analog Devices MAX30210
Date: Fri, 23 Oct 2026 08:30:26 +0000	[thread overview]
Message-ID: <sashiko-outbox-182518@kernel.org> (raw)
In-Reply-To: <20261023080623.1830-3-johnerasmusmari.geronimo@analog.com>

Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] iio: temperature: max30210: wrong scale factor in max30210_read_raw()
- [High] iio: temperature: max30210: race between buffer disable and max30210_fifo_read()
- [Medium] iio: temperature: max30210: broken events and missing quiesce without IRQ
- [Medium] iio: temperature: max30210: FIFO enable order and watermark race in preenable
- [Medium] iio: temperature: max30210: missing SYS_CONF_REG in max30210_volatile_reg()
- [Medium] iio: temperature: max30210: IRQ requested after devm_iio_device_register()
- [Medium] iio: temperature: max30210: non-sleeping gpiod_set_value() in max30210_setup()
- [Medium] iio: temperature: max30210: unmasked status bits in max30210_irq_handler()
- [Medium] iio: temperature: max30210: unconditional decrement in max30210_autoconv_put()
--

commit 3d7457ec9559f89625f738367b02f298331f750f
Author: John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com>

iio: temperature: add support for Analog Devices MAX30210

This commit adds an IIO driver for the Analog Devices MAX30210 I2C
temperature sensor, supporting direct mode reads, configurable sampling
frequency, threshold events, and a 64-sample hardware FIFO buffer.

> diff --git a/drivers/iio/temperature/max30210.c b/drivers/iio/temperature/max30210.c
> new file mode 100644
> index 0000000000000..01a1eff4da663
> --- /dev/null
> +++ b/drivers/iio/temperature/max30210.c

[ ... ]

> +static bool max30210_volatile_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case MAX30210_STATUS_REG:
> +	case MAX30210_FIFO_DATA_REG:
> +	case MAX30210_FIFO_CONF_2_REG:
> +	case MAX30210_TEMP_CONV_REG:
> +	/*
> +	 * TEMP_DATA_REG is a 16-bit value spanning two byte registers;
> +	 * both must be volatile or regmap_bulk_read()'s range check falls
> +	 * back to caching the second byte forever (see max30210_read_temp()).
> +	 */
> +	case MAX30210_TEMP_DATA_REG:
> +	case MAX30210_TEMP_DATA_REG + 1:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}

[Severity: Medium]
Should MAX30210_SYS_CONF_REG also be marked volatile in
max30210_volatile_reg()?

When probing without powerdown-gpios, max30210_setup() calls:

    regmap_set_bits(st->regmap, MAX30210_SYS_CONF_REG,
                    MAX30210_SYSCONF_RESET);

Because max30210_regmap uses REGCACHE_MAPLE and MAX30210_SYS_CONF_REG is not
in max30210_volatile_reg(), the self-clearing MAX30210_SYSCONF_RESET bit
(0x01) remains cached after the hardware finishes resetting and clears it to
0. Would this cause subsequent reads (such as via max30210_reg_access()) to
return a stale 0x01 value, or cause future read-modify-write operations on
MAX30210_SYS_CONF_REG to re-trigger a device reset?

[ ... ]

> +static int max30210_autoconv_put(struct max30210_state *st)
> +{
> +	int ret = 0;
> +
> +	lockdep_assert_held(&st->lock);
> +
> +	if (st->autoconv_users == 1)
> +		ret = regmap_write(st->regmap, MAX30210_TEMP_CONV_REG, 0);
> +	if (st->autoconv_users)
> +		st->autoconv_users--;
> +
> +	return ret;
> +}

[Severity: Medium]
If regmap_write() fails in max30210_autoconv_put() when st->autoconv_users
is 1, should st->autoconv_users still be decremented?

Decrementing st->autoconv_users on error leaves st->autoconv_users at 0 while
autonomous conversions remain enabled in MAX30210_TEMP_CONV_REG. In
max30210_write_event_config(), MAX30210_INT_EN_REG is cleared before calling
max30210_autoconv_put(st), so a subsequent attempt to disable the event sees
the bit already cleared and returns 0 early without stopping
MAX30210_TEMP_CONV_REG, while max30210_read_raw() sees st->autoconv_users == 0
even though AUTO mode is still running.

> +
> +static void max30210_fifo_read(struct iio_dev *indio_dev)
> +{
> +	struct max30210_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	/*
> +	 * FIFO_DATA_REG auto-increments in hardware; bulk_read would treat
> +	 * each subsequent byte as a separate cacheable register.
> +	 */
> +	ret = regmap_noinc_read(st->regmap, MAX30210_FIFO_DATA_REG,
> +				st->fifo_buf,
> +				MAX30210_FIFO_BYTES_PER_SAMPLE * st->watermark);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "Failed to read from fifo.\n");
> +		return;
> +	}
> +
> +	for (unsigned int i = 0; i < st->watermark; i++) {
> +		u32 raw = get_unaligned_be24(&st->fifo_buf[MAX30210_FIFO_BYTES_PER_SAMPLE * i]);
> +		s16 temp;
> +
> +		if (raw == MAX30210_FIFO_INVAL_DATA) {
> +			dev_err_ratelimited(&indio_dev->dev, "Invalid data\n");
> +			continue;
> +		}
> +
> +		temp = sign_extend32(FIELD_GET(MAX30210_FIFO_TEMP_MASK, raw), 15);
> +
> +		iio_push_to_buffers(indio_dev, &temp);
> +	}
> +}
> +
> +static irqreturn_t max30210_irq_handler(int irq, void *dev_id)
> +{
> +	struct iio_dev *indio_dev = dev_id;
> +	struct max30210_state *st = iio_priv(indio_dev);
> +	unsigned int status;
> +	int ret;
> +
> +	ret = regmap_read(st->regmap, MAX30210_STATUS_REG, &status);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "Status read failed\n");
> +		return IRQ_NONE;
> +	}
> +
> +	if (!(status & (MAX30210_STATUS_A_FULL_MASK |
> +			MAX30210_STATUS_TEMP_HI_MASK |
> +			MAX30210_STATUS_TEMP_LO_MASK)))
> +		return IRQ_NONE;
> +
> +	if (status & MAX30210_STATUS_A_FULL_MASK)
> +		max30210_fifo_read(indio_dev);
> +
> +	if (status & MAX30210_STATUS_TEMP_HI_MASK)
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0,
> +						    IIO_EV_TYPE_THRESH,
> +						    IIO_EV_DIR_RISING),
> +			       iio_get_time_ns(indio_dev));
> +
> +	if (status & MAX30210_STATUS_TEMP_LO_MASK)
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0,
> +						    IIO_EV_TYPE_THRESH,
> +						    IIO_EV_DIR_FALLING),
> +			       iio_get_time_ns(indio_dev));
> +
> +	return IRQ_HANDLED;
> +}

[Severity: Medium]
Does max30210_irq_handler() need to mask status with the enabled bits in
MAX30210_INT_EN_REG before dispatching FIFO reads and threshold events?

In hardware, MAX30210_STATUS_REG sets the A_FULL, TEMP_HI, and TEMP_LO flags
whenever those conditions occur during autonomous conversions, regardless of
which bits are enabled in MAX30210_INT_EN_REG.

If only threshold events are enabled (buffer disabled), autonomous conversions
still fill the FIFO and set MAX30210_STATUS_A_FULL_MASK, causing a threshold
interrupt to also invoke max30210_fifo_read() while the buffer is disabled.
Conversely, if the FIFO buffer (or only one threshold direction) is enabled
and the temperature crosses an unenabled threshold in MAX30210_TEMP_ALM_HI_REG
or MAX30210_TEMP_ALM_LO_REG, every FIFO interrupt will see TEMP_HI or TEMP_LO
set in status and push disabled threshold events via iio_push_event().

[ ... ]

> +static int max30210_read_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int *val, int *val2, long mask)
> +{
> +	struct max30210_state *st = iio_priv(indio_dev);
> +	unsigned int uval;
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SCALE:
> +		*val = 5;
> +		*val2 = MILLI;
> +
> +		return IIO_VAL_FRACTIONAL;

[Severity: High]
Is the scale factor for IIO_CHAN_INFO_SCALE in max30210_read_raw() off by a
factor of 1000?

Per Documentation/ABI/testing/sysfs-bus-iio, IIO_TEMP channels report values
in milli-degrees Celsius. Since one LSB of the MAX30210 temperature data
register is 0.005 degrees Celsius (5 milli-degrees Celsius, matching MAX30208
in drivers/iio/temperature/max30208.c), setting *val = 5 and *val2 = MILLI
(1000) with IIO_VAL_FRACTIONAL reports 5 / 1000 = 0.005000000 instead of 5.
Should this set *val = 5 and return IIO_VAL_INT?

[ ... ]

> +static int max30210_set_watermark(struct iio_dev *indio_dev, unsigned int val)
> +{
> +	struct max30210_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	if (val < 1 || val > MAX30210_FIFO_SIZE)
> +		return -EINVAL;
> +
> +	/*
> +	 * FIFO_A_FULL sets the number of empty FIFO slots remaining when the
> +	 * A_FULL interrupt fires. For a watermark of N samples, set
> +	 * FIFO_A_FULL = FIFO_SIZE - N.
> +	 */
> +	ret = regmap_write(st->regmap, MAX30210_FIFO_CONF_1_REG,
> +			   FIELD_PREP(MAX30210_FIFOCONF1_A_FULL_MASK,
> +				      MAX30210_FIFO_SIZE - val));
> +	if (ret)
> +		return ret;
> +
> +	st->watermark = val;
> +
> +	return 0;
> +}

[ ... ]

> +static int max30210_buffer_preenable(struct iio_dev *indio_dev)
> +{
> +	struct max30210_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	/* Enable FIFO-full interrupt */
> +	ret = regmap_set_bits(st->regmap, MAX30210_INT_EN_REG,
> +			      MAX30210_STATUS_A_FULL_MASK);
> +	if (ret)
> +		return ret;
> +
> +	/* Flush FIFO before starting autonomous conversions */
> +	ret = regmap_set_bits(st->regmap, MAX30210_FIFO_CONF_2_REG,
> +			      MAX30210_FIFOCONF2_FLUSH_FIFO);
> +	if (ret)
> +		goto err_disable_irq;
> +
> +	scoped_guard(mutex, &st->lock)
> +		ret = max30210_autoconv_get(st);
> +	if (ret)
> +		goto err_disable_irq;
> +
> +	return 0;
> +
> +err_disable_irq:
> +	/* The core won't call postdisable() when preenable() fails. */
> +	regmap_clear_bits(st->regmap, MAX30210_INT_EN_REG,
> +			  MAX30210_STATUS_A_FULL_MASK);
> +
> +	return ret;
> +}

[Severity: Medium]
Could the ordering in max30210_buffer_preenable() and max30210_set_watermark()
cause stale FIFO reads and a race on st->watermark?

First, max30210_buffer_preenable() enables MAX30210_STATUS_A_FULL_MASK in
MAX30210_INT_EN_REG before flushing the FIFO via MAX30210_FIFOCONF2_FLUSH_FIFO.
If autonomous conversions were already active for threshold events, A_FULL can
already be asserted in MAX30210_STATUS_REG and trigger max30210_irq_handler()
on stale FIFO samples before the flush happens.

Second, in iio_enable_buffers(), setup_ops->preenable() runs before
info->hwfifo_set_watermark(). Because the FIFO interrupt and autonomous
conversions are started in .preenable rather than .postenable,
max30210_irq_handler() -> max30210_fifo_read() can run concurrently while
max30210_set_watermark() updates MAX30210_FIFO_CONF_1_REG and st->watermark
without locking. In max30210_fifo_read(), st->watermark is read unlocked for
the regmap_noinc_read() length and re-read unlocked in the loop bound:

    ret = regmap_noinc_read(st->regmap, MAX30210_FIFO_DATA_REG,
                            st->fifo_buf,
                            MAX30210_FIFO_BYTES_PER_SAMPLE * st->watermark);
    ...
    for (unsigned int i = 0; i < st->watermark; i++) {

If max30210_set_watermark() increases st->watermark while regmap_noinc_read()
is in flight, the loop can iterate past the number of samples actually read
into st->fifo_buf and push uninitialized or stale bytes to the IIO buffer.

Additionally, when val > MAX30210_FIFO_SIZE, should max30210_set_watermark()
clamp val to MAX30210_FIFO_SIZE instead of returning -EINVAL, as expected by
iio_enable_buffers() and Documentation/ABI/testing/sysfs-bus-iio?

> +
> +static int max30210_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> +	struct max30210_state *st = iio_priv(indio_dev);
> +	int ret, err;
> +
> +	scoped_guard(mutex, &st->lock)
> +		err = max30210_autoconv_put(st);
> +
> +	/* Keep going on error so the interrupt is always disabled below. */
> +	ret = regmap_set_bits(st->regmap, MAX30210_FIFO_CONF_2_REG,
> +			      MAX30210_FIFOCONF2_FLUSH_FIFO);
> +	if (!err)
> +		err = ret;
> +
> +	/* Disable FIFO-full interrupt */
> +	ret = regmap_clear_bits(st->regmap, MAX30210_INT_EN_REG,
> +				MAX30210_STATUS_A_FULL_MASK);
> +
> +	return err ?: ret;
> +}

[Severity: High]
Can max30210_buffer_postdisable() return while max30210_irq_handler() is
still executing max30210_fifo_read() on another CPU?

When disabling the buffer via __iio_update_buffers() -> iio_disable_buffers()
-> max30210_buffer_postdisable(), the IIO core is operating in
INDIO_BUFFER_SOFTWARE mode and does not synchronize client->irq.
max30210_buffer_postdisable() also flushes the FIFO before clearing
MAX30210_STATUS_A_FULL_MASK and returns without calling synchronize_irq() or
serializing against max30210_fifo_read().

If max30210_fifo_read() is in flight during an I2C read and calling
iio_push_to_buffers(), __iio_update_buffers() immediately proceeds to
iio_buffer_deactivate() and deletes the buffer from
iio_dev_opaque->buffer_list via list_del_init() while iio_push_to_buffers()
iterates buffer_list unlocked, and a subsequent buffer resize and re-enable
via iio_request_update_kfifo() can free and reallocate the underlying kfifo
while iio_store_to_kfifo() is writing to it. Should
max30210_buffer_postdisable() disable MAX30210_STATUS_A_FULL_MASK first and
synchronize with the IRQ handler before flushing the FIFO and returning?

[ ... ]

> +static void max30210_powerdown(void *data)
> +{
> +	struct gpio_desc *powerdown_gpio = data;
> +
> +	gpiod_set_value(powerdown_gpio, 1);
> +}
> +
> +static void max30210_quiesce(void *data)
> +{
> +	struct max30210_state *st = data;
> +
> +	/* Stop interrupt sources and autonomous conversions; errors are not actionable here */
> +	regmap_write(st->regmap, MAX30210_INT_EN_REG, 0);
> +	regmap_write(st->regmap, MAX30210_TEMP_CONV_REG, 0);
> +}
> +
> +static int max30210_setup(struct max30210_state *st, struct device *dev)
> +{
> +	struct gpio_desc *powerdown_gpio;
> +	unsigned int val;
> +	int ret;
> +
> +	powerdown_gpio = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
> +	if (IS_ERR(powerdown_gpio))
> +		return dev_err_probe(dev, PTR_ERR(powerdown_gpio),
> +				     "failed to request powerdown GPIO\n");
> +
> +	if (powerdown_gpio) {
> +		/* Deassert powerdown to power up device */
> +		gpiod_set_value(powerdown_gpio, 0);

[Severity: Medium]
Should max30210_setup() and max30210_powerdown() use
gpiod_set_value_cansleep() instead of gpiod_set_value()?

Both functions run in sleepable process context, so if powerdown-gpios is
connected through an I2C or SPI GPIO expander, calling gpiod_set_value() will
trigger a warning in gpiolib. Also, in max30210_setup(), powerdown_gpio is
requested with GPIOD_OUT_HIGH and immediately set to 0 on the next line
without any delay; should it either be requested with GPIOD_OUT_LOW directly
or wait between asserting and deasserting powerdown?

[ ... ]

> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = &max30210_channel;
> +	indio_dev->num_channels = 1;
> +	indio_dev->name = "max30210";
> +	indio_dev->info = &max30210_info;
> +
> +	/* Currently, without an interrupt there is no way to drain the FIFO into the buffer */
> +	if (client->irq) {
> +		ret = devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, &max30210_buffer_ops,
> +						      max30210_fifo_attributes);
> +		if (ret)
> +			return ret;
> +
> +		/* Runs after unregister, so nothing can re-arm the device; before powerdown. */
> +		ret = devm_add_action_or_reset(dev, max30210_quiesce, st);
> +		if (ret)
> +			return ret;
> +	}

[Severity: Medium]
What happens if client->irq is not provided, but userspace enables threshold
events via sysfs?

In max30210_probe(), indio_dev->channels is unconditionally set to
&max30210_channel (which includes max30210_events) and indio_dev->info is set
to &max30210_info. When client->irq == 0, no IRQ handler is registered, so
events can never be delivered, yet writing 1 to events/in_temp_thresh_*_en
still invokes max30210_write_event_config() -> max30210_autoconv_get(st). That
starts autonomous conversions and increments st->autoconv_users, which causes
max30210_read_raw() to reject all single-shot direct reads with -EBUSY, and
because max30210_quiesce() is only registered inside the if (client->irq)
block, unbinding the driver leaves autonomous conversions and interrupt bits
enabled in hardware. Should event support only be registered when client->irq
is present?

> +
> +	ret = devm_iio_device_register(dev, indio_dev);
> +	if (ret)
> +		return ret;
> +
> +	if (!client->irq)
> +		return 0;
> +
> +	/*
> +	 * Request the IRQ after registering the IIO device so devm tears it
> +	 * down first on removal, before the event interface is freed.
> +	 */
> +	return devm_request_threaded_irq(dev, client->irq, NULL,
> +					 max30210_irq_handler, IRQF_ONESHOT,
> +					 indio_dev->name, indio_dev);
> +}

[Severity: Medium]
Should devm_request_threaded_irq() be called before
devm_iio_device_register() in max30210_probe()?

Registering the IIO device first exposes the sysfs and character device
interfaces to userspace before max30210_irq_handler() is installed, and on
driver removal devres frees the IRQ while the IIO device is still registered
and before devm_iio_device_unreg() disables buffers or max30210_quiesce()
masks hardware interrupts. Note that the event interface is freed in
iio_dev_release() (registered via devm_iio_device_alloc()), so requesting the
IRQ before devm_iio_device_register() still ensures the IRQ is torn down
before indio_dev is freed.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261023080623.1830-1-johnerasmusmari.geronimo@analog.com?part=2

  reply	other threads:[~2026-10-23  8:30 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-23  8:06 [PATCH v5 0/2] Add support for Analog Devices MAX30210 John Erasmus Mari Geronimo
2026-10-23  8:06 ` [PATCH v5 1/2] dt-bindings: iio: temperature: add ADI MAX30210 John Erasmus Mari Geronimo
2026-10-23  8:06 ` [PATCH v5 2/2] iio: temperature: add support for Analog Devices MAX30210 John Erasmus Mari Geronimo
2026-10-23  8:30   ` sashiko-bot [this message]
2026-10-23  9:57   ` Andy Shevchenko
2026-10-24  2:24   ` Jonathan Cameron
2026-10-26  0:10 ` [PATCH v5 0/2] Add " Guenter Roeck

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=sashiko-outbox-182518@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=johnerasmusmari.geronimo@analog.com \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox