Skip to contentSkip to conversation

iio: health: add MAX86150 ECG and PPG biosensor driver

PATCH Superseded Md Shofiqul Islam 9 participants 39 messages Jun 23 – Jul 6

Subsystems (3)

v1 of 11 Superseded Latest is v11 →

lore
Share & tools
lore
#0 lore raw

cover iio: health: add MAX86150 ECG and PPG biosensor driver

The MAX86150 (Maxim/Analog Devices) combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device. Data is produced at up to 3200 Hz and buffered in a 32-entry hardware FIFO with a configurable almost-full interrupt.

This patch adds an IIO driver that exposes three channels:

  in_intensity_red_raw   -- PPG Red LED, 19-bit unsigned
  in_intensity_ir_raw    -- PPG IR LED, 19-bit unsigned
  in_voltage0_raw        -- ECG biopotential, 18-bit signed

Key implementation choices:

- regmap_noinc_read() for FIFO access: reads 9 bytes (3 slots x 3 bytes) from the streaming FIFO_DATA register in a single I2C burst without address auto-increment.

- IIO hardware trigger backed by the device interrupt (active-low, FIFO almost-full). When an interrupt GPIO is provided in the device tree, a trigger is registered and wired to the IIO triggered buffer path for continuous low-overhead capture.

- All resource management uses devm_* APIs.

Known limitations to be addressed in v2:
- IIO_CHAN_INFO_SCALE not yet implemented.
- Runtime PM (SYS_CTRL SHDN bit) not yet implemented.
- Not yet tested on physical hardware.

Md Shofiqul Islam (1):
iio: health: add MAX86150 ECG and PPG biosensor driver

 .../bindings/iio/health/maxim,max86150.yaml   |  67 +++
 drivers/iio/health/Kconfig                    |  23 +
 drivers/iio/health/Makefile                   |   1 +
 drivers/iio/health/max86150.c                 | 512 ++++++++++++++++++
 4 files changed, 603 insertions(+)
 create mode 100755 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
 create mode 100755 drivers/iio/health/max86150.c
Signature · 1 line
2.51.1
#1 lore raw

PATCH 1/1 iio: health: add MAX86150 ECG and PPG biosensor driver

The MAX86150 (Maxim/Analog Devices) integrates two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device. Data is captured via a 32-entry hardware FIFO with a configurable almost-full interrupt.

The driver exposes three IIO channels:

  - in_intensity_red_raw   (PPG Red, 19-bit unsigned)
  - in_intensity_ir_raw    (PPG IR, 19-bit unsigned)
  - in_voltage0_raw        (ECG, 18-bit signed)

A hardware trigger is registered when an interrupt GPIO is provided in the device tree, enabling the IIO triggered buffer path for continuous low-overhead capture.

FIFO reads use regmap_noinc_read() to burst-read 9 bytes (3 slots x 3 bytes) from the streaming FIFO_DATA register in a single I2C transaction.

---
 .../bindings/iio/health/maxim,max86150.yaml   |  67 +++
 drivers/iio/health/Kconfig                    |  23 +
 drivers/iio/health/Makefile                   |   1 +
 drivers/iio/health/max86150.c                 | 512 ++++++++++++++++++
 4 files changed, 603 insertions(+)
 create mode 100755 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
 create mode 100755 drivers/iio/health/max86150.c
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +67 -0
new file mode 100755
index 000000000000..1bf10fd1a3d2
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/health/maxim,max86150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX86150 ECG and PPG Biosensor
+
+maintainers:
+  - Md Shofiqul Islam <[email protected]>
+
+description: |
+  The MAX86150 is an integrated biosensor SoC that combines:
+    - Two PPG (photoplethysmography) channels: Red LED and IR LED,
+      for heart rate and blood-oxygen saturation (SpO2) measurement.
+    - One ECG (electrocardiogram) channel for biopotential recording.
+
+  The device communicates over I2C at up to 400 kHz and raises an
+  active-low interrupt when the 32-entry hardware FIFO reaches its
+  configurable almost-full threshold.
+
+  Datasheet:
+    https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf
+
+properties:
+  compatible:
+    const: maxim,max86150
+
+  reg:
+    maxItems: 1
+    description: I2C device address, always 0x5E.
+
+  interrupts:
+    maxItems: 1
+    description: |
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready.
+
+  vdd-supply:
+    description: Digital core supply, 1.71 V to 1.89 V.
+
+  leds-supply:
+    description: LED anode supply, 3.0 V to 5.5 V.
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        biosensor@5e {
+            compatible = "maxim,max86150";
+            reg = <0x5e>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            vdd-supply = <&vdd_1v8>;
+            leds-supply = <&vdd_3v3>;
+        };
+    };
drivers/iio/health/Kconfig +23 -0
index a89f3abf11f4..6496cf55290c 100644
@@ -13,6 +13,7 @@ config AFE4403
 	depends on SPI_MASTER
 	select REGMAP_SPI
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4403
@@ -26,6 +27,7 @@ config AFE4404
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4404
@@ -39,6 +41,7 @@ config MAX30100
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -52,6 +55,7 @@ config MAX30102
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -62,4 +66,23 @@ config MAX30102
 
 endmenu
 
+
+config MAX86150
+	tristate "MAX86150 ECG and PPG biosensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGER
+	select IIO_TRIGGERED_BUFFER
+	select REGMAP_I2C
+	help
+	  Say Y here to enable support for the Maxim MAX86150 combined
+	  ECG and photoplethysmography (PPG) biosensor.
+
+	  The driver exposes three IIO channels: two PPG optical channels
+	  (Red and IR LED) for heart rate and SpO2 monitoring, and one
+	  ECG channel for biopotential recording.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called max86150.
+
 endmenu
drivers/iio/health/Makefile +1 -0
index 910817112258..04fc73c58444 100644
@@ -9,3 +9,4 @@ obj-$(CONFIG_AFE4403)		+= afe4403.o
 obj-$(CONFIG_AFE4404)		+= afe4404.o
 obj-$(CONFIG_MAX30100)		+= max30100.o
 obj-$(CONFIG_MAX30102)		+= max30102.o
+obj-$(CONFIG_MAX86150)		+= max86150.o
drivers/iio/health/max86150.c new file +512 -0

512 lines not on this page · raw message

Signature · 1 line
2.51.1
#2 lore raw
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: - [High] Buffer overflow during triggered buffer capture. - [High] Missing IIO direct mode locking allows…

Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] Buffer overflow during triggered buffer capture.
- [High] Missing IIO direct mode locking allows concurrent sysfs reads to silently corrupt the hardware FIFO state.
- [High] Calculation of available FIFO samples evaluates to 0 when the FIFO is fully saturated, leading to a permanent capture stall.
- [High] Buffer data packing ignores active_scan_mask, leading to data corruption when a subset of channels is enabled.
- [High] Requesting an IRQ with IRQF_ONESHOT without a threaded handler causes unconditional probe failure.
- [High] Hardware interrupt unconditionally enabled at boot can permanently stall the edge-triggered line.
- [Medium] UAPI Endianness Mismatch causing data corruption on Big Endian architectures.
- [Medium] Device is left running at full power and emitting interrupts when the driver is unloaded.
- [Medium] Documented DT regulators vdd-supply and leds-supply are ignored, causing initialization failure on unpowered platforms.
- [Medium] Unbalanced iio_trigger_get() causes a reference count memory leak.
--

commit 86527e62ebc7afa90aeb829c2bdf9c041cf99dd9
Author: Md Shofiqul Islam <[email protected]>

iio: health: add MAX86150 ECG and PPG biosensor driver

The MAX86150 integrates PPG and ECG channels in a single I2C device. The driver exposes three IIO channels and supports hardware-triggered continuous low-overhead capture.

Quoted 5 lines
drivers/iio/health/max86150.c new file +0 -0
new file mode 100755
index 0000000000000..3cde4af6650b7

[ ... ]

drivers/iio/health/max86150.c line 113

Quoted 6 lines
drivers/iio/health/max86150.c new file +6 -0
+struct max86150_data {
+	struct regmap		*regmap;
+	struct device		*dev;
+	struct iio_trigger	*trig;
+	s32  buf[4] __aligned(8);
+};

[Severity: High]
Does this buffer have enough space for the 64-bit timestamp?

With three 32-bit channels (12 bytes) and a 64-bit timestamp needing 8-byte alignment, the timestamp would be placed at byte offset 16. However, this array is exactly 16 bytes.

Calling iio_push_to_buffers_with_timestamp() in max86150_trigger_handler() could write the timestamp past the end of the array.

[ ... ]

Quoted 5 lines
drivers/iio/health/max86150.c new file +5 -0
+static const struct iio_chan_spec max86150_channels[] = {
+	{
+		/* PPG Red LED - optical intensity, 19-bit unsigned */
+		.type               = IIO_INTENSITY,
+		.modified           = 1,

drivers/iio/health/max86150.c line 131

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		.channel2           = IIO_MOD_LIGHT_RED,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.scan_index         = MAX86150_IDX_PPG_RED,
+		.scan_type = {
+			.sign        = 'u',
+			.realbits    = 19,
+			.storagebits = 32,
+			.endianness  = IIO_LE,
+		},

[Severity: Medium]
Since max86150_trigger_handler() places native integers directly into the buffer without cpu_to_le32() conversions, will this advertise incorrect endianness to userspace on big endian architectures?

[ ... ]

Quoted 12 lines
drivers/iio/health/max86150.c new file +12 -0
+static int max86150_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct max86150_data *data = iio_priv(indio_dev);
+	u32 ppg_red, ppg_ir;
+	s32 ecg;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		/*

drivers/iio/health/max86150.c line 237

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		 * Single-shot path: clear the FIFO so we know we are reading
+		 * a sample that arrived after this call, not stale data.
+		 */
+		ret = regmap_write(data->regmap, MAX86150_REG_FIFO_WR_PTR, 0);
+		if (ret)
+			return ret;
+		ret = regmap_write(data->regmap, MAX86150_REG_OVF_COUNTER, 0);
+		if (ret)
+			return ret;
+		ret = regmap_write(data->regmap, MAX86150_REG_FIFO_RD_PTR, 0);

[Severity: High]
Does this path need iio_device_claim_direct_mode() to prevent concurrent sysfs reads from silently corrupting the hardware FIFO pointers while a continuous triggered buffer capture is active?

[ ... ]

Quoted 20 lines
drivers/iio/health/max86150.c new file +20 -0
+static irqreturn_t max86150_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func	*pf   = p;
+	struct iio_dev		*idev = pf->indio_dev;
+	struct max86150_data	*data = iio_priv(idev);
+	unsigned int status, wr_ptr, rd_ptr, available;
+	u32 ppg_red, ppg_ir;
+	s32 ecg;
+	int ret;
+
+	/*
+	 * Reading INT_STATUS1 clears the interrupt.  Do this before touching
+	 * the FIFO so the pin is de-asserted while we drain samples.
+	 */
+	ret = regmap_read(data->regmap, MAX86150_REG_INT_STATUS1, &status);
+	if (ret)
+		goto done;
+
+	ret = regmap_read(data->regmap, MAX86150_REG_FIFO_WR_PTR, &wr_ptr);
+	if (ret)

drivers/iio/health/max86150.c line 318

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		goto done;
+	ret = regmap_read(data->regmap, MAX86150_REG_FIFO_RD_PTR, &rd_ptr);
+	if (ret)
+		goto done;
+
+	/*
+	 * Number of unread samples.  The FIFO is circular with depth 32, so
+	 * wrap with (depth - 1) as mask.
+	 */
+	available = (wr_ptr - rd_ptr) & (MAX86150_FIFO_DEPTH - 1);

[Severity: High]
When the 32-entry FIFO becomes perfectly full and wr_ptr wraps around to equal rd_ptr, will this evaluate to 0?

If so, the handler might skip the read loop entirely and leave the FIFO full, causing the interrupt condition to persist and resulting in an endless interrupt loop or a permanent capture stall.

drivers/iio/health/max86150.c line 327

Quoted 9 lines
drivers/iio/health/max86150.c new file +9 -0
+
+	while (available--) {
+		ret = max86150_read_one_sample(data, &ppg_red, &ppg_ir, &ecg);
+		if (ret)
+			break;
+
+		data->buf[MAX86150_IDX_PPG_RED] = ppg_red;
+		data->buf[MAX86150_IDX_PPG_IR]  = ppg_ir;
+		data->buf[MAX86150_IDX_ECG]     = ecg;

[Severity: High]
If userspace enables only a subset of the channels (e.g., only ECG), will this static indexing ignore the active_scan_mask?

IIO expects enabled channels to be tightly packed at the start of the buffer. If only ECG is enabled, the IIO core calculates the timestamp offset based on a single channel. The static writes here might cause the timestamp to later overwrite the channel data, corrupting both the measurement and the timestamp.

[ ... ]

Quoted 7 lines
drivers/iio/health/max86150.c new file +7 -0
+static int max86150_chip_init(struct max86150_data *data)
+{
+	int ret;
+
+	/* Software reset; the bit self-clears within 1 ms */
+	ret = regmap_write(data->regmap, MAX86150_REG_SYS_CTRL,
+			   MAX86150_SYS_RESET);

[ ... ]

drivers/iio/health/max86150.c line 399

Quoted 4 lines
drivers/iio/health/max86150.c new file +4 -0
+	/* Enable FIFO almost-full interrupt only */
+	return regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1,
+			    MAX86150_INT_A_FULL);
+}

[Severity: Medium]
Are there teardown steps needed when the driver is unloaded?

This initialization starts continuous sampling, but no .remove callback or devm action seems to be registered to write to MAX86150_SYS_SHDN or disable the interrupts, leaving the device fully active on unload.

[Severity: High]
Could enabling the hardware interrupt here before the IIO buffer is active cause the edge-triggered interrupt pin to get permanently stuck?

If the sensor asserts the active-low pin early, the hardirq handler will fire but won't read INT_STATUS1 to clear the line (since the threaded handler is only active when the buffer is enabled). The interrupt controller might ignore the stuck low line forever, breaking capture once the buffer is actually started.

[ ... ]

Quoted 8 lines
drivers/iio/health/max86150.c new file +8 -0
+static int max86150_probe(struct i2c_client *client)
+{
+	struct iio_dev		*indio_dev;
+	struct max86150_data	*data;
+	unsigned int		 part_id;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));

drivers/iio/health/max86150.c line 420

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data     = iio_priv(indio_dev);
+	data->dev = &client->dev;
+
+	data->regmap = devm_regmap_init_i2c(client, &max86150_regmap_config);
+	if (IS_ERR(data->regmap))
+		return dev_err_probe(&client->dev, PTR_ERR(data->regmap),
+				     "Failed to initialise regmap\n");

[Severity: Medium]
Will this fail to probe on unpowered platforms since the documented devicetree regulators vdd-supply and leds-supply are not requested or enabled before I2C communication starts?

[ ... ]

drivers/iio/health/max86150.c line 459

Quoted 12 lines
drivers/iio/health/max86150.c new file +12 -0
+	if (client->irq > 0) {
+		data->trig = devm_iio_trigger_alloc(&client->dev,
+						    "%s-dev%d",
+						    indio_dev->name,
+						    iio_device_id(indio_dev));
+		if (!data->trig)
+			return -ENOMEM;
+
+		ret = devm_request_irq(&client->dev, client->irq,
+				       iio_trigger_generic_data_rdy_poll,
+				       IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+				       "max86150", data->trig);

[Severity: High]
Will the irq core reject this request during probe?

The interrupt core requires a threaded handler when IRQF_ONESHOT is used, but iio_trigger_generic_data_rdy_poll is passed as the hardirq handler with no threaded handler provided.

Quoted 4 lines
drivers/iio/health/max86150.c new file +4 -0
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot request IRQ %d\n",
+					     client->irq);

drivers/iio/health/max86150.c line 473

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+
+		iio_trigger_set_drvdata(data->trig, indio_dev);
+
+		ret = devm_iio_trigger_register(&client->dev, data->trig);
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot register trigger\n");
+
+		indio_dev->trig = iio_trigger_get(data->trig);
+	}

[Severity: Medium]
Is there a corresponding iio_trigger_put() needed here? Without it, the trigger reference count could be leaked when the device is unbound or if a subsequent probe step fails.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#3 lore raw
In reply to Md Shofiqul Islam
Quoted 34 lines · On Tue, 23 Jun 2026 17:01:13 +0300 Md Shofiqul Islam <[email protected]>…

The MAX86150 (Maxim/Analog Devices) integrates two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device. Data is captured via a 32-entry hardware FIFO with a configurable almost-full interrupt.

The driver exposes three IIO channels:

  - in_intensity_red_raw   (PPG Red, 19-bit unsigned)
  - in_intensity_ir_raw    (PPG IR, 19-bit unsigned)
  - in_voltage0_raw        (ECG, 18-bit signed)

A hardware trigger is registered when an interrupt GPIO is provided in the device tree, enabling the IIO triggered buffer path for continuous low-overhead capture.

FIFO reads use regmap_noinc_read() to burst-read 9 bytes (3 slots x 3 bytes) from the streaming FIFO_DATA register in a single I2C transaction.

---
 .../bindings/iio/health/maxim,max86150.yaml   |  67 +++
 drivers/iio/health/Kconfig                    |  23 +
 drivers/iio/health/Makefile                   |   1 +
 drivers/iio/health/max86150.c                 | 512 ++++++++++++++++++
 4 files changed, 603 insertions(+)
 create mode 100755 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
 create mode 100755 drivers/iio/health/max86150.c
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +0 -0
new file mode 100755
index 000000000000..1bf10fd1a3d2

Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml line 6

Quoted 6 lines
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +6 -0
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/health/maxim,max86150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+

Split this into 2 patches - one for the new dt-binding and one for the actual driver, KConfig and Makefile.

Additionally, since you're adding a brand new driver, a MAINTAINERS entry is required.

Kind regards

CJD

#4 lore raw
In reply to Md Shofiqul Islam
Quoted 14 lines · Md Shofiqul Islam

The MAX86150 (Maxim/Analog Devices) integrates two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device. Data is captured via a 32-entry hardware FIFO with a configurable almost-full interrupt.

The driver exposes three IIO channels:

  - in_intensity_red_raw   (PPG Red, 19-bit unsigned)
  - in_intensity_ir_raw    (PPG IR, 19-bit unsigned)
  - in_voltage0_raw        (ECG, 18-bit signed)

A hardware trigger is registered when an interrupt GPIO is provided in the device tree, enabling the IIO triggered buffer path for continuous low-overhead capture.

Quoted 5 lines

FIFO reads use regmap_noinc_read() to burst-read 9 bytes (3 slots x 3 bytes) from the streaming FIFO_DATA register in a single I2C transaction.

Please run scripts/checkpatch.pl on the patches and fix reported warnings. After that, run also 'scripts/checkpatch.pl --strict' on the patches and (probably) fix more warnings. Some warnings can be ignored, especially from --strict run, but the code here looks like it needs a fix. Feel free to get in touch if the warning is not clear.

Best regards,
Krzysztof

#6 lore raw

v2 cover iio: health: add MAX86150 ECG and PPG biosensor driver

In reply to Md Shofiqul Islam

The MAX86150 (Maxim/Analog Devices) combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device. Data is produced at up to 3200 Hz and buffered in a 32-entry hardware FIFO with a configurable almost-full interrupt.

This series adds:
1. DT binding YAML schema for maxim,max86150
2. IIO driver with triggered buffer support
3. MAINTAINERS entry

Changes in v2 (addressing Sashiko AI review, Joshua Crofts, Krzysztof Kozlowski):
- Fix buffer overflow: expand scan buffer to s32 buf[6] to hold
3 x s32 channels + 4-byte pad + s64 timestamp (24 bytes total)
- Add iio_device_claim_direct() in read_raw to prevent concurrent sysfs reads from corrupting FIFO pointers during buffer capture
- Fix FIFO-full detection: read OVF_COUNTER to distinguish a fully

    saturated FIFO (wr_ptr == rd_ptr after wrap) from an empty one

- Fix active_scan_mask handling: pack only active channels at consecutive positions using test_bit(idx, active_scan_mask)
- Remove IRQF_ONESHOT from IRQ request (not needed for edge-triggered; iio_trigger_generic_data_rdy_poll is a hard-IRQ handler)
- Add set_trigger_state callback to enable/disable A_FULL interrupt only when the IIO buffer is active, preventing the interrupt line from asserting before the handler is registered
- Fix endianness: change scan_type.endianness from IIO_LE to IIO_CPU to match native integers written directly into the buffer
- Add max86150_powerdown() devm action to disable interrupts and assert SYS_SHDN on driver unload
- Add devm_regulator_get_enable() for vdd-supply and leds-supply before any I2C access
- Add devm action for iio_trigger_put() to balance the reference count increment from iio_trigger_get()
- Split DT binding into a separate first patch (requested by Joshua Crofts)
- Add MAINTAINERS entry (requested by Joshua Crofts)

Known limitations (to be addressed in v3):
- IIO_CHAN_INFO_SCALE not yet implemented
- Runtime PM (SYS_CTRL SHDN bit) not yet implemented
- Not yet tested on physical hardware

Md Shofiqul Islam (3):
dt-bindings: iio: health: add maxim,max86150
iio: health: add MAX86150 ECG and PPG biosensor driver
MAINTAINERS: add entry for MAX86150 IIO health driver

 .../bindings/iio/health/maxim,max86150.yaml   |  67 ++
 MAINTAINERS                                   |   7 +
 drivers/iio/health/Kconfig                    |  18 +
 drivers/iio/health/Makefile                   |   1 +
 drivers/iio/health/max86150.c                 | 612 ++++++++++++++++++
 5 files changed, 705 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
 create mode 100644 drivers/iio/health/max86150.c
Signature · 1 line
2.51.1
#7 lore raw

PATCH v2 1/3 dt-bindings: iio: health: add maxim,max86150

Add YAML schema binding for the Maxim MAX86150 combined ECG and PPG biosensor. The device communicates over I2C at up to 400 kHz and exposes an active-low interrupt when the 32-entry hardware FIFO reaches its configurable almost-full threshold.

---
 .../bindings/iio/health/maxim,max86150.yaml   | 67 +++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100755 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +67 -0
new file mode 100755
index 000000000000..1bf10fd1a3d2
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/health/maxim,max86150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX86150 ECG and PPG Biosensor
+
+maintainers:
+  - Md Shofiqul Islam <[email protected]>
+
+description: |
+  The MAX86150 is an integrated biosensor SoC that combines:
+    - Two PPG (photoplethysmography) channels: Red LED and IR LED,
+      for heart rate and blood-oxygen saturation (SpO2) measurement.
+    - One ECG (electrocardiogram) channel for biopotential recording.
+
+  The device communicates over I2C at up to 400 kHz and raises an
+  active-low interrupt when the 32-entry hardware FIFO reaches its
+  configurable almost-full threshold.
+
+  Datasheet:
+    https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf
+
+properties:
+  compatible:
+    const: maxim,max86150
+
+  reg:
+    maxItems: 1
+    description: I2C device address, always 0x5E.
+
+  interrupts:
+    maxItems: 1
+    description: |
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready.
+
+  vdd-supply:
+    description: Digital core supply, 1.71 V to 1.89 V.
+
+  leds-supply:
+    description: LED anode supply, 3.0 V to 5.5 V.
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        biosensor@5e {
+            compatible = "maxim,max86150";
+            reg = <0x5e>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            vdd-supply = <&vdd_1v8>;
+            leds-supply = <&vdd_3v3>;
+        };
+    };
Signature · 1 line
2.51.1
#8 lore raw

Re: [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The YAML binding schema file is added with executable permissions (100755) instead of standard text…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The YAML binding schema file is added with executable permissions (100755) instead of standard text file permissions (100644). --

commit c219f77d4696cb841b5ce1448aac625e0d36d4ad
Author: Md Shofiqul Islam <[email protected]>

dt-bindings: iio: health: add maxim,max86150

This commit adds a new YAML device tree binding schema for the Maxim MAX86150 ECG and PPG biosensor.

Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml

Quoted 2 lines
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +0 -0
new file mode 100755

[Severity: Low]
This isn't a bug, but should this YAML binding schema file be added with standard text file permissions (100644) instead of being marked as executable?

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#9 lore raw

PATCH v2 2/3 iio: health: add MAX86150 ECG and PPG biosensor driver

In reply to Md Shofiqul Islam

The MAX86150 (Maxim/Analog Devices) integrates two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device. Data is captured via a 32-entry hardware FIFO with a configurable almost-full interrupt.

The driver exposes three IIO channels:

  - in_intensity_red_raw   (PPG Red, 19-bit unsigned)
  - in_intensity_ir_raw    (PPG IR, 19-bit unsigned)
  - in_voltage0_raw        (ECG, 18-bit signed)

A hardware trigger is registered when an interrupt GPIO is provided in the device tree, enabling the IIO triggered buffer path for continuous low-overhead capture. The trigger enable/disable is driven by the set_trigger_state callback to prevent the interrupt line from asserting before the handler is ready.

FIFO reads use regmap_noinc_read() to burst-read 9 bytes (3 slots x 3 bytes) from the streaming FIFO_DATA register in a single I2C transaction.

---
 drivers/iio/health/Kconfig    |  23 ++
 drivers/iio/health/Makefile   |   1 +
 drivers/iio/health/max86150.c | 612 ++++++++++++++++++++++++++++++++++
 3 files changed, 636 insertions(+)
 create mode 100755 drivers/iio/health/max86150.c
drivers/iio/health/Kconfig +23 -0
index a89f3abf11f4..6496cf55290c 100644
@@ -13,6 +13,7 @@ config AFE4403
 	depends on SPI_MASTER
 	select REGMAP_SPI
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4403
@@ -26,6 +27,7 @@ config AFE4404
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4404
@@ -39,6 +41,7 @@ config MAX30100
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -52,6 +55,7 @@ config MAX30102
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -62,4 +66,23 @@ config MAX30102
 
 endmenu
 
+
+config MAX86150
+	tristate "MAX86150 ECG and PPG biosensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGER
+	select IIO_TRIGGERED_BUFFER
+	select REGMAP_I2C
+	help
+	  Say Y here to enable support for the Maxim MAX86150 combined
+	  ECG and photoplethysmography (PPG) biosensor.
+
+	  The driver exposes three IIO channels: two PPG optical channels
+	  (Red and IR LED) for heart rate and SpO2 monitoring, and one
+	  ECG channel for biopotential recording.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called max86150.
+
 endmenu
drivers/iio/health/Makefile +1 -0
index 910817112258..04fc73c58444 100644
@@ -9,3 +9,4 @@ obj-$(CONFIG_AFE4403)		+= afe4403.o
 obj-$(CONFIG_AFE4404)		+= afe4404.o
 obj-$(CONFIG_MAX30100)		+= max30100.o
 obj-$(CONFIG_MAX30102)		+= max30102.o
+obj-$(CONFIG_MAX86150)		+= max86150.o
drivers/iio/health/max86150.c new file +612 -0

612 lines not on this page · raw message

Signature · 1 line
2.51.1
#10 lore raw
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider: - [High] A stack-allocated buffer is passed to regmap_noinc_read(), which violates DMA mapping rules and…

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] A stack-allocated buffer is passed to regmap_noinc_read(), which violates DMA mapping rules and can cause memory corruption if the I2C host controller uses DMA.
- [High] Redundant manual invocation of iio_trigger_put via a devm action causes a double put and a use-after-free when the IIO device is released.
- [Medium] The driver exposes unimplemented scale attributes to userspace, which unconditionally return an error.
- [Medium] The FIFO drain loop assigns incorrect, heavily clustered timestamps to buffered samples, destroying time-domain information.
- [Medium] The driver incorrectly uses devm_regulator_get_enable for optional supplies, erroneously assuming it gracefully handles missing regulators.
- [Medium] Uninitialized padding bytes in data->buf leak previous sensor readings to userspace when fewer than 3 channels are enabled.
- [Medium] Missing FIFO flush when enabling the triggered buffer leaves stale data to be processed immediately.
- [Medium] The driver calls iio_trigger_get() on its own trigger during probe, permanently elevating its own module reference count and preventing module unload. --

commit 28ae0a6a433f8b0e50a4557f62c8f075a8c69870
Author: Md Shofiqul Islam <[email protected]>

iio: health: add MAX86150 ECG and PPG biosensor driver

This commit adds a driver for the MAX86150 combined ECG and PPG biosensor. It exposes three IIO channels and supports the IIO triggered buffer path for continuous data capture.

Quoted 5 lines
drivers/iio/health/max86150.c new file +0 -0
new file mode 100755
index 0000000000000..70c51ccb4672b

[ ... ]

drivers/iio/health/max86150.c line 126

Quoted 8 lines
drivers/iio/health/max86150.c new file +8 -0
+static const struct iio_chan_spec max86150_channels[] = {
+	{
+		/* PPG Red LED - optical intensity, 19-bit unsigned */
+		.type               = IIO_INTENSITY,
+		.modified           = 1,
+		.channel2           = IIO_MOD_LIGHT_RED,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),

[Severity: Medium]
Will this cause sysfs reads for the scale attribute to always fail?

The driver exposes IIO_CHAN_INFO_SCALE for all channels here, but max86150_read_raw() unconditionally returns -EINVAL for it. This breaks the IIO ABI contract and might cause userspace tools to fail when discovering channel properties.

[ ... ]

drivers/iio/health/max86150.c line 199

Quoted 8 lines
drivers/iio/health/max86150.c new file +8 -0
+static int max86150_read_one_sample(struct max86150_data *data,
+				    u32 *ppg_red, u32 *ppg_ir, s32 *ecg)
+{
+	u8 buf[MAX86150_SAMPLE_BYTES];  /* 9 bytes */
+	int ret;
+
+	ret = regmap_noinc_read(data->regmap, MAX86150_REG_FIFO_DATA,
+				buf, sizeof(buf));

[Severity: High]
Can this cause memory corruption if the I2C host controller uses DMA?

The buf array is allocated on the stack and passed directly to regmap_noinc_read(). Since the regmap I2C implementation does not internally bounce buffers, this passes a stack buffer directly to i2c_transfer(). When CONFIG_VMAP_STACK is enabled, stack buffers are not physically contiguous and are not DMA-safe.

[ ... ]

drivers/iio/health/max86150.c line 282

Quoted 7 lines
drivers/iio/health/max86150.c new file +7 -0
+	case IIO_CHAN_INFO_SCALE:
+		/*
+		 * TODO: return the hardware scale factor based on the
+		 * configured PPG ADC range (PPG_ADC_RGE) and ECG PGA gain
+		 * (ECG_PGA_GAIN).  For PPG, scale is nA/LSB; for ECG, uV/LSB.
+		 */
+		return -EINVAL;

[Severity: Medium]
Is it intentional to advertise the scale attribute in the channel spec but unconditionally reject it here?

This will cause reads of the sysfs attribute to always fail.

[ ... ]

drivers/iio/health/max86150.c line 307

Quoted 7 lines
drivers/iio/health/max86150.c new file +7 -0
+static int max86150_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct max86150_data *data = iio_priv(indio_dev);
+	unsigned int mask = state ? MAX86150_INT_A_FULL : 0;
+
+	return regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1, mask);

[Severity: Medium]
Does the hardware FIFO need to be flushed before unmasking the interrupt here?

Since the device continuously acquires data into the FIFO starting from probe(), the FIFO might already be full when userspace enables the triggered buffer. This would cause the interrupt to assert immediately and drain extremely stale samples into the new capture session.

[ ... ]

Quoted 4 lines
drivers/iio/health/max86150.c new file +4 -0
+		/*
+		 * Pack only active channels at consecutive positions [0..j-1].
+		 * iio_push_to_buffers_with_timestamp() uses scan_bytes (which
+		 * accounts for the active channel count) to place the timestamp,

drivers/iio/health/max86150.c line 383

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		 * so static indexing would misplace it when fewer than 3
+		 * channels are enabled.
+		 */
+		j = 0;
+		if (test_bit(MAX86150_IDX_PPG_RED, idev->active_scan_mask))
+			data->buf[j++] = ppg_red;
+		if (test_bit(MAX86150_IDX_PPG_IR, idev->active_scan_mask))
+			data->buf[j++] = ppg_ir;
+		if (test_bit(MAX86150_IDX_ECG, idev->active_scan_mask))
+			data->buf[j++] = ecg;

[Severity: Medium]
Does this sequentially pack active channels without zeroing the entire buffer first, leaking previous sensor readings?

If the active channel count is reduced, unused array elements in data->buf retain stale data from previous captures. iio_push_to_buffers_with_timestamp() then copies the entire memory block up to the timestamp offset, exposing the uninitialized padding bytes to userspace.

drivers/iio/health/max86150.c line 386

Quoted 3 lines
drivers/iio/health/max86150.c new file +3 -0
+
+		iio_push_to_buffers_with_timestamp(idev, data->buf,
+						   iio_get_time_ns(idev));

[Severity: Medium]
Will this assign the same batch execution time to all historical samples pulled from the FIFO?

Inside the loop, fetching the current time for every sample means up to 32 samples will receive nearly identical timestamps separated only by I2C read latencies. This misrepresents the true sample capture period (e.g., 100 Hz). Should the driver backdate timestamps using the top-half IRQ timestamp (pf->timestamp) instead?

[ ... ]

drivers/iio/health/max86150.c line 492

Quoted 7 lines
drivers/iio/health/max86150.c new file +7 -0
+	/*
+	 * Enable power supplies before any I2C access.  Both are optional
+	 * in the device tree (devm_regulator_get_enable returns 0 when the
+	 * supply is not described), but must be present and stable on real
+	 * hardware before the first register read.
+	 */
+	ret = devm_regulator_get_enable(&client->dev, "vdd");

[Severity: Medium]
Will this cause the probe to fail on boards where the regulator is legitimately missing?

The comment indicates the supply is optional and assumes this API returns 0 when it is not described. However, devm_regulator_get_enable() only returns 0 for missing regulators if the system provides a dummy regulator. Otherwise, it returns -ENODEV, fatally aborting the probe. Should this use devm_regulator_get_enable_optional() instead?

[ ... ]

drivers/iio/health/max86150.c line 567

Quoted 6 lines
drivers/iio/health/max86150.c new file +6 -0
+		ret = devm_iio_trigger_register(&client->dev, data->trig);
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot register trigger\n");
+
+		indio_dev->trig = iio_trigger_get(data->trig);

[Severity: Medium]
Does this take a circular reference on the driver's own module?

Calling iio_trigger_get() on a trigger owned by this module increments the module reference count during probe(). The rmmod command will then refuse to unload the module because the reference count will never drop to 0, permanently pinning it in memory.

drivers/iio/health/max86150.c line 570

Quoted 3 lines
drivers/iio/health/max86150.c new file +3 -0
+		ret = devm_add_action_or_reset(&client->dev,
+					       max86150_trigger_put,
+					       data->trig);

[Severity: High]
Can this cause a double-put and a use-after-free when the IIO device is released?

The IIO core automatically handles releasing the trigger during device unregistration via iio_device_unregister_trigger_consumer() which implicitly calls iio_trigger_put(). Because devm actions run subsequently, max86150_trigger_put() drops the reference a second time, resulting in a use-after-free of the trigger object.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#11 lore raw

PATCH v2 3/3 MAINTAINERS: add entry for MAX86150 IIO health driver

In reply to Md Shofiqul Islam

Add maintainer entry for the new MAX86150 ECG and PPG biosensor driver.

---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)
MAINTAINERS +7 -0
index 3115538ce829..a441ec44bb27 100644
@@ -15863,6 +15863,13 @@ S:	Supported
 F:	Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
 F:	drivers/power/supply/max77976_charger.c
 
+MAXIM MAX86150 ECG AND PPG BIOSENSOR DRIVER
+M:	Md Shofiqul Islam <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
+F:	drivers/iio/health/max86150.c
+
 MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
 M:	Krzysztof Kozlowski <[email protected]>
 L:	[email protected]
Signature · 1 line
2.51.1
#12 lore raw

v4 cover iio: health: add MAX86150 ECG and PPG biosensor driver

In reply to Md Shofiqul Islam

Changes in v4 (addressing Sashiko review of v3):

- [High] Fix optional regulator probe failure: treat -ENODEV from devm_regulator_get_enable_optional() as success (supply absent, not an error).

- [High] Align fifo_raw to ARCH_DMA_MINALIGN to satisfy DMA mapping requirements of I2C host controllers that use DMA for burst transfers.

- [High] Disambiguate FIFO empty vs exactly-full: when wr_ptr == rd_ptr with OVF_COUNTER == 0, consult the A_FULL interrupt status bit to determine whether the FIFO pointer wrapped to full or is truly empty.

- [High] Remove iio_trigger_get() in probe: the incremented refcount leaks on the error path when devm_iio_device_register() fails because iio_device_unregister() (and its paired iio_trigger_put()) never runs. Users set the trigger via the current_trigger sysfs attribute as normal.

- [High] Assert SYS_SHDN in chip_init() so the LED drivers draw no current while capture is inactive. set_trigger_state() clears SHDN when the buffer is enabled and re-asserts it when disabled. read_raw() wakes and sleeps the device around each single-shot read.

- [Medium] Replace IRQF_TRIGGER_FALLING with irq_get_trigger_type() to honour the interrupt trigger type from the device tree; falls back to falling-edge if the DT does not specify one.

- [Medium] Add .validate_trigger = iio_trigger_validate_own_device to prevent incompatible external triggers from being attached.

- [Medium] Fix per-sample timestamp jitter: anchor timestamps to the A_FULL IRQ capture time. The sample at index (A_FULL_SAMPLES - 1) corresponds to pf->timestamp; samples accumulated between the IRQ and handler execution receive future timestamps, eliminating scheduling- latency-dependent jitter in multi-sample drains.

v3 cover letter

Md Shofiqul Islam (3):
dt-bindings: iio: health: add maxim,max86150
iio: health: add MAX86150 ECG and PPG biosensor driver
MAINTAINERS: add entry for MAX86150 IIO health driver

Signature · 1 line
2.49.0
#13 lore raw

PATCH v4 1/3 dt-bindings: iio: health: add maxim,max86150

Add YAML binding schema for the Maxim MAX86150 combined ECG and PPG biosensor. The device exposes two PPG optical channels (Red and IR LED) for heart rate and SpO2 measurement, and one ECG biopotential channel, all accessible over I2C at up to 400 kHz.

An optional active-low interrupt line connects to the 32-entry hardware FIFO almost-full output. Two optional regulator supplies (vdd for the digital core and leds for the LED anodes) cover boards that require explicit power sequencing.

---
 .../bindings/iio/health/maxim,max86150.yaml   | 67 +++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +67 -0
new file mode 100644
index 000000000000..1bf10fd1a3d2
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/health/maxim,max86150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX86150 ECG and PPG Biosensor
+
+maintainers:
+  - Md Shofiqul Islam <[email protected]>
+
+description: |
+  The MAX86150 is an integrated biosensor SoC that combines:
+    - Two PPG (photoplethysmography) channels: Red LED and IR LED,
+      for heart rate and blood-oxygen saturation (SpO2) measurement.
+    - One ECG (electrocardiogram) channel for biopotential recording.
+
+  The device communicates over I2C at up to 400 kHz and raises an
+  active-low interrupt when the 32-entry hardware FIFO reaches its
+  configurable almost-full threshold.
+
+  Datasheet:
+    https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf
+
+properties:
+  compatible:
+    const: maxim,max86150
+
+  reg:
+    maxItems: 1
+    description: I2C device address, always 0x5E.
+
+  interrupts:
+    maxItems: 1
+    description: |
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready.
+
+  vdd-supply:
+    description: Digital core supply, 1.71 V to 1.89 V.
+
+  leds-supply:
+    description: LED anode supply, 3.0 V to 5.5 V.
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        biosensor@5e {
+            compatible = "maxim,max86150";
+            reg = <0x5e>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            vdd-supply = <&vdd_1v8>;
+            leds-supply = <&vdd_3v3>;
+        };
+    };
Signature · 1 line
2.51.1
#14 lore raw

PATCH v4 2/3 iio: health: add MAX86150 ECG and PPG biosensor driver

In reply to Md Shofiqul Islam

The MAX86150 (Maxim/Analog Devices) combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device with a 32-entry hardware FIFO.

Driver features:
- Three IIO channels: in_intensityred_raw, in_intensityir_raw, in_voltage0_raw
- Triggered buffer path driven by the FIFO almost-full interrupt; set_trigger_state enables/disables the interrupt only while the buffer is active and FIFO is flushed before capture starts
- validate_trigger = iio_trigger_validate_own_device prevents incompatible external triggers from being attached
- IRQ trigger type taken from irq_get_trigger_type() to honour DT configuration; falls back to falling-edge if unspecified
- fifo_raw burst-read buffer is heap-allocated in struct max86150_data and aligned to ARCH_DMA_MINALIGN to satisfy DMA mapping requirements of I2C host controllers that use DMA for burst reads
- SYS_SHDN asserted at end of chip_init so LED drivers draw no current when capture is inactive; set_trigger_state() and read_raw() wake and sleep the device on demand
- Per-sample timestamps anchored to the A_FULL IRQ capture time: the sample at index (A_FULL_SAMPLES - 1) maps to pf->timestamp, samples accumulated after the IRQ receive future timestamps, eliminating load-dependent jitter
- FIFO empty vs exactly-full disambiguation: when wr_ptr == rd_ptr with OVF_COUNTER == 0, the A_FULL status bit distinguishes a pointer wrap-around (full) from a genuinely empty FIFO
- devm_regulator_get_enable_optional() for the two optional supplies; -ENODEV is treated as success (supply absent, not an error)
- Powerdown devm action disables interrupts and asserts SYS_SHDN

---
 drivers/iio/health/Kconfig    |  23 ++
 drivers/iio/health/Makefile   |   1 +
 drivers/iio/health/max86150.c | 677 ++++++++++++++++++++++++++++++++++
 3 files changed, 701 insertions(+)
 create mode 100644 drivers/iio/health/max86150.c
drivers/iio/health/Kconfig +23 -0
index a89f3abf11f4..6496cf55290c 100644
@@ -13,6 +13,7 @@ config AFE4403
 	depends on SPI_MASTER
 	select REGMAP_SPI
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4403
@@ -26,6 +27,7 @@ config AFE4404
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4404
@@ -39,6 +41,7 @@ config MAX30100
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -52,6 +55,7 @@ config MAX30102
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -62,4 +66,23 @@ config MAX30102
 
 endmenu
 
+
+config MAX86150
+	tristate "MAX86150 ECG and PPG biosensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGER
+	select IIO_TRIGGERED_BUFFER
+	select REGMAP_I2C
+	help
+	  Say Y here to enable support for the Maxim MAX86150 combined
+	  ECG and photoplethysmography (PPG) biosensor.
+
+	  The driver exposes three IIO channels: two PPG optical channels
+	  (Red and IR LED) for heart rate and SpO2 monitoring, and one
+	  ECG channel for biopotential recording.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called max86150.
+
 endmenu
drivers/iio/health/Makefile +1 -0
index 910817112258..04fc73c58444 100644
@@ -9,3 +9,4 @@ obj-$(CONFIG_AFE4403)		+= afe4403.o
 obj-$(CONFIG_AFE4404)		+= afe4404.o
 obj-$(CONFIG_MAX30100)		+= max30100.o
 obj-$(CONFIG_MAX30102)		+= max30102.o
+obj-$(CONFIG_MAX86150)		+= max86150.o
drivers/iio/health/max86150.c new file +677 -0

677 lines not on this page · raw message

Signature · 1 line
2.51.1
#15 lore raw
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [High] The driver fails to assign `indio_dev->trig`, causing `iio_trigger_validate_own_device` to…

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] The driver fails to assign indio_dev->trig, causing iio_trigger_validate_own_device to unconditionally reject the trigger.
- [High] Using a hard IRQ handler for the IIO trigger without interrupt masking causes a system-locking interrupt storm for level-triggered interrupts.
- [Medium] Timestamp reconstruction assigns inaccurate timestamps into the past when the hardware FIFO overflows because it ignores the OVF_COUNTER dropped sample count.
- [Medium] The I2C DMA buffer fifo_raw shares a cacheline with the pushed CPU buffer buf, violating DMA API constraints.
- [Medium] A blind sleep in read_raw risks reading an empty FIFO if the internal oscillator or device wake-up is slower than nominal.
--

commit 44deffd1bb30e8d4384d605ba5d156fb29678ad5
Author: Md Shofiqul Islam <[email protected]>

iio: health: add MAX86150 ECG and PPG biosensor driver

The MAX86150 (Maxim/Analog Devices) combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device with a 32-entry hardware FIFO.

Quoted 5 lines
drivers/iio/health/max86150.c new file +0 -0
new file mode 100644
index 0000000000000..0140c93bd4ec9

[ ... ]

drivers/iio/health/max86150.c line 125

Quoted 8 lines
drivers/iio/health/max86150.c new file +8 -0
+struct max86150_data {
+	struct regmap		*regmap;
+	struct device		*dev;
+	struct iio_trigger	*trig;
+	u32			 sample_period_ns;
+	u8			 fifo_raw[MAX86150_SAMPLE_BYTES] __aligned(ARCH_DMA_MINALIGN);
+	s32 buf[6] __aligned(8);
+};

[Severity: Medium]
Since fifo_raw is 9 bytes long, does the __aligned macro only align the start of the array, leaving the buf array packed into the same DMA cacheline?

Mapping an unpadded buffer that shares a cacheline with CPU-accessed data might violate kernel DMA mapping constraints and trigger CONFIG_DMA_API_DEBUG warnings.

[ ... ]

Quoted 4 lines
drivers/iio/health/max86150.c new file +4 -0
+static int max86150_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{

[ ... ]

drivers/iio/health/max86150.c line 275

Quoted 4 lines
drivers/iio/health/max86150.c new file +4 -0
+		/* Wait for one complete sample period at 100 Hz (<= 10 ms) */
+		usleep_range(11000, 13000);
+
+		ret = max86150_read_one_sample(data, &ppg_red, &ppg_ir, &ecg);

[Severity: Medium]
Is it possible for the hardware read to execute while the FIFO is still empty if the internal oscillator or device wake-up is slower than nominal?

Would it be safer to use regmap_read_poll_timeout on the PPG_RDY status bit to ensure a sample is actually ready?

[ ... ]

Quoted 2 lines
drivers/iio/health/max86150.c new file +2 -0
+static irqreturn_t max86150_trigger_handler(int irq, void *p)
+{

[ ... ]

drivers/iio/health/max86150.c line 418

Quoted 11 lines
drivers/iio/health/max86150.c new file +11 -0
+	/*
+	 * Anchor timestamps to the A_FULL IRQ capture time: sample index
+	 * (MAX86150_A_FULL_SAMPLES - 1) corresponds to pf->timestamp.
+	 * Samples that accumulated between the IRQ and handler execution
+	 * receive timestamps in the future relative to the IRQ, eliminating
+	 * load-dependent jitter in multi-sample drains.
+	 */
+	for (i = 0; i < n_avail; i++) {
+		s64 ts = pf->timestamp +
+			 (s64)(i - (MAX86150_A_FULL_SAMPLES - 1)) *
+			 data->sample_period_ns;

[Severity: Medium]
When the FIFO overflows (ovf > 0), the hardware overwrites the oldest samples. Does this calculation assign inaccurate timestamps into the past because it ignores the OVF_COUNTER dropped sample count?

The remaining 32 samples are chronologically shifted forward by ovf relative to the original A_FULL IRQ event.

[ ... ]

Quoted 2 lines
drivers/iio/health/max86150.c new file +2 -0
+static int max86150_probe(struct i2c_client *client)
+{

[ ... ]

Quoted 7 lines
drivers/iio/health/max86150.c new file +7 -0
+	/*
+	 * If the device tree provides an interrupt, set up a hardware
+	 * trigger so userspace can use the FIFO almost-full signal to
+	 * drive capture without polling.
+	 */
+	if (client->irq > 0) {
+		unsigned long irq_trig;

drivers/iio/health/max86150.c line 615

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+
+		data->trig = devm_iio_trigger_alloc(&client->dev,
+						    "%s-dev%d",
+						    indio_dev->name,
+						    iio_device_id(indio_dev));
+		if (!data->trig)
+			return -ENOMEM;
+
+		data->trig->ops = &max86150_trigger_ops;
+		iio_trigger_set_drvdata(data->trig, indio_dev);

[Severity: High]
Does the driver fail to assign indio_dev->trig = data->trig after allocating the trigger?

Since validate_trigger is set to iio_trigger_validate_own_device, it looks like missing this assignment will cause any attempt to write to the current_trigger sysfs node to unconditionally fail with -EINVAL.

drivers/iio/health/max86150.c line 628

Quoted 13 lines
drivers/iio/health/max86150.c new file +13 -0
+
+		/*
+		 * Honour the interrupt trigger type from the device tree.
+		 * Fall back to falling-edge if the DT does not specify one.
+		 */
+		irq_trig = irq_get_trigger_type(client->irq);
+		if (!irq_trig)
+			irq_trig = IRQF_TRIGGER_FALLING;
+
+		ret = devm_request_irq(&client->dev, client->irq,
+				       iio_trigger_generic_data_rdy_poll,
+				       irq_trig,
+				       "max86150", data->trig);

[Severity: High]
Will using a hard IRQ handler for the IIO trigger without interrupt masking cause an interrupt storm for level-triggered interrupts?

For level-triggered interrupts, the hard handler wakes the consumer thread and returns IRQ_HANDLED without clearing the physical interrupt via I2C. The IRQ core then unmasks the still-asserted interrupt line, causing it to immediately fire again and preventing the threaded consumer from ever running to clear the source.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#16 lore raw
In reply to Md Shofiqul Islam
Quoted 30 lines · Md Shofiqul Islam

The MAX86150 (Maxim/Analog Devices) combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device with a 32-entry hardware FIFO.

Driver features:
- Three IIO channels: in_intensityred_raw, in_intensityir_raw, in_voltage0_raw
- Triggered buffer path driven by the FIFO almost-full interrupt; set_trigger_state enables/disables the interrupt only while the buffer is active and FIFO is flushed before capture starts
- validate_trigger = iio_trigger_validate_own_device prevents incompatible external triggers from being attached
- IRQ trigger type taken from irq_get_trigger_type() to honour DT configuration; falls back to falling-edge if unspecified
- fifo_raw burst-read buffer is heap-allocated in struct max86150_data and aligned to ARCH_DMA_MINALIGN to satisfy DMA mapping requirements of I2C host controllers that use DMA for burst reads
- SYS_SHDN asserted at end of chip_init so LED drivers draw no current when capture is inactive; set_trigger_state() and read_raw() wake and sleep the device on demand
- Per-sample timestamps anchored to the A_FULL IRQ capture time: the sample at index (A_FULL_SAMPLES - 1) maps to pf->timestamp, samples accumulated after the IRQ receive future timestamps, eliminating load-dependent jitter
- FIFO empty vs exactly-full disambiguation: when wr_ptr == rd_ptr with OVF_COUNTER == 0, the A_FULL status bit distinguishes a pointer wrap-around (full) from a genuinely empty FIFO
- devm_regulator_get_enable_optional() for the two optional supplies; -ENODEV is treated as success (supply absent, not an error)
- Powerdown devm action disables interrupts and asserts SYS_SHDN

...

Quoted 3 lines

endmenu

+

Single blank line is enough.

Quoted 1 line

+config MAX86150

...

IWYU, please! (The list of missing headers below may not be comprehensive.)

+ array_size.h

Quoted 1 line

+#include <linux/bitfield.h>

+ bitops.h // GENMASK(), sign_extend32()

Quoted 1 line

+#include <linux/delay.h>

+ dev_printk.h // dev_err_probe()
+ device/devres.h // devm_add_action_or_reset()

+ err.h // IS_ERR(), -ENOMEM, ...

drivers/iio/health/max86150.c line 25

Quoted 8 lines
drivers/iio/health/max86150.c new file +7 -0
+#include <linux/i2c.h>
+#include <linux/irq.h>
 
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>

Can we move this group out...

drivers/iio/health/max86150.c line 28

Quoted 3 lines
drivers/iio/health/max86150.c new file +3 -0
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>

+ types.h // uXX, sXX

Quoted 1 line

+

...to be here?

...

drivers/iio/health/max86150.c line 101

Quoted 6 lines
drivers/iio/health/max86150.c new file +6 -0
+/* Scan element indices */
+enum max86150_scan_idx {
+	MAX86150_IDX_PPG_RED = 0,
+	MAX86150_IDX_PPG_IR  = 1,
+	MAX86150_IDX_ECG     = 2,
+	MAX86150_IDX_TS,

Why no value for TS? Or why values for the rest? I assume it's HW/SW cases?

Quoted 9 lines
drivers/iio/health/max86150.c new file +8 -0
+};
 
+/**
+ * struct max86150_data - driver private state
+ * @regmap:           register map for this device
+ * @dev:              parent device (for dev_err logging)
+ * @trig:             IIO hardware trigger backed by the device interrupt line
+ * @sample_period_ns: sample period in nanoseconds (set from configured rate)
+ * @fifo_raw:         DMA-safe buffer for regmap_noinc_read() FIFO bursts;

drivers/iio/health/max86150.c line 120

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+ *                    must be in struct (heap) not on the stack to satisfy
+ *                    DMA mapping requirements of some I2C host controllers
+ * @buf:              IIO push buffer sized for worst-case (all 3 channels
+ *                    active): 3 x s32 (12 bytes) + 4-byte pad + s64
+ *                    timestamp = 24 bytes.  __aligned(8) satisfies
+ *                    iio_push_to_buffers_with_timestamp().
+ */
+struct max86150_data {
+	struct regmap		*regmap;
+	struct device		*dev;

Is regmap device and dev is the same? If so, drop one and derive the other.

drivers/iio/health/max86150.c line 124

Quoted 5 lines
drivers/iio/health/max86150.c new file +4 -0
+	struct iio_trigger	*trig;
+	u32			 sample_period_ns;
 
+	u8			 fifo_raw[MAX86150_SAMPLE_BYTES] __aligned(ARCH_DMA_MINALIGN);
+	s32 buf[6] __aligned(8);

We have a macro for these. (For alignment.)

Quoted 1 line

+};

...

Quoted 7 lines
drivers/iio/health/max86150.c new file +7 -0
+static int max86150_read_one_sample(struct max86150_data *data,
+				    u32 *ppg_red, u32 *ppg_ir, s32 *ecg)
+{
+	int ret;
+
+	/*
+	 * Use data->fifo_raw (heap memory) not a local array so the buffer is

drivers/iio/health/max86150.c line 215

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	 * DMA-mappable for I2C host controllers that use DMA for burst reads.
+	 */
+	ret = regmap_noinc_read(data->regmap, MAX86150_REG_FIFO_DATA,
+				data->fifo_raw, sizeof(data->fifo_raw));
+	if (ret)
+		return ret;
+
+	/* Bytes [0..2]: PPG Red - 19-bit value in bits [18:0] */
+	*ppg_red = (u32)(data->fifo_raw[0] & 0x07) << 16 |
+		   (u32)data->fifo_raw[1] << 8 | data->fifo_raw[2];

Casting to u32 makes a little sense. Why?

drivers/iio/health/max86150.c line 223

Quoted 7 lines
drivers/iio/health/max86150.c new file +7 -0
+	/* Bytes [3..5]: PPG IR - same format */
+	*ppg_ir  = (u32)(data->fifo_raw[3] & 0x07) << 16 |
+		   (u32)data->fifo_raw[4] << 8 | data->fifo_raw[5];
+
+	/* Bytes [6..8]: ECG - 18-bit signed, sign-extend to s32 */
+	*ecg = sign_extend32((u32)(data->fifo_raw[6] & 0x03) << 16 |
+			     (u32)data->fifo_raw[7] << 8 | data->fifo_raw[8], 17);

Ditto for all these...

Quoted 2 lines
(quoted diff) +2 -0
+	return 0;
+}

...

Quoted 30 lines
drivers/iio/health/max86150.c new file +30 -0
+static int max86150_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct max86150_data *data = iio_priv(indio_dev);
+	u32 ppg_red, ppg_ir;
+	s32 ecg;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		/*
+		 * Claim direct mode to prevent concurrent sysfs reads from
+		 * corrupting the FIFO pointers while a triggered buffer
+		 * capture is active.
+		 */
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+
+		/*
+		 * Single-shot path: wake the device, flush stale FIFO data,
+		 * wait one sample period, read, then return to shutdown so
+		 * the LEDs are not drawing current when idle.
+		 */
+		ret = regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+					 MAX86150_SYS_SHDN, 0);
+		if (!ret)
+			ret = regmap_write(data->regmap,
+					   MAX86150_REG_FIFO_WR_PTR, 0);
+		if (!ret)

drivers/iio/health/max86150.c line 269

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+			ret = regmap_write(data->regmap,
+					   MAX86150_REG_OVF_COUNTER, 0);
+		if (!ret)
+			ret = regmap_write(data->regmap,
+					   MAX86150_REG_FIFO_RD_PTR, 0);
+		if (ret) {
+			regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+					   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+			iio_device_release_direct(indio_dev);
+			return ret;

No, use regular pattern, id est

		if (ret)
			return ret;

Ditto for other cases like this.

Quoted 31 lines
drivers/iio/health/max86150.c new file +31 -0
+		}
+
+		/* Wait for one complete sample period at 100 Hz (<= 10 ms) */
+		usleep_range(11000, 13000);
+
+		ret = max86150_read_one_sample(data, &ppg_red, &ppg_ir, &ecg);
+		regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+		iio_device_release_direct(indio_dev);
+		if (ret)
+			return ret;
+
+		switch (chan->scan_index) {
+		case MAX86150_IDX_PPG_RED:
+			*val = ppg_red;
+			break;
+		case MAX86150_IDX_PPG_IR:
+			*val = ppg_ir;
+			break;
+		case MAX86150_IDX_ECG:
+			*val = ecg;
+			break;
+		default:
+			return -EINVAL;
+		}
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}

...

Quoted 11 lines
drivers/iio/health/max86150.c new file +11 -0
+/*
+ * Control device power and the FIFO almost-full interrupt when the IIO
+ * triggered buffer is started (state=true) or stopped (state=false).
+ *
+ * On start: wake from shutdown, flush stale FIFO data so the first
+ * samples pushed to userspace are from after buffer enable, then
+ * unmask the A_FULL interrupt.
+ *
+ * On stop: mask the interrupt, then return to shutdown so the LED
+ * drivers do not draw current while capture is inactive.
+ */

drivers/iio/health/max86150.c line 329

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+static int max86150_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct max86150_data *data = iio_priv(indio_dev);
+	int ret;
+
+	if (!state) {
+		ret = regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1, 0);
+		regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);

_set_bits(), but why no check?

Quoted 8 lines
drivers/iio/health/max86150.c new file +8 -0
+		return ret;
+	}
+
+	ret = regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				 MAX86150_SYS_SHDN, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_FIFO_WR_PTR, 0);
+	if (!ret)

drivers/iio/health/max86150.c line 347

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		ret = regmap_write(data->regmap, MAX86150_REG_OVF_COUNTER, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_FIFO_RD_PTR, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1,
+				   MAX86150_INT_A_FULL);
+	if (ret)
+		regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+	return ret;

Ditto.

...

Also why not to split to trigger_enable and trigger_disable?

_trigger_enable(max86150_data *data)

_trigger_disable(...)

_set_trigger_state()

	if (enable)
		return _trigger_enable()

	return trigger_disable();
Quoted 1 line

+}

...

Quoted 7 lines
drivers/iio/health/max86150.c new file +7 -0
+/**
+ * max86150_trigger_handler - threaded IRQ handler for FIFO almost-full
+ *
+ * Called by the IIO buffer infrastructure when the hardware trigger fires.
+ * Reads INT_STATUS1 to de-assert the interrupt, then drains all available
+ * FIFO samples into the IIO push buffer, packing only the channels that
+ * are currently enabled in active_scan_mask.

drivers/iio/health/max86150.c line 372

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+ */
+static irqreturn_t max86150_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func	*pf   = p;
+	struct iio_dev		*idev = pf->indio_dev;
+	struct max86150_data	*data = iio_priv(idev);
+	unsigned int status, wr_ptr, rd_ptr, ovf;
+	u32 ppg_red, ppg_ir;
+	s32 ecg;
+	int ret, n_avail, i, j;

Usually it's not a good idea to mix ret with other variables that not semantically related. Also, why those are signed?

Quoted 32 lines
drivers/iio/health/max86150.c new file +32 -0
+	/*
+	 * Reading INT_STATUS1 clears the interrupt.  Do this before touching
+	 * the FIFO so the pin is de-asserted while we drain samples.
+	 */
+	ret = regmap_read(data->regmap, MAX86150_REG_INT_STATUS1, &status);
+	if (ret)
+		goto done;
+
+	ret = regmap_read(data->regmap, MAX86150_REG_FIFO_WR_PTR, &wr_ptr);
+	if (ret)
+		goto done;
+	ret = regmap_read(data->regmap, MAX86150_REG_FIFO_RD_PTR, &rd_ptr);
+	if (ret)
+		goto done;
+
+	/*
+	 * OVF_COUNTER: if non-zero the FIFO overflowed; drain all 32 slots.
+	 * When wr_ptr == rd_ptr with no overflow the FIFO could be empty OR
+	 * hold exactly MAX86150_FIFO_DEPTH entries (pointer wrap-around).
+	 * Use the A_FULL status bit to disambiguate: if the IRQ fired for
+	 * A_FULL but the computed count is zero, the FIFO wrapped to full.
+	 */
+	ret = regmap_read(data->regmap, MAX86150_REG_OVF_COUNTER, &ovf);
+	if (ret)
+		goto done;
+
+	if (ovf > 0) {
+		n_avail = MAX86150_FIFO_DEPTH;
+	} else {
+		n_avail = (wr_ptr - rd_ptr) & (MAX86150_FIFO_DEPTH - 1);
+		if (n_avail == 0 && (status & MAX86150_INT_A_FULL))
+			n_avail = MAX86150_FIFO_DEPTH;

drivers/iio/health/max86150.c line 415

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	}
+
+	/*
+	 * Anchor timestamps to the A_FULL IRQ capture time: sample index
+	 * (MAX86150_A_FULL_SAMPLES - 1) corresponds to pf->timestamp.
+	 * Samples that accumulated between the IRQ and handler execution
+	 * receive timestamps in the future relative to the IRQ, eliminating
+	 * load-dependent jitter in multi-sample drains.
+	 */
+	for (i = 0; i < n_avail; i++) {
	for (unsigned int i = 0; i < n_avail; i++) {

drivers/iio/health/max86150.c line 418

Quoted 3 lines
drivers/iio/health/max86150.c new file +3 -0
+		s64 ts = pf->timestamp +
+			 (s64)(i - (MAX86150_A_FULL_SAMPLES - 1)) *
+			 data->sample_period_ns;

This is unmaintainable. Split definition and assignment. Also, do you really need a casting? I.o.w. isn't pf->timestamp already an s64?

Quoted 43 lines
drivers/iio/health/max86150.c new file +43 -0
+		ret = max86150_read_one_sample(data, &ppg_red, &ppg_ir, &ecg);
+		if (ret)
+			break;
+
+		/*
+		 * Zero the entire buffer before packing so padding bytes
+		 * between enabled channels do not leak previous sample data
+		 * to userspace when fewer than 3 channels are active.
+		 */
+		memset(data->buf, 0, sizeof(data->buf));
+
+		/*
+		 * Pack only active channels at consecutive positions [0..j-1].
+		 * iio_push_to_buffers_with_timestamp() uses scan_bytes (which
+		 * accounts for the active channel count) to place the timestamp,
+		 * so static indexing would misplace it when fewer than 3
+		 * channels are enabled.
+		 */
+		j = 0;
+		if (test_bit(MAX86150_IDX_PPG_RED, idev->active_scan_mask))
+			data->buf[j++] = ppg_red;
+		if (test_bit(MAX86150_IDX_PPG_IR, idev->active_scan_mask))
+			data->buf[j++] = ppg_ir;
+		if (test_bit(MAX86150_IDX_ECG, idev->active_scan_mask))
+			data->buf[j++] = ecg;
+
+		iio_push_to_buffers_with_timestamp(idev, data->buf, ts);
+	}
+
+done:
+	iio_trigger_notify_done(idev->trig);
+	return IRQ_HANDLED;
+}
+
+/* Chip initialisation / teardown */
+
+static void max86150_powerdown(void *arg)
+{
+	struct max86150_data *data = arg;
+
+	regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1, 0);
+	regmap_write(data->regmap, MAX86150_REG_SYS_CTRL, MAX86150_SYS_SHDN);
+}

...

drivers/iio/health/max86150.c line 473

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+static int max86150_chip_init(struct max86150_data *data)
+{
+	int ret;
+
+	/* Software reset; the bit self-clears within 1 ms */
+	ret = regmap_write(data->regmap, MAX86150_REG_SYS_CTRL,
+			   MAX86150_SYS_RESET);
+	if (ret)
+		return ret;
+	usleep_range(1000, 2000);

fsleep(). Also need a reference to datasheet section, table, et cetera.

Quoted 40 lines
drivers/iio/health/max86150.c new file +40 -0
+	/*
+	 * FIFO: no sample averaging, rollover enabled, assert A_FULL when
+	 * 17 samples are in the FIFO (32 - 15 = 17 available to read).
+	 */
+	ret = regmap_write(data->regmap, MAX86150_REG_FIFO_CONFIG,
+			   MAX86150_FIFO_ROLLOVER_EN |
+			   FIELD_PREP(MAX86150_FIFO_A_FULL,
+				      MAX86150_FIFO_A_FULL_VAL));
+	if (ret)
+		return ret;
+
+	/* Slot 1 = PPG Red (LED1), Slot 2 = PPG IR (LED2) */
+	ret = regmap_write(data->regmap, MAX86150_REG_FIFO_DCTRL1,
+			   FIELD_PREP(MAX86150_FIFO_FD1, MAX86150_FD_LED1) |
+			   FIELD_PREP(MAX86150_FIFO_FD2, MAX86150_FD_LED2));
+	if (ret)
+		return ret;
+
+	/* Slot 3 = ECG, Slot 4 = disabled */
+	ret = regmap_write(data->regmap, MAX86150_REG_FIFO_DCTRL2,
+			   FIELD_PREP(MAX86150_FIFO_FD3, MAX86150_FD_ECG) |
+			   FIELD_PREP(MAX86150_FIFO_FD4, MAX86150_FD_NONE));
+	if (ret)
+		return ret;
+
+	/* PPG: 100 Hz sample rate, 16384 nA ADC full-scale range */
+	ret = regmap_write(data->regmap, MAX86150_REG_PPG_CONFIG1,
+			   FIELD_PREP(MAX86150_PPG_ADC_RGE,
+				      MAX86150_PPG_ADC_RGE_16384) |
+			   FIELD_PREP(MAX86150_PPG_SR,
+				      MAX86150_PPG_SR_100HZ));
+	if (ret)
+		return ret;
+
+	/* LED pulse amplitudes (~50 mA) */
+	ret = regmap_write(data->regmap, MAX86150_REG_LED1_PA,
+			   MAX86150_LED_PA_DEFAULT);
+	if (ret)
+		return ret;
+

drivers/iio/health/max86150.c line 524

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	ret = regmap_write(data->regmap, MAX86150_REG_LED2_PA,
+			   MAX86150_LED_PA_DEFAULT);
+	if (ret)
+		return ret;
+
+	/*
+	 * Record sample period for timestamp reconstruction in the trigger
+	 * handler.  The PPG_SR field is fixed to 100 Hz in this driver.
+	 */
+	data->sample_period_ns = 10000000; /* 100 Hz = 10 ms */
	10 * NSEC_PER_MSEC

from time.h.

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	/*
+	 * Assert SYS_SHDN so the LED drivers do not draw current while
+	 * the driver is bound but no capture is active.
+	 * set_trigger_state() clears SHDN when the IIO buffer is enabled
+	 * and re-asserts it when disabled.  read_raw() wakes and sleeps
+	 * the device around each single-shot read.
+	 */
+	return regmap_write(data->regmap, MAX86150_REG_SYS_CTRL,
+			    MAX86150_SYS_SHDN);
+}

...

Quoted 1 line

+/* Probe */

These comments bring no value, please, drop all of a such.

...

Quoted 4 lines
drivers/iio/health/max86150.c new file +4 -0
+static int max86150_probe(struct i2c_client *client)
+{
+	struct iio_dev		*indio_dev;
+	struct max86150_data	*data;

drivers/iio/health/max86150.c line 551

Quoted 10 lines
drivers/iio/health/max86150.c new file +9 -0
+	unsigned int		 part_id;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data      = iio_priv(indio_dev);
 
+	data->dev = &client->dev;

A duplication. You have regmap with the same device.

drivers/iio/health/max86150.c line 561

Quoted 9 lines
drivers/iio/health/max86150.c new file +9 -0
+	/*
+	 * Enable power supplies before any I2C access.  Both supplies are
+	 * optional in the device tree; use _optional variant so probing
+	 * succeeds on boards that power the device from fixed rails with no
+	 * DT regulator node.
+	 */
+	ret = devm_regulator_get_enable_optional(&client->dev, "vdd");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,

Add

	struct device *dev = &client->dev;

to the top of the function and use everywhere.

Quoted 49 lines
drivers/iio/health/max86150.c new file +49 -0
+				     "Failed to get/enable vdd supply\n");
+
+	ret = devm_regulator_get_enable_optional(&client->dev, "leds");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to get/enable leds supply\n");
+
+	data->regmap = devm_regmap_init_i2c(client, &max86150_regmap_config);
+	if (IS_ERR(data->regmap))
+		return dev_err_probe(&client->dev, PTR_ERR(data->regmap),
+				     "Failed to initialise regmap\n");
+
+	ret = regmap_read(data->regmap, MAX86150_REG_PART_ID, &part_id);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Cannot read part ID\n");
+
+	if (part_id != MAX86150_PART_ID_VAL)
+		return dev_err_probe(&client->dev, -ENODEV,
+				     "Unexpected part ID 0x%02x (expected 0x%02x)\n",
+				     part_id, MAX86150_PART_ID_VAL);
+
+	ret = max86150_chip_init(data);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Chip initialisation failed\n");
+
+	ret = devm_add_action_or_reset(&client->dev, max86150_powerdown, data);
+	if (ret)
+		return ret;
+
+	indio_dev->name         = "max86150";
+	indio_dev->channels     = max86150_channels;
+	indio_dev->num_channels = ARRAY_SIZE(max86150_channels);
+	indio_dev->info         = &max86150_iio_info;
+	indio_dev->modes        = INDIO_DIRECT_MODE;
+
+	/*
+	 * If the device tree provides an interrupt, set up a hardware
+	 * trigger so userspace can use the FIFO almost-full signal to
+	 * drive capture without polling.
+	 */
+	if (client->irq > 0) {
+		unsigned long irq_trig;
+
+		data->trig = devm_iio_trigger_alloc(&client->dev,
+						    "%s-dev%d",
+						    indio_dev->name,
+						    iio_device_id(indio_dev));

drivers/iio/health/max86150.c line 619

Quoted 10 lines
drivers/iio/health/max86150.c new file +9 -0
+		if (!data->trig)
+			return -ENOMEM;
+
+		data->trig->ops = &max86150_trigger_ops;
+		iio_trigger_set_drvdata(data->trig, indio_dev);
+
+		/*
+		 * Honour the interrupt trigger type from the device tree.
 
+		 * Fall back to falling-edge if the DT does not specify one.

Why? Do we need to support broken DT?

drivers/iio/health/max86150.c line 623

Quoted 5 lines
drivers/iio/health/max86150.c new file +4 -0
+		 */
+		irq_trig = irq_get_trigger_type(client->irq);
 
+		if (!irq_trig)
+			irq_trig = IRQF_TRIGGER_FALLING;

Simply drop this.

drivers/iio/health/max86150.c line 632

Quoted 9 lines
drivers/iio/health/max86150.c new file +8 -0
+		ret = devm_request_irq(&client->dev, client->irq,
+				       iio_trigger_generic_data_rdy_poll,
+				       irq_trig,
+				       "max86150", data->trig);
+		if (ret)
 
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot request IRQ %d\n",
+					     client->irq);

No dup messages.

Quoted 4 lines
drivers/iio/health/max86150.c new file +4 -0
+
+		ret = devm_iio_trigger_register(&client->dev, data->trig);
+		if (ret)
+			return dev_err_probe(&client->dev, ret,

drivers/iio/health/max86150.c line 646

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+					     "Cannot register trigger\n");
+	}
+
+	ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
+					      iio_pollfunc_store_time,
+					      max86150_trigger_handler,
+					      NULL);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Cannot setup triggered buffer\n");

So, it seems this message won't ever be printed. Drop it.

Quoted 2 lines
drivers/iio/health/max86150.c new file +2 -0
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
Signature · 2 lines
With Best Regards,
Andy Shevchenko
#17 lore raw

PATCH v4 3/3 MAINTAINERS: add entry for MAX86150 IIO health driver

In reply to Md Shofiqul Islam

Add a MAINTAINERS entry for the new MAX86150 ECG and PPG biosensor driver, covering both the driver and its DT binding schema.

---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)
MAINTAINERS +7 -0
index 3115538ce829..a441ec44bb27 100644
@@ -15863,6 +15863,13 @@ S:	Supported
 F:	Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
 F:	drivers/power/supply/max77976_charger.c
 
+MAXIM MAX86150 ECG AND PPG BIOSENSOR DRIVER
+M:	Md Shofiqul Islam <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
+F:	drivers/iio/health/max86150.c
+
 MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
 M:	Krzysztof Kozlowski <[email protected]>
 L:	[email protected]
Signature · 1 line
2.51.1
#18 lore raw

v5 cover iio: health: add MAX86150 ECG and PPG biosensor driver

In reply to Md Shofiqul Islam

Changes in v5 (addressing Conor Dooley and Sashiko review of v4):

DT binding (Conor Dooley):
- Rename binding file and compatible to adi,max86150 per ADI policy for former Maxim products.
- Add missing power supply properties: avdd-supply (analog core), vref-supply (ECG reference); make all four supplies required.
- Fix vdd-supply description: remove voltage tolerances; say "1.8 V".
- Fix leds-supply description: say "typically 3.3 V" (datasheet max is 5.0 V, not 5.5 V as previously stated).
- Rename example node to heart-rate@5e per prior DT art.

Driver (Sashiko):
- [High] Re-assign indio_dev->trig after trigger registration so the hardware trigger is the default and iio_trigger_validate_own_device accepts writes to current_trigger without a manual sysfs step. iio_device_unregister() releases the reference via iio_trigger_put().
- [High] Switch from devm_request_irq() to devm_request_threaded_irq() with IRQF_ONESHOT. A hard handler returning IRQ_HANDLED on a level-triggered line unmasks the still-asserted INT pin and causes an immediate re-fire loop. IRQF_ONESHOT keeps the line masked until max86150_trigger_handler reads INT_STATUS1 and de-asserts the source.
- [Medium] Fix overflow timestamps: when OVF_COUNTER > 0, pf->timestamp reflects an earlier A_FULL event and is no longer a valid anchor. Capture ktime_get_ns() at drain time and reconstruct timestamps relative to the newest surviving sample instead.
- [Medium] Pad fifo_raw to ARCH_DMA_MINALIGN bytes so that buf starts in the next cacheline. Previously the 9-byte fifo_raw (DMA target) and buf (CPU push buffer) shared a cacheline, violating DMA API constraints flagged by CONFIG_DMA_API_DEBUG.
- [Medium] Replace blind usleep_range() in read_raw() with regmap_read_poll_timeout() on the PPG_RDY bit. A fixed 11 ms sleep risks reading an empty FIFO if the internal oscillator starts slower than nominal; polling with a 25 ms timeout is robust.

v4 cover letter

Md Shofiqul Islam (3):
dt-bindings: iio: health: add adi,max86150
iio: health: add MAX86150 ECG and PPG biosensor driver
MAINTAINERS: add entry for MAX86150 IIO health driver

 .../bindings/iio/health/adi,max86150.yaml     |  78 ++
 MAINTAINERS                                   |   7 +
 drivers/iio/health/Kconfig                    |  23 +
 drivers/iio/health/Makefile                   |   1 +
 drivers/iio/health/max86150.c                 | 730 ++++++++++++++++++
 5 files changed, 839 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/adi,max86150.yaml
 create mode 100644 drivers/iio/health/max86150.c
Signature · 1 line
2.51.1
#19 lore raw

PATCH v5 1/3 dt-bindings: iio: health: add adi,max86150

Add Device Tree binding schema for the Analog Devices MAX86150 integrated ECG and PPG biosensor.

The device exposes two PPG channels (Red LED and IR LED) and one ECG channel over I2C, with a 32-entry hardware FIFO and an active-low interrupt.

---
 .../bindings/iio/health/adi,max86150.yaml     | 78 +++++++++++++++++++
 1 file changed, 78 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/adi,max86150.yaml
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +78 -0
new file mode 100644
index 000000000000..c191f4f1525b
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/health/adi,max86150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices MAX86150 ECG and PPG Biosensor
+
+maintainers:
+  - Md Shofiqul Islam <[email protected]>
+
+description: |
+  The MAX86150 is an integrated biosensor SoC that combines:
+    - Two PPG (photoplethysmography) channels: Red LED and IR LED,
+      for heart rate and blood-oxygen saturation (SpO2) measurement.
+    - One ECG (electrocardiogram) channel for biopotential recording.
+
+  The device communicates over I2C at up to 400 kHz and raises an
+  active-low interrupt when the 32-entry hardware FIFO reaches its
+  configurable almost-full threshold.
+
+  Datasheet:
+    https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf
+
+properties:
+  compatible:
+    const: adi,max86150
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+    description: |
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready.
+
+  vdd-supply:
+    description: Digital core power supply (1.8 V).
+
+  avdd-supply:
+    description: Analog core power supply (1.8 V).
+
+  vref-supply:
+    description: ECG reference voltage supply.
+
+  leds-supply:
+    description: LED anode supply, typically 3.3 V.
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+  - avdd-supply
+  - vref-supply
+  - leds-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        heart-rate@5e {
+            compatible = "adi,max86150";
+            reg = <0x5e>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            vdd-supply = <&vdd_1v8>;
+            avdd-supply = <&vdd_1v8>;
+            vref-supply = <&vdd_1v8>;
+            leds-supply = <&vdd_3v3>;
+        };
+    };
Signature · 1 line
2.51.1
#20 lore raw

Re: [PATCH v5 1/3] dt-bindings: iio: health: add adi,max86150

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The description for the `interrupts` property explicitly states it is an 'Active-low interrupt…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The description for the interrupts property explicitly states it is an 'Active-low interrupt line', but the example uses IRQ_TYPE_EDGE_FALLING. --

commit 842884bb765a65ccd68dc98c1bab3a30ca22640a
Author: Md Shofiqul Islam <[email protected]>

dt-bindings: iio: health: add adi,max86150

Add Device Tree binding schema for the Analog Devices MAX86150 integrated ECG and PPG biosensor.

Quoted 6 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +0 -0
new file mode 100644
index 0000000000000..c191f4f1525b1
@@ -0,0 +1,78 @@

[ ... ]

Quoted 5 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +5 -0
+  interrupts:
+    maxItems: 1
+    description: |
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready.

[ ... ]

Documentation/devicetree/bindings/iio/health/adi,max86150.yaml line 72

Quoted 13 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +13 -0
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        heart-rate@5e {
+            compatible = "adi,max86150";
+            reg = <0x5e>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;

[Severity: Low]
The property description states this is an "Active-low interrupt line", which typically corresponds to IRQ_TYPE_LEVEL_LOW. Does the example use IRQ_TYPE_EDGE_FALLING intentionally, or should it be updated to match the description to avoid misleading developers who might copy this example?

Quoted 1 line

+ vdd-supply = <&vdd_1v8>;

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#21 lore raw

Re: [PATCH v5 1/3] dt-bindings: iio: health: add adi,max86150

In reply to Md Shofiqul Islam

On Tue, Jun 23, 2026 at 11:11:21PM +0300, Md Shofiqul Islam wrote:

Do not attach (thread) your patchsets to some other threads (unrelated or older versions). This buries them deep in the mailbox and might interfere with applying entire sets. See also:
https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830

Documentation/devicetree/bindings/iio/health/adi,max86150.yaml line 34

Quoted 6 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +6 -0
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+    description: |

Do not need '|' unless you need to preserve formatting.

Documentation/devicetree/bindings/iio/health/adi,max86150.yaml line 38

Quoted 4 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +4 -0
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready.
+
+  vdd-supply:

vdddig? Which supply is this?

Documentation/devicetree/bindings/iio/health/adi,max86150.yaml line 41

Quoted 3 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +3 -0
+    description: Digital core power supply (1.8 V).
+
+  avdd-supply:

I cannot find it in datasheet.

Documentation/devicetree/bindings/iio/health/adi,max86150.yaml line 47

Quoted 6 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +6 -0
+    description: Analog core power supply (1.8 V).
+
+  vref-supply:
+    description: ECG reference voltage supply.
+
+  leds-supply:

Datasheet calls this VLED. Don't invent names.

Best regards,
Krzysztof

maintainer
#22 lore raw

Re: [PATCH v5 1/3] dt-bindings: iio: health: add adi,max86150

In reply to Md Shofiqul Islam
Quoted 19 lines · On Tue, 23 Jun 2026 23:11:21 +0300 Md Shofiqul Islam <[email protected]>…

Add Device Tree binding schema for the Analog Devices MAX86150 integrated ECG and PPG biosensor.

The device exposes two PPG channels (Red LED and IR LED) and one ECG channel over I2C, with a 32-entry hardware FIFO and an active-low interrupt.

---
 .../bindings/iio/health/adi,max86150.yaml     | 78 +++++++++++++++++++
 1 file changed, 78 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/adi,max86150.yaml
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +0 -0
new file mode 100644
index 000000000000..c191f4f1525b

Documentation/devicetree/bindings/iio/health/adi,max86150.yaml line 7

Quoted 7 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +7 -0
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/health/adi,max86150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices MAX86150 ECG and PPG Biosensor

I'd spell out the acronyms. I knew what ECGs were but PPG was new to me. Oddly I did know what Plethysmography is and even more randomly have a patent on a different approach to measure a different form (respiration rather than blood).

Like many of these sensors it is serious optimistic to say the sensor measures anything itself - instead it measures stuff that after some tricky processing can be used to derive it and I'm going to guess that like the previous sensors that magic maths is not readily available?

The datasheet only seems to mention pulse oximetry which brings it inline with the older parts.

Documentation/devicetree/bindings/iio/health/adi,max86150.yaml line 13

Quoted 6 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +6 -0
+
+maintainers:
+  - Md Shofiqul Islam <[email protected]>
+
+description: |
+  The MAX86150 is an integrated biosensor SoC that combines:

Why call it a SoC? that term isn't used on the datasheet and to me would imply sophisticated CPU to do the processing on chip.

Documentation/devicetree/bindings/iio/health/adi,max86150.yaml line 20

Quoted 7 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +7 -0
+    - Two PPG (photoplethysmography) channels: Red LED and IR LED,
+      for heart rate and blood-oxygen saturation (SpO2) measurement.
+    - One ECG (electrocardiogram) channel for biopotential recording.
+
+  The device communicates over I2C at up to 400 kHz and raises an
+  active-low interrupt when the 32-entry hardware FIFO reaches its
+  configurable almost-full threshold.

Not sure this bit is useful for the binding. Almost all I2C devices work at 400kHz maximum. Maybe mentioning the fifo is worthwhile.

Quoted 6 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +6 -0
+
+  Datasheet:
+    https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf
+
+properties:
+  compatible:

Documentation/devicetree/bindings/iio/health/adi,max86150.yaml

Quoted 10 lines
Documentation/devicetree/bindings/iio/health/adi,max86150.yaml new file +10 -0
+    const: adi,max86150
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+    description: |
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready

The almost full definition is certainly 'novel'. Not a suggestion to change this, but it rather surprised me when I wondered what this was talking about.

Jonathan

#23 lore raw

PATCH v5 2/3 iio: health: add MAX86150 ECG and PPG biosensor driver

In reply to Md Shofiqul Islam

Add a new IIO driver for the Analog Devices MAX86150 integrated biosensor, which combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device.

Key features:
- 32-entry hardware FIFO with configurable almost-full threshold
- Interrupt-driven triggered buffer using IIO trigger framework
- Per-sample timestamp reconstruction anchored to A_FULL IRQ time
- DMA-safe FIFO read buffer padded to full cacheline to satisfy CONFIG_DMA_API_DEBUG constraints (fifo_raw and buf in separate cachelines)
- Level-triggered interrupt safety via IRQF_ONESHOT (threaded IRQ keeps line masked until INT_STATUS1 is cleared)
- PPG_RDY polling in read_raw() to handle slow oscillator start-up
- SYS_SHDN power management: device stays off until buffer enabled or sysfs read
- Optional vdd, avdd, vref, leds regulator support

---
 drivers/iio/health/Kconfig    |  23 ++
 drivers/iio/health/Makefile   |   1 +
 drivers/iio/health/max86150.c | 730 ++++++++++++++++++++++++++++++++++
 3 files changed, 754 insertions(+)
 create mode 100644 drivers/iio/health/max86150.c
drivers/iio/health/Kconfig +23 -0
index a89f3abf11f4..6496cf55290c 100644
@@ -13,6 +13,7 @@ config AFE4403
 	depends on SPI_MASTER
 	select REGMAP_SPI
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4403
@@ -26,6 +27,7 @@ config AFE4404
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4404
@@ -39,6 +41,7 @@ config MAX30100
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -52,6 +55,7 @@ config MAX30102
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -62,4 +66,23 @@ config MAX30102
 
 endmenu
 
+
+config MAX86150
+	tristate "MAX86150 ECG and PPG biosensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGER
+	select IIO_TRIGGERED_BUFFER
+	select REGMAP_I2C
+	help
+	  Say Y here to enable support for the Maxim MAX86150 combined
+	  ECG and photoplethysmography (PPG) biosensor.
+
+	  The driver exposes three IIO channels: two PPG optical channels
+	  (Red and IR LED) for heart rate and SpO2 monitoring, and one
+	  ECG channel for biopotential recording.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called max86150.
+
 endmenu
drivers/iio/health/Makefile +1 -0
index 910817112258..04fc73c58444 100644
@@ -9,3 +9,4 @@ obj-$(CONFIG_AFE4403)		+= afe4403.o
 obj-$(CONFIG_AFE4404)		+= afe4404.o
 obj-$(CONFIG_MAX30100)		+= max30100.o
 obj-$(CONFIG_MAX30102)		+= max30102.o
+obj-$(CONFIG_MAX86150)		+= max86150.o
drivers/iio/health/max86150.c new file +730 -0

730 lines not on this page · raw message

Signature · 1 line
2.51.1
#24 lore raw
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] Hardware FIFO overrun and data corruption due to incorrect burst read length. - [High] Infinite…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Hardware FIFO overrun and data corruption due to incorrect burst read length.
- [High] Infinite IRQ storm on level-triggered interrupts due to improper IRQF_ONESHOT handling, and potential DoS via missing .validate_device.
- [Medium] IIO trigger reference leak on probe error path.
--

commit 9590a122a97de73064d5ca132b0611828fac557d
Author: Md Shofiqul Islam <[email protected]>

iio: health: add MAX86150 ECG and PPG biosensor driver

Add a new IIO driver for the Analog Devices MAX86150 integrated biosensor, which combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device.

Quoted 5 lines
drivers/iio/health/max86150.c new file +0 -0
new file mode 100644
index 0000000000000..92bf31f0a7615

[ ... ]

drivers/iio/health/max86150.c line 211

Quoted 11 lines
drivers/iio/health/max86150.c new file +11 -0
+static int max86150_read_one_sample(struct max86150_data *data,
+				    u32 *ppg_red, u32 *ppg_ir, s32 *ecg)
+{
+	int ret;
+
+	/*
+	 * Use data->fifo_raw (heap memory) not a local array so the buffer is
+	 * DMA-mappable for I2C host controllers that use DMA for burst reads.
+	 */
+	ret = regmap_noinc_read(data->regmap, MAX86150_REG_FIFO_DATA,
+				data->fifo_raw, sizeof(data->fifo_raw));

[Severity: High]
Since data->fifo_raw is padded to ARCH_DMA_MINALIGN (e.g., 64 or 128 bytes) for DMA safety, will sizeof(data->fifo_raw) cause the I2C burst read to pop 64 or 128 bytes from the hardware FIFO instead of the intended 9 bytes?

Could this destroy the 3-byte slot alignment and permanently drop data?

Quoted 2 lines
drivers/iio/health/max86150.c new file +2 -0
+	if (ret)
+		return ret;

[ ... ]

drivers/iio/health/max86150.c line 370

Quoted 3 lines
drivers/iio/health/max86150.c new file +3 -0
+static const struct iio_trigger_ops max86150_trigger_ops = {
+	.set_trigger_state = max86150_set_trigger_state,
+};

[Severity: High]
Does the absence of .validate_device here allow external IIO devices to attach to this trigger?

If they do attach, could that prevent the consumer thread from clearing this device's interrupt, leading to a permanent IRQ storm?

[ ... ]

drivers/iio/health/max86150.c line 674

Quoted 9 lines
drivers/iio/health/max86150.c new file +9 -0
+		irq_trig = irq_get_trigger_type(client->irq);
+		if (!irq_trig)
+			irq_trig = IRQF_TRIGGER_FALLING;
+
+		ret = devm_request_threaded_irq(&client->dev, client->irq,
+						NULL,
+						iio_trigger_generic_data_rdy_poll,
+						irq_trig | IRQF_ONESHOT,
+						"max86150", data->trig);

[Severity: High]
For level-triggered interrupts, will IRQF_ONESHOT correctly mask the line until the hardware is cleared?

Looking at iio_trigger_generic_data_rdy_poll(), it acts as the primary threaded handler and returns IRQ_HANDLED immediately after scheduling the consumer thread.

Doesn't returning IRQ_HANDLED unmask the interrupt line before max86150_trigger_handler() has a chance to read the hardware registers and clear the source, causing an immediate IRQ storm?

Quoted 12 lines
drivers/iio/health/max86150.c new file +12 -0
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot request IRQ %d\n",
+					     client->irq);
+
+		ret = devm_iio_trigger_register(&client->dev, data->trig);
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot register trigger\n");
+
+		/*
+		 * Set the default trigger so userspace can enable the buffer

drivers/iio/health/max86150.c line 696

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		 * without a manual current_trigger write.  iio_device_unregister()
+		 * calls iio_trigger_put() to release this reference.
+		 */
+		indio_dev->trig = iio_trigger_get(data->trig);
+	}
+
+	ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
+					      iio_pollfunc_store_time,
+					      max86150_trigger_handler,
+					      NULL);

[Severity: Medium]
If devm_iio_triggered_buffer_setup() fails here, is the trigger reference acquired by iio_trigger_get() leaked?

Since INDIO_BUFFER_TRIGGERED is not set when buffer setup fails, iio_dev_release() will skip calling iio_device_unregister_trigger_consumer(). Does this mean iio_trigger_put() is never called to clean up this reference?

Quoted 6 lines
drivers/iio/health/max86150.c new file +6 -0
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Cannot setup triggered buffer\n");
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
maintainer
#25 lore raw
In reply to Md Shofiqul Islam
Quoted 18 lines · On Tue, 23 Jun 2026 23:11:22 +0300 Md Shofiqul Islam <[email protected]>…

Add a new IIO driver for the Analog Devices MAX86150 integrated biosensor, which combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device.

Key features:
- 32-entry hardware FIFO with configurable almost-full threshold
- Interrupt-driven triggered buffer using IIO trigger framework
- Per-sample timestamp reconstruction anchored to A_FULL IRQ time
- DMA-safe FIFO read buffer padded to full cacheline to satisfy CONFIG_DMA_API_DEBUG constraints (fifo_raw and buf in separate cachelines)
- Level-triggered interrupt safety via IRQF_ONESHOT (threaded IRQ keeps line masked until INT_STATUS1 is cleared)
- PPG_RDY polling in read_raw() to handle slow oscillator start-up
- SYS_SHDN power management: device stays off until buffer enabled or sysfs read
- Optional vdd, avdd, vref, leds regulator support

Quoted 1 line

Hi,

Various comments inline.

Thanks

Jonathan

Quoted 36 lines
---
 drivers/iio/health/Kconfig    |  23 ++
 drivers/iio/health/Makefile   |   1 +
 drivers/iio/health/max86150.c | 730 ++++++++++++++++++++++++++++++++++
 3 files changed, 754 insertions(+)
 create mode 100644 drivers/iio/health/max86150.c
drivers/iio/health/Kconfig +3 -0
index a89f3abf11f4..6496cf55290c 100644
@@ -13,6 +13,7 @@ config AFE4403
 	depends on SPI_MASTER
 	select REGMAP_SPI
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4403
@@ -26,6 +27,7 @@ config AFE4404
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4404
@@ -39,6 +41,7 @@ config MAX30100
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim

drivers/iio/health/Kconfig config MAX30102 · line 58

Quoted 4 lines
drivers/iio/health/Kconfig +1 -0
@@ -52,6 +55,7 @@ config MAX30102
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER

Why are you touching other drivers?

drivers/iio/health/max86150.c line 79

Quoted 11 lines
drivers/iio/health/Kconfig +0 -0
 	select IIO_KFIFO_BUF
 	help
drivers/iio/health/max86150.c new file +3 -0
new file mode 100644
index 000000000000..92bf31f0a761
 
+/* PPG_CONFIG1 */
+#define MAX86150_PPG_ADC_RGE		GENMASK(7, 6)
+#define MAX86150_PPG_SR			GENMASK(5, 1)

Name the defines so it is obvious what register they apply to. e.g. MAX86150_PPG_CONFIG1_ADC_RGE_MASK

You can group them with register defines using indent to make it clear which are fields and which are registers.

Quoted 10 lines

+/**
+ * struct max86150_data - driver private state

+ * @regmap:           register map for this device
+ * @dev:              parent device (for dev_err logging)
+ * @trig:             IIO hardware trigger backed by the device interrupt line
+ * @sample_period_ns: sample period in nanoseconds (set from configured rate)
+ * @fifo_raw:         DMA-safe buffer for regmap_noinc_read() FIFO bursts.
+ *                    Padded to ARCH_DMA_MINALIGN bytes so that @buf starts
+ *                    in the next cacheline and the two fields never share a
+ *                    cacheline -- required by CONFIG_DMA_API_DEBUG.

Nope. That's not what it __aligned(ARCH_DMA_MINALIGN) does. Also use IIO_DMA_MINALIGN for consistency - it predates the ARCH one being generally available. Also I'm not sure why the API_DEBUG is relevant. The care around this is needed to avoid actual data corruption not for API reasons.

Quoted 6 lines
+ * @buf:              IIO push buffer sized for worst-case (all 3 channels
+ *                    active): 3 x s32 (12 bytes) + 4-byte pad + s64
+ *                    timestamp = 24 bytes.  __aligned(8) satisfies
+ *                    iio_push_to_buffers_with_timestamp().

+ */

drivers/iio/health/max86150.c line 126

Quoted 8 lines
drivers/iio/health/max86150.c new file +8 -0
+struct max86150_data {
+	struct regmap		*regmap;
+	struct device		*dev;
+	struct iio_trigger	*trig;
+	u32			 sample_period_ns;
+	u8			 fifo_raw[ALIGN(MAX86150_SAMPLE_BYTES, ARCH_DMA_MINALIGN)]
+				 __aligned(ARCH_DMA_MINALIGN);
+	s32 buf[6] __aligned(8);

Use IIO_DECLARE_BUFFER_WITH_TIMESTAMP() and drop the comment above as the macro handles all that for you.

Quoted 29 lines
drivers/iio/health/max86150.c new file +27 -0
+};
+
+/* IIO channel specification */
+
 
 
+
+/**
+ * max86150_read_one_sample - burst-read one complete 3-slot FIFO entry
+ * @data:    driver state
+ * @ppg_red: out - 19-bit PPG Red ADC value (unsigned)
+ * @ppg_ir:  out - 19-bit PPG IR ADC value (unsigned)
+ * @ecg:     out - 18-bit ECG ADC value (sign-extended to s32)
+ *
+ * Each FIFO entry is 9 bytes (3 slots x 3 bytes).  FIFO_DATA is a
+ * streaming register - the address does not auto-increment on each
+ * byte, so regmap_noinc_read() is used instead of regmap_bulk_read().
+ *
+ * Byte layout in the 24-bit FIFO word (MSB first):
+ *   PPG 19-bit unsigned: bits [18:0], top 5 bits are always zero
+ *   ECG 18-bit signed:   bits [17:0], top 6 bits are sign extension
+ */
+static int max86150_read_one_sample(struct max86150_data *data,
+				    u32 *ppg_red, u32 *ppg_ir, s32 *ecg)
+{
+	int ret;
+
+	/*
+	 * Use data->fifo_raw (heap memory) not a local array so the buffer is

drivers/iio/health/max86150.c line 217

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	 * DMA-mappable for I2C host controllers that use DMA for burst reads.
+	 */
+	ret = regmap_noinc_read(data->regmap, MAX86150_REG_FIFO_DATA,
+				data->fifo_raw, sizeof(data->fifo_raw));
+	if (ret)
+		return ret;
+
+	/* Bytes [0..2]: PPG Red - 19-bit value in bits [18:0] */
+	*ppg_red = (u32)(data->fifo_raw[0] & 0x07) << 16 |
+		   (u32)data->fifo_raw[1] << 8 | data->fifo_raw[2];

These smell like get_unaligned_be24() with a single mask applied to the result

drivers/iio/health/max86150.c line 230

Quoted 13 lines
drivers/iio/health/max86150.c new file +13 -0
+
+	/* Bytes [3..5]: PPG IR - same format */
+	*ppg_ir  = (u32)(data->fifo_raw[3] & 0x07) << 16 |
+		   (u32)data->fifo_raw[4] << 8 | data->fifo_raw[5];
+
+	/* Bytes [6..8]: ECG - 18-bit signed, sign-extend to s32 */
+	*ecg = sign_extend32((u32)(data->fifo_raw[6] & 0x03) << 16 |
+			     (u32)data->fifo_raw[7] << 8 | data->fifo_raw[8], 17);
+
+	return 0;
+}
+
+/* IIO read_raw */

Drop that comment. It is obvious anyway from the naming.

Quoted 19 lines
drivers/iio/health/max86150.c new file +19 -0
+
+static int max86150_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct max86150_data *data = iio_priv(indio_dev);
+	unsigned int ppg_rdy_status;
+	u32 ppg_red, ppg_ir;
+	s32 ecg;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		/*
+		 * Claim direct mode to prevent concurrent sysfs reads from
+		 * corrupting the FIFO pointers while a triggered buffer
+		 * capture is active.
+		 */
+		if (!iio_device_claim_direct(indio_dev))

drivers/iio/health/max86150.c line 259

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+			return -EBUSY;
+
+		/*
+		 * Single-shot path: wake the device, flush stale FIFO data,
+		 * wait one sample period, read, then return to shutdown so
+		 * the LEDs are not drawing current when idle.
+		 */
+		ret = regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+					 MAX86150_SYS_SHDN, 0);
+		if (!ret)

This is not the way we handle lots of possible failures. See below for options.

Quoted 20 lines
drivers/iio/health/max86150.c new file +20 -0
+			ret = regmap_write(data->regmap,
+					   MAX86150_REG_FIFO_WR_PTR, 0);
+		if (!ret)
+			ret = regmap_write(data->regmap,
+					   MAX86150_REG_OVF_COUNTER, 0);
+		if (!ret)
+			ret = regmap_write(data->regmap,
+					   MAX86150_REG_FIFO_RD_PTR, 0);
+		if (ret) {
+			regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+					   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+			iio_device_release_direct(indio_dev);
+			return ret;
+		}
+
+		/*
+		 * Poll PPG_RDY rather than sleeping a fixed interval -- the
+		 * internal oscillator may start slower than nominal, leaving
+		 * the FIFO empty if we read too early.  25 ms timeout covers
+		 * more than two 100 Hz sample periods.

drivers/iio/health/max86150.c line 289

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		 */
+		ret = regmap_read_poll_timeout(data->regmap,
+					       MAX86150_REG_INT_STATUS1,
+					       ppg_rdy_status,
+					       ppg_rdy_status & MAX86150_INT_PPG_RDY,
+					       1000, 25000);
+		if (ret) {
+			regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+					   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+			iio_device_release_direct(indio_dev);

Look at the ACQUIRE stuff in iio.h. That should simplify this. It doesn't matter if we hold the deice in direct mode for a little longer than minimum necessary.

Quoted 23 lines
drivers/iio/health/max86150.c new file +23 -0
+			return ret;
+		}
+
+		ret = max86150_read_one_sample(data, &ppg_red, &ppg_ir, &ecg);
+		regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+		iio_device_release_direct(indio_dev);
+		if (ret)
+			return ret;
+
+		switch (chan->scan_index) {
+		case MAX86150_IDX_PPG_RED:
+			*val = ppg_red;
+			break;
+		case MAX86150_IDX_PPG_IR:
+			*val = ppg_ir;
+			break;
+		case MAX86150_IDX_ECG:
+			*val = ecg;
+			break;
+		default:
+			return -EINVAL;
+		}

drivers/iio/health/max86150.c line 322

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info max86150_iio_info = {
+	.read_raw         = max86150_read_raw,
+	.validate_trigger = iio_trigger_validate_own_device,

Will probably go away. See comments below on appropriateness of using a trigger or triggered buffer when a fifo is present.

drivers/iio/health/max86150.c line 325

Quoted 3 lines
drivers/iio/health/max86150.c new file +3 -0
+};
+
+/* Trigger */

Drop. See below.

Quoted 12 lines
drivers/iio/health/max86150.c new file +12 -0
+
+/*
+ * Control device power and the FIFO almost-full interrupt when the IIO
+ * triggered buffer is started (state=true) or stopped (state=false).
+ *
+ * On start: wake from shutdown, flush stale FIFO data so the first
+ * samples pushed to userspace are from after buffer enable, then
+ * unmask the A_FULL interrupt.
+ *
+ * On stop: mask the interrupt, then return to shutdown so the LED
+ * drivers do not draw current while capture is inactive.
+ */

drivers/iio/health/max86150.c line 347

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+static int max86150_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct max86150_data *data = iio_priv(indio_dev);
+	int ret;
+
+	if (!state) {
+		ret = regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1, 0);
+		regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);

regmap_set_bits()

drivers/iio/health/max86150.c line 258

Quoted 5 lines
drivers/iio/health/max86150.c new file +5 -0
+		return ret;
+	}
+
+	ret = regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				 MAX86150_SYS_SHDN, 0);

regmap_clear_bits().
Though if this fails setting it to SYS_SHDN makes little sense - it is probably already in that state.

drivers/iio/health/max86150.c line 362

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_FIFO_WR_PTR, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_OVF_COUNTER, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_FIFO_RD_PTR, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1,
+				   MAX86150_INT_A_FULL);
+	if (ret)

Either use a label and error blcok, or a helper function so all the if (!ret) stuff above can be replace with goto err / returns as appropriate. Given you call this shutdown in all cases a helper is probably easier.

drivers/iio/health/max86150.c line 372

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+	return ret;
+}
+
+static const struct iio_trigger_ops max86150_trigger_ops = {
+	.set_trigger_state = max86150_set_trigger_state,
+};
+
+/* Triggered buffer */

See below - another comment to drop.

Quoted 13 lines
drivers/iio/health/max86150.c new file +13 -0
+
+/**
+ * max86150_trigger_handler - threaded IRQ handler for FIFO almost-full
+ *
+ * Called by the IIO buffer infrastructure when the hardware trigger fires.
+ * Reads INT_STATUS1 to de-assert the interrupt, then drains all available
+ * FIFO samples into the IIO push buffer, packing only the channels that
+ * are currently enabled in active_scan_mask.
+ */
+static irqreturn_t max86150_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func	*pf   = p;
+	struct iio_dev		*idev = pf->indio_dev;

drivers/iio/health/max86150.c line 395

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	struct max86150_data	*data = iio_priv(idev);
+	unsigned int status, wr_ptr, rd_ptr, ovf;
+	u32 ppg_red, ppg_ir;
+	s32 ecg;
+	s64 t_drain = 0;
+	int ret, n_avail, i, j;
+
+	/*
+	 * Reading INT_STATUS1 clears the interrupt.  Do this before touching
+	 * the FIFO so the pin is de-asserted while we drain samples.

Why? What's the chance of a race if a sample comes in whilst you are reading?

Quoted 31 lines
drivers/iio/health/max86150.c new file +31 -0
+	 */
+	ret = regmap_read(data->regmap, MAX86150_REG_INT_STATUS1, &status);
+	if (ret)
+		goto done;
+
+	ret = regmap_read(data->regmap, MAX86150_REG_FIFO_WR_PTR, &wr_ptr);
+	if (ret)
+		goto done;
+	ret = regmap_read(data->regmap, MAX86150_REG_FIFO_RD_PTR, &rd_ptr);
+	if (ret)
+		goto done;
+
+	/*
+	 * OVF_COUNTER: if non-zero the FIFO overflowed; drain all 32 slots.
+	 * When wr_ptr == rd_ptr with no overflow the FIFO could be empty OR
+	 * hold exactly MAX86150_FIFO_DEPTH entries (pointer wrap-around).
+	 * Use the A_FULL status bit to disambiguate: if the IRQ fired for
+	 * A_FULL but the computed count is zero, the FIFO wrapped to full.
+	 */
+	ret = regmap_read(data->regmap, MAX86150_REG_OVF_COUNTER, &ovf);
+	if (ret)
+		goto done;
+
+	if (ovf > 0) {
+		n_avail = MAX86150_FIFO_DEPTH;
+		t_drain = ktime_get_ns();
+	} else {
+		n_avail = (wr_ptr - rd_ptr) & (MAX86150_FIFO_DEPTH - 1);
+		if (n_avail == 0 && (status & MAX86150_INT_A_FULL))
+			n_avail = MAX86150_FIFO_DEPTH;
+	}

drivers/iio/health/max86150.c line 436

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+
+	/*
+	 * In the normal (no-overflow) case, anchor timestamps to the A_FULL
+	 * IRQ capture time: sample index (A_FULL_SAMPLES - 1) corresponds to
+	 * pf->timestamp, eliminating scheduling-latency jitter.
+	 *
+	 * In the overflow case the hardware has overwritten the oldest samples;
+	 * pf->timestamp reflects an earlier A_FULL event and is no longer a
+	 * valid anchor.  t_drain (set above) is used as a best-effort reference
+	 * for the newest surviving sample instead.

Normal overflow case would be to drop everything as we don't know the status. Why attempt to handle?

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	 */
+	for (i = 0; i < n_avail; i++) {
+		s64 ts;
+
+		if (ovf > 0)
+			ts = t_drain -
+			     (s64)(n_avail - 1 - i) * data->sample_period_ns;
+		else
+			ts = pf->timestamp +
+			     (s64)(i - (MAX86150_A_FULL_SAMPLES - 1)) *

drivers/iio/health/max86150.c line 456

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+			     data->sample_period_ns;
+
+		ret = max86150_read_one_sample(data, &ppg_red, &ppg_ir, &ecg);
+		if (ret)
+			break;
+
+		/*
+		 * Zero the entire buffer before packing so padding bytes
+		 * between enabled channels do not leak previous sample data
+		 * to userspace when fewer than 3 channels are active.

We don't care about that leak. Userspace is wrong if it reads it and there is no security concern. So generally we don't do this clear.

drivers/iio/health/max86150.c line 465

Quoted 9 lines
drivers/iio/health/max86150.c new file +9 -0
+		 */
+		memset(data->buf, 0, sizeof(data->buf));
+
+		/*
+		 * Pack only active channels at consecutive positions [0..j-1].
+		 * iio_push_to_buffers_with_timestamp() uses scan_bytes (which
+		 * accounts for the active channel count) to place the timestamp,
+		 * so static indexing would misplace it when fewer than 3
+		 * channels are enabled.

Might be cleaner to use available_scan that includes the full set of channels then let the IIO core buffer demux deal with the data mangling. However you need to mangle anyway to make space for the timestamp so I guess this is fine.

drivers/iio/health/max86150.c line 475

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		 */
+		j = 0;
+		if (test_bit(MAX86150_IDX_PPG_RED, idev->active_scan_mask))
+			data->buf[j++] = ppg_red;
+		if (test_bit(MAX86150_IDX_PPG_IR, idev->active_scan_mask))
+			data->buf[j++] = ppg_ir;
+		if (test_bit(MAX86150_IDX_ECG, idev->active_scan_mask))
+			data->buf[j++] = ecg;
+
+		iio_push_to_buffers_with_timestamp(idev, data->buf, ts);

Use iio_push_to_buffers_with_ts()

drivers/iio/health/max86150.c line 483

Quoted 8 lines
drivers/iio/health/max86150.c new file +8 -0
+	}
+
+done:
+	iio_trigger_notify_done(idev->trig);
+	return IRQ_HANDLED;
+}
+
+/* Chip initialisation / teardown */

As below - remove these code structure comments from a driver prior to submitting.

Quoted 9 lines
drivers/iio/health/max86150.c new file +9 -0
+
+static void max86150_powerdown(void *arg)
+{
+	struct max86150_data *data = arg;
+
+	regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1, 0);
+	regmap_write(data->regmap, MAX86150_REG_SYS_CTRL, MAX86150_SYS_SHDN);
+}
+

drivers/iio/health/max86150.c line 502

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+static int max86150_chip_init(struct max86150_data *data)
+{
+	int ret;
+
+	/* Software reset; the bit self-clears within 1 ms */
+	ret = regmap_write(data->regmap, MAX86150_REG_SYS_CTRL,
+			   MAX86150_SYS_RESET);
+	if (ret)
+		return ret;
+	usleep_range(1000, 2000);

fsleep()

Quoted 28 lines
drivers/iio/health/max86150.c new file +28 -0
+
+	/*
+	 * FIFO: no sample averaging, rollover enabled, assert A_FULL when
+	 * 17 samples are in the FIFO (32 - 15 = 17 available to read).
+	 */
+	ret = regmap_write(data->regmap, MAX86150_REG_FIFO_CONFIG,
+			   MAX86150_FIFO_ROLLOVER_EN |
+			   FIELD_PREP(MAX86150_FIFO_A_FULL,
+				      MAX86150_FIFO_A_FULL_VAL));
+	if (ret)
+		return ret;
+
+	/* Slot 1 = PPG Red (LED1), Slot 2 = PPG IR (LED2) */
+	ret = regmap_write(data->regmap, MAX86150_REG_FIFO_DCTRL1,
+			   FIELD_PREP(MAX86150_FIFO_FD1, MAX86150_FD_LED1) |
+			   FIELD_PREP(MAX86150_FIFO_FD2, MAX86150_FD_LED2));
+	if (ret)
+		return ret;
+
+	/* Slot 3 = ECG, Slot 4 = disabled */
+	ret = regmap_write(data->regmap, MAX86150_REG_FIFO_DCTRL2,
+			   FIELD_PREP(MAX86150_FIFO_FD3, MAX86150_FD_ECG) |
+			   FIELD_PREP(MAX86150_FIFO_FD4, MAX86150_FD_NONE));
+	if (ret)
+		return ret;
+
+	/* PPG: 100 Hz sample rate, 16384 nA ADC full-scale range */
+	ret = regmap_write(data->regmap, MAX86150_REG_PPG_CONFIG1,

drivers/iio/health/max86150.c line 540

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+			   FIELD_PREP(MAX86150_PPG_ADC_RGE,
+				      MAX86150_PPG_ADC_RGE_16384) |
+			   FIELD_PREP(MAX86150_PPG_SR,
+				      MAX86150_PPG_SR_100HZ));
+	if (ret)
+		return ret;
+
+	/* LED pulse amplitudes (~50 mA) */
+	ret = regmap_write(data->regmap, MAX86150_REG_LED1_PA,
+			   MAX86150_LED_PA_DEFAULT);

Better to have a define that describes the 50mA part in the name than calling it default.

drivers/iio/health/max86150.c line 553

Quoted 13 lines
drivers/iio/health/max86150.c new file +13 -0
+	if (ret)
+		return ret;
+
+	ret = regmap_write(data->regmap, MAX86150_REG_LED2_PA,
+			   MAX86150_LED_PA_DEFAULT);
+	if (ret)
+		return ret;
+
+	/*
+	 * Record sample period for timestamp reconstruction in the trigger
+	 * handler.  The PPG_SR field is fixed to 100 Hz in this driver.
+	 */
+	data->sample_period_ns = 10000000; /* 100 Hz = 10 ms */

Use some defined multipliers to avoid having lots of 0s.
I haven't figured out what is appropriate here. Maybe 10 * NANO / MILLI;

drivers/iio/health/max86150.c line 566

Quoted 13 lines
drivers/iio/health/max86150.c new file +13 -0
+
+	/*
+	 * Assert SYS_SHDN so the LED drivers do not draw current while
+	 * the driver is bound but no capture is active.
+	 * set_trigger_state() clears SHDN when the IIO buffer is enabled
+	 * and re-asserts it when disabled.  read_raw() wakes and sleeps
+	 * the device around each single-shot read.
+	 */
+	return regmap_write(data->regmap, MAX86150_REG_SYS_CTRL,
+			    MAX86150_SYS_SHDN);
+}
+
+/* Probe */

Remove these 'code' structure comments. They are just noise and can easily become wrong in an upstream driver.

drivers/iio/health/max86150.c line 579

Quoted 13 lines
drivers/iio/health/max86150.c new file +13 -0
+
+static int max86150_probe(struct i2c_client *client)
+{
+	struct iio_dev		*indio_dev;
+	struct max86150_data	*data;
+	unsigned int		 part_id;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data      = iio_priv(indio_dev);

I would never bother trying to force indent. It doesn't make the code much easier to read and results in huge amounts of churn when the indent needs to be changed. Better to not bother in the first place. Note this applies to the variable declarations above as well.

drivers/iio/health/max86150.c line 586

Quoted 7 lines
drivers/iio/health/max86150.c new file +7 -0
+	data->dev = &client->dev;
+
+	/*
+	 * Enable power supplies before any I2C access.  Both supplies are
+	 * optional in the device tree; use _optional variant so probing
+	 * succeeds on boards that power the device from fixed rails with no
+	 * DT regulator node.

Turning supplies one before talking to device is kind of normal. So no need for comment. However, I'm very suspicious. Why are these optional? They should be required in the binding, though that doesn't actually mean they need to be there for the driver to work. Look at how the regulator subsystem provides stub regulators.

drivers/iio/health/max86150.c

Quoted 2 lines
drivers/iio/health/max86150.c new file +2 -0
+	 */
+	ret = devm_regulator_get_enable_optional(&client->dev, "vdd");

Is this vdd_dig on the datasheet? If so should match that naming.

I'd also suggest a local struct device *dev = &client->dev; as it is used a lot.

drivers/iio/health/max86150.c line 593

Quoted 5 lines
drivers/iio/health/max86150.c new file +5 -0
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to get/enable vdd supply\n");
+
+	ret = devm_regulator_get_enable_optional(&client->dev, "avdd");

Not sure what supply this is. VDD_ANA maybe? Generally we match
datasheet naming rather than trying to map to 'standard' names.

drivers/iio/health/max86150.c line 598

Quoted 5 lines
drivers/iio/health/max86150.c new file +5 -0
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to get/enable avdd supply\n");
+
+	ret = devm_regulator_get_enable_optional(&client->dev, "vref");

Looking at the datasheet, that isn't a supply.

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to get/enable vref supply\n");
+
+	ret = devm_regulator_get_enable_optional(&client->dev, "leds");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to get/enable leds supply\n");
+
+	data->regmap = devm_regmap_init_i2c(client, &max86150_regmap_config);

drivers/iio/health/max86150.c line 618

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+	if (IS_ERR(data->regmap))
+		return dev_err_probe(&client->dev, PTR_ERR(data->regmap),
+				     "Failed to initialise regmap\n");
+
+	ret = regmap_read(data->regmap, MAX86150_REG_PART_ID, &part_id);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Cannot read part ID\n");
+
+	if (part_id != MAX86150_PART_ID_VAL)

Print a message but carry on. Rejecting like this prevents use of fully backwards compatible via dt fallback compatibles. Hence we don't do it in modern drivers and are slowly fixing existing drivers not to do this.

Quoted 8 lines
drivers/iio/health/max86150.c new file +8 -0
+		return dev_err_probe(&client->dev, -ENODEV,
+				     "Unexpected part ID 0x%02x (expected 0x%02x)\n",
+				     part_id, MAX86150_PART_ID_VAL);
+
+	ret = max86150_chip_init(data);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Chip initialisation failed\n");

drivers/iio/health/max86150.c line 636

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+
+	ret = devm_add_action_or_reset(&client->dev, max86150_powerdown, data);
+	if (ret)
+		return ret;
+
+	indio_dev->name         = "max86150";
+	indio_dev->channels     = max86150_channels;
+	indio_dev->num_channels = ARRAY_SIZE(max86150_channels);
+	indio_dev->info         = &max86150_iio_info;
+	indio_dev->modes        = INDIO_DIRECT_MODE;

As above, forcing indent is a recipe for churn over time.

drivers/iio/health/max86150.c line 642

Quoted 6 lines
drivers/iio/health/max86150.c new file +6 -0
+
+	/*
+	 * If the device tree provides an interrupt, set up a hardware
+	 * trigger so userspace can use the FIFO almost-full signal to
+	 * drive capture without polling.
+	 */

Not sure this adds anything we can't immediately see from the code.

Quoted 6 lines
drivers/iio/health/max86150.c new file +6 -0
+	if (client->irq > 0) {
+		unsigned long irq_trig;
+
+		data->trig = devm_iio_trigger_alloc(&client->dev,
+						    "%s-dev%d",
+						    indio_dev->name,

drivers/iio/health/max86150.c line 658

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+						    iio_device_id(indio_dev));
+		if (!data->trig)
+			return -ENOMEM;
+
+		data->trig->ops = &max86150_trigger_ops;
+		iio_trigger_set_drvdata(data->trig, indio_dev);
+
+		/*
+		 * Honour the interrupt trigger type from the device tree.
+		 * Fall back to falling-edge if the DT does not specify one.

No. Do what DT says. We only paper over settings where we had them wrong and can't now fix it.

drivers/iio/health/max86150.c line 664

Quoted 6 lines
drivers/iio/health/max86150.c new file +6 -0
+		 *
+		 * Use a threaded IRQ with IRQF_ONESHOT so that level-triggered
+		 * lines are kept masked until max86150_trigger_handler reads
+		 * INT_STATUS1 and de-asserts the interrupt.  A hard handler
+		 * returning IRQ_HANDLED without clearing the source would cause
+		 * an immediate re-fire loop on active-low level interrupts.

Don't think we need this comment either.

Quoted 16 lines
drivers/iio/health/max86150.c new file +16 -0
+		 */
+		irq_trig = irq_get_trigger_type(client->irq);
+		if (!irq_trig)
+			irq_trig = IRQF_TRIGGER_FALLING;
+
+		ret = devm_request_threaded_irq(&client->dev, client->irq,
+						NULL,
+						iio_trigger_generic_data_rdy_poll,
+						irq_trig | IRQF_ONESHOT,
+						"max86150", data->trig);
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot request IRQ %d\n",
+					     client->irq);
+
+		ret = devm_iio_trigger_register(&client->dev, data->trig);

drivers/iio/health/max86150.c line 690

Quoted 10 lines
drivers/iio/health/max86150.c new file +10 -0
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot register trigger\n");
+
+		/*
+		 * Set the default trigger so userspace can enable the buffer
+		 * without a manual current_trigger write.  iio_device_unregister()
+		 * calls iio_trigger_put() to release this reference.
+		 */
+		indio_dev->trig = iio_trigger_get(data->trig);

This is using a fifo, so a trigger is probably inappropriate. Look at other fifo equipped parts and now they use a kfifo buffer directly.

drivers/iio/health/max86150.c line 696

Quoted 6 lines
drivers/iio/health/max86150.c new file +6 -0
+	}
+
+	ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
+					      iio_pollfunc_store_time,
+					      max86150_trigger_handler,
+					      NULL);

With a fifo involved, I doubt using a triggered buffer makes sense either.

drivers/iio/health/max86150.c line 704

Quoted 8 lines
drivers/iio/health/max86150.c new file +8 -0
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Cannot setup triggered buffer\n");
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+/* I2C driver table */

That comment doesn't add anything we can't see by looking at the code so drop it.

Quoted 6 lines
drivers/iio/health/max86150.c new file +6 -0
+
+static const struct i2c_device_id max86150_id[] = {
+	{ "max86150" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, max86150_id);
#26 lore raw
Quoted 1 line · Jonathan Cameron

[PATCH v5 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver

Thank you for the thorough review. I apologise for sending v6 before addressing the driver feedback — that was a mistake. v7 (being sent now) addresses all points raised below:

Architectural change:
- Switch from devm_iio_triggered_buffer_setup() to devm_iio_kfifo_buffer_setup() with iio_buffer_setup_ops (postenable/predisable), matching MAX30100 and MAX30102. The IIO trigger object and trigger_ops are removed entirely.

Kconfig:
- Remove spurious 'select IIO_TRIGGER' from AFE4403, AFE4404, MAX30100, MAX30102. Those were wrong — I was not touching those drivers.
- MAX86150 now selects IIO_KFIFO_BUF, not IIO_TRIGGER or IIO_TRIGGERED_BUFFER.

Register field macros:
- All renamed to include the parent register name and _MASK suffix, e.g. MAX86150_PPG_ADC_RGE -> MAX86150_PPG_CONFIG1_ADC_RGE_MASK.

Buffer declaration:
- s32 buf[6] -> IIO_DECLARE_BUFFER_WITH_TS(s32, buf, 3).
- ARCH_DMA_MINALIGN -> IIO_DMA_MINALIGN; comment about CONFIG_DMA_API_DEBUG removed.

FIFO read:
- get_unaligned_be24() with a single mask for each 24-bit word, matching si1133.c and rm3100-core.c.

Error handling:
- if (!ret) chains replaced with goto throughout read_raw and buffer_postenable.

regmap helpers:
- regmap_update_bits(..., SHDN, 0) -> regmap_clear_bits().
- regmap_update_bits(..., SHDN, SHDN) -> regmap_set_bits().

IRQ handler:
- Overflow: flush FIFO pointers and return — no attempt to reconstruct timestamps from unreliable data.
- memset of push buffer removed.
- iio_push_to_buffers_with_timestamp() -> iio_push_to_buffers_with_ts().
- pf->timestamp replaced with ktime_get_ns() (no trigger object now).

Other style fixes:
- All code-section comments removed (/* Trigger */, /* Probe */, etc.).
- Forced variable declaration alignment removed.
- usleep_range(1000, 2000) -> fsleep(1000).
- 10000000 -> 10 * NSEC_PER_MSEC.
- LED_PA_DEFAULT -> LED_PA_50MA.
- Part ID mismatch: dev_warn() instead of returning -ENODEV.
- IRQ trigger type fallback (IRQF_TRIGGER_FALLING) removed.

Regulators:
- vref removed from driver and DT binding (not a supply per datasheet).
- devm_regulator_get_enable_optional() -> devm_regulator_get_enable().

#27 lore raw

PATCH v5 3/3 MAINTAINERS: add entry for MAX86150 IIO health driver

In reply to Md Shofiqul Islam
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)
MAINTAINERS +7 -0
index 3115538ce829..361a7c8b99ea 100644
@@ -15863,6 +15863,13 @@ S:	Supported
 F:	Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
 F:	drivers/power/supply/max77976_charger.c
 
+MAX86150 ECG AND PPG BIOSENSOR DRIVER
+M:	Md Shofiqul Islam <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/health/adi,max86150.yaml
+F:	drivers/iio/health/max86150.c
+
 MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
 M:	Krzysztof Kozlowski <[email protected]>
 L:	[email protected]
Signature · 1 line
2.51.1
#28 lore raw

Re: [PATCH v5 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver

Quoted 5 lines · Md Shofiqul Islam
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

You need to slow down, please. One version per 24h.

And the last one is not even correct anymore...

Best regards,
Krzysztof

#29 lore raw

Re: [PATCH v5 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver

Quoted 11 lines · On Wed, 24 Jun 2026 08:22:48 +0200 Krzysztof Kozlowski <[email protected]> wrote:
Quoted 5 lines · Md Shofiqul Islam
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

You need to slow down, please. One version per 24h.

And the last one is not even correct anymore...

Additionally, refrain from sending a separate patch for MAINTAINERS, fold the new addition into your dt-binding patch, and then add the
F: entry for the driver code in the actual driver patch.

Signature · 3 lines
Kind regards

CJD
maintainer
#30 lore raw
In reply to Md Shofiqul Islam
Quoted 1 line · On Tue, 23 Jun 2026 23:11:20 +0300 Md Shofiqul Islam <[email protected]>…

Changes in v5 (addressing Conor Dooley and Sashiko review of v4):

No idea if someone else has commented on it in the nested threads I'm seeing but never send a new patch version in reply to another. It leads to really tricky to follow threads in a threading email client + also ensures your whole series is pages up in the maintainers backlog.

New version, new email thread.

Thanks,

Jonathan

Quoted 51 lines

DT binding (Conor Dooley):
- Rename binding file and compatible to adi,max86150 per ADI policy for former Maxim products.
- Add missing power supply properties: avdd-supply (analog core), vref-supply (ECG reference); make all four supplies required.
- Fix vdd-supply description: remove voltage tolerances; say "1.8 V".
- Fix leds-supply description: say "typically 3.3 V" (datasheet max is 5.0 V, not 5.5 V as previously stated).
- Rename example node to heart-rate@5e per prior DT art.

Driver (Sashiko):
- [High] Re-assign indio_dev->trig after trigger registration so the hardware trigger is the default and iio_trigger_validate_own_device accepts writes to current_trigger without a manual sysfs step. iio_device_unregister() releases the reference via iio_trigger_put().
- [High] Switch from devm_request_irq() to devm_request_threaded_irq() with IRQF_ONESHOT. A hard handler returning IRQ_HANDLED on a level-triggered line unmasks the still-asserted INT pin and causes an immediate re-fire loop. IRQF_ONESHOT keeps the line masked until max86150_trigger_handler reads INT_STATUS1 and de-asserts the source.
- [Medium] Fix overflow timestamps: when OVF_COUNTER > 0, pf->timestamp reflects an earlier A_FULL event and is no longer a valid anchor. Capture ktime_get_ns() at drain time and reconstruct timestamps relative to the newest surviving sample instead.
- [Medium] Pad fifo_raw to ARCH_DMA_MINALIGN bytes so that buf starts in the next cacheline. Previously the 9-byte fifo_raw (DMA target) and buf (CPU push buffer) shared a cacheline, violating DMA API constraints flagged by CONFIG_DMA_API_DEBUG.
- [Medium] Replace blind usleep_range() in read_raw() with regmap_read_poll_timeout() on the PPG_RDY bit. A fixed 11 ms sleep risks reading an empty FIFO if the internal oscillator starts slower than nominal; polling with a 25 ms timeout is robust.

v4 cover letter

Md Shofiqul Islam (3):
dt-bindings: iio: health: add adi,max86150
iio: health: add MAX86150 ECG and PPG biosensor driver
MAINTAINERS: add entry for MAX86150 IIO health driver

 .../bindings/iio/health/adi,max86150.yaml     |  78 ++
 MAINTAINERS                                   |   7 +
 drivers/iio/health/Kconfig                    |  23 +
 drivers/iio/health/Makefile                   |   1 +
 drivers/iio/health/max86150.c                 | 730 ++++++++++++++++++
 5 files changed, 839 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/adi,max86150.yaml
 create mode 100644 drivers/iio/health/max86150.c
reviewer
#31 lore raw
In reply to Md Shofiqul Islam
Quoted 11 lines · Md Shofiqul Islam

The MAX86150 (Maxim/Analog Devices) combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device. Data is produced at up to 3200 Hz and buffered in a 32-entry hardware FIFO with a configurable almost-full interrupt.

This series adds:
1. DT binding YAML schema for maxim,max86150
2. IIO driver with triggered buffer support
3. MAINTAINERS entry

Changes in v2 (addressing Sashiko AI review, Joshua Crofts, Krzysztof Kozlowski):

Please don't send revisions in reply to previous series. It breaks many workflows and tools that expect each series to be it's own message thread.

Also, when submitting a series for a new driver, please wait at least one week to gather feedback before sending the next revision.

maintainer
#32 lore raw
Quoted 11 lines · On Tue, 23 Jun 2026 16:04:13 -0500 David Lechner <[email protected]> wrote:
Quoted 10 lines · Md Shofiqul Islam

The MAX86150 (Maxim/Analog Devices) combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device. Data is produced at up to 3200 Hz and buffered in a 32-entry hardware FIFO with a configurable almost-full interrupt.

This series adds:
1. DT binding YAML schema for maxim,max86150
2. IIO driver with triggered buffer support
3. MAINTAINERS entry

Quoted 9 lines
Quoted 1 line

Changes in v2 (addressing Sashiko AI review, Joshua Crofts, Krzysztof Kozlowski):

Please don't send revisions in reply to previous series. It breaks many workflows and tools that expect each series to be it's own message thread.

Also, when submitting a series for a new driver, please wait at least one week to gather feedback before sending the next revision.

One more thing - if you send a new version (e.g v3 here) without anyone having reviewed, reply yourself to the previous version to say what was wrong!

Jonathan

Quoted 2 lines
#33 lore raw

v3 cover iio: health: add MAX86150 ECG and PPG biosensor driver

In reply to Md Shofiqul Islam

The MAX86150 (Maxim/Analog Devices) combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device.

Changes since v2 (addressing Sashiko review):
- Move FIFO read buffer from stack to struct field (DMA-safe for I2C host controllers that use DMA for burst reads)
- Remove redundant devm iio_trigger_put action; the IIO core calls iio_trigger_put(indio_dev->trig) in iio_dev_release()
- Remove IIO_CHAN_INFO_SCALE from info_mask_separate until hardware scale factors (PPG nA/LSB and ECG uV/LSB) are implemented with register readback in a follow-up
- Reconstruct per-sample timestamps from IRQ capture time (pf->timestamp) and the configured sample period (10 ms at 100 Hz) so buffered samples carry accurate, evenly-spaced timestamps instead of all sharing the drain-loop clock reading
- Switch to devm_regulator_get_enable_optional() for vdd-supply and leds-supply so probing succeeds on boards with fixed (non-DT) rails
- Zero data->buf at the start of each FIFO drain iteration to prevent padding bytes from leaking previous sample data when fewer than 3 channels are active
- Flush FIFO (reset wr_ptr, rd_ptr, ovf_counter) in set_trigger_state() before enabling the interrupt so capture starts with a clean FIFO
- Fix file permissions: both new files are 100644

Changes since v1 (addressing Joshua Crofts and Krzysztof Kozlowski review):
- Split into 3 patches: DT binding / driver+Kconfig+Makefile / MAINTAINERS
- Add MAINTAINERS entry

Md Shofiqul Islam (3):
dt-bindings: iio: health: add maxim,max86150
iio: health: add MAX86150 ECG and PPG biosensor driver
MAINTAINERS: add entry for MAX86150 IIO health driver

 .../bindings/iio/health/maxim,max86150.yaml   |  67 +++
 MAINTAINERS                                   |   7 +
 drivers/iio/health/Kconfig                    |  19 +
 drivers/iio/health/Makefile                   |   1 +
 drivers/iio/health/max86150.c                 | 651 ++++++++++++++++++
 5 files changed, 745 insertions(+)
Signature · 1 line
2.43.0
#34 lore raw

PATCH v3 1/3 dt-bindings: iio: health: add maxim,max86150

Add YAML binding schema for the Maxim MAX86150 combined ECG and PPG biosensor. The device exposes two PPG optical channels (Red and IR LED) for heart rate and SpO2 measurement, and one ECG biopotential channel, all accessible over I2C at up to 400 kHz.

An optional active-low interrupt line connects to the 32-entry hardware FIFO almost-full output. Two optional regulator supplies (vdd for the digital core and leds for the LED anodes) cover boards that require explicit power sequencing.

---
 .../bindings/iio/health/maxim,max86150.yaml   | 67 +++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +67 -0
new file mode 100644
index 000000000000..1bf10fd1a3d2
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/health/maxim,max86150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX86150 ECG and PPG Biosensor
+
+maintainers:
+  - Md Shofiqul Islam <[email protected]>
+
+description: |
+  The MAX86150 is an integrated biosensor SoC that combines:
+    - Two PPG (photoplethysmography) channels: Red LED and IR LED,
+      for heart rate and blood-oxygen saturation (SpO2) measurement.
+    - One ECG (electrocardiogram) channel for biopotential recording.
+
+  The device communicates over I2C at up to 400 kHz and raises an
+  active-low interrupt when the 32-entry hardware FIFO reaches its
+  configurable almost-full threshold.
+
+  Datasheet:
+    https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf
+
+properties:
+  compatible:
+    const: maxim,max86150
+
+  reg:
+    maxItems: 1
+    description: I2C device address, always 0x5E.
+
+  interrupts:
+    maxItems: 1
+    description: |
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready.
+
+  vdd-supply:
+    description: Digital core supply, 1.71 V to 1.89 V.
+
+  leds-supply:
+    description: LED anode supply, 3.0 V to 5.5 V.
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        biosensor@5e {
+            compatible = "maxim,max86150";
+            reg = <0x5e>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            vdd-supply = <&vdd_1v8>;
+            leds-supply = <&vdd_3v3>;
+        };
+    };
Signature · 1 line
2.51.1
#35 lore raw

Re: [PATCH v3 1/3] dt-bindings: iio: health: add maxim,max86150

Quoted 39 lines · Md Shofiqul Islam

Add YAML binding schema for the Maxim MAX86150 combined ECG and PPG biosensor. The device exposes two PPG optical channels (Red and IR LED) for heart rate and SpO2 measurement, and one ECG biopotential channel, all accessible over I2C at up to 400 kHz.

An optional active-low interrupt line connects to the 32-entry hardware FIFO almost-full output. Two optional regulator supplies (vdd for the digital core and leds for the LED anodes) cover boards that require explicit power sequencing.

---
 .../bindings/iio/health/maxim,max86150.yaml   | 67 +++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +17 -0
new file mode 100644
index 000000000000..1bf10fd1a3d2
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/health/maxim,max86150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX86150 ECG and PPG Biosensor
+
+maintainers:
+  - Md Shofiqul Islam <[email protected]>
+
+description: |
+  The MAX86150 is an integrated biosensor SoC that combines:
+    - Two PPG (photoplethysmography) channels: Red LED and IR LED,
+      for heart rate and blood-oxygen saturation (SpO2) measurement.
+    - One ECG (electrocardiogram) channel for biopotential recording.
+

Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml line 27

Quoted 10 lines
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +10 -0
+  The device communicates over I2C at up to 400 kHz and raises an
+  active-low interrupt when the 32-entry hardware FIFO reaches its
+  configurable almost-full threshold.
+
+  Datasheet:
+    https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf
+
+properties:
+  compatible:
+    const: maxim,max86150

ADI would like the prefix for newly added former maxim products to be "adi,".

Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml line 40

Quoted 13 lines
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +13 -0
+
+  reg:
+    maxItems: 1
+    description: I2C device address, always 0x5E.
+
+  interrupts:
+    maxItems: 1
+    description: |
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready.
+
+  vdd-supply:
+    description: Digital core supply, 1.71 V to 1.89 V.

This is a 1.8v supply, the tolerances on it don't need to be mentioned here.

Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml line 43

Quoted 2 lines
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +2 -0
+  leds-supply:
+    description: LED anode supply, 3.0 V to 5.5 V.

Datasheet cites 5v as the max for this, not 5.5v, with a typical value of 3.3v. Makes me think that 3.3 is the intended value, since using a 5v supply could easily fluctuate over the maximum rated value.

Looks like there's more supplies than this?
I see:
Vdd_ana
Vdd_dig
Vref
Vled

Additionally, these supplies should be mandatory.

pw-bot: changes-requested

Quoted 6 lines
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +6 -0
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false

Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml line 59

Quoted 10 lines
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +10 -0
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        biosensor@5e {

Seems to be prior art for calling things like this "heart-rate", although that is somewhat limited compared to what this device can do.

Cheers,
Conor.

Quoted 11 lines
Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file +8 -0
+            compatible = "maxim,max86150";
+            reg = <0x5e>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            vdd-supply = <&vdd_1v8>;
+            leds-supply = <&vdd_3v3>;
+        };
+    };
Signature · 1 line
2.51.1
#36 lore raw

PATCH v3 2/3 iio: health: add MAX86150 ECG and PPG biosensor driver

In reply to Md Shofiqul Islam

The MAX86150 (Maxim/Analog Devices) combines two PPG optical channels (Red/IR LED) and one ECG biopotential channel in a single I2C device with a 32-entry hardware FIFO.

Driver features:
- Three IIO channels: in_intensityred_raw, in_intensityir_raw, in_voltage0_raw
- Triggered buffer path driven by the FIFO almost-full interrupt
- set_trigger_state callback enables/disables the interrupt only while the buffer is active; FIFO is flushed before capture starts
- Per-sample timestamps reconstructed from IRQ capture time and the configured 100 Hz sample period
- regmap_noinc_read() burst reads into a struct-allocated (heap) buffer to satisfy DMA mapping requirements of I2C host controllers
- devm_regulator_get_enable_optional() for the two optional supplies
- Powerdown devm action disables interrupts and asserts SYS_SHDN

---
 drivers/iio/health/Kconfig    |  23 ++
 drivers/iio/health/Makefile   |   1 +
 drivers/iio/health/max86150.c | 651 ++++++++++++++++++++++++++++++++++
 3 files changed, 675 insertions(+)
 create mode 100644 drivers/iio/health/max86150.c
drivers/iio/health/Kconfig +23 -0
index a89f3abf11f4..6496cf55290c 100644
@@ -13,6 +13,7 @@ config AFE4403
 	depends on SPI_MASTER
 	select REGMAP_SPI
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4403
@@ -26,6 +27,7 @@ config AFE4404
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes to choose the Texas Instruments AFE4404
@@ -39,6 +41,7 @@ config MAX30100
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -52,6 +55,7 @@ config MAX30102
 	depends on I2C
 	select REGMAP_I2C
 	select IIO_BUFFER
+	select IIO_TRIGGER
 	select IIO_KFIFO_BUF
 	help
 	  Say Y here to build I2C interface support for the Maxim
@@ -62,4 +66,23 @@ config MAX30102
 
 endmenu
 
+
+config MAX86150
+	tristate "MAX86150 ECG and PPG biosensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGER
+	select IIO_TRIGGERED_BUFFER
+	select REGMAP_I2C
+	help
+	  Say Y here to enable support for the Maxim MAX86150 combined
+	  ECG and photoplethysmography (PPG) biosensor.
+
+	  The driver exposes three IIO channels: two PPG optical channels
+	  (Red and IR LED) for heart rate and SpO2 monitoring, and one
+	  ECG channel for biopotential recording.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called max86150.
+
 endmenu
drivers/iio/health/Makefile +1 -0
index 910817112258..04fc73c58444 100644
@@ -9,3 +9,4 @@ obj-$(CONFIG_AFE4403)		+= afe4403.o
 obj-$(CONFIG_AFE4404)		+= afe4404.o
 obj-$(CONFIG_MAX30100)		+= max30100.o
 obj-$(CONFIG_MAX30102)		+= max30102.o
+obj-$(CONFIG_MAX86150)		+= max86150.o
drivers/iio/health/max86150.c new file +651 -0

651 lines not on this page · raw message

Signature · 1 line
2.51.1
#37 lore raw
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider: - [High] Probe fails unconditionally when optional regulators are absent. - [High] The `fifo_raw` buffer is…

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] Probe fails unconditionally when optional regulators are absent.
- [High] The fifo_raw buffer is not cacheline aligned, violating DMA API rules and risking memory corruption.
- [High] FIFO pointer math cannot distinguish an empty FIFO from an exactly full FIFO, permanently stalling capture.
- [High] Trigger reference leak on error path if triggered buffer setup fails.
- [High] The biosensor is fully powered on and pulsing its LEDs continuously while the driver is bound, causing excessive power drain.
- [Medium] The driver hardcodes IRQF_TRIGGER_FALLING, overriding the device tree interrupt configuration.
- [Medium] The driver lacks a validate_trigger callback, allowing incompatible external software triggers to corrupt FIFO reads.
- [Medium] Per-sample timestamps are incorrectly reconstructed backwards from the most recent sample, introducing load-dependent jitter. --

commit b7b1aa92d36a3970970aa6f40becae60ad1ec8c5
Author: Md Shofiqul Islam <[email protected]>
Subject: iio: health: add MAX86150 ECG and PPG biosensor driver

This commit adds an I2C driver for the MAX86150 ECG and PPG biosensor, exposing three IIO channels. It includes a hardware FIFO triggered buffer path and devm regulator support.

drivers/iio/health/max86150.c struct max86150_data { · line 119

Quoted 11 lines
drivers/iio/health/max86150.c new file +0 -0
@@ -113,8 +113,8 @@ struct max86150_data {
 	struct regmap		*regmap;
 	struct device		*dev;
 	struct iio_trigger	*trig;
 	u32			 sample_period_ns;
 	u8			 fifo_raw[MAX86150_SAMPLE_BYTES];
 	s32 buf[6] __aligned(8);
 };

[Severity: High]
Does this struct layout risk memory corruption during DMA burst reads?

Since data->fifo_raw is passed to regmap_noinc_read(), which may perform DMA burst transfers on certain I2C host controllers, it lacks cacheline alignment.

If the CPU flushes dirty cache lines encompassing the adjacent buf during the DMA transfer, wouldn't the flush clobber the incoming DMA data? Could we add __aligned(IIO_DMA_MINALIGN) to fifo_raw?

[ ... ]

drivers/iio/health/max86150.c static int max86150_read_raw(struct iio_dev *indio_dev, · line 288

Quoted 4 lines
drivers/iio/health/max86150.c new file +0 -0
@@ -286,4 +286,4 @@ static int max86150_read_raw(struct iio_dev *indio_dev,
 static const struct iio_info max86150_iio_info = {
 	.read_raw = max86150_read_raw,
 };

[Severity: Medium]
Should max86150_iio_info implement a validate_trigger callback?

This device relies on its hardware FIFO being correctly flushed and configured only when its own hardware trigger is enabled via .set_trigger_state. Without a validate_trigger check like iio_validate_own_trigger, userspace could bind arbitrary software triggers (like sysfs or hrtimer).

If an external trigger is used, .set_trigger_state is bypassed, the hardware interrupt is never enabled, and wouldn't the driver blindly read unaligned, duplicated, or stale samples from the continuously running FIFO?

[ ... ]

drivers/iio/health/max86150.c static irqreturn_t max86150_trigger_handler(int irq, void *p) · line 378

Quoted 5 lines
drivers/iio/health/max86150.c new file +0 -0
@@ -375,6 +375,6 @@ static irqreturn_t max86150_trigger_handler(int irq, void *p)
 	if (ovf > 0)
 		n_avail = MAX86150_FIFO_DEPTH;
 	else
 		n_avail = (wr_ptr - rd_ptr) & (MAX86150_FIFO_DEPTH - 1);

[Severity: High]
Does this logic correctly handle an exactly full FIFO?

If the FIFO reaches exactly 32 samples (completely full) but has not yet overflowed (ovf == 0), wr_ptr will wrap and equal rd_ptr. This calculation evaluates to n_avail = 0, causing the driver to skip reading any samples.

Because the interrupt is configured as edge-triggered, the line won't assert again until the FIFO drops below the almost-full threshold. Will this permanently stall the triggered buffer capture?

drivers/iio/health/max86150.c static irqreturn_t max86150_trigger_handler(int irq, void *p) · line 389

Quoted 4 lines
drivers/iio/health/max86150.c new file +0 -0
@@ -387,4 +387,4 @@ static irqreturn_t max86150_trigger_handler(int irq, void *p)
 	for (i = 0; i < n_avail; i++) {
 		s64 ts = pf->timestamp -
 			 (s64)(n_avail - 1 - i) * data->sample_period_ns;

[Severity: Medium]
Are the per-sample timestamps aligned correctly?

iio_pollfunc_store_time() records the interrupt capture time in pf->timestamp when the FIFO hits the almost-full watermark (17 samples). If the threaded IRQ handler is delayed by system load, additional samples may accumulate.

This loop assigns pf->timestamp to the newest sample (i = n_avail - 1) instead of the watermark sample, backdating the rest. Doesn't this variable scheduling latency shift all timestamps backwards and corrupt their regular spacing?

[ ... ]

drivers/iio/health/max86150.c static int max86150_chip_init(struct max86150_data *data) · line 447

Quoted 8 lines
drivers/iio/health/max86150.c new file +0 -0
@@ -441,7 +441,7 @@ static int max86150_chip_init(struct max86150_data *data)
 
 	/* PPG: 100 Hz sample rate, 16384 nA ADC full-scale range */
 	ret = regmap_write(data->regmap, MAX86150_REG_PPG_CONFIG1,
 			   FIELD_PREP(MAX86150_PPG_ADC_RGE,
 				      MAX86150_PPG_ADC_RGE_16384) |
 			   FIELD_PREP(MAX86150_PPG_SR,
 				      MAX86150_PPG_SR_100HZ));

[Severity: High]
Is the biosensor intentionally left continuously powered on?

Because the driver doesn't implement IIO buffer setup_ops (e.g., preenable and postdisable) or Runtime PM, the hardware remains active permanently after initialization.

Won't the optical LEDs continuously pulse at 100 Hz (drawing ~50 mA) even when no userspace process is capturing data?

[ ... ]

Quoted 11 lines
drivers/iio/health/max86150.c new file +0 -0
@@ -528,10 +528,10 @@ static int max86150_probe(struct i2c_client *client)
 	 * DT regulator node.
 	 */
 	ret = devm_regulator_get_enable_optional(&client->dev, "vdd");
 	if (ret)
 		return dev_err_probe(&client->dev, ret,
 				     "Failed to get/enable vdd supply\n");
 
 	ret = devm_regulator_get_enable_optional(&client->dev, "leds");
 	if (ret)
 		return dev_err_probe(&client->dev, ret,
Quoted 1 line

"Failed to get/enable leds supply\n");

[Severity: High]
Does the driver fail to probe on valid configurations without regulators?

If a board supplies power via fixed rails and omits the DT regulator nodes, devm_regulator_get_enable_optional() correctly returns -ENODEV.

By directly forwarding ret to dev_err_probe() and aborting, doesn't this prevent the driver from binding on hardware that intentionally omits these optional regulators?

[ ... ]

drivers/iio/health/max86150.c static int max86150_probe(struct i2c_client *client) · line 578

Quoted 5 lines
drivers/iio/health/max86150.c new file +0 -0
@@ -575,6 +575,6 @@ static int max86150_probe(struct i2c_client *client)
 		ret = devm_request_irq(&client->dev, client->irq,
 				       iio_trigger_generic_data_rdy_poll,
 				       IRQF_TRIGGER_FALLING,
 				       "max86150", data->trig);

[Severity: Medium]
Is it safe to hardcode IRQF_TRIGGER_FALLING here?

Statically setting the IRQF_TRIGGER_FALLING flag overrides any trigger type specified by the firmware in the device tree's interrupts property.

Will this break compatibility with board layouts that use an inverter or level-shifter on the interrupt line? Should it pass 0 or IRQF_ONESHOT instead to allow the core to apply the DT-defined polarity?

[ ... ]

Quoted 10 lines
drivers/iio/health/max86150.c new file +0 -0
@@ -602,8 +602,8 @@ static int max86150_probe(struct i2c_client *client)
 		 * the iio_dev is freed.  No additional devm action needed.
 		 */
 		indio_dev->trig = iio_trigger_get(data->trig);
 	}
 
 	ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
 					      iio_pollfunc_store_time,
 					      max86150_trigger_handler,

NULL);

[Severity: High]
Is there a trigger reference leak if the buffer setup fails?

iio_trigger_get() increments the default trigger's reference count. The IIO core only balances this in iio_dev_release() if the INDIO_ALL_TRIGGERED_MODES mask flag is set in indio_dev->modes.

Since this flag is set inside devm_iio_triggered_buffer_setup(), if the buffer setup fails, the flag is never set. Consequently, the devm cleanup path will skip iio_device_unregister_trigger_consumer(). Doesn't this permanently leak the trigger device memory and its module reference?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#38 lore raw

PATCH v3 3/3 MAINTAINERS: add entry for MAX86150 IIO health driver

In reply to Md Shofiqul Islam

Add a MAINTAINERS entry for the new MAX86150 ECG and PPG biosensor driver, covering both the driver and its DT binding schema.

---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)
MAINTAINERS +7 -0
index 3115538ce829..a441ec44bb27 100644
@@ -15863,6 +15863,13 @@ S:	Supported
 F:	Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
 F:	drivers/power/supply/max77976_charger.c
 
+MAXIM MAX86150 ECG AND PPG BIOSENSOR DRIVER
+M:	Md Shofiqul Islam <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
+F:	drivers/iio/health/max86150.c
+
 MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
 M:	Krzysztof Kozlowski <[email protected]>
 L:	[email protected]
Signature · 1 line
2.51.1