Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH v3 06/10] drm/rockchip: dw_dp: Implement out-of-band HPD handling
From: sashiko-bot @ 2026-06-23 20:20 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: robh, conor+dt, dri-devel, devicetree
In-Reply-To: <20260612-synopsys-dw-dp-improvements-v3-6-dc61e6352508@collabora.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Using IS_ERR() to validate the return value of platform_get_mem_or_io() fails to catch NULL, leading to a kernel crash when the resource is missing.
- [Medium] The `dp_dp_rockchip_get_vo_grf` function fails to implement the documented fallback to read the `vo-grf` phandle from the DisplayPort device node.
--

commit 2739e6354d59ce0c07c5ba0c2a12b2779e399b4a
Author: Sebastian Reichel <sebastian.reichel@collabora.com>
drm/rockchip: dw_dp: Implement out-of-band HPD handling

Implement out-of-band hotplug handling to receive external hotplug
information from the USB-C state machine. This properly detects if a cable
is plugged in for adapters.

> diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> index 35598ab9fe84a..9c53f1d2c29ab 100644
> --- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
[ ... ]
> @@ -72,14 +117,49 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
>  	.atomic_check		= dw_dp_encoder_atomic_check,
>  };
>  
> +static struct regmap *dp_dp_rockchip_get_vo_grf(struct rockchip_dw_dp *dp)
> +{
> +	struct device_node *np = dev_of_node(dp->dev);
> +	struct of_phandle_args args;
> +	struct regmap *regmap;
> +	int ret;
> +
> +	ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", 0, &args);
> +	if (ret)
> +		return ERR_PTR(-ENODEV);
> +
> +	/*
> +	 * Limit this workaround to RK3576 and RK3588, new platforms should
> +	 * add a VO GRF phandle in the DisplayPort DT node.
> +	 */
> +	if (!of_device_is_compatible(args.np, "rockchip,rk3576-usbdp-phy") &&
> +	    !of_device_is_compatible(args.np, "rockchip,rk3588-usbdp-phy")) {
> +		regmap = ERR_PTR(-ENODEV);
> +		goto out_put_node;
> +	}
> +
> +	regmap = syscon_regmap_lookup_by_phandle(args.np, "rockchip,vo-grf");

[Severity: Medium]
Does this function actually implement the forward-compatibility behavior
described in the comment?

If a new platform follows the advice and adds a VO GRF phandle directly to
the DisplayPort DT node (dropping the "phys" property), it looks like the
unconditional of_parse_phandle_with_args() check for "phys" will fail and
return ERR_PTR(-ENODEV) early.

Should there be a primary lookup path for "rockchip,vo-grf" on the DP node
itself before falling back to this PHY workaround?

> +
> +out_put_node:
> +	of_node_put(args.np);
> +	return regmap;
> +}
> +
>  static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
>  {
> -	const struct dw_dp_plat_data *plat_data;
> +	const struct rockchip_dw_dp_plat_data *plat_data_const;
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct dw_dp_plat_data *plat_data;
>  	struct drm_device *drm_dev = data;
>  	struct rockchip_dw_dp *dp;
>  	struct drm_encoder *encoder;
>  	struct drm_connector *connector;
> -	int ret;
> +	struct resource *res;
> +	int ret, id;
> +
> +	plat_data = drmm_kzalloc(drm_dev, sizeof(*plat_data), GFP_KERNEL);
> +	if (!plat_data)
> +		return -ENOMEM;
[ ... ]
> +	dp->pdata = plat_data_const;
> +
> +	res = platform_get_mem_or_io(pdev, 0);
> +	if (IS_ERR(res))
> +		return PTR_ERR(res);
> +
> +	/* find the DisplayPort ID from the io address */
> +	dp->id = -ENODEV;
> +	for (id = 0; id < plat_data_const->num_ctrls; id++) {
> +		if (res->start == plat_data_const->ctrl_ids[id]) {
> +			dp->id = id;
> +			break;
> +		}
> +	}

[Severity: High]
Can this code cause a NULL pointer dereference if the resource is missing
from the device tree?

Looking at platform_get_mem_or_io(), it returns NULL when the resource is
not found, rather than an ERR_PTR. Because IS_ERR(NULL) evaluates to false,
the check "if (IS_ERR(res))" would let a NULL pointer slip through. This
NULL pointer would then be dereferenced when accessing res->start in the
loop.

Should this be updated to check "if (!res)" instead?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com?part=6

^ permalink raw reply

* Re: [PATCH v3 05/10] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD
From: sashiko-bot @ 2026-06-23 20:11 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: dri-devel, robh, conor+dt, devicetree
In-Reply-To: <20260612-synopsys-dw-dp-improvements-v3-5-dc61e6352508@collabora.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [High] Reference leak of the downstream bridge in `dw_dp_bind()` on both success and error paths.
- [High] The driver incorrectly treats the absence of a downstream bridge as a fatal error, breaking backward compatibility.

Pre-existing issues:
- [High] Missing `cancel_work_sync()` for `dp->hpd_work` during unbind leads to a Use-After-Free.
--

commit 121b9fc711d52bcdbd8b50a6dc50a5308864d167
Author: Sebastian Reichel <sebastian.reichel@collabora.com>

drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD

Add support for USB-C DP AltMode out-of-band hotplug handling. The
handling itself is implemented in the platform specific driver as the
registers to force HPD state are not part of the Designware DisplayPort
IP itself. Instead the platform integration might provide the necessary
functionality to mux the HPD signal.

> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index 98cff435dfb87..7fa38145e35ce 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c

[ ... ]

> @@ -2078,6 +2107,13 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
>  		goto unregister_aux;
>  	}

[Severity: High]
Does dw_dp_bind() leak the reference to the downstream bridge?

of_drm_get_bridge_by_endpoint() returns a bridge pointer with an incremented 
reference count, which is stored in next_bridge:

	next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, 0);
	if (IS_ERR(next_bridge)) {
		ret = PTR_ERR(next_bridge);
        ...

The reference is not dropped after drm_bridge_attach() succeeds, nor is it 
dropped on the error path before jumping to unregister_aux. Could this leak 
the reference on both success and error paths, preventing the downstream 
bridge from cleanly unloading?


[Severity: High]
Should the absence of a downstream bridge be treated as a fatal error?

If the device tree defines the DP controller as the end of the display chain
(i.e., no endpoint 1 is defined), of_drm_get_bridge_by_endpoint() returns
-ENODEV. The code unconditionally aborts initialization here instead of 
gracefully ignoring it:

	next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, 0);
	if (IS_ERR(next_bridge)) {
		ret = PTR_ERR(next_bridge);
		dev_err_probe(dev, ret, "failed to get follow-up bridge.\n");
		goto unregister_aux;
	}

Could this break backward compatibility and cause the driver to fail to probe 
on boards that do not define a downstream bridge?

>  
> +	if (dw_dp_is_routed_to_usb_c(encoder)) {
> +		dev_dbg(dev, "USB-C mode\n");
> +
> +		if (dp->plat_data.hpd_sw_sel)
> +			dp->plat_data.hpd_sw_sel(dp->plat_data.data, 1);
> +	}
> +
>  	dw_dp_init_hw(dp);

[ ... ]

[Severity: High]
This isn't a bug introduced by this patch, but does dw_dp_unbind() leave 
pending work scheduled?

During bind, the driver initializes dp->hpd_work and schedules it in the 
interrupt handler dw_dp_handle_hpd_event(). However, dw_dp_unbind() does not 
cancel it:

void dw_dp_unbind(struct dw_dp *dp)
{
	drm_dp_aux_unregister(&dp->aux);
}

Because the dp context is allocated with devm_drm_bridge_alloc(), it is 
automatically freed shortly after unbind returns. If the scheduled work 
executes after dp is freed, could it cause a use-after-free in 
dw_dp_hpd_work()?

Should cancel_work_sync(&dp->hpd_work) be added to dw_dp_unbind()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com?part=5

^ permalink raw reply

* [PATCH v5 2/3] iio: health: add MAX86150 ECG and PPG biosensor driver
From: Md Shofiqul Islam @ 2026-06-23 20:11 UTC (permalink / raw)
  To: linux-iio
  Cc: jic23, lars, conor, conor+dt, robh, krzk+dt, devicetree,
	linux-kernel, Md Shofiqul Islam
In-Reply-To: <20260623201124.18271-1-shofiqtest@gmail.com>

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

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

Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com>
---
 drivers/iio/health/Kconfig    |  23 ++
 drivers/iio/health/Makefile   |   1 +
 drivers/iio/health/max86150.c | 730 ++++++++++++++++++++++++++++++++++
 3 files changed, 754 insertions(+)
 create mode 100644 drivers/iio/health/max86150.c

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..92bf31f0a761
--- /dev/null
+++ b/drivers/iio/health/max86150.c
@@ -0,0 +1,730 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MAX86150 combined ECG and PPG biosensor driver
+ *
+ * Copyright (C) 2026 Md Shofiqul Islam <shofiqtest@gmail.com>
+ *
+ * The MAX86150 integrates two PPG optical channels (Red/IR LED) and one
+ * ECG biopotential channel in a single I2C device.  Data is captured
+ * through a 32-entry hardware FIFO with a configurable almost-full
+ * interrupt, making it well-suited for continuous monitoring with a
+ * low-power host.
+ *
+ * Datasheet:
+ *   https://www.analog.com/media/en/technical-documentation/data-sheets/MAX86150.pdf
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+/* Register addresses */
+#define MAX86150_REG_INT_STATUS1	0x00
+#define MAX86150_REG_INT_STATUS2	0x01
+#define MAX86150_REG_INT_ENABLE1	0x02
+#define MAX86150_REG_INT_ENABLE2	0x03
+#define MAX86150_REG_FIFO_WR_PTR	0x04
+#define MAX86150_REG_OVF_COUNTER	0x05
+#define MAX86150_REG_FIFO_RD_PTR	0x06
+#define MAX86150_REG_FIFO_DATA		0x07
+#define MAX86150_REG_FIFO_CONFIG	0x08
+#define MAX86150_REG_FIFO_DCTRL1	0x09  /* FD1[3:0]  FD2[7:4] */
+#define MAX86150_REG_FIFO_DCTRL2	0x0A  /* FD3[3:0]  FD4[7:4] */
+#define MAX86150_REG_SYS_CTRL		0x0D
+#define MAX86150_REG_PPG_CONFIG1	0x10
+#define MAX86150_REG_PPG_CONFIG2	0x11
+#define MAX86150_REG_LED1_PA		0x14  /* Red LED pulse amplitude */
+#define MAX86150_REG_LED2_PA		0x15  /* IR LED pulse amplitude  */
+#define MAX86150_REG_ECG_CONFIG1	0x3C
+#define MAX86150_REG_ECG_CONFIG3	0x3E
+#define MAX86150_REG_PART_ID		0xFF
+
+/* Field masks */
+#define MAX86150_PART_ID_VAL		0x1E
+
+/* INT_STATUS1 / INT_ENABLE1 */
+#define MAX86150_INT_A_FULL		BIT(7)  /* FIFO almost full */
+#define MAX86150_INT_PPG_RDY		BIT(6)  /* new PPG sample ready */
+
+/* SYS_CTRL */
+#define MAX86150_SYS_SHDN		BIT(1)
+#define MAX86150_SYS_RESET		BIT(0)
+
+/* FIFO_CONFIG */
+#define MAX86150_FIFO_SMP_AVE		GENMASK(7, 5)
+#define MAX86150_FIFO_ROLLOVER_EN	BIT(4)
+#define MAX86150_FIFO_A_FULL		GENMASK(3, 0)
+
+/* FIFO slot data-type codes */
+#define MAX86150_FD_NONE		0x0
+#define MAX86150_FD_LED1		0x1   /* Red PPG */
+#define MAX86150_FD_LED2		0x2   /* IR PPG  */
+#define MAX86150_FD_ECG			0x9
+#define MAX86150_FIFO_FD1		GENMASK(3, 0)
+#define MAX86150_FIFO_FD2		GENMASK(7, 4)
+#define MAX86150_FIFO_FD3		GENMASK(3, 0)
+#define MAX86150_FIFO_FD4		GENMASK(7, 4)
+
+/* PPG_CONFIG1 */
+#define MAX86150_PPG_ADC_RGE		GENMASK(7, 6)
+#define MAX86150_PPG_SR			GENMASK(5, 1)
+
+/* Geometry */
+#define MAX86150_FIFO_DEPTH		32
+#define MAX86150_BYTES_PER_SLOT		3    /* 24-bit word per slot */
+#define MAX86150_NUM_SLOTS		3    /* Red, IR, ECG */
+#define MAX86150_SAMPLE_BYTES		(MAX86150_NUM_SLOTS * MAX86150_BYTES_PER_SLOT)
+/* Samples present in the FIFO when the A_FULL interrupt fires */
+#define MAX86150_A_FULL_SAMPLES		(MAX86150_FIFO_DEPTH - MAX86150_FIFO_A_FULL_VAL)
+
+/* Default hardware configuration */
+#define MAX86150_LED_PA_DEFAULT		0x3F  /* ~50 mA */
+#define MAX86150_PPG_SR_100HZ		4     /* PPG_SR field value for 100 Hz */
+#define MAX86150_PPG_ADC_RGE_16384	2     /* 16384 nA full scale */
+/* Fire A_FULL when 17 slots remain (32 - 15 = 17 samples in FIFO) */
+#define MAX86150_FIFO_A_FULL_VAL	15
+
+/* Scan element indices */
+enum max86150_scan_idx {
+	MAX86150_IDX_PPG_RED = 0,
+	MAX86150_IDX_PPG_IR  = 1,
+	MAX86150_IDX_ECG     = 2,
+	MAX86150_IDX_TS,
+};
+
+/**
+ * struct max86150_data - driver private state
+ * @regmap:           register map for this device
+ * @dev:              parent device (for dev_err logging)
+ * @trig:             IIO hardware trigger backed by the device interrupt line
+ * @sample_period_ns: sample period in nanoseconds (set from configured rate)
+ * @fifo_raw:         DMA-safe buffer for regmap_noinc_read() FIFO bursts.
+ *                    Padded to ARCH_DMA_MINALIGN bytes so that @buf starts
+ *                    in the next cacheline and the two fields never share a
+ *                    cacheline -- required by CONFIG_DMA_API_DEBUG.
+ * @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[ALIGN(MAX86150_SAMPLE_BYTES, ARCH_DMA_MINALIGN)]
+				 __aligned(ARCH_DMA_MINALIGN);
+	s32 buf[6] __aligned(8);
+};
+
+/* IIO channel specification */
+
+static const struct iio_chan_spec max86150_channels[] = {
+	{
+		/* PPG Red LED - optical intensity, 19-bit unsigned */
+		.type               = IIO_INTENSITY,
+		.modified           = 1,
+		.channel2           = IIO_MOD_LIGHT_RED,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.scan_index         = MAX86150_IDX_PPG_RED,
+		.scan_type = {
+			.sign        = 'u',
+			.realbits    = 19,
+			.storagebits = 32,
+			.endianness  = IIO_CPU,
+		},
+	},
+	{
+		/* PPG IR LED - optical intensity, 19-bit unsigned */
+		.type               = IIO_INTENSITY,
+		.modified           = 1,
+		.channel2           = IIO_MOD_LIGHT_IR,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.scan_index         = MAX86150_IDX_PPG_IR,
+		.scan_type = {
+			.sign        = 'u',
+			.realbits    = 19,
+			.storagebits = 32,
+			.endianness  = IIO_CPU,
+		},
+	},
+	{
+		/* ECG biopotential - voltage, 18-bit signed two's complement */
+		.type               = IIO_VOLTAGE,
+		.channel            = 0,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.scan_index         = MAX86150_IDX_ECG,
+		.scan_type = {
+			.sign        = 's',
+			.realbits    = 18,
+			.storagebits = 32,
+			.endianness  = IIO_CPU,
+		},
+	},
+	IIO_CHAN_SOFT_TIMESTAMP(MAX86150_IDX_TS),
+};
+
+/* Regmap configuration */
+
+static const struct regmap_config max86150_regmap_config = {
+	.reg_bits     = 8,
+	.val_bits     = 8,
+	.max_register = MAX86150_REG_PART_ID,
+};
+
+/* FIFO helper */
+
+/**
+ * max86150_read_one_sample - burst-read one complete 3-slot FIFO entry
+ * @data:    driver state
+ * @ppg_red: out - 19-bit PPG Red ADC value (unsigned)
+ * @ppg_ir:  out - 19-bit PPG IR ADC value (unsigned)
+ * @ecg:     out - 18-bit ECG ADC value (sign-extended to s32)
+ *
+ * Each FIFO entry is 9 bytes (3 slots x 3 bytes).  FIFO_DATA is a
+ * streaming register - the address does not auto-increment on each
+ * byte, so regmap_noinc_read() is used instead of regmap_bulk_read().
+ *
+ * Byte layout in the 24-bit FIFO word (MSB first):
+ *   PPG 19-bit unsigned: bits [18:0], top 5 bits are always zero
+ *   ECG 18-bit signed:   bits [17:0], top 6 bits are sign extension
+ */
+static int max86150_read_one_sample(struct max86150_data *data,
+				    u32 *ppg_red, u32 *ppg_ir, s32 *ecg)
+{
+	int ret;
+
+	/*
+	 * Use data->fifo_raw (heap memory) not a local array so the buffer is
+	 * DMA-mappable for I2C host controllers that use DMA for burst reads.
+	 */
+	ret = regmap_noinc_read(data->regmap, MAX86150_REG_FIFO_DATA,
+				data->fifo_raw, sizeof(data->fifo_raw));
+	if (ret)
+		return ret;
+
+	/* Bytes [0..2]: PPG Red - 19-bit value in bits [18:0] */
+	*ppg_red = (u32)(data->fifo_raw[0] & 0x07) << 16 |
+		   (u32)data->fifo_raw[1] << 8 | data->fifo_raw[2];
+
+	/* Bytes [3..5]: PPG IR - same format */
+	*ppg_ir  = (u32)(data->fifo_raw[3] & 0x07) << 16 |
+		   (u32)data->fifo_raw[4] << 8 | data->fifo_raw[5];
+
+	/* Bytes [6..8]: ECG - 18-bit signed, sign-extend to s32 */
+	*ecg = sign_extend32((u32)(data->fifo_raw[6] & 0x03) << 16 |
+			     (u32)data->fifo_raw[7] << 8 | data->fifo_raw[8], 17);
+
+	return 0;
+}
+
+/* IIO read_raw */
+
+static int max86150_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct max86150_data *data = iio_priv(indio_dev);
+	unsigned int ppg_rdy_status;
+	u32 ppg_red, ppg_ir;
+	s32 ecg;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		/*
+		 * Claim direct mode to prevent concurrent sysfs reads from
+		 * corrupting the FIFO pointers while a triggered buffer
+		 * capture is active.
+		 */
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+
+		/*
+		 * Single-shot path: wake the device, flush stale FIFO data,
+		 * wait one sample period, read, then return to shutdown so
+		 * the LEDs are not drawing current when idle.
+		 */
+		ret = regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+					 MAX86150_SYS_SHDN, 0);
+		if (!ret)
+			ret = regmap_write(data->regmap,
+					   MAX86150_REG_FIFO_WR_PTR, 0);
+		if (!ret)
+			ret = regmap_write(data->regmap,
+					   MAX86150_REG_OVF_COUNTER, 0);
+		if (!ret)
+			ret = regmap_write(data->regmap,
+					   MAX86150_REG_FIFO_RD_PTR, 0);
+		if (ret) {
+			regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+					   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+			iio_device_release_direct(indio_dev);
+			return ret;
+		}
+
+		/*
+		 * Poll PPG_RDY rather than sleeping a fixed interval -- the
+		 * internal oscillator may start slower than nominal, leaving
+		 * the FIFO empty if we read too early.  25 ms timeout covers
+		 * more than two 100 Hz sample periods.
+		 */
+		ret = regmap_read_poll_timeout(data->regmap,
+					       MAX86150_REG_INT_STATUS1,
+					       ppg_rdy_status,
+					       ppg_rdy_status & MAX86150_INT_PPG_RDY,
+					       1000, 25000);
+		if (ret) {
+			regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+					   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+			iio_device_release_direct(indio_dev);
+			return ret;
+		}
+
+		ret = max86150_read_one_sample(data, &ppg_red, &ppg_ir, &ecg);
+		regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+		iio_device_release_direct(indio_dev);
+		if (ret)
+			return ret;
+
+		switch (chan->scan_index) {
+		case MAX86150_IDX_PPG_RED:
+			*val = ppg_red;
+			break;
+		case MAX86150_IDX_PPG_IR:
+			*val = ppg_ir;
+			break;
+		case MAX86150_IDX_ECG:
+			*val = ecg;
+			break;
+		default:
+			return -EINVAL;
+		}
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info max86150_iio_info = {
+	.read_raw         = max86150_read_raw,
+	.validate_trigger = iio_trigger_validate_own_device,
+};
+
+/* Trigger */
+
+/*
+ * Control device power and the FIFO almost-full interrupt when the IIO
+ * triggered buffer is started (state=true) or stopped (state=false).
+ *
+ * On start: wake from shutdown, flush stale FIFO data so the first
+ * samples pushed to userspace are from after buffer enable, then
+ * unmask the A_FULL interrupt.
+ *
+ * On stop: mask the interrupt, then return to shutdown so the LED
+ * drivers do not draw current while capture is inactive.
+ */
+static int max86150_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct max86150_data *data = iio_priv(indio_dev);
+	int ret;
+
+	if (!state) {
+		ret = regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1, 0);
+		regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+		return ret;
+	}
+
+	ret = regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				 MAX86150_SYS_SHDN, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_FIFO_WR_PTR, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_OVF_COUNTER, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_FIFO_RD_PTR, 0);
+	if (!ret)
+		ret = regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1,
+				   MAX86150_INT_A_FULL);
+	if (ret)
+		regmap_update_bits(data->regmap, MAX86150_REG_SYS_CTRL,
+				   MAX86150_SYS_SHDN, MAX86150_SYS_SHDN);
+	return ret;
+}
+
+static const struct iio_trigger_ops max86150_trigger_ops = {
+	.set_trigger_state = max86150_set_trigger_state,
+};
+
+/* Triggered buffer */
+
+/**
+ * max86150_trigger_handler - threaded IRQ handler for FIFO almost-full
+ *
+ * Called by the IIO buffer infrastructure when the hardware trigger fires.
+ * Reads INT_STATUS1 to de-assert the interrupt, then drains all available
+ * FIFO samples into the IIO push buffer, packing only the channels that
+ * are currently enabled in active_scan_mask.
+ */
+static irqreturn_t max86150_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func	*pf   = p;
+	struct iio_dev		*idev = pf->indio_dev;
+	struct max86150_data	*data = iio_priv(idev);
+	unsigned int status, wr_ptr, rd_ptr, ovf;
+	u32 ppg_red, ppg_ir;
+	s32 ecg;
+	s64 t_drain = 0;
+	int ret, n_avail, i, j;
+
+	/*
+	 * Reading INT_STATUS1 clears the interrupt.  Do this before touching
+	 * the FIFO so the pin is de-asserted while we drain samples.
+	 */
+	ret = regmap_read(data->regmap, MAX86150_REG_INT_STATUS1, &status);
+	if (ret)
+		goto done;
+
+	ret = regmap_read(data->regmap, MAX86150_REG_FIFO_WR_PTR, &wr_ptr);
+	if (ret)
+		goto done;
+	ret = regmap_read(data->regmap, MAX86150_REG_FIFO_RD_PTR, &rd_ptr);
+	if (ret)
+		goto done;
+
+	/*
+	 * OVF_COUNTER: if non-zero the FIFO overflowed; drain all 32 slots.
+	 * When wr_ptr == rd_ptr with no overflow the FIFO could be empty OR
+	 * hold exactly MAX86150_FIFO_DEPTH entries (pointer wrap-around).
+	 * Use the A_FULL status bit to disambiguate: if the IRQ fired for
+	 * A_FULL but the computed count is zero, the FIFO wrapped to full.
+	 */
+	ret = regmap_read(data->regmap, MAX86150_REG_OVF_COUNTER, &ovf);
+	if (ret)
+		goto done;
+
+	if (ovf > 0) {
+		n_avail = MAX86150_FIFO_DEPTH;
+		t_drain = ktime_get_ns();
+	} else {
+		n_avail = (wr_ptr - rd_ptr) & (MAX86150_FIFO_DEPTH - 1);
+		if (n_avail == 0 && (status & MAX86150_INT_A_FULL))
+			n_avail = MAX86150_FIFO_DEPTH;
+	}
+
+	/*
+	 * In the normal (no-overflow) case, anchor timestamps to the A_FULL
+	 * IRQ capture time: sample index (A_FULL_SAMPLES - 1) corresponds to
+	 * pf->timestamp, eliminating scheduling-latency jitter.
+	 *
+	 * In the overflow case the hardware has overwritten the oldest samples;
+	 * pf->timestamp reflects an earlier A_FULL event and is no longer a
+	 * valid anchor.  t_drain (set above) is used as a best-effort reference
+	 * for the newest surviving sample instead.
+	 */
+	for (i = 0; i < n_avail; i++) {
+		s64 ts;
+
+		if (ovf > 0)
+			ts = t_drain -
+			     (s64)(n_avail - 1 - i) * data->sample_period_ns;
+		else
+			ts = pf->timestamp +
+			     (s64)(i - (MAX86150_A_FULL_SAMPLES - 1)) *
+			     data->sample_period_ns;
+
+		ret = max86150_read_one_sample(data, &ppg_red, &ppg_ir, &ecg);
+		if (ret)
+			break;
+
+		/*
+		 * Zero the entire buffer before packing so padding bytes
+		 * between enabled channels do not leak previous sample data
+		 * to userspace when fewer than 3 channels are active.
+		 */
+		memset(data->buf, 0, sizeof(data->buf));
+
+		/*
+		 * Pack only active channels at consecutive positions [0..j-1].
+		 * iio_push_to_buffers_with_timestamp() uses scan_bytes (which
+		 * accounts for the active channel count) to place the timestamp,
+		 * so static indexing would misplace it when fewer than 3
+		 * channels are enabled.
+		 */
+		j = 0;
+		if (test_bit(MAX86150_IDX_PPG_RED, idev->active_scan_mask))
+			data->buf[j++] = ppg_red;
+		if (test_bit(MAX86150_IDX_PPG_IR, idev->active_scan_mask))
+			data->buf[j++] = ppg_ir;
+		if (test_bit(MAX86150_IDX_ECG, idev->active_scan_mask))
+			data->buf[j++] = ecg;
+
+		iio_push_to_buffers_with_timestamp(idev, data->buf, ts);
+	}
+
+done:
+	iio_trigger_notify_done(idev->trig);
+	return IRQ_HANDLED;
+}
+
+/* Chip initialisation / teardown */
+
+static void max86150_powerdown(void *arg)
+{
+	struct max86150_data *data = arg;
+
+	regmap_write(data->regmap, MAX86150_REG_INT_ENABLE1, 0);
+	regmap_write(data->regmap, MAX86150_REG_SYS_CTRL, MAX86150_SYS_SHDN);
+}
+
+static int max86150_chip_init(struct max86150_data *data)
+{
+	int ret;
+
+	/* Software reset; the bit self-clears within 1 ms */
+	ret = regmap_write(data->regmap, MAX86150_REG_SYS_CTRL,
+			   MAX86150_SYS_RESET);
+	if (ret)
+		return ret;
+	usleep_range(1000, 2000);
+
+	/*
+	 * FIFO: no sample averaging, rollover enabled, assert A_FULL when
+	 * 17 samples are in the FIFO (32 - 15 = 17 available to read).
+	 */
+	ret = regmap_write(data->regmap, MAX86150_REG_FIFO_CONFIG,
+			   MAX86150_FIFO_ROLLOVER_EN |
+			   FIELD_PREP(MAX86150_FIFO_A_FULL,
+				      MAX86150_FIFO_A_FULL_VAL));
+	if (ret)
+		return ret;
+
+	/* Slot 1 = PPG Red (LED1), Slot 2 = PPG IR (LED2) */
+	ret = regmap_write(data->regmap, MAX86150_REG_FIFO_DCTRL1,
+			   FIELD_PREP(MAX86150_FIFO_FD1, MAX86150_FD_LED1) |
+			   FIELD_PREP(MAX86150_FIFO_FD2, MAX86150_FD_LED2));
+	if (ret)
+		return ret;
+
+	/* Slot 3 = ECG, Slot 4 = disabled */
+	ret = regmap_write(data->regmap, MAX86150_REG_FIFO_DCTRL2,
+			   FIELD_PREP(MAX86150_FIFO_FD3, MAX86150_FD_ECG) |
+			   FIELD_PREP(MAX86150_FIFO_FD4, MAX86150_FD_NONE));
+	if (ret)
+		return ret;
+
+	/* PPG: 100 Hz sample rate, 16384 nA ADC full-scale range */
+	ret = regmap_write(data->regmap, MAX86150_REG_PPG_CONFIG1,
+			   FIELD_PREP(MAX86150_PPG_ADC_RGE,
+				      MAX86150_PPG_ADC_RGE_16384) |
+			   FIELD_PREP(MAX86150_PPG_SR,
+				      MAX86150_PPG_SR_100HZ));
+	if (ret)
+		return ret;
+
+	/* LED pulse amplitudes (~50 mA) */
+	ret = regmap_write(data->regmap, MAX86150_REG_LED1_PA,
+			   MAX86150_LED_PA_DEFAULT);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(data->regmap, MAX86150_REG_LED2_PA,
+			   MAX86150_LED_PA_DEFAULT);
+	if (ret)
+		return ret;
+
+	/*
+	 * Record sample period for timestamp reconstruction in the trigger
+	 * handler.  The PPG_SR field is fixed to 100 Hz in this driver.
+	 */
+	data->sample_period_ns = 10000000; /* 100 Hz = 10 ms */
+
+	/*
+	 * Assert SYS_SHDN so the LED drivers do not draw current while
+	 * the driver is bound but no capture is active.
+	 * set_trigger_state() clears SHDN when the IIO buffer is enabled
+	 * and re-asserts it when disabled.  read_raw() wakes and sleeps
+	 * the device around each single-shot read.
+	 */
+	return regmap_write(data->regmap, MAX86150_REG_SYS_CTRL,
+			    MAX86150_SYS_SHDN);
+}
+
+/* Probe */
+
+static int max86150_probe(struct i2c_client *client)
+{
+	struct iio_dev		*indio_dev;
+	struct max86150_data	*data;
+	unsigned int		 part_id;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data      = iio_priv(indio_dev);
+	data->dev = &client->dev;
+
+	/*
+	 * Enable power supplies before any I2C access.  Both supplies are
+	 * optional in the device tree; use _optional variant so probing
+	 * succeeds on boards that power the device from fixed rails with no
+	 * DT regulator node.
+	 */
+	ret = devm_regulator_get_enable_optional(&client->dev, "vdd");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to get/enable vdd supply\n");
+
+	ret = devm_regulator_get_enable_optional(&client->dev, "avdd");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to get/enable avdd supply\n");
+
+	ret = devm_regulator_get_enable_optional(&client->dev, "vref");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to get/enable vref supply\n");
+
+	ret = devm_regulator_get_enable_optional(&client->dev, "leds");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to get/enable leds supply\n");
+
+	data->regmap = devm_regmap_init_i2c(client, &max86150_regmap_config);
+	if (IS_ERR(data->regmap))
+		return dev_err_probe(&client->dev, PTR_ERR(data->regmap),
+				     "Failed to initialise regmap\n");
+
+	ret = regmap_read(data->regmap, MAX86150_REG_PART_ID, &part_id);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Cannot read part ID\n");
+
+	if (part_id != MAX86150_PART_ID_VAL)
+		return dev_err_probe(&client->dev, -ENODEV,
+				     "Unexpected part ID 0x%02x (expected 0x%02x)\n",
+				     part_id, MAX86150_PART_ID_VAL);
+
+	ret = max86150_chip_init(data);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Chip initialisation failed\n");
+
+	ret = devm_add_action_or_reset(&client->dev, max86150_powerdown, data);
+	if (ret)
+		return ret;
+
+	indio_dev->name         = "max86150";
+	indio_dev->channels     = max86150_channels;
+	indio_dev->num_channels = ARRAY_SIZE(max86150_channels);
+	indio_dev->info         = &max86150_iio_info;
+	indio_dev->modes        = INDIO_DIRECT_MODE;
+
+	/*
+	 * If the device tree provides an interrupt, set up a hardware
+	 * trigger so userspace can use the FIFO almost-full signal to
+	 * drive capture without polling.
+	 */
+	if (client->irq > 0) {
+		unsigned long irq_trig;
+
+		data->trig = devm_iio_trigger_alloc(&client->dev,
+						    "%s-dev%d",
+						    indio_dev->name,
+						    iio_device_id(indio_dev));
+		if (!data->trig)
+			return -ENOMEM;
+
+		data->trig->ops = &max86150_trigger_ops;
+		iio_trigger_set_drvdata(data->trig, indio_dev);
+
+		/*
+		 * Honour the interrupt trigger type from the device tree.
+		 * Fall back to falling-edge if the DT does not specify one.
+		 *
+		 * Use a threaded IRQ with IRQF_ONESHOT so that level-triggered
+		 * lines are kept masked until max86150_trigger_handler reads
+		 * INT_STATUS1 and de-asserts the interrupt.  A hard handler
+		 * returning IRQ_HANDLED without clearing the source would cause
+		 * an immediate re-fire loop on active-low level interrupts.
+		 */
+		irq_trig = irq_get_trigger_type(client->irq);
+		if (!irq_trig)
+			irq_trig = IRQF_TRIGGER_FALLING;
+
+		ret = devm_request_threaded_irq(&client->dev, client->irq,
+						NULL,
+						iio_trigger_generic_data_rdy_poll,
+						irq_trig | IRQF_ONESHOT,
+						"max86150", data->trig);
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot request IRQ %d\n",
+					     client->irq);
+
+		ret = devm_iio_trigger_register(&client->dev, data->trig);
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Cannot register trigger\n");
+
+		/*
+		 * Set the default trigger so userspace can enable the buffer
+		 * without a manual current_trigger write.  iio_device_unregister()
+		 * calls iio_trigger_put() to release this reference.
+		 */
+		indio_dev->trig = iio_trigger_get(data->trig);
+	}
+
+	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 = "adi,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 v5 3/3] MAINTAINERS: add entry for MAX86150 IIO health driver
From: Md Shofiqul Islam @ 2026-06-23 20:11 UTC (permalink / raw)
  To: linux-iio
  Cc: jic23, lars, conor, conor+dt, robh, krzk+dt, devicetree,
	linux-kernel, Md Shofiqul Islam
In-Reply-To: <20260623201124.18271-1-shofiqtest@gmail.com>

Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 3115538ce829..361a7c8b99ea 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
 
+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/adi,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 v5 1/3] dt-bindings: iio: health: add adi,max86150
From: Md Shofiqul Islam @ 2026-06-23 20:11 UTC (permalink / raw)
  To: linux-iio
  Cc: jic23, lars, conor, conor+dt, robh, krzk+dt, devicetree,
	linux-kernel, Md Shofiqul Islam
In-Reply-To: <20260623201124.18271-1-shofiqtest@gmail.com>

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

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

Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com>
---
 .../bindings/iio/health/adi,max86150.yaml     | 78 +++++++++++++++++++
 1 file changed, 78 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/adi,max86150.yaml

diff --git a/Documentation/devicetree/bindings/iio/health/adi,max86150.yaml b/Documentation/devicetree/bindings/iio/health/adi,max86150.yaml
new file mode 100644
index 000000000000..c191f4f1525b
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/health/adi,max86150.yaml
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/health/adi,max86150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices MAX86150 ECG and PPG Biosensor
+
+maintainers:
+  - Md Shofiqul Islam <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: adi,max86150
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+    description: |
+      Active-low interrupt line.  Asserted when the FIFO almost-full
+      threshold is reached or when a new PPG sample is ready.
+
+  vdd-supply:
+    description: Digital core power supply (1.8 V).
+
+  avdd-supply:
+    description: Analog core power supply (1.8 V).
+
+  vref-supply:
+    description: ECG reference voltage supply.
+
+  leds-supply:
+    description: LED anode supply, typically 3.3 V.
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+  - avdd-supply
+  - vref-supply
+  - leds-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        heart-rate@5e {
+            compatible = "adi,max86150";
+            reg = <0x5e>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
+            vdd-supply = <&vdd_1v8>;
+            avdd-supply = <&vdd_1v8>;
+            vref-supply = <&vdd_1v8>;
+            leds-supply = <&vdd_3v3>;
+        };
+    };
-- 
2.51.1


^ permalink raw reply related

* [PATCH v5 0/3] iio: health: add MAX86150 ECG and PPG biosensor driver
From: Md Shofiqul Islam @ 2026-06-23 20:11 UTC (permalink / raw)
  To: linux-iio
  Cc: jic23, lars, conor, conor+dt, robh, krzk+dt, devicetree,
	linux-kernel, Md Shofiqul Islam
In-Reply-To: <20260623174600.17100-1-shofiqtest@gmail.com>

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

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

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

Link: https://lore.kernel.org/linux-iio/20260623174600.17100-1-shofiqtest@gmail.com/
  v4 cover letter

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

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

-- 
2.51.1


^ permalink raw reply

* Re: [PATCH v5 3/7] drm/rcar-du: dsc: Add rudimentary Renesas R-Car V4H DSC driver
From: sashiko-bot @ 2026-06-23 20:01 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: robh, conor+dt, wsa+renesas, devicetree, dri-devel
In-Reply-To: <20260615-rcar-du-dsc-v5-3-aed1a28610e4@ideasonboard.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The DRM bridge remains registered and globally accessible after `pm_runtime_disable()` is called, creating a teardown order race during driver removal.
- [Medium] The sleeping function `pm_runtime_get_sync()` is called from an atomic context path (`rcar_dsc_atomic_enable`).
--

commit a8616c71b764dc9fa4ae244e47795371e7fead44
Author: Marek Vasut <marek.vasut+renesas@mailbox.org>

drm/rcar-du: dsc: Add rudimentary Renesas R-Car V4H DSC driver

The Renesas DSC Display Stream Compression is a bridge embedded in the
Renesas R-Car V4H SoC. The bridge performs VESA DSC encoding of up to
8k or 400 Mpixel/s. Add rudimentary driver, which currently acts as a
pass-through bridge and allows DSI1 to be operational on R-Car V4H.

> diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_dsc.c b/drivers/gpu/drm/renesas/rcar-du/rcar_dsc.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_dsc.c

[ ... ]

> +static void rcar_dsc_atomic_enable(struct drm_bridge *bridge,
> +				   struct drm_atomic_commit *commit)
> +{
> +	struct rcar_dsc *dsc = bridge_to_rcar_dsc(bridge);
> +
> +	WARN_ON(pm_runtime_get_sync(dsc->dev) < 0);

[Severity: Medium]
Is it safe to call the sleeping function pm_runtime_get_sync() here?

Since rcar_dsc_atomic_enable() is called during non-blocking atomic commits,
doesn't this path execute in atomic context where sleeping is forbidden and
can lead to kernel warnings or deadlocks?

> +}

[ ... ]

> +static int rcar_dsc_probe(struct platform_device *pdev)
> +{

[ ... ]

> +	pm_runtime_enable(&pdev->dev);
> +
> +	ret = devm_drm_bridge_add(dev, &dsc->bridge);
> +	if (ret)
> +		goto err_runtime_disable;
> +
> +	return 0;
> +
> +err_runtime_disable:
> +	pm_runtime_disable(&pdev->dev);
> +
> +	return ret;
> +}
> +
> +static void rcar_dsc_remove(struct platform_device *pdev)
> +{
> +	pm_runtime_disable(&pdev->dev);
> +}

[Severity: High]
Does this create a teardown order race during driver removal?

Because devm_drm_bridge_add() defers unregistration until after
rcar_dsc_remove() returns, the bridge remains globally accessible to the DRM
subsystem after pm_runtime_disable() has already shut down power management.

If a concurrent modeset calls rcar_dsc_atomic_enable() during this window,
won't pm_runtime_get_sync() return -EACCES and trigger the WARN_ON since
power management is disabled?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615-rcar-du-dsc-v5-0-aed1a28610e4@ideasonboard.com?part=3

^ permalink raw reply

* [PATCH 5/5] arm64: dts: ti: k3-am62-verdin: Add Mezzanine with Toradex Display 10.1" LVDS
From: Leonardo Costa @ 2026-06-23 19:57 UTC (permalink / raw)
  To: laurent.pinchart, neil.armstrong, jesszhan0024, maarten.lankhorst,
	mripard, tzimmermann, airlied, simona, robh, krzk+dt, conor+dt,
	nm, vigneshr, kristo, prabhakar.mahadev-lad.rj, thierry.reding,
	sam
  Cc: leonardo.costa, dri-devel, devicetree, linux-kernel,
	linux-arm-kernel
In-Reply-To: <20260623195741.495734-1-leoreis.costa@gmail.com>

From: Leonardo Costa <leonardo.costa@toradex.com>

Add a device tree overlay enabling the Toradex Capacitive Touch Display
10.1" LVDS V2 on the Verdin Development Board with Verdin AM62 Mezzanine
expansion board. The panel connects via the AM62 OLDI0 on the Mezzanine
LVDS interface (J10). The panel is an Opto Logic SCX1001511GGC49 10.1" WXGA
TFT LCD LVDS and the touch input is provided by an ILITEK ILI251x
capacitive touch controller.

Link: https://developer.toradex.com/hardware/accessories/displays/capacitive-touch-display-101inch-lvds
Signed-off-by: Leonardo Costa <leonardo.costa@toradex.com>
---
 arch/arm64/boot/dts/ti/Makefile               |   5 +
 ...zanine-panel-cap-touch-10inch-lvds-v2.dtso | 109 ++++++++++++++++++
 2 files changed, 114 insertions(+)
 create mode 100644 arch/arm64/boot/dts/ti/k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lvds-v2.dtso

diff --git a/arch/arm64/boot/dts/ti/Makefile b/arch/arm64/boot/dts/ti/Makefile
index 8b90fef11cb32..d9e0b1b80b28d 100644
--- a/arch/arm64/boot/dts/ti/Makefile
+++ b/arch/arm64/boot/dts/ti/Makefile
@@ -31,6 +31,7 @@ dtb-$(CONFIG_ARCH_K3) += k3-am625-sk.dtb
 dtb-$(CONFIG_ARCH_K3) += k3-am625-tqma62xx-mba62xx.dtb
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dev-mezzanine-can.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lvds.dtbo
+dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lvds-v2.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dev-nau8822-btl.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dsi-to-hdmi.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dsi-to-lvds-panel-cap-touch-10inch.dtbo
@@ -240,6 +241,9 @@ k3-am625-verdin-wifi-dev-mezzanine-can-dtbs := k3-am625-verdin-wifi-dev.dtb \
 k3-am625-verdin-wifi-dev-mezzanine-panel-cap-touch-10inch-lvds-dtbs := \
 	k3-am625-verdin-wifi-dev.dtb \
 	k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lvds.dtbo
+k3-am625-verdin-wifi-dev-mezzanine-panel-cap-touch-10inch-lvds-v2-dtbs := \
+	k3-am625-verdin-wifi-dev.dtb \
+	k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lvds-v2.dtbo
 k3-am625-verdin-wifi-dev-nau8822-btl-dtbs := k3-am625-verdin-wifi-dev.dtb \
 	k3-am625-verdin-dev-nau8822-btl.dtbo
 k3-am625-verdin-wifi-dev-ov5640-24mhz-dtbs := k3-am625-verdin-wifi-dev.dtb \
@@ -362,6 +366,7 @@ dtb- += k3-am625-beagleplay-csi2-ov5640.dtb \
 	k3-am625-verdin-wifi-dev-dsi-to-lvds-v2-panel-cap-touch-10inch.dtb \
 	k3-am625-verdin-wifi-dev-mezzanine-can.dtb \
 	k3-am625-verdin-wifi-dev-mezzanine-panel-cap-touch-10inch-lvds.dtb \
+	k3-am625-verdin-wifi-dev-mezzanine-panel-cap-touch-10inch-lvds-v2.dtb \
 	k3-am625-verdin-wifi-dev-nau8822-btl.dtb \
 	k3-am625-verdin-wifi-dev-ov5640-24mhz.dtb \
 	k3-am625-verdin-wifi-dev-ov5640.dtb \
diff --git a/arch/arm64/boot/dts/ti/k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lvds-v2.dtso b/arch/arm64/boot/dts/ti/k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lvds-v2.dtso
new file mode 100644
index 0000000000000..26f6715463986
--- /dev/null
+++ b/arch/arm64/boot/dts/ti/k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lvds-v2.dtso
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/*
+ * Copyright (c) Toradex
+ *
+ * Toradex Capacitive Touch Display 10.1" LVDS V2 on the Verdin AM62 Mezzanine
+ * LVDS interface (J10), used with the Verdin Development Board.
+ *
+ * https://developer.toradex.com/hardware/accessories/displays/capacitive-touch-display-101inch-lvds
+ * https://www.toradex.com/accessories/capacitive-touch-display-10.1-inch-lvds
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pwm/pwm.h>
+
+&{/} {
+	backlight_pwm2: backlight-pwm2 {
+		compatible = "pwm-backlight";
+		brightness-levels = <0 45 63 88 119 158 203 255>;
+		default-brightness-level = <4>;
+		/* Verdin GPIO_4 (SODIMM 212) - LVDS_BKL_EN */
+		enable-gpios = <&mcu_gpio0 4 GPIO_ACTIVE_HIGH>;
+		/* Verdin PWM_2 (SODIMM 16) - LVDS_PWM */
+		pwms = <&epwm0 1 6666667 0>;
+	};
+
+	panel-lvds-native {
+		compatible = "optologic,scx1001511ggc49", "panel-lvds";
+		backlight = <&backlight_pwm2>;
+		data-mapping = "vesa-24";
+		height-mm = <136>;
+		width-mm = <217>;
+
+		panel-timing {
+			clock-frequency = <64500000>;
+			hactive = <1280>;
+			hback-porch = <20>;
+			hfront-porch = <20>;
+			hsync-len = <2>;
+			vactive = <800>;
+			vback-porch = <5>;
+			vfront-porch = <5>;
+			vsync-len = <3>;
+			de-active = <1>;
+			hsync-active = <0>;
+			vsync-active = <0>;
+			pixelclk-active = <0>;
+		};
+
+		port {
+			panel_lvds_native_in: endpoint {
+				remote-endpoint = <&oldi0_out>;
+			};
+		};
+	};
+};
+
+&dss {
+	status = "okay";
+};
+
+&dss_ports {
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	/* DSS VP1: internal DPI output to OLDIx */
+	port@0 {
+		reg = <0>;
+
+		dss0_out: endpoint {
+			remote-endpoint = <&oldi0_in>;
+		};
+	};
+};
+
+/* Verdin I2C_2_DSI */
+&main_i2c2 {
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	touchscreen@41 {
+		compatible = "ilitek,ili251x";
+		reg = <0x41>;
+		/* Verdin GPIO_3 (SODIMM 210) - LVDS_TOUCH_INT# */
+		interrupt-parent = <&mcu_gpio0>;
+		interrupts = <3 IRQ_TYPE_EDGE_RISING>;
+		/* Verdin GPIO_2 (SODIMM 208) - LVDS_TOUCH_RST# */
+		reset-gpios = <&mcu_gpio0 2 GPIO_ACTIVE_LOW>;
+	};
+};
+
+&oldi0 {
+	status = "okay";
+};
+
+&oldi0_port0 {
+	oldi0_in: endpoint {
+		remote-endpoint = <&dss0_out>;
+	};
+};
+
+&oldi0_port1 {
+	oldi0_out: endpoint {
+		remote-endpoint = <&panel_lvds_native_in>;
+	};
+};

^ permalink raw reply related

* [PATCH 4/5] arm64: dts: ti: k3-am62-verdin: Add Toradex DSI to LVDS adapter with the 10.1" V2 display
From: Leonardo Costa @ 2026-06-23 19:57 UTC (permalink / raw)
  To: laurent.pinchart, neil.armstrong, jesszhan0024, maarten.lankhorst,
	mripard, tzimmermann, airlied, simona, robh, krzk+dt, conor+dt,
	nm, vigneshr, kristo, prabhakar.mahadev-lad.rj, thierry.reding,
	sam
  Cc: leonardo.costa, dri-devel, devicetree, linux-kernel,
	linux-arm-kernel
In-Reply-To: <20260623195741.495734-1-leoreis.costa@gmail.com>

From: Leonardo Costa <leonardo.costa@toradex.com>

Add a device tree overlay for the Toradex DSI to LVDS Adapter with the
Toradex Capacitive Touch Display 10.1" LVDS V2. The adapter connects to the
Verdin DSI_1 interface. It is based on the Texas Instruments SN65DSI84
DSI-to-LVDS bridge and drives an Opto Logic SCX1001511GGC49 10.1" WXGA TFT
LCD LVDS panel. Touch input is provided by an ILITEK ILI251x capacitive
touch controller.

Link: https://developer.toradex.com/hardware/accessories/add-ons/dsi-lvds-adapter
Link: https://developer.toradex.com/hardware/accessories/displays/capacitive-touch-display-101inch-lvds
Signed-off-by: Leonardo Costa <leonardo.costa@toradex.com>
---
 arch/arm64/boot/dts/ti/Makefile               |   5 +
 ...dsi-to-lvds-v2-panel-cap-touch-10inch.dtso | 143 ++++++++++++++++++
 2 files changed, 148 insertions(+)
 create mode 100644 arch/arm64/boot/dts/ti/k3-am625-verdin-dsi-to-lvds-v2-panel-cap-touch-10inch.dtso

diff --git a/arch/arm64/boot/dts/ti/Makefile b/arch/arm64/boot/dts/ti/Makefile
index 61ae9039e561d..8b90fef11cb32 100644
--- a/arch/arm64/boot/dts/ti/Makefile
+++ b/arch/arm64/boot/dts/ti/Makefile
@@ -34,6 +34,7 @@ dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lv
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dev-nau8822-btl.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dsi-to-hdmi.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dsi-to-lvds-panel-cap-touch-10inch.dtbo
+dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-dsi-to-lvds-v2-panel-cap-touch-10inch.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-nonwifi-dahlia-dsi-to-hdmi.dtb
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-nonwifi-dahlia-panel-cap-touch-10inch-dsi.dtb
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-nonwifi-dahlia.dtb
@@ -231,6 +232,9 @@ k3-am625-sk-hdmi-audio-dtbs := k3-am625-sk.dtb k3-am62x-sk-hdmi-audio.dtbo
 k3-am625-verdin-wifi-dev-dsi-to-lvds-panel-cap-touch-10inch-dtbs := \
 	k3-am625-verdin-wifi-dev.dtb \
 	k3-am625-verdin-dsi-to-lvds-panel-cap-touch-10inch.dtbo
+k3-am625-verdin-wifi-dev-dsi-to-lvds-v2-panel-cap-touch-10inch-dtbs := \
+	k3-am625-verdin-wifi-dev.dtb \
+	k3-am625-verdin-dsi-to-lvds-v2-panel-cap-touch-10inch.dtbo
 k3-am625-verdin-wifi-dev-mezzanine-can-dtbs := k3-am625-verdin-wifi-dev.dtb \
 	k3-am625-verdin-dev-mezzanine-can.dtbo
 k3-am625-verdin-wifi-dev-mezzanine-panel-cap-touch-10inch-lvds-dtbs := \
@@ -355,6 +359,7 @@ dtb- += k3-am625-beagleplay-csi2-ov5640.dtb \
 	k3-am625-sk-csi2-tevi-ov5640.dtb \
 	k3-am625-sk-hdmi-audio.dtb \
 	k3-am625-verdin-wifi-dev-dsi-to-lvds-panel-cap-touch-10inch.dtb \
+	k3-am625-verdin-wifi-dev-dsi-to-lvds-v2-panel-cap-touch-10inch.dtb \
 	k3-am625-verdin-wifi-dev-mezzanine-can.dtb \
 	k3-am625-verdin-wifi-dev-mezzanine-panel-cap-touch-10inch-lvds.dtb \
 	k3-am625-verdin-wifi-dev-nau8822-btl.dtb \
diff --git a/arch/arm64/boot/dts/ti/k3-am625-verdin-dsi-to-lvds-v2-panel-cap-touch-10inch.dtso b/arch/arm64/boot/dts/ti/k3-am625-verdin-dsi-to-lvds-v2-panel-cap-touch-10inch.dtso
new file mode 100644
index 0000000000000..dba0b6b9f02db
--- /dev/null
+++ b/arch/arm64/boot/dts/ti/k3-am625-verdin-dsi-to-lvds-v2-panel-cap-touch-10inch.dtso
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/*
+ * Copyright (c) Toradex
+ *
+ * Toradex DSI to LVDS Adapter on Verdin DSI_1 with Capacitive Touch Display 10.1"
+ * Used on Dahlia (X17) and Development Board (X48) that expose DSI_1 via an
+ * Samtec LSS-130 connector.
+ *
+ * https://developer.toradex.com/hardware/accessories/displays/capacitive-touch-display-101inch-lvds
+ * https://www.toradex.com/accessories/capacitive-touch-display-10.1-inch-lvds
+ * https://developer.toradex.com/hardware/accessories/add-ons/dsi-lvds-adapter
+ * https://www.toradex.com/accessories/verdin-dsi-to-lvds-adapter
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pwm/pwm.h>
+
+&{/} {
+	backlight_pwm3: backlight-pwm3 {
+		compatible = "pwm-backlight";
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_i2s_2_d_out_gpio>;
+		brightness-levels = <0 45 63 88 119 158 203 255>;
+		default-brightness-level = <4>;
+		/* Verdin I2S_2_D_OUT as GPIO (SODIMM 46) */
+		enable-gpios = <&main_gpio0 34 GPIO_ACTIVE_HIGH>;
+		power-supply = <&reg_3v3>;
+		/* Verdin PWM_3_DSI (SODIMM 19) */
+		pwms = <&epwm1 0 6666667 0>;
+	};
+
+	panel-lvds-bridge {
+		compatible = "optologic,scx1001511ggc49", "panel-lvds";
+		backlight = <&backlight_pwm3>;
+		data-mapping = "vesa-24";
+		height-mm = <136>;
+		width-mm = <217>;
+
+		/*
+		 * The timings here are different from the nominal ones for the
+		 * SCX1001511GGC49 display. They are adjusted to ensure that
+		 * the Horizontal Front Porch is long enough for the Verdin
+		 * AM62 DPI->DSI bridge (Toshiba TC358768) to enter/exit LP
+		 * mode. This is required for the display pipeline to work
+		 * correctly.
+		 */
+		panel-timing {
+			clock-frequency = <69500000>;
+			hactive = <1280>;
+			hback-porch = <20>;
+			hfront-porch = <100>;
+			hsync-len = <24>;
+			vactive = <800>;
+			vback-porch = <5>;
+			vfront-porch = <5>;
+			vsync-len = <3>;
+			de-active = <1>;
+			hsync-active = <0>;
+			vsync-active = <0>;
+			pixelclk-active = <0>;
+		};
+
+		port {
+			panel_lvds_bridge_in: endpoint {
+				remote-endpoint = <&dsi_lvds_bridge_out>;
+			};
+		};
+	};
+};
+
+&dsi_bridge {
+	status = "okay";
+};
+
+&dsi_bridge_ports {
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	port@1 {
+		reg = <1>;
+
+		dsi_bridge_out: endpoint {
+			remote-endpoint = <&dsi_lvds_bridge_in>;
+		};
+	};
+};
+
+&dss {
+	status = "okay";
+};
+
+/* Verdin I2C_1 */
+&main_i2c1 {
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	bridge@2c {
+		compatible = "ti,sn65dsi84";
+		reg = <0x2c>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_dsi1_bkl_en>;
+		/* Verdin GPIO_10_DSI (SODIMM 21) - DSI_1_BKL_EN */
+		enable-gpios = <&main_gpio0 30 GPIO_ACTIVE_HIGH>;
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			port@0 {
+				reg = <0>;
+
+				dsi_lvds_bridge_in: endpoint {
+					remote-endpoint = <&dsi_bridge_out>;
+					data-lanes = <1 2 3 4>;
+				};
+			};
+
+			port@2 {
+				reg = <2>;
+
+				dsi_lvds_bridge_out: endpoint {
+					remote-endpoint = <&panel_lvds_bridge_in>;
+				};
+			};
+		};
+	};
+
+	touchscreen@41 {
+		compatible = "ilitek,ili251x";
+		reg = <0x41>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_dsi1_int>, <&pinctrl_i2s_2_bclk_gpio>;
+		/* Verdin GPIO_9_DSI (SODIMM 17) - TOUCH_INT# */
+		interrupt-parent = <&main_gpio1>;
+		interrupts = <49 IRQ_TYPE_EDGE_RISING>;
+		/* Verdin I2S_2_BCLK (SODIMM 42) - TOUCH_RESET# */
+		reset-gpios = <&main_gpio0 35 GPIO_ACTIVE_LOW>;
+	};
+};

^ permalink raw reply related

* [PATCH 3/5] arm64: dts: ti: k3-am62-verdin: Add Toradex Capacitive Touch Display 10.1" LVDS V2
From: Leonardo Costa @ 2026-06-23 19:57 UTC (permalink / raw)
  To: laurent.pinchart, neil.armstrong, jesszhan0024, maarten.lankhorst,
	mripard, tzimmermann, airlied, simona, robh, krzk+dt, conor+dt,
	nm, vigneshr, kristo, prabhakar.mahadev-lad.rj, thierry.reding,
	sam
  Cc: leonardo.costa, dri-devel, devicetree, linux-kernel,
	linux-arm-kernel
In-Reply-To: <20260623195741.495734-1-leoreis.costa@gmail.com>

From: Leonardo Costa <leonardo.costa@toradex.com>

Add a device tree overlay for the Toradex Capacitive Touch Display 10.1"
LVDS V2 connected via Verdin AM62 OLDI on carrier boards exposing LVDS
interface (e.g., Mallow). The panel is an Opto Logic SCX1001511GGC49 10.1"
WXGA TFT LCD and the touch input is provided by an ILITEK ILI251x
capacitive touch controller.

Link: https://developer.toradex.com/hardware/accessories/displays/capacitive-touch-display-101inch-lvds
Signed-off-by: Leonardo Costa <leonardo.costa@toradex.com>
---
 arch/arm64/boot/dts/ti/Makefile               |   5 +
 ...verdin-panel-cap-touch-10inch-lvds-v2.dtso | 129 ++++++++++++++++++
 2 files changed, 134 insertions(+)
 create mode 100644 arch/arm64/boot/dts/ti/k3-am625-verdin-panel-cap-touch-10inch-lvds-v2.dtso

diff --git a/arch/arm64/boot/dts/ti/Makefile b/arch/arm64/boot/dts/ti/Makefile
index 371f9a043fe52..61ae9039e561d 100644
--- a/arch/arm64/boot/dts/ti/Makefile
+++ b/arch/arm64/boot/dts/ti/Makefile
@@ -47,6 +47,7 @@ dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-ov5640-24mhz.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-ov5640.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-panel-cap-touch-10inch-dsi.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-panel-cap-touch-10inch-lvds.dtbo
+dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-panel-cap-touch-10inch-lvds-v2.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-panel-cap-touch-7inch-dsi.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-uart4-mcu.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-am625-verdin-wifi-dahlia-dsi-to-hdmi.dtb
@@ -249,6 +250,9 @@ k3-am625-verdin-wifi-dev-uart4-mcu-dtbs := k3-am625-verdin-wifi-dev.dtb \
 k3-am625-verdin-wifi-mallow-panel-cap-touch-10inch-lvds-dtbs := \
 	k3-am625-verdin-wifi-mallow.dtb \
 	k3-am625-verdin-panel-cap-touch-10inch-lvds.dtbo
+k3-am625-verdin-wifi-mallow-panel-cap-touch-10inch-lvds-v2-dtbs := \
+	k3-am625-verdin-wifi-mallow.dtb \
+	k3-am625-verdin-panel-cap-touch-10inch-lvds-v2.dtbo
 k3-am62-lp-sk-hdmi-audio-dtbs := k3-am62-lp-sk.dtb k3-am62x-sk-hdmi-audio.dtbo
 k3-am62-lp-sk-nand-dtbs := k3-am62-lp-sk.dtb k3-am62-lp-sk-nand.dtbo
 k3-am62a7-phyboard-lyra-disable-eth-phy-dtbs := k3-am62a7-phyboard-lyra-rdk.dtb \
@@ -359,6 +363,7 @@ dtb- += k3-am625-beagleplay-csi2-ov5640.dtb \
 	k3-am625-verdin-wifi-dev-panel-cap-touch-7inch-dsi.dtb \
 	k3-am625-verdin-wifi-dev-uart4-mcu.dtb \
 	k3-am625-verdin-wifi-mallow-panel-cap-touch-10inch-lvds.dtb \
+	k3-am625-verdin-wifi-mallow-panel-cap-touch-10inch-lvds-v2.dtb \
 	k3-am62-lp-sk-hdmi-audio.dtb \
 	k3-am62-lp-sk-nand.dtb \
 	k3-am62a7-phyboard-lyra-disable-eth-phy.dtb \
diff --git a/arch/arm64/boot/dts/ti/k3-am625-verdin-panel-cap-touch-10inch-lvds-v2.dtso b/arch/arm64/boot/dts/ti/k3-am625-verdin-panel-cap-touch-10inch-lvds-v2.dtso
new file mode 100644
index 0000000000000..3ea8ccb8bff99
--- /dev/null
+++ b/arch/arm64/boot/dts/ti/k3-am625-verdin-panel-cap-touch-10inch-lvds-v2.dtso
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/*
+ * Copyright (c) Toradex
+ *
+ * Toradex Capacitive Touch Display 10.1" V2 connected via Verdin AM62 OLDI
+ * on carrier boards with a Toradex standard LVDS display connector
+ *
+ * https://developer.toradex.com/hardware/accessories/displays/capacitive-touch-display-101inch-lvds
+ * https://www.toradex.com/accessories/capacitive-touch-display-10.1-inch-lvds
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pwm/pwm.h>
+#include "k3-pinctrl.h"
+
+&{/} {
+	backlight_pwm2: backlight-pwm2 {
+		compatible = "pwm-backlight";
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_i2s_2_d_out_gpio>;
+		brightness-levels = <0 45 63 88 119 158 203 255>;
+		default-brightness-level = <4>;
+		/* Verdin I2S_2_D_OUT as GPIO (SODIMM 46) */
+		enable-gpios = <&main_gpio0 34 GPIO_ACTIVE_HIGH>;
+		/* Verdin PWM_2 (SODIMM 16) */
+		pwms = <&epwm0 1 6666667 0>;
+	};
+
+	panel-lvds-native {
+		compatible = "optologic,scx1001511ggc49", "panel-lvds";
+		backlight = <&backlight_pwm2>;
+		data-mapping = "vesa-24";
+		height-mm = <136>;
+		width-mm = <217>;
+
+		panel-timing {
+			clock-frequency = <64500000>;
+			hactive = <1280>;
+			hback-porch = <20>;
+			hfront-porch = <20>;
+			hsync-len = <2>;
+			vactive = <800>;
+			vback-porch = <5>;
+			vfront-porch = <5>;
+			vsync-len = <3>;
+			de-active = <1>;
+			hsync-active = <0>;
+			vsync-active = <0>;
+			pixelclk-active = <0>;
+		};
+
+		port {
+			panel_lvds_native_in: endpoint {
+				remote-endpoint = <&oldi0_out>;
+			};
+		};
+	};
+};
+
+&dss {
+	status = "okay";
+};
+
+&dss_ports {
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	/* DSS VP1: internal DPI output to OLDIx */
+	port@0 {
+		reg = <0>;
+
+		dss0_out: endpoint {
+			remote-endpoint = <&oldi0_in>;
+		};
+	};
+};
+
+/* Verdin I2C_2_DSI */
+&main_i2c2 {
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	touchscreen@41 {
+		compatible = "ilitek,ili251x";
+		reg = <0x41>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_i2s_2_d_in_gpio>,
+			    <&pinctrl_i2s_2_sync_gpio>;
+		/* Verdin I2S_2_SYNC as GPIO (SODIMM 44) */
+		interrupt-parent = <&main_gpio0>;
+		interrupts = <37 IRQ_TYPE_EDGE_RISING>;
+		/* Verdin I2S_2_D_IN as GPIO(SODIMM 48) */
+		reset-gpios = <&main_gpio0 33 GPIO_ACTIVE_LOW>;
+	};
+};
+
+&main_pmx0 {
+	pinctrl_i2s_2_d_in_gpio: main-gpio0-33-default-pins {
+		pinctrl-single,pins = <
+			AM62X_IOPAD(0x0088, PIN_INPUT, 7) /* (L24) GPMC0_OEn_REn.GPIO0_33 */ /* SODIMM 48 */
+		>;
+	};
+
+	pinctrl_i2s_2_sync_gpio: main-gpio0-37-default-pins {
+		pinctrl-single,pins = <
+			AM62X_IOPAD(0x0098, PIN_INPUT, 7) /* (U23) GPMC0_WAIT0.GPIO0_37 */ /* SODIMM 44 */
+		>;
+	};
+};
+
+&oldi0 {
+	status = "okay";
+};
+
+&oldi0_port0 {
+	oldi0_in: endpoint {
+		remote-endpoint = <&dss0_out>;
+	};
+};
+
+&oldi0_port1 {
+	oldi0_out: endpoint {
+		remote-endpoint = <&panel_lvds_native_in>;
+	};
+};

^ permalink raw reply related

* [PATCH 2/5] dt-bindings: display: panel-lvds: Add compatible for Opto Logic SCX1001511GGC49
From: Leonardo Costa @ 2026-06-23 19:57 UTC (permalink / raw)
  To: laurent.pinchart, neil.armstrong, jesszhan0024, maarten.lankhorst,
	mripard, tzimmermann, airlied, simona, robh, krzk+dt, conor+dt,
	nm, vigneshr, kristo, prabhakar.mahadev-lad.rj, thierry.reding,
	sam
  Cc: leonardo.costa, dri-devel, devicetree, linux-kernel,
	linux-arm-kernel
In-Reply-To: <20260623195741.495734-1-leoreis.costa@gmail.com>

From: Leonardo Costa <leonardo.costa@toradex.com>

The Opto Logic SCX1001511GGC49 is a 10.1" WXGA (1280x800) TFT LCD LVDS
panel.

Signed-off-by: Leonardo Costa <leonardo.costa@toradex.com>
---
 Documentation/devicetree/bindings/display/panel/panel-lvds.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml b/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml
index b89f86bc06837..8382d4920bfd6 100644
--- a/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml
+++ b/Documentation/devicetree/bindings/display/panel/panel-lvds.yaml
@@ -58,6 +58,8 @@ properties:
           - hydis,hv070wx2-1e0
           # Jenson Display BL-JT60050-01A 7" WSVGA (1024x600) color TFT LCD LVDS panel
           - jenson,bl-jt60050-01a
+          # Opto Logic SCX1001511GGC49 10.1" WXGA (1280x800) TFT LCD LVDS panel
+          - optologic,scx1001511ggc49
           # Riverdi RVT101HVLNWC00 10.1" WXGA (1280x800) TFT LCD LVDS panel
           - riverdi,rvt101hvlnwc00
           # Riverdi RVT70HSLNWCA0 7.0" WSVGA (1024x600) TFT LCD LVDS panel

^ permalink raw reply related

* [PATCH 1/5] dt-bindings: vendor-prefixes: Add Opto Logic
From: Leonardo Costa @ 2026-06-23 19:57 UTC (permalink / raw)
  To: laurent.pinchart, neil.armstrong, jesszhan0024, maarten.lankhorst,
	mripard, tzimmermann, airlied, simona, robh, krzk+dt, conor+dt,
	nm, vigneshr, kristo, prabhakar.mahadev-lad.rj, thierry.reding,
	sam
  Cc: leonardo.costa, dri-devel, devicetree, linux-kernel,
	linux-arm-kernel
In-Reply-To: <20260623195741.495734-1-leoreis.costa@gmail.com>

From: Leonardo Costa <leonardo.costa@toradex.com>

Add vendor prefix for Opto Logic, a Swiss display solutions provider and
printing systems manufacturer.

Link: https://optologic.ch/
Signed-off-by: Leonardo Costa <leonardo.costa@toradex.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index dd94c50e97f9c..ce0607afc1f4b 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1247,6 +1247,8 @@ patternProperties:
     description: OpenWrt
   "^option,.*":
     description: Option NV
+  "^optologic,.*":
+    description: Opto Logic S.A.
   "^oranth,.*":
     description: Shenzhen Oranth Technology Co., Ltd.
   "^ORCL,.*":

^ permalink raw reply related

* [PATCH 0/5] arm64: dts: ti: k3-am62-verdin: Add Toradex Capacitive Touch Display 10.1" LVDS
From: Leonardo Costa @ 2026-06-23 19:57 UTC (permalink / raw)
  To: laurent.pinchart, neil.armstrong, jesszhan0024, maarten.lankhorst,
	mripard, tzimmermann, airlied, simona, robh, krzk+dt, conor+dt,
	nm, vigneshr, kristo, prabhakar.mahadev-lad.rj, thierry.reding,
	sam
  Cc: leonardo.costa, dri-devel, devicetree, linux-kernel,
	linux-arm-kernel

From: Leonardo Costa <leonardo.costa@toradex.com>

This series adds device tree overlays for Toradex Capacitive 
Touch Display 10.1" LVDS V2 on Verdin AM62.

The display addition covers two interface types:

- Native OLDI (LVDS)
- DSI-to-LVDS adapter based on the SN65DSI84

The Opto Logic vendor prefix and panel binding for the SCX1001511GGC49 is
also added.

Leonardo Costa (5):
  dt-bindings: vendor-prefixes: Add Opto Logic
  dt-bindings: display: panel-lvds: Add compatible for Opto Logic
    SCX1001511GGC49
  arm64: dts: ti: k3-am62-verdin: Add Toradex Capacitive Touch Display
    10.1" LVDS V2
  arm64: dts: ti: k3-am62-verdin: Add Toradex DSI to LVDS adapter with
    the 10.1" V2 display
  arm64: dts: ti: k3-am62-verdin: Add Mezzanine with Toradex Display
    10.1" LVDS

 .../bindings/display/panel/panel-lvds.yaml    |   2 +
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 arch/arm64/boot/dts/ti/Makefile               |  15 ++
 ...zanine-panel-cap-touch-10inch-lvds-v2.dtso | 109 +++++++++++++
 ...dsi-to-lvds-v2-panel-cap-touch-10inch.dtso | 143 ++++++++++++++++++
 ...verdin-panel-cap-touch-10inch-lvds-v2.dtso | 129 ++++++++++++++++
 6 files changed, 400 insertions(+)
 create mode 100644 arch/arm64/boot/dts/ti/k3-am625-verdin-dev-mezzanine-panel-cap-touch-10inch-lvds-v2.dtso
 create mode 100644 arch/arm64/boot/dts/ti/k3-am625-verdin-dsi-to-lvds-v2-panel-cap-touch-10inch.dtso
 create mode 100644 arch/arm64/boot/dts/ti/k3-am625-verdin-panel-cap-touch-10inch-lvds-v2.dtso


^ permalink raw reply

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

On 6/23/26 11:07 AM, Lukas Metz wrote:
> Add device tree binding for the Texas Instruments DAC8163 family
> including the DAC7562, DAC7563, DAC8162, DAC8163, DAC8562 and DAC8563.

One other thing worth mentioning here as to why none of these are fallback
compatible (in addition to the resolution bits) is that the xxx2 chips
and xxx3 chips have a different output state when CLR is asserted.

> 
> 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
> +

^ permalink raw reply

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

On 6/23/26 11:07 AM, Lukas Metz wrote:
> 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.

Nice and simple driver. Mostly just needs better alignment with usual
IIO conventions.

> 
> 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

Prefer GPL-2.0-only or GPL-2.0-or-later.

> +/*
> + * 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)))

Usually, we try to avoid putting FIELD_PREP() in macros. This
is only used once in the code anyway, so dropping the macro
will actually save a line or two.

> +
> +#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 {

In IIO, we always avoid putting xx in identifier names. Just use dac8163
everwhere instead since that is the name of the driver.

> +	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;

This is 

> +	struct gpio_desc *loaddacs;

LDAC is very common in DACs, so I would call this ldac_gpio. Also, it isn't
currently used outside of probe, so we don't really need it here.

> +
> +	bool internal_ref;
> +	int vref_uv;

As in a later comment, we should be able to drop these as well if we can
get rid of the remove() callback.

> +
> +	unsigned int cached[2];

Can we get a more descriptive name for this? Is it raw value?

> +
> +	/*
> +	 * 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,          \

Please put each field on a new line.

> +		.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),
> +		}
> +	},
> +};

We are trying to get rid of arrays like this in drivers. We can just make
individual structs instead.

> +
> +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);

This can be moved out of the case statement so we don't have to
repeat it each time.

> +		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 */

We've been writing this like:

		*val = st->vref_uV / (MICRO/ MILLI);

Then we don't really need a comment.

> +		*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);

Do we really need to keep this debug print?

> +		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,

Usually, we would declare `int ret;` at the top of the function.

> +					    (unsigned int)val

Using u32 type instead of unsigned int would make this probably fit
on one line too.

> +						    << 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);

As in the dt-bindings, we would expect the bindings to be active low
so this would be GPIOD_OUT_HIGH to assert the LDAC signal.

Could also use a comment to say that for now we are just holding this
asserted so that individual outputs are updated when we each raw attribute.

> +	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);

devm_mutex_init(). Also should not be in the middle of regulator code.

> +
> +	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;
> +		}
> +	}

The way we've been doing optional reference voltage lately is like this:

	if (device_property_present(dev, "refin-supply")) {
		ret = devm_regulator_get_enable_read_voltage(dev, "refin");
		if (ret < 0)
			return ret;

		st->vref_mV = ret / (MICRO / MILLI);
	} else {
		st->vref_mV = DAC8163_INTERNAL_REF_mV;
	}

This avoids the need for the ti,internal-ref DT property, avoid the need to convert
uV to mV later and the macro for the internal reference makes it self-documenting
so we don't need a comment. And it automacially cleans up after itself.

> +
> +	gpiod_set_value(st->loaddacs, 0);

devm_gpiod_get_optional() already set this, so this is redundant.

> +
> +	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));
> +

Some of these lines are getting a bit long. We try to stick to close
to 80 columns in IIO when we can.

> +	if (ret < 0)
> +		goto err;
> +
> +	info = spi_get_device_match_data(spi);

	if (!info)
		return -EINVAL;

> +
> +	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);

	return devm_iio_device_register();

> +	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);

We can use devm_* functions and avoid the need for the remove callback.

> +}
> +
> +#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),
> +	{}

IIO style is `{ }` (with space inbetween braces)

> +};
> +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");
> 


^ permalink raw reply

* Re: [PATCH v2 3/3] iio: magnetometer: st_magn: honour st,fullscale-milligauss DT property
From: Andy Shevchenko @ 2026-06-23 19:49 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Herman van Hazendonk, David Lechner, Nuno Sá,
	Andy Shevchenko, Nathan Chancellor, Nick Desaulniers,
	Bill Wendling, Justin Stitt, Denis Ciocca, Lars-Peter Clausen,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Denis Ciocca,
	Linus Walleij, linux-iio, linux-kernel, llvm, devicetree
In-Reply-To: <20260623202916.5f5d520e@jic23-huawei>

On Tue, Jun 23, 2026 at 08:29:16PM +0100, Jonathan Cameron wrote:
> On Tue, 16 Jun 2026 15:02:06 +0200
> Herman van Hazendonk <github.com@herrie.org> wrote:
> 
> > The ST magnetometer core's common probe hardcodes fs_avl[0] -- the
> > highest-sensitivity full-scale supported by the chip -- as the
> > starting range. For the LSM303DLH that is +/-1.3 G; for the
> > LSM303DLHC and LSM303DLM it is +/-2 G; for the LIS3MDL it is +/-4 G.
> > 
> > That is the right default for "minimal noise floor at a desk", but
> > it leaves no margin for boards that pick up appreciable DC bias from
> > nearby PCB structures. On the HP TouchPad (apq8060 / tenderloin) the
> > LSM303DLH magnetometer is mounted close enough to the surrounding
> > power planes that X reads back as the chip's 0xF000 overflow
> > sentinel (== -4096 raw, the value the chip publishes when the ADC
> > saturates) on every sample at the chip-default range, while Y and Z
> > fall well within the +/-1.3 G window.
> > 
> > Parse the st,fullscale-milligauss device-tree property (documented
> > separately in dt-bindings/iio/st,st-sensors.yaml) in the
> > magnetometer common probe to select the initial fs_avl entry by its
> > mg value. The DT binding pins the accepted value set per compatible
> > via allOf/if-then enum clauses, so a malformed mg value fails
> > dt_binding_check rather than reaching the driver. Sensors with a
> > fixed full-scale (fs.addr == 0: LSM303AGR, LIS2MDL, IIS2MDC) have no
> > register to switch and the property is rejected outright for them
> > in the binding; the parse block is additionally gated on fs.addr as
> > defence in depth against stale DTBs.
> > 
> > Per-sensor mg ranges are listed in st_magn_sensors_settings[]. For
> > LSM303DLH and LSM303DLHC/DLM the valid values are 1300, 1900, 2500,
> > 4000, 4700, 5600 and 8100; for LIS3MDL, LSM9DS1-magn and LSM303C-magn
> > they are 4000, 8000, 12000, 16000.
> > 
> > Empirical scale sweep on the HP TouchPad confirmed that on this
> > board any fs_avl >= 1 produces non-saturated X readings:
> > 
> >     scale (0.001 G/LSB)  | X raw    Y raw    Z raw
> >     --------------------+-------------------------------
> >             1.100        | -4096    44       46    (X saturated)
> >             0.855        |  -547    37       37    (clean)
> >             0.670        |  -433    94      103    (clean)
> >             0.450        |  -266    44       71    (clean)
> >             0.400        |  -235    34       65    (clean)
> >             0.330        |  -196    27       56    (clean)
> >             0.230        |  -145    15       40    (clean)
> > 
> > 2500 mg is the natural choice for tenderloin: comfortably outside
> > the saturation regime while keeping useful precision for compass
> > applications.
> > 
> > Assisted-by: Claude:claude-opus-4-7 sparse smatch clang-analyzer coccinelle checkpatch
> > Assisted-by: Sashiko:claude-opus-4-7
> Hmm. First time I remember seeing Sashiko credited like this. Seems like pretty much
> every patch series of any complexity would end up crediting sashiko.
> Out of curiosity were you just looking at reports, or were you running it locally to
> help with development?

I believe it's the second one, because LKML version uses Gemini (as far as I
understand the case). At least that's why I haven't commented on this tag.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v3 03/10] drm/bridge: synopsys: dw-dp: Add follow-up bridge support
From: sashiko-bot @ 2026-06-23 19:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: devicetree, conor+dt, robh, dri-devel
In-Reply-To: <20260612-synopsys-dw-dp-improvements-v3-3-dc61e6352508@collabora.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Memory leak of the follow-up bridge reference count.
- [High] Unconditional probe failure causing DT backward compatibility breakage.

Pre-existing issues:
- [High] Missing completion reinitialization causes race conditions with stale completion states during AUX transfers.
- [High] Premature registration and exposure of the DP AUX channel to userspace before hardware and IRQ initialization.
--

commit ceef3e5c9dcfb09773dad5a727e1e2147c87f8e5
Author: Sebastian Reichel <sebastian.reichel@collabora.com>

drm/bridge: synopsys: dw-dp: Add follow-up bridge support

Add support to use USB-C connectors with the DP altmode helper code on
devicetree based platforms. To get this working there must be a DRM bridge
chain from the DisplayPort controller to the USB-C connector.

> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> index ac7779e42bb24..98cff435dfb87 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> @@ -1970,7 +1970,7 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
>  {
>  	struct platform_device *pdev = to_platform_device(dev);
>  	struct dw_dp *dp;
> -	struct drm_bridge *bridge;
> +	struct drm_bridge *bridge, *next_bridge;
>  	void __iomem *res;
>  	int ret;
>  
> @@ -2064,6 +2064,20 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
>  		goto unregister_aux;
>  	}
>  
> +	next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, 0);
> +	if (IS_ERR(next_bridge)) {

[Severity: High]
Can this unconditional check break device tree backward compatibility?

By making the absence of a follow-up bridge a fatal error instead of an
optional configuration, this appears to cause the display driver to
completely fail to probe on existing platforms that do not define a downstream
bridge endpoint in their device tree.

> +		ret = PTR_ERR(next_bridge);
> +		dev_err_probe(dev, ret, "failed to get follow-up bridge.\n");
> +		goto unregister_aux;
> +	}
> +
> +	ret = drm_bridge_attach(encoder, next_bridge, bridge,
> +				DRM_BRIDGE_ATTACH_NO_CONNECTOR);

[Severity: High]
Does this code leak the next_bridge reference?

Calling of_drm_get_bridge_by_endpoint() increments the reference count of the
returned bridge, but there doesn't seem to be a corresponding drm_bridge_put()
on either the success or error paths to release it.

> +	if (ret) {
> +		dev_err_probe(dev, ret, "Failed to attach next bridge\n");
> +		goto unregister_aux;
> +	}
> +
>  	dw_dp_init_hw(dp);
>  

[Severity: High]
This is a pre-existing issue, but does registering the AUX channel early
expose uninitialized hardware to userspace?

Looking earlier in dw_dp_bind():

	ret = drm_dp_aux_register(&dp->aux);
	if (ret) {
		dev_err_probe(dev, ret, "Aux register failed\n");
		return ERR_PTR(ret);
	}

And then later:

	dw_dp_init_hw(dp);

	ret = phy_init(dp->phy);

If a userspace process opens the AUX character device and initiates a
transaction before dw_dp_init_hw() and the IRQ setup are called, the hardware
may enter an invalid state and miss completion interrupts.

>  	ret = phy_init(dp->phy);

[Severity: High]
This is a pre-existing issue, but can the lack of completion reinitialization
cause race conditions?

Looking at dw_dp_aux_transfer() in the same file:

	regmap_write(dp->regmap, DW_DP_AUX_CMD, value);

	status = wait_for_completion_timeout(&dp->complete, timeout);

If a previous AUX transfer timed out but the hardware eventually triggered a
late interrupt, the completion object will remain in a done state. The next
AUX transfer will see this stale state, causing wait_for_completion_timeout()
to return immediately before the new transfer has actually completed.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com?part=3

^ permalink raw reply

* Re: [PATCH 0/2] Add driver for DAC8163:
From: Andy Shevchenko @ 2026-06-23 19:40 UTC (permalink / raw)
  To: David Lechner
  Cc: Lukas Metz, Jonathan Cameron, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
	linux-iio, devicetree
In-Reply-To: <9176e97e-c92c-4d9a-a0b3-da2e7983d9e1@baylibre.com>

On Tue, Jun 23, 2026 at 01:50:27PM -0500, David Lechner wrote:
> On 6/23/26 1:35 PM, Andy Shevchenko wrote:
> > On Tue, Jun 23, 2026 at 06:07:26PM +0200, Lukas Metz wrote:
> >> 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
> > 
> > Why do we need a brand new driver? Do we have an existing one that may be
> > expanded to support this HW? (Note, not all existing drivers are under IIO
> > folder, some of them might be found in hwmon, input, or drivers/misc.)
> 
> I thought the statement above is clear that there are not any compatible
> drivers already. And I would not expect a DAC to have a driver in hwmon
> or input since it is an output device.

Ah, my bad, I haven't read carefully. Fair point, I see the answer is already
here.

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

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

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

On Tue, Jun 23, 2026 at 06:07:27PM +0200, Lukas Metz wrote:
> 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.

> +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.

This list doesn't scale. Put one part number per line as
	  - DAC7... (channel bits)
	  - DAC8... (...)
	  - ...

> +	  If compiled as a module, it will be called ti-dac8163.

...

> +#include <linux/module.h>
> +#include <linux/spi/spi.h>

> +#include <linux/of.h>

No, for this header in new drivers. Can't we use device and/or fwnode property
APIs instead?

> +#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>

? Usually one wants dev_printk.h.

> +#include <linux/bitfield.h>

Keep the list sorted, and also follow the IWYU principle.

...

> +#define COMMAND_SET(x, y) (FIELD_PREP(COMMAND_MASK, (x)) | \
> +							FIELD_PREP(ADDRESS_MASK, (y)))

This is ugly indentation. Compare to

#define COMMAND_SET(x, y)			\
	(FIELD_PREP(COMMAND_MASK, (x)) | FIELD_PREP(ADDRESS_MASK, (y)))

...

> +enum dacxx6x_ldac_modes {
> +	LDAC_MODE_ACTIVE = 0,
> +	LDAC_MODE_INACTIVE = 1

Leave trailing commas in the non-terminator entries here and there.

> +};

...

> +enum dacxx6x_supported_device_ids {
> +	ID_DAC7562,
> +	ID_DAC7563,
> +	ID_DAC8162,
> +	ID_DAC8163,
> +	ID_DAC8562,
> +	ID_DAC8563
> +};

This is solely used to make indexed array with chip_info. Instead kill this
enum and use separate structures.

...

> +struct dacxx6x_state {
> +	struct spi_device *spi;

Why not regmap?

> +	struct regulator *vref;
> +	struct gpio_desc *loaddacs;
> +
> +	bool internal_ref;
> +	int vref_uv;

_uV

> +	unsigned int cached[2];
> +
> +	/*
> +	 * Lock to protect the state of the device from potential concurrent
> +	 * write accesses from userspace.
> +	 */
> +	struct mutex lock;
> +};

...

> +#define DACXX6X_CHAN(id, resolution)                                        \
> +	{                                                                   \
> +		.type = IIO_VOLTAGE, .channel = (id), .output = 1,          \
> +		.indexed = 1, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \

Do not put two or more things on the same line, it's unreadable.

> +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),       \

> +		.scan_type = { .realbits = (resolution),                    \
> +			       .shift = 16 - (resolution) },                \

Make these to be 4 lines.

> +	}

...

> +static const struct dacxx6x_chip_info dacxx6x_chip_info_table[6] = {

Drop the number in the square brackets, let compiler do that job. But see
above, this has to be just 6 different data structures.

> +};

...

> +static int dacxx6x_write_reg(struct dacxx6x_state *st, int reg, int addr,
> +			     unsigned int val)
> +{
> +	u8 tx[3];

Are you sure about this? How would it work with DMA?

> +	tx[0] = COMMAND_SET(reg, addr);
> +	tx[1] = (val >> 8) & 0xff;
> +	tx[2] = val & 0xff;

Use proper unaligned setters: put_unaligned_be16() from linux/unaligned.h.

> +	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)

Split logically:

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);

No. Is it RFC? PoC? Or production-ready? If not the latter, come when it will
be production-ready.

> +		if (val2 != 0)
> +			return -EINVAL;

> +		if (val < 0 || val >= BIT(chan->scan_type.realbits))
> +			return -EINVAL;

Why not ERANGE or something like this?

> +
> +		mutex_lock(&st->lock);

Why not guard()()?

> +		int ret = dacxx6x_write_reg(st, CMD_WRITE_UPDATE, chan->channel,
> +					    (unsigned int)val
> +						    << chan->scan_type.shift);

Awful indentation. Please, check

> +		if (!ret)

No, use regular pattern: Check for error first.

> +			st->cached[chan->channel] = val;
> +		mutex_unlock(&st->lock);
> +		return ret;
> +	default:
> +		return -EINVAL;
> +	}
> +}

...

> +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;

Reversed xmas tree order, please.

> +	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);

Is it being used?

> +	st->loaddacs = devm_gpiod_get_optional(&spi->dev, "ti,loaddacs",
> +					       GPIOD_OUT_LOW);

In the above you used temporary variable for struct device, why not here?

> +	if (IS_ERR(st->loaddacs))
> +		return PTR_ERR(st->loaddacs);
> +
> +	st->internal_ref =
> +		device_property_read_bool(&spi->dev, "ti,internal-ref");

And here (and it becomes a single line as well).

> +	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;
> +	}

Can't you get just voltage and be done with it for now?

> +	mutex_init(&st->lock);

devm_mutex_init.

> +	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));

> +

Stray blank line.

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

Ditto.

> +	if (ret < 0)
> +		goto err;
> +
> +	info = spi_get_device_match_data(spi);

We need to check for NULL here due to driver_override.

> +	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);
> +}

Finish devm conversion and drop this function.

...

> +#define DACXX6X_COMPATIBLE(of_compatible, id)        \
> +	{                                            \
> +		.compatible = of_compatible,         \
> +		.data = &dacxx6x_chip_info_table[id] \
> +	}

No, just use directly, so drop this macro.

> +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] },
> +	{}
> +};

> +

Unneeded blank line.

> +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,
> +};

> +

Ditto.

> +module_spi_driver(dacxx6x_driver);

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v2 3/3] iio: magnetometer: st_magn: honour st,fullscale-milligauss DT property
From: Jonathan Cameron @ 2026-06-23 19:29 UTC (permalink / raw)
  To: Herman van Hazendonk
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, Nathan Chancellor,
	Nick Desaulniers, Bill Wendling, Justin Stitt, Denis Ciocca,
	Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Denis Ciocca, Linus Walleij, linux-iio,
	linux-kernel, llvm, devicetree
In-Reply-To: <20260616-submit-iio-lsm303dlh-magn-fixes-v2-3-063edcf74e60@herrie.org>

On Tue, 16 Jun 2026 15:02:06 +0200
Herman van Hazendonk <github.com@herrie.org> wrote:

> The ST magnetometer core's common probe hardcodes fs_avl[0] -- the
> highest-sensitivity full-scale supported by the chip -- as the
> starting range. For the LSM303DLH that is +/-1.3 G; for the
> LSM303DLHC and LSM303DLM it is +/-2 G; for the LIS3MDL it is +/-4 G.
> 
> That is the right default for "minimal noise floor at a desk", but
> it leaves no margin for boards that pick up appreciable DC bias from
> nearby PCB structures. On the HP TouchPad (apq8060 / tenderloin) the
> LSM303DLH magnetometer is mounted close enough to the surrounding
> power planes that X reads back as the chip's 0xF000 overflow
> sentinel (== -4096 raw, the value the chip publishes when the ADC
> saturates) on every sample at the chip-default range, while Y and Z
> fall well within the +/-1.3 G window.
> 
> Parse the st,fullscale-milligauss device-tree property (documented
> separately in dt-bindings/iio/st,st-sensors.yaml) in the
> magnetometer common probe to select the initial fs_avl entry by its
> mg value. The DT binding pins the accepted value set per compatible
> via allOf/if-then enum clauses, so a malformed mg value fails
> dt_binding_check rather than reaching the driver. Sensors with a
> fixed full-scale (fs.addr == 0: LSM303AGR, LIS2MDL, IIS2MDC) have no
> register to switch and the property is rejected outright for them
> in the binding; the parse block is additionally gated on fs.addr as
> defence in depth against stale DTBs.
> 
> Per-sensor mg ranges are listed in st_magn_sensors_settings[]. For
> LSM303DLH and LSM303DLHC/DLM the valid values are 1300, 1900, 2500,
> 4000, 4700, 5600 and 8100; for LIS3MDL, LSM9DS1-magn and LSM303C-magn
> they are 4000, 8000, 12000, 16000.
> 
> Empirical scale sweep on the HP TouchPad confirmed that on this
> board any fs_avl >= 1 produces non-saturated X readings:
> 
>     scale (0.001 G/LSB)  | X raw    Y raw    Z raw
>     --------------------+-------------------------------
>             1.100        | -4096    44       46    (X saturated)
>             0.855        |  -547    37       37    (clean)
>             0.670        |  -433    94      103    (clean)
>             0.450        |  -266    44       71    (clean)
>             0.400        |  -235    34       65    (clean)
>             0.330        |  -196    27       56    (clean)
>             0.230        |  -145    15       40    (clean)
> 
> 2500 mg is the natural choice for tenderloin: comfortably outside
> the saturation regime while keeping useful precision for compass
> applications.
> 
> Assisted-by: Claude:claude-opus-4-7 sparse smatch clang-analyzer coccinelle checkpatch
> Assisted-by: Sashiko:claude-opus-4-7
Hmm. First time I remember seeing Sashiko credited like this. Seems like pretty much
every patch series of any complexity would end up crediting sashiko.
Out of curiosity were you just looking at reports, or were you running it locally to
help with development?

One thing inline.
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
>  drivers/iio/magnetometer/st_magn_core.c | 32 ++++++++++++++++++++++++++++++++
>  1 file changed, 32 insertions(+)
> 
> diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c
> index ef348d316c00..6f369e8dddea 100644
> --- a/drivers/iio/magnetometer/st_magn_core.c
> +++ b/drivers/iio/magnetometer/st_magn_core.c
> @@ -10,6 +10,7 @@
>  #include <linux/kernel.h>
>  #include <linux/module.h>
>  #include <linux/mutex.h>
> +#include <linux/property.h>
>  #include <linux/sysfs.h>
>  #include <linux/iio/iio.h>
>  #include <linux/iio/sysfs.h>
> @@ -608,6 +609,7 @@ int st_magn_common_probe(struct iio_dev *indio_dev)
>  	struct st_sensor_data *mdata = iio_priv(indio_dev);
>  	struct device *parent = indio_dev->dev.parent;
>  	struct st_sensors_platform_data *pdata = dev_get_platdata(parent);
> +	const char *propname;
>  	int err;
>  
>  	indio_dev->modes = INDIO_DIRECT_MODE;
> @@ -628,6 +630,36 @@ int st_magn_common_probe(struct iio_dev *indio_dev)
>  	mdata->current_fullscale = &mdata->sensor_settings->fs.fs_avl[0];
>  	mdata->odr = mdata->sensor_settings->odr.odr_avl[0].hz;
>  
> +	/*
> +	 * Skip fixed-FS chips (fs.addr == 0): no register to switch.
> +	 * The binding rejects the property on these compatibles too;
> +	 * the gate guards stale DTBs.
Isn't it optional?  If so they aren't necessarily stale, people
may have relied on the default - which I now notice isn't specified
in the dt-binding (just replied to that patch).

> +	 */
> +	propname = "st,fullscale-milligauss";
> +	if (mdata->sensor_settings->fs.addr &&
> +	    device_property_present(parent, propname)) {

^ permalink raw reply

* Re: [PATCH v2 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-milligauss
From: Jonathan Cameron @ 2026-06-23 19:26 UTC (permalink / raw)
  To: Herman van Hazendonk
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, Nathan Chancellor,
	Nick Desaulniers, Bill Wendling, Justin Stitt, Denis Ciocca,
	Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Denis Ciocca, Linus Walleij, linux-iio,
	linux-kernel, llvm, devicetree
In-Reply-To: <20260616-submit-iio-lsm303dlh-magn-fixes-v2-2-063edcf74e60@herrie.org>

On Tue, 16 Jun 2026 15:02:05 +0200
Herman van Hazendonk <github.com@herrie.org> wrote:

> Add an optional st,fullscale-milligauss property that selects the
> initial magnetometer full-scale range at probe time, expressed in
> milligauss.
> 
> The motivating case is the LSM303DLH magnetometer on the HP TouchPad
> (apq8060 / tenderloin) where the kernel's chip-default +/-1.3 G range
> saturates the X axis to the chip's 0xF000 overflow sentinel out of
> probe, because the chip is mounted close to surrounding power planes
> and picks up enough DC bias to exceed the smallest range.
> 
> The chip is not wedged by the saturation: a sysfs write of a wider
> range to in_magn_x_scale recovers it on the next conversion, and a
> UDEV rule on add of the IIO device is a viable steady-state
> workaround. What the DT property buys is the probe-time window: the
> in-tree consumers we use (sensorfw's iio-sensors-adaptor and the
> geomagnetic / orientation services on top of it) start polling
> in_magn_x_raw essentially as soon as the device node appears and
> treat the 0xF000 sentinel as a legitimate sample. Until a UDEV rule
> fires and commits the wider range, every read returns the stuck
> sentinel, and on slow-boot paths the consumer may have already
> cached a bogus calibration baseline by the time UDEV catches up.
> 
> st,fullscale-milligauss lets the device tree declare a wider
> initial range up-front so the correct range is in effect before any
> IIO consumer can open the device, and keeps the board-specific
> magnetometer calibration alongside the rest of the hardware
> description rather than splitting it between DTS and per-distro
> UDEV rules.
> 
> The full property name is spelled out rather than abbreviated to
> "-mg" because this same binding file already covers ST accelerometers
> where the conventional shorthand "mg" reads as milli-g (acceleration);
> the explicit "-milligauss" suffix makes the unit unambiguous if a
> similar tunable is ever introduced for the accel/gyro/pressure
> families.
> 
> The property is scoped to magnetometer compatibles via allOf/if-then
> clauses, with per-family enum lists of the accepted milligauss
> values (1300..8100 for LSM303DLH/DLHC/DLM, 4000..16000 for
> LIS3MDL/LSM9DS1/LSM303C). LSM303AGR / LIS2MDL / IIS2MDC have a
> single fixed full-scale (15000 mg) with no register to switch
> ranges, so the property is rejected outright for them. DTSes that
> misspell the value, place the property on an accelerometer /
> gyroscope / pressure node, or set it on a fixed-FS magnetometer
> fail dt_binding_check rather than emitting a runtime warning.
> 
> The property is purely additive: if absent, drivers fall back to
> their existing chip default. No existing in-tree DTS is affected.
> 
> Assisted-by: Claude:claude-opus-4-7 dt_binding_check checkpatch
> Assisted-by: Sashiko:claude-opus-4-7
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
>  .../devicetree/bindings/iio/st,st-sensors.yaml     | 71 ++++++++++++++++++++++
>  1 file changed, 71 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> index a1a958215cdb..f0805604c849 100644
> --- a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> +++ b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> @@ -126,6 +126,13 @@ properties:
>    mount-matrix:
>      description: an optional 3x3 mounting rotation matrix.
>  
> +  st,fullscale-milligauss:
> +    description:
> +      Initial magnetometer full-scale at probe time, in milligauss.
> +      Per-chip allowed values are enumerated in the allOf clauses
> +      below.
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +
>  allOf:
>    - if:
>        properties:
> @@ -163,6 +170,70 @@ allOf:
>            maxItems: 1
>          st,drdy-int-pin: false
>  
> +  # Per-chip enum lists for st,fullscale-milligauss. Out-of-range
> +  # values fail dt_binding_check instead of being demoted to a
> +  # runtime warning by the driver.
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - st,lsm303dlh-magn
> +              - st,lsm303dlhc-magn
> +              - st,lsm303dlm-magn
> +    then:
> +      properties:
> +        st,fullscale-milligauss:
> +          enum: [1300, 1900, 2500, 4000, 4700, 5600, 8100]
Can we also add default: to these entries to document what happens
when nothing is provided?

Thanks,

Jonathan

^ permalink raw reply

* Re: [PATCH v2 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data
From: Jonathan Cameron @ 2026-06-23 19:23 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Herman van Hazendonk, David Lechner, Nuno Sá,
	Andy Shevchenko, Nathan Chancellor, Nick Desaulniers,
	Bill Wendling, Justin Stitt, Denis Ciocca, Lars-Peter Clausen,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Denis Ciocca,
	Linus Walleij, linux-iio, linux-kernel, llvm, devicetree, stable
In-Reply-To: <ajJwzsIMxX3Ew1x_@ashevche-desk.local>

On Wed, 17 Jun 2026 13:02:54 +0300
Andy Shevchenko <andriy.shevchenko@intel.com> wrote:

> On Tue, Jun 16, 2026 at 03:02:04PM +0200, Herman van Hazendonk wrote:
> > st_sensors_read_axis_data() unconditionally decoded multi-byte
> > results with get_unaligned_le16() / get_unaligned_le24() regardless
> > of the channel's declared scan_type.endianness.
> > 
> > For every ST sensor that has used this helper since it was introduced
> > this happened to be fine because the ST IMU/accel/gyro/pressure
> > families publish their data registers as little-endian and the
> > channel specs in those drivers declare IIO_LE accordingly.
> > 
> > The LSM303DLH magnetometer however publishes its X/Y/Z output as a
> > pair of big-endian bytes (the H register sits at the lower address,
> > 0x03/0x05/0x07, and the L register immediately after), and its
> > channel specs in st_magn_core.c correctly declare IIO_BE -- but
> > read_axis_data() ignored that and decoded as little-endian, swapping
> > the high and low bytes of every magnetometer sample. The LSM303DLHC
> > and LSM303DLM share the same st_magn_16bit_channels (IIO_BE) and
> > were therefore byte-swapped by the same bug; users of those parts
> > will see different in_magn_*_raw values after this fix lands.
> > 
> > The bug is most visible on a stationary chip: in earth's field the
> > true X reading is small and the high byte sits at 0x00, so swapping
> > the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0
> > = 240). Y and Z still appear "to vary" because their magnitudes are
> > larger and the noise in the low byte produces big swings in the
> > swapped high byte:
> > 
> >   before (LSM303DLH flat, sysfs in_magn_*_raw):
> >       X=240 (stuck), Y= 12032..23296, Z=-16128..-9728
> > 
> >   after (direct i2c-dev big-endian decode, same chip same orientation):
> >       X≈-4096, Y≈210, Z≈80     (sensible values reflecting earth's
> >                                 ambient field at low gauss range)
> > 
> > Fix read_axis_data() to dispatch on ch->scan_type.endianness and
> > call get_unaligned_be16() / get_unaligned_be24() when the channel
> > declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro,
> > st_pressure, st_lsm6dsx and others) are unaffected because their
> > channel specs already declare IIO_LE and the LE path is unchanged.
> > 
> > While restructuring the branches, replace the previously implicit
> > silent-success-with-uninitialised-*data fall-through for
> > byte_for_channel outside 1..3 with an explicit return -EINVAL. No
> > in-tree ST sensor publishes such a channel, but the new behaviour
> > is strictly safer than handing userspace garbage.  
> 
> Sounds like inevitable change in ABI, but worth doing it.
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Applied to the fixes-togreg branch of iio.git. I'll wait until after
rc1 to send a pull request though (and rebase on rc1 in the meantime).
The other patches may have to wait, I haven't checked yet!

Jonathan

> 


^ permalink raw reply

* Re:[PATCH 1/2] dt-bindings: hwmon: chipcap2: Add label property
From: Flaviu Nistor @ 2026-06-23 19:22 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Javier Carrasco, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Shuah Khan, Flaviu Nistor, linux-hwmon,
	linux-kernel, devicetree, linux-doc
In-Reply-To: <b6d34af3-0c4a-4870-a240-f7873621d2ce@roeck-us.net>

On 6/23/26 9:58 PM CET, Guenter Roeck wrote:
>On 6/23/26 11:16, Flaviu Nistor wrote:
>> On Mon Jun 22, 2026 at 7:29 PM CEST, Javier Carrasco wrote:
>>> On Mon Jun 22, 2026 at 2:21 PM CEST, Flaviu Nistor wrote:
>>>> Add support for an optional label property similar to other hwmon devices
>>>> This allows, in case of boards with multiple CHIPCAP2 sensors, to assign
>>>> distinct names to each instance.
>>>>
>>>> Signed-off-by: Flaviu Nistor <flaviu.nistor@gmail.com>
>>>> ---
>>>>   .../devicetree/bindings/hwmon/amphenol,chipcap2.yaml         | 5 +++++
>>>>   1 file changed, 5 insertions(+)
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.ya=
>>> ml b/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml
>>>> index 17351fdbefce..f00b5a4b14dd 100644
>>>> --- a/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml
>>>> +++ b/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml
>>>> @@ -33,6 +33,10 @@ properties:
>>>>     reg:
>>>>       maxItems: 1
>>>>
>>>> +  label:
>>>> +    description:
>>>> +      A descriptive name for this channel, like "ambient" or "psu".
>>>> +
>>>>     interrupts:
>>>>       items:
>>>>         - description: measurement ready indicator
>>>> @@ -72,6 +76,7 @@ examples:
>>>>                            <5 IRQ_TYPE_EDGE_RISING>,
>>>>                            <6 IRQ_TYPE_EDGE_RISING>;
>>>>               interrupt-names =3D "ready", "low", "high";
>>>> +            label =3D "somelabel";
>>>>               vdd-supply =3D <&reg_vdd>;
>>>>           };
>>>       };
>>>
>>> Hello Falviu, thank you for your patch.
>>>
>>
>> Hello Javier, thanks for your reply.
>>
>>> Should we not add a reference to hwmon-common.yaml (with
>>> unevelautedProperties instead of additionalProperties), as label is
>>> defined there? I believe that Krzysztof Kozlowski did something similar
>>> for the shunt-resistor-micro-ohms property. Could we follow suit here?
>>>
>>
>> This is a good question and I am happy you asked. I also thought a lot
>> about this and the reason I decided to go for this approach is that by using
>> $ref: hwmon-common.yaml#, I would have to change additionalProperties: false
>> to unevaluatedProperties: false, which will evaluate in case it is used, also
>> shunt-resistor-micro-ohms property which does not apply to this sensor. At
>> least this is my understanding, but of course I can be wrong (I see lm75 binding
>> also uses $ref: hwmon-common.yaml# but shunt-resistor-micro-ohms does not apply).
>>
>
>Where does the idea come from that shunt-resistor-micro-ohms would be mandatory ?
>That would make hwmon-common.yaml unusable for most chips.

I think this is a misunderstanding since I never had the intention to imply that
shunt-resistor-micro-ohms would be mandatory, but rather I observed that if I used
$ref: hwmon-common.yaml#, property shunt-resistor-micro-ohms can be added (no need to,
but still possible) in the example section and the dt_binding_check will pass.
Since hwmon-common.yaml is already there I will change the binding in a v2 and use it.

Best regards,
Flaviu

^ permalink raw reply

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

It is more logical to put the dt-bindings patch first in the series
before the driver that makes use of it.

On 6/23/26 11:07 AM, Lukas Metz wrote:
> 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
> +

There are a couple of more SPI properties needed since this is not a "normal"
SPI device. We can only write and not read because there is no D_OUT pin. So

spi-rx-bus-width:
  items:
    - const: 0

will describe this.

There is also no chip select, but now that I am looking, there doesn't seem to
be anything to describe this.

> +  ti,loaddacs-gpios:

This should match the actual pin name. And we don't need vendor prefix on gpios.
So `ldac-gpios`.

We also want the binding to be complete even if the driver doesn't all of it, so
`clear-gpios` and `sync-gpios` probably make sense too.

Usually for SPI devices we also add:

spi-max-frequency:
  maximum: ...

> +    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.

I prefer to use the actual pin name, i.e. vrefin-supply.

And for the description, it is the actual external reference voltage
supply, not just the voltage.

And we also need `avdd-supply: true`.

> +
> +  ti,internal-ref:
> +    type: boolean
> +    description:
> +      Flag if the internal reference is used (external otherwise).

Usually, we don't bother with a property like this since it is redundant.
If an external reference supply is given, then it gets used, otherwise
the internal reference is used.

> +
> +required:
> +  - compatible
> +  - reg

avdd-supply will be required too

> +
> +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 */

These chips don't appear to have a chip select pin, so this comment
doesn't make sense to me. More logical would be to just use dac@0
and reg = <0>; since it should just be ignored.

> +            ti,loaddacs-gpios = <&gpiog 8 GPIO_ACTIVE_HIGH>;

The pin is marked active low in the datasheet, so I would expect
this to be GPIO_ACTIVE_LOW.

> +            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
> 


^ permalink raw reply

* Re: [PATCH v3 3/3] iio: magnetometer: add driver for QST QMC5883L Sensor
From: Jonathan Cameron @ 2026-06-23 19:16 UTC (permalink / raw)
  To: Siratul Islam
  Cc: robh, krzk+dt, conor+dt, dlechner, nuno.sa, andy, linux-iio,
	devicetree, linux-kernel
In-Reply-To: <20260619104524.10172-4-siratul.islam@linux.dev>

On Fri, 19 Jun 2026 16:45:06 +0600
Siratul Islam <siratul.islam@linux.dev> wrote:

> Add driver for the QST QMC5883L 3-Axis Magnetic Sensor
> connected via i2c.
> 
> Signed-off-by: Siratul Islam <siratul.islam@linux.dev>

I'm going to apply this now but given rc1 is still some way off plenty
of time for more feedback or tags!

Applied to the testing branch of iio.git with a tiny tweak.

Thanks,

Jonathan

> diff --git a/drivers/iio/magnetometer/qmc5883l.c b/drivers/iio/magnetometer/qmc5883l.c
> new file mode 100644
> index 000000000000..b7fff3b63ca4
> --- /dev/null
> +++ b/drivers/iio/magnetometer/qmc5883l.c
> @@ -0,0 +1,517 @@
> +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
> +/*
> + * Support for QST QMC5883L 3-Axis Magnetic Sensor on I2C bus.
> + *
> + * Copyright (C) 2026 Siratul Islam <siratul.islam@linux.dev>
> + *
> + * Datasheet available at
> + * <https://www.qstcorp.com/upload/pdf/202512/13-52-04%20QMC5883L%20Datasheet%20Rev.%20B.pdf>
> + *
My pet trivial complaint.  Blank line adds nothing useful. I'll remove it whilst applying ;)
> + */

^ permalink raw reply


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