* [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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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:59 ` sashiko-bot
2026-10-15 16:49 ` [PATCH 07/10] iio: frequency: ad9910: support drctl control Rodrigo Alencar via B4 Relay
` (3 subsequent siblings)
9 siblings, 1 reply; 18+ 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] 18+ messages in thread* Re: [PATCH 06/10] iio: frequency: ad9910: add axi backend driver
2026-10-15 16:49 ` [PATCH 06/10] iio: frequency: ad9910: add axi backend driver Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:59 ` sashiko-bot
0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-10-15 16:59 UTC (permalink / raw)
To: Rodrigo Alencar, devicetree; +Cc: robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] iio: ad9910: incorrect destination mapping swaps amplitude and frequency
- [High] iio: frequency: ad9910-axi: zero-divisor panic if MMIO read returns ~0
- [Medium] iio: ad9910-axi: profile channels return -EINVAL on disable
--
commit 07c9b224d9d92ea624369fff596857cafd508264
Author: Rodrigo Alencar <rodrigo.alencar@analog.com>
Date: Thu Oct 15 16:49:46 2026 +0000
iio: frequency: ad9910: add axi backend driver
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 buffer output, parallel data destination configuration,
and PWM integration for ramp direction control.
> diff --git a/drivers/iio/frequency/ad9910-axi-backend.c b/drivers/iio/frequency/ad9910-axi-backend.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/iio/frequency/ad9910-axi-backend.c
[ ... ]
> +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);
[Severity: High]
Does this subtraction incorrectly map IIO channel indices to hardware
destination codes?
Looking at enum ad9910_channel_idx, AD9910_CHAN_IDX_PARALLEL_AMP is ordered
before AD9910_CHAN_IDX_PARALLEL_FREQ. In this subtraction, Amplitude evaluates
to 0 and Frequency evaluates to 2.
However, the hardware destination codes defined in ad9910.h map
AD9910_DEST_FREQUENCY to 0 and AD9910_DEST_AMPLITUDE to 2.
Will writing this mapped value to AD9910_AXI_REG_F_CFG cause the IIO
parallel amplitude channel to inadvertently configure the hardware for a
frequency destination, and vice versa?
> + 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;
[Severity: Medium]
Will profile channels fall into this default case and return an error instead
of succeeding cleanly?
The ad9910_axi_chan_enable() function supports enabling profile channels
(AD9910_CHAN_IDX_PROFILE_0 to 7) and successfully returns 0. However, this
disable function lacks a corresponding case for them.
When the IIO core attempts to disable a previously enabled profile channel,
will this unconditionally return -EINVAL and potentially leave the device
state inconsistent or emit unwanted kernel log warnings during teardown?
> + }
> +}
[ ... ]
> +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);
[Severity: High]
Could this lead to a division-by-zero panic if the MMIO read returns ~0?
If the AXI IP core or bus becomes unresponsive or disconnected, the MMIO read
via regmap_read() may silently return 0xFFFFFFFF (~0) without an error code.
Since div is a u32, div + 1 would overflow to 0. This 0 is then passed
directly to div_s64() as the divisor.
Because ad9910_axi_read_raw can be triggered from userspace by reading sysfs
attributes, could a local attacker or unprivileged user reliably trigger a
kernel panic if the hardware enters an error state?
> + return IIO_VAL_DECIMAL64_MICRO;
> + }
> + default:
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-ad9910-backend-support-v1-0-3c21648c8e26@analog.com?part=6
^ permalink raw reply [flat|nested] 18+ 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:58 ` sashiko-bot
2026-10-15 16:49 ` [PATCH 08/10] iio: frequency: ad9910: support iio backend Rodrigo Alencar via B4 Relay
` (2 subsequent siblings)
9 siblings, 1 reply; 18+ 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] 18+ messages in thread* Re: [PATCH 07/10] iio: frequency: ad9910: support drctl control
2026-10-15 16:49 ` [PATCH 07/10] iio: frequency: ad9910: support drctl control Rodrigo Alencar via B4 Relay
@ 2026-10-15 16:58 ` sashiko-bot
0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-10-15 16:58 UTC (permalink / raw)
To: Rodrigo Alencar, devicetree; +Cc: robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] iio: ad9910: missing error rollback for integration_time on hardware failure
- [Medium] iio: ad9910: integration_time write fails if PWM lacks hardware readback
- [Medium] iio: ad9910: missing PWM disable on driver unbind causing resource leak
--
commit 3bdef9985085248f9ab75c013e273ef9d7e62a3f
Author: Rodrigo Alencar <rodrigo.alencar@analog.com>
iio: frequency: ad9910: support drctl control
This commit adds support for controlling the AD9910 digital ramp generator
slope direction using a PWM signal applied to the DRCTL pin. It exposes
integration_time attributes to set the time intervals for ramp up and ramp
down sweeps.
> diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
> index dc13986dff3db..3d517879dee39 100644
> --- a/drivers/iio/frequency/ad9910.c
> +++ b/drivers/iio/frequency/ad9910.c
[ ... ]
> @@ -609,6 +614,66 @@ static int ad9910_sw_powerdown_set(struct ad9910_state *st, bool enable)
[ ... ]
> +static int ad9910_drctl_sync(struct ad9910_state *st)
> +{
[ ... ]
> + 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;
[Severity: Medium]
Will this cause spurious write failures on systems where the PWM driver
lacks hardware state readback support? If the underlying PWM driver does
not implement .get_state or .read_waveform, pwm_get_state_hw() returns
-EOPNOTSUPP. This forces ad9910_drctl_sync() to abort and return an
error, despite the PWM configuration having been successfully applied
via pwm_apply_might_sleep() right before this.
[ ... ]
> @@ -1637,6 +1726,26 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
[ ... ]
> + 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);
> + }
[Severity: Medium]
Is there a risk of state corruption here if the hardware sync fails? The
driver updates the cached software state (ramp_up_time_ns or
ramp_down_time_ns) immediately with the user-provided value before applying
it to the hardware via ad9910_drctl_sync().
If the underlying PWM provider rejects the configuration or lacks state
readback, ad9910_drctl_sync() returns an error. This error is propagated
to userspace, but the old integration time value is never restored in the
software cache.
[ ... ]
> @@ -2249,6 +2360,15 @@ static int ad9910_probe(struct spi_device *spi)
[ ... ]
>
> + 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;
> + }
> +
[Severity: Medium]
Does this leak the active PWM hardware state on driver unbind? The PWM is
acquired here using devm_pwm_get(), and enabled during integration_time
writes via pwm_apply_might_sleep() inside ad9910_drctl_sync().
However, there doesn't appear to be any corresponding devm_add_action()
registered, or a .remove callback, to ensure pwm_disable() is called.
When the driver unbinds, devres will automatically release the PWM reference
via pwm_put(), but pwm_put() does not disable active PWM hardware. This
might leave the PWM signal toggling indefinitely after the AD9910 is
powered down.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-ad9910-backend-support-v1-0-3c21648c8e26@analog.com?part=7
^ permalink raw reply [flat|nested] 18+ 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 17:04 ` sashiko-bot
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, 1 reply; 18+ 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] 18+ messages in thread* Re: [PATCH 08/10] iio: frequency: ad9910: support iio backend
2026-10-15 16:49 ` [PATCH 08/10] iio: frequency: ad9910: support iio backend Rodrigo Alencar via B4 Relay
@ 2026-10-15 17:04 ` sashiko-bot
0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-10-15 17:04 UTC (permalink / raw)
To: Rodrigo Alencar, devicetree; +Cc: robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] iio: ad9910: missing error rollback causing state desync and resource leaks
- [Medium] iio: frequency: ad9910: backend channel leak on preenable error
- [Medium] iio: frequency: ad9910: missing backend cleanup on postdisable error
--
commit 7c7244ed8ac1bd865ad6a2d0af50cec74add275e
Author: Rodrigo Alencar <rodrigo.alencar@analog.com>
Subject: iio: frequency: ad9910: support iio backend
This commit adds support for an optional IIO backend to the AD9910 parallel
data port. It handles backend data rate control and synchronizes hardware
states with buffer callbacks.
> diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
> index 3d517879dee39..39d65dafd5a18 100644
> --- a/drivers/iio/frequency/ad9910.c
> +++ b/drivers/iio/frequency/ad9910.c
[ ... ]
> @@ -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;
> }
[Severity: High]
If iio_backend_set_sampling_freq() fails, are the software cache and SPI
hardware states left desynchronized from the IIO backend?
It appears that st->data.sysclk_freq_hz and potentially the SPI hardware
(via earlier updates in this function) are modified before the backend call.
If the backend rejects the configuration, this returns an error without
rolling back those earlier updates, leaving the driver state inconsistent.
[ ... ]
> @@ -2278,6 +2400,72 @@ static inline void ad9910_debugfs_init(struct ad9910_state *st,
[ ... ]
> +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);
> +}
[Severity: High]
Does returning early here leak the backend channel if ad9910_reg32_update()
fails?
The function successfully enables the backend channel via
iio_backend_chan_enable(), but if the subsequent SPI write fails, it returns
the error code directly. Because the IIO core handles preenable() failures
by aborting the buffer enablement process without invoking postdisable(),
the backend channel seems to remain permanently enabled.
Should there be an error path here that calls iio_backend_chan_disable()
when the register update fails?
> +
> +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));
> +}
[Severity: Medium]
Could this early return skip the required backend cleanup?
If ad9910_reg32_update() fails during this teardown path, it immediately
returns the error code, completely skipping iio_backend_chan_disable().
This could leak the backend channel and leave the backend state desynchronized
from the IIO buffer state.
Would it be better to always attempt iio_backend_chan_disable() on a
best-effort basis, even if the hardware write fails?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-ad9910-backend-support-v1-0-3c21648c8e26@analog.com?part=8
^ permalink raw reply [flat|nested] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ messages in thread