* [PATCH v5 0/2] Add support for Analog Devices MAX30210
@ 2026-10-23 8:06 John Erasmus Mari Geronimo
2026-10-23 8:06 ` [PATCH v5 1/2] dt-bindings: iio: temperature: add ADI MAX30210 John Erasmus Mari Geronimo
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: John Erasmus Mari Geronimo @ 2026-10-23 8:06 UTC (permalink / raw)
To: linux-iio, devicetree
Cc: krzk+dt, jic23, dlechner, nuno.sa, Michael.Hennerich, andy, robh,
conor+dt, joshua.crofts1, Guenter Roeck, linux-hwmon
This series adds support for the Analog Devices MAX30210 I2C
temperature sensor.
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max30210.pdf
The device provides a 16-bit temperature output and includes
features such as a hardware FIFO, programmable sampling rate,
threshold alarms, and interrupt support. These capabilities map
naturally to the IIO subsystem, particularly the buffered interface
and event framework -- hwmon has no equivalent buffered-capture
framework, and its alarm attributes are polled rather than
interrupt-driven, so there is no path to supporting the FIFO or the
autonomous threshold comparators there.
Although the MAX30210 belongs to the same device family as the
MAX30208, it is implemented as a separate driver. The MAX30210 has
a significantly different feature set, including a 64-sample hardware
FIFO with configurable watermark interrupts, programmable sampling
rates, and event support. These features require buffered operation
and IRQ-driven data handling which are not present in the MAX30208
driver (which talks to the device directly via i2c_smbus_*() calls
with no regmap, and has no IIO buffer or event interface at all).
In addition, the MAX30210 uses a different register map and a more
flexible I2C address selection scheme. Supporting both devices in a
single driver would introduce substantial conditional logic, so a
separate driver keeps the implementation simpler and easier to
maintain.
Patch 1 adds the devicetree binding.
Patch 2 adds the IIO driver implementation.
Changes since v4:
https://lore.kernel.org/all/20261015012159.44-1-johnerasmusmari.geronimo@analog.com/
- Patch 1: restore Krzysztof's Reviewed-by, which was accidentally
left out of v4.
- Add Datasheet: tags and a datasheet link in the driver header.
- Disable interrupt sources and autonomous conversion on removal,
independently of whether a powerdown GPIO is present.
- Only take/release an autonomous-conversion reference when an
interrupt enable bit really changes; repeated writes of the same
value no longer leak a reference.
- buffer_preenable() now unwinds the A_FULL interrupt enable on
failure and buffer_postdisable() always clears it.
- Return IRQ_NONE from the interrupt handler when none of the
handled status bits is set.
- Wait tPU before the software reset when no powerdown GPIO is used.
- Minor review cleanups: error paths out of line in
max30210_autoconv_get(), min() for the period clamp,
devm_mutex_init(), designated initializer for i2c_device_id,
header grouping, one-line val/val2/mask argument lists.
- I kept requesting the interrupt after devm_iio_device_register() so
it is freed before the event interface on removal. I could not
test the proposed core change because my test platform runs a
6.12-based kernel.
Tested on real MAX30210 hardware (Raspberry Pi 4) with a 6.12-based
vendor kernel: no-IRQ fallback, buffer and event refcounting
(independently and concurrently), repeated rmmod/insmod under live
interrupts, powerdown GPIO release on removal and FIFO flush
correctness. That kernel has no KASAN or lockdep, and this version
is only build-tested against mainline.
Changes since v3:
https://lore.kernel.org/all/cover.1789032019.git.johnerasmusmari.geronimo@analog.com/
- regmap_noinc_read() for FIFO reads instead of regmap_bulk_read(),
which (combined with regmap caching) could silently return stale
cached values of unrelated registers instead of real FIFO data.
- Mark both bytes of TEMP_DATA_REG volatile for the same reason.
- Decouple threshold events from the buffer via a refcounted
autoconv_get()/autoconv_put() pair, so either can be used
independently or concurrently.
- Don't let a single-shot direct-mode read race with/clobber
autonomous conversion mode if a threshold event or the buffer is
already using it -- return -EBUSY instead.
- Don't hard-fail probe when there is no interrupt; fall back to
direct-mode-only operation.
- Fix devm teardown ordering so an in-flight interrupt can't use a
freed event interface on removal.
- Re-assert the powerdown GPIO on removal so the device reaches
standby instead of staying powered indefinitely.
- Style/API fixes from review (register/field grouping, _MASK
naming, FIELD_PREP/regmap_assign_bits usage, C99 initializers,
dropped redundant i2c_check_functionality() check, comment
cleanup).
Changes since v2:
https://lore.kernel.org/all/20260304122509.67931-1-johnerasmusmari.geronimo@analog.com/
- Fix FIFO byte-count handling and sample decoding.
- Fix IIO debug register read/write access.
- Validate event threshold values and sampling-frequency register values.
- Return the correct sampling-frequency format and reject direct reads
while buffered capture is enabled.
- Clean up the Kconfig entry and device-tree supply declaration.
John Erasmus Mari Geronimo (2):
dt-bindings: iio: temperature: add ADI MAX30210
iio: temperature: add support for Analog Devices MAX30210
.../iio/temperature/adi,max30210.yaml | 61 ++
MAINTAINERS | 8 +
drivers/iio/temperature/Kconfig | 13 +
drivers/iio/temperature/Makefile | 1 +
drivers/iio/temperature/max30210.c | 844 ++++++++++++++++++
5 files changed, 927 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/temperature/adi,max30210.yaml
create mode 100644 drivers/iio/temperature/max30210.c
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v5 1/2] dt-bindings: iio: temperature: add ADI MAX30210 2026-10-23 8:06 [PATCH v5 0/2] Add support for Analog Devices MAX30210 John Erasmus Mari Geronimo @ 2026-10-23 8:06 ` 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-26 0:10 ` [PATCH v5 0/2] Add " Guenter Roeck 2 siblings, 0 replies; 7+ messages in thread From: John Erasmus Mari Geronimo @ 2026-10-23 8:06 UTC (permalink / raw) To: linux-iio, devicetree Cc: krzk+dt, jic23, dlechner, nuno.sa, Michael.Hennerich, andy, robh, conor+dt, joshua.crofts1, Krzysztof Kozlowski Add device tree binding documentation for the Analog Devices MAX30210 temperature sensor. Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max30210.pdf Signed-off-by: John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> --- .../iio/temperature/adi,max30210.yaml | 61 +++++++++++++++++++ MAINTAINERS | 7 +++ 2 files changed, 68 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/temperature/adi,max30210.yaml diff --git a/Documentation/devicetree/bindings/iio/temperature/adi,max30210.yaml b/Documentation/devicetree/bindings/iio/temperature/adi,max30210.yaml new file mode 100644 index 0000000000000..8bfd154bbc438 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/temperature/adi,max30210.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2026 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/temperature/adi,max30210.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX30210 Temperature Sensor + +maintainers: + - John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com> + +description: | + The MAX30210 is a temperature sensor with an I2C interface. + https://www.analog.com/media/en/technical-documentation/data-sheets/max30210.pdf + +properties: + compatible: + enum: + - adi,max30210 + + reg: + maxItems: 1 + + vdd-supply: true + + powerdown-gpios: + description: GPIO connected to the CVT/PDB pin (active low). + maxItems: 1 + + interrupts: + description: Connected to INT pin. Interrupt triggered on both rising and falling edges. + maxItems: 1 + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/gpio/gpio.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + temperature-sensor@40 { + compatible = "adi,max30210"; + reg = <0x40>; + vdd-supply = <&vdd>; + powerdown-gpios = <&gpio 12 GPIO_ACTIVE_LOW>; + + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_EDGE_BOTH>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 396d4e76dccc2..6eb3cf66f431c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1653,6 +1653,13 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml F: drivers/iio/dac/max22007.c +ANALOG DEVICES INC MAX30210 DRIVER +M: John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com> +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/temperature/adi,max30210.yaml + ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus <antoniu.miclaus@analog.com> L: linux-iio@vger.kernel.org -- 2.34.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v5 2/2] iio: temperature: add support for Analog Devices MAX30210 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 ` John Erasmus Mari Geronimo 2026-10-23 8:30 ` sashiko-bot ` (2 more replies) 2026-10-26 0:10 ` [PATCH v5 0/2] Add " Guenter Roeck 2 siblings, 3 replies; 7+ messages in thread From: John Erasmus Mari Geronimo @ 2026-10-23 8:06 UTC (permalink / raw) To: linux-iio, devicetree Cc: krzk+dt, jic23, dlechner, nuno.sa, Michael.Hennerich, andy, robh, conor+dt, joshua.crofts1 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. Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max30210.pdf Signed-off-by: John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com> --- MAINTAINERS | 1 + drivers/iio/temperature/Kconfig | 13 + drivers/iio/temperature/Makefile | 1 + drivers/iio/temperature/max30210.c | 844 +++++++++++++++++++++++++++++ 4 files changed, 859 insertions(+) create mode 100644 drivers/iio/temperature/max30210.c diff --git a/MAINTAINERS b/MAINTAINERS index 6eb3cf66f431c..e479c64a284e0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1659,6 +1659,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/temperature/adi,max30210.yaml +F: drivers/iio/temperature/max30210.c ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus <antoniu.miclaus@analog.com> diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig index 9328b2250aced..05b3af6872b31 100644 --- a/drivers/iio/temperature/Kconfig +++ b/drivers/iio/temperature/Kconfig @@ -152,6 +152,19 @@ config MAX30208 This driver can also be built as a module. If so, the module will be called max30208. +config MAX30210 + tristate "Analog Devices MAX30210 temperature sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + If you say yes here you get support for MAX30210 low-power digital + temperature sensor chip connected via I2C. + + This driver can also be built as a module. If so, the module + will be called max30210. + config MAX31856 tristate "MAX31856 thermocouple sensor" depends on SPI diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile index 07d6e65709f7f..e5aad14dc09b9 100644 --- a/drivers/iio/temperature/Makefile +++ b/drivers/iio/temperature/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_LTC2983) += ltc2983.o obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o obj-$(CONFIG_MAX30208) += max30208.o +obj-$(CONFIG_MAX30210) += max30210.o obj-$(CONFIG_MAX31856) += max31856.o obj-$(CONFIG_MAX31865) += max31865.o obj-$(CONFIG_MCP9600) += mcp9600.o 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 @@ -0,0 +1,844 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices MAX30210 I2C Temperature Sensor driver + * + * Copyright 2026 Analog Devices Inc. + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max30210.pdf + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/lockdep.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/sysfs.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/units.h> + +#include <asm/byteorder.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/sysfs.h> + +#define MAX30210_STATUS_REG 0x00 +#define MAX30210_STATUS_A_FULL_MASK BIT(7) +#define MAX30210_STATUS_TEMP_RDY_MASK BIT(6) +#define MAX30210_STATUS_TEMP_DEC_MASK BIT(5) +#define MAX30210_STATUS_TEMP_INC_MASK BIT(4) +#define MAX30210_STATUS_TEMP_LO_MASK BIT(3) +#define MAX30210_STATUS_TEMP_HI_MASK BIT(2) +#define MAX30210_STATUS_PWR_RDY BIT(0) +#define MAX30210_INT_EN_REG 0x02 +#define MAX30210_FIFO_DATA_REG 0x08 +#define MAX30210_FIFO_CONF_1_REG 0x09 +#define MAX30210_FIFOCONF1_A_FULL_MASK GENMASK(5, 0) +#define MAX30210_FIFO_CONF_2_REG 0x0A +#define MAX30210_FIFOCONF2_FLUSH_FIFO BIT(4) +#define MAX30210_SYS_CONF_REG 0x11 +#define MAX30210_SYSCONF_RESET BIT(0) +#define MAX30210_PIN_CONF_REG 0x12 +#define MAX30210_PINCONF_EXT_CNV_EN BIT(7) +#define MAX30210_PINCONF_EXT_CVT_ICFG BIT(6) +#define MAX30210_PINCONF_INT_FCFG_MASK GENMASK(3, 2) +#define MAX30210_PINCONF_INT_OCFG_MASK GENMASK(1, 0) +#define MAX30210_TEMP_ALM_HI_REG 0x22 +#define MAX30210_TEMP_ALM_LO_REG 0x24 +#define MAX30210_TEMP_INC_THRESH_REG 0x26 +#define MAX30210_TEMP_DEC_THRESH_REG 0x27 +#define MAX30210_TEMP_CONF_1_REG 0x28 +#define MAX30210_TEMPCONF1_CHG_DET_EN BIT(3) +#define MAX30210_TEMPCONF1_RATE_CHG_FILTER_MASK GENMASK(2, 0) +#define MAX30210_TEMP_CONF_2_REG 0x29 +#define MAX30210_TEMPCONF2_TEMP_PERIOD_MASK GENMASK(3, 0) +#define MAX30210_TEMPCONF2_ALERT_MODE BIT(7) +#define MAX30210_TEMP_CONV_REG 0x2A +#define MAX30210_TEMPCONV_CONV_T BIT(0) +#define MAX30210_TEMPCONV_AUTO BIT(1) +#define MAX30210_TEMP_DATA_REG 0x2B +#define MAX30210_TEMP_SLOPE_REG 0x2D +#define MAX30210_UNIQUE_ID_REG 0x30 +#define MAX30210_PART_ID_REG 0xFF + +#define MAX30210_PART_ID 0x45 +#define MAX30210_FIFO_SIZE 64 +#define MAX30210_FIFO_BYTES_PER_SAMPLE 3 +#define MAX30210_FIFO_TEMP_MASK GENMASK(15, 0) +#define MAX30210_FIFO_INVAL_DATA GENMASK(23, 0) +#define MAX30210_WATERMARK_DEFAULT (0x40 - 0x1F) + +#define MAX30210_UNIQUE_ID_LEN 6 +#define MAX30210_EXT_CVT_FREQ_MIN 1 +#define MAX30210_EXT_CVT_FREQ_MAX 20 + +struct max30210_state { + struct regmap *regmap; + /* Raw FIFO byte buffer */ + u8 fifo_buf[MAX30210_FIFO_BYTES_PER_SAMPLE * MAX30210_FIFO_SIZE]; + /* Protects autoconv_users and the autonomous-conversion enable bit */ + struct mutex lock; + unsigned int autoconv_users; + u8 watermark; +}; + +static const int max30210_samp_freq_avail[][2] = { + { 0, 15625 }, + { 0, 31250 }, + { 0, 62500 }, + { 0, 125000 }, + { 0, 250000 }, + { 0, 500000 }, + { 1, 0 }, + { 2, 0 }, + { 4, 0 }, + { 8, 0 }, +}; + +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; + } +} + +static const struct regmap_config max30210_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX30210_PART_ID_REG, + .volatile_reg = max30210_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + +static int max30210_read_temp(struct regmap *regmap, unsigned int reg, int *temp) +{ + __be16 val; + int ret; + + ret = regmap_bulk_read(regmap, reg, &val, sizeof(val)); + if (ret) + return ret; + + *temp = sign_extend32(be16_to_cpu(val), 15); + + return IIO_VAL_INT; +} + +static int max30210_write_temp(struct regmap *regmap, unsigned int reg, int temp) +{ + __be16 val; + + val = cpu_to_be16(temp); + + return regmap_bulk_write(regmap, reg, &val, sizeof(val)); +} + +/* Keeps the device sampling autonomously while buffer and/or events need it. */ +static int max30210_autoconv_get(struct max30210_state *st) +{ + int ret; + + lockdep_assert_held(&st->lock); + + if (st->autoconv_users == 0) { + ret = regmap_write(st->regmap, MAX30210_TEMP_CONV_REG, + MAX30210_TEMPCONV_AUTO | MAX30210_TEMPCONV_CONV_T); + if (ret) + return ret; + } + + st->autoconv_users++; + + return 0; +} + +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; +} + +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; +} + +static int max30210_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct max30210_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int max30210_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct max30210_state *st = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + switch (type) { + case IIO_EV_TYPE_THRESH: + return max30210_read_temp(st->regmap, MAX30210_TEMP_ALM_HI_REG, val); + default: + return -EINVAL; + } + case IIO_EV_DIR_FALLING: + switch (type) { + case IIO_EV_TYPE_THRESH: + return max30210_read_temp(st->regmap, MAX30210_TEMP_ALM_LO_REG, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int max30210_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct max30210_state *st = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + if (val < S16_MIN || val > S16_MAX) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + switch (type) { + case IIO_EV_TYPE_THRESH: + return max30210_write_temp(st->regmap, MAX30210_TEMP_ALM_HI_REG, val); + default: + return -EINVAL; + } + case IIO_EV_DIR_FALLING: + switch (type) { + case IIO_EV_TYPE_THRESH: + return max30210_write_temp(st->regmap, MAX30210_TEMP_ALM_LO_REG, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int max30210_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct max30210_state *st = iio_priv(indio_dev); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, MAX30210_INT_EN_REG, &val); + if (ret) + return ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + switch (type) { + case IIO_EV_TYPE_THRESH: + return FIELD_GET(MAX30210_STATUS_TEMP_HI_MASK, val); + default: + return -EINVAL; + } + case IIO_EV_DIR_FALLING: + switch (type) { + case IIO_EV_TYPE_THRESH: + return FIELD_GET(MAX30210_STATUS_TEMP_LO_MASK, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int max30210_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct max30210_state *st = iio_priv(indio_dev); + unsigned int mask, val; + int ret; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + mask = MAX30210_STATUS_TEMP_HI_MASK; + break; + case IIO_EV_DIR_FALLING: + mask = MAX30210_STATUS_TEMP_LO_MASK; + break; + default: + return -EINVAL; + } + + guard(mutex)(&st->lock); + + /* The core does not dedupe; only take/drop a reference on a real change. */ + ret = regmap_read(st->regmap, MAX30210_INT_EN_REG, &val); + if (ret) + return ret; + + if (!!(val & mask) == state) + return 0; + + /* + * Threshold events need the device sampling autonomously, independent + * of whether the FIFO buffer is also enabled. + */ + if (state) { + ret = max30210_autoconv_get(st); + if (ret) + return ret; + } + + ret = regmap_assign_bits(st->regmap, MAX30210_INT_EN_REG, mask, state); + if (ret) { + if (state) + max30210_autoconv_put(st); + return ret; + } + + return state ? 0 : max30210_autoconv_put(st); +} + +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; + case IIO_CHAN_INFO_SAMP_FREQ: { + unsigned int period; + + ret = regmap_read(st->regmap, MAX30210_TEMP_CONF_2_REG, &uval); + if (ret) + return ret; + + period = FIELD_GET(MAX30210_TEMPCONF2_TEMP_PERIOD_MASK, uval); + + /* + * TEMP_PERIOD is an index into the sampling frequency lookup + * table. Clamp in case the register holds a reserved value. + */ + period = min(period, ARRAY_SIZE(max30210_samp_freq_avail) - 1); + + *val = max30210_samp_freq_avail[period][0]; + *val2 = max30210_samp_freq_avail[period][1]; + + return IIO_VAL_INT_PLUS_MICRO; + } + case IIO_CHAN_INFO_RAW: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + guard(mutex)(&st->lock); + + /* + * TEMP_DATA_REG is only refreshed by a single-shot + * conversion; the buffer/FIFO path is the only thing that + * gets updated during AUTO mode. If AUTO is already running + * for the buffer and/or events, triggering a one-off + * conversion here would stomp the AUTO bit out from under + * whoever enabled it, and there is no way to get a fresh + * single-shot value without doing so -- refuse instead of + * returning stale data. + */ + if (st->autoconv_users > 0) + return -EBUSY; + + ret = regmap_write(st->regmap, MAX30210_TEMP_CONV_REG, + MAX30210_TEMPCONV_CONV_T); + if (ret) + return ret; + + /* + * Wait until CONVERT_T auto-clears. + * Datasheet: + * tBIAS_WU = 260 µs + * tINT = 8 ms + * + * Worst-case conversion ≈ 8.26 ms. + * Use 10 ms timeout for margin. + */ + ret = regmap_read_poll_timeout(st->regmap, MAX30210_TEMP_CONV_REG, uval, + !(uval & MAX30210_TEMPCONV_CONV_T), + 500, 10 * USEC_PER_MSEC); + if (ret) + return ret; + + return max30210_read_temp(st->regmap, MAX30210_TEMP_DATA_REG, val); + } + default: + return -EINVAL; + } +} + +static int max30210_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = &max30210_samp_freq_avail[0][0]; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(max30210_samp_freq_avail) * 2; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +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: { + if (val < 0 || val2 < 0) + return -EINVAL; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + 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]) + continue; + + 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_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 ssize_t hwfifo_watermark_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct max30210_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d\n", st->watermark); +} + +static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0); + +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1"); +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max, __stringify(MAX30210_FIFO_SIZE)); + +static const struct iio_dev_attr *max30210_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark_min, + &iio_dev_attr_hwfifo_watermark_max, + &iio_dev_attr_hwfifo_watermark, + NULL +}; + +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; +} + +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; +} + +static const struct iio_buffer_setup_ops max30210_buffer_ops = { + .preenable = max30210_buffer_preenable, + .postdisable = max30210_buffer_postdisable, +}; + +static const struct iio_info max30210_info = { + .read_raw = max30210_read_raw, + .read_avail = max30210_read_avail, + .write_raw = max30210_write_raw, + .hwfifo_set_watermark = max30210_set_watermark, + .debugfs_reg_access = max30210_reg_access, + .read_event_value = max30210_read_event, + .write_event_value = max30210_write_event, + .write_event_config = max30210_write_event_config, + .read_event_config = max30210_read_event_config, +}; + +static const struct iio_event_spec max30210_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec 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, + .endianness = IIO_CPU, + }, +}; + +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); + + ret = devm_add_action_or_reset(dev, max30210_powerdown, powerdown_gpio); + if (ret) + return ret; + } else { + /* The supply was just enabled; the device needs tPU before it accepts I2C. */ + fsleep(700); + + ret = regmap_set_bits(st->regmap, MAX30210_SYS_CONF_REG, + MAX30210_SYSCONF_RESET); + if (ret) + return ret; + } + + /* + * Datasheet Figure 6: + * 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) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct max30210_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable vdd regulator.\n"); + + st->regmap = devm_regmap_init_i2c(client, &max30210_regmap); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to allocate regmap.\n"); + + st->watermark = MAX30210_WATERMARK_DEFAULT; + + ret = max30210_setup(st, dev); + if (ret) + return ret; + + 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; + } + + 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); +} + +static const struct i2c_device_id max30210_id[] = { + { .name = "max30210" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max30210_id); + +static const struct of_device_id max30210_of_match[] = { + { .compatible = "adi,max30210" }, + { } +}; +MODULE_DEVICE_TABLE(of, max30210_of_match); + +static struct i2c_driver max30210_driver = { + .driver = { + .name = "max30210", + .of_match_table = max30210_of_match, + }, + .probe = max30210_probe, + .id_table = max30210_id, +}; +module_i2c_driver(max30210_driver); + +MODULE_AUTHOR("John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com>"); +MODULE_DESCRIPTION("MAX30210 low-power digital temperature sensor"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v5 2/2] iio: temperature: add support for Analog Devices MAX30210 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 2026-10-23 9:57 ` Andy Shevchenko 2026-10-24 2:24 ` Jonathan Cameron 2 siblings, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-10-23 8:30 UTC (permalink / raw) To: John Erasmus Mari Geronimo, devicetree; +Cc: robh, conor+dt 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 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 2/2] iio: temperature: add support for Analog Devices MAX30210 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 @ 2026-10-23 9:57 ` Andy Shevchenko 2026-10-24 2:24 ` Jonathan Cameron 2 siblings, 0 replies; 7+ messages in thread From: Andy Shevchenko @ 2026-10-23 9:57 UTC (permalink / raw) To: John Erasmus Mari Geronimo Cc: linux-iio, devicetree, krzk+dt, jic23, dlechner, nuno.sa, Michael.Hennerich, andy, robh, conor+dt, joshua.crofts1 On Fri, Oct 23, 2026 at 04:06:23PM +0800, John Erasmus Mari Geronimo 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. ... > +static int max30210_read_event(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir, > + enum iio_event_info info, int *val, int *val2) > +{ > + struct max30210_state *st = iio_priv(indio_dev); > + > + if (info != IIO_EV_INFO_VALUE) > + return -EINVAL; Seems in the same way you can test for IIO_EV_TYPE_THRESH. > + switch (dir) { > + case IIO_EV_DIR_RISING: > + switch (type) { > + case IIO_EV_TYPE_THRESH: > + return max30210_read_temp(st->regmap, MAX30210_TEMP_ALM_HI_REG, val); > + default: > + return -EINVAL; > + } > + case IIO_EV_DIR_FALLING: > + switch (type) { > + case IIO_EV_TYPE_THRESH: > + return max30210_read_temp(st->regmap, MAX30210_TEMP_ALM_LO_REG, val); > + default: > + return -EINVAL; > + } > + default: > + return -EINVAL; > + } > +} > + > +static int max30210_write_event(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir, > + enum iio_event_info info, int val, int val2) > +{ > + struct max30210_state *st = iio_priv(indio_dev); > + > + if (info != IIO_EV_INFO_VALUE) > + return -EINVAL; Ditto. > + if (val < S16_MIN || val > S16_MAX) > + return -EINVAL; > + > + switch (dir) { > + case IIO_EV_DIR_RISING: > + switch (type) { > + case IIO_EV_TYPE_THRESH: > + return max30210_write_temp(st->regmap, MAX30210_TEMP_ALM_HI_REG, val); > + default: > + return -EINVAL; > + } > + case IIO_EV_DIR_FALLING: > + switch (type) { > + case IIO_EV_TYPE_THRESH: > + return max30210_write_temp(st->regmap, MAX30210_TEMP_ALM_LO_REG, val); You can reduce the lines at least by 7 characters with struct regmap *map = st->regmap; at the top of the function. Use same trick elsewhere. > + default: > + return -EINVAL; > + } > + default: > + return -EINVAL; > + } > +} > + > +static int max30210_read_event_config(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir) > +{ > + struct max30210_state *st = iio_priv(indio_dev); > + unsigned int val; > + int ret; > + > + ret = regmap_read(st->regmap, MAX30210_INT_EN_REG, &val); > + if (ret) > + return ret; Do you need to do this for other than IIO_EV_TYPE_THRESH? Otherwise check for that first and bail out earlier. > + switch (dir) { > + case IIO_EV_DIR_RISING: > + switch (type) { > + case IIO_EV_TYPE_THRESH: > + return FIELD_GET(MAX30210_STATUS_TEMP_HI_MASK, val); > + default: > + return -EINVAL; > + } > + case IIO_EV_DIR_FALLING: > + switch (type) { > + case IIO_EV_TYPE_THRESH: > + return FIELD_GET(MAX30210_STATUS_TEMP_LO_MASK, val); > + default: > + return -EINVAL; > + } > + default: > + return -EINVAL; > + } > +} ... > + guard(mutex)(&st->lock); > + > + /* The core does not dedupe; only take/drop a reference on a real change. */ > + ret = regmap_read(st->regmap, MAX30210_INT_EN_REG, &val); > + if (ret) > + return ret; > + > + if (!!(val & mask) == state) > + return 0; regmap_test_bits() ? > + /* > + * Threshold events need the device sampling autonomously, independent > + * of whether the FIFO buffer is also enabled. > + */ > + if (state) { > + ret = max30210_autoconv_get(st); > + if (ret) > + return ret; > + } > + ret = regmap_assign_bits(st->regmap, MAX30210_INT_EN_REG, mask, state); > + if (ret) { > + if (state) > + max30210_autoconv_put(st); > + return ret; > + } > + > + return state ? 0 : max30210_autoconv_put(st); Why not simply if (state) { ret = regmap_set_bits(map, MAX30210_INT_EN_REG, mask); if (ret) max30210_autoconv_put(st); return ret; } else { ret = regmap_clear_bits(map, MAX30210_INT_EN_REG, mask); if (ret) return ret; return max30210_autoconv_put(st); } (Yeah, if I suggested _assign_bits() earlier, I was clearly wrong, this is more complex logic here.) > +} ... > +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; > + case IIO_CHAN_INFO_SAMP_FREQ: { > + unsigned int period; Units? period_Hz? > + ret = regmap_read(st->regmap, MAX30210_TEMP_CONF_2_REG, &uval); > + if (ret) > + return ret; > + > + period = FIELD_GET(MAX30210_TEMPCONF2_TEMP_PERIOD_MASK, uval); > + > + /* > + * TEMP_PERIOD is an index into the sampling frequency lookup > + * table. Clamp in case the register holds a reserved value. > + */ > + period = min(period, ARRAY_SIZE(max30210_samp_freq_avail) - 1); > + > + *val = max30210_samp_freq_avail[period][0]; > + *val2 = max30210_samp_freq_avail[period][1]; > + > + return IIO_VAL_INT_PLUS_MICRO; > + } > + case IIO_CHAN_INFO_RAW: { > + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); > + if (IIO_DEV_ACQUIRE_FAILED(claim)) > + return -EBUSY; > + > + guard(mutex)(&st->lock); > + > + /* > + * TEMP_DATA_REG is only refreshed by a single-shot > + * conversion; the buffer/FIFO path is the only thing that > + * gets updated during AUTO mode. If AUTO is already running > + * for the buffer and/or events, triggering a one-off > + * conversion here would stomp the AUTO bit out from under > + * whoever enabled it, and there is no way to get a fresh > + * single-shot value without doing so -- refuse instead of > + * returning stale data. > + */ > + if (st->autoconv_users > 0) > + return -EBUSY; > + > + ret = regmap_write(st->regmap, MAX30210_TEMP_CONV_REG, > + MAX30210_TEMPCONV_CONV_T); > + if (ret) > + return ret; > + > + /* > + * Wait until CONVERT_T auto-clears. > + * Datasheet: > + * tBIAS_WU = 260 µs > + * tINT = 8 ms > + * > + * Worst-case conversion ≈ 8.26 ms. > + * Use 10 ms timeout for margin. > + */ > + ret = regmap_read_poll_timeout(st->regmap, MAX30210_TEMP_CONV_REG, uval, > + !(uval & MAX30210_TEMPCONV_CONV_T), > + 500, 10 * USEC_PER_MSEC); Logical split ret = regmap_read_poll_timeout(map, MAX30210_TEMP_CONV_REG, uval, !(uval & MAX30210_TEMPCONV_CONV_T), 500, 10 * USEC_PER_MSEC); > + if (ret) > + return ret; > + > + return max30210_read_temp(st->regmap, MAX30210_TEMP_DATA_REG, val); return max30210_read_temp(map, MAX30210_TEMP_DATA_REG, val); will fit into 80 characters limit. > + } > + default: > + return -EINVAL; > + } > +} ... > +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. > + */ The rule of thumb is to make lines in the comments balanced (by length): /* * 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; Oh, does it compile this way with scoped_guard()? > + 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; > +} > +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; > +} TBH, I don't like how these two callbacks are implemented in terms of goto:s and additional error code variable, but I have nothing to suggest, so we would live with the above... :-( ... > +static void max30210_powerdown(void *data) > +{ > + struct gpio_desc *powerdown_gpio = data; > + > + gpiod_set_value(powerdown_gpio, 1); > +} static void max30210_powerdown(void *powerdown_gpio) { 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 */ Make it multi-line to fit 80 characters limit. > + regmap_write(st->regmap, MAX30210_INT_EN_REG, 0); > + regmap_write(st->regmap, MAX30210_TEMP_CONV_REG, 0); > +} ... > +static int max30210_probe(struct i2c_client *client) > +{ > + struct device *dev = &client->dev; > + struct iio_dev *indio_dev; > + struct max30210_state *st; > + int ret; > + > + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); > + if (!indio_dev) > + return -ENOMEM; > + > + st = iio_priv(indio_dev); > + > + ret = devm_mutex_init(dev, &st->lock); > + if (ret) > + return ret; > + > + ret = devm_regulator_get_enable(dev, "vdd"); > + if (ret) > + return dev_err_probe(dev, ret, > + "Failed to enable vdd regulator.\n"); Here it is fine to have a single line return dev_err_probe(dev, ret, "Failed to enable vdd regulator.\n"); > + st->regmap = devm_regmap_init_i2c(client, &max30210_regmap); > + if (IS_ERR(st->regmap)) > + return dev_err_probe(dev, PTR_ERR(st->regmap), > + "Failed to allocate regmap.\n"); > + > + st->watermark = MAX30210_WATERMARK_DEFAULT; > + > + ret = max30210_setup(st, dev); > + if (ret) > + return ret; > + > + 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); 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; > + } > + > + 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); > +} ... Module description: > +MODULE_DESCRIPTION("MAX30210 low-power digital temperature sensor"); Kconfig: > + tristate "Analog Devices MAX30210 temperature sensor" Top comment: > * Analog Devices MAX30210 I2C Temperature Sensor driver Make all three texts to be the same. -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 2/2] iio: temperature: add support for Analog Devices MAX30210 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 2026-10-23 9:57 ` Andy Shevchenko @ 2026-10-24 2:24 ` Jonathan Cameron 2 siblings, 0 replies; 7+ messages in thread From: Jonathan Cameron @ 2026-10-24 2:24 UTC (permalink / raw) To: John Erasmus Mari Geronimo Cc: linux-iio, devicetree, krzk+dt, dlechner, nuno.sa, Michael.Hennerich, andy, robh, conor+dt, joshua.crofts1 On Fri, 23 Oct 2026 16:06:23 +0800 John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com> 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. > > Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max30210.pdf > Signed-off-by: John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com> A few things from sashiko https://sashiko.dev/#/patchset/20261023080623.1830-1-johnerasmusmari.geronimo%40analog.com Feel free to just reply to the thread saying why they are wrong if they are. In particularly it reminded me of the reply I made to patch 2 about what we might want to do to fix the event interface lifetime issue. I haven't had a chance to get back to that since. > 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 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) See the stuff in cleanup.h Don't combine guards and gotos in same function. They may be fine in some cases but it is considered bad practice. Here I'd just not use the guard. Make sure to check for other instances of this. > + 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; > +} > +static int max30210_probe(struct i2c_client *client) > +{ > + struct device *dev = &client->dev; > + struct iio_dev *indio_dev; > + struct max30210_state *st; > + int ret; > + > + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); > + if (!indio_dev) > + return -ENOMEM; > + > + st = iio_priv(indio_dev); > + > + ret = devm_mutex_init(dev, &st->lock); > + if (ret) > + return ret; > + > + ret = devm_regulator_get_enable(dev, "vdd"); > + if (ret) > + return dev_err_probe(dev, ret, > + "Failed to enable vdd regulator.\n"); > + > + st->regmap = devm_regmap_init_i2c(client, &max30210_regmap); > + if (IS_ERR(st->regmap)) > + return dev_err_probe(dev, PTR_ERR(st->regmap), > + "Failed to allocate regmap.\n"); > + > + st->watermark = MAX30210_WATERMARK_DEFAULT; > + > + ret = max30210_setup(st, dev); > + if (ret) > + return ret; > + > + 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; > + } > + > + 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); Please take a look at what I said on v4 about this. I agree there is an issue in the core code but don't want to fix it by introducing a race here. It would be helpful to know if the code I put in that thread works or not. If you are seeing reasons not to make a change along those lines reply to that thread to continue the discussion. Jonathan > +} ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 0/2] Add support for Analog Devices MAX30210 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-26 0:10 ` Guenter Roeck 2 siblings, 0 replies; 7+ messages in thread From: Guenter Roeck @ 2026-10-26 0:10 UTC (permalink / raw) To: John Erasmus Mari Geronimo, linux-iio, devicetree Cc: krzk+dt, jic23, dlechner, nuno.sa, Michael.Hennerich, andy, robh, conor+dt, joshua.crofts1, linux-hwmon On 10/23/26 01:06, John Erasmus Mari Geronimo wrote: > This series adds support for the Analog Devices MAX30210 I2C > temperature sensor. > > Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max30210.pdf > > The device provides a 16-bit temperature output and includes > features such as a hardware FIFO, programmable sampling rate, > threshold alarms, and interrupt support. These capabilities map > naturally to the IIO subsystem, particularly the buffered interface > and event framework -- hwmon has no equivalent buffered-capture > framework, and its alarm attributes are polled rather than > interrupt-driven, so there is no path to supporting the FIFO or the > autonomous threshold comparators there. Oh, please, do not make up stuff. The hardware monitoring subsystem does support interrupts, but it is not mandatory. Sure, most drivers don't implement it, but that does not mean it is not supported. Guenter ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-26 0:10 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 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 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
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.