Re: [PATCH v2 2/2] iio: temperature: add support for Analog Devices MAX30210

From: Jonathan Cameron

Date: Sat Mar 07 2026 - 07:38:58 EST


On Wed, 4 Mar 2026 20:25:09 +0800
John Erasmus Mari Geronimo <johnerasmusmari.geronimo@xxxxxxxxxx> wrote:

> Add support for the Analog Devices MAX30210 I2C temperature
> sensor.
>
> The driver uses regmap for register access and integrates with
> the IIO framework. It supports:
>
> - Direct mode temperature conversion
> - Configurable sampling frequency
> - Threshold events
> - FIFO operation with IIO kfifo buffer support
> - Optional interrupt-driven data ready signaling
>
> The device provides 16-bit signed temperature data and a
> 64-sample FIFO.
>
> Signed-off-by: John Erasmus Mari Geronimo <johnerasmusmari.geronimo@xxxxxxxxxx>
Hi John

A few additional comment from me, but overall this is coming together nicely.

Thanks,

Jonathan

> diff --git a/drivers/iio/temperature/max30210.c b/drivers/iio/temperature/max30210.c
> new file mode 100644
> index 000000000000..839ed9830957
> --- /dev/null
> +++ b/drivers/iio/temperature/max30210.c

> +static void max30210_fifo_read(struct iio_dev *indio_dev)
> +{
> + struct max30210_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = regmap_bulk_read(st->regmap, MAX30210_FIFO_DATA_REG,
> + st->fifo_buf, 3 * 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[3 * i]);
> +
> + if (raw == MAX30210_FIFO_INVAL_DATA) {
Whilst this aligns with how the datasheet describes it, the
only bit that is definitely different for invalid data is bit 7 of the
tag. So could just check that. The code to get the temperature value then
just becomes memcpy() with the channel described as bit endian.
I don't mind if you prefer it this way though, just thought I'd raise
the possibility.

> + dev_err_ratelimited(&indio_dev->dev, "Invalid data\n");
> + continue;
> + }
> +
> + s16 temp = (s16)(raw & 0xFFFF);
> +
> + iio_push_to_buffers(indio_dev, &temp);
> + }
> +}


> +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 = 1000;
> +
> + return IIO_VAL_FRACTIONAL;
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + ret = regmap_read(st->regmap, MAX30210_TEMP_CONF_2_REG, &uval);
> + if (ret)
> + return ret;
> +
> + uval = FIELD_GET(MAX30210_TEMPCONF2_TEMP_PERIOD_MASK, uval);
> +
> + if (uval >= ARRAY_SIZE(max30210_samp_freq_avail))
> + uval = ARRAY_SIZE(max30210_samp_freq_avail) - 1;
uval = min(uval, ARRAY_SIZE(max30210_samp_freq_avail) - 1);

perhaps?

> +
> + *val = max30210_samp_freq_avail[uval][0];
> + *val2 = max30210_samp_freq_avail[uval][1];
> +
> + return IIO_VAL_FRACTIONAL;


> +static int max30210_write_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);
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ: {
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + if (val < 0 || val2 < 0)
> + return -EINVAL;
> +
> + for (unsigned int i = 0; i < ARRAY_SIZE(max30210_samp_freq_avail); i++) {
> + if (val == max30210_samp_freq_avail[i][0] &&
> + val2 == max30210_samp_freq_avail[i][1]) {

Flip the logic to reduce indent.
if (val != max...[0] ||
val != max...[1])
continue;

return....

> + return regmap_update_bits(st->regmap, MAX30210_TEMP_CONF_2_REG,
> + MAX30210_TEMPCONF2_TEMP_PERIOD_MASK,
> + FIELD_PREP(MAX30210_TEMPCONF2_TEMP_PERIOD_MASK, i));
> + }
> + }
> +
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> +}

> +static int max30210_buffer_preenable(struct iio_dev *indio_dev)
> +{
> + struct max30210_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = regmap_set_bits(st->regmap, MAX30210_INT_EN_REG, MAX30210_STATUS_A_FULL_MASK);
I would wrap this one. Won't hurt readability and keeps it inline with the wrapping
on the clear in postdisable()

> + if (ret)
> + return ret;
> +
> + ret = regmap_set_bits(st->regmap, MAX30210_FIFO_CONF_2_REG,
> + MAX30210_FIFOCONF1_FLUSH_FIFO_MASK);
> + if (ret)
> + return ret;
> +
> + return regmap_write(st->regmap, MAX30210_TEMP_CONV_REG,
> + MAX30210_TEMPCONV_AUTO_MASK | MAX30210_TEMPCONV_CONV_T_MASK);
> +}
> +
> +static int max30210_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> + struct max30210_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = regmap_write(st->regmap, MAX30210_TEMP_CONV_REG, 0x0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_set_bits(st->regmap, MAX30210_FIFO_CONF_2_REG,
> + MAX30210_FIFOCONF1_FLUSH_FIFO_MASK);
> + if (ret)
> + return ret;
> +
> + return regmap_clear_bits(st->regmap, MAX30210_INT_EN_REG,
> + MAX30210_STATUS_A_FULL_MASK);
> +}

> +
> +static const struct iio_chan_spec max30210_channels = {

only one. So max30210_channel

> + .type = IIO_TEMP,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_SCALE) |
> + BIT(IIO_CHAN_INFO_SAMP_FREQ),
> + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
> + .scan_index = 0,
> + .event_spec = max30210_events,
> + .num_event_specs = ARRAY_SIZE(max30210_events),
> + .scan_type = {
> + .sign = 's',
> + .realbits = 16,
> + .storagebits = 16,
> + .shift = 0,

A zero shift is kind of considered the obvious default, so we normally don't
bother setting it explicitly.

> + .endianness = IIO_CPU,
> + },
> +};
> +
> +static int max30210_setup(struct max30210_state *st, struct device *dev)
> +{
> + struct gpio_desc *powerdown_gpio;
> + unsigned int val;
> + int ret;
> +
> + /* Optional hardware reset via powerdown GPIO */
> + 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);
> + } else {
> + /* Software reset fallback */
> + ret = regmap_update_bits(st->regmap, MAX30210_SYS_CONF_REG,
> + MAX30210_SYSCONF_RESET_MASK,
> + MAX30210_SYSCONF_RESET_MASK);
> + if (ret)
> + return ret;
> + }
> +
> + /* Datasheet Figure 6:
/*
* Datasheet..

> + * tPU max = 700 µs after power-up or reset before device is ready.
> + */
> + fsleep(700);
> +
> + /* Clear status byte */
> + return regmap_read(st->regmap, MAX30210_STATUS_REG, &val);
> +}
> +

> +static int max30210_probe(struct i2c_client *client)
> +{

...

> + ret = devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, &max30210_buffer_ops,
> + max30210_fifo_attributes);
> + if (ret)
> + return ret;
> +
> + if (client->irq) {
> + ret = devm_request_irq(dev, client->irq, max30210_irq_handler, IRQF_NO_THREAD,

You are doing a bunch of bus accesses in your handler and I can't see anything int here
that wouldn't work in a thread. So I think this is backwards. All the work should be
done in an interrupt thread.

> + indio_dev->name, indio_dev);
> + if (ret)
> + return ret;
> + }
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}