Linux Documentation
 help / color / mirror / Atom feed
* [PATCH 00/10] iio: frequency: ad9910: add IIO backend support
@ 2026-10-15 16:49 Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 01/10] iio: frequency: ad9910: powerdown fix Rodrigo Alencar via B4 Relay
                   ` (9 more replies)
  0 siblings, 10 replies; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

This series extends the AD9910 DDS driver with support for an IIO backend,
enabling data streaming through the parallel data port, and with control of
the DRCTL pin, which defines the slope direction of the digital ramp
generator (DRG).

The parallel data port allows modulation of the DDS core parameters
(amplitude, phase, frequency or polar) at up to the PDCLK rate, which is
SYSCLK / 4. Such data rates require an FPGA IP core as the data source, so
an AXI backend driver is added for the AD9910 HDL reference design. Besides
streaming data from memory via DMA, the IP core also drives the PROFILE[2:0]
and DRCTL pins. The latter is exposed as a PWM output, so that the frontend
driver can consume it through the PWM framework without depending on the
backend being present.

The backend is optional: the AD9910 remains functional through the SPI
control interface alone, hence the new devm_iio_backend_get_optional()
helper.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Rodrigo Alencar (10):
      iio: frequency: ad9910: powerdown fix
      iio: backend: add devm_iio_backend_get_optional()
      dt-bindings: iio: frequency: add adi,ad9910-axi-backend
      dt-bindings: iio: frequency: ad9910: add io-backends property
      iio: frequency: ad9910: move channel indexes into a shared header
      iio: frequency: ad9910: add axi backend driver
      iio: frequency: ad9910: support drctl control
      iio: frequency: ad9910: support iio backend
      iio: ABI: add integration_time for output channels
      docs: iio: ad9910: document DRCTL pin control

 Documentation/ABI/testing/sysfs-bus-iio            |  17 +
 .../iio/frequency/adi,ad9910-axi-backend.yaml      |  74 ++++
 .../bindings/iio/frequency/adi,ad9910.yaml         |  38 ++
 Documentation/iio/ad9910.rst                       |  90 ++++-
 MAINTAINERS                                        |   3 +
 drivers/iio/frequency/Kconfig                      |  18 +
 drivers/iio/frequency/Makefile                     |   1 +
 drivers/iio/frequency/ad9910-axi-backend.c         | 396 ++++++++++++++++++++
 drivers/iio/frequency/ad9910.c                     | 402 ++++++++++++++++++---
 drivers/iio/frequency/ad9910.h                     |  44 +++
 drivers/iio/industrialio-backend.c                 |  34 +-
 include/linux/iio/backend.h                        |   2 +
 12 files changed, 1052 insertions(+), 67 deletions(-)
---
base-commit: a22dc7ebb21a6bd4c8b63bc2c1dd46a24945aae7
change-id: 20260930-ad9910-backend-support-17a47d174430

Best regards,
--  
Rodrigo Alencar <rodrigo.alencar@analog.com>



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

* [PATCH 01/10] iio: frequency: ad9910: powerdown fix
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 02/10] iio: backend: add devm_iio_backend_get_optional() Rodrigo Alencar via B4 Relay
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Enable the software powerdown on all the applicable CFR1 register bits at
once with an IO update.

It turns out that DIGITAL and REFCLK_INPUT powerdown do need an IO update
when enabling the powerdown, but that is not the case when powering it up
again. It is an asymmetric behavior, which was observed while profiling
power consumption in the lab.

Fixes: 22452fe6cd08 ("iio: frequency: ad9910: initial driver implementation")
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/frequency/ad9910.c | 26 ++++++++------------------
 1 file changed, 8 insertions(+), 18 deletions(-)

diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 04a4e184b873..8bc24d0b7442 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -621,26 +621,16 @@ static int ad9910_sw_powerdown_set(struct ad9910_state *st, bool enable)
 	if (ad9910_sw_powerdown_get(st) == enable)
 		return 0;
 
-	/*
-	 * When powering down, the DAC and AUX_DAC (SW1) must be powered down
-	 * first, as they require an I/O update to take effect. The opposite is
-	 * true when powering up, the DAC and AUX_DAC must be powered up last,
-	 * i.e., after the DIGITAL and REFCLK_INPUT (SW0) power up.
-	 */
-	if (enable) {
-		ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
-					  AD9910_CFR1_SW1_POWER_DOWN_MSK,
-					  AD9910_CFR1_SW1_POWER_DOWN_MSK,
-					  true);
-		if (ret)
-			return ret;
-
+	if (enable)
 		return ad9910_reg32_update(st, AD9910_REG_CFR1,
-					   AD9910_CFR1_SW0_POWER_DOWN_MSK,
-					   AD9910_CFR1_SW0_POWER_DOWN_MSK,
-					   false);
-	}
+					   AD9910_CFR1_SW_POWER_DOWN_MSK,
+					   AD9910_CFR1_SW_POWER_DOWN_MSK,
+					   true);
 
+	/*
+	 * When powering up, the DIGITAL and REFCLK_INPUT (SW0) must be powered
+	 * up first, as they do not require an I/O update to take effect.
+	 */
 	ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
 				  AD9910_CFR1_SW0_POWER_DOWN_MSK, 0, false);
 	if (ret)

-- 
2.43.0



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

* [PATCH 02/10] iio: backend: add devm_iio_backend_get_optional()
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 01/10] iio: frequency: ad9910: powerdown fix Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  2026-10-15 18:41   ` Andy Shevchenko
  2026-10-15 16:49 ` [PATCH 03/10] dt-bindings: iio: frequency: add adi,ad9910-axi-backend Rodrigo Alencar via B4 Relay
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Frontend devices might not need a backend driver to rely on. An IIO device
that supports backends can behave differently when they are not available
and that should be acceptable. Such parts may provide basic functionality
solely using the control interface (SPI for instance), being able to have
extended features when a backend is present.

Add devm_iio_backend_get_optional(), which returns NULL when the backend
is not described in firmware, i.e., when the io-backends property is not
present or, for a named lookup, when the name is not listed in the
io-backend-names property. Other errors, such as probe deferral, are
still returned.

To make that possible, a named lookup that does not match any entry of
io-backend-names now fails with -ENOENT, the same error returned when the
io-backends reference is missing. Also, the missing reference is no longer
logged as an error, leaving that to the caller.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/industrialio-backend.c | 34 +++++++++++++++++++++++++++++++++-
 include/linux/iio/backend.h        |  2 ++
 2 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c
index fbf5c39d7655..7af41ba50a65 100644
--- a/drivers/iio/industrialio-backend.c
+++ b/drivers/iio/industrialio-backend.c
@@ -1006,9 +1006,14 @@ static struct iio_backend *__devm_iio_backend_fwnode_get_by_index(struct device
 
 	struct fwnode_handle *fwnode_back __free(fwnode_handle) =
 		fwnode_find_reference(fwnode, "io-backends", index);
-	if (IS_ERR(fwnode_back))
+	if (IS_ERR(fwnode_back)) {
+		/* not described in firmware, let the caller decide what to do */
+		if (PTR_ERR(fwnode_back) == -ENOENT)
+			return ERR_CAST(fwnode_back);
+
 		return dev_err_cast_probe(dev, fwnode_back,
 					  "Cannot get Firmware reference\n");
+	}
 
 	guard(mutex)(&iio_back_lock);
 	list_for_each_entry(back, &iio_back_list, entry) {
@@ -1035,6 +1040,8 @@ static struct iio_backend *__devm_iio_backend_fwnode_get(struct device *dev, con
 
 	if (name) {
 		ret = device_property_match_string(dev, "io-backend-names", name);
+		if (ret == -EINVAL || ret == -ENODATA)
+			return ERR_PTR(-ENOENT);
 		if (ret < 0)
 			return ERR_PTR(ret);
 		index = ret;
@@ -1061,6 +1068,31 @@ 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_optional - Optional device managed backend device get
+ * @dev: Consumer device for the backend
+ * @name: Backend name
+ *
+ * Gets the backend associated with @dev. A backend not described in firmware,
+ * i.e., the io-backends property is not present or @name is not listed in
+ * the io-backend-names property, is not treated as an error.
+ *
+ * RETURNS:
+ * A backend pointer, NULL if the backend is not described in firmware,
+ * negative error pointer otherwise.
+ */
+struct iio_backend *devm_iio_backend_get_optional(struct device *dev,
+						  const char *name)
+{
+	struct iio_backend *back = devm_iio_backend_get(dev, name);
+
+	if (IS_ERR(back) && PTR_ERR(back) == -ENOENT)
+		return NULL;
+
+	return back;
+}
+EXPORT_SYMBOL_NS_GPL(devm_iio_backend_get_optional, "IIO_BACKEND");
+
 /**
  * devm_iio_backend_get_by_index - Device managed backend device get by index
  * @dev: Consumer device for the backend
diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h
index 9c16b695a602..93d5f2073cc3 100644
--- a/include/linux/iio/backend.h
+++ b/include/linux/iio/backend.h
@@ -267,6 +267,8 @@ int iio_backend_extend_chan_spec(struct iio_backend *back,
 bool iio_backend_has_caps(struct iio_backend *back, u32 caps);
 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_optional(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,

-- 
2.43.0



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

* [PATCH 03/10] dt-bindings: iio: frequency: add adi,ad9910-axi-backend
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 01/10] iio: frequency: ad9910: powerdown fix Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 02/10] iio: backend: add devm_iio_backend_get_optional() Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  2026-10-15 18:59   ` Conor Dooley
  2026-10-15 16:49 ` [PATCH 04/10] dt-bindings: iio: frequency: ad9910: add io-backends property Rodrigo Alencar via B4 Relay
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add bindings for the AD9910 AXI backend IP core. The FPGA IP core
interfaces with the data and control pins of the AD9910 DDS, streaming
data from memory via DMA into the 18-bit parallel data port (F[1:0] and
D[15:0] pins), which is clocked by the PDCLK output of the device. It
also drives the PROFILE[2:0] pins and the DRCTL pin, which controls the
slope direction of the digital ramp generator (DRG). The DRCTL pin is
driven by a PWM generator, so the IP core is described both as an IIO
backend and as a PWM provider.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 .../iio/frequency/adi,ad9910-axi-backend.yaml      | 74 ++++++++++++++++++++++
 MAINTAINERS                                        |  1 +
 2 files changed, 75 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml
new file mode 100644
index 000000000000..20b884e8eb10
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/frequency/adi,ad9910-axi-backend.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD9910 AXI Backend IP core
+
+maintainers:
+  - Rodrigo Alencar <rodrigo.alencar@analog.com>
+
+description:
+  FPGA IP core that interfaces with the data and control pins of the AD9910
+  Direct Digital Synthesizer. The SPI bus is used as the control interface
+  of the AD9910 device, while this IP core streams data from memory via DMA
+  into the 18-bit parallel data port (F[1:0] and D[15:0] pins), which is
+  clocked by the PDCLK output of the AD9910. The IP core also drives the
+  PROFILE[2:0] pins for profile selection and provides a PWM generator that
+  drives the DRCTL pin, controlling the digital ramp generator (DRG) slope
+  direction.
+
+  https://www.analog.com/en/products/ad9910.html
+
+properties:
+  compatible:
+    const: adi,ad9910-axi-backend
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+    description: AXI bus clock (s_axi_aclk).
+
+  dmas:
+    maxItems: 1
+
+  dma-names:
+    items:
+      - const: tx
+
+  '#io-backend-cells':
+    const: 0
+
+  '#pwm-cells':
+    const: 3
+    description:
+      The first cell must be 0, as only the DRCTL PWM output is available.
+      The second cell is the PWM period in nanoseconds and the third cell
+      holds the PWM flags.
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - dmas
+  - dma-names
+  - '#io-backend-cells'
+  - '#pwm-cells'
+
+additionalProperties: false
+
+examples:
+  - |
+    ad9910_backend: dds-backend@44a00000 {
+        compatible = "adi,ad9910-axi-backend";
+        reg = <0x44a00000 0x10000>;
+        clocks = <&clkc 15>;
+        dmas = <&tx_dma 0>;
+        dma-names = "tx";
+        #io-backend-cells = <0>;
+        #pwm-cells = <3>;
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 3a2dfd1a4fd8..93ad6a829cb3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1731,6 +1731,7 @@ S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/ABI/testing/debugfs-iio-ad9910
 F:	Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
+F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml
 F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
 F:	Documentation/iio/ad9910.rst
 F:	drivers/iio/frequency/ad9910.c

-- 
2.43.0



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

* [PATCH 04/10] dt-bindings: iio: frequency: ad9910: add io-backends property
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
                   ` (2 preceding siblings ...)
  2026-10-15 16:49 ` [PATCH 03/10] dt-bindings: iio: frequency: add adi,ad9910-axi-backend Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  2026-10-15 19:00   ` Conor Dooley
  2026-10-15 16:49 ` [PATCH 05/10] iio: frequency: ad9910: move channel indexes into a shared header Rodrigo Alencar via B4 Relay
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

The AD9910 parallel data port allows high-speed modulation of the DDS
core parameters, which requires a data source such as an FPGA IP core
connected to the F[1:0] and D[15:0] pins. Add the io-backends property,
so that an IIO backend providing such interface can be referenced.

The property is optional as the parallel port is. The device remains
functional through the SPI control interface only, as other modes can
be used to drive the DDS core.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 .../bindings/iio/frequency/adi,ad9910.yaml         | 38 ++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
index 4eebbd388b27..02fd49db93ff 100644
--- a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
+++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
@@ -120,6 +120,13 @@ properties:
     items:
       - const: drctl
 
+  io-backends:
+    maxItems: 1
+    description:
+      IIO backend that interfaces with the parallel data port, enabling data
+      streaming into the device. The backend may also drive the PROFILE[2:0]
+      and DRCTL pins, the latter being exposed as a PWM provider.
+
   adi,pll-enable:
     type: boolean
     description:
@@ -206,4 +213,35 @@ examples:
             adi,refclk-out-drive-strength = "disabled";
         };
     };
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        dds@0 {
+            compatible = "adi,ad9910";
+            reg = <0>;
+            spi-max-frequency = <1000000>;
+
+            #clock-cells = <1>;
+            clock-output-names = "sync_clk", "pdclk", "sync_out";
+
+            clocks = <&ad9910_refclk>;
+            clock-names = "ref_clk";
+
+            dvdd-io33-supply = <&vdd_io33>;
+            avdd33-supply = <&vdd_a33>;
+            dvdd18-supply = <&vdd_d18>;
+            avdd18-supply = <&vdd_a18>;
+
+            reset-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+            io-reset-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
+            powerdown-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
+            update-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
+
+            io-backends = <&ad9910_backend>;
+            pwms = <&ad9910_backend 0 1000000 0>;
+            pwm-names = "drctl";
+        };
+    };
 ...

-- 
2.43.0



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

* [PATCH 05/10] iio: frequency: ad9910: move channel indexes into a shared header
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
                   ` (3 preceding siblings ...)
  2026-10-15 16:49 ` [PATCH 04/10] dt-bindings: iio: frequency: ad9910: add io-backends property Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 06/10] iio: frequency: ad9910: add axi backend driver Rodrigo Alencar via B4 Relay
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

The channel indexes are used as channel identifiers in IIO backend
operations, such as enabling a parallel data port destination or
selecting a profile. Move them, along with the data destination
definitions, into a shared header, so that the frontend and backend
drivers agree on those identifiers.

No functional change.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 MAINTAINERS                    |  1 +
 drivers/iio/frequency/ad9910.c | 35 ++-------------------------------
 drivers/iio/frequency/ad9910.h | 44 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 47 insertions(+), 33 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 93ad6a829cb3..a9cb4396b3e1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1735,6 +1735,7 @@ F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml
 F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
 F:	Documentation/iio/ad9910.rst
 F:	drivers/iio/frequency/ad9910.c
+F:	drivers/iio/frequency/ad9910.h
 
 ANALOG DEVICES INC MAX22007 DRIVER
 M:	Janani Sunil <janani.sunil@analog.com>
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 8bc24d0b7442..dc13986dff3d 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -33,6 +33,8 @@
 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
 
+#include "ad9910.h"
+
 /* Register addresses */
 #define AD9910_REG_CFR1			0x00
 #define AD9910_REG_CFR2			0x01
@@ -194,11 +196,6 @@
 #define AD9910_RAM_ENABLED(st)		\
 	FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, (st)->reg[AD9910_REG_CFR1].val32)
 
-#define AD9910_DEST_FREQUENCY		0
-#define AD9910_DEST_PHASE		1
-#define AD9910_DEST_AMPLITUDE		2
-#define AD9910_DEST_POLAR		3
-
 /* PLL constants */
 #define AD9910_PLL_MIN_N		12
 #define AD9910_PLL_MAX_N		127
@@ -302,34 +299,6 @@ struct ad9910_ram_fw {
 	__be32 words[] __counted_by_be(wcount);
 } __packed;
 
-enum {
-	AD9910_CHAN_IDX_PHY,
-	AD9910_CHAN_IDX_PROFILE_0,
-	AD9910_CHAN_IDX_PROFILE_1,
-	AD9910_CHAN_IDX_PROFILE_2,
-	AD9910_CHAN_IDX_PROFILE_3,
-	AD9910_CHAN_IDX_PROFILE_4,
-	AD9910_CHAN_IDX_PROFILE_5,
-	AD9910_CHAN_IDX_PROFILE_6,
-	AD9910_CHAN_IDX_PROFILE_7,
-	AD9910_CHAN_IDX_PARALLEL_AMP,
-	AD9910_CHAN_IDX_PARALLEL_PHASE,
-	AD9910_CHAN_IDX_PARALLEL_FREQ,
-	AD9910_CHAN_IDX_PARALLEL_POLAR_AMP,
-	AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE,
-	AD9910_CHAN_IDX_DRG_FREQ,
-	AD9910_CHAN_IDX_DRG_PHASE,
-	AD9910_CHAN_IDX_DRG_AMP,
-	AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP,
-	AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN,
-	AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP,
-	AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN,
-	AD9910_CHAN_IDX_DRG_AMP_RAMP_UP,
-	AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN,
-	AD9910_CHAN_IDX_RAM,
-	AD9910_CHAN_IDX_OSK,
-};
-
 enum {
 	AD9910_POWERDOWN,
 	AD9910_DWELL_EN,
diff --git a/drivers/iio/frequency/ad9910.h b/drivers/iio/frequency/ad9910.h
new file mode 100644
index 000000000000..6452a8678ed9
--- /dev/null
+++ b/drivers/iio/frequency/ad9910.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AD9910 SPI DDS (Direct Digital Synthesizer) driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#ifndef IIO_FREQUENCY_AD9910_H_
+#define IIO_FREQUENCY_AD9910_H_
+
+#define AD9910_DEST_FREQUENCY		0
+#define AD9910_DEST_PHASE		1
+#define AD9910_DEST_AMPLITUDE		2
+#define AD9910_DEST_POLAR		3
+
+enum ad9910_channel_idx {
+	AD9910_CHAN_IDX_PHY,
+	AD9910_CHAN_IDX_PROFILE_0,
+	AD9910_CHAN_IDX_PROFILE_1,
+	AD9910_CHAN_IDX_PROFILE_2,
+	AD9910_CHAN_IDX_PROFILE_3,
+	AD9910_CHAN_IDX_PROFILE_4,
+	AD9910_CHAN_IDX_PROFILE_5,
+	AD9910_CHAN_IDX_PROFILE_6,
+	AD9910_CHAN_IDX_PROFILE_7,
+	AD9910_CHAN_IDX_PARALLEL_AMP,
+	AD9910_CHAN_IDX_PARALLEL_PHASE,
+	AD9910_CHAN_IDX_PARALLEL_FREQ,
+	AD9910_CHAN_IDX_PARALLEL_POLAR_AMP,
+	AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE,
+	AD9910_CHAN_IDX_DRG_FREQ,
+	AD9910_CHAN_IDX_DRG_PHASE,
+	AD9910_CHAN_IDX_DRG_AMP,
+	AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP,
+	AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN,
+	AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP,
+	AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN,
+	AD9910_CHAN_IDX_DRG_AMP_RAMP_UP,
+	AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN,
+	AD9910_CHAN_IDX_RAM,
+	AD9910_CHAN_IDX_OSK,
+};
+
+#endif /* IIO_FREQUENCY_AD9910_H_ */

-- 
2.43.0



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

* [PATCH 06/10] iio: frequency: ad9910: add axi backend driver
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
                   ` (4 preceding siblings ...)
  2026-10-15 16:49 ` [PATCH 05/10] iio: frequency: ad9910: move channel indexes into a shared header Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 07/10] iio: frequency: ad9910: support drctl control Rodrigo Alencar via B4 Relay
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add the driver for the AD9910 AXI backend IP core, which interfaces with
the data and control pins of the AD9910 DDS. It registers as an IIO
backend that provides:

- a DMA output buffer that streams data into the parallel data port;
- the parallel data destination (F[1:0] pins) and data rate, derived
  from the PDCLK frequency;
- the PROFILE[2:0] pins control, through profile channel enables.

It also registers a PWM chip with a single output driving the DRCTL
pin, which the AD9910 driver uses to control the digital ramp generator
direction. PWM period and duty cycle are programmed in PDCLK cycles.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 MAINTAINERS                                |   1 +
 drivers/iio/frequency/Kconfig              |  17 ++
 drivers/iio/frequency/Makefile             |   1 +
 drivers/iio/frequency/ad9910-axi-backend.c | 396 +++++++++++++++++++++++++++++
 4 files changed, 415 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index a9cb4396b3e1..c3cda855dabb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1734,6 +1734,7 @@ F:	Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
 F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml
 F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
 F:	Documentation/iio/ad9910.rst
+F:	drivers/iio/frequency/ad9910-axi-backend.c
 F:	drivers/iio/frequency/ad9910.c
 F:	drivers/iio/frequency/ad9910.h
 
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index 994d199080eb..f7f5c3cce005 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -42,6 +42,23 @@ config AD9910
 	  To compile this driver as a module, choose M here: the
 	  module will be called ad9910.
 
+config AD9910_AXI_BACKEND
+	tristate "Analog Devices AD9910 AXI Backend driver"
+	depends on AD9910
+	depends on HAS_IOMEM && PWM
+	select IIO_BACKEND
+	select IIO_BUFFER
+	select IIO_BUFFER_DMAENGINE
+	select REGMAP_MMIO
+	help
+	  Say yes here to build support for the AXI Backend Driver for the
+	  Analog Devices AD9910 DDS. The IP core is used to drive digital ramp
+	  control pins and interface with the parallel data port of the AD9910
+	  device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad9910-axi-backend.
+
 endmenu
 
 #
diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
index e3b1d36ed620..bfac9e205e81 100644
--- a/drivers/iio/frequency/Makefile
+++ b/drivers/iio/frequency/Makefile
@@ -6,6 +6,7 @@
 # When adding new entries keep the list in alphabetical order
 obj-$(CONFIG_AD9523) += ad9523.o
 obj-$(CONFIG_AD9910) += ad9910.o
+obj-$(CONFIG_AD9910_AXI_BACKEND) += ad9910-axi-backend.o
 obj-$(CONFIG_ADF41513) += adf41513.o
 obj-$(CONFIG_ADF4350) += adf4350.o
 obj-$(CONFIG_ADF4371) += adf4371.o
diff --git a/drivers/iio/frequency/ad9910-axi-backend.c b/drivers/iio/frequency/ad9910-axi-backend.c
new file mode 100644
index 000000000000..f94cb54af517
--- /dev/null
+++ b/drivers/iio/frequency/ad9910-axi-backend.c
@@ -0,0 +1,396 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD9910 AXI Backend driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <linux/iio/backend.h>
+#include <linux/iio/buffer-dmaengine.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+
+#include "ad9910.h"
+
+/* Register addresses */
+#define AD9910_AXI_REG_RESET		0x040
+#define AD9910_AXI_REG_DRG		0x084
+#define AD9910_AXI_REG_PROFILE		0x088
+#define AD9910_AXI_REG_DRCTL_PERIOD	0x08C
+#define AD9910_AXI_REG_DRCTL_WIDTH	0x090
+#define AD9910_AXI_REG_RAMP_DELAY_BST	0x094
+#define AD9910_AXI_REG_BURST_COUNT	0x098
+#define AD9910_AXI_REG_BURST_DELAY	0x09C
+#define AD9910_AXI_REG_RAMP_CFG		0x0A0
+
+#define AD9910_AXI_REG_UPDATE_CTRL	0x104
+#define AD9910_AXI_REG_PAR_RATE		0x108
+#define AD9910_AXI_REG_F_CFG		0x10C
+
+#define AD9910_AXI_REG_RESET_CORE_MSK		BIT(0)
+
+#define AD9910_AXI_REG_DRG_DRCTL_MSK		BIT(2)
+#define AD9910_AXI_REG_DRG_TOGGLE_EN_MSK	BIT(3)
+
+#define AD9910_AXI_REG_RAMP_CFG_ONE_SHOT_MSK	GENMASK(1, 0)
+#define AD9910_AXI_REG_BURST_COUNT_MSK		GENMASK(19, 0)
+
+#define AD9910_AXI_REG_UPDATE_RATE_MSK		BIT(0)
+#define AD9910_AXI_REG_UPDATE_PAR_IF_EN_MSK	BIT(1)
+#define AD9910_AXI_REG_UPDATE_EXT_TRIG_MSK	BIT(2)
+
+#define AD9910_AXI_REG_PROFILE_MSK		GENMASK(2, 0)
+#define AD9910_AXI_REG_IO_UPDATE_MSK		BIT(0)
+#define AD9910_AXI_REG_F_CFG_MSK		GENMASK(1, 0)
+
+#define AD9910_AXI_PD_CLK_MAX_FREQ_HZ		(250ULL * HZ_PER_MHZ)
+#define AD9910_AXI_PAR_RATE_MAX			U16_MAX
+#define AD9910_AXI_ONE_SHOT_BURST		1
+
+struct ad9910_axi_state {
+	struct device *dev;
+	struct regmap *regmap;
+	/*
+	 * lock to protect multiple accesses to the device registers and global
+	 * data/variables.
+	 */
+	struct mutex lock;
+	u64 pd_clk_freq_uhz;
+	enum pwm_polarity pwm_polarity;
+};
+
+static int ad9910_axi_chan_enable(struct iio_backend *back, unsigned int chan)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	switch (chan) {
+	/*
+	 * Profiles are mutually exclusive, as the PROFILE[2:0] pins always
+	 * select one of them. Enabling a profile channel selects it, which
+	 * implicitly disables the previously selected one, hence there is
+	 * nothing to do when disabling a profile channel.
+	 */
+	case AD9910_CHAN_IDX_PROFILE_0 ... AD9910_CHAN_IDX_PROFILE_7:
+		chan -= AD9910_CHAN_IDX_PROFILE_0;
+		return regmap_update_bits(st->regmap, AD9910_AXI_REG_PROFILE,
+					  AD9910_AXI_REG_PROFILE_MSK,
+					  FIELD_PREP(AD9910_AXI_REG_PROFILE_MSK, chan));
+	case AD9910_CHAN_IDX_PARALLEL_AMP ... AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+		chan = min(chan - AD9910_CHAN_IDX_PARALLEL_AMP, AD9910_DEST_POLAR);
+		ret = regmap_update_bits(st->regmap, AD9910_AXI_REG_F_CFG,
+					 AD9910_AXI_REG_F_CFG_MSK, chan);
+		if (ret)
+			return ret;
+
+		return regmap_set_bits(st->regmap, AD9910_AXI_REG_UPDATE_CTRL,
+				       AD9910_AXI_REG_UPDATE_PAR_IF_EN_MSK);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad9910_axi_chan_disable(struct iio_backend *back, unsigned int chan)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+
+	switch (chan) {
+	case AD9910_CHAN_IDX_PARALLEL_AMP ... AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+		return regmap_clear_bits(st->regmap, AD9910_AXI_REG_UPDATE_CTRL,
+					 AD9910_AXI_REG_UPDATE_PAR_IF_EN_MSK);
+	default:
+		return -EINVAL;
+	}
+}
+
+static struct iio_buffer *ad9910_axi_request_buffer(struct iio_backend *back,
+						    struct iio_dev *indio_dev)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+	const char *dma_name;
+
+	if (device_property_read_string(st->dev, "dma-names", &dma_name))
+		dma_name = "tx";
+
+	return iio_dmaengine_buffer_setup_ext(st->dev, indio_dev, dma_name,
+					      IIO_BUFFER_DIRECTION_OUT);
+}
+
+static void ad9910_axi_free_buffer(struct iio_backend *back,
+				   struct iio_buffer *buffer)
+{
+	iio_dmaengine_buffer_teardown(buffer);
+}
+
+static int ad9910_axi_set_sample_rate(struct iio_backend *back,
+				      unsigned int chan, u64 sample_rate)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+	u64 div;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	switch (chan) {
+	case AD9910_CHAN_IDX_PHY:
+		if (!sample_rate || sample_rate > 4 * AD9910_AXI_PD_CLK_MAX_FREQ_HZ)
+			return -EINVAL;
+
+		st->pd_clk_freq_uhz = sample_rate * MICROHZ_PER_HZ >> 2;
+		return 0;
+	case AD9910_CHAN_IDX_PARALLEL_AMP ... AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+		if (!sample_rate || sample_rate > AD9910_AXI_PD_CLK_MAX_FREQ_HZ)
+			return -EINVAL;
+
+		sample_rate *= MICROHZ_PER_HZ;
+		div = DIV64_U64_ROUND_CLOSEST(st->pd_clk_freq_uhz, sample_rate);
+		div = clamp(div, 1U, AD9910_AXI_PAR_RATE_MAX);
+		ret = regmap_write(st->regmap, AD9910_AXI_REG_PAR_RATE, div - 1);
+		if (ret)
+			return ret;
+
+		return regmap_set_bits(st->regmap, AD9910_AXI_REG_UPDATE_CTRL,
+				       AD9910_AXI_REG_UPDATE_RATE_MSK);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad9910_axi_read_raw(struct iio_backend *back,
+			       struct iio_chan_spec const *chan, int *val,
+			       int *val2, long mask)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+	u32 div;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		switch (chan->address) {
+		case AD9910_CHAN_IDX_PARALLEL_AMP ... AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE: {
+			guard(mutex)(&st->lock);
+
+			ret = regmap_read(st->regmap, AD9910_AXI_REG_PAR_RATE,
+					  &div);
+			if (ret)
+				return ret;
+
+			iio_val_s64_decompose(div_s64(st->pd_clk_freq_uhz, div + 1),
+					      val, val2);
+			return IIO_VAL_DECIMAL64_MICRO;
+		}
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad9910_axi_reg_access(struct iio_backend *back, unsigned int reg,
+				 unsigned int writeval, unsigned int *readval)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+
+	if (readval)
+		return regmap_read(st->regmap, reg, readval);
+
+	return regmap_write(st->regmap, reg, writeval);
+}
+
+static const struct iio_backend_ops ad9910_axi_iio_back_ops = {
+	.chan_enable = ad9910_axi_chan_enable,
+	.chan_disable = ad9910_axi_chan_disable,
+	.request_buffer = ad9910_axi_request_buffer,
+	.free_buffer = ad9910_axi_free_buffer,
+	.set_sample_rate = ad9910_axi_set_sample_rate,
+	.read_raw = ad9910_axi_read_raw,
+	.debugfs_reg_access = iio_backend_debugfs_ptr(ad9910_axi_reg_access),
+};
+
+static const struct iio_backend_info ad9910_axi_back_info = {
+	.name = "ad9910-axi",
+	.ops = &ad9910_axi_iio_back_ops,
+};
+
+static const struct regmap_config ad9910_axi_regmap_config = {
+	.val_bits = 32,
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.max_register = 0x0800,
+};
+
+static int ad9910_axi_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+				const struct pwm_state *state)
+{
+	struct ad9910_axi_state *st = pwmchip_get_drvdata(chip);
+	u64 period, duty;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	period = mul_u64_u64_div_u64(state->period, st->pd_clk_freq_uhz,
+				     1ULL * NANO * MICROHZ_PER_HZ);
+	period = min(period, U32_MAX);
+	duty = mul_u64_u64_div_u64(state->duty_cycle, st->pd_clk_freq_uhz,
+				   1ULL * NANO * MICROHZ_PER_HZ);
+	duty = min(duty, period);
+
+	if (state->polarity == PWM_POLARITY_INVERSED)
+		duty = period - duty;
+
+	ret = regmap_write(st->regmap, AD9910_AXI_REG_DRCTL_PERIOD, period);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD9910_AXI_REG_DRCTL_WIDTH, duty);
+	if (ret)
+		return ret;
+
+	st->pwm_polarity = state->polarity;
+
+	if (state->enabled)
+		return regmap_set_bits(st->regmap, AD9910_AXI_REG_DRG,
+				       AD9910_AXI_REG_DRG_TOGGLE_EN_MSK);
+
+	return regmap_clear_bits(st->regmap, AD9910_AXI_REG_DRG,
+				 AD9910_AXI_REG_DRG_TOGGLE_EN_MSK);
+}
+
+static int ad9910_axi_pwm_get_state(struct pwm_chip *chip,
+				    struct pwm_device *pwm,
+				    struct pwm_state *state)
+{
+	struct ad9910_axi_state *st = pwmchip_get_drvdata(chip);
+	u32 period, width, drg;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	ret = regmap_read(st->regmap, AD9910_AXI_REG_DRCTL_PERIOD, &period);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(st->regmap, AD9910_AXI_REG_DRCTL_WIDTH, &width);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(st->regmap, AD9910_AXI_REG_DRG, &drg);
+	if (ret)
+		return ret;
+
+	state->period = mul_u64_u64_div_u64_roundup((u64)period * NANO,
+						    MICROHZ_PER_HZ,
+						    st->pd_clk_freq_uhz);
+	if (st->pwm_polarity == PWM_POLARITY_INVERSED)
+		width = period - width;
+	state->duty_cycle = mul_u64_u64_div_u64_roundup((u64)width * NANO,
+							MICROHZ_PER_HZ,
+							st->pd_clk_freq_uhz);
+	state->polarity = st->pwm_polarity;
+	state->enabled = FIELD_GET(AD9910_AXI_REG_DRG_TOGGLE_EN_MSK, drg);
+
+	return 0;
+}
+
+static const struct pwm_ops ad9910_axi_pwm_ops = {
+	.apply = ad9910_axi_pwm_apply,
+	.get_state = ad9910_axi_pwm_get_state,
+};
+
+static int ad9910_axi_setup(struct ad9910_axi_state *st)
+{
+	int ret;
+
+	st->pd_clk_freq_uhz = AD9910_AXI_PD_CLK_MAX_FREQ_HZ * MICROHZ_PER_HZ;
+
+	ret = regmap_set_bits(st->regmap, AD9910_AXI_REG_RESET,
+			      AD9910_AXI_REG_RESET_CORE_MSK);
+	if (ret)
+		return ret;
+
+	return regmap_clear_bits(st->regmap, AD9910_AXI_REG_RESET,
+				 AD9910_AXI_REG_RESET_CORE_MSK);
+}
+
+static int ad9910_axi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ad9910_axi_state *st;
+	struct pwm_chip *pwm;
+	void __iomem *base;
+	struct clk *clk;
+	int ret;
+
+	pwm = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*st));
+	if (IS_ERR(pwm))
+		return PTR_ERR(pwm);
+
+	st = pwmchip_get_drvdata(pwm);
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	clk = devm_clk_get_enabled(dev, NULL);
+	if (IS_ERR(clk))
+		return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
+
+	st->dev = dev;
+	st->regmap = devm_regmap_init_mmio(dev, base,
+					   &ad9910_axi_regmap_config);
+	if (IS_ERR(st->regmap))
+		return dev_err_probe(dev, PTR_ERR(st->regmap),
+				     "failed to init register map\n");
+
+	ret = devm_mutex_init(dev, &st->lock);
+	if (ret)
+		return ret;
+
+	ret = ad9910_axi_setup(st);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to setup device\n");
+
+	pwm->ops = &ad9910_axi_pwm_ops;
+
+	ret = devm_pwmchip_add(dev, pwm);
+	if (ret)
+		return dev_err_probe(dev, ret, "Could not register PWM chip\n");
+
+	return devm_iio_backend_register(dev, &ad9910_axi_back_info, st);
+}
+
+static const struct of_device_id ad9910_axi_of_match[] = {
+	{ .compatible = "adi,ad9910-axi-backend" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ad9910_axi_of_match);
+
+static struct platform_driver ad9910_axi_driver = {
+	.driver = {
+		.name = "ad9910-axi-backend",
+		.of_match_table = ad9910_axi_of_match,
+	},
+	.probe = ad9910_axi_probe,
+};
+module_platform_driver(ad9910_axi_driver);
+
+MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD9910 AXI Backend driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
+MODULE_IMPORT_NS("IIO_BACKEND");

-- 
2.43.0



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

* [PATCH 07/10] iio: frequency: ad9910: support drctl control
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
                   ` (5 preceding siblings ...)
  2026-10-15 16:49 ` [PATCH 06/10] iio: frequency: ad9910: add axi backend driver Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 08/10] iio: frequency: ad9910: support iio backend Rodrigo Alencar via B4 Relay
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

The DRCTL pin controls the slope direction of the AD9910 digital ramp
generator (DRG). When dwell is enabled at a ramp limit, the DRG holds
that limit until the next DRCTL transition, so the sweep pattern is
defined by the waveform applied to that pin, which can be generated by
a PWM.

Get the optional "drctl" PWM and expose the integration_time attribute
in the DRG ramp up and ramp down channels, which sets the time interval
assigned to each ramp direction while the sweep parameter is accumulating.

The PWM is disabled when the resulting period is zero. The applied state
is read back, so that integration_time reports the intervals achieved
by the PWM provider. Writing to integration_time fails with -EOPNOTSUPP
when no DRCTL PWM is available.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/frequency/ad9910.c | 132 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 126 insertions(+), 6 deletions(-)

diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index dc13986dff3d..3d517879dee3 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -21,6 +21,7 @@
 #include <linux/math64.h>
 #include <linux/module.h>
 #include <linux/property.h>
+#include <linux/pwm.h>
 #include <linux/regulator/consumer.h>
 #include <linux/reset.h>
 #include <linux/seq_file.h>
@@ -324,12 +325,16 @@ union ad9910_reg {
 struct ad9910_state {
 	struct spi_device *spi;
 	struct clk *refclk;
+	struct pwm_device *drctl_pwm;
 	struct fw_upload *ram_fwu;
 
 	struct gpio_desc *gpio_pwdown;
 	struct gpio_desc *gpio_update;
 	struct gpio_descs *gpio_profile;
 
+	s64 ramp_up_time_ns;
+	s64 ramp_down_time_ns;
+
 	/* cached registers */
 	union ad9910_reg reg[AD9910_REG_NUM_CACHED];
 
@@ -609,6 +614,66 @@ static int ad9910_sw_powerdown_set(struct ad9910_state *st, bool enable)
 				   AD9910_CFR1_SW1_POWER_DOWN_MSK, 0, true);
 }
 
+static int ad9910_drctl_sync(struct ad9910_state *st)
+{
+	struct pwm_state state;
+	bool ndh, ndl;
+	int ret;
+
+	if (!st->drctl_pwm)
+		return 0;
+
+	ndh = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK,
+			st->reg[AD9910_REG_CFR2].val32);
+	ndl = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
+			st->reg[AD9910_REG_CFR2].val32);
+
+	pwm_init_state(st->drctl_pwm, &state);
+
+	if (!ndh && ndl) {
+		state.duty_cycle = st->ramp_down_time_ns / 2;
+		state.period = st->ramp_down_time_ns;
+		state.polarity = PWM_POLARITY_INVERSED;
+	} else if (ndh && !ndl) {
+		state.duty_cycle = st->ramp_up_time_ns / 2;
+		state.period = st->ramp_up_time_ns;
+		state.polarity = PWM_POLARITY_NORMAL;
+	} else if (!ndh && !ndl) {
+		state.duty_cycle = st->ramp_up_time_ns;
+		state.period = st->ramp_up_time_ns + st->ramp_down_time_ns;
+		state.polarity = PWM_POLARITY_NORMAL;
+	} else {
+		state.duty_cycle = 0;
+		state.period = st->ramp_up_time_ns + st->ramp_down_time_ns;
+		state.polarity = PWM_POLARITY_NORMAL;
+	}
+
+	if (!state.period) {
+		pwm_disable(st->drctl_pwm);
+		return 0;
+	}
+
+	state.enabled = true;
+	ret = pwm_apply_might_sleep(st->drctl_pwm, &state);
+	if (ret)
+		return ret;
+
+	ret = pwm_get_state_hw(st->drctl_pwm, &state);
+	if (ret)
+		return ret;
+
+	if (!ndh && ndl) {
+		st->ramp_down_time_ns = state.period;
+	} else if (ndh && !ndl) {
+		st->ramp_up_time_ns = state.period;
+	} else if (!ndh && !ndl) {
+		st->ramp_up_time_ns = state.duty_cycle;
+		st->ramp_down_time_ns = state.period - st->ramp_up_time_ns;
+	}
+
+	return 0;
+}
+
 static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
 				    uintptr_t private,
 				    const struct iio_chan_spec *chan,
@@ -675,6 +740,13 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
 			if (ret)
 				return ret;
 		}
+
+		/*
+		 * DRCTL waveform depends on the dwell configuration, but the
+		 * previous integration time values may not suit the new
+		 * waveform, which should not prevent dwell_en from changing.
+		 */
+		ad9910_drctl_sync(st);
 		break;
 	default:
 		return -EINVAL;
@@ -1054,7 +1126,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.channel = AD9910_CHANNEL_DRG_RAMP_UP,
 		.address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_INT_TIME),
 		.ext_info = ad9910_drg_ramp_ext_info,
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ],
 	},
@@ -1065,7 +1138,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
 		.address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_INT_TIME),
 		.ext_info = ad9910_drg_ramp_ext_info,
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ],
 	},
@@ -1076,7 +1150,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.channel = AD9910_CHANNEL_DRG_RAMP_UP,
 		.address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_INT_TIME),
 		.ext_info = ad9910_drg_ramp_ext_info,
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE],
 	},
@@ -1087,7 +1162,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
 		.address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_INT_TIME),
 		.ext_info = ad9910_drg_ramp_ext_info,
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE],
 	},
@@ -1098,7 +1174,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.channel = AD9910_CHANNEL_DRG_RAMP_UP,
 		.address = AD9910_CHAN_IDX_DRG_AMP_RAMP_UP,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_INT_TIME),
 		.ext_info = ad9910_drg_ramp_ext_info,
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP],
 	},
@@ -1109,7 +1186,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
 		.address = AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_INT_TIME),
 		.ext_info = ad9910_drg_ramp_ext_info,
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP],
 	},
@@ -1337,6 +1415,17 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 		default:
 			return -EINVAL;
 		}
+	case IIO_CHAN_INFO_INT_TIME:
+		switch (chan->channel) {
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			iio_val_s64_decompose(st->ramp_up_time_ns, val, val2);
+			return IIO_VAL_DECIMAL64_NANO;
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			iio_val_s64_decompose(st->ramp_down_time_ns, val, val2);
+			return IIO_VAL_DECIMAL64_NANO;
+		default:
+			return -EINVAL;
+		}
 	default:
 		return -EINVAL;
 	}
@@ -1637,6 +1726,26 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return -EINVAL;
 		}
 	}
+	case IIO_CHAN_INFO_INT_TIME: {
+		s64 val64 = iio_val_s64_compose(val, val2);
+
+		if (!st->drctl_pwm)
+			return -EOPNOTSUPP;
+		if (val64 < 0)
+			return -EINVAL;
+
+		switch (chan->channel) {
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			st->ramp_up_time_ns = val64;
+			break;
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			st->ramp_down_time_ns = val64;
+			break;
+		default:
+			return -EINVAL;
+		}
+		return ad9910_drctl_sync(st);
+	}
 	default:
 		return -EINVAL;
 	}
@@ -1671,6 +1780,8 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
 		return IIO_VAL_INT_PLUS_NANO;
 	case IIO_CHAN_INFO_OFFSET:
 		return IIO_VAL_DECIMAL64_MICRO;
+	case IIO_CHAN_INFO_INT_TIME:
+		return IIO_VAL_DECIMAL64_NANO;
 	default:
 		return -EINVAL;
 	}
@@ -2249,6 +2360,15 @@ static int ad9910_probe(struct spi_device *spi)
 
 	fsleep(AD9910_WAKEUP_DELAY_us);
 
+	st->drctl_pwm = devm_pwm_get(dev, "drctl");
+	if (IS_ERR(st->drctl_pwm)) {
+		ret = PTR_ERR(st->drctl_pwm);
+		if (ret != -ENOENT && ret != -ENODEV)
+			return dev_err_probe(dev, PTR_ERR(st->drctl_pwm),
+					     "failed to get drctl pwm\n");
+		st->drctl_pwm = NULL;
+	}
+
 	ret = ad9910_parse_fw(st);
 	if (ret)
 		return ret;

-- 
2.43.0



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

* [PATCH 08/10] iio: frequency: ad9910: support iio backend
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
                   ` (6 preceding siblings ...)
  2026-10-15 16:49 ` [PATCH 07/10] iio: frequency: ad9910: support drctl control Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 09/10] iio: ABI: add integration_time for output channels Rodrigo Alencar via B4 Relay
  2026-10-15 16:49 ` [PATCH 10/10] docs: iio: ad9910: document DRCTL pin control Rodrigo Alencar via B4 Relay
  9 siblings, 0 replies; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

The AD9910 parallel data port allows modulation of the DDS core parameters
at up to the PDCLK rate, which requires a high-speed data source. Add
support for an optional IIO backend that provides such interface, with
data streamed into the device through an output buffer.

The parallel port channels get scan indexes and scan types according to
the data destination: 14-bit amplitude, 16-bit phase, 16-bit frequency,
or the 8-bit amplitude and phase pair for polar modulation. As only one
destination can be active at a time, this is enforced by the available
scan masks. On buffer enable, the destination is configured in the
backend and the parallel data port is enabled. The sampling_frequency
attribute of the parallel port channels controls the backend data rate.

The backend is also notified about profile changes, as it may drive the
PROFILE[2:0] pins, and the debugfs source attributes now consider the
parallel data port when reporting which channel controls each DDS core
parameter.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/frequency/Kconfig  |   1 +
 drivers/iio/frequency/ad9910.c | 215 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 210 insertions(+), 6 deletions(-)

diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index f7f5c3cce005..8480076c779f 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -32,6 +32,7 @@ config AD9910
 	select CRC32
 	select FW_LOADER
 	select FW_UPLOAD
+	select IIO_BACKEND
 	help
 	  Say yes here to build support for Analog Devices AD9910
 	  1 GSPS, 14-Bit DDS with integrated DAC.
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 3d517879dee3..39d65dafd5a1 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -31,6 +31,7 @@
 #include <linux/units.h>
 #include <linux/unaligned.h>
 
+#include <linux/iio/backend.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
 
@@ -300,6 +301,15 @@ struct ad9910_ram_fw {
 	__be32 words[] __counted_by_be(wcount);
 } __packed;
 
+enum {
+	AD9910_SCAN_IDX_AMP = 0,
+	AD9910_SCAN_IDX_PHASE,
+	AD9910_SCAN_IDX_FREQ,
+	/* polar phase goes first, as it is placed in the lower byte, D[7:0] */
+	AD9910_SCAN_IDX_POLAR_PHASE,
+	AD9910_SCAN_IDX_POLAR_AMP,
+};
+
 enum {
 	AD9910_POWERDOWN,
 	AD9910_DWELL_EN,
@@ -331,6 +341,7 @@ struct ad9910_state {
 	struct gpio_desc *gpio_pwdown;
 	struct gpio_desc *gpio_update;
 	struct gpio_descs *gpio_profile;
+	struct iio_backend *back;
 
 	s64 ramp_up_time_ns;
 	s64 ramp_down_time_ns;
@@ -350,6 +361,7 @@ struct ad9910_state {
 
 	struct ad9910_data data;
 	u8 profile;
+	u8 scan_mask;
 
 	bool ram_fwu_cancel;
 	char ram_fwu_name[20];
@@ -568,6 +580,11 @@ static int ad9910_set_sysclk_freq(struct ad9910_state *st, u32 freq_hz,
 
 	st->data.sysclk_freq_hz = sysclk_freq_hz;
 
+	if (st->back)
+		return iio_backend_set_sampling_freq(st->back,
+						     AD9910_CHAN_IDX_PHY,
+						     sysclk_freq_hz);
+
 	return 0;
 }
 
@@ -579,6 +596,10 @@ static int ad9910_profile_set(struct ad9910_state *st, u8 profile)
 	values[0] = profile;
 	gpiod_multi_set_value_cansleep(st->gpio_profile, values);
 
+	if (st->back)
+		return iio_backend_chan_enable(st->back,
+					       AD9910_CHAN_IDX_PROFILE_0 + profile);
+
 	return 0;
 }
 
@@ -1016,6 +1037,7 @@ static const struct iio_chan_spec_ext_info ad9910_osk_ext_info[] = {
 	.output = 1,						\
 	.channel = AD9910_CHANNEL_PROFILE_ ## idx,		\
 	.address = AD9910_CHAN_IDX_PROFILE_ ## idx,		\
+	.scan_index = -1,					\
 	.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |	\
 			      BIT(IIO_CHAN_INFO_FREQUENCY) |	\
 			      BIT(IIO_CHAN_INFO_PHASE) |	\
@@ -1030,6 +1052,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_PHY,
 		.address = AD9910_CHAN_IDX_PHY,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ),
 		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
 		.ext_info = ad9910_phy_ext_info,
@@ -1048,6 +1071,14 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_PARALLEL,
 		.address = AD9910_CHAN_IDX_PARALLEL_AMP,
+		.scan_index = AD9910_SCAN_IDX_AMP,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 14,
+			.storagebits = 16,
+			.shift = 2,
+		},
+		.info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ),
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
 	},
 	[AD9910_CHAN_IDX_PARALLEL_PHASE] = {
@@ -1056,7 +1087,15 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_PARALLEL,
 		.address = AD9910_CHAN_IDX_PARALLEL_PHASE,
-		.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE),
+		.scan_index = AD9910_SCAN_IDX_PHASE,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.shift = 0,
+		},
+		.info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_SCALE),
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
 	},
 	[AD9910_CHAN_IDX_PARALLEL_FREQ] = {
@@ -1065,7 +1104,15 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_PARALLEL,
 		.address = AD9910_CHAN_IDX_PARALLEL_FREQ,
-		.info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) |
+		.scan_index = AD9910_SCAN_IDX_FREQ,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.shift = 0,
+		},
+		.info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_OFFSET) |
 				      BIT(IIO_CHAN_INFO_SCALE),
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
 	},
@@ -1075,7 +1122,15 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_PARALLEL_POLAR,
 		.address = AD9910_CHAN_IDX_PARALLEL_POLAR_AMP,
-		.info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) |
+		.scan_index = AD9910_SCAN_IDX_POLAR_AMP,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 8,
+			.storagebits = 8,
+			.shift = 0,
+		},
+		.info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_OFFSET) |
 				      BIT(IIO_CHAN_INFO_SCALE),
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
 	},
@@ -1085,7 +1140,15 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_PARALLEL_POLAR,
 		.address = AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE,
-		.info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) |
+		.scan_index = AD9910_SCAN_IDX_POLAR_PHASE,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 8,
+			.storagebits = 8,
+			.shift = 0,
+		},
+		.info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_OFFSET) |
 				      BIT(IIO_CHAN_INFO_SCALE),
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
 	},
@@ -1095,6 +1158,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_DRG,
 		.address = AD9910_CHAN_IDX_DRG_FREQ,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
 				      BIT(IIO_CHAN_INFO_SCALE),
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
@@ -1105,6 +1169,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_DRG,
 		.address = AD9910_CHAN_IDX_DRG_PHASE,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
 				      BIT(IIO_CHAN_INFO_SCALE),
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
@@ -1115,6 +1180,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_DRG,
 		.address = AD9910_CHAN_IDX_DRG_AMP,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
 				      BIT(IIO_CHAN_INFO_SCALE),
 		.parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
@@ -1125,6 +1191,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_DRG_RAMP_UP,
 		.address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
 				      BIT(IIO_CHAN_INFO_INT_TIME),
@@ -1137,6 +1204,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
 		.address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
 				      BIT(IIO_CHAN_INFO_INT_TIME),
@@ -1149,6 +1217,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_DRG_RAMP_UP,
 		.address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
 				      BIT(IIO_CHAN_INFO_INT_TIME),
@@ -1161,6 +1230,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
 		.address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
 				      BIT(IIO_CHAN_INFO_INT_TIME),
@@ -1173,6 +1243,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_DRG_RAMP_UP,
 		.address = AD9910_CHAN_IDX_DRG_AMP_RAMP_UP,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
 				      BIT(IIO_CHAN_INFO_INT_TIME),
@@ -1185,6 +1256,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
 		.address = AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
 				      BIT(IIO_CHAN_INFO_INT_TIME),
@@ -1197,6 +1269,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_RAM,
 		.address = AD9910_CHAN_IDX_RAM,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
 				      BIT(IIO_CHAN_INFO_FREQUENCY) |
 				      BIT(IIO_CHAN_INFO_PHASE) |
@@ -1209,6 +1282,7 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.output = 1,
 		.channel = AD9910_CHANNEL_OSK,
 		.address = AD9910_CHAN_IDX_OSK,
+		.scan_index = -1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
 				      BIT(IIO_CHAN_INFO_RAW) |
 				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
@@ -1322,6 +1396,12 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 		case AD9910_CHANNEL_PHY:
 			*val = st->data.sysclk_freq_hz;
 			return IIO_VAL_INT;
+		case AD9910_CHANNEL_PARALLEL:
+		case AD9910_CHANNEL_PARALLEL_POLAR:
+			if (!st->back)
+				return -EOPNOTSUPP;
+
+			return iio_backend_read_raw(st->back, chan, val, val2, info);
 		case AD9910_CHANNEL_DRG_RAMP_UP:
 			tmp32 = FIELD_GET(AD9910_DRG_RATE_INC_MSK,
 					  st->reg[AD9910_REG_DRG_RATE].val32);
@@ -1631,6 +1711,17 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 		if (chan->channel == AD9910_CHANNEL_PHY)
 			return ad9910_set_sysclk_freq(st, val, true);
 
+		if (chan->channel == AD9910_CHANNEL_PARALLEL ||
+		    chan->channel == AD9910_CHANNEL_PARALLEL_POLAR) {
+			if (!st->back)
+				return -EOPNOTSUPP;
+			if (val < 0)
+				return -EINVAL;
+
+			return iio_backend_set_sampling_freq(st->back,
+							     chan->address, val);
+		}
+
 		if (val < 0 || val2 < 0 || val > st->data.sysclk_freq_hz / 4)
 			return -EINVAL;
 
@@ -1773,9 +1864,14 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
 			return -EINVAL;
 		}
 	case IIO_CHAN_INFO_SAMP_FREQ:
-		if (chan->channel == AD9910_CHANNEL_PHY)
+		switch (chan->channel) {
+		case AD9910_CHANNEL_PHY:
+		case AD9910_CHANNEL_PARALLEL:
+		case AD9910_CHANNEL_PARALLEL_POLAR:
 			return IIO_VAL_INT;
-		return IIO_VAL_INT_PLUS_MICRO;
+		default:
+			return IIO_VAL_INT_PLUS_MICRO;
+		}
 	case IIO_CHAN_INFO_SCALE:
 		return IIO_VAL_INT_PLUS_NANO;
 	case IIO_CHAN_INFO_OFFSET:
@@ -2164,6 +2260,12 @@ static inline const char *ad9910_frequency_source_get(struct ad9910_state *st)
 				 st->reg[AD9910_REG_CFR2].val32))
 		return ad9910_channel_str[AD9910_CHAN_IDX_DRG_FREQ];
 
+	/* Parallel data port enabled and data destination is frequency */
+	mode_en = FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+			    st->reg[AD9910_REG_CFR2].val32);
+	if (mode_en && (st->scan_mask & BIT(AD9910_SCAN_IDX_FREQ)))
+		return ad9910_channel_str[AD9910_CHAN_IDX_PARALLEL_FREQ];
+
 	/* FTW: RAM enabled and data destination is phase, amplitude, or polar */
 	if (ram_en)
 		return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
@@ -2204,6 +2306,16 @@ static inline const char *ad9910_phase_source_get(struct ad9910_state *st)
 				 st->reg[AD9910_REG_CFR2].val32))
 		return ad9910_channel_str[AD9910_CHAN_IDX_DRG_PHASE];
 
+	/* Parallel data port enabled and data destination is phase */
+	mode_en = FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+			    st->reg[AD9910_REG_CFR2].val32);
+	if (mode_en) {
+		if (st->scan_mask & BIT(AD9910_SCAN_IDX_PHASE))
+			return ad9910_channel_str[AD9910_CHAN_IDX_PARALLEL_PHASE];
+		if (st->scan_mask & BIT(AD9910_SCAN_IDX_POLAR_PHASE))
+			return ad9910_channel_str[AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE];
+	}
+
 	/* POW: RAM enabled and data destination is frequency or amplitude */
 	if (ram_en)
 		return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
@@ -2250,6 +2362,16 @@ static inline const char *ad9910_amplitude_source_get(struct ad9910_state *st)
 				 st->reg[AD9910_REG_CFR2].val32))
 		return ad9910_channel_str[AD9910_CHAN_IDX_DRG_AMP];
 
+	/* Parallel data port enabled and data destination is amplitude */
+	mode_en = FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+			    st->reg[AD9910_REG_CFR2].val32);
+	if (mode_en) {
+		if (st->scan_mask & BIT(AD9910_SCAN_IDX_AMP))
+			return ad9910_channel_str[AD9910_CHAN_IDX_PARALLEL_AMP];
+		if (st->scan_mask & BIT(AD9910_SCAN_IDX_POLAR_AMP))
+			return ad9910_channel_str[AD9910_CHAN_IDX_PARALLEL_POLAR_AMP];
+	}
+
 	/* only way to control amplitude at this point is through OSK */
 	if (ram_en)
 		return ad9910_channel_str[AD9910_CHAN_IDX_OSK];
@@ -2278,6 +2400,72 @@ static inline void ad9910_debugfs_init(struct ad9910_state *st,
 			    &ad9910_amplitude_source_fops);
 }
 
+static const u8 ad9910_scan_chan_idx[] = {
+	[AD9910_SCAN_IDX_AMP] = AD9910_CHAN_IDX_PARALLEL_AMP,
+	[AD9910_SCAN_IDX_PHASE] = AD9910_CHAN_IDX_PARALLEL_PHASE,
+	[AD9910_SCAN_IDX_FREQ] = AD9910_CHAN_IDX_PARALLEL_FREQ,
+	[AD9910_SCAN_IDX_POLAR_PHASE] = AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE,
+	[AD9910_SCAN_IDX_POLAR_AMP] = AD9910_CHAN_IDX_PARALLEL_POLAR_AMP,
+};
+
+static u32 ad9910_buffer_chan_idx(struct iio_dev *indio_dev)
+{
+	unsigned int scan_idx = find_first_bit(indio_dev->active_scan_mask,
+					       iio_get_masklength(indio_dev));
+
+	return ad9910_scan_chan_idx[scan_idx];
+}
+
+static int ad9910_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct ad9910_state *st = iio_priv(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	st->scan_mask = *indio_dev->active_scan_mask;
+	ret = iio_backend_chan_enable(st->back,
+				      ad9910_buffer_chan_idx(indio_dev));
+	if (ret)
+		return ret;
+
+	return ad9910_reg32_update(st, AD9910_REG_CFR2,
+				   AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+				   AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+				   true);
+}
+
+static int ad9910_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct ad9910_state *st = iio_priv(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	st->scan_mask = 0;
+	ret = ad9910_reg32_update(st, AD9910_REG_CFR2,
+				  AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+				  0, true);
+	if (ret)
+		return ret;
+
+	return iio_backend_chan_disable(st->back,
+					ad9910_buffer_chan_idx(indio_dev));
+}
+
+static const struct iio_buffer_setup_ops ad9910_buffer_setup_ops = {
+	.preenable = ad9910_buffer_preenable,
+	.postdisable = ad9910_buffer_postdisable,
+};
+
+static const unsigned long ad9910_available_scan_masks[] = {
+	BIT(AD9910_SCAN_IDX_AMP),
+	BIT(AD9910_SCAN_IDX_PHASE),
+	BIT(AD9910_SCAN_IDX_FREQ),
+	BIT(AD9910_SCAN_IDX_POLAR_AMP) | BIT(AD9910_SCAN_IDX_POLAR_PHASE),
+	0
+};
+
 static int ad9910_probe(struct spi_device *spi)
 {
 	static const char * const supplies[] = {
@@ -2321,6 +2509,20 @@ static int ad9910_probe(struct spi_device *spi)
 		return dev_err_probe(dev, PTR_ERR(dev_rst),
 				     "failed to get device reset control\n");
 
+	st->back = devm_iio_backend_get_optional(dev, NULL);
+	if (IS_ERR(st->back))
+		return dev_err_probe(dev, PTR_ERR(st->back),
+				     "failed to get iio backend\n");
+	if (st->back) {
+		ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to request iio backend buffer\n");
+
+		indio_dev->setup_ops = &ad9910_buffer_setup_ops;
+		indio_dev->available_scan_masks = ad9910_available_scan_masks;
+	}
+
 	/*
 	 * The IO RESET pin is not used in this driver, as we assume that all
 	 * SPI transfers are complete, but if it is wired up, we need to make
@@ -2424,3 +2626,4 @@ module_spi_driver(ad9910_driver);
 MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
 MODULE_DESCRIPTION("Analog Devices AD9910 DDS driver");
 MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BACKEND");

-- 
2.43.0



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

* [PATCH 09/10] iio: ABI: add integration_time for output channels
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
                   ` (7 preceding siblings ...)
  2026-10-15 16:49 ` [PATCH 08/10] iio: frequency: ad9910: support iio backend Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  2026-10-15 18:38   ` Andy Shevchenko
  2026-10-15 16:49 ` [PATCH 10/10] docs: iio: ad9910: document DRCTL pin control Rodrigo Alencar via B4 Relay
  9 siblings, 1 reply; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Document the integration_time attribute for output channels, which is
the time interval during which the output is generated by integrating
a quantity, such as its rate of change. It is the output counterpart of
the integration_time already defined for input channels.
Add entries for the altcurrent, frequency and phase channel types, which
are going to be used by the AD9910 DDS driver.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 Documentation/ABI/testing/sysfs-bus-iio | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio
index 2b6f57041763..a41a96935cfb 100644
--- a/Documentation/ABI/testing/sysfs-bus-iio
+++ b/Documentation/ABI/testing/sysfs-bus-iio
@@ -1744,6 +1744,23 @@ Description:
 		seconds. If shared across all channels of a given type,
 		<type>_integration_time is used.
 
+What:		/sys/bus/iio/devices/iio:deviceX/out_altcurrentY_integration_time
+What:		/sys/bus/iio/devices/iio:deviceX/out_frequencyY_integration_time
+What:		/sys/bus/iio/devices/iio:deviceX/out_phaseY_integration_time
+KernelVersion:	7.4
+Contact:	linux-iio@vger.kernel.org
+Description:
+		This attribute is used to get/set the integration time in
+		seconds of an output channel, i.e., the time interval during
+		which the output is generated by integrating (accumulating) a
+		quantity over time, such as the rate of change (roc) of the
+		channel. The output variation over this interval corresponds to
+		the integral of that quantity, which may be bounded by limits
+		configured through other channel attributes. The written value
+		may be adjusted to the nearest interval supported by the
+		hardware. If shared across all channels of a given type,
+		<type>_integration_time is used.
+
 What:		/sys/.../iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_integration_time
 KernelVersion:	4.0
 Contact:	linux-iio@vger.kernel.org

-- 
2.43.0



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

* [PATCH 10/10] docs: iio: ad9910: document DRCTL pin control
  2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
                   ` (8 preceding siblings ...)
  2026-10-15 16:49 ` [PATCH 09/10] iio: ABI: add integration_time for output channels Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:49 ` Rodrigo Alencar via B4 Relay
  9 siblings, 0 replies; 15+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-15 16:49 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron
  Cc: Olivier Moysan, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Michael Hennerich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Document the integration_time attribute of the digital ramp generator
(DRG) rising and falling channels, and describe how the DRCTL pin
waveform is generated from those values according to the dwell_en
configuration. Add usage examples for triangular and sawtooth sweeps
driven through the DRCTL pin.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 Documentation/iio/ad9910.rst | 90 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 84 insertions(+), 6 deletions(-)

diff --git a/Documentation/iio/ad9910.rst b/Documentation/iio/ad9910.rst
index aab569f561db..bff55cca8c82 100644
--- a/Documentation/iio/ad9910.rst
+++ b/Documentation/iio/ad9910.rst
@@ -85,13 +85,13 @@ system clock (SYSCLK) which affects all other channels.
      - ``drg_rising_frequency``
      - ``drg_frequency``
      - DRG frequency rising-ramp parameters: limit code, dwell enable,
-       ramp clock, rate of change.
+       ramp clock, rate of change, integration time.
 
    * - ``out_frequency132``
      - ``drg_falling_frequency``
      - ``drg_frequency``
      - DRG frequency falling-ramp parameters: limit code, dwell enable,
-       ramp clock, rate of change.
+       ramp clock, rate of change, integration time.
 
    * - ``out_phase130``
      - ``drg_phase``
@@ -104,13 +104,13 @@ system clock (SYSCLK) which affects all other channels.
      - ``drg_rising_phase``
      - ``drg_phase``
      - DRG phase rising-ramp parameters: limit code, dwell enable, ramp clock,
-       rate of change.
+       rate of change, integration time.
 
    * - ``out_phase132``
      - ``drg_falling_phase``
      - ``drg_phase``
      - DRG phase falling-ramp parameters: limit code, dwell enable, ramp clock,
-       rate of change.
+       rate of change, integration time.
 
    * - ``out_altcurrent130``
      - ``drg_amplitude``
@@ -123,13 +123,13 @@ system clock (SYSCLK) which affects all other channels.
      - ``drg_rising_amplitude``
      - ``drg_amplitude``
      - DRG amplitude rising-ramp parameters: limit code, dwell enable,
-       ramp clock, rate of change.
+       ramp clock, rate of change, integration time.
 
    * - ``out_altcurrent132``
      - ``drg_falling_amplitude``
      - ``drg_amplitude``
      - DRG amplitude falling-ramp parameters: limit code, dwell enable,
-       ramp clock, rate of change.
+       ramp clock, rate of change, integration time.
 
    * - ``out_altcurrent140``
      - ``ram``
@@ -493,6 +493,60 @@ Ramp channel attributes
        from the hardware step size and the current ramp clock. Writing
        requires ``sampling_frequency`` to be configured first.
 
+   * - ``integration_time``
+     - s
+     - Time interval assigned to the ramp direction, which is used to generate
+       the DRCTL pin waveform. See `DRCTL pin control`_. Rising and falling
+       ramp channels of all destinations share the same underlying value for
+       their respective directions. The written value is adjusted to the
+       resolution supported by the PWM provider.
+
+DRCTL pin control
+^^^^^^^^^^^^^^^^^
+
+The DRCTL pin selects the DRG slope direction: a logic high drives the ramp
+towards the upper limit and a logic low drives it towards the lower limit.
+When a PWM is assigned to the DRCTL pin (via the ``pwms`` and ``pwm-names``
+devicetree properties), the driver generates the DRCTL waveform from the
+``integration_time`` of the rising and falling ramp channels, according to the
+``dwell_en`` configuration:
+
+.. flat-table::
+   :header-rows: 1
+
+   * - Rising ``dwell_en``
+     - Falling ``dwell_en``
+     - DRCTL waveform
+
+   * - 1
+     - 1
+     - DRCTL is held high for the rising ``integration_time`` and then held
+       low for the falling ``integration_time``. The PWM period is the sum of
+       both intervals. If a ramp reaches its limit before its interval
+       elapses, the output dwells at that limit.
+
+   * - 0
+     - 1
+     - Sawtooth rising ramp: a new ramp starts at every DRCTL rising edge,
+       which happens periodically at the rising ``integration_time``. The
+       falling ``integration_time`` is not used.
+
+   * - 1
+     - 0
+     - Sawtooth falling ramp: a new ramp starts at every DRCTL falling edge,
+       which happens periodically at the falling ``integration_time``. The
+       rising ``integration_time`` is not used.
+
+   * - 0
+     - 0
+     - Bidirectional continuous ramp. DRCTL is not relevant and it is held
+       low.
+
+The DRCTL output is disabled when the resulting PWM period is zero, e.g., when
+the relevant ``integration_time`` attributes are set to ``0``. Without a PWM
+assigned to the DRCTL pin, writing to ``integration_time`` fails with
+``-EOPNOTSUPP``.
+
 Usage examples
 ^^^^^^^^^^^^^^
 
@@ -525,6 +579,30 @@ Configure a frequency sweep from 40 MHz to 60 MHz with a rate of change of
   # Enable the DRG with frequency as the destination
   echo 1 > /sys/bus/iio/devices/iio\:device0/out_frequency130_en
 
+With the same limits and rate of change, each ramp takes 0.8 ms to complete.
+Configure a PWM-driven triangular sweep that dwells 0.2 ms at each limit:
+
+.. code-block:: bash
+
+  # Enable dwell on both limits, so DRCTL controls the ramp direction
+  echo 1 > /sys/bus/iio/devices/iio\:device0/out_frequency131_dwell_en
+  echo 1 > /sys/bus/iio/devices/iio\:device0/out_frequency132_dwell_en
+
+  # DRCTL high for 1 ms (rising ramp) and low for 1 ms (falling ramp)
+  echo 0.001 > /sys/bus/iio/devices/iio\:device0/out_frequency131_integration_time
+  echo 0.001 > /sys/bus/iio/devices/iio\:device0/out_frequency132_integration_time
+
+Configure a rising sawtooth sweep that restarts every 1 ms:
+
+.. code-block:: bash
+
+  # No dwell at the upper limit: the ramp returns to the lower limit
+  echo 0 > /sys/bus/iio/devices/iio\:device0/out_frequency131_dwell_en
+  echo 1 > /sys/bus/iio/devices/iio\:device0/out_frequency132_dwell_en
+
+  # A new rising ramp starts every 1 ms
+  echo 0.001 > /sys/bus/iio/devices/iio\:device0/out_frequency131_integration_time
+
 RAM mode
 --------
 

-- 
2.43.0



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

* Re: [PATCH 09/10] iio: ABI: add integration_time for output channels
  2026-10-15 16:49 ` [PATCH 09/10] iio: ABI: add integration_time for output channels Rodrigo Alencar via B4 Relay
@ 2026-10-15 18:38   ` Andy Shevchenko
  0 siblings, 0 replies; 15+ messages in thread
From: Andy Shevchenko @ 2026-10-15 18:38 UTC (permalink / raw)
  To: rodrigo.alencar
  Cc: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron, Olivier Moysan, Jonathan Cameron, David Lechner,
	Andy Shevchenko, Michael Hennerich, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Jonathan Corbet, Shuah Khan, Randy Dunlap

On Thu, Oct 15, 2026 at 01:49:49PM -0300, Rodrigo Alencar via B4 Relay wrote:

> Document the integration_time attribute for output channels, which is
> the time interval during which the output is generated by integrating
> a quantity, such as its rate of change. It is the output counterpart of
> the integration_time already defined for input channels.
> Add entries for the altcurrent, frequency and phase channel types, which
> are going to be used by the AD9910 DDS driver.

...

> +KernelVersion:	7.4

That boat is about to sail... It means, no chances you can get onboard.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 02/10] iio: backend: add devm_iio_backend_get_optional()
  2026-10-15 16:49 ` [PATCH 02/10] iio: backend: add devm_iio_backend_get_optional() Rodrigo Alencar via B4 Relay
@ 2026-10-15 18:41   ` Andy Shevchenko
  0 siblings, 0 replies; 15+ messages in thread
From: Andy Shevchenko @ 2026-10-15 18:41 UTC (permalink / raw)
  To: rodrigo.alencar
  Cc: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron, Olivier Moysan, Jonathan Cameron, David Lechner,
	Andy Shevchenko, Michael Hennerich, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Jonathan Corbet, Shuah Khan, Randy Dunlap

On Thu, Oct 15, 2026 at 01:49:42PM -0300, Rodrigo Alencar via B4 Relay wrote:

> Frontend devices might not need a backend driver to rely on. An IIO device
> that supports backends can behave differently when they are not available
> and that should be acceptable. Such parts may provide basic functionality
> solely using the control interface (SPI for instance), being able to have
> extended features when a backend is present.
> 
> Add devm_iio_backend_get_optional(), which returns NULL when the backend
> is not described in firmware, i.e., when the io-backends property is not
> present or, for a named lookup, when the name is not listed in the
> io-backend-names property. Other errors, such as probe deferral, are
> still returned.
> 
> To make that possible, a named lookup that does not match any entry of
> io-backend-names now fails with -ENOENT, the same error returned when the
> io-backends reference is missing. Also, the missing reference is no longer
> logged as an error, leaving that to the caller.

...

>  	struct fwnode_handle *fwnode_back __free(fwnode_handle) =
>  		fwnode_find_reference(fwnode, "io-backends", index);
> -	if (IS_ERR(fwnode_back))
> +	if (IS_ERR(fwnode_back)) {
> +		/* not described in firmware, let the caller decide what to do */
> +		if (PTR_ERR(fwnode_back) == -ENOENT)
> +			return ERR_CAST(fwnode_back);
> +
>  		return dev_err_cast_probe(dev, fwnode_back,
>  					  "Cannot get Firmware reference\n");
> +	}

Simpler to do is just to add

	/* not described in firmware, let the caller decide what to do */
	if (PTR_ERR(fwnode_back) == -ENOENT)
		return ERR_CAST(fwnode_back);

just before IS_ERR() check.

...

>  		ret = device_property_match_string(dev, "io-backend-names", name);
> +		if (ret == -EINVAL || ret == -ENODATA)
> +			return ERR_PTR(-ENOENT);

No. Please, use device_property_present() instead.

>  		if (ret < 0)
>  			return ERR_PTR(ret);

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 03/10] dt-bindings: iio: frequency: add adi,ad9910-axi-backend
  2026-10-15 16:49 ` [PATCH 03/10] dt-bindings: iio: frequency: add adi,ad9910-axi-backend Rodrigo Alencar via B4 Relay
@ 2026-10-15 18:59   ` Conor Dooley
  0 siblings, 0 replies; 15+ messages in thread
From: Conor Dooley @ 2026-10-15 18:59 UTC (permalink / raw)
  To: rodrigo.alencar
  Cc: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron, Olivier Moysan, Jonathan Cameron, David Lechner,
	Andy Shevchenko, Michael Hennerich, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Jonathan Corbet, Shuah Khan, Randy Dunlap

[-- Attachment #1: Type: text/plain, Size: 4142 bytes --]

On Thu, Oct 15, 2026 at 01:49:43PM -0300, Rodrigo Alencar via B4 Relay wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Add bindings for the AD9910 AXI backend IP core. The FPGA IP core
> interfaces with the data and control pins of the AD9910 DDS, streaming
> data from memory via DMA into the 18-bit parallel data port (F[1:0] and
> D[15:0] pins), which is clocked by the PDCLK output of the device. It
> also drives the PROFILE[2:0] pins and the DRCTL pin, which controls the
> slope direction of the digital ramp generator (DRG). The DRCTL pin is
> driven by a PWM generator, so the IP core is described both as an IIO
> backend and as a PWM provider.
> 
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> ---
>  .../iio/frequency/adi,ad9910-axi-backend.yaml      | 74 ++++++++++++++++++++++
>  MAINTAINERS                                        |  1 +
>  2 files changed, 75 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml
> new file mode 100644
> index 000000000000..20b884e8eb10
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml
> @@ -0,0 +1,74 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/frequency/adi,ad9910-axi-backend.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices AD9910 AXI Backend IP core
> +
> +maintainers:
> +  - Rodrigo Alencar <rodrigo.alencar@analog.com>
> +
> +description:
> +  FPGA IP core that interfaces with the data and control pins of the AD9910
> +  Direct Digital Synthesizer. The SPI bus is used as the control interface
> +  of the AD9910 device, while this IP core streams data from memory via DMA
> +  into the 18-bit parallel data port (F[1:0] and D[15:0] pins), which is
> +  clocked by the PDCLK output of the AD9910. The IP core also drives the
> +  PROFILE[2:0] pins for profile selection and provides a PWM generator that
> +  drives the DRCTL pin, controlling the digital ramp generator (DRG) slope
> +  direction.
> +
> +  https://www.analog.com/en/products/ad9910.html

Should this link not point to the docs for the backend device?

Otherwise, this is pretty fine I think..

Conor.

> +
> +properties:
> +  compatible:
> +    const: adi,ad9910-axi-backend
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +    description: AXI bus clock (s_axi_aclk).
> +
> +  dmas:
> +    maxItems: 1
> +
> +  dma-names:
> +    items:
> +      - const: tx
> +
> +  '#io-backend-cells':
> +    const: 0
> +
> +  '#pwm-cells':
> +    const: 3
> +    description:
> +      The first cell must be 0, as only the DRCTL PWM output is available.
> +      The second cell is the PWM period in nanoseconds and the third cell
> +      holds the PWM flags.
> +
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - dmas
> +  - dma-names
> +  - '#io-backend-cells'
> +  - '#pwm-cells'
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    ad9910_backend: dds-backend@44a00000 {
> +        compatible = "adi,ad9910-axi-backend";
> +        reg = <0x44a00000 0x10000>;
> +        clocks = <&clkc 15>;
> +        dmas = <&tx_dma 0>;
> +        dma-names = "tx";
> +        #io-backend-cells = <0>;
> +        #pwm-cells = <3>;
> +    };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3a2dfd1a4fd8..93ad6a829cb3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1731,6 +1731,7 @@ S:	Supported
>  W:	https://ez.analog.com/linux-software-drivers
>  F:	Documentation/ABI/testing/debugfs-iio-ad9910
>  F:	Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
> +F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml
>  F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
>  F:	Documentation/iio/ad9910.rst
>  F:	drivers/iio/frequency/ad9910.c
> 
> -- 
> 2.43.0
> 
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

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

* Re: [PATCH 04/10] dt-bindings: iio: frequency: ad9910: add io-backends property
  2026-10-15 16:49 ` [PATCH 04/10] dt-bindings: iio: frequency: ad9910: add io-backends property Rodrigo Alencar via B4 Relay
@ 2026-10-15 19:00   ` Conor Dooley
  0 siblings, 0 replies; 15+ messages in thread
From: Conor Dooley @ 2026-10-15 19:00 UTC (permalink / raw)
  To: rodrigo.alencar
  Cc: linux-iio, linux-kernel, linux, devicetree, linux-pwm, linux-doc,
	Jonathan Cameron, Olivier Moysan, Jonathan Cameron, David Lechner,
	Andy Shevchenko, Michael Hennerich, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Jonathan Corbet, Shuah Khan, Randy Dunlap

[-- Attachment #1: Type: text/plain, Size: 75 bytes --]

Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

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

end of thread, other threads:[~2026-10-15 19:00 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 16:49 [PATCH 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
2026-10-15 16:49 ` [PATCH 01/10] iio: frequency: ad9910: powerdown fix Rodrigo Alencar via B4 Relay
2026-10-15 16:49 ` [PATCH 02/10] iio: backend: add devm_iio_backend_get_optional() Rodrigo Alencar via B4 Relay
2026-10-15 18:41   ` Andy Shevchenko
2026-10-15 16:49 ` [PATCH 03/10] dt-bindings: iio: frequency: add adi,ad9910-axi-backend Rodrigo Alencar via B4 Relay
2026-10-15 18:59   ` Conor Dooley
2026-10-15 16:49 ` [PATCH 04/10] dt-bindings: iio: frequency: ad9910: add io-backends property Rodrigo Alencar via B4 Relay
2026-10-15 19:00   ` Conor Dooley
2026-10-15 16:49 ` [PATCH 05/10] iio: frequency: ad9910: move channel indexes into a shared header Rodrigo Alencar via B4 Relay
2026-10-15 16:49 ` [PATCH 06/10] iio: frequency: ad9910: add axi backend driver Rodrigo Alencar via B4 Relay
2026-10-15 16:49 ` [PATCH 07/10] iio: frequency: ad9910: support drctl control Rodrigo Alencar via B4 Relay
2026-10-15 16:49 ` [PATCH 08/10] iio: frequency: ad9910: support iio backend Rodrigo Alencar via B4 Relay
2026-10-15 16:49 ` [PATCH 09/10] iio: ABI: add integration_time for output channels Rodrigo Alencar via B4 Relay
2026-10-15 18:38   ` Andy Shevchenko
2026-10-15 16:49 ` [PATCH 10/10] docs: iio: ad9910: document DRCTL pin control Rodrigo Alencar via B4 Relay

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