* [PATCH 0/1] iio: health: add MAX86150 ECG and PPG biosensor driver
@ 2026-06-23 14:01 Md Shofiqul Islam
2026-06-23 14:01 ` [PATCH 1/1] " Md Shofiqul Islam
` (2 more replies)
0 siblings, 3 replies; 20+ messages in thread
From: Md Shofiqul Islam @ 2026-06-23 14:01 UTC (permalink / raw)
To: linux-iio
Cc: jic23, dlechner, nuno.sa, robh, krzk+dt, devicetree,
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 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
--
2.51.1
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH 1/1] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 14:01 [PATCH 0/1] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam @ 2026-06-23 14:01 ` Md Shofiqul Islam 2026-06-23 14:14 ` sashiko-bot ` (2 more replies) 2026-06-23 15:55 ` [PATCH v2 0/3] " Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2 siblings, 3 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 14:01 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, robh, krzk+dt, devicetree, 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. 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. Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- .../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 diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file mode 100755 index 000000000000..1bf10fd1a3d2 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml @@ -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 <shofiqtest@gmail.com> + +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>; + }; + }; diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig index a89f3abf11f4..6496cf55290c 100644 --- a/drivers/iio/health/Kconfig +++ b/drivers/iio/health/Kconfig @@ -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 diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile index 910817112258..04fc73c58444 100644 --- a/drivers/iio/health/Makefile +++ b/drivers/iio/health/Makefile @@ -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 diff --git a/drivers/iio/health/max86150.c b/drivers/iio/health/max86150.c new file mode 100755 index 000000000000..3cde4af6650b --- /dev/null +++ b/drivers/iio/health/max86150.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAX86150 combined ECG and PPG biosensor driver + * + * Copyright (C) 2026 Md Shofiqul Islam <shofiqtest@gmail.com> + * + * The MAX86150 integrates two PPG optical channels (Red/IR LED) and one + * ECG biopotential channel in a single I2C device. Data is captured + * through a 32-entry hardware FIFO with a configurable almost-full + * interrupt, making it well-suited for continuous monitoring with a + * low-power host. + * + * Datasheet: + * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/i2c.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> +#include <linux/module.h> +#include <linux/regmap.h> + +/* Register addresses */ +#define MAX86150_REG_INT_STATUS1 0x00 +#define MAX86150_REG_INT_STATUS2 0x01 +#define MAX86150_REG_INT_ENABLE1 0x02 +#define MAX86150_REG_INT_ENABLE2 0x03 +#define MAX86150_REG_FIFO_WR_PTR 0x04 +#define MAX86150_REG_OVF_COUNTER 0x05 +#define MAX86150_REG_FIFO_RD_PTR 0x06 +#define MAX86150_REG_FIFO_DATA 0x07 +#define MAX86150_REG_FIFO_CONFIG 0x08 +#define MAX86150_REG_FIFO_DCTRL1 0x09 /* FD1[3:0] FD2[7:4] */ +#define MAX86150_REG_FIFO_DCTRL2 0x0A /* FD3[3:0] FD4[7:4] */ +#define MAX86150_REG_SYS_CTRL 0x0D +#define MAX86150_REG_PPG_CONFIG1 0x10 +#define MAX86150_REG_PPG_CONFIG2 0x11 +#define MAX86150_REG_LED1_PA 0x14 /* Red LED pulse amplitude */ +#define MAX86150_REG_LED2_PA 0x15 /* IR LED pulse amplitude */ +#define MAX86150_REG_ECG_CONFIG1 0x3C +#define MAX86150_REG_ECG_CONFIG3 0x3E +#define MAX86150_REG_PART_ID 0xFF + +/* Field masks */ +#define MAX86150_PART_ID_VAL 0x1E + +/* INT_STATUS1 / INT_ENABLE1 */ +#define MAX86150_INT_A_FULL BIT(7) /* FIFO almost full */ +#define MAX86150_INT_PPG_RDY BIT(6) /* new PPG sample ready */ + +/* SYS_CTRL */ +#define MAX86150_SYS_SHDN BIT(1) +#define MAX86150_SYS_RESET BIT(0) + +/* FIFO_CONFIG */ +#define MAX86150_FIFO_SMP_AVE GENMASK(7, 5) +#define MAX86150_FIFO_ROLLOVER_EN BIT(4) +#define MAX86150_FIFO_A_FULL GENMASK(3, 0) + +/* FIFO slot data-type codes */ +#define MAX86150_FD_NONE 0x0 +#define MAX86150_FD_LED1 0x1 /* Red PPG */ +#define MAX86150_FD_LED2 0x2 /* IR PPG */ +#define MAX86150_FD_ECG 0x9 +#define MAX86150_FIFO_FD1 GENMASK(3, 0) +#define MAX86150_FIFO_FD2 GENMASK(7, 4) +#define MAX86150_FIFO_FD3 GENMASK(3, 0) +#define MAX86150_FIFO_FD4 GENMASK(7, 4) + +/* PPG_CONFIG1 */ +#define MAX86150_PPG_ADC_RGE GENMASK(7, 6) +#define MAX86150_PPG_SR GENMASK(5, 1) + +/* Geometry */ +#define MAX86150_FIFO_DEPTH 32 +#define MAX86150_BYTES_PER_SLOT 3 /* 24-bit word per slot */ +#define MAX86150_NUM_SLOTS 3 /* Red, IR, ECG */ +#define MAX86150_SAMPLE_BYTES (MAX86150_NUM_SLOTS * MAX86150_BYTES_PER_SLOT) + +/* Default hardware configuration */ +#define MAX86150_LED_PA_DEFAULT 0x3F /* ~50 mA */ +#define MAX86150_PPG_SR_100HZ 4 /* PPG_SR field value for 100 Hz */ +#define MAX86150_PPG_ADC_RGE_16384 2 /* 16384 nA full scale */ +/* Fire A_FULL when 17 slots remain (32 - 15 = 17 samples in FIFO) */ +#define MAX86150_FIFO_A_FULL_VAL 15 + +/* Scan element indices */ +enum max86150_scan_idx { + MAX86150_IDX_PPG_RED = 0, + MAX86150_IDX_PPG_IR = 1, + MAX86150_IDX_ECG = 2, + MAX86150_IDX_TS, +}; + +/** + * 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 + * @buf: DMA-safe capture buffer: 3 x s32 samples + s64 timestamp. + * __aligned(8) satisfies iio_push_to_buffers_with_timestamp(). + */ +struct max86150_data { + struct regmap *regmap; + struct device *dev; + struct iio_trigger *trig; + s32 buf[4] __aligned(8); +}; + +/* IIO channel specification */ + +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), + .scan_index = MAX86150_IDX_PPG_RED, + .scan_type = { + .sign = 'u', + .realbits = 19, + .storagebits = 32, + .endianness = IIO_LE, + }, + }, + { + /* PPG IR LED - optical intensity, 19-bit unsigned */ + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = MAX86150_IDX_PPG_IR, + .scan_type = { + .sign = 'u', + .realbits = 19, + .storagebits = 32, + .endianness = IIO_LE, + }, + }, + { + /* ECG biopotential - voltage, 18-bit signed two's complement */ + .type = IIO_VOLTAGE, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = MAX86150_IDX_ECG, + .scan_type = { + .sign = 's', + .realbits = 18, + .storagebits = 32, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(MAX86150_IDX_TS), +}; + +/* Regmap configuration */ + +static const struct regmap_config max86150_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX86150_REG_PART_ID, +}; + +/* FIFO helper */ + +/** + * 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) +{ + u8 buf[MAX86150_SAMPLE_BYTES]; /* 9 bytes */ + int ret; + + ret = regmap_noinc_read(data->regmap, MAX86150_REG_FIFO_DATA, + buf, sizeof(buf)); + if (ret) + return ret; + + /* Bytes [0..2]: PPG Red - 19-bit value in bits [18:0] */ + *ppg_red = (u32)(buf[0] & 0x07) << 16 | (u32)buf[1] << 8 | buf[2]; + + /* Bytes [3..5]: PPG IR - same format */ + *ppg_ir = (u32)(buf[3] & 0x07) << 16 | (u32)buf[4] << 8 | buf[5]; + + /* Bytes [6..8]: ECG - 18-bit signed, sign-extend to s32 */ + *ecg = sign_extend32((u32)(buf[6] & 0x03) << 16 | + (u32)buf[7] << 8 | buf[8], 17); + + return 0; +} + +/* IIO read_raw */ + +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: + /* + * 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); + if (ret) + return ret; + + /* 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); + 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; + + 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; + + default: + return -EINVAL; + } +} + +static const struct iio_info max86150_iio_info = { + .read_raw = max86150_read_raw, +}; + +/* Triggered buffer */ + +/** + * 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. + */ +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) + 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); + + 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; + + iio_push_to_buffers_with_timestamp(idev, data->buf, + iio_get_time_ns(idev)); + } + +done: + iio_trigger_notify_done(idev->trig); + return IRQ_HANDLED; +} + +/* Chip initialisation */ + +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); + + /* + * 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; + + ret = regmap_write(data->regmap, MAX86150_REG_LED2_PA, + MAX86150_LED_PA_DEFAULT); + if (ret) + return ret; + + /* Enable FIFO almost-full interrupt only */ + return regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1, + MAX86150_INT_A_FULL); +} + +/* Probe */ + +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); + 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"); + + 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"); + + 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) { + 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); + if (ret) + return dev_err_probe(&client->dev, ret, + "Cannot request IRQ %d\n", + client->irq); + + 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); + } + + 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"); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +/* I2C driver table */ + +static const struct i2c_device_id max86150_id[] = { + { "max86150" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max86150_id); + +static const struct of_device_id max86150_of_match[] = { + { .compatible = "maxim,max86150" }, + { } +}; +MODULE_DEVICE_TABLE(of, max86150_of_match); + +static struct i2c_driver max86150_driver = { + .driver = { + .name = "max86150", + .of_match_table = max86150_of_match, + }, + .probe = max86150_probe, + .id_table = max86150_id, +}; +module_i2c_driver(max86150_driver); + +MODULE_AUTHOR("Md Shofiqul Islam <shofiqtest@gmail.com>"); +MODULE_DESCRIPTION("MAX86150 ECG and PPG biosensor driver"); +MODULE_LICENSE("GPL"); -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 1/1] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 14:01 ` [PATCH 1/1] " Md Shofiqul Islam @ 2026-06-23 14:14 ` sashiko-bot 2026-06-23 14:38 ` Joshua Crofts 2026-06-23 15:38 ` Krzysztof Kozlowski 2 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-06-23 14:14 UTC (permalink / raw) To: Md Shofiqul Islam; +Cc: robh, conor+dt, devicetree 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 <shofiqtest@gmail.com> 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. > diff --git a/drivers/iio/health/max86150.c b/drivers/iio/health/max86150.c > new file mode 100755 > index 0000000000000..3cde4af6650b7 > --- /dev/null > +++ b/drivers/iio/health/max86150.c [ ... ] > +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. [ ... ] > +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), > + .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? [ ... ] > +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: > + /* > + * 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? [ ... ] > +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) > + 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. > + > + 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. [ ... ] > +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); [ ... ] > + /* 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. [ ... ] > +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); > + 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? [ ... ] > + 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. > + if (ret) > + return dev_err_probe(&client->dev, ret, > + "Cannot request IRQ %d\n", > + client->irq); > + > + 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. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260623140113.12574-2-shofiqtest@gmail.com?part=1 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/1] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 14:01 ` [PATCH 1/1] " Md Shofiqul Islam 2026-06-23 14:14 ` sashiko-bot @ 2026-06-23 14:38 ` Joshua Crofts 2026-06-23 15:38 ` Krzysztof Kozlowski 2 siblings, 0 replies; 20+ messages in thread From: Joshua Crofts @ 2026-06-23 14:38 UTC (permalink / raw) To: Md Shofiqul Islam Cc: linux-iio, jic23, dlechner, nuno.sa, robh, krzk+dt, devicetree On Tue, 23 Jun 2026 17:01:13 +0300 Md Shofiqul Islam <shofiqtest@gmail.com> wrote: > 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. > > Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> > --- > .../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 > > diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml > new file mode 100755 > index 000000000000..1bf10fd1a3d2 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml > @@ -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 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/1] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 14:01 ` [PATCH 1/1] " Md Shofiqul Islam 2026-06-23 14:14 ` sashiko-bot 2026-06-23 14:38 ` Joshua Crofts @ 2026-06-23 15:38 ` Krzysztof Kozlowski 2 siblings, 0 replies; 20+ messages in thread From: Krzysztof Kozlowski @ 2026-06-23 15:38 UTC (permalink / raw) To: Md Shofiqul Islam, linux-iio Cc: jic23, dlechner, nuno.sa, robh, krzk+dt, devicetree On 23/06/2026 16:01, Md Shofiqul Islam wrote: > 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. > > Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> 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 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 14:01 [PATCH 0/1] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 14:01 ` [PATCH 1/1] " Md Shofiqul Islam @ 2026-06-23 15:55 ` Md Shofiqul Islam 2026-06-23 15:55 ` [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam ` (3 more replies) 2026-06-23 16:43 ` [PATCH v3 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2 siblings, 4 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 15:55 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, robh, krzk+dt, conor+dt, devicetree, joshua.crofts1, 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 -- 2.51.1 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150 2026-06-23 15:55 ` [PATCH v2 0/3] " Md Shofiqul Islam @ 2026-06-23 15:55 ` Md Shofiqul Islam 2026-06-23 16:02 ` sashiko-bot 2026-06-23 15:55 ` [PATCH v2 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam ` (2 subsequent siblings) 3 siblings, 1 reply; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 15:55 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, robh, krzk+dt, conor+dt, devicetree, joshua.crofts1, Md Shofiqul Islam 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. Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- .../bindings/iio/health/maxim,max86150.yaml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100755 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file mode 100755 index 000000000000..1bf10fd1a3d2 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml @@ -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 <shofiqtest@gmail.com> + +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>; + }; + }; -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150 2026-06-23 15:55 ` [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam @ 2026-06-23 16:02 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-06-23 16:02 UTC (permalink / raw) To: Md Shofiqul Islam; +Cc: robh, devicetree, conor+dt 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 <shofiqtest@gmail.com> 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. > diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml > 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? [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260623155556.13701-1-shofiqtest@gmail.com?part=1 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 15:55 ` [PATCH v2 0/3] " Md Shofiqul Islam 2026-06-23 15:55 ` [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam @ 2026-06-23 15:55 ` Md Shofiqul Islam 2026-06-23 16:12 ` sashiko-bot 2026-06-23 15:55 ` [PATCH v2 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 3 siblings, 1 reply; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 15:55 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, robh, krzk+dt, conor+dt, devicetree, joshua.crofts1, 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. Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- 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 diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig index a89f3abf11f4..6496cf55290c 100644 --- a/drivers/iio/health/Kconfig +++ b/drivers/iio/health/Kconfig @@ -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 diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile index 910817112258..04fc73c58444 100644 --- a/drivers/iio/health/Makefile +++ b/drivers/iio/health/Makefile @@ -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 diff --git a/drivers/iio/health/max86150.c b/drivers/iio/health/max86150.c new file mode 100755 index 000000000000..70c51ccb4672 --- /dev/null +++ b/drivers/iio/health/max86150.c @@ -0,0 +1,612 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAX86150 combined ECG and PPG biosensor driver + * + * Copyright (C) 2026 Md Shofiqul Islam <shofiqtest@gmail.com> + * + * The MAX86150 integrates two PPG optical channels (Red/IR LED) and one + * ECG biopotential channel in a single I2C device. Data is captured + * through a 32-entry hardware FIFO with a configurable almost-full + * interrupt, making it well-suited for continuous monitoring with a + * low-power host. + * + * Datasheet: + * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/i2c.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> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +/* Register addresses */ +#define MAX86150_REG_INT_STATUS1 0x00 +#define MAX86150_REG_INT_STATUS2 0x01 +#define MAX86150_REG_INT_ENABLE1 0x02 +#define MAX86150_REG_INT_ENABLE2 0x03 +#define MAX86150_REG_FIFO_WR_PTR 0x04 +#define MAX86150_REG_OVF_COUNTER 0x05 +#define MAX86150_REG_FIFO_RD_PTR 0x06 +#define MAX86150_REG_FIFO_DATA 0x07 +#define MAX86150_REG_FIFO_CONFIG 0x08 +#define MAX86150_REG_FIFO_DCTRL1 0x09 /* FD1[3:0] FD2[7:4] */ +#define MAX86150_REG_FIFO_DCTRL2 0x0A /* FD3[3:0] FD4[7:4] */ +#define MAX86150_REG_SYS_CTRL 0x0D +#define MAX86150_REG_PPG_CONFIG1 0x10 +#define MAX86150_REG_PPG_CONFIG2 0x11 +#define MAX86150_REG_LED1_PA 0x14 /* Red LED pulse amplitude */ +#define MAX86150_REG_LED2_PA 0x15 /* IR LED pulse amplitude */ +#define MAX86150_REG_ECG_CONFIG1 0x3C +#define MAX86150_REG_ECG_CONFIG3 0x3E +#define MAX86150_REG_PART_ID 0xFF + +/* Field masks */ +#define MAX86150_PART_ID_VAL 0x1E + +/* INT_STATUS1 / INT_ENABLE1 */ +#define MAX86150_INT_A_FULL BIT(7) /* FIFO almost full */ +#define MAX86150_INT_PPG_RDY BIT(6) /* new PPG sample ready */ + +/* SYS_CTRL */ +#define MAX86150_SYS_SHDN BIT(1) +#define MAX86150_SYS_RESET BIT(0) + +/* FIFO_CONFIG */ +#define MAX86150_FIFO_SMP_AVE GENMASK(7, 5) +#define MAX86150_FIFO_ROLLOVER_EN BIT(4) +#define MAX86150_FIFO_A_FULL GENMASK(3, 0) + +/* FIFO slot data-type codes */ +#define MAX86150_FD_NONE 0x0 +#define MAX86150_FD_LED1 0x1 /* Red PPG */ +#define MAX86150_FD_LED2 0x2 /* IR PPG */ +#define MAX86150_FD_ECG 0x9 +#define MAX86150_FIFO_FD1 GENMASK(3, 0) +#define MAX86150_FIFO_FD2 GENMASK(7, 4) +#define MAX86150_FIFO_FD3 GENMASK(3, 0) +#define MAX86150_FIFO_FD4 GENMASK(7, 4) + +/* PPG_CONFIG1 */ +#define MAX86150_PPG_ADC_RGE GENMASK(7, 6) +#define MAX86150_PPG_SR GENMASK(5, 1) + +/* Geometry */ +#define MAX86150_FIFO_DEPTH 32 +#define MAX86150_BYTES_PER_SLOT 3 /* 24-bit word per slot */ +#define MAX86150_NUM_SLOTS 3 /* Red, IR, ECG */ +#define MAX86150_SAMPLE_BYTES (MAX86150_NUM_SLOTS * MAX86150_BYTES_PER_SLOT) + +/* Default hardware configuration */ +#define MAX86150_LED_PA_DEFAULT 0x3F /* ~50 mA */ +#define MAX86150_PPG_SR_100HZ 4 /* PPG_SR field value for 100 Hz */ +#define MAX86150_PPG_ADC_RGE_16384 2 /* 16384 nA full scale */ +/* Fire A_FULL when 17 slots remain (32 - 15 = 17 samples in FIFO) */ +#define MAX86150_FIFO_A_FULL_VAL 15 + +/* Scan element indices */ +enum max86150_scan_idx { + MAX86150_IDX_PPG_RED = 0, + MAX86150_IDX_PPG_IR = 1, + MAX86150_IDX_ECG = 2, + MAX86150_IDX_TS, +}; + +/** + * 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 + * @buf: DMA-safe capture 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; + struct iio_trigger *trig; + s32 buf[6] __aligned(8); +}; + +/* IIO channel specification */ + +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), + .scan_index = MAX86150_IDX_PPG_RED, + .scan_type = { + .sign = 'u', + .realbits = 19, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + /* PPG IR LED - optical intensity, 19-bit unsigned */ + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = MAX86150_IDX_PPG_IR, + .scan_type = { + .sign = 'u', + .realbits = 19, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + /* ECG biopotential - voltage, 18-bit signed two's complement */ + .type = IIO_VOLTAGE, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = MAX86150_IDX_ECG, + .scan_type = { + .sign = 's', + .realbits = 18, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(MAX86150_IDX_TS), +}; + +/* Regmap configuration */ + +static const struct regmap_config max86150_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX86150_REG_PART_ID, +}; + +/* FIFO helper */ + +/** + * 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) +{ + u8 buf[MAX86150_SAMPLE_BYTES]; /* 9 bytes */ + int ret; + + ret = regmap_noinc_read(data->regmap, MAX86150_REG_FIFO_DATA, + buf, sizeof(buf)); + if (ret) + return ret; + + /* Bytes [0..2]: PPG Red - 19-bit value in bits [18:0] */ + *ppg_red = (u32)(buf[0] & 0x07) << 16 | (u32)buf[1] << 8 | buf[2]; + + /* Bytes [3..5]: PPG IR - same format */ + *ppg_ir = (u32)(buf[3] & 0x07) << 16 | (u32)buf[4] << 8 | buf[5]; + + /* Bytes [6..8]: ECG - 18-bit signed, sign-extend to s32 */ + *ecg = sign_extend32((u32)(buf[6] & 0x03) << 16 | + (u32)buf[7] << 8 | buf[8], 17); + + return 0; +} + +/* IIO read_raw */ + +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: reset FIFO pointers so the next sample + * read is one that arrived after this call. + */ + 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) { + iio_device_release_direct(indio_dev); + return ret; + } + + /* 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); + 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; + + 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; + + default: + return -EINVAL; + } +} + +static const struct iio_info max86150_iio_info = { + .read_raw = max86150_read_raw, +}; + +/* Trigger */ + +/* + * Enable or disable the FIFO almost-full interrupt when the IIO triggered + * buffer is started or stopped. Keeping the interrupt masked until capture + * is active prevents a pre-existing FIFO fill from asserting the line before + * the handler is ready to clear it. + */ +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); +} + +static const struct iio_trigger_ops max86150_trigger_ops = { + .set_trigger_state = max86150_set_trigger_state, +}; + +/* Triggered buffer */ + +/** + * 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; + struct max86150_data *data = iio_priv(idev); + unsigned int status, wr_ptr, rd_ptr, ovf, available; + u32 ppg_red, ppg_ir; + s32 ecg; + int ret, j; + + /* + * 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; + + /* + * Read OVF_COUNTER to distinguish a full FIFO from an empty one: + * both produce wr_ptr == rd_ptr after the pointers wrap around. + * If OVF_COUNTER is non-zero the FIFO has saturated; treat all 32 + * slots as available so we drain the entire FIFO and clear the + * interrupt condition. + */ + ret = regmap_read(data->regmap, MAX86150_REG_OVF_COUNTER, &ovf); + if (ret) + goto done; + + if (ovf > 0) + available = MAX86150_FIFO_DEPTH; + else + available = (wr_ptr - rd_ptr) & (MAX86150_FIFO_DEPTH - 1); + + while (available--) { + ret = max86150_read_one_sample(data, &ppg_red, &ppg_ir, &ecg); + if (ret) + break; + + /* + * 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, + iio_get_time_ns(idev)); + } + +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); +} + +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); + + /* + * 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; + + return regmap_write(data->regmap, MAX86150_REG_LED2_PA, + MAX86150_LED_PA_DEFAULT); + /* + * Do NOT enable the A_FULL interrupt here. It is enabled and + * disabled via max86150_set_trigger_state() when the IIO triggered + * buffer is started and stopped, which prevents the edge-triggered + * interrupt line from asserting before the handler is registered. + */ +} + +static void max86150_trigger_put(void *trig) +{ + iio_trigger_put(trig); +} + +/* Probe */ + +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); + data->dev = &client->dev; + + /* + * 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"); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to get/enable vdd supply\n"); + + ret = devm_regulator_get_enable(&client->dev, "leds"); + if (ret) + 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) { + 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); + + /* + * iio_trigger_generic_data_rdy_poll is a hard-IRQ handler + * that calls iio_trigger_poll() and returns IRQ_HANDLED. + * No IRQF_ONESHOT is needed for this edge-triggered line. + */ + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_FALLING, + "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); + if (ret) + return dev_err_probe(&client->dev, ret, + "Cannot register trigger\n"); + + indio_dev->trig = iio_trigger_get(data->trig); + ret = devm_add_action_or_reset(&client->dev, + max86150_trigger_put, + data->trig); + if (ret) + return ret; + } + + 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"); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +/* I2C driver table */ + +static const struct i2c_device_id max86150_id[] = { + { "max86150" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max86150_id); + +static const struct of_device_id max86150_of_match[] = { + { .compatible = "maxim,max86150" }, + { } +}; +MODULE_DEVICE_TABLE(of, max86150_of_match); + +static struct i2c_driver max86150_driver = { + .driver = { + .name = "max86150", + .of_match_table = max86150_of_match, + }, + .probe = max86150_probe, + .id_table = max86150_id, +}; +module_i2c_driver(max86150_driver); + +MODULE_AUTHOR("Md Shofiqul Islam <shofiqtest@gmail.com>"); +MODULE_DESCRIPTION("MAX86150 ECG and PPG biosensor driver"); +MODULE_LICENSE("GPL"); -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 15:55 ` [PATCH v2 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam @ 2026-06-23 16:12 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-06-23 16:12 UTC (permalink / raw) To: Md Shofiqul Islam; +Cc: devicetree, robh, conor+dt 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 <shofiqtest@gmail.com> 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. > diff --git a/drivers/iio/health/max86150.c b/drivers/iio/health/max86150.c > new file mode 100755 > index 0000000000000..70c51ccb4672b > --- /dev/null > +++ b/drivers/iio/health/max86150.c [ ... ] > +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. [ ... ] > +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. [ ... ] > + 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. [ ... ] > +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. [ ... ] > + /* > + * 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; [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. > + > + 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? [ ... ] > + /* > + * 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? [ ... ] > + 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. > + 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. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260623155556.13701-1-shofiqtest@gmail.com?part=2 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver 2026-06-23 15:55 ` [PATCH v2 0/3] " Md Shofiqul Islam 2026-06-23 15:55 ` [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam 2026-06-23 15:55 ` [PATCH v2 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam @ 2026-06-23 15:55 ` Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 3 siblings, 0 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 15:55 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, robh, krzk+dt, conor+dt, devicetree, joshua.crofts1, Md Shofiqul Islam Add maintainer entry for the new MAX86150 ECG and PPG biosensor driver. Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3115538ce829..a441ec44bb27 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 <shofiqtest@gmail.com> +L: linux-iio@vger.kernel.org +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 <krzk@kernel.org> L: linux-pm@vger.kernel.org -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v4 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 15:55 ` [PATCH v2 0/3] " Md Shofiqul Islam ` (2 preceding siblings ...) 2026-06-23 15:55 ` [PATCH v2 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver Md Shofiqul Islam @ 2026-06-23 17:45 ` Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam ` (2 more replies) 3 siblings, 3 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 17:45 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, lars, devicetree, linux-kernel, 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. Link: https://lore.kernel.org/linux-iio/20260623155556.13701-1-shofiqtest@gmail.com/ 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 -- 2.49.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v4 1/3] dt-bindings: iio: health: add maxim,max86150 2026-06-23 17:45 ` [PATCH v4 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam @ 2026-06-23 17:45 ` Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 17:46 ` [PATCH v4 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver Md Shofiqul Islam 2 siblings, 0 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 17:45 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, lars, devicetree, linux-kernel, 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. Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- .../bindings/iio/health/maxim,max86150.yaml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file mode 100644 index 000000000000..1bf10fd1a3d2 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml @@ -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 <shofiqtest@gmail.com> + +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>; + }; + }; -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v4 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 17:45 ` [PATCH v4 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam @ 2026-06-23 17:45 ` Md Shofiqul Islam 2026-06-23 17:46 ` [PATCH v4 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver Md Shofiqul Islam 2 siblings, 0 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 17:45 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, lars, devicetree, linux-kernel, 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 Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- 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 diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig index a89f3abf11f4..6496cf55290c 100644 --- a/drivers/iio/health/Kconfig +++ b/drivers/iio/health/Kconfig @@ -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 diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile index 910817112258..04fc73c58444 100644 --- a/drivers/iio/health/Makefile +++ b/drivers/iio/health/Makefile @@ -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 diff --git a/drivers/iio/health/max86150.c b/drivers/iio/health/max86150.c new file mode 100644 index 000000000000..0140c93bd4ec --- /dev/null +++ b/drivers/iio/health/max86150.c @@ -0,0 +1,677 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAX86150 combined ECG and PPG biosensor driver + * + * Copyright (C) 2026 Md Shofiqul Islam <shofiqtest@gmail.com> + * + * The MAX86150 integrates two PPG optical channels (Red/IR LED) and one + * ECG biopotential channel in a single I2C device. Data is captured + * through a 32-entry hardware FIFO with a configurable almost-full + * interrupt, making it well-suited for continuous monitoring with a + * low-power host. + * + * Datasheet: + * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#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> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +/* Register addresses */ +#define MAX86150_REG_INT_STATUS1 0x00 +#define MAX86150_REG_INT_STATUS2 0x01 +#define MAX86150_REG_INT_ENABLE1 0x02 +#define MAX86150_REG_INT_ENABLE2 0x03 +#define MAX86150_REG_FIFO_WR_PTR 0x04 +#define MAX86150_REG_OVF_COUNTER 0x05 +#define MAX86150_REG_FIFO_RD_PTR 0x06 +#define MAX86150_REG_FIFO_DATA 0x07 +#define MAX86150_REG_FIFO_CONFIG 0x08 +#define MAX86150_REG_FIFO_DCTRL1 0x09 /* FD1[3:0] FD2[7:4] */ +#define MAX86150_REG_FIFO_DCTRL2 0x0A /* FD3[3:0] FD4[7:4] */ +#define MAX86150_REG_SYS_CTRL 0x0D +#define MAX86150_REG_PPG_CONFIG1 0x10 +#define MAX86150_REG_PPG_CONFIG2 0x11 +#define MAX86150_REG_LED1_PA 0x14 /* Red LED pulse amplitude */ +#define MAX86150_REG_LED2_PA 0x15 /* IR LED pulse amplitude */ +#define MAX86150_REG_ECG_CONFIG1 0x3C +#define MAX86150_REG_ECG_CONFIG3 0x3E +#define MAX86150_REG_PART_ID 0xFF + +/* Field masks */ +#define MAX86150_PART_ID_VAL 0x1E + +/* INT_STATUS1 / INT_ENABLE1 */ +#define MAX86150_INT_A_FULL BIT(7) /* FIFO almost full */ +#define MAX86150_INT_PPG_RDY BIT(6) /* new PPG sample ready */ + +/* SYS_CTRL */ +#define MAX86150_SYS_SHDN BIT(1) +#define MAX86150_SYS_RESET BIT(0) + +/* FIFO_CONFIG */ +#define MAX86150_FIFO_SMP_AVE GENMASK(7, 5) +#define MAX86150_FIFO_ROLLOVER_EN BIT(4) +#define MAX86150_FIFO_A_FULL GENMASK(3, 0) + +/* FIFO slot data-type codes */ +#define MAX86150_FD_NONE 0x0 +#define MAX86150_FD_LED1 0x1 /* Red PPG */ +#define MAX86150_FD_LED2 0x2 /* IR PPG */ +#define MAX86150_FD_ECG 0x9 +#define MAX86150_FIFO_FD1 GENMASK(3, 0) +#define MAX86150_FIFO_FD2 GENMASK(7, 4) +#define MAX86150_FIFO_FD3 GENMASK(3, 0) +#define MAX86150_FIFO_FD4 GENMASK(7, 4) + +/* PPG_CONFIG1 */ +#define MAX86150_PPG_ADC_RGE GENMASK(7, 6) +#define MAX86150_PPG_SR GENMASK(5, 1) + +/* Geometry */ +#define MAX86150_FIFO_DEPTH 32 +#define MAX86150_BYTES_PER_SLOT 3 /* 24-bit word per slot */ +#define MAX86150_NUM_SLOTS 3 /* Red, IR, ECG */ +#define MAX86150_SAMPLE_BYTES (MAX86150_NUM_SLOTS * MAX86150_BYTES_PER_SLOT) +/* Samples present in the FIFO when the A_FULL interrupt fires */ +#define MAX86150_A_FULL_SAMPLES (MAX86150_FIFO_DEPTH - MAX86150_FIFO_A_FULL_VAL) + +/* Default hardware configuration */ +#define MAX86150_LED_PA_DEFAULT 0x3F /* ~50 mA */ +#define MAX86150_PPG_SR_100HZ 4 /* PPG_SR field value for 100 Hz */ +#define MAX86150_PPG_ADC_RGE_16384 2 /* 16384 nA full scale */ +/* Fire A_FULL when 17 slots remain (32 - 15 = 17 samples in FIFO) */ +#define MAX86150_FIFO_A_FULL_VAL 15 + +/* Scan element indices */ +enum max86150_scan_idx { + MAX86150_IDX_PPG_RED = 0, + MAX86150_IDX_PPG_IR = 1, + MAX86150_IDX_ECG = 2, + MAX86150_IDX_TS, +}; + +/** + * 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; + * 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; + struct iio_trigger *trig; + u32 sample_period_ns; + u8 fifo_raw[MAX86150_SAMPLE_BYTES] __aligned(ARCH_DMA_MINALIGN); + s32 buf[6] __aligned(8); +}; + +/* IIO channel specification */ + +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), + .scan_index = MAX86150_IDX_PPG_RED, + .scan_type = { + .sign = 'u', + .realbits = 19, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + /* PPG IR LED - optical intensity, 19-bit unsigned */ + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = MAX86150_IDX_PPG_IR, + .scan_type = { + .sign = 'u', + .realbits = 19, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + /* ECG biopotential - voltage, 18-bit signed two's complement */ + .type = IIO_VOLTAGE, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = MAX86150_IDX_ECG, + .scan_type = { + .sign = 's', + .realbits = 18, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(MAX86150_IDX_TS), +}; + +/* Regmap configuration */ + +static const struct regmap_config max86150_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX86150_REG_PART_ID, +}; + +/* FIFO helper */ + +/** + * 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 + * 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]; + + /* 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 */ + +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) + 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; + } + + /* 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; + } +} + +static const struct iio_info max86150_iio_info = { + .read_raw = max86150_read_raw, + .validate_trigger = iio_trigger_validate_own_device, +}; + +/* Trigger */ + +/* + * 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. + */ +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); + 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) + 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; +} + +static const struct iio_trigger_ops max86150_trigger_ops = { + .set_trigger_state = max86150_set_trigger_state, +}; + +/* Triggered buffer */ + +/** + * 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; + 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; + + /* + * 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; + } + + /* + * 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; + + 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); +} + +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); + + /* + * 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; + + 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 */ + + /* + * 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 */ + +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); + 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. + */ + ret = devm_regulator_get_enable_optional(&client->dev, "vdd"); + 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, "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)); + 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. + */ + 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); + 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"); + } + + 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"); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +/* I2C driver table */ + +static const struct i2c_device_id max86150_id[] = { + { "max86150" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max86150_id); + +static const struct of_device_id max86150_of_match[] = { + { .compatible = "maxim,max86150" }, + { } +}; +MODULE_DEVICE_TABLE(of, max86150_of_match); + +static struct i2c_driver max86150_driver = { + .driver = { + .name = "max86150", + .of_match_table = max86150_of_match, + }, + .probe = max86150_probe, + .id_table = max86150_id, +}; +module_i2c_driver(max86150_driver); + +MODULE_AUTHOR("Md Shofiqul Islam <shofiqtest@gmail.com>"); +MODULE_DESCRIPTION("MAX86150 ECG and PPG biosensor driver"); +MODULE_LICENSE("GPL"); -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v4 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver 2026-06-23 17:45 ` [PATCH v4 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam @ 2026-06-23 17:46 ` Md Shofiqul Islam 2 siblings, 0 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 17:46 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, lars, devicetree, linux-kernel, 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. Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3115538ce829..a441ec44bb27 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 <shofiqtest@gmail.com> +L: linux-iio@vger.kernel.org +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 <krzk@kernel.org> L: linux-pm@vger.kernel.org -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v3 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 14:01 [PATCH 0/1] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 14:01 ` [PATCH 1/1] " Md Shofiqul Islam 2026-06-23 15:55 ` [PATCH v2 0/3] " Md Shofiqul Islam @ 2026-06-23 16:43 ` Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam ` (2 more replies) 2 siblings, 3 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 16:43 UTC (permalink / raw) To: linux-iio Cc: jic23, lars, robh, conor+dt, krzk+dt, devicetree, 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(+) -- 2.43.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 1/3] dt-bindings: iio: health: add maxim,max86150 2026-06-23 16:43 ` [PATCH v3 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam @ 2026-06-23 16:43 ` Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver Md Shofiqul Islam 2 siblings, 0 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 16:43 UTC (permalink / raw) To: linux-iio Cc: jic23, lars, robh, conor+dt, krzk+dt, devicetree, 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. Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- .../bindings/iio/health/maxim,max86150.yaml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml new file mode 100644 index 000000000000..1bf10fd1a3d2 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml @@ -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 <shofiqtest@gmail.com> + +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>; + }; + }; -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v3 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 16:43 ` [PATCH v3 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam @ 2026-06-23 16:43 ` Md Shofiqul Islam 2026-06-23 16:57 ` sashiko-bot 2026-06-23 16:43 ` [PATCH v3 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver Md Shofiqul Islam 2 siblings, 1 reply; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 16:43 UTC (permalink / raw) To: linux-iio Cc: jic23, lars, robh, conor+dt, krzk+dt, devicetree, 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 Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- 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 diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig index a89f3abf11f4..6496cf55290c 100644 --- a/drivers/iio/health/Kconfig +++ b/drivers/iio/health/Kconfig @@ -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 diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile index 910817112258..04fc73c58444 100644 --- a/drivers/iio/health/Makefile +++ b/drivers/iio/health/Makefile @@ -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 diff --git a/drivers/iio/health/max86150.c b/drivers/iio/health/max86150.c new file mode 100644 index 000000000000..b6e241307646 --- /dev/null +++ b/drivers/iio/health/max86150.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAX86150 combined ECG and PPG biosensor driver + * + * Copyright (C) 2026 Md Shofiqul Islam <shofiqtest@gmail.com> + * + * The MAX86150 integrates two PPG optical channels (Red/IR LED) and one + * ECG biopotential channel in a single I2C device. Data is captured + * through a 32-entry hardware FIFO with a configurable almost-full + * interrupt, making it well-suited for continuous monitoring with a + * low-power host. + * + * Datasheet: + * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/i2c.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> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +/* Register addresses */ +#define MAX86150_REG_INT_STATUS1 0x00 +#define MAX86150_REG_INT_STATUS2 0x01 +#define MAX86150_REG_INT_ENABLE1 0x02 +#define MAX86150_REG_INT_ENABLE2 0x03 +#define MAX86150_REG_FIFO_WR_PTR 0x04 +#define MAX86150_REG_OVF_COUNTER 0x05 +#define MAX86150_REG_FIFO_RD_PTR 0x06 +#define MAX86150_REG_FIFO_DATA 0x07 +#define MAX86150_REG_FIFO_CONFIG 0x08 +#define MAX86150_REG_FIFO_DCTRL1 0x09 /* FD1[3:0] FD2[7:4] */ +#define MAX86150_REG_FIFO_DCTRL2 0x0A /* FD3[3:0] FD4[7:4] */ +#define MAX86150_REG_SYS_CTRL 0x0D +#define MAX86150_REG_PPG_CONFIG1 0x10 +#define MAX86150_REG_PPG_CONFIG2 0x11 +#define MAX86150_REG_LED1_PA 0x14 /* Red LED pulse amplitude */ +#define MAX86150_REG_LED2_PA 0x15 /* IR LED pulse amplitude */ +#define MAX86150_REG_ECG_CONFIG1 0x3C +#define MAX86150_REG_ECG_CONFIG3 0x3E +#define MAX86150_REG_PART_ID 0xFF + +/* Field masks */ +#define MAX86150_PART_ID_VAL 0x1E + +/* INT_STATUS1 / INT_ENABLE1 */ +#define MAX86150_INT_A_FULL BIT(7) /* FIFO almost full */ +#define MAX86150_INT_PPG_RDY BIT(6) /* new PPG sample ready */ + +/* SYS_CTRL */ +#define MAX86150_SYS_SHDN BIT(1) +#define MAX86150_SYS_RESET BIT(0) + +/* FIFO_CONFIG */ +#define MAX86150_FIFO_SMP_AVE GENMASK(7, 5) +#define MAX86150_FIFO_ROLLOVER_EN BIT(4) +#define MAX86150_FIFO_A_FULL GENMASK(3, 0) + +/* FIFO slot data-type codes */ +#define MAX86150_FD_NONE 0x0 +#define MAX86150_FD_LED1 0x1 /* Red PPG */ +#define MAX86150_FD_LED2 0x2 /* IR PPG */ +#define MAX86150_FD_ECG 0x9 +#define MAX86150_FIFO_FD1 GENMASK(3, 0) +#define MAX86150_FIFO_FD2 GENMASK(7, 4) +#define MAX86150_FIFO_FD3 GENMASK(3, 0) +#define MAX86150_FIFO_FD4 GENMASK(7, 4) + +/* PPG_CONFIG1 */ +#define MAX86150_PPG_ADC_RGE GENMASK(7, 6) +#define MAX86150_PPG_SR GENMASK(5, 1) + +/* Geometry */ +#define MAX86150_FIFO_DEPTH 32 +#define MAX86150_BYTES_PER_SLOT 3 /* 24-bit word per slot */ +#define MAX86150_NUM_SLOTS 3 /* Red, IR, ECG */ +#define MAX86150_SAMPLE_BYTES (MAX86150_NUM_SLOTS * MAX86150_BYTES_PER_SLOT) + +/* Default hardware configuration */ +#define MAX86150_LED_PA_DEFAULT 0x3F /* ~50 mA */ +#define MAX86150_PPG_SR_100HZ 4 /* PPG_SR field value for 100 Hz */ +#define MAX86150_PPG_ADC_RGE_16384 2 /* 16384 nA full scale */ +/* Fire A_FULL when 17 slots remain (32 - 15 = 17 samples in FIFO) */ +#define MAX86150_FIFO_A_FULL_VAL 15 + +/* Scan element indices */ +enum max86150_scan_idx { + MAX86150_IDX_PPG_RED = 0, + MAX86150_IDX_PPG_IR = 1, + MAX86150_IDX_ECG = 2, + MAX86150_IDX_TS, +}; + +/** + * 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; + * 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; + struct iio_trigger *trig; + u32 sample_period_ns; + u8 fifo_raw[MAX86150_SAMPLE_BYTES]; + s32 buf[6] __aligned(8); +}; + +/* IIO channel specification */ + +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), + .scan_index = MAX86150_IDX_PPG_RED, + .scan_type = { + .sign = 'u', + .realbits = 19, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + /* PPG IR LED - optical intensity, 19-bit unsigned */ + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = MAX86150_IDX_PPG_IR, + .scan_type = { + .sign = 'u', + .realbits = 19, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + /* ECG biopotential - voltage, 18-bit signed two's complement */ + .type = IIO_VOLTAGE, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = MAX86150_IDX_ECG, + .scan_type = { + .sign = 's', + .realbits = 18, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(MAX86150_IDX_TS), +}; + +/* Regmap configuration */ + +static const struct regmap_config max86150_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX86150_REG_PART_ID, +}; + +/* FIFO helper */ + +/** + * 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 + * 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]; + + /* 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 */ + +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: reset FIFO pointers so the next sample + * read is one that arrived after this call. + */ + 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) { + iio_device_release_direct(indio_dev); + return ret; + } + + /* 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); + 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; + } +} + +static const struct iio_info max86150_iio_info = { + .read_raw = max86150_read_raw, +}; + +/* Trigger */ + +/* + * Enable or disable the FIFO almost-full interrupt when the IIO triggered + * buffer is started or stopped. Keeping the interrupt masked until capture + * is active prevents a pre-existing FIFO fill from asserting the line before + * the handler is ready to clear it. + */ +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; + int ret; + + if (state) { + /* + * Flush stale FIFO data before enabling capture so the first + * samples pushed to userspace are not from before buffer enable. + */ + 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); + if (ret) + return ret; + } + + return regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1, mask); +} + +static const struct iio_trigger_ops max86150_trigger_ops = { + .set_trigger_state = max86150_set_trigger_state, +}; + +/* Triggered buffer */ + +/** + * 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; + 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; + + /* + * 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; + + /* + * Read OVF_COUNTER to distinguish a full FIFO from an empty one: + * both produce wr_ptr == rd_ptr after the pointers wrap around. + * If OVF_COUNTER is non-zero the FIFO has saturated; treat all 32 + * slots as available so we drain the entire FIFO and clear the + * interrupt condition. + */ + 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); + + /* + * Reconstruct per-sample timestamps from the IRQ capture time. + * iio_pollfunc_store_time() stored the IRQ time in pf->timestamp; + * that corresponds to the newest sample in the FIFO. Older samples + * are spaced by sample_period_ns going backward in time. + */ + for (i = 0; i < n_avail; i++) { + s64 ts = pf->timestamp - + (s64)(n_avail - 1 - i) * 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. + */ + 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); +} + +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); + + /* + * 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; + + 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 */ + + /* + * Do NOT enable the A_FULL interrupt here. It is enabled and + * disabled via max86150_set_trigger_state() when the IIO triggered + * buffer is started and stopped, which prevents the edge-triggered + * interrupt line from asserting before the handler is registered. + */ + return 0; +} + +/* Probe */ + +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); + 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. + */ + 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, + "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) { + 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); + + /* + * iio_trigger_generic_data_rdy_poll is a hard-IRQ handler + * that calls iio_trigger_poll() and returns IRQ_HANDLED. + * No IRQF_ONESHOT is needed for this edge-triggered line. + */ + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_FALLING, + "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); + if (ret) + return dev_err_probe(&client->dev, ret, + "Cannot register trigger\n"); + + /* + * Set the device's default trigger. iio_trigger_get() + * increments the trigger refcount; the IIO core balances + * this with iio_trigger_put() in iio_dev_release() when + * 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); + 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 */ + +static const struct i2c_device_id max86150_id[] = { + { "max86150" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max86150_id); + +static const struct of_device_id max86150_of_match[] = { + { .compatible = "maxim,max86150" }, + { } +}; +MODULE_DEVICE_TABLE(of, max86150_of_match); + +static struct i2c_driver max86150_driver = { + .driver = { + .name = "max86150", + .of_match_table = max86150_of_match, + }, + .probe = max86150_probe, + .id_table = max86150_id, +}; +module_i2c_driver(max86150_driver); + +MODULE_AUTHOR("Md Shofiqul Islam <shofiqtest@gmail.com>"); +MODULE_DESCRIPTION("MAX86150 ECG and PPG biosensor driver"); +MODULE_LICENSE("GPL"); -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v3 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver 2026-06-23 16:43 ` [PATCH v3 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam @ 2026-06-23 16:57 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-06-23 16:57 UTC (permalink / raw) To: Md Shofiqul Islam; +Cc: conor+dt, robh, devicetree 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 <shofiqtest@gmail.com> 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. > diff --git a/drivers/iio/health/max86150.c b/drivers/iio/health/max86150.c > --- /dev/null > +++ b/drivers/iio/health/max86150.c > @@ -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? [ ... ] > @@ -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? [ ... ] > @@ -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? > @@ -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? [ ... ] > @@ -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? [ ... ] > @@ -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, > "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? [ ... ] > @@ -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? [ ... ] > @@ -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? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260623164309.16452-1-shofiqtest@gmail.com?part=2 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver 2026-06-23 16:43 ` [PATCH v3 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam @ 2026-06-23 16:43 ` Md Shofiqul Islam 2 siblings, 0 replies; 20+ messages in thread From: Md Shofiqul Islam @ 2026-06-23 16:43 UTC (permalink / raw) To: linux-iio Cc: jic23, lars, robh, conor+dt, krzk+dt, devicetree, 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. Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com> --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3115538ce829..a441ec44bb27 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 <shofiqtest@gmail.com> +L: linux-iio@vger.kernel.org +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 <krzk@kernel.org> L: linux-pm@vger.kernel.org -- 2.51.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-06-23 17:46 UTC | newest] Thread overview: 20+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-23 14:01 [PATCH 0/1] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 14:01 ` [PATCH 1/1] " Md Shofiqul Islam 2026-06-23 14:14 ` sashiko-bot 2026-06-23 14:38 ` Joshua Crofts 2026-06-23 15:38 ` Krzysztof Kozlowski 2026-06-23 15:55 ` [PATCH v2 0/3] " Md Shofiqul Islam 2026-06-23 15:55 ` [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam 2026-06-23 16:02 ` sashiko-bot 2026-06-23 15:55 ` [PATCH v2 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 16:12 ` sashiko-bot 2026-06-23 15:55 ` [PATCH v2 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam 2026-06-23 17:45 ` [PATCH v4 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 17:46 ` [PATCH v4 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 1/3] dt-bindings: iio: health: add maxim,max86150 Md Shofiqul Islam 2026-06-23 16:43 ` [PATCH v3 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver Md Shofiqul Islam 2026-06-23 16:57 ` sashiko-bot 2026-06-23 16:43 ` [PATCH v3 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver Md Shofiqul Islam
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox