Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH] of_numa: fix return -EINVAL when numa-node-id is not found in last node
From: Sang-Heon Jeon @ 2026-06-23 17:08 UTC (permalink / raw)
  To: Rob Herring
  Cc: Saravana Kannan, Andrew Morton, Dan Williams, David Hildenbrand,
	devicetree, Mike Rapoport (Microsoft)
In-Reply-To: <CAL_JsqJ9Aedo1HA7mrb=99eAQPJ2L5G7P9inL5D4AHfqrvuxBw@mail.gmail.com>

On Tue, Jun 23, 2026 at 9:53 PM Rob Herring <robh@kernel.org> wrote:
>
> On Tue, Jun 23, 2026 at 7:08 AM Sang-Heon Jeon <ekffu200098@gmail.com> wrote:
> >
> > On Tue, Jun 23, 2026 at 10:42 AM Rob Herring <robh@kernel.org> wrote:
> > >
> > > On Sun, Jun 21, 2026 at 11:39:18PM +0900, Sang-Heon Jeon wrote:
> > > > When the numa-node-id property is not found in the last memory node,
> > > > of_property_read_u32() returns -EINVAL, which then becomes the return
> > > > value of of_numa_parse_memory_nodes(), even though earlier memory nodes
> > > > were parsed successfully.
> > > >
> > > > Commit 7e488677a54a ("of, numa: return -EINVAL when no numa-node-id is
> > > > found") meant -EINVAL to be returned only when the numa-node-id property
> > > > is not found at all, not when it is found in an earlier memory node but
> > > > not in the last.
> > > >
> > > > Check whether at least one memory node was parsed successfully, and return
> > > > 0 in that case, -EINVAL otherwise, so the return value of
> > > > of_property_read_u32() for the last memory node no longer corrupts the
> > > > overall return value.
> > >
> > > IDK, it's arguable that an incomplete DT isn't valid and something we
> > > need to support. Is missing numa-node-id valid or it's just better to
> > > have at least partially
> > > configured NUMA nodes?
> >
> > You're right.
> >
> > Since the first implementation 298535c00a2c ("of, numa: Add NUMA of
> > binding implementation.") skips memory nodes without `numa-node-id`,
> > I only try to fix the error case introduced by 7e488677a54a ("of,
> > numa: return -EINVAL when no numa-node-id is found").
> >
> > IMHO, if changing the logic is acceptable, rejecting DT with an
> > incomplete NUMA configuration also seems reasonable.
> > We already call memblock_validate_numa_coverage() after
> > of_numa_parse_memory_nodes(),
> > so such an incomplete configuration would most likely fail from that
> > validation anyway.
> >
> > So, would it be OK to reject a DT with an incomplete NUMA
> > configuration? If so, I'll make a patch in that direction instead.
>
> Reject? As in panic()? No, because why would we make a system that
> boots not boot and a panic at that stage won't be seen because your
> console is not up yet. If you want to add a warning that the NUMA
> information is incomplete, that would be fine.

Sorry for the confusion. Previous "reject" meant returning a non-zero
value from of_numa_parse_memory_nodes().
When of_numa_parse_memory_nodes() returns non-zero, NUMA
initialization from DT is considered as failed and dummy_numa_init()
is called,
which leaves the system with a single node covering all memory.

In conclusion, the direction that i meant is returning -EINVAL instead
of skipping no `numa-node-id` node,

> Also, are we sure it's not valid to have some memory associated with a
> numa node and some that is not?

As I described above, we already call
memblock_validate_numa_coverage() after of_numa_parse_memory_nodes()
success.
The threshold bytes of no node assigned memory is 0(arm64 / riscv) or
1MB(x86, introduced for e820/SRAT mismatch, but shared with DTB path
as well),
so in practice existing logic already doesn't allow no node assigned
memory, which corresponds to a missing `numa-node-id` in DT.

So as far as I know, the only case that passes
memblock_validate_numa_coverage() with a DT missing `numa-node-id` is
when numa_cleanup_meminfo() join two blocks of the same node (same
scenario as QEMU test case). An unassigned region exists between two
blocks of the same node, and no other node's memory exists in it.

So, in my opinion, at least for now, partial existence of
`numa-node-id` DT is not valid.
But maybe, somewhere, someone might want to skip `numa-node-id` for
the memory in the middle of the same node. So I can't be 100% sure.

Since neither of us is sure whether such a DT is valid, I'd rather
keep the current patch's direction because it doesn't change existing
policy.

> Rob

And always, thanks for taking the time to review.

Best Regards,
Sang-Heon Jeon

^ permalink raw reply

* Re: [PATCH] dt-bindings: clock: renesas,versaclock7: Update maintainer
From: Brian Masney @ 2026-06-23 17:00 UTC (permalink / raw)
  To: Biju
  Cc: Geert Uytterhoeven, Alex Helms, Michael Turquette, Stephen Boyd,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Magnus Damm,
	Biju Das, linux-renesas-soc, linux-clk, devicetree, linux-kernel,
	Prabhakar Mahadev Lad
In-Reply-To: <20260623162039.153291-1-biju.das.jz@bp.renesas.com>

On Tue, Jun 23, 2026 at 05:20:37PM +0100, Biju wrote:
> From: Biju Das <biju.das.jz@bp.renesas.com>
> 
> Alex's email is bouncing. Update the maintainers list with my contact
> details to take over the schema maintenance.
> 
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> ---
> Ref [1]
> [1] https://lore.kernel.org/all/ajqWevofEJ3fv856@redhat.com/

Reviewed-by: Brian Masney <bmasney@redhat.com>

Thanks for submitting this quickly.

Brian


^ permalink raw reply

* Re: [PATCH v3 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver
From: sashiko-bot @ 2026-06-23 16:57 UTC (permalink / raw)
  To: Md Shofiqul Islam; +Cc: conor+dt, robh, devicetree
In-Reply-To: <20260623164309.16452-3-shofiqtest@gmail.com>

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

* Re: [PATCH v9 2/2] i3c: master: Add driver for AMD AXI I3C master controller
From: Frank Li @ 2026-06-23 16:45 UTC (permalink / raw)
  To: Shubham Patil
  Cc: git, michal.simek, alexandre.belloni, Frank.Li, robh, krzk+dt,
	conor+dt, pgaj, wsa+renesas, tommaso.merciai.xr, arnd,
	quic_msavaliy, Shyam-sundar.S-k, sakari.ailus, billy_tsai, kees,
	gustavoars, jarkko.nikula, jorge.marques, linux-i3c, devicetree,
	linux-kernel, linux-arch, linux-hardening, radhey.shyam.pandey,
	srinivas.goud, shubhrajyoti.datta, Manikanta Guntupalli
In-Reply-To: <20260623114417.2578189-3-shubhamsanjay.patil@amd.com>

On Tue, Jun 23, 2026 at 05:14:16PM +0530, Shubham Patil wrote:
> From: Manikanta Guntupalli <manikanta.guntupalli@amd.com>
>
> Add an I3C master driver and maintainers fragment for the AMD I3C bus
> controller.
>
> The driver currently supports the I3C bus operating in SDR mode,
> with features including Dynamic Address Assignment, private data
> transfers, and CCC transfers in both broadcast and direct modes. It
> also supports operation in I2C mode.
>
> The controller's data FIFOs are accessed big-endian; the driver performs
> this conversion locally using ioread32be()/iowrite32be() with the
> helpers, so it does not depend on any core FIFO-endianness helpers.
>
> Signed-off-by: Manikanta Guntupalli <manikanta.guntupalli@amd.com>
> Co-developed-by: Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
> Signed-off-by: Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
> Co-developed-by: Shubham Patil <shubhamsanjay.patil@amd.com>
> Signed-off-by: Shubham Patil <shubhamsanjay.patil@amd.com>
> ---
...
> +#define XI3C_REV_NUM_MASK			GENMASK(15, 8)
> +#define XI3C_PID1_MASK				GENMASK(15, 0)
> +#define XI3C_FIFO_LEVEL_MASK			GENMASK(15, 0)
> +#define XI3C_RESP_CODE_MASK			GENMASK(8, 5)
> +#define XI3C_RESP_CODE_SUCCESS			0	/* Transfer completed OK */
> +#define XI3C_RESP_CODE_NO_TARGET		2	/* 7E NACK: no target on bus */
> +#define XI3C_RESP_CODE_NACK			3	/* Target NACK / CE2 / DAA end */
> +#define XI3C_ADDR_MASK				GENMASK(6, 0)
> +#define XI3C_FIFOS_RST_MASK			GENMASK(4, 1)
> +
> +/* Command FIFO word layout (bit ranges encoded in the GENMASK/BIT args) */
> +#define XI3C_CMD_TYPE				GENMASK(3, 0)	/* command type */
> +#define XI3C_CMD_TERMINATE			BIT(4)		/* terminate (last cmd of xfer) */
> +#define XI3C_CMD_ADDR				GENMASK(15, 8)	/* target address << 1 | RnW */
> +#define XI3C_CMD_LEN				GENMASK(27, 16)	/* payload length in bytes */
> +#define XI3C_CMD_TID				GENMASK(31, 28)	/* transfer ID */
> +
> +#define XI3C_OD_TLOW_NS				500000
> +#define XI3C_OD_THIGH_NS			41000
> +#define XI3C_I2C_TCASMIN_NS			600000
> +#define XI3C_TCASMIN_NS				260000
> +#define XI3C_MAXDATA_LENGTH			4095
> +#define XI3C_MAX_DEVS				32
> +#define XI3C_DAA_SLAVEINFO_READ_BYTECOUNT	8
> +
> +#define XI3C_THOLD_MIN_REV0			5	/* Min SDA hold cycles, rev 0 IP */
> +#define XI3C_THOLD_MIN_REV1			6	/* Min SDA hold cycles, rev >= 1 IP */
> +#define XI3C_CYCLE_ADJUST			2	/* SCL/SDA pre-bias for HW pipeline */
> +#define XI3C_FIFO_RESET_DELAY_US		10	/* HW settling time after FIFO reset */
> +#define XI3C_POLL_INTERVAL_US			10	/* readl_poll_timeout() sleep slice */

Can you provide comment where these value come from, spec, datasheet ...?

> +
> +#define XI3C_I2C_MODE				0
> +#define XI3C_I2C_TID				0
> +#define XI3C_SDR_MODE				1
> +#define XI3C_SDR_TID				1
> +
> +#define XI3C_WORD_LEN				4
> +
> +/*
> + * XI3C_RESP_TIMEOUT_US is in microseconds because it is passed as the
> + * timeout_us argument of readl_poll_timeout(). XI3C_XFER_TIMEOUT_MS is in
> + * milliseconds because it feeds msecs_to_jiffies(). Keep the two units
> + * distinct in the names so callers cannot mix them up.
> + */
> +#define XI3C_RESP_TIMEOUT_US			500000
> +#define XI3C_XFER_TIMEOUT_MS			1000

the same here.

> +
> +struct xi3c_cmd {
> +	const void *tx_buf;
> +	void *rx_buf;
> +	u16 tx_len;
> +	u16 rx_len;
> +	u8 addr;
> +	u8 type;
> +	u8 tid;
> +	bool rnw;
> +	bool is_daa;
> +	bool continued;
> +	enum i3c_error_code err;
> +};
> +
...
> +
> +static void xi3c_master_reset_fifos(struct xi3c_master *master)
> +{
> +	u32 data;
> +
> +	/* Assert FIFO reset. */
> +	data = ioread32(master->membase + XI3C_RESET_OFFSET);
> +	data |= XI3C_FIFOS_RST_MASK;
> +	iowrite32(data, master->membase + XI3C_RESET_OFFSET);
> +	/* Read-back flushes the posted write before the settling delay below. */
> +	ioread32(master->membase + XI3C_RESET_OFFSET);
> +	udelay(XI3C_FIFO_RESET_DELAY_US);

now suggest use fsleep()

> +
> +	/* De-assert FIFO reset, then wait for the FIFOs to come back up. */
> +	data &= ~XI3C_FIFOS_RST_MASK;
> +	iowrite32(data, master->membase + XI3C_RESET_OFFSET);
> +	ioread32(master->membase + XI3C_RESET_OFFSET);
> +	udelay(XI3C_FIFO_RESET_DELAY_US);
> +}
> +
> +static inline void xi3c_master_init(struct xi3c_master *master)
> +{
> +	/* Reset fifos */
> +	xi3c_master_reset_fifos(master);
> +
> +	/* Enable controller */
> +	xi3c_master_enable(master);
> +}
> +
> +static inline void xi3c_master_reinit(struct xi3c_master *master)
> +{
> +	/* Reset fifos */
> +	xi3c_master_reset_fifos(master);
> +
> +	/* Resume controller */
> +	xi3c_master_resume(master);
> +}
> +
> +static struct xi3c_xfer *xi3c_master_alloc_xfer(unsigned int ncmds)
> +{
> +	struct xi3c_xfer *xfer;
> +
> +	xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL);

ues new API,  kzalloc_flex()

> +	if (!xfer)
> +		return NULL;
> +
> +	xfer->ncmds = ncmds;
> +
> +	return xfer;
> +}
> +
> +static void xi3c_master_rd_from_rx_fifo(struct xi3c_master *master,
> +					struct xi3c_cmd *cmd)
> +{
> +	u16 rx_data_available;
> +	u16 copy_len;
> +	u16 len;
> +
> +	rx_data_available = xi3c_rd_fifo_level(master);
> +	len = rx_data_available * XI3C_WORD_LEN;
> +
> +	if (!len)
> +		return;
> +
> +	copy_len = min_t(u16, len, cmd->rx_len);

now need't min_t, just min

> +	xi3c_readl_fifo(master->membase + XI3C_RD_FIFO_OFFSET,
> +			(u8 *)cmd->rx_buf, copy_len);
> +
> +	cmd->rx_buf = (u8 *)cmd->rx_buf + copy_len;
> +	cmd->rx_len -= copy_len;
> +}
> +
...
> +
> +	timeout = jiffies + msecs_to_jiffies(XI3C_XFER_TIMEOUT_MS);
> +
> +	/* Read data from rx fifo */
> +	while (cmd->rx_len > 0 && !xi3c_is_resp_available(master)) {
> +		if (time_after(jiffies, timeout)) {
> +			dev_err(master->dev, "XI3C read timeout\n");
> +			return -EIO;
> +		}
> +		xi3c_master_rd_from_rx_fifo(master, cmd);
> +		usleep_range(XI3C_POLL_INTERVAL_US, 2 * XI3C_POLL_INTERVAL_US);
> +	}

can you use read_poll_timeout macro?

> +
> +	/* Read remaining data */
> +	xi3c_master_rd_from_rx_fifo(master, cmd);
> +
> +	return 0;
> +}
> +
...
> +
> +	for (i = 0; i < master->daa.index; i++) {
> +		u64 pid;
> +
> +		ret = i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]);
> +		if (ret)
> +			goto err_daa;


https://lore.kernel.org/linux-i3c/20260608054312.10604-7-adrian.hunter@intel.com/T/#u
which defer add i3c device.

And don't check error here, because one device add failure should not impact
other following devices.

Frank
> +
> +		pid = FIELD_GET(XI3C_PID_MASK,
> +				get_unaligned_be64(pid_bufs[i]));
> +		dev_dbg(master->dev, "Client %d: PID: 0x%llx\n", i, pid);
> +	}
> +
> +	return 0;
> +
> +err_daa:
> +	xi3c_master_reinit(master);
> +	return ret;
> +}
> +
...
> +static int xi3c_master_send_bdcast_ccc_cmd(struct xi3c_master *master,
> +					   struct i3c_ccc_cmd *ccc)
> +{
> +	struct xi3c_xfer *xfer __free(kfree) = NULL;
> +	u8 *buf __free(kfree) = NULL;
> +	struct xi3c_cmd *cmd;
> +	u16 xfer_len;
> +	int ret;
> +
> +	if (ccc->dests[0].payload.len >= XI3C_MAXDATA_LENGTH)
> +		return -EINVAL;
> +
> +	xfer_len = ccc->dests[0].payload.len + 1;
> +
> +	xfer = xi3c_master_alloc_xfer(1);
> +	if (!xfer)
> +		return -ENOMEM;
> +
> +	buf = kmalloc(xfer_len, GFP_KERNEL);

kmalloc_obj

Frank
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	buf[0] = ccc->id;
> +	memcpy(&buf[1], ccc->dests[0].payload.data, ccc->dests[0].payload.len);
> +
> +	cmd = &xfer->cmds[0];
> +	cmd->addr = ccc->dests[0].addr;
> +	cmd->rnw = ccc->rnw;
> +	cmd->tx_buf = buf;
> +	cmd->tx_len = xfer_len;
> +	cmd->type = XI3C_SDR_MODE;
> +	cmd->tid = XI3C_SDR_TID;
> +	cmd->continued = false;
> +
> +	ret = xi3c_master_common_xfer(master, xfer);
> +	ccc->err = cmd->err;
> +
> +	return ret;
> +}

^ permalink raw reply

* [PATCH v3 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver
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
In-Reply-To: <20260623164309.16452-1-shofiqtest@gmail.com>

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

* [PATCH v3 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver
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
In-Reply-To: <20260623164309.16452-1-shofiqtest@gmail.com>

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

* [PATCH v3 1/3] dt-bindings: iio: health: add maxim,max86150
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
In-Reply-To: <20260623164309.16452-1-shofiqtest@gmail.com>

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

* [PATCH v3 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver
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
In-Reply-To: <20260623140113.12574-1-shofiqtest@gmail.com>

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

* Re: [PATCH V13 8/9] iio: imu: inv_icm42607: Add Gyroscope to icm42607
From: Chris Morgan @ 2026-06-23 16:38 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Andy Shevchenko, Chris Morgan, linux-iio, andy, nuno.sa, dlechner,
	jean-baptiste.maneyrol, linux-rockchip, devicetree, heiko,
	conor+dt, krzk+dt, robh
In-Reply-To: <20260623161527.7b7b0ccb@jic23-huawei>

On Tue, Jun 23, 2026 at 04:15:27PM +0100, Jonathan Cameron wrote:
> On Tue, 23 Jun 2026 09:52:36 +0300
> Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> 
> > On Wed, Jun 17, 2026 at 04:10:49PM -0500, Chris Morgan wrote:
> > > On Tue, Jun 16, 2026 at 01:13:03PM +0300, Andy Shevchenko wrote:  
> > > > On Mon, Jun 15, 2026 at 12:25:51PM -0500, Chris Morgan wrote:  
> > 
> > ...
> > 
> > > > Can be some of the code deduplicated between gyro and accel?  
> > > 
> > > Probably a fair amount, but the deduplication will likely need to be
> > > undone somewhat if we get buffer, WoM or apex support added back
> > > (I don't have any devices with such functionality, so if anyone will
> > > do it then it won't be me). I can refactor more if you want, or we
> > > can keep it split like this to make it easy if someone else wants to
> > > tackle the buffers/IRQs stuff later? Your call.  
> > 
> > Just asking. Jonathan, David, Nuno, what's your opinion on this?
> > Personal opinion is to avoid solving the issues that do not exist.
> > If you are not committing into those features, let's not prepare
> > driver for them right now.
> > 
> Agreed.  My only exception to this is normally when someone tells
> me they have the code and plan to post a follow up set very shortly.
> Then I get a bit more relaxed as they chances of 'preparing' for the
> future being relevant become much higher!
> 
> Jonathan

Okay, I'll refactor a bit more then and resubmit in another week or
two.

Thanks,
Chris

^ permalink raw reply

* Re: [PATCH v7 01/15] arm64: dts: qcom: kodiak: Add EL2 overlay
From: Mukesh Ojha @ 2026-06-23 16:31 UTC (permalink / raw)
  To: Sumit Garg
  Cc: andersson, linux-arm-msm, devicetree, dri-devel, freedreno,
	linux-media, netdev, linux-wireless, ath12k, linux-remoteproc,
	konradybcio, robh, krzk+dt, conor+dt, robin.clark, sean, akhilpo,
	lumag, abhinav.kumar, jesszhan0024, marijn.suijten, airlied,
	simona, vikash.garodia, dikshita.agarwal, bod, mchehab, elder,
	andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
	mathieu.poirier, trilokkumar.soni, pavan.kondeti, jorge.ramirez,
	tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
	jens.wiklander, op-tee, apurupa, skare, linux-kernel, Sumit Garg
In-Reply-To: <20260522115936.201208-2-sumit.garg@kernel.org>

On Fri, May 22, 2026 at 05:29:22PM +0530, Sumit Garg wrote:
> From: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
> 
> All the existing variants Kodiak boards are using Gunyah hypervisor
> which means that, so far, Linux-based OS could only boot in EL1 on those
> devices.  However, it is possible for us to boot Linux at EL2 on these
> devices [1].
> 
> When running under Gunyah, the remote processor firmware IOMMU
> streams are controlled by Gunyah. However, without Gunyah, the IOMMU is
> managed by the consumer of this DeviceTree. Therefore, describe the
> firmware streams for each remote processor.
> 
> Add a EL2-specific DT overlay and apply it to Kodiak IOT variant
> devices to create -el2.dtb for each of them alongside "normal" dtb.
> 
> Note that modem and media subsystems haven't been supported yet due
> to missing dependencies. For GPU to work, zap shader is disabled and
> in EL2 mode the kernel owns hardware watchdog which is enabled here.
> 
> [1]
> https://docs.qualcomm.com/bundle/publicresource/topics/80-70020-4/boot-developer-touchpoints.html#uefi
> 
> Signed-off-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
> [SG: watchdog and modem fixup]
> Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>

As discussed internally, I will be taking this patch separately and you
can drop this from series.

-- 
-Mukesh Ojha

^ permalink raw reply

* Re: [PATCH 3/5] dt-bindings: clock: qcom: Add Maili global clock controller
From: Taniya Das @ 2026-06-23 16:22 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vivek Aknurwar,
	Ajit Pandey, Imran Shaik, Jagadeesh Kona, linux-arm-msm,
	linux-clk, devicetree, linux-kernel
In-Reply-To: <20260622-complex-dashing-gopher-9f43a3@quoll>



On 6/22/2026 7:28 PM, Krzysztof Kozlowski wrote:

> I think this should be moved to its own header which will include the
> qcom,hawi-gcc.h. We already do it for
> include/dt-bindings/clock/qcom,sm8650-videocc.h.

Sure, Krzysztof let me take a look and update in the next patch.

-- 
Thanks,
Taniya Das


^ permalink raw reply

* [PATCH] dt-bindings: clock: renesas,versaclock7: Update maintainer
From: Biju @ 2026-06-23 16:20 UTC (permalink / raw)
  To: Geert Uytterhoeven, Alex Helms, Michael Turquette, Stephen Boyd,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Magnus Damm
  Cc: Biju Das, Brian Masney, linux-renesas-soc, linux-clk, devicetree,
	linux-kernel, Prabhakar Mahadev Lad, Biju Das

From: Biju Das <biju.das.jz@bp.renesas.com>

Alex's email is bouncing. Update the maintainers list with my contact
details to take over the schema maintenance.

Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
Ref [1]
[1] https://lore.kernel.org/all/ajqWevofEJ3fv856@redhat.com/
---
 .../devicetree/bindings/clock/renesas,versaclock7.yaml          | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml b/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml
index b339f1f9f072..990d287d0a90 100644
--- a/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml
+++ b/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml
@@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
 title: Renesas Versaclock7 Programmable Clock
 
 maintainers:
-  - Alex Helms <alexander.helms.jy@renesas.com>
+  - Biju Das <biju.das.jz@bp.renesas.com>
 
 description: |
   Renesas Versaclock7 is a family of configurable clock generator and
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH 2/2] dt-bindings: iio: dac: Add DAC8163
From: sashiko-bot @ 2026-06-23 16:18 UTC (permalink / raw)
  To: Lukas Metz; +Cc: conor+dt, robh, devicetree
In-Reply-To: <20260623-dac8163-work-v1-2-5b508158faa0@gmx.net>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The schema does not properly enforce mutual exclusivity between `ti,internal-ref` and `vref-supply`.
--

commit c91535ac2ae4b61ce673dbacd2798488e644cdb0
Author: Lukas Metz <lukas.metz@gmx.net>

dt-bindings: iio: dac: Add DAC8163

This commit adds the device tree binding schema for the Texas Instruments
DAC8163 family of digital-to-analog converters.

> diff --git a/Documentation/devicetree/bindings/iio/dac/ti,dac8163.yaml b/Documentation/devicetree/bindings/iio/dac/ti,dac8163.yaml
> new file mode 100644
> index 0000000000000..bb4bad3893235
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/dac/ti,dac8163.yaml

[ ... ]

> +required:
> +  - compatible
> +  - reg
> +
> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +  - if:
> +      not:
> +        required:
> +          - ti,internal-ref
> +    then:
> +      required:
> +        - vref-supply

[Severity: Low]
Does this schema properly enforce mutual exclusivity between the internal
reference and an external supply?

If a devicetree incorrectly defines both the "ti,internal-ref" flag and a
"vref-supply" property, it seems this block will silently pass validation. 
While it requires "vref-supply" when "ti,internal-ref" is absent, it doesn't
appear to forbid "vref-supply" (e.g., via `properties: { vref-supply: false }`)
when "ti,internal-ref" is present.

> +
> +unevaluatedProperties: false

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260623-dac8163-work-v1-0-5b508158faa0@gmx.net?part=2

^ permalink raw reply

* Re: [PATCH 1/2] iio: dac: dac8163: Add driver for DAC8163
From: sashiko-bot @ 2026-06-23 16:18 UTC (permalink / raw)
  To: Lukas Metz; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260623-dac8163-work-v1-1-5b508158faa0@gmx.net>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The SPI transfer buffer `tx` is allocated on the stack, which is not DMA-safe and violates SPI subsystem requirements.
- [High] The pointer returned by `spi_get_device_match_data()` is unconditionally dereferenced without a NULL check.
- [Medium] The IIO scale attribute calculation ignores the hardware gain when the internal reference is used, returning an incorrect scale factor.
--

commit db89a9af22e26ad0341eb42f1659ed8a2fad0e51
Author: Lukas Metz <lukas.metz@gmx.net>

iio: dac: dac8163: Add driver for DAC8163

This commit adds a new driver for the Texas Instruments DAC756x, DAC816x,
and DAC856x series devices. These are low-power, voltage-output,
dual-channel DACs with up to 16 bits of resolution.

> diff --git a/drivers/iio/dac/ti-dac8163.c b/drivers/iio/dac/ti-dac8163.c
> new file mode 100644
> index 0000000000000..84a9dfb5347d6
> --- /dev/null
> +++ b/drivers/iio/dac/ti-dac8163.c

[ ... ]

> +static int dacxx6x_read_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan, int *val,
> +			    int *val2, long mask)
> +{
> +	struct dacxx6x_state *st;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:

[ ... ]

> +	case IIO_CHAN_INFO_SCALE:
> +		st = iio_priv(indio_dev);
> +		*val = st->vref_uv / MILLI; /* vref in mV */
> +		*val2 = chan->scan_type.realbits;

[Severity: Medium]
Does this scale calculation account for the hardware gain when using the
internal reference?

When st->internal_ref is true, the internal reference is 2.5V, but these
devices typically have a default internal gain of 2, making the full-scale
voltage 5V.

Should the gain multiplier be factored into the vref_uv calculation before
exposing the scale to userspace?

> +		return IIO_VAL_FRACTIONAL_LOG2;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int dacxx6x_write_reg(struct dacxx6x_state *st, int reg, int addr,
> +			     unsigned int val)
> +{
> +	u8 tx[3];
> +
> +	tx[0] = COMMAND_SET(reg, addr);
> +	tx[1] = (val >> 8) & 0xff;
> +	tx[2] = val & 0xff;
> +
> +	return spi_write(st->spi, tx, sizeof(tx));

[Severity: High]
Is it safe to pass a stack-allocated buffer to spi_write() here?

Since spi_write() maps the provided buffer for DMA, passing a stack
variable can lead to invalid physical address mappings or memory corruption,
especially on systems with CONFIG_VMAP_STACK enabled.

Could the tx buffer be moved into the dacxx6x_state structure, protected by
the lock, and properly cacheline-aligned to ensure it is DMA-safe?

> +}

[ ... ]

> +static int dacxx6x_probe(struct spi_device *spi)
> +{

[ ... ]

> +	ret = dacxx6x_write_reg(st, CMD_SEL_REFERENCE, 0,
> +				FIELD_PREP(VOLTAGE_REFERENCE_MASK, st->internal_ref));
> +
> +	if (ret < 0)
> +		goto err;
> +
> +	info = spi_get_device_match_data(spi);
> +
> +	indio_dev->name = info->name;

[Severity: High]
Can spi_get_device_match_data() return NULL here if the device is
instantiated from userspace via sysfs (using driver_override and bind)
with a name that doesn't match the ID tables?

If it does return NULL, the subsequent dereference of info->name will cause
a kernel oops. Is a NULL check needed before accessing info?

> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->info = &dacxx6x_iio_info;
> +	indio_dev->channels = info->channels;
> +	indio_dev->num_channels = 2;
> +
> +	ret = iio_device_register(indio_dev);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260623-dac8163-work-v1-0-5b508158faa0@gmx.net?part=1

^ permalink raw reply

* Re: [PATCH v2 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver
From: sashiko-bot @ 2026-06-23 16:12 UTC (permalink / raw)
  To: Md Shofiqul Islam; +Cc: devicetree, robh, conor+dt
In-Reply-To: <20260623155556.13701-3-shofiqtest@gmail.com>

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

* Re: [PATCH v14 4/4] crypto: spacc - Add SPAcc Kconfig and Makefile
From: Julian Braha @ 2026-06-23 16:10 UTC (permalink / raw)
  To: Pavitrakumar Managutte, linux-crypto, linux-kernel, devicetree,
	herbert, robh
  Cc: krzk, conor+dt, Ruud.Derwig, rbannerm, manjunath.hadli, adityak,
	navami.telsang, bhoomikak
In-Reply-To: <20260619144558.1868995-5-pavitrakumarm@vayavyalabs.com>

Hi Pavitrakumar,

On 6/19/26 15:45, Pavitrakumar Managutte wrote:

> diff --git a/drivers/crypto/dwc-spacc/Kconfig b/drivers/crypto/dwc-spacc/Kconfig
> new file mode 100644
> index 0000000000000..b253f8dc539c1
> --- /dev/null
> +++ b/drivers/crypto/dwc-spacc/Kconfig
> @@ -0,0 +1,87 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config CRYPTO_DEV_SPACC
> +	tristate "Support for dwc_spacc Security Protocol Accelerator"
> +	depends on HAS_DMA
> +	select CRYPTO_ENGINE
> +	default n
> +
> +	help
> +	  This enables support for SPAcc Hardware Accelerator.
> +
> +config CRYPTO_DEV_SPACC_HASH
> +	bool "Enable HASH functionality"
> +	depends on CRYPTO_DEV_SPACC
> +	default y
> +	select CRYPTO_HASH
> +	select CRYPTO_SHA1
> +	select CRYPTO_MD5
> +	select CRYPTO_SHA256
> +	select CRYPTO_SHA512
> +	select CRYPTO_HMAC
> +	select CRYPTO_SM3
> +	select CRYPTO_CMAC
> +	select CRYPTO_XCBC
> +	select CRYPTO_AES
> +	select CRYPTO_SM4_GENERIC
> +
> +	help
> +	  Say y to enable Hash functionality of SPAcc.
> +
> +config CRYPTO_DEV_SPACC_AUTODETECT
> +	bool "Enable Autodetect functionality"
> +	depends on CRYPTO_DEV_SPACC
> +	default y
> +	help
> +	  Say y to enable Autodetect functionality of SPAcc.
> +
> +config CRYPTO_DEV_SPACC_DEBUG_TRACE_IO
> +	bool "Enable Trace MMIO reads/writes stats"
> +	depends on CRYPTO_DEV_SPACC
> +	default n
> +	help
> +	  Say y to enable Trace MMIO reads/writes stats.
> +	  To Debug and trace IO register read/write oprations.
> +
> +config CRYPTO_DEV_SPACC_DEBUG_TRACE_DDT
> +	bool "Enable Trace DDT entries stats"
> +	default n
> +	depends on CRYPTO_DEV_SPACC
> +	help
> +	  Say y to enable Enable DDT entry stats.
> +	  To Debug and trace DDT opration
> +
> +config CRYPTO_DEV_SPACC_SECURE_MODE
> +	bool "Enable Spacc secure mode stats"
> +	default n
> +	depends on CRYPTO_DEV_SPACC
> +	help
> +	  Say y to enable SPAcc secure modes stats.
> +
> +config CRYPTO_DEV_SPACC_PRIORITY
> +	int "VSPACC priority value"
> +	depends on CRYPTO_DEV_SPACC
> +	range 0 15
> +	default 1
> +	help
> +	  Default arbitration priority weight for this Virtual SPAcc instance.
> +	  Hardware resets this to 1. Higher values means higher priority.
> +
> +config CRYPTO_DEV_SPACC_INTERNAL_COUNTER
> +	int "SPAcc internal counter value"
> +	depends on CRYPTO_DEV_SPACC
> +	range 100000 1048575
> +	default 100000
> +	help
> +	  This value configures a hardware watchdog counter in the SPAcc engine.
> +	  The counter starts ticking when a completed cryptographic job is
> +	  sitting in the STATUS FIFO. If the job remains unprocessed for the
> +	  configured duration, an interrupt is triggered to ensure it is serviced.
> +
> +config CRYPTO_DEV_SPACC_CONFIG_DEBUG
> +	bool "Enable SPAcc debug logs"
> +	default n
> +	depends on CRYPTO_DEV_SPACC
Just a kconfig style thing, it would be a bit cleaner to factor out the
repeated 'depends on CRYPTO_DEV_SPACC' by wrapping the options
in:
`if CRYPTO_DEV_SPACC .. endif`

- Julian Braha

^ permalink raw reply

* [PATCH 1/2] iio: dac: dac8163: Add driver for DAC8163
From: Lukas Metz @ 2026-06-23 16:07 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Lukas Metz
In-Reply-To: <20260623-dac8163-work-v1-0-5b508158faa0@gmx.net>

The DAC756x, DAC816x, and DAC856x devices are low-power, voltage-output,
dual-channel, 12-, 14-, and 16-bit digital-to-analog converters (DACs),
respectively. These devices include a 2.5-V, 4-ppm/°C internal
reference, giving a full-scale output voltage range of 2.5 V or 5 V.

Signed-off-by: Lukas Metz <lukas.metz@gmx.net>
---
 MAINTAINERS                  |   6 +
 drivers/iio/dac/Kconfig      |  10 ++
 drivers/iio/dac/Makefile     |   1 +
 drivers/iio/dac/ti-dac8163.c | 339 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 356 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index d238590a31f2..e82cc28e1bc3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26394,6 +26394,12 @@ S:	Odd Fixes
 F:	drivers/clk/ti/
 F:	include/linux/clk/ti.h
 
+TI DAC8163 DAC DRIVER
+M:	Lukas Metz <lukas.metz@gmx.net>
+L:	linux-iio@vger.kernel.org
+S:	Maintained
+F:	drivers/iio/dac/ti-dac8163.c
+
 TI DATA TRANSFORM AND HASHING ENGINE (DTHE) V2 CRYPTO DRIVER
 M:	T Pratham <t-pratham@ti.com>
 L:	linux-crypto@vger.kernel.org
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index db9f5c711b3d..6b6e5ee0732a 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -632,6 +632,16 @@ config TI_DAC7612
 
 	  If compiled as a module, it will be called ti-dac7612.
 
+config TI_DAC8163
+	tristate "Texas Instruments 12/14/16-bit 2-channel DAC driver"
+	depends on SPI_MASTER
+	help
+	  Driver for the Texas Instruments digital-to-analog converter
+	  family dacxx6x compatible with the variants DAC7562,
+	  DAC7563, DAC8162, DAC8163, DAC8562 and DAC8563.
+
+	  If compiled as a module, it will be called ti-dac8163.
+
 config VF610_DAC
 	tristate "Vybrid vf610 DAC driver"
 	depends on HAS_IOMEM
diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
index 2a80bbf4e80a..359cde446623 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -62,4 +62,5 @@ obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o
 obj-$(CONFIG_TI_DAC5571) += ti-dac5571.o
 obj-$(CONFIG_TI_DAC7311) += ti-dac7311.o
 obj-$(CONFIG_TI_DAC7612) += ti-dac7612.o
+obj-$(CONFIG_TI_DAC8163) += ti-dac8163.o
 obj-$(CONFIG_VF610_DAC) += vf610_dac.o
diff --git a/drivers/iio/dac/ti-dac8163.c b/drivers/iio/dac/ti-dac8163.c
new file mode 100644
index 000000000000..84a9dfb5347d
--- /dev/null
+++ b/drivers/iio/dac/ti-dac8163.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DACxx6x IIO driver (SPI)
+ */
+
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/iio/iio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/units.h>
+#include <linux/gpio/consumer.h>
+#include <linux/printk.h>
+#include <linux/bitfield.h>
+
+#define COMMAND_MASK GENMASK(6, 3)
+#define ADDRESS_MASK GENMASK(2, 0)
+
+#define COMMAND_SET(x, y) (FIELD_PREP(COMMAND_MASK, (x)) | \
+							FIELD_PREP(ADDRESS_MASK, (y)))
+
+#define CMD_WRITE_INPUT_REG	0x0
+#define CMD_UPDATE_DAC	0x1
+#define CMD_WRITE_UPDATE_ALL	0x2
+#define CMD_WRITE_UPDATE	0x3
+#define CMD_SET_PWR_MODE		0x4
+#define CMD_SOFT_RST			0x5
+
+#define CMD_LDAC_MODE		0x6
+#define LDAC_MODE_CHANNEL_A_MASK BIT(0)
+#define LDAC_MODE_CHANNEL_B_MASK BIT(1)
+
+#define CMD_SEL_REFERENCE	0x7
+#define VOLTAGE_REFERENCE_MASK BIT(0)
+
+enum dacxx6x_ldac_modes {
+	LDAC_MODE_ACTIVE = 0,
+	LDAC_MODE_INACTIVE = 1
+};
+
+enum dacxx6x_voltage_reference {
+	VOLTAGE_REFERENCE_EXTERNAL = 0,
+	VOLTAGE_REFERENCE_INTERNAL = 1
+};
+
+enum dacxx6x_supported_device_ids {
+	ID_DAC7562,
+	ID_DAC7563,
+	ID_DAC8162,
+	ID_DAC8163,
+	ID_DAC8562,
+	ID_DAC8563
+};
+
+struct dacxx6x_state {
+	struct spi_device *spi;
+
+	struct regulator *vref;
+	struct gpio_desc *loaddacs;
+
+	bool internal_ref;
+	int vref_uv;
+
+	unsigned int cached[2];
+
+	/*
+	 * Lock to protect the state of the device from potential concurrent
+	 * write accesses from userspace.
+	 */
+	struct mutex lock;
+};
+
+struct dacxx6x_chip_info {
+	const char *name;
+	const struct iio_chan_spec channels[2];
+};
+
+#define DACXX6X_CHAN(id, resolution)                                        \
+	{                                                                   \
+		.type = IIO_VOLTAGE, .channel = (id), .output = 1,          \
+		.indexed = 1, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),       \
+		.scan_type = { .realbits = (resolution),                    \
+			       .shift = 16 - (resolution) },                \
+	}
+
+static const struct dacxx6x_chip_info dacxx6x_chip_info_table[6] = {
+	[ID_DAC7562] = {
+		.name = "dac7562",
+		.channels = {
+			DACXX6X_CHAN(0, 12),
+			DACXX6X_CHAN(1, 12),
+		}
+	},
+	[ID_DAC7563] = {
+		.name = "dac7563",
+		.channels = {
+			DACXX6X_CHAN(0, 12),
+			DACXX6X_CHAN(1, 12),
+		}
+	},
+	[ID_DAC8162] = {
+		.name = "dac8162",
+		.channels = {
+			DACXX6X_CHAN(0, 14),
+			DACXX6X_CHAN(1, 14),
+		}
+	},
+	[ID_DAC8163] = {
+		.name = "dac8163",
+		.channels = {
+			DACXX6X_CHAN(0, 14),
+			DACXX6X_CHAN(1, 14),
+		}
+	},
+	[ID_DAC8562] = {
+		.name = "dac8562",
+		.channels = {
+			DACXX6X_CHAN(0, 16),
+			DACXX6X_CHAN(1, 16),
+		}
+	},
+	[ID_DAC8563] = {
+		.name = "dac8563",
+		.channels = {
+			DACXX6X_CHAN(0, 16),
+			DACXX6X_CHAN(1, 16),
+		}
+	},
+};
+
+static int dacxx6x_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int *val,
+			    int *val2, long mask)
+{
+	struct dacxx6x_state *st;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		st = iio_priv(indio_dev);
+		mutex_lock(&st->lock);
+		*val = st->cached[chan->channel];
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		st = iio_priv(indio_dev);
+		*val = st->vref_uv / MILLI; /* vref in mV */
+		*val2 = chan->scan_type.realbits;
+		return IIO_VAL_FRACTIONAL_LOG2;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int dacxx6x_write_reg(struct dacxx6x_state *st, int reg, int addr,
+			     unsigned int val)
+{
+	u8 tx[3];
+
+	tx[0] = COMMAND_SET(reg, addr);
+	tx[1] = (val >> 8) & 0xff;
+	tx[2] = val & 0xff;
+
+	return spi_write(st->spi, tx, sizeof(tx));
+}
+
+static int dacxx6x_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{
+	struct dacxx6x_state *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		dev_dbg(dev, "%s: val=%d val2=%d\n", __func__, val, val2);
+		if (val2 != 0)
+			return -EINVAL;
+
+		if (val < 0 || val >= BIT(chan->scan_type.realbits))
+			return -EINVAL;
+
+		mutex_lock(&st->lock);
+		int ret = dacxx6x_write_reg(st, CMD_WRITE_UPDATE, chan->channel,
+					    (unsigned int)val
+						    << chan->scan_type.shift);
+
+		if (!ret)
+			st->cached[chan->channel] = val;
+		mutex_unlock(&st->lock);
+		return ret;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info dacxx6x_iio_info = {
+	.write_raw = dacxx6x_write_raw,
+	.read_raw = dacxx6x_read_raw
+};
+
+static int dacxx6x_probe(struct spi_device *spi)
+{
+	struct iio_dev *indio_dev;
+	struct dacxx6x_state *st;
+	const struct dacxx6x_chip_info *info;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->spi = spi;
+	spi_set_drvdata(spi, indio_dev);
+
+	st->loaddacs = devm_gpiod_get_optional(&spi->dev, "ti,loaddacs",
+					       GPIOD_OUT_LOW);
+	if (IS_ERR(st->loaddacs))
+		return PTR_ERR(st->loaddacs);
+
+	st->internal_ref =
+		device_property_read_bool(&spi->dev, "ti,internal-ref");
+
+	if (!st->internal_ref) {
+		st->vref = devm_regulator_get(&spi->dev, "vref");
+		if (IS_ERR(st->vref))
+			return PTR_ERR(st->vref);
+
+		ret = regulator_enable(st->vref);
+		if (ret < 0)
+			return ret;
+	}
+
+	mutex_init(&st->lock);
+
+	if (st->internal_ref) {
+		st->vref_uv = 2500000; /* 2.5V internal reference */
+	} else {
+		st->vref_uv = regulator_get_voltage(st->vref);
+		if (st->vref_uv < 0) {
+			ret = st->vref_uv;
+			goto err;
+		}
+	}
+
+	gpiod_set_value(st->loaddacs, 0);
+
+	ret = dacxx6x_write_reg(st, CMD_LDAC_MODE, 0,
+				FIELD_PREP(LDAC_MODE_CHANNEL_A_MASK, LDAC_MODE_INACTIVE) |
+				FIELD_PREP(LDAC_MODE_CHANNEL_B_MASK, LDAC_MODE_INACTIVE));
+
+	if (ret < 0)
+		goto err;
+
+	ret = dacxx6x_write_reg(st, CMD_SEL_REFERENCE, 0,
+				FIELD_PREP(VOLTAGE_REFERENCE_MASK, st->internal_ref));
+
+	if (ret < 0)
+		goto err;
+
+	info = spi_get_device_match_data(spi);
+
+	indio_dev->name = info->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &dacxx6x_iio_info;
+	indio_dev->channels = info->channels;
+	indio_dev->num_channels = 2;
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto err;
+
+	return 0;
+
+err:
+	if (!st->internal_ref)
+		regulator_disable(st->vref);
+	mutex_destroy(&st->lock);
+	return ret;
+}
+
+static void dacxx6x_remove(struct spi_device *spi)
+{
+	struct iio_dev *indio_dev = spi_get_drvdata(spi);
+	struct dacxx6x_state *st = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+	mutex_destroy(&st->lock);
+	if (!st->internal_ref)
+		regulator_disable(st->vref);
+}
+
+#define DACXX6X_COMPATIBLE(of_compatible, id)        \
+	{                                            \
+		.compatible = of_compatible,         \
+		.data = &dacxx6x_chip_info_table[id] \
+	}
+
+static const struct of_device_id dacxx6x_of_match[] = {
+	DACXX6X_COMPATIBLE("ti,dac7562", ID_DAC7562),
+	DACXX6X_COMPATIBLE("ti,dac7563", ID_DAC7563),
+	DACXX6X_COMPATIBLE("ti,dac8162", ID_DAC8162),
+	DACXX6X_COMPATIBLE("ti,dac8163", ID_DAC8163),
+	DACXX6X_COMPATIBLE("ti,dac8562", ID_DAC8562),
+	DACXX6X_COMPATIBLE("ti,dac8563", ID_DAC8563),
+	{}
+};
+MODULE_DEVICE_TABLE(of, dacxx6x_of_match);
+
+static const struct spi_device_id dacxx6x_id_table[] = {
+	{ "dac7562", (kernel_ulong_t)&dacxx6x_chip_info_table[ID_DAC7562] },
+	{ "dac7563", (kernel_ulong_t)&dacxx6x_chip_info_table[ID_DAC7563] },
+	{ "dac8162", (kernel_ulong_t)&dacxx6x_chip_info_table[ID_DAC8162] },
+	{ "dac8163", (kernel_ulong_t)&dacxx6x_chip_info_table[ID_DAC8163] },
+	{ "dac8562", (kernel_ulong_t)&dacxx6x_chip_info_table[ID_DAC8562] },
+	{ "dac8563", (kernel_ulong_t)&dacxx6x_chip_info_table[ID_DAC8563] },
+	{}
+};
+
+MODULE_DEVICE_TABLE(spi, dacxx6x_id_table);
+
+static struct spi_driver dacxx6x_driver = {
+	.driver = {
+		.name = "ti-dacxx6x",
+		.of_match_table = dacxx6x_of_match,
+	},
+	.probe = dacxx6x_probe,
+	.remove = dacxx6x_remove,
+	.id_table = dacxx6x_id_table,
+};
+
+module_spi_driver(dacxx6x_driver);
+
+MODULE_AUTHOR("Lukas Metz <lukas.metz@gmx.net>");
+MODULE_DESCRIPTION("Texas Instruments 12/14/16-bit 2-channel DAC driver");
+MODULE_LICENSE("GPL");

-- 
2.43.0


^ permalink raw reply related

* [PATCH 0/2] Add driver for DAC8163:
From: Lukas Metz @ 2026-06-23 16:07 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Lukas Metz

This series adds an IIO driver for the Texas Instruments DAC7562, DAC7563,
DAC8162, DAC8163, DAC8562, and DAC8563 dual-channel voltage-output DACs.

These devices are pin-compatible 12-, 14-, and 16-bit variants sharing the
same 24-bit SPI command interface. Each device provides two independently
addressable output channels and includes a 2.5 V, 4 ppm/°C internal
reference that can be enabled via device tree, or an external reference
supplied through a regulator. The register and command structure differs
from already existing drivers which makes adding a new driver a
reasonable choice in my opinion.

The driver supports:
 - All six device variants via a shared chip info table
 - DAC updates in synchronous mode
 - Configurable internal or external voltage reference
 - Optional LDAC GPIO which has to be asserted permanently when using
   synchronous updates.
 - IIO_CHAN_INFO_RAW and IIO_CHAN_INFO_SCALE attributes per channel

Datasheet (DAC8163):
  https://www.ti.com/lit/gpn/dac8163

The driver was tested with a DAC8163 on a custom STM32MP157F board with
external reference enabled.

Signed-off-by: Lukas Metz <lukas.metz@gmx.net>
---
Lukas Metz (2):
      iio: dac: dac8163: Add driver for DAC8163
      dt-bindings: iio: dac: Add DAC8163

 .../devicetree/bindings/iio/dac/ti,dac8163.yaml    |  75 +++++
 MAINTAINERS                                        |   7 +
 drivers/iio/dac/Kconfig                            |  10 +
 drivers/iio/dac/Makefile                           |   1 +
 drivers/iio/dac/ti-dac8163.c                       | 339 +++++++++++++++++++++
 5 files changed, 432 insertions(+)
---
base-commit: 76b6720279964612111352ca5d09f5bd61e41ce4
change-id: 20260413-dac8163-work-2138a775b515

Best regards,
--  
Lukas <lukas.metz@gmx.net>


^ permalink raw reply

* [PATCH 2/2] dt-bindings: iio: dac: Add DAC8163
From: Lukas Metz @ 2026-06-23 16:07 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-kernel, linux-iio, devicetree, Lukas Metz
In-Reply-To: <20260623-dac8163-work-v1-0-5b508158faa0@gmx.net>

Add device tree binding for the Texas Instruments DAC8163 family
including the DAC7562, DAC7563, DAC8162, DAC8163, DAC8562 and DAC8563.

Signed-off-by: Lukas Metz <lukas.metz@gmx.net>
---
 .../devicetree/bindings/iio/dac/ti,dac8163.yaml    | 75 ++++++++++++++++++++++
 MAINTAINERS                                        |  1 +
 2 files changed, 76 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/dac/ti,dac8163.yaml b/Documentation/devicetree/bindings/iio/dac/ti,dac8163.yaml
new file mode 100644
index 000000000000..bb4bad389323
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/dac/ti,dac8163.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/dac/ti,dac8163.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments DAC8163 family of DACs
+
+description:
+  The DAC756x, DAC816x, and DAC856x devices are low-power, voltage-output,
+  dual-channel, 12-, 14-, and 16-bit digital-to-analog converters (DACs),
+  respectively. These devices include a 2.5-V, 4-ppm/°C internal
+  reference, giving a full-scale output voltage range of 2.5 V or 5 V.
+
+maintainers:
+  - Lukas Metz <lukas.metz@gmx.net>
+
+properties:
+  compatible:
+    enum:
+      - ti,dac7562
+      - ti,dac7563
+      - ti,dac8162
+      - ti,dac8163
+      - ti,dac8562
+      - ti,dac8563
+
+  reg:
+    maxItems: 1
+
+  ti,loaddacs-gpios:
+    description:
+      Pin needs to be asserted permanently when updating the DAC synchronously.
+    maxItems: 1
+
+  vref-supply:
+    description:
+      Reference voltage for scaling if an external reference is used.
+
+  ti,internal-ref:
+    type: boolean
+    description:
+      Flag if the internal reference is used (external otherwise).
+
+required:
+  - compatible
+  - reg
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+  - if:
+      not:
+        required:
+          - ti,internal-ref
+    then:
+      required:
+        - vref-supply
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        dac@1 {
+            compatible = "ti,dac8163";
+            reg = <0x1>; /* CS1 */
+            ti,loaddacs-gpios = <&gpiog 8 GPIO_ACTIVE_HIGH>;
+            ti,internal-ref; /* internal reference used*/
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index e82cc28e1bc3..5512f5eaab44 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26398,6 +26398,7 @@ TI DAC8163 DAC DRIVER
 M:	Lukas Metz <lukas.metz@gmx.net>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
+F:	Documentation/devicetree/bindings/iio/dac/ti,dac8163.yaml
 F:	drivers/iio/dac/ti-dac8163.c
 
 TI DATA TRANSFORM AND HASHING ENGINE (DTHE) V2 CRYPTO DRIVER

-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150
From: sashiko-bot @ 2026-06-23 16:02 UTC (permalink / raw)
  To: Md Shofiqul Islam; +Cc: robh, devicetree, conor+dt
In-Reply-To: <20260623155556.13701-2-shofiqtest@gmail.com>

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

* Re: [PATCH 0/3] SM8450 IPA support
From: Alex Elder @ 2026-06-23 15:56 UTC (permalink / raw)
  To: esteuwu, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alex Elder
  Cc: linux-arm-msm, devicetree, linux-kernel, netdev
In-Reply-To: <20260622-sm8450-ipa-v1-0-532f0299f96e@proton.me>

On 6/22/26 8:44 PM, Esteban Urrutia via B4 Relay wrote:
> This series adds support for the IPA subsystem found in the SM8450 SoC.
> While IPA v5.0 is very similar to IPA v5.1 (heck, it even managed to
> properly get the modem up and running), it wasn't perfect, since the
> modem would sometimes hang when rebooting or powering the AP off.
> After a thorough investigation, I managed to create the proper data file
> required for IPA v5.1.
> 
> Regards,
> Esteban

I assume you have implemented this based on what you found in
some downstream code.  And if so, could you please indicate
where to find that (so I can do some cross-referencing myself).
I no longer have access to any Qualcomm internal documentation.

Thanks.

					-Alex

> Signed-off-by: Esteban Urrutia <esteuwu@proton.me>
> ---
> Esteban Urrutia (3):
>        arm64: dts: qcom: sm8450: Add IPA support
>        dt-bindings: net: qcom,ipa: Add SM8450 compatible string
>        net: ipa: Add IPA v5.1 data
> 
>   .../devicetree/bindings/net/qcom,ipa.yaml          |   1 +
>   arch/arm64/boot/dts/qcom/sm8450.dtsi               |  55 ++-
>   drivers/net/ipa/Makefile                           |   2 +-
>   drivers/net/ipa/data/ipa_data-v5.1.c               | 477 +++++++++++++++++++++
>   drivers/net/ipa/gsi_reg.c                          |   1 +
>   drivers/net/ipa/ipa_data.h                         |   1 +
>   drivers/net/ipa/ipa_main.c                         |   4 +
>   drivers/net/ipa/ipa_reg.c                          |   1 +
>   8 files changed, 536 insertions(+), 6 deletions(-)
> ---
> base-commit: 948efecf22e49aa4bf55bb73ec79a0ddcfd38571
> change-id: 20260622-sm8450-ipa-5da81f67eb65
> 
> Best regards,
> --
> Esteban Urrutia <esteuwu@proton.me>
> 
> 


^ permalink raw reply

* Re: [PATCH v4 11/12] iio: dac: ad5686: add triggered buffer support
From: Jonathan Cameron @ 2026-06-23 15:56 UTC (permalink / raw)
  To: Rodrigo Alencar via B4 Relay
  Cc: rodrigo.alencar, Michael Auchter, linux, linux-iio, devicetree,
	linux-kernel, linux-hardening, Michael Hennerich, David Lechner,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Philipp Zabel, Kees Cook, Gustavo A. R. Silva
In-Reply-To: <20260623-ad5686-new-features-v4-11-28962a57db0f@analog.com>

On Tue, 23 Jun 2026 11:55:51 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:

> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Implement trigger handler by leveraging the LDAC gpio to update all DAC
> channels at once when it is available. Also, the multiple channel writes
> can be flushed at once with the sync() operation.
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
Hi Rodrigo

A follow up on the rework you did for this version,

Thanks,

Jonathan

> diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
> index db175e77b0b7..4dc681eb077d 100644
> --- a/drivers/iio/dac/ad5686.c
> +++ b/drivers/iio/dac/ad5686.c

> @@ -467,6 +472,60 @@ const struct ad5686_chip_info ad5679r_chip_info = {
>  };
>  EXPORT_SYMBOL_NS_GPL(ad5679r_chip_info, "IIO_AD5686");
>  
> +static irqreturn_t ad5686_trigger_handler(int irq, void *p)
> +{
> +	struct iio_poll_func *pf = p;
> +	struct iio_dev *indio_dev = pf->indio_dev;
> +	struct iio_buffer *buffer = indio_dev->buffer;
> +	struct ad5686_state *st = iio_priv(indio_dev);
> +	u16 val[AD5686_MAX_CHANNELS] = { };
> +	unsigned int scan_count, ch, i;
> +	bool async_update;
> +	int ret;
> +	u8 cmd;
> +
> +	ret = iio_pop_from_buffer(buffer, val);
> +	if (ret) {
> +		iio_trigger_notify_done(indio_dev->trig);

I think I'd prefer a wrapper to this if we are going to always say
it is IRQ_HANDLED (which is reasonable here I think)
Something like the following (unfortunately I only just read the
previous version thread so didn't head off the approach you have here.


static void do_ad5686_trigger_handler(struct iio_dev *indio_dev)
{
	struct iio_buffer *buffer = indio_dev->buffer;
	struct ad5686_state *st = iio_priv(indio_dev);
	u16 val[AD5686_MAX_CHANNELS] = { };
	int ret;

	ret = iio_pop_from_buffer(buffer, val);
	if (ret) {
		iio_trigger_notify_done(indio_dev->trig);

	guard(mutex)(&st->lock);

	guts of current function
}

static irqreturn_t ad5686_trigger_handler(int irq, void *p)
{
	struct iio_poll_func *pf = p;
	struct iio_dev *indio_dev = pf->indio_dev;

	do_ad5686_trigger_handler(indio_dev);
	iio_trigger_notify_done(indio_dev->trig);
	return IRQ_HANDLED;
}



> +		return IRQ_HANDLED;
> +	}
> +
> +	guard(mutex)(&st->lock);
> +
> +	scan_count = bitmap_weight(indio_dev->active_scan_mask,
> +				   iio_get_masklength(indio_dev));
> +	async_update = st->ldac_gpio && scan_count > 1;
> +	if (async_update) {
> +		/* use LDAC to update all channels simultaneously */
> +		cmd = AD5686_CMD_WRITE_INPUT_N;
> +		gpiod_set_value_cansleep(st->ldac_gpio, 0);
> +	} else {
> +		cmd = AD5686_CMD_WRITE_INPUT_N_UPDATE_N;
> +	}
> +
> +	i = 0;
> +	iio_for_each_active_channel(indio_dev, ch) {
> +		ret = st->ops->write(st, cmd, indio_dev->channels[ch].address, val[i++]);
> +		if (ret)
> +			break;
> +	}
> +
> +	/*
> +	 * If sync() is available, it is called here regardless of write
> +	 * failure to allow bus implementation to reset. In that case, partial
> +	 * writes are unlikely as the write operations would just queue up
> +	 * the transfers.
> +	 */
> +	if (st->ops->sync)
> +		st->ops->sync(st);
> +
> +	if (async_update)
> +		gpiod_set_value_cansleep(st->ldac_gpio, 1);
> +
> +	iio_trigger_notify_done(indio_dev->trig);
> +	return IRQ_HANDLED;
> +}


^ permalink raw reply

* [PATCH v2 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver
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
In-Reply-To: <20260623155556.13701-1-shofiqtest@gmail.com>

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

* [PATCH v2 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver
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
In-Reply-To: <20260623155556.13701-1-shofiqtest@gmail.com>

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

* [PATCH v2 1/3] dt-bindings: iio: health: add maxim,max86150
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
In-Reply-To: <20260623155556.13701-1-shofiqtest@gmail.com>

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


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox