public inbox for devicetree@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC
@ 2026-02-13 14:47 Antoniu Miclaus
  2026-02-13 14:47 ` [PATCH v3 1/5] spi: allow ancillary devices to share parent's chip selects Antoniu Miclaus
                   ` (5 more replies)
  0 siblings, 6 replies; 15+ messages in thread
From: Antoniu Miclaus @ 2026-02-13 14:47 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Antoniu Miclaus,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

Add support for the AD4880, a dual-channel 20-bit 40MSPS SAR ADC from
the same family as AD4080.

The AD4880 has two independent ADC channels, each with its own SPI
configuration interface and LVDS data output. The driver uses
spi_new_ancillary_device() for the second channel's SPI and requires
two io-backend instances for the data interfaces.

This series includes:
  - SPI core fix to allow ancillary devices to share parent's chip selects
  - New devm_spi_new_ancillary_device() managed helper
  - Refactored devm_iio_backend_get_by_index() for multi-channel backend lookup
  - DT bindings update for AD4880
  - Driver support for AD4880

Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ad4880.pdf

Changes in v3:
  - Drop redundant NULL check for info->parent in spi_dev_check()
  - Add new devm_spi_new_ancillary_device() managed helper (new patch)
  - Refactor iio backend: make __devm_iio_backend_fwnode_get() reuse
    __devm_iio_backend_fwnode_get_by_index() to avoid code duplication
  - Use __free(fwnode_handle) for automatic cleanup in backend code
  - Add items descriptions for io-backends in dt-bindings
  - Fix reg example format in dt-bindings
  - Use devm_spi_new_ancillary_device() instead of manual
    spi_new_ancillary_device() + devm_add_action_or_reset()
  - Generalize ancillary device setup with loop instead of
    hardcoding channel 1


Antoniu Miclaus (5):
  spi: allow ancillary devices to share parent's chip selects
  spi: add devm_spi_new_ancillary_device()
  iio: backend: add devm_iio_backend_get_by_index()
  dt-bindings: iio: adc: ad4080: add AD4880 support
  iio: adc: ad4080: add support for AD4880 dual-channel ADC

 .../bindings/iio/adc/adi,ad4080.yaml          |  54 +++-
 drivers/iio/adc/ad4080.c                      | 248 ++++++++++++++----
 drivers/iio/industrialio-backend.c            |  61 +++--
 drivers/spi/spi.c                             |  69 ++++-
 include/linux/iio/backend.h                   |   2 +
 include/linux/spi/spi.h                       |   1 +
 6 files changed, 357 insertions(+), 78 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v3 1/5] spi: allow ancillary devices to share parent's chip selects
  2026-02-13 14:47 [PATCH v3 0/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
@ 2026-02-13 14:47 ` Antoniu Miclaus
  2026-02-13 16:39   ` Nuno Sá
  2026-02-15 18:36   ` Jonathan Cameron
  2026-02-13 14:47 ` [PATCH v3 2/5] spi: add devm_spi_new_ancillary_device() Antoniu Miclaus
                   ` (4 subsequent siblings)
  5 siblings, 2 replies; 15+ messages in thread
From: Antoniu Miclaus @ 2026-02-13 14:47 UTC (permalink / raw)
  To: Antoniu Miclaus, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

When registering an ancillary SPI device, the current code flags a chip
select conflict with the parent device. This happens because the
ancillary device intentionally uses one of the parent's chip selects,
but __spi_add_device() checks against all existing devices including
the parent.

Allow this by passing the parent device pointer to __spi_add_device()
and skipping the conflict check when the existing device is the parent.

Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
Reviewed-by: David Lechner <dlechner@baylibre.com>
---
Changes in v3:
  - Drop redundant NULL check for info->parent in spi_dev_check()

 drivers/spi/spi.c | 29 +++++++++++++++++++++++------
 1 file changed, 23 insertions(+), 6 deletions(-)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index e25df9990f82..693bdcc5a12a 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -641,12 +641,26 @@ static inline int spi_dev_check_cs(struct device *dev,
 	return 0;
 }
 
+struct spi_dev_check_info {
+	struct spi_device *new_spi;
+	struct spi_device *parent;	/* set for ancillary devices */
+};
+
 static int spi_dev_check(struct device *dev, void *data)
 {
 	struct spi_device *spi = to_spi_device(dev);
-	struct spi_device *new_spi = data;
+	struct spi_dev_check_info *info = data;
+	struct spi_device *new_spi = info->new_spi;
 	int status, idx;
 
+	/*
+	 * When registering an ancillary device, skip checking against the
+	 * parent device since the ancillary is intentionally using one of
+	 * the parent's chip selects.
+	 */
+	if (spi == info->parent)
+		return 0;
+
 	if (spi->controller == new_spi->controller) {
 		for (idx = 0; idx < spi->num_chipselect; idx++) {
 			status = spi_dev_check_cs(dev, spi, idx, new_spi, 0);
@@ -663,10 +677,11 @@ static void spi_cleanup(struct spi_device *spi)
 		spi->controller->cleanup(spi);
 }
 
-static int __spi_add_device(struct spi_device *spi)
+static int __spi_add_device(struct spi_device *spi, struct spi_device *parent)
 {
 	struct spi_controller *ctlr = spi->controller;
 	struct device *dev = ctlr->dev.parent;
+	struct spi_dev_check_info check_info;
 	int status, idx;
 	u8 cs;
 
@@ -710,7 +725,9 @@ static int __spi_add_device(struct spi_device *spi)
 	 * chipselect **BEFORE** we call setup(), else we'll trash
 	 * its configuration.
 	 */
-	status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);
+	check_info.new_spi = spi;
+	check_info.parent = parent;
+	status = bus_for_each_dev(&spi_bus_type, NULL, &check_info, spi_dev_check);
 	if (status)
 		return status;
 
@@ -772,7 +789,7 @@ int spi_add_device(struct spi_device *spi)
 	spi_dev_set_name(spi);
 
 	mutex_lock(&ctlr->add_lock);
-	status = __spi_add_device(spi);
+	status = __spi_add_device(spi, NULL);
 	mutex_unlock(&ctlr->add_lock);
 	return status;
 }
@@ -2580,8 +2597,8 @@ struct spi_device *spi_new_ancillary_device(struct spi_device *spi,
 
 	WARN_ON(!mutex_is_locked(&ctlr->add_lock));
 
-	/* Register the new device */
-	rc = __spi_add_device(ancillary);
+	/* Register the new device, passing the parent to skip CS conflict check */
+	rc = __spi_add_device(ancillary, spi);
 	if (rc) {
 		dev_err(&spi->dev, "failed to register ancillary device\n");
 		goto err_out;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v3 2/5] spi: add devm_spi_new_ancillary_device()
  2026-02-13 14:47 [PATCH v3 0/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
  2026-02-13 14:47 ` [PATCH v3 1/5] spi: allow ancillary devices to share parent's chip selects Antoniu Miclaus
@ 2026-02-13 14:47 ` Antoniu Miclaus
  2026-02-13 16:40   ` Nuno Sá
  2026-02-13 14:47 ` [PATCH v3 3/5] iio: backend: add devm_iio_backend_get_by_index() Antoniu Miclaus
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 15+ messages in thread
From: Antoniu Miclaus @ 2026-02-13 14:47 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Antoniu Miclaus,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

Add a devres-managed version of spi_new_ancillary_device() that
automatically unregisters the ancillary SPI device when the parent
device is removed.

This follows the same devm_add_action_or_reset() pattern used by the
other managed SPI functions (devm_spi_optimize_message,
devm_spi_register_controller, etc.) and eliminates the need for drivers
to open-code their own devm cleanup callbacks for ancillary devices.

Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
Changes in v3:
  - New patch

 drivers/spi/spi.c       | 40 ++++++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi.h |  1 +
 2 files changed, 41 insertions(+)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 693bdcc5a12a..1b48ec67b8e0 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -2612,6 +2612,46 @@ struct spi_device *spi_new_ancillary_device(struct spi_device *spi,
 }
 EXPORT_SYMBOL_GPL(spi_new_ancillary_device);
 
+static void devm_spi_unregister_device(void *spi)
+{
+	spi_unregister_device(spi);
+}
+
+/**
+ * devm_spi_new_ancillary_device() - Register managed ancillary SPI device
+ * @spi:         Pointer to the main SPI device registering the ancillary device
+ * @chip_select: Chip Select of the ancillary device
+ *
+ * Register an ancillary SPI device; for example some chips have a chip-select
+ * for normal device usage and another one for setup/firmware upload.
+ *
+ * This is the managed version of spi_new_ancillary_device(). The ancillary
+ * device will be unregistered automatically when the parent SPI device is
+ * unregistered.
+ *
+ * This may only be called from main SPI device's probe routine.
+ *
+ * Return: Pointer to new ancillary device on success; ERR_PTR on failure
+ */
+struct spi_device *devm_spi_new_ancillary_device(struct spi_device *spi,
+						 u8 chip_select)
+{
+	struct spi_device *ancillary;
+	int ret;
+
+	ancillary = spi_new_ancillary_device(spi, chip_select);
+	if (IS_ERR(ancillary))
+		return ancillary;
+
+	ret = devm_add_action_or_reset(&spi->dev, devm_spi_unregister_device,
+				       ancillary);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return ancillary;
+}
+EXPORT_SYMBOL_GPL(devm_spi_new_ancillary_device);
+
 #ifdef CONFIG_ACPI
 struct acpi_spi_lookup {
 	struct spi_controller 	*ctlr;
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index cb2c2df31089..9aef1987b12f 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -374,6 +374,7 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv)
 }
 
 extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 chip_select);
+extern struct spi_device *devm_spi_new_ancillary_device(struct spi_device *spi, u8 chip_select);
 
 /* Use a define to avoid include chaining to get THIS_MODULE */
 #define spi_register_driver(driver) \
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v3 3/5] iio: backend: add devm_iio_backend_get_by_index()
  2026-02-13 14:47 [PATCH v3 0/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
  2026-02-13 14:47 ` [PATCH v3 1/5] spi: allow ancillary devices to share parent's chip selects Antoniu Miclaus
  2026-02-13 14:47 ` [PATCH v3 2/5] spi: add devm_spi_new_ancillary_device() Antoniu Miclaus
@ 2026-02-13 14:47 ` Antoniu Miclaus
  2026-02-13 16:33   ` Nuno Sá
  2026-02-13 14:47 ` [PATCH v3 4/5] dt-bindings: iio: adc: ad4080: add AD4880 support Antoniu Miclaus
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 15+ messages in thread
From: Antoniu Miclaus @ 2026-02-13 14:47 UTC (permalink / raw)
  To: Antoniu Miclaus, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

Add a new function to get an IIO backend by its index in the
io-backends device tree property. This is useful for multi-channel
devices that have multiple backends, where looking up by index is
more straightforward than using named backends.

The new function directly uses the index to find the backend reference
in the io-backends property, avoiding the need for io-backend-names.

Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
Changes in v3:
  - Refactor __devm_iio_backend_fwnode_get() to reuse
    __devm_iio_backend_fwnode_get_by_index() instead of duplicating
    the lookup logic
  - Use __free(fwnode_handle) for automatic cleanup instead of manual
    fwnode_handle_put()
  - Set back->idx unconditionally in the by_index path

 drivers/iio/industrialio-backend.c | 61 ++++++++++++++++++++----------
 include/linux/iio/backend.h        |  2 +
 2 files changed, 44 insertions(+), 19 deletions(-)

diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c
index 447b694d6d5f..d90a3a0b17c6 100644
--- a/drivers/iio/industrialio-backend.c
+++ b/drivers/iio/industrialio-backend.c
@@ -949,25 +949,16 @@ int iio_backend_data_transfer_addr(struct iio_backend *back, u32 address)
 }
 EXPORT_SYMBOL_NS_GPL(iio_backend_data_transfer_addr, "IIO_BACKEND");
 
-static struct iio_backend *__devm_iio_backend_fwnode_get(struct device *dev, const char *name,
-							 struct fwnode_handle *fwnode)
+static struct iio_backend *
+__devm_iio_backend_fwnode_get_by_index(struct device *dev,
+				       struct fwnode_handle *fwnode,
+				       unsigned int index)
 {
-	struct fwnode_handle *fwnode_back;
+	struct fwnode_handle *fwnode_back __free(fwnode_handle) =
+		fwnode_find_reference(fwnode, "io-backends", index);
 	struct iio_backend *back;
-	unsigned int index;
 	int ret;
 
-	if (name) {
-		ret = device_property_match_string(dev, "io-backend-names",
-						   name);
-		if (ret < 0)
-			return ERR_PTR(ret);
-		index = ret;
-	} else {
-		index = 0;
-	}
-
-	fwnode_back = fwnode_find_reference(fwnode, "io-backends", index);
 	if (IS_ERR(fwnode_back))
 		return dev_err_cast_probe(dev, fwnode_back,
 					  "Cannot get Firmware reference\n");
@@ -977,21 +968,35 @@ static struct iio_backend *__devm_iio_backend_fwnode_get(struct device *dev, con
 		if (!device_match_fwnode(back->dev, fwnode_back))
 			continue;
 
-		fwnode_handle_put(fwnode_back);
 		ret = __devm_iio_backend_get(dev, back);
 		if (ret)
 			return ERR_PTR(ret);
 
-		if (name)
-			back->idx = index;
+		back->idx = index;
 
 		return back;
 	}
 
-	fwnode_handle_put(fwnode_back);
 	return ERR_PTR(-EPROBE_DEFER);
 }
 
+static struct iio_backend *__devm_iio_backend_fwnode_get(struct device *dev, const char *name,
+							 struct fwnode_handle *fwnode)
+{
+	unsigned int index = 0;
+	int ret;
+
+	if (name) {
+		ret = device_property_match_string(dev, "io-backend-names",
+						   name);
+		if (ret < 0)
+			return ERR_PTR(ret);
+		index = ret;
+	}
+
+	return __devm_iio_backend_fwnode_get_by_index(dev, fwnode, index);
+}
+
 /**
  * devm_iio_backend_get - Device managed backend device get
  * @dev: Consumer device for the backend
@@ -1008,6 +1013,24 @@ struct iio_backend *devm_iio_backend_get(struct device *dev, const char *name)
 }
 EXPORT_SYMBOL_NS_GPL(devm_iio_backend_get, "IIO_BACKEND");
 
+/**
+ * devm_iio_backend_get_by_index - Device managed backend device get by index
+ * @dev: Consumer device for the backend
+ * @index: Index of the backend in the io-backends property
+ *
+ * Get's the backend at @index associated with @dev.
+ *
+ * RETURNS:
+ * A backend pointer, negative error pointer otherwise.
+ */
+struct iio_backend *devm_iio_backend_get_by_index(struct device *dev,
+						  unsigned int index)
+{
+	return __devm_iio_backend_fwnode_get_by_index(dev, dev_fwnode(dev),
+						      index);
+}
+EXPORT_SYMBOL_NS_GPL(devm_iio_backend_get_by_index, "IIO_BACKEND");
+
 /**
  * devm_iio_backend_fwnode_get - Device managed backend firmware node get
  * @dev: Consumer device for the backend
diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h
index 7f815f3fed6a..8f18df0ca896 100644
--- a/include/linux/iio/backend.h
+++ b/include/linux/iio/backend.h
@@ -237,6 +237,8 @@ int iio_backend_extend_chan_spec(struct iio_backend *back,
 				 struct iio_chan_spec *chan);
 void *iio_backend_get_priv(const struct iio_backend *conv);
 struct iio_backend *devm_iio_backend_get(struct device *dev, const char *name);
+struct iio_backend *devm_iio_backend_get_by_index(struct device *dev,
+						  unsigned int index);
 struct iio_backend *devm_iio_backend_fwnode_get(struct device *dev,
 						const char *name,
 						struct fwnode_handle *fwnode);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v3 4/5] dt-bindings: iio: adc: ad4080: add AD4880 support
  2026-02-13 14:47 [PATCH v3 0/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
                   ` (2 preceding siblings ...)
  2026-02-13 14:47 ` [PATCH v3 3/5] iio: backend: add devm_iio_backend_get_by_index() Antoniu Miclaus
@ 2026-02-13 14:47 ` Antoniu Miclaus
  2026-02-13 16:34   ` Rob Herring (Arm)
  2026-02-14  8:54   ` Krzysztof Kozlowski
  2026-02-13 14:47 ` [PATCH v3 5/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
  2026-02-15 18:34 ` [PATCH v3 0/5] " Jonathan Cameron
  5 siblings, 2 replies; 15+ messages in thread
From: Antoniu Miclaus @ 2026-02-13 14:47 UTC (permalink / raw)
  To: Lars-Peter Clausen, Michael Hennerich, Antoniu Miclaus,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

Add support for the AD4880, a dual-channel 20-bit 40MSPS SAR ADC
with integrated fully differential amplifiers (FDA).

The AD4880 has two independent ADC channels, each with its own SPI
configuration interface. This requires:
- Two entries in reg property for primary and secondary channel
  chip selects
- Two io-backends entries for the two data channels

Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
Changes in v3:
  - Add items descriptions for io-backends entries
  - Fix reg example format: reg = <0 1> -> reg = <0>, <1>

 .../bindings/iio/adc/adi,ad4080.yaml          | 54 ++++++++++++++++++-
 1 file changed, 52 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml
index ccd6a0ac1539..871bbdd7e1ce 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml
@@ -18,7 +18,11 @@ description: |
   service a wide variety of precision, wide bandwidth data acquisition
   applications.
 
+  The AD4880 is a dual-channel variant with two independent ADC channels,
+  each with its own SPI configuration interface.
+
   https://www.analog.com/media/en/technical-documentation/data-sheets/ad4080.pdf
+  https://www.analog.com/media/en/technical-documentation/data-sheets/ad4880.pdf
 
 $ref: /schemas/spi/spi-peripheral-props.yaml#
 
@@ -31,9 +35,15 @@ properties:
       - adi,ad4084
       - adi,ad4086
       - adi,ad4087
+      - adi,ad4880
 
   reg:
-    maxItems: 1
+    minItems: 1
+    maxItems: 2
+    description:
+      SPI chip select(s). For single-channel devices, one chip select.
+      For multi-channel devices like AD4880, two chip selects are required
+      as each channel has its own SPI configuration interface.
 
   spi-max-frequency:
     description: Configuration of the SPI bus.
@@ -57,7 +67,11 @@ properties:
   vrefin-supply: true
 
   io-backends:
-    maxItems: 1
+    minItems: 1
+    maxItems: 2
+    items:
+      - description: Backend for channel A (primary)
+      - description: Backend for channel B (secondary)
 
   adi,lvds-cnv-enable:
     description: Enable the LVDS signal type on the CNV pin. Default is CMOS.
@@ -78,6 +92,25 @@ required:
   - vdd33-supply
   - vrefin-supply
 
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: adi,ad4880
+    then:
+      properties:
+        reg:
+          minItems: 2
+        io-backends:
+          minItems: 2
+    else:
+      properties:
+        reg:
+          maxItems: 1
+        io-backends:
+          maxItems: 1
+
 additionalProperties: false
 
 examples:
@@ -98,4 +131,21 @@ examples:
           io-backends = <&iio_backend>;
         };
     };
+  - |
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@0 {
+          compatible = "adi,ad4880";
+          reg = <0>, <1>;
+          spi-max-frequency = <10000000>;
+          vdd33-supply = <&vdd33>;
+          vddldo-supply = <&vddldo>;
+          vrefin-supply = <&vrefin>;
+          clocks = <&cnv>;
+          clock-names = "cnv";
+          io-backends = <&iio_backend_cha>, <&iio_backend_chb>;
+        };
+    };
 ...
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v3 5/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC
  2026-02-13 14:47 [PATCH v3 0/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
                   ` (3 preceding siblings ...)
  2026-02-13 14:47 ` [PATCH v3 4/5] dt-bindings: iio: adc: ad4080: add AD4880 support Antoniu Miclaus
@ 2026-02-13 14:47 ` Antoniu Miclaus
  2026-02-13 16:43   ` Nuno Sá
  2026-02-15 18:42   ` Jonathan Cameron
  2026-02-15 18:34 ` [PATCH v3 0/5] " Jonathan Cameron
  5 siblings, 2 replies; 15+ messages in thread
From: Antoniu Miclaus @ 2026-02-13 14:47 UTC (permalink / raw)
  To: Antoniu Miclaus, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

Add support for the AD4880, a dual-channel 20-bit 40MSPS SAR ADC with
integrated fully differential amplifiers (FDA).

The AD4880 has two independent ADC channels, each with its own SPI
configuration interface. The driver uses spi_new_ancillary_device() to
create an additional SPI device for the second channel, allowing both
channels to share the same SPI bus with different chip selects.

Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
Changes in v3:
  - Use devm_spi_new_ancillary_device() instead of manual
    spi_new_ancillary_device() + devm_add_action_or_reset()
  - Remove ad4080_unregister_ancillary() cleanup function
  - Generalize ancillary device setup with loop instead of
    hardcoding channel 1

 drivers/iio/adc/ad4080.c | 248 +++++++++++++++++++++++++++++++--------
 1 file changed, 197 insertions(+), 51 deletions(-)

diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c
index 7cf3b6ed7940..36a265ded80d 100644
--- a/drivers/iio/adc/ad4080.c
+++ b/drivers/iio/adc/ad4080.c
@@ -16,6 +16,7 @@
 #include <linux/mod_devicetable.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/property.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
 #include <linux/spi/spi.h>
@@ -131,6 +132,9 @@
 #define AD4084_CHIP_ID						0x0054
 #define AD4086_CHIP_ID						0x0056
 #define AD4087_CHIP_ID						0x0057
+#define AD4880_CHIP_ID						0x0750
+
+#define AD4080_MAX_CHANNELS					2
 
 #define AD4080_LVDS_CNV_CLK_CNT_MAX				7
 
@@ -176,8 +180,9 @@ struct ad4080_chip_info {
 };
 
 struct ad4080_state {
-	struct regmap			*regmap;
-	struct iio_backend		*back;
+	struct spi_device		*spi[AD4080_MAX_CHANNELS];
+	struct regmap			*regmap[AD4080_MAX_CHANNELS];
+	struct iio_backend		*back[AD4080_MAX_CHANNELS];
 	const struct ad4080_chip_info	*info;
 	/*
 	 * Synchronize access to members the of driver state, and ensure
@@ -203,10 +208,11 @@ static int ad4080_reg_access(struct iio_dev *indio_dev, unsigned int reg,
 {
 	struct ad4080_state *st = iio_priv(indio_dev);
 
+	/* Use channel 0 regmap for debugfs access */
 	if (readval)
-		return regmap_read(st->regmap, reg, readval);
+		return regmap_read(st->regmap[0], reg, readval);
 
-	return regmap_write(st->regmap, reg, writeval);
+	return regmap_write(st->regmap[0], reg, writeval);
 }
 
 static int ad4080_get_scale(struct ad4080_state *st, int *val, int *val2)
@@ -227,8 +233,9 @@ static unsigned int ad4080_get_dec_rate(struct iio_dev *dev,
 	struct ad4080_state *st = iio_priv(dev);
 	int ret;
 	unsigned int data;
+	unsigned int ch = chan->channel;
 
-	ret = regmap_read(st->regmap, AD4080_REG_FILTER_CONFIG, &data);
+	ret = regmap_read(st->regmap[ch], AD4080_REG_FILTER_CONFIG, &data);
 	if (ret)
 		return ret;
 
@@ -240,13 +247,14 @@ static int ad4080_set_dec_rate(struct iio_dev *dev,
 			       unsigned int mode)
 {
 	struct ad4080_state *st = iio_priv(dev);
+	unsigned int ch = chan->channel;
 
 	guard(mutex)(&st->lock);
 
 	if ((st->filter_type >= SINC_5 && mode >= 512) || mode < 2)
 		return -EINVAL;
 
-	return regmap_update_bits(st->regmap, AD4080_REG_FILTER_CONFIG,
+	return regmap_update_bits(st->regmap[ch], AD4080_REG_FILTER_CONFIG,
 				  AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK,
 				  FIELD_PREP(AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK,
 					     (ilog2(mode) - 1)));
@@ -304,23 +312,23 @@ static int ad4080_write_raw(struct iio_dev *indio_dev,
 	}
 }
 
-static int ad4080_lvds_sync_write(struct ad4080_state *st)
+static int ad4080_lvds_sync_write(struct ad4080_state *st, unsigned int ch)
 {
-	struct device *dev = regmap_get_device(st->regmap);
+	struct device *dev = regmap_get_device(st->regmap[ch]);
 	int ret;
 
-	ret = regmap_set_bits(st->regmap, AD4080_REG_ADC_DATA_INTF_CONFIG_A,
+	ret = regmap_set_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A,
 			      AD4080_ADC_DATA_INTF_CONFIG_A_INTF_CHK_EN);
 	if (ret)
 		return ret;
 
-	ret = iio_backend_interface_data_align(st->back, 10000);
+	ret = iio_backend_interface_data_align(st->back[ch], 10000);
 	if (ret)
 		return dev_err_probe(dev, ret,
 				     "Data alignment process failed\n");
 
 	dev_dbg(dev, "Success: Pattern correct and Locked!\n");
-	return regmap_clear_bits(st->regmap, AD4080_REG_ADC_DATA_INTF_CONFIG_A,
+	return regmap_clear_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A,
 				 AD4080_ADC_DATA_INTF_CONFIG_A_INTF_CHK_EN);
 }
 
@@ -329,9 +337,10 @@ static int ad4080_get_filter_type(struct iio_dev *dev,
 {
 	struct ad4080_state *st = iio_priv(dev);
 	unsigned int data;
+	unsigned int ch = chan->channel;
 	int ret;
 
-	ret = regmap_read(st->regmap, AD4080_REG_FILTER_CONFIG, &data);
+	ret = regmap_read(st->regmap[ch], AD4080_REG_FILTER_CONFIG, &data);
 	if (ret)
 		return ret;
 
@@ -343,6 +352,7 @@ static int ad4080_set_filter_type(struct iio_dev *dev,
 				  unsigned int mode)
 {
 	struct ad4080_state *st = iio_priv(dev);
+	unsigned int ch = chan->channel;
 	int dec_rate;
 	int ret;
 
@@ -355,11 +365,11 @@ static int ad4080_set_filter_type(struct iio_dev *dev,
 	if (mode >= SINC_5 && dec_rate >= 512)
 		return -EINVAL;
 
-	ret = iio_backend_filter_type_set(st->back, mode);
+	ret = iio_backend_filter_type_set(st->back[ch], mode);
 	if (ret)
 		return ret;
 
-	ret = regmap_update_bits(st->regmap, AD4080_REG_FILTER_CONFIG,
+	ret = regmap_update_bits(st->regmap[ch], AD4080_REG_FILTER_CONFIG,
 				 AD4080_FILTER_CONFIG_FILTER_SEL_MSK,
 				 FIELD_PREP(AD4080_FILTER_CONFIG_FILTER_SEL_MSK,
 					    mode));
@@ -399,6 +409,29 @@ static int ad4080_read_avail(struct iio_dev *indio_dev,
 	}
 }
 
+static int ad4880_update_scan_mode(struct iio_dev *indio_dev,
+				   const unsigned long *scan_mask)
+{
+	struct ad4080_state *st = iio_priv(indio_dev);
+	unsigned int ch;
+	int ret;
+
+	for (ch = 0; ch < st->info->num_channels; ch++) {
+		/*
+		 * Each backend has a single channel (channel 0 from the
+		 * backend's perspective), so always use channel index 0.
+		 */
+		if (test_bit(ch, scan_mask))
+			ret = iio_backend_chan_enable(st->back[ch], 0);
+		else
+			ret = iio_backend_chan_disable(st->back[ch], 0);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static const struct iio_info ad4080_iio_info = {
 	.debugfs_reg_access = ad4080_reg_access,
 	.read_raw = ad4080_read_raw,
@@ -406,6 +439,19 @@ static const struct iio_info ad4080_iio_info = {
 	.read_avail = ad4080_read_avail,
 };
 
+/*
+ * AD4880 needs update_scan_mode to enable/disable individual backend channels.
+ * Single-channel devices don't need this as their backends may not implement
+ * chan_enable/chan_disable operations.
+ */
+static const struct iio_info ad4880_iio_info = {
+	.debugfs_reg_access = ad4080_reg_access,
+	.read_raw = ad4080_read_raw,
+	.write_raw = ad4080_write_raw,
+	.read_avail = ad4080_read_avail,
+	.update_scan_mode = ad4880_update_scan_mode,
+};
+
 static const struct iio_enum ad4080_filter_type_enum = {
 	.items = ad4080_filter_type_iio_enum,
 	.num_items = ARRAY_SIZE(ad4080_filter_type_iio_enum),
@@ -420,17 +466,51 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = {
 	{ }
 };
 
-#define AD4080_CHANNEL_DEFINE(bits, storage) {				\
+/*
+ * AD4880 needs per-channel filter configuration since each channel has
+ * its own independent ADC with separate SPI interface.
+ */
+static struct iio_chan_spec_ext_info ad4880_ext_info[] = {
+	IIO_ENUM("filter_type", IIO_SEPARATE, &ad4080_filter_type_enum),
+	IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE,
+			   &ad4080_filter_type_enum),
+	{ }
+};
+
+#define AD4080_CHANNEL_DEFINE(bits, storage, idx) {			\
 	.type = IIO_VOLTAGE,						\
 	.indexed = 1,							\
-	.channel = 0,							\
+	.channel = (idx),						\
 	.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE),			\
 	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) |	\
 			BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),		\
 	.info_mask_shared_by_all_available =				\
 			BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),		\
 	.ext_info = ad4080_ext_info,					\
-	.scan_index = 0,						\
+	.scan_index = (idx),						\
+	.scan_type = {							\
+		.sign = 's',						\
+		.realbits = (bits),					\
+		.storagebits = (storage),				\
+	},								\
+}
+
+/*
+ * AD4880 has per-channel attributes (filter_type, oversampling_ratio,
+ * sampling_frequency) since each channel has its own independent ADC
+ * with separate SPI configuration interface.
+ */
+#define AD4880_CHANNEL_DEFINE(bits, storage, idx) {		\
+	.type = IIO_VOLTAGE,						\
+	.indexed = 1,							\
+	.channel = (idx),						\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) |		\
+			BIT(IIO_CHAN_INFO_SAMP_FREQ) |			\
+			BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),		\
+	.info_mask_separate_available =					\
+			BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),		\
+	.ext_info = ad4880_ext_info,				\
+	.scan_index = (idx),						\
 	.scan_type = {							\
 		.sign = 's',						\
 		.realbits = (bits),					\
@@ -438,17 +518,22 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = {
 	},								\
 }
 
-static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32);
+static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32, 0);
 
-static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32);
+static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32, 0);
 
-static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16);
+static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16, 0);
 
-static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16);
+static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16, 0);
 
-static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16);
+static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16, 0);
 
-static const struct iio_chan_spec ad4087_channel = AD4080_CHANNEL_DEFINE(14, 16);
+static const struct iio_chan_spec ad4087_channel = AD4080_CHANNEL_DEFINE(14, 16, 0);
+
+static const struct iio_chan_spec ad4880_channels[] = {
+	AD4880_CHANNEL_DEFINE(20, 32, 0),
+	AD4880_CHANNEL_DEFINE(20, 32, 1),
+};
 
 static const struct ad4080_chip_info ad4080_chip_info = {
 	.name = "ad4080",
@@ -510,25 +595,34 @@ static const struct ad4080_chip_info ad4087_chip_info = {
 	.lvds_cnv_clk_cnt_max = 1,
 };
 
-static int ad4080_setup(struct iio_dev *indio_dev)
+static const struct ad4080_chip_info ad4880_chip_info = {
+	.name = "ad4880",
+	.product_id = AD4880_CHIP_ID,
+	.scale_table = ad4080_scale_table,
+	.num_scales = ARRAY_SIZE(ad4080_scale_table),
+	.num_channels = 2,
+	.channels = ad4880_channels,
+	.lvds_cnv_clk_cnt_max = AD4080_LVDS_CNV_CLK_CNT_MAX,
+};
+
+static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch)
 {
-	struct ad4080_state *st = iio_priv(indio_dev);
-	struct device *dev = regmap_get_device(st->regmap);
+	struct device *dev = regmap_get_device(st->regmap[ch]);
 	__le16 id_le;
 	u16 id;
 	int ret;
 
-	ret = regmap_write(st->regmap, AD4080_REG_INTERFACE_CONFIG_A,
+	ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A,
 			   AD4080_INTERFACE_CONFIG_A_SW_RESET);
 	if (ret)
 		return ret;
 
-	ret = regmap_write(st->regmap, AD4080_REG_INTERFACE_CONFIG_A,
+	ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A,
 			   AD4080_INTERFACE_CONFIG_A_SDO_ENABLE);
 	if (ret)
 		return ret;
 
-	ret = regmap_bulk_read(st->regmap, AD4080_REG_PRODUCT_ID_L, &id_le,
+	ret = regmap_bulk_read(st->regmap[ch], AD4080_REG_PRODUCT_ID_L, &id_le,
 			       sizeof(id_le));
 	if (ret)
 		return ret;
@@ -537,18 +631,18 @@ static int ad4080_setup(struct iio_dev *indio_dev)
 	if (id != st->info->product_id)
 		dev_info(dev, "Unrecognized CHIP_ID 0x%X\n", id);
 
-	ret = regmap_set_bits(st->regmap, AD4080_REG_GPIO_CONFIG_A,
+	ret = regmap_set_bits(st->regmap[ch], AD4080_REG_GPIO_CONFIG_A,
 			      AD4080_GPIO_CONFIG_A_GPO_1_EN);
 	if (ret)
 		return ret;
 
-	ret = regmap_write(st->regmap, AD4080_REG_GPIO_CONFIG_B,
+	ret = regmap_write(st->regmap[ch], AD4080_REG_GPIO_CONFIG_B,
 			   FIELD_PREP(AD4080_GPIO_CONFIG_B_GPIO_1_SEL_MSK,
 				      AD4080_GPIO_CONFIG_B_GPIO_FILTER_RES_RDY));
 	if (ret)
 		return ret;
 
-	ret = iio_backend_num_lanes_set(st->back, st->num_lanes);
+	ret = iio_backend_num_lanes_set(st->back[ch], st->num_lanes);
 	if (ret)
 		return ret;
 
@@ -556,7 +650,7 @@ static int ad4080_setup(struct iio_dev *indio_dev)
 		return 0;
 
 	/* Set maximum LVDS Data Transfer Latency */
-	ret = regmap_update_bits(st->regmap,
+	ret = regmap_update_bits(st->regmap[ch],
 				 AD4080_REG_ADC_DATA_INTF_CONFIG_B,
 				 AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK,
 				 FIELD_PREP(AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK,
@@ -565,24 +659,39 @@ static int ad4080_setup(struct iio_dev *indio_dev)
 		return ret;
 
 	if (st->num_lanes > 1) {
-		ret = regmap_set_bits(st->regmap, AD4080_REG_ADC_DATA_INTF_CONFIG_A,
+		ret = regmap_set_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A,
 				      AD4080_ADC_DATA_INTF_CONFIG_A_SPI_LVDS_LANES);
 		if (ret)
 			return ret;
 	}
 
-	ret = regmap_set_bits(st->regmap,
+	ret = regmap_set_bits(st->regmap[ch],
 			      AD4080_REG_ADC_DATA_INTF_CONFIG_B,
 			      AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_EN);
 	if (ret)
 		return ret;
 
-	return ad4080_lvds_sync_write(st);
+	return ad4080_lvds_sync_write(st, ch);
+}
+
+static int ad4080_setup(struct iio_dev *indio_dev)
+{
+	struct ad4080_state *st = iio_priv(indio_dev);
+	unsigned int ch;
+	int ret;
+
+	for (ch = 0; ch < st->info->num_channels; ch++) {
+		ret = ad4080_setup_channel(st, ch);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static int ad4080_properties_parse(struct ad4080_state *st)
 {
-	struct device *dev = regmap_get_device(st->regmap);
+	struct device *dev = regmap_get_device(st->regmap[0]);
 
 	st->lvds_cnv_en = device_property_read_bool(dev, "adi,lvds-cnv-enable");
 
@@ -602,6 +711,7 @@ static int ad4080_probe(struct spi_device *spi)
 	struct device *dev = &spi->dev;
 	struct ad4080_state *st;
 	struct clk *clk;
+	unsigned int ch;
 	int ret;
 
 	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
@@ -610,6 +720,10 @@ static int ad4080_probe(struct spi_device *spi)
 
 	st = iio_priv(indio_dev);
 
+	st->info = spi_get_device_match_data(spi);
+	if (!st->info)
+		return -ENODEV;
+
 	ret = devm_regulator_bulk_get_enable(dev,
 					     ARRAY_SIZE(ad4080_power_supplies),
 					     ad4080_power_supplies);
@@ -617,13 +731,34 @@ static int ad4080_probe(struct spi_device *spi)
 		return dev_err_probe(dev, ret,
 				     "failed to get and enable supplies\n");
 
-	st->regmap = devm_regmap_init_spi(spi, &ad4080_regmap_config);
-	if (IS_ERR(st->regmap))
-		return PTR_ERR(st->regmap);
+	/* Setup primary SPI device (channel 0) */
+	st->spi[0] = spi;
+	st->regmap[0] = devm_regmap_init_spi(spi, &ad4080_regmap_config);
+	if (IS_ERR(st->regmap[0]))
+		return PTR_ERR(st->regmap[0]);
 
-	st->info = spi_get_device_match_data(spi);
-	if (!st->info)
-		return -ENODEV;
+	/* Setup ancillary SPI devices for additional channels */
+	if (st->info->num_channels > 1) {
+		u32 reg[AD4080_MAX_CHANNELS];
+
+		ret = device_property_read_u32_array(dev, "reg", reg,
+						     st->info->num_channels);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "missing reg entries for multi-channel device\n");
+
+		for (int i = 1; i < st->info->num_channels; i++) {
+			st->spi[i] = devm_spi_new_ancillary_device(spi, reg[i]);
+			if (IS_ERR(st->spi[i]))
+				return dev_err_probe(dev, PTR_ERR(st->spi[i]),
+						     "failed to register ancillary device\n");
+
+			st->regmap[i] = devm_regmap_init_spi(st->spi[i],
+							     &ad4080_regmap_config);
+			if (IS_ERR(st->regmap[i]))
+				return PTR_ERR(st->regmap[i]);
+		}
+	}
 
 	ret = devm_mutex_init(dev, &st->lock);
 	if (ret)
@@ -632,7 +767,8 @@ static int ad4080_probe(struct spi_device *spi)
 	indio_dev->name = st->info->name;
 	indio_dev->channels = st->info->channels;
 	indio_dev->num_channels = st->info->num_channels;
-	indio_dev->info = &ad4080_iio_info;
+	indio_dev->info = st->info->num_channels > 1 ?
+			  &ad4880_iio_info : &ad4080_iio_info;
 
 	ret = ad4080_properties_parse(st);
 	if (ret)
@@ -644,15 +780,23 @@ static int ad4080_probe(struct spi_device *spi)
 
 	st->clk_rate = clk_get_rate(clk);
 
-	st->back = devm_iio_backend_get(dev, NULL);
-	if (IS_ERR(st->back))
-		return PTR_ERR(st->back);
+	/* Get backends for all channels */
+	for (ch = 0; ch < st->info->num_channels; ch++) {
+		st->back[ch] = devm_iio_backend_get_by_index(dev, ch);
+		if (IS_ERR(st->back[ch]))
+			return PTR_ERR(st->back[ch]);
 
-	ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
-	if (ret)
-		return ret;
+		ret = devm_iio_backend_enable(dev, st->back[ch]);
+		if (ret)
+			return ret;
+	}
 
-	ret = devm_iio_backend_enable(dev, st->back);
+	/*
+	 * Request buffer from the first backend only. For multi-channel
+	 * devices (e.g., AD4880), all backends share a single IIO buffer
+	 * as data from all ADC channels is interleaved into one stream.
+	 */
+	ret = devm_iio_backend_request_buffer(dev, st->back[0], indio_dev);
 	if (ret)
 		return ret;
 
@@ -670,6 +814,7 @@ static const struct spi_device_id ad4080_id[] = {
 	{ "ad4084", (kernel_ulong_t)&ad4084_chip_info },
 	{ "ad4086", (kernel_ulong_t)&ad4086_chip_info },
 	{ "ad4087", (kernel_ulong_t)&ad4087_chip_info },
+	{ "ad4880", (kernel_ulong_t)&ad4880_chip_info },
 	{ }
 };
 MODULE_DEVICE_TABLE(spi, ad4080_id);
@@ -681,6 +826,7 @@ static const struct of_device_id ad4080_of_match[] = {
 	{ .compatible = "adi,ad4084", &ad4084_chip_info },
 	{ .compatible = "adi,ad4086", &ad4086_chip_info },
 	{ .compatible = "adi,ad4087", &ad4087_chip_info },
+	{ .compatible = "adi,ad4880", &ad4880_chip_info },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, ad4080_of_match);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 3/5] iio: backend: add devm_iio_backend_get_by_index()
  2026-02-13 14:47 ` [PATCH v3 3/5] iio: backend: add devm_iio_backend_get_by_index() Antoniu Miclaus
@ 2026-02-13 16:33   ` Nuno Sá
  0 siblings, 0 replies; 15+ messages in thread
From: Nuno Sá @ 2026-02-13 16:33 UTC (permalink / raw)
  To: Antoniu Miclaus, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

On Fri, 2026-02-13 at 16:47 +0200, Antoniu Miclaus wrote:
> Add a new function to get an IIO backend by its index in the
> io-backends device tree property. This is useful for multi-channel
> devices that have multiple backends, where looking up by index is
> more straightforward than using named backends.
> 
> The new function directly uses the index to find the backend reference
> in the io-backends property, avoiding the need for io-backend-names.
> 
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> ---

LGTM

Reviewed-by: Nuno Sá <nuno.sa@analog.com>

> Changes in v3:
>   - Refactor __devm_iio_backend_fwnode_get() to reuse
>     __devm_iio_backend_fwnode_get_by_index() instead of duplicating
>     the lookup logic
>   - Use __free(fwnode_handle) for automatic cleanup instead of manual
>     fwnode_handle_put()
>   - Set back->idx unconditionally in the by_index path
> 
>  drivers/iio/industrialio-backend.c | 61 ++++++++++++++++++++----------
>  include/linux/iio/backend.h        |  2 +
>  2 files changed, 44 insertions(+), 19 deletions(-)
> 
> diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c
> index 447b694d6d5f..d90a3a0b17c6 100644
> --- a/drivers/iio/industrialio-backend.c
> +++ b/drivers/iio/industrialio-backend.c
> @@ -949,25 +949,16 @@ int iio_backend_data_transfer_addr(struct iio_backend *back, u32 address)
>  }
>  EXPORT_SYMBOL_NS_GPL(iio_backend_data_transfer_addr, "IIO_BACKEND");
>  
> -static struct iio_backend *__devm_iio_backend_fwnode_get(struct device *dev, const char *name,
> -							 struct fwnode_handle *fwnode)
> +static struct iio_backend *
> +__devm_iio_backend_fwnode_get_by_index(struct device *dev,
> +				       struct fwnode_handle *fwnode,
> +				       unsigned int index)
>  {
> -	struct fwnode_handle *fwnode_back;
> +	struct fwnode_handle *fwnode_back __free(fwnode_handle) =
> +		fwnode_find_reference(fwnode, "io-backends", index);
>  	struct iio_backend *back;
> -	unsigned int index;
>  	int ret;
>  
> -	if (name) {
> -		ret = device_property_match_string(dev, "io-backend-names",
> -						   name);
> -		if (ret < 0)
> -			return ERR_PTR(ret);
> -		index = ret;
> -	} else {
> -		index = 0;
> -	}
> -
> -	fwnode_back = fwnode_find_reference(fwnode, "io-backends", index);
>  	if (IS_ERR(fwnode_back))
>  		return dev_err_cast_probe(dev, fwnode_back,
>  					  "Cannot get Firmware reference\n");
> @@ -977,21 +968,35 @@ static struct iio_backend *__devm_iio_backend_fwnode_get(struct device *dev,
> con
>  		if (!device_match_fwnode(back->dev, fwnode_back))
>  			continue;
>  
> -		fwnode_handle_put(fwnode_back);
>  		ret = __devm_iio_backend_get(dev, back);
>  		if (ret)
>  			return ERR_PTR(ret);
>  
> -		if (name)
> -			back->idx = index;
> +		back->idx = index;
>  
>  		return back;
>  	}
>  
> -	fwnode_handle_put(fwnode_back);
>  	return ERR_PTR(-EPROBE_DEFER);
>  }
>  
> +static struct iio_backend *__devm_iio_backend_fwnode_get(struct device *dev, const char *name,
> +							 struct fwnode_handle *fwnode)
> +{
> +	unsigned int index = 0;
> +	int ret;
> +
> +	if (name) {
> +		ret = device_property_match_string(dev, "io-backend-names",
> +						   name);
> +		if (ret < 0)
> +			return ERR_PTR(ret);
> +		index = ret;
> +	}
> +
> +	return __devm_iio_backend_fwnode_get_by_index(dev, fwnode, index);
> +}
> +
>  /**
>   * devm_iio_backend_get - Device managed backend device get
>   * @dev: Consumer device for the backend
> @@ -1008,6 +1013,24 @@ struct iio_backend *devm_iio_backend_get(struct device *dev, const char
> *name)
>  }
>  EXPORT_SYMBOL_NS_GPL(devm_iio_backend_get, "IIO_BACKEND");
>  
> +/**
> + * devm_iio_backend_get_by_index - Device managed backend device get by index
> + * @dev: Consumer device for the backend
> + * @index: Index of the backend in the io-backends property
> + *
> + * Get's the backend at @index associated with @dev.
> + *
> + * RETURNS:
> + * A backend pointer, negative error pointer otherwise.
> + */
> +struct iio_backend *devm_iio_backend_get_by_index(struct device *dev,
> +						  unsigned int index)
> +{
> +	return __devm_iio_backend_fwnode_get_by_index(dev, dev_fwnode(dev),
> +						      index);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_iio_backend_get_by_index, "IIO_BACKEND");
> +
>  /**
>   * devm_iio_backend_fwnode_get - Device managed backend firmware node get
>   * @dev: Consumer device for the backend
> diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h
> index 7f815f3fed6a..8f18df0ca896 100644
> --- a/include/linux/iio/backend.h
> +++ b/include/linux/iio/backend.h
> @@ -237,6 +237,8 @@ int iio_backend_extend_chan_spec(struct iio_backend *back,
>  				 struct iio_chan_spec *chan);
>  void *iio_backend_get_priv(const struct iio_backend *conv);
>  struct iio_backend *devm_iio_backend_get(struct device *dev, const char *name);
> +struct iio_backend *devm_iio_backend_get_by_index(struct device *dev,
> +						  unsigned int index);
>  struct iio_backend *devm_iio_backend_fwnode_get(struct device *dev,
>  						const char *name,
>  						struct fwnode_handle *fwnode);

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 4/5] dt-bindings: iio: adc: ad4080: add AD4880 support
  2026-02-13 14:47 ` [PATCH v3 4/5] dt-bindings: iio: adc: ad4080: add AD4880 support Antoniu Miclaus
@ 2026-02-13 16:34   ` Rob Herring (Arm)
  2026-02-14  8:54   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 15+ messages in thread
From: Rob Herring (Arm) @ 2026-02-13 16:34 UTC (permalink / raw)
  To: Antoniu Miclaus
  Cc: devicetree, Michael Hennerich, David Lechner, Mark Brown,
	Nuno Sá, Krzysztof Kozlowski, linux-kernel,
	Lars-Peter Clausen, Olivier Moysan, Andy Shevchenko, linux-spi,
	Jonathan Cameron, Conor Dooley, linux-iio


On Fri, 13 Feb 2026 16:47:36 +0200, Antoniu Miclaus wrote:
> Add support for the AD4880, a dual-channel 20-bit 40MSPS SAR ADC
> with integrated fully differential amplifiers (FDA).
> 
> The AD4880 has two independent ADC channels, each with its own SPI
> configuration interface. This requires:
> - Two entries in reg property for primary and secondary channel
>   chip selects
> - Two io-backends entries for the two data channels
> 
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> ---
> Changes in v3:
>   - Add items descriptions for io-backends entries
>   - Fix reg example format: reg = <0 1> -> reg = <0>, <1>
> 
>  .../bindings/iio/adc/adi,ad4080.yaml          | 54 ++++++++++++++++++-
>  1 file changed, 52 insertions(+), 2 deletions(-)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml: properties:io-backends: {'minItems': 1, 'maxItems': 2, 'items': [{'description': 'Backend for channel A (primary)'}, {'description': 'Backend for channel B (secondary)'}]} should not be valid under {'required': ['maxItems']}
	hint: "maxItems" is not needed with an "items" list
	from schema $id: http://devicetree.org/meta-schemas/items.yaml

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260213144742.16394-5-antoniu.miclaus@analog.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 1/5] spi: allow ancillary devices to share parent's chip selects
  2026-02-13 14:47 ` [PATCH v3 1/5] spi: allow ancillary devices to share parent's chip selects Antoniu Miclaus
@ 2026-02-13 16:39   ` Nuno Sá
  2026-02-15 18:36   ` Jonathan Cameron
  1 sibling, 0 replies; 15+ messages in thread
From: Nuno Sá @ 2026-02-13 16:39 UTC (permalink / raw)
  To: Antoniu Miclaus, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

On Fri, 2026-02-13 at 16:47 +0200, Antoniu Miclaus wrote:
> When registering an ancillary SPI device, the current code flags a chip
> select conflict with the parent device. This happens because the
> ancillary device intentionally uses one of the parent's chip selects,
> but __spi_add_device() checks against all existing devices including
> the parent.
> 
> Allow this by passing the parent device pointer to __spi_add_device()
> and skipping the conflict check when the existing device is the parent.
> 
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> Reviewed-by: David Lechner <dlechner@baylibre.com>
> ---

Acked-by: Nuno Sá <nuno.sa@analog.com>

> Changes in v3:
>   - Drop redundant NULL check for info->parent in spi_dev_check()
> 
>  drivers/spi/spi.c | 29 +++++++++++++++++++++++------
>  1 file changed, 23 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
> index e25df9990f82..693bdcc5a12a 100644
> --- a/drivers/spi/spi.c
> +++ b/drivers/spi/spi.c
> @@ -641,12 +641,26 @@ static inline int spi_dev_check_cs(struct device *dev,
>  	return 0;
>  }
>  
> +struct spi_dev_check_info {
> +	struct spi_device *new_spi;
> +	struct spi_device *parent;	/* set for ancillary devices */
> +};
> +
>  static int spi_dev_check(struct device *dev, void *data)
>  {
>  	struct spi_device *spi = to_spi_device(dev);
> -	struct spi_device *new_spi = data;
> +	struct spi_dev_check_info *info = data;
> +	struct spi_device *new_spi = info->new_spi;
>  	int status, idx;
>  
> +	/*
> +	 * When registering an ancillary device, skip checking against the
> +	 * parent device since the ancillary is intentionally using one of
> +	 * the parent's chip selects.
> +	 */
> +	if (spi == info->parent)
> +		return 0;
> +
>  	if (spi->controller == new_spi->controller) {
>  		for (idx = 0; idx < spi->num_chipselect; idx++) {
>  			status = spi_dev_check_cs(dev, spi, idx, new_spi, 0);
> @@ -663,10 +677,11 @@ static void spi_cleanup(struct spi_device *spi)
>  		spi->controller->cleanup(spi);
>  }
>  
> -static int __spi_add_device(struct spi_device *spi)
> +static int __spi_add_device(struct spi_device *spi, struct spi_device *parent)
>  {
>  	struct spi_controller *ctlr = spi->controller;
>  	struct device *dev = ctlr->dev.parent;
> +	struct spi_dev_check_info check_info;
>  	int status, idx;
>  	u8 cs;
>  
> @@ -710,7 +725,9 @@ static int __spi_add_device(struct spi_device *spi)
>  	 * chipselect **BEFORE** we call setup(), else we'll trash
>  	 * its configuration.
>  	 */
> -	status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);
> +	check_info.new_spi = spi;
> +	check_info.parent = parent;
> +	status = bus_for_each_dev(&spi_bus_type, NULL, &check_info, spi_dev_check);
>  	if (status)
>  		return status;
>  
> @@ -772,7 +789,7 @@ int spi_add_device(struct spi_device *spi)
>  	spi_dev_set_name(spi);
>  
>  	mutex_lock(&ctlr->add_lock);
> -	status = __spi_add_device(spi);
> +	status = __spi_add_device(spi, NULL);
>  	mutex_unlock(&ctlr->add_lock);
>  	return status;
>  }
> @@ -2580,8 +2597,8 @@ struct spi_device *spi_new_ancillary_device(struct spi_device *spi,
>  
>  	WARN_ON(!mutex_is_locked(&ctlr->add_lock));
>  
> -	/* Register the new device */
> -	rc = __spi_add_device(ancillary);
> +	/* Register the new device, passing the parent to skip CS conflict check */
> +	rc = __spi_add_device(ancillary, spi);
>  	if (rc) {
>  		dev_err(&spi->dev, "failed to register ancillary device\n");
>  		goto err_out;

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 2/5] spi: add devm_spi_new_ancillary_device()
  2026-02-13 14:47 ` [PATCH v3 2/5] spi: add devm_spi_new_ancillary_device() Antoniu Miclaus
@ 2026-02-13 16:40   ` Nuno Sá
  0 siblings, 0 replies; 15+ messages in thread
From: Nuno Sá @ 2026-02-13 16:40 UTC (permalink / raw)
  To: Antoniu Miclaus, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

On Fri, 2026-02-13 at 16:47 +0200, Antoniu Miclaus wrote:
> Add a devres-managed version of spi_new_ancillary_device() that
> automatically unregisters the ancillary SPI device when the parent
> device is removed.
> 
> This follows the same devm_add_action_or_reset() pattern used by the
> other managed SPI functions (devm_spi_optimize_message,
> devm_spi_register_controller, etc.) and eliminates the need for drivers
> to open-code their own devm cleanup callbacks for ancillary devices.
> 
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> ---

Acked-by: Nuno Sá <nuno.sa@analog.com>

> Changes in v3:
>   - New patch
> 
>  drivers/spi/spi.c       | 40 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/spi/spi.h |  1 +
>  2 files changed, 41 insertions(+)
> 
> diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
> index 693bdcc5a12a..1b48ec67b8e0 100644
> --- a/drivers/spi/spi.c
> +++ b/drivers/spi/spi.c
> @@ -2612,6 +2612,46 @@ struct spi_device *spi_new_ancillary_device(struct spi_device *spi,
>  }
>  EXPORT_SYMBOL_GPL(spi_new_ancillary_device);
>  
> +static void devm_spi_unregister_device(void *spi)
> +{
> +	spi_unregister_device(spi);
> +}
> +
> +/**
> + * devm_spi_new_ancillary_device() - Register managed ancillary SPI device
> + * @spi:         Pointer to the main SPI device registering the ancillary device
> + * @chip_select: Chip Select of the ancillary device
> + *
> + * Register an ancillary SPI device; for example some chips have a chip-select
> + * for normal device usage and another one for setup/firmware upload.
> + *
> + * This is the managed version of spi_new_ancillary_device(). The ancillary
> + * device will be unregistered automatically when the parent SPI device is
> + * unregistered.
> + *
> + * This may only be called from main SPI device's probe routine.
> + *
> + * Return: Pointer to new ancillary device on success; ERR_PTR on failure
> + */
> +struct spi_device *devm_spi_new_ancillary_device(struct spi_device *spi,
> +						 u8 chip_select)
> +{
> +	struct spi_device *ancillary;
> +	int ret;
> +
> +	ancillary = spi_new_ancillary_device(spi, chip_select);
> +	if (IS_ERR(ancillary))
> +		return ancillary;
> +
> +	ret = devm_add_action_or_reset(&spi->dev, devm_spi_unregister_device,
> +				       ancillary);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	return ancillary;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_new_ancillary_device);
> +
>  #ifdef CONFIG_ACPI
>  struct acpi_spi_lookup {
>  	struct spi_controller 	*ctlr;
> diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
> index cb2c2df31089..9aef1987b12f 100644
> --- a/include/linux/spi/spi.h
> +++ b/include/linux/spi/spi.h
> @@ -374,6 +374,7 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv)
>  }
>  
>  extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 chip_select);
> +extern struct spi_device *devm_spi_new_ancillary_device(struct spi_device *spi, u8 chip_select);
>  
>  /* Use a define to avoid include chaining to get THIS_MODULE */
>  #define spi_register_driver(driver) \

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 5/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC
  2026-02-13 14:47 ` [PATCH v3 5/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
@ 2026-02-13 16:43   ` Nuno Sá
  2026-02-15 18:42   ` Jonathan Cameron
  1 sibling, 0 replies; 15+ messages in thread
From: Nuno Sá @ 2026-02-13 16:43 UTC (permalink / raw)
  To: Antoniu Miclaus, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

On Fri, 2026-02-13 at 16:47 +0200, Antoniu Miclaus wrote:
> Add support for the AD4880, a dual-channel 20-bit 40MSPS SAR ADC with
> integrated fully differential amplifiers (FDA).
> 
> The AD4880 has two independent ADC channels, each with its own SPI
> configuration interface. The driver uses spi_new_ancillary_device() to
> create an additional SPI device for the second channel, allowing both
> channels to share the same SPI bus with different chip selects.
> 
> Reviewed-by: David Lechner <dlechner@baylibre.com>
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> ---

Reviewed-by: Nuno Sá <nuno.sa@analog.com>

> Changes in v3:
>   - Use devm_spi_new_ancillary_device() instead of manual
>     spi_new_ancillary_device() + devm_add_action_or_reset()
>   - Remove ad4080_unregister_ancillary() cleanup function
>   - Generalize ancillary device setup with loop instead of
>     hardcoding channel 1
> 
>  drivers/iio/adc/ad4080.c | 248 +++++++++++++++++++++++++++++++--------
>  1 file changed, 197 insertions(+), 51 deletions(-)
> 
> diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c
> index 7cf3b6ed7940..36a265ded80d 100644
> --- a/drivers/iio/adc/ad4080.c
> +++ b/drivers/iio/adc/ad4080.c
> @@ -16,6 +16,7 @@
>  #include <linux/mod_devicetable.h>
>  #include <linux/module.h>
>  #include <linux/mutex.h>
> +#include <linux/property.h>
>  #include <linux/regmap.h>
>  #include <linux/regulator/consumer.h>
>  #include <linux/spi/spi.h>
> @@ -131,6 +132,9 @@
>  #define AD4084_CHIP_ID						0x0054
>  #define AD4086_CHIP_ID						0x0056
>  #define AD4087_CHIP_ID						0x0057
> +#define AD4880_CHIP_ID						0x0750
> +
> +#define AD4080_MAX_CHANNELS					2
>  
>  #define AD4080_LVDS_CNV_CLK_CNT_MAX				7
>  
> @@ -176,8 +180,9 @@ struct ad4080_chip_info {
>  };
>  
>  struct ad4080_state {
> -	struct regmap			*regmap;
> -	struct iio_backend		*back;
> +	struct spi_device		*spi[AD4080_MAX_CHANNELS];
> +	struct regmap			*regmap[AD4080_MAX_CHANNELS];
> +	struct iio_backend		*back[AD4080_MAX_CHANNELS];
>  	const struct ad4080_chip_info	*info;
>  	/*
>  	 * Synchronize access to members the of driver state, and ensure
> @@ -203,10 +208,11 @@ static int ad4080_reg_access(struct iio_dev *indio_dev, unsigned int reg,
>  {
>  	struct ad4080_state *st = iio_priv(indio_dev);
>  
> +	/* Use channel 0 regmap for debugfs access */
>  	if (readval)
> -		return regmap_read(st->regmap, reg, readval);
> +		return regmap_read(st->regmap[0], reg, readval);
>  
> -	return regmap_write(st->regmap, reg, writeval);
> +	return regmap_write(st->regmap[0], reg, writeval);
>  }
>  
>  static int ad4080_get_scale(struct ad4080_state *st, int *val, int *val2)
> @@ -227,8 +233,9 @@ static unsigned int ad4080_get_dec_rate(struct iio_dev *dev,
>  	struct ad4080_state *st = iio_priv(dev);
>  	int ret;
>  	unsigned int data;
> +	unsigned int ch = chan->channel;
>  
> -	ret = regmap_read(st->regmap, AD4080_REG_FILTER_CONFIG, &data);
> +	ret = regmap_read(st->regmap[ch], AD4080_REG_FILTER_CONFIG, &data);
>  	if (ret)
>  		return ret;
>  
> @@ -240,13 +247,14 @@ static int ad4080_set_dec_rate(struct iio_dev *dev,
>  			       unsigned int mode)
>  {
>  	struct ad4080_state *st = iio_priv(dev);
> +	unsigned int ch = chan->channel;
>  
>  	guard(mutex)(&st->lock);
>  
>  	if ((st->filter_type >= SINC_5 && mode >= 512) || mode < 2)
>  		return -EINVAL;
>  
> -	return regmap_update_bits(st->regmap, AD4080_REG_FILTER_CONFIG,
> +	return regmap_update_bits(st->regmap[ch], AD4080_REG_FILTER_CONFIG,
>  				  AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK,
>  				  FIELD_PREP(AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK,
>  					     (ilog2(mode) - 1)));
> @@ -304,23 +312,23 @@ static int ad4080_write_raw(struct iio_dev *indio_dev,
>  	}
>  }
>  
> -static int ad4080_lvds_sync_write(struct ad4080_state *st)
> +static int ad4080_lvds_sync_write(struct ad4080_state *st, unsigned int ch)
>  {
> -	struct device *dev = regmap_get_device(st->regmap);
> +	struct device *dev = regmap_get_device(st->regmap[ch]);
>  	int ret;
>  
> -	ret = regmap_set_bits(st->regmap, AD4080_REG_ADC_DATA_INTF_CONFIG_A,
> +	ret = regmap_set_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A,
>  			      AD4080_ADC_DATA_INTF_CONFIG_A_INTF_CHK_EN);
>  	if (ret)
>  		return ret;
>  
> -	ret = iio_backend_interface_data_align(st->back, 10000);
> +	ret = iio_backend_interface_data_align(st->back[ch], 10000);
>  	if (ret)
>  		return dev_err_probe(dev, ret,
>  				     "Data alignment process failed\n");
>  
>  	dev_dbg(dev, "Success: Pattern correct and Locked!\n");
> -	return regmap_clear_bits(st->regmap, AD4080_REG_ADC_DATA_INTF_CONFIG_A,
> +	return regmap_clear_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A,
>  				 AD4080_ADC_DATA_INTF_CONFIG_A_INTF_CHK_EN);
>  }
>  
> @@ -329,9 +337,10 @@ static int ad4080_get_filter_type(struct iio_dev *dev,
>  {
>  	struct ad4080_state *st = iio_priv(dev);
>  	unsigned int data;
> +	unsigned int ch = chan->channel;
>  	int ret;
>  
> -	ret = regmap_read(st->regmap, AD4080_REG_FILTER_CONFIG, &data);
> +	ret = regmap_read(st->regmap[ch], AD4080_REG_FILTER_CONFIG, &data);
>  	if (ret)
>  		return ret;
>  
> @@ -343,6 +352,7 @@ static int ad4080_set_filter_type(struct iio_dev *dev,
>  				  unsigned int mode)
>  {
>  	struct ad4080_state *st = iio_priv(dev);
> +	unsigned int ch = chan->channel;
>  	int dec_rate;
>  	int ret;
>  
> @@ -355,11 +365,11 @@ static int ad4080_set_filter_type(struct iio_dev *dev,
>  	if (mode >= SINC_5 && dec_rate >= 512)
>  		return -EINVAL;
>  
> -	ret = iio_backend_filter_type_set(st->back, mode);
> +	ret = iio_backend_filter_type_set(st->back[ch], mode);
>  	if (ret)
>  		return ret;
>  
> -	ret = regmap_update_bits(st->regmap, AD4080_REG_FILTER_CONFIG,
> +	ret = regmap_update_bits(st->regmap[ch], AD4080_REG_FILTER_CONFIG,
>  				 AD4080_FILTER_CONFIG_FILTER_SEL_MSK,
>  				 FIELD_PREP(AD4080_FILTER_CONFIG_FILTER_SEL_MSK,
>  					    mode));
> @@ -399,6 +409,29 @@ static int ad4080_read_avail(struct iio_dev *indio_dev,
>  	}
>  }
>  
> +static int ad4880_update_scan_mode(struct iio_dev *indio_dev,
> +				   const unsigned long *scan_mask)
> +{
> +	struct ad4080_state *st = iio_priv(indio_dev);
> +	unsigned int ch;
> +	int ret;
> +
> +	for (ch = 0; ch < st->info->num_channels; ch++) {
> +		/*
> +		 * Each backend has a single channel (channel 0 from the
> +		 * backend's perspective), so always use channel index 0.
> +		 */
> +		if (test_bit(ch, scan_mask))
> +			ret = iio_backend_chan_enable(st->back[ch], 0);
> +		else
> +			ret = iio_backend_chan_disable(st->back[ch], 0);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
>  static const struct iio_info ad4080_iio_info = {
>  	.debugfs_reg_access = ad4080_reg_access,
>  	.read_raw = ad4080_read_raw,
> @@ -406,6 +439,19 @@ static const struct iio_info ad4080_iio_info = {
>  	.read_avail = ad4080_read_avail,
>  };
>  
> +/*
> + * AD4880 needs update_scan_mode to enable/disable individual backend channels.
> + * Single-channel devices don't need this as their backends may not implement
> + * chan_enable/chan_disable operations.
> + */
> +static const struct iio_info ad4880_iio_info = {
> +	.debugfs_reg_access = ad4080_reg_access,
> +	.read_raw = ad4080_read_raw,
> +	.write_raw = ad4080_write_raw,
> +	.read_avail = ad4080_read_avail,
> +	.update_scan_mode = ad4880_update_scan_mode,
> +};
> +
>  static const struct iio_enum ad4080_filter_type_enum = {
>  	.items = ad4080_filter_type_iio_enum,
>  	.num_items = ARRAY_SIZE(ad4080_filter_type_iio_enum),
> @@ -420,17 +466,51 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = {
>  	{ }
>  };
>  
> -#define AD4080_CHANNEL_DEFINE(bits, storage) {				\
> +/*
> + * AD4880 needs per-channel filter configuration since each channel has
> + * its own independent ADC with separate SPI interface.
> + */
> +static struct iio_chan_spec_ext_info ad4880_ext_info[] = {
> +	IIO_ENUM("filter_type", IIO_SEPARATE, &ad4080_filter_type_enum),
> +	IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE,
> +			   &ad4080_filter_type_enum),
> +	{ }
> +};
> +
> +#define AD4080_CHANNEL_DEFINE(bits, storage, idx) {			\
>  	.type = IIO_VOLTAGE,						\
>  	.indexed = 1,							\
> -	.channel = 0,							\
> +	.channel = (idx),						\
>  	.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE),			\
>  	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) |	\
>  			BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),		\
>  	.info_mask_shared_by_all_available =				\
>  			BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),		\
>  	.ext_info = ad4080_ext_info,					\
> -	.scan_index = 0,						\
> +	.scan_index = (idx),						\
> +	.scan_type = {							\
> +		.sign = 's',						\
> +		.realbits = (bits),					\
> +		.storagebits = (storage),				\
> +	},								\
> +}
> +
> +/*
> + * AD4880 has per-channel attributes (filter_type, oversampling_ratio,
> + * sampling_frequency) since each channel has its own independent ADC
> + * with separate SPI configuration interface.
> + */
> +#define AD4880_CHANNEL_DEFINE(bits, storage, idx) {		\
> +	.type = IIO_VOLTAGE,						\
> +	.indexed = 1,							\
> +	.channel = (idx),						\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) |		\
> +			BIT(IIO_CHAN_INFO_SAMP_FREQ) |			\
> +			BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),		\
> +	.info_mask_separate_available =					\
> +			BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),		\
> +	.ext_info = ad4880_ext_info,				\
> +	.scan_index = (idx),						\
>  	.scan_type = {							\
>  		.sign = 's',						\
>  		.realbits = (bits),					\
> @@ -438,17 +518,22 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = {
>  	},								\
>  }
>  
> -static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32);
> +static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32, 0);
>  
> -static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32);
> +static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32, 0);
>  
> -static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16);
> +static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16, 0);
>  
> -static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16);
> +static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16, 0);
>  
> -static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16);
> +static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16, 0);
>  
> -static const struct iio_chan_spec ad4087_channel = AD4080_CHANNEL_DEFINE(14, 16);
> +static const struct iio_chan_spec ad4087_channel = AD4080_CHANNEL_DEFINE(14, 16, 0);
> +
> +static const struct iio_chan_spec ad4880_channels[] = {
> +	AD4880_CHANNEL_DEFINE(20, 32, 0),
> +	AD4880_CHANNEL_DEFINE(20, 32, 1),
> +};
>  
>  static const struct ad4080_chip_info ad4080_chip_info = {
>  	.name = "ad4080",
> @@ -510,25 +595,34 @@ static const struct ad4080_chip_info ad4087_chip_info = {
>  	.lvds_cnv_clk_cnt_max = 1,
>  };
>  
> -static int ad4080_setup(struct iio_dev *indio_dev)
> +static const struct ad4080_chip_info ad4880_chip_info = {
> +	.name = "ad4880",
> +	.product_id = AD4880_CHIP_ID,
> +	.scale_table = ad4080_scale_table,
> +	.num_scales = ARRAY_SIZE(ad4080_scale_table),
> +	.num_channels = 2,
> +	.channels = ad4880_channels,
> +	.lvds_cnv_clk_cnt_max = AD4080_LVDS_CNV_CLK_CNT_MAX,
> +};
> +
> +static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch)
>  {
> -	struct ad4080_state *st = iio_priv(indio_dev);
> -	struct device *dev = regmap_get_device(st->regmap);
> +	struct device *dev = regmap_get_device(st->regmap[ch]);
>  	__le16 id_le;
>  	u16 id;
>  	int ret;
>  
> -	ret = regmap_write(st->regmap, AD4080_REG_INTERFACE_CONFIG_A,
> +	ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A,
>  			   AD4080_INTERFACE_CONFIG_A_SW_RESET);
>  	if (ret)
>  		return ret;
>  
> -	ret = regmap_write(st->regmap, AD4080_REG_INTERFACE_CONFIG_A,
> +	ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A,
>  			   AD4080_INTERFACE_CONFIG_A_SDO_ENABLE);
>  	if (ret)
>  		return ret;
>  
> -	ret = regmap_bulk_read(st->regmap, AD4080_REG_PRODUCT_ID_L, &id_le,
> +	ret = regmap_bulk_read(st->regmap[ch], AD4080_REG_PRODUCT_ID_L, &id_le,
>  			       sizeof(id_le));
>  	if (ret)
>  		return ret;
> @@ -537,18 +631,18 @@ static int ad4080_setup(struct iio_dev *indio_dev)
>  	if (id != st->info->product_id)
>  		dev_info(dev, "Unrecognized CHIP_ID 0x%X\n", id);
>  
> -	ret = regmap_set_bits(st->regmap, AD4080_REG_GPIO_CONFIG_A,
> +	ret = regmap_set_bits(st->regmap[ch], AD4080_REG_GPIO_CONFIG_A,
>  			      AD4080_GPIO_CONFIG_A_GPO_1_EN);
>  	if (ret)
>  		return ret;
>  
> -	ret = regmap_write(st->regmap, AD4080_REG_GPIO_CONFIG_B,
> +	ret = regmap_write(st->regmap[ch], AD4080_REG_GPIO_CONFIG_B,
>  			   FIELD_PREP(AD4080_GPIO_CONFIG_B_GPIO_1_SEL_MSK,
>  				      AD4080_GPIO_CONFIG_B_GPIO_FILTER_RES_RDY));
>  	if (ret)
>  		return ret;
>  
> -	ret = iio_backend_num_lanes_set(st->back, st->num_lanes);
> +	ret = iio_backend_num_lanes_set(st->back[ch], st->num_lanes);
>  	if (ret)
>  		return ret;
>  
> @@ -556,7 +650,7 @@ static int ad4080_setup(struct iio_dev *indio_dev)
>  		return 0;
>  
>  	/* Set maximum LVDS Data Transfer Latency */
> -	ret = regmap_update_bits(st->regmap,
> +	ret = regmap_update_bits(st->regmap[ch],
>  				 AD4080_REG_ADC_DATA_INTF_CONFIG_B,
>  				 AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK,
>  				 FIELD_PREP(AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK,
> @@ -565,24 +659,39 @@ static int ad4080_setup(struct iio_dev *indio_dev)
>  		return ret;
>  
>  	if (st->num_lanes > 1) {
> -		ret = regmap_set_bits(st->regmap, AD4080_REG_ADC_DATA_INTF_CONFIG_A,
> +		ret = regmap_set_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A,
>  				      AD4080_ADC_DATA_INTF_CONFIG_A_SPI_LVDS_LANES);
>  		if (ret)
>  			return ret;
>  	}
>  
> -	ret = regmap_set_bits(st->regmap,
> +	ret = regmap_set_bits(st->regmap[ch],
>  			      AD4080_REG_ADC_DATA_INTF_CONFIG_B,
>  			      AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_EN);
>  	if (ret)
>  		return ret;
>  
> -	return ad4080_lvds_sync_write(st);
> +	return ad4080_lvds_sync_write(st, ch);
> +}
> +
> +static int ad4080_setup(struct iio_dev *indio_dev)
> +{
> +	struct ad4080_state *st = iio_priv(indio_dev);
> +	unsigned int ch;
> +	int ret;
> +
> +	for (ch = 0; ch < st->info->num_channels; ch++) {
> +		ret = ad4080_setup_channel(st, ch);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
>  }
>  
>  static int ad4080_properties_parse(struct ad4080_state *st)
>  {
> -	struct device *dev = regmap_get_device(st->regmap);
> +	struct device *dev = regmap_get_device(st->regmap[0]);
>  
>  	st->lvds_cnv_en = device_property_read_bool(dev, "adi,lvds-cnv-enable");
>  
> @@ -602,6 +711,7 @@ static int ad4080_probe(struct spi_device *spi)
>  	struct device *dev = &spi->dev;
>  	struct ad4080_state *st;
>  	struct clk *clk;
> +	unsigned int ch;
>  	int ret;
>  
>  	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> @@ -610,6 +720,10 @@ static int ad4080_probe(struct spi_device *spi)
>  
>  	st = iio_priv(indio_dev);
>  
> +	st->info = spi_get_device_match_data(spi);
> +	if (!st->info)
> +		return -ENODEV;
> +
>  	ret = devm_regulator_bulk_get_enable(dev,
>  					     ARRAY_SIZE(ad4080_power_supplies),
>  					     ad4080_power_supplies);
> @@ -617,13 +731,34 @@ static int ad4080_probe(struct spi_device *spi)
>  		return dev_err_probe(dev, ret,
>  				     "failed to get and enable supplies\n");
>  
> -	st->regmap = devm_regmap_init_spi(spi, &ad4080_regmap_config);
> -	if (IS_ERR(st->regmap))
> -		return PTR_ERR(st->regmap);
> +	/* Setup primary SPI device (channel 0) */
> +	st->spi[0] = spi;
> +	st->regmap[0] = devm_regmap_init_spi(spi, &ad4080_regmap_config);
> +	if (IS_ERR(st->regmap[0]))
> +		return PTR_ERR(st->regmap[0]);
>  
> -	st->info = spi_get_device_match_data(spi);
> -	if (!st->info)
> -		return -ENODEV;
> +	/* Setup ancillary SPI devices for additional channels */
> +	if (st->info->num_channels > 1) {
> +		u32 reg[AD4080_MAX_CHANNELS];
> +
> +		ret = device_property_read_u32_array(dev, "reg", reg,
> +						     st->info->num_channels);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "missing reg entries for multi-channel device\n");
> +
> +		for (int i = 1; i < st->info->num_channels; i++) {
> +			st->spi[i] = devm_spi_new_ancillary_device(spi, reg[i]);
> +			if (IS_ERR(st->spi[i]))
> +				return dev_err_probe(dev, PTR_ERR(st->spi[i]),
> +						     "failed to register ancillary device\n");
> +
> +			st->regmap[i] = devm_regmap_init_spi(st->spi[i],
> +							     &ad4080_regmap_config);
> +			if (IS_ERR(st->regmap[i]))
> +				return PTR_ERR(st->regmap[i]);
> +		}
> +	}
>  
>  	ret = devm_mutex_init(dev, &st->lock);
>  	if (ret)
> @@ -632,7 +767,8 @@ static int ad4080_probe(struct spi_device *spi)
>  	indio_dev->name = st->info->name;
>  	indio_dev->channels = st->info->channels;
>  	indio_dev->num_channels = st->info->num_channels;
> -	indio_dev->info = &ad4080_iio_info;
> +	indio_dev->info = st->info->num_channels > 1 ?
> +			  &ad4880_iio_info : &ad4080_iio_info;
>  
>  	ret = ad4080_properties_parse(st);
>  	if (ret)
> @@ -644,15 +780,23 @@ static int ad4080_probe(struct spi_device *spi)
>  
>  	st->clk_rate = clk_get_rate(clk);
>  
> -	st->back = devm_iio_backend_get(dev, NULL);
> -	if (IS_ERR(st->back))
> -		return PTR_ERR(st->back);
> +	/* Get backends for all channels */
> +	for (ch = 0; ch < st->info->num_channels; ch++) {
> +		st->back[ch] = devm_iio_backend_get_by_index(dev, ch);
> +		if (IS_ERR(st->back[ch]))
> +			return PTR_ERR(st->back[ch]);
>  
> -	ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
> -	if (ret)
> -		return ret;
> +		ret = devm_iio_backend_enable(dev, st->back[ch]);
> +		if (ret)
> +			return ret;
> +	}
>  
> -	ret = devm_iio_backend_enable(dev, st->back);
> +	/*
> +	 * Request buffer from the first backend only. For multi-channel
> +	 * devices (e.g., AD4880), all backends share a single IIO buffer
> +	 * as data from all ADC channels is interleaved into one stream.
> +	 */
> +	ret = devm_iio_backend_request_buffer(dev, st->back[0], indio_dev);
>  	if (ret)
>  		return ret;
>  
> @@ -670,6 +814,7 @@ static const struct spi_device_id ad4080_id[] = {
>  	{ "ad4084", (kernel_ulong_t)&ad4084_chip_info },
>  	{ "ad4086", (kernel_ulong_t)&ad4086_chip_info },
>  	{ "ad4087", (kernel_ulong_t)&ad4087_chip_info },
> +	{ "ad4880", (kernel_ulong_t)&ad4880_chip_info },
>  	{ }
>  };
>  MODULE_DEVICE_TABLE(spi, ad4080_id);
> @@ -681,6 +826,7 @@ static const struct of_device_id ad4080_of_match[] = {
>  	{ .compatible = "adi,ad4084", &ad4084_chip_info },
>  	{ .compatible = "adi,ad4086", &ad4086_chip_info },
>  	{ .compatible = "adi,ad4087", &ad4087_chip_info },
> +	{ .compatible = "adi,ad4880", &ad4880_chip_info },
>  	{ }
>  };
>  MODULE_DEVICE_TABLE(of, ad4080_of_match);

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 4/5] dt-bindings: iio: adc: ad4080: add AD4880 support
  2026-02-13 14:47 ` [PATCH v3 4/5] dt-bindings: iio: adc: ad4080: add AD4880 support Antoniu Miclaus
  2026-02-13 16:34   ` Rob Herring (Arm)
@ 2026-02-14  8:54   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-14  8:54 UTC (permalink / raw)
  To: Antoniu Miclaus, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivier Moysan,
	Mark Brown, linux-iio, devicetree, linux-kernel, linux-spi

On 13/02/2026 15:47, Antoniu Miclaus wrote:
> Add support for the AD4880, a dual-channel 20-bit 40MSPS SAR ADC
> with integrated fully differential amplifiers (FDA).
> 
> The AD4880 has two independent ADC channels, each with its own SPI
> configuration interface. This requires:
> - Two entries in reg property for primary and secondary channel
>   chip selects
> - Two io-backends entries for the two data channels
> 
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> ---
> Changes in v3:
>   - Add items descriptions for io-backends entries
>   - Fix reg example format: reg = <0 1> -> reg = <0>, <1>

Not tested.

Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 0/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC
  2026-02-13 14:47 [PATCH v3 0/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
                   ` (4 preceding siblings ...)
  2026-02-13 14:47 ` [PATCH v3 5/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
@ 2026-02-15 18:34 ` Jonathan Cameron
  5 siblings, 0 replies; 15+ messages in thread
From: Jonathan Cameron @ 2026-02-15 18:34 UTC (permalink / raw)
  To: Antoniu Miclaus
  Cc: Lars-Peter Clausen, Michael Hennerich, David Lechner,
	Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Olivier Moysan, Mark Brown, linux-iio, devicetree,
	linux-kernel, linux-spi

On Fri, 13 Feb 2026 16:47:32 +0200
Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:

> Add support for the AD4880, a dual-channel 20-bit 40MSPS SAR ADC from
> the same family as AD4080.
> 
> The AD4880 has two independent ADC channels, each with its own SPI
> configuration interface and LVDS data output. The driver uses
> spi_new_ancillary_device() for the second channel's SPI and requires
> two io-backend instances for the data interfaces.
> 
> This series includes:
>   - SPI core fix to allow ancillary devices to share parent's chip selects
>   - New devm_spi_new_ancillary_device() managed helper
>   - Refactored devm_iio_backend_get_by_index() for multi-channel backend lookup
>   - DT bindings update for AD4880
>   - Driver support for AD4880
> 
> Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ad4880.pdf

Just to be clear on status, this set is held on resolving Andy's
observation (that probably raced with posting this update) that
the SPI core has support for multiple chip select devices and how
this code differs from what that provides.

I haven't looked into this in detail so it may well just be a case
of providing more details on what is going on here vs what the SPI
subsystem supports (which might just be switching all the chip selects
together?)

Jonathan

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 1/5] spi: allow ancillary devices to share parent's chip selects
  2026-02-13 14:47 ` [PATCH v3 1/5] spi: allow ancillary devices to share parent's chip selects Antoniu Miclaus
  2026-02-13 16:39   ` Nuno Sá
@ 2026-02-15 18:36   ` Jonathan Cameron
  1 sibling, 0 replies; 15+ messages in thread
From: Jonathan Cameron @ 2026-02-15 18:36 UTC (permalink / raw)
  To: Antoniu Miclaus
  Cc: Lars-Peter Clausen, Michael Hennerich, David Lechner,
	Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Olivier Moysan, Mark Brown, linux-iio, devicetree,
	linux-kernel, linux-spi

On Fri, 13 Feb 2026 16:47:33 +0200
Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:

> When registering an ancillary SPI device, the current code flags a chip
> select conflict with the parent device. This happens because the
> ancillary device intentionally uses one of the parent's chip selects,
> but __spi_add_device() checks against all existing devices including
> the parent.
> 
> Allow this by passing the parent device pointer to __spi_add_device()
> and skipping the conflict check when the existing device is the parent.

Smells like a fix. If so why don't we have a fixes tag?

This will need review from Mark. I haven't really understood how this
is intended to work.

Jonathan


> 
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> Reviewed-by: David Lechner <dlechner@baylibre.com>
> ---
> Changes in v3:
>   - Drop redundant NULL check for info->parent in spi_dev_check()
> 
>  drivers/spi/spi.c | 29 +++++++++++++++++++++++------
>  1 file changed, 23 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
> index e25df9990f82..693bdcc5a12a 100644
> --- a/drivers/spi/spi.c
> +++ b/drivers/spi/spi.c
> @@ -641,12 +641,26 @@ static inline int spi_dev_check_cs(struct device *dev,
>  	return 0;
>  }
>  
> +struct spi_dev_check_info {
> +	struct spi_device *new_spi;
> +	struct spi_device *parent;	/* set for ancillary devices */
> +};
> +
>  static int spi_dev_check(struct device *dev, void *data)
>  {
>  	struct spi_device *spi = to_spi_device(dev);
> -	struct spi_device *new_spi = data;
> +	struct spi_dev_check_info *info = data;
> +	struct spi_device *new_spi = info->new_spi;
>  	int status, idx;
>  
> +	/*
> +	 * When registering an ancillary device, skip checking against the
> +	 * parent device since the ancillary is intentionally using one of
> +	 * the parent's chip selects.
> +	 */
> +	if (spi == info->parent)
> +		return 0;
> +
>  	if (spi->controller == new_spi->controller) {
>  		for (idx = 0; idx < spi->num_chipselect; idx++) {
>  			status = spi_dev_check_cs(dev, spi, idx, new_spi, 0);
> @@ -663,10 +677,11 @@ static void spi_cleanup(struct spi_device *spi)
>  		spi->controller->cleanup(spi);
>  }
>  
> -static int __spi_add_device(struct spi_device *spi)
> +static int __spi_add_device(struct spi_device *spi, struct spi_device *parent)
>  {
>  	struct spi_controller *ctlr = spi->controller;
>  	struct device *dev = ctlr->dev.parent;
> +	struct spi_dev_check_info check_info;
>  	int status, idx;
>  	u8 cs;
>  
> @@ -710,7 +725,9 @@ static int __spi_add_device(struct spi_device *spi)
>  	 * chipselect **BEFORE** we call setup(), else we'll trash
>  	 * its configuration.
>  	 */
> -	status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);
> +	check_info.new_spi = spi;
> +	check_info.parent = parent;
> +	status = bus_for_each_dev(&spi_bus_type, NULL, &check_info, spi_dev_check);
>  	if (status)
>  		return status;
>  
> @@ -772,7 +789,7 @@ int spi_add_device(struct spi_device *spi)
>  	spi_dev_set_name(spi);
>  
>  	mutex_lock(&ctlr->add_lock);
> -	status = __spi_add_device(spi);
> +	status = __spi_add_device(spi, NULL);
>  	mutex_unlock(&ctlr->add_lock);
>  	return status;
>  }
> @@ -2580,8 +2597,8 @@ struct spi_device *spi_new_ancillary_device(struct spi_device *spi,
>  
>  	WARN_ON(!mutex_is_locked(&ctlr->add_lock));
>  
> -	/* Register the new device */
> -	rc = __spi_add_device(ancillary);
> +	/* Register the new device, passing the parent to skip CS conflict check */
> +	rc = __spi_add_device(ancillary, spi);
>  	if (rc) {
>  		dev_err(&spi->dev, "failed to register ancillary device\n");
>  		goto err_out;


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 5/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC
  2026-02-13 14:47 ` [PATCH v3 5/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
  2026-02-13 16:43   ` Nuno Sá
@ 2026-02-15 18:42   ` Jonathan Cameron
  1 sibling, 0 replies; 15+ messages in thread
From: Jonathan Cameron @ 2026-02-15 18:42 UTC (permalink / raw)
  To: Antoniu Miclaus
  Cc: Lars-Peter Clausen, Michael Hennerich, David Lechner,
	Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Olivier Moysan, Mark Brown, linux-iio, devicetree,
	linux-kernel, linux-spi

On Fri, 13 Feb 2026 16:47:37 +0200
Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:

> Add support for the AD4880, a dual-channel 20-bit 40MSPS SAR ADC with
> integrated fully differential amplifiers (FDA).
> 
> The AD4880 has two independent ADC channels, each with its own SPI
> configuration interface. The driver uses spi_new_ancillary_device() to
> create an additional SPI device for the second channel, allowing both
> channels to share the same SPI bus with different chip selects.
> 
> Reviewed-by: David Lechner <dlechner@baylibre.com>
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>

One small thing inline.

> @@ -617,13 +731,34 @@ static int ad4080_probe(struct spi_device *spi)
>  		return dev_err_probe(dev, ret,
>  				     "failed to get and enable supplies\n");
>  
> -	st->regmap = devm_regmap_init_spi(spi, &ad4080_regmap_config);
> -	if (IS_ERR(st->regmap))
> -		return PTR_ERR(st->regmap);
> +	/* Setup primary SPI device (channel 0) */
> +	st->spi[0] = spi;
> +	st->regmap[0] = devm_regmap_init_spi(spi, &ad4080_regmap_config);
> +	if (IS_ERR(st->regmap[0]))
> +		return PTR_ERR(st->regmap[0]);
>  
> -	st->info = spi_get_device_match_data(spi);
> -	if (!st->info)
> -		return -ENODEV;
> +	/* Setup ancillary SPI devices for additional channels */
> +	if (st->info->num_channels > 1) {
> +		u32 reg[AD4080_MAX_CHANNELS];
> +
> +		ret = device_property_read_u32_array(dev, "reg", reg,
> +						     st->info->num_channels);
Can we just use

		spi_get_chipselect(spi, 1) ?

I think the generic firmware parser will have already parsed the DT and filled
that in by this point.

> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "missing reg entries for multi-channel device\n");
> +
> +		for (int i = 1; i < st->info->num_channels; i++) {
> +			st->spi[i] = devm_spi_new_ancillary_device(spi, reg[i]);
> +			if (IS_ERR(st->spi[i]))
> +				return dev_err_probe(dev, PTR_ERR(st->spi[i]),
> +						     "failed to register ancillary device\n");
> +
> +			st->regmap[i] = devm_regmap_init_spi(st->spi[i],
> +							     &ad4080_regmap_config);
> +			if (IS_ERR(st->regmap[i]))
> +				return PTR_ERR(st->regmap[i]);
> +		}
> +	}



^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2026-02-15 18:42 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-13 14:47 [PATCH v3 0/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
2026-02-13 14:47 ` [PATCH v3 1/5] spi: allow ancillary devices to share parent's chip selects Antoniu Miclaus
2026-02-13 16:39   ` Nuno Sá
2026-02-15 18:36   ` Jonathan Cameron
2026-02-13 14:47 ` [PATCH v3 2/5] spi: add devm_spi_new_ancillary_device() Antoniu Miclaus
2026-02-13 16:40   ` Nuno Sá
2026-02-13 14:47 ` [PATCH v3 3/5] iio: backend: add devm_iio_backend_get_by_index() Antoniu Miclaus
2026-02-13 16:33   ` Nuno Sá
2026-02-13 14:47 ` [PATCH v3 4/5] dt-bindings: iio: adc: ad4080: add AD4880 support Antoniu Miclaus
2026-02-13 16:34   ` Rob Herring (Arm)
2026-02-14  8:54   ` Krzysztof Kozlowski
2026-02-13 14:47 ` [PATCH v3 5/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC Antoniu Miclaus
2026-02-13 16:43   ` Nuno Sá
2026-02-15 18:42   ` Jonathan Cameron
2026-02-15 18:34 ` [PATCH v3 0/5] " Jonathan Cameron

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