* [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910
2026-02-20 16:46 [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
@ 2026-02-20 16:46 ` Rodrigo Alencar via B4 Relay
2026-02-21 20:43 ` David Lechner
2026-03-01 12:50 ` Jonathan Cameron
2026-02-20 16:46 ` [PATCH RFC 2/8] iio: frequency: ad9910: initial driver implementation Rodrigo Alencar via B4 Relay
` (7 subsequent siblings)
8 siblings, 2 replies; 46+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-02-20 16:46 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
DT-bindings for AD9910, a 1 GSPS DDS with 14-bit DAC. It includes
configurations for the reference clock path, DAC current, reset and basic
GPIO control.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
.../bindings/iio/frequency/adi,ad9910.yaml | 236 +++++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 243 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
new file mode 100644
index 000000000000..43b21d1428ba
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
@@ -0,0 +1,236 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/frequency/adi,ad9910.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD9910 Direct Digital Synthesizer
+
+maintainers:
+ - Rodrigo Alencar <rodrigo.alencar@analog.com>
+
+description:
+ The AD9910 is a 1 GSPS direct digital synthesizer (DDS) with an integrated
+ 14-bit DAC. It features single tone mode with 8 configurable profiles,
+ a digital ramp generator, RAM control, OSK, and a parallel data port for
+ high-speed streaming.
+
+ https://www.analog.com/en/products/ad9910.html
+
+properties:
+ compatible:
+ const: adi,ad9910
+
+ reg:
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 70000000
+
+ clocks:
+ maxItems: 1
+ description:
+ Reference clock input (REFCLK). When the PLL is enabled, this is
+ multiplied by adi,pll-multiplier to produce the system clock.
+ When the PLL is bypassed, the reference clock is used directly or divided
+ by 2 based on adi,reference-div2-enable to produce the system clock.
+
+ dvdd-io33-supply:
+ description: 3.3V Digital I/O supply.
+
+ avdd33-supply:
+ description: 3.3V Analog DAC supply.
+
+ dvdd18-supply:
+ description: 1.8V Digital Core supply.
+
+ avdd18-supply:
+ description: 1.8V Analog Core supply.
+
+ resets:
+ minItems: 1
+ maxItems: 2
+
+ reset-names:
+ oneOf:
+ - items:
+ - const: dev
+ - items:
+ - const: dev
+ - const: io
+
+ reset-gpios:
+ maxItems: 2
+ description:
+ GPIOs controlling the device reset and the I/O_RESET pins. This is only
+ used if resets property is not defined.
+
+ powerdown-gpios:
+ maxItems: 1
+ description:
+ GPIO controlling the EXT_PWR_DWN pin.
+
+ update-gpios:
+ maxItems: 1
+ description:
+ GPIO controlling the I/O_UPDATE pin.
+
+ profile-gpios:
+ minItems: 3
+ maxItems: 3
+ description:
+ GPIOs controlling the PROFILE[2:0] pins for profile selection.
+
+ adi,pll-multiplier:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 12
+ maximum: 127
+ description:
+ PLL feedback divider value (N). The system clock frequency is
+ REFCLK * N. When not specified, the PLL is bypassed.
+
+ adi,pll-vco-select:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0
+ maximum: 5
+ description: |
+ VCO frequency range selection (0-5). When not specified and the PLL
+ is enabled, the VCO range is automatically selected based on the
+ computed system clock frequency. Typical VCO frequency ranges are:
+ - Range 0: 370 MHz to 510 MHz (Auto-selected when <= 465 MHz)
+ - Range 1: 420 MHz to 590 MHz (Auto-selected when > 465 MHz and <= 545 MHz)
+ - Range 2: 500 MHz to 700 MHz (Auto-selected when > 545 MHz and <= 650 MHz)
+ - Range 3: 600 MHz to 880 MHz (Auto-selected when > 650 MHz and <= 790 MHz)
+ - Range 4: 700 MHz to 950 MHz (Auto-selected when > 790 MHz and <= 885 MHz)
+ - Range 5: 820 MHz to 1050 MHz (Auto-selected when > 885 MHz)
+
+ adi,charge-pump-current-microamp:
+ minimum: 212
+ maximum: 387
+ default: 387
+ description:
+ PLL charge pump current in microamps. Only applicable when the PLL
+ is enabled. The value is rounded to the nearest supported step.
+
+ adi,refclk-out-drive-strength:
+ $ref: /schemas/types.yaml#/definitions/string
+ enum: [ disabled, low, medium, high ]
+ default: disabled
+ description:
+ Reference clock output (DRV0) drive strength. Only applicable when
+ the PLL is enabled.
+
+ adi,reference-div2-enable:
+ type: boolean
+ description:
+ Enable the reference clock input divider. When enabled, the input
+ reference frequency is halved before deriving the system clock.
+ This is only applicable when the PLL is bypassed.
+
+ adi,inverse-sinc-enable:
+ type: boolean
+ description:
+ Enable the inverse sinc filter that compensates for the sinc roll-off
+ of the DAC output. When it is enabled, the filter introduces up to 3 dB
+ of insertion loss.
+
+ adi,sine-output-enable:
+ type: boolean
+ description:
+ Select sine wave output from the DDS core. When not set, the
+ output is a cosine wave.
+
+ adi,sync-clk-disable:
+ type: boolean
+ description:
+ Disable the SYNC_CLK output pin. SYNC_CLK runs at one quarter
+ of the system clock frequency.
+
+ adi,pdclk-disable:
+ type: boolean
+ description:
+ Disable the parallel data clock (PDCLK) output. PDCLK runs at
+ one quarter of the system clock frequency.
+
+ adi,pdclk-invert:
+ type: boolean
+ description:
+ Invert the polarity of the PDCLK output.
+
+ adi,tx-enable-invert:
+ type: boolean
+ description:
+ Invert the polarity of the TX_ENABLE input pin.
+
+ adi,dac-output-current-microamp:
+ minimum: 8640
+ maximum: 31590
+ default: 20070
+ description:
+ DAC full-scale output current in microamps.
+
+dependencies:
+ adi,pll-vco-select: [ 'adi,pll-multiplier' ]
+ adi,charge-pump-current-microamp: [ 'adi,pll-multiplier' ]
+ adi,refclk-out-drive-strength: [ 'adi,pll-multiplier' ]
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - dvdd-io33-supply
+ - avdd33-supply
+ - dvdd18-supply
+ - avdd18-supply
+
+dependentSchemas:
+ resets:
+ properties:
+ reset-gpios: false
+ reset-gpios:
+ properties:
+ resets: false
+ adi,reference-div2-enable:
+ properties:
+ adi,pll-multiplier: false
+ adi,pll-multiplier:
+ properties:
+ adi,reference-div2-enable: false
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ dds@0 {
+ compatible = "adi,ad9910";
+ reg = <0>;
+ spi-max-frequency = <1000000>;
+ clocks = <&ad9910_refclk>;
+
+ dvdd-io33-supply = <&vdd_io33>;
+ avdd33-supply = <&vdd_a33>;
+ dvdd18-supply = <&vdd_d18>;
+ avdd18-supply = <&vdd_a18>;
+
+ reset-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>,
+ <&gpio 1 GPIO_ACTIVE_HIGH>;
+ powerdown-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
+ update-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
+ profile-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>,
+ <&gpio 5 GPIO_ACTIVE_HIGH>,
+ <&gpio 6 GPIO_ACTIVE_HIGH>;
+
+ adi,pll-multiplier = <40>;
+ adi,charge-pump-current-microamp = <387>;
+ adi,refclk-out-drive-strength = "disabled";
+ adi,inverse-sinc-enable;
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 1251965d70bd..79b4180e2334 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1610,6 +1610,13 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml
F: drivers/iio/dac/ad9739a.c
+ANALOG DEVICES INC AD9910 DRIVER
+M: Rodrigo Alencar <rodrigo.alencar@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
+
ANALOG DEVICES INC MAX22007 DRIVER
M: Janani Sunil <janani.sunil@analog.com>
L: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread* Re: [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910
2026-02-20 16:46 ` [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910 Rodrigo Alencar via B4 Relay
@ 2026-02-21 20:43 ` David Lechner
2026-02-21 22:43 ` Conor Dooley
2026-02-22 10:47 ` Rodrigo Alencar
2026-03-01 12:50 ` Jonathan Cameron
1 sibling, 2 replies; 46+ messages in thread
From: David Lechner @ 2026-02-21 20:43 UTC (permalink / raw)
To: rodrigo.alencar, linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> DT-bindings for AD9910, a 1 GSPS DDS with 14-bit DAC. It includes
> configurations for the reference clock path, DAC current, reset and basic
> GPIO control.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> ---
> .../bindings/iio/frequency/adi,ad9910.yaml | 236 +++++++++++++++++++++
> MAINTAINERS | 7 +
> 2 files changed, 243 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
> new file mode 100644
> index 000000000000..43b21d1428ba
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
> @@ -0,0 +1,236 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/frequency/adi,ad9910.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices AD9910 Direct Digital Synthesizer
> +
> +maintainers:
> + - Rodrigo Alencar <rodrigo.alencar@analog.com>
> +
> +description:
> + The AD9910 is a 1 GSPS direct digital synthesizer (DDS) with an integrated
> + 14-bit DAC. It features single tone mode with 8 configurable profiles,
> + a digital ramp generator, RAM control, OSK, and a parallel data port for
> + high-speed streaming.
> +
> + https://www.analog.com/en/products/ad9910.html
> +
> +properties:
> + compatible:
> + const: adi,ad9910
> +
> + reg:
> + maxItems: 1
> +
> + spi-max-frequency:
> + maximum: 70000000
> +
> + clocks:
> + maxItems: 1
> + description:
> + Reference clock input (REFCLK). When the PLL is enabled, this is
> + multiplied by adi,pll-multiplier to produce the system clock.
> + When the PLL is bypassed, the reference clock is used directly or divided
> + by 2 based on adi,reference-div2-enable to produce the system clock.
Devicetree is just concerned with how it is wired up. The first
sentence is enough.
> +
> + dvdd-io33-supply:
> + description: 3.3V Digital I/O supply.
> +
> + avdd33-supply:
> + description: 3.3V Analog DAC supply.
> +
> + dvdd18-supply:
> + description: 1.8V Digital Core supply.
> +
> + avdd18-supply:
> + description: 1.8V Analog Core supply.
> +
> + resets:
> + minItems: 1
> + maxItems: 2
> +
> + reset-names:
> + oneOf:
> + - items:
> + - const: dev
> + - items:
> + - const: dev
> + - const: io
This could be a bit more clear. Does dev == MASTER_RESET pin?
And what about DAC_RSET?
> +
> + reset-gpios:
> + maxItems: 2
> + description:
> + GPIOs controlling the device reset and the I/O_RESET pins. This is only
> + used if resets property is not defined.
> +
> + powerdown-gpios:
> + maxItems: 1
> + description:
> + GPIO controlling the EXT_PWR_DWN pin.
> +
> + update-gpios:
> + maxItems: 1
> + description:
> + GPIO controlling the I/O_UPDATE pin.
> +
> + profile-gpios:
> + minItems: 3
> + maxItems: 3
> + description:
> + GPIOs controlling the PROFILE[2:0] pins for profile selection.
> +
Looks like possibly some interrupts as well: RAM_SWP_OVR and SYNC_SMP_ERR
> + adi,pll-multiplier:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 12
> + maximum: 127
> + description:
> + PLL feedback divider value (N). The system clock frequency is
> + REFCLK * N. When not specified, the PLL is bypassed.
What determines the value that should be selected here? This doesn't seem like
something we would normally put in the devicetree. (Smells like configuration
rather than describing what is wired up.)
> +
> + adi,pll-vco-select:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 0
> + maximum: 5
> + description: |
> + VCO frequency range selection (0-5). When not specified and the PLL
> + is enabled, the VCO range is automatically selected based on the
> + computed system clock frequency. Typical VCO frequency ranges are:
> + - Range 0: 370 MHz to 510 MHz (Auto-selected when <= 465 MHz)
> + - Range 1: 420 MHz to 590 MHz (Auto-selected when > 465 MHz and <= 545 MHz)
> + - Range 2: 500 MHz to 700 MHz (Auto-selected when > 545 MHz and <= 650 MHz)
> + - Range 3: 600 MHz to 880 MHz (Auto-selected when > 650 MHz and <= 790 MHz)
> + - Range 4: 700 MHz to 950 MHz (Auto-selected when > 790 MHz and <= 885 MHz)
> + - Range 5: 820 MHz to 1050 MHz (Auto-selected when > 885 MHz)
> +
Same here. How does this depend on how the chip is wired up?
> + adi,charge-pump-current-microamp:
> + minimum: 212
> + maximum: 387
> + default: 387
> + description:
> + PLL charge pump current in microamps. Only applicable when the PLL
> + is enabled. The value is rounded to the nearest supported step.
> +
> + adi,refclk-out-drive-strength:
> + $ref: /schemas/types.yaml#/definitions/string
> + enum: [ disabled, low, medium, high ]
> + default: disabled
> + description:
> + Reference clock output (DRV0) drive strength. Only applicable when
> + the PLL is enabled.
> +
> + adi,reference-div2-enable:
> + type: boolean
> + description:
> + Enable the reference clock input divider. When enabled, the input
> + reference frequency is halved before deriving the system clock.
> + This is only applicable when the PLL is bypassed.
> +
> + adi,inverse-sinc-enable:
> + type: boolean
> + description:
> + Enable the inverse sinc filter that compensates for the sinc roll-off
> + of the DAC output. When it is enabled, the filter introduces up to 3 dB
> + of insertion loss.
> +
> + adi,sine-output-enable:
> + type: boolean
> + description:
> + Select sine wave output from the DDS core. When not set, the
> + output is a cosine wave.
And these last few are really getting into things that are typically
controlled by the IIO driver and not hard-coded.
> +
> + adi,sync-clk-disable:
> + type: boolean
> + description:
> + Disable the SYNC_CLK output pin. SYNC_CLK runs at one quarter
> + of the system clock frequency.
Clock outputs should be described as clock-controller and #clock-cells.
The actual enabling/disabling can be done at runtime.
> +
> + adi,pdclk-disable:
> + type: boolean
> + description:
> + Disable the parallel data clock (PDCLK) output. PDCLK runs at
> + one quarter of the system clock frequency.
> +
> + adi,pdclk-invert:
> + type: boolean
> + description:
> + Invert the polarity of the PDCLK output.
> +
> + adi,tx-enable-invert:
> + type: boolean
> + description:
> + Invert the polarity of the TX_ENABLE input pin.
> +
> + adi,dac-output-current-microamp:
> + minimum: 8640
> + maximum: 31590
> + default: 20070
> + description:
> + DAC full-scale output current in microamps.
> +
> +dependencies:
> + adi,pll-vco-select: [ 'adi,pll-multiplier' ]
> + adi,charge-pump-current-microamp: [ 'adi,pll-multiplier' ]
> + adi,refclk-out-drive-strength: [ 'adi,pll-multiplier' ]
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - dvdd-io33-supply
> + - avdd33-supply
> + - dvdd18-supply
> + - avdd18-supply
> +
> +dependentSchemas:
> + resets:
> + properties:
> + reset-gpios: false
> + reset-gpios:
> + properties:
> + resets: false
This seems too strict. Couldn't we have some resets from a reset
controller and others from gpios?
Or maybe the reset bindings are enough and we don't need the gpio
bindings for the same pin?
> + adi,reference-div2-enable:
> + properties:
> + adi,pll-multiplier: false
> + adi,pll-multiplier:
> + properties:
> + adi,reference-div2-enable: false
> +
> +allOf:
> + - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + dds@0 {
> + compatible = "adi,ad9910";
> + reg = <0>;
> + spi-max-frequency = <1000000>;
> + clocks = <&ad9910_refclk>;
> +
> + dvdd-io33-supply = <&vdd_io33>;
> + avdd33-supply = <&vdd_a33>;
> + dvdd18-supply = <&vdd_d18>;
> + avdd18-supply = <&vdd_a18>;
> +
> + reset-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>,
> + <&gpio 1 GPIO_ACTIVE_HIGH>;
> + powerdown-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
> + update-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
> + profile-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>,
> + <&gpio 5 GPIO_ACTIVE_HIGH>,
> + <&gpio 6 GPIO_ACTIVE_HIGH>;
> +
> + adi,pll-multiplier = <40>;
> + adi,charge-pump-current-microamp = <387>;
> + adi,refclk-out-drive-strength = "disabled";
> + adi,inverse-sinc-enable;
> + };
> + };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1251965d70bd..79b4180e2334 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1610,6 +1610,13 @@ W: https://ez.analog.com/linux-software-drivers
> F: Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml
> F: drivers/iio/dac/ad9739a.c
>
> +ANALOG DEVICES INC AD9910 DRIVER
> +M: Rodrigo Alencar <rodrigo.alencar@analog.com>
> +L: linux-iio@vger.kernel.org
> +S: Supported
> +W: https://ez.analog.com/linux-software-drivers
> +F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
> +
> ANALOG DEVICES INC MAX22007 DRIVER
> M: Janani Sunil <janani.sunil@analog.com>
> L: linux-iio@vger.kernel.org
>
^ permalink raw reply [flat|nested] 46+ messages in thread* Re: [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910
2026-02-21 20:43 ` David Lechner
@ 2026-02-21 22:43 ` Conor Dooley
2026-02-22 10:49 ` Rodrigo Alencar
2026-02-22 10:47 ` Rodrigo Alencar
1 sibling, 1 reply; 46+ messages in thread
From: Conor Dooley @ 2026-02-21 22:43 UTC (permalink / raw)
To: David Lechner
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
[-- Attachment #1: Type: text/plain, Size: 725 bytes --]
On Sat, Feb 21, 2026 at 02:43:02PM -0600, David Lechner wrote:
> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > +dependentSchemas:
> > + resets:
> > + properties:
> > + reset-gpios: false
> > + reset-gpios:
> > + properties:
> > + resets: false
>
> This seems too strict. Couldn't we have some resets from a reset
> controller and others from gpios?
>
> Or maybe the reset bindings are enough and we don't need the gpio
> bindings for the same pin?
Actually, I'd be interested in seeing evidence for the resets property
ever being used with this device. I think every single reset-controller
that's currently documented is an on-chip device for resetting
peripherals.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910
2026-02-21 22:43 ` Conor Dooley
@ 2026-02-22 10:49 ` Rodrigo Alencar
2026-02-22 13:24 ` Conor Dooley
0 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar @ 2026-02-22 10:49 UTC (permalink / raw)
To: Conor Dooley, David Lechner
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 26/02/21 10:43PM, Conor Dooley wrote:
> On Sat, Feb 21, 2026 at 02:43:02PM -0600, David Lechner wrote:
> > On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
>
> > > +dependentSchemas:
> > > + resets:
> > > + properties:
> > > + reset-gpios: false
> > > + reset-gpios:
> > > + properties:
> > > + resets: false
> >
> > This seems too strict. Couldn't we have some resets from a reset
> > controller and others from gpios?
> >
> > Or maybe the reset bindings are enough and we don't need the gpio
> > bindings for the same pin?
>
> Actually, I'd be interested in seeing evidence for the resets property
> ever being used with this device. I think every single reset-controller
> that's currently documented is an on-chip device for resetting
> peripherals.
As mentioned, an FPGA IP as an IIO backend (MMIO platform_device) will
implement the reset controller interface.
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910
2026-02-22 10:49 ` Rodrigo Alencar
@ 2026-02-22 13:24 ` Conor Dooley
0 siblings, 0 replies; 46+ messages in thread
From: Conor Dooley @ 2026-02-22 13:24 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: David Lechner, rodrigo.alencar, linux-iio, devicetree,
linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
[-- Attachment #1: Type: text/plain, Size: 1156 bytes --]
On Sun, Feb 22, 2026 at 10:49:35AM +0000, Rodrigo Alencar wrote:
> On 26/02/21 10:43PM, Conor Dooley wrote:
> > On Sat, Feb 21, 2026 at 02:43:02PM -0600, David Lechner wrote:
> > > On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> >
> > > > +dependentSchemas:
> > > > + resets:
> > > > + properties:
> > > > + reset-gpios: false
> > > > + reset-gpios:
> > > > + properties:
> > > > + resets: false
> > >
> > > This seems too strict. Couldn't we have some resets from a reset
> > > controller and others from gpios?
> > >
> > > Or maybe the reset bindings are enough and we don't need the gpio
> > > bindings for the same pin?
> >
> > Actually, I'd be interested in seeing evidence for the resets property
> > ever being used with this device. I think every single reset-controller
> > that's currently documented is an on-chip device for resetting
> > peripherals.
>
> As mentioned, an FPGA IP as an IIO backend (MMIO platform_device) will
> implement the reset controller interface.
I think you're confusing this with some other patch, I don't see any
mention of FPGA IPs or io-backends here.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910
2026-02-21 20:43 ` David Lechner
2026-02-21 22:43 ` Conor Dooley
@ 2026-02-22 10:47 ` Rodrigo Alencar
2026-02-22 20:28 ` David Lechner
2026-02-22 20:31 ` Conor Dooley
1 sibling, 2 replies; 46+ messages in thread
From: Rodrigo Alencar @ 2026-02-22 10:47 UTC (permalink / raw)
To: David Lechner, rodrigo.alencar, linux-iio, devicetree,
linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 26/02/21 02:43PM, David Lechner wrote:
> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
...
> > + resets:
> > + minItems: 1
> > + maxItems: 2
> > +
> > + reset-names:
> > + oneOf:
> > + - items:
> > + - const: dev
> > + - items:
> > + - const: dev
> > + - const: io
>
> This could be a bit more clear. Does dev == MASTER_RESET pin?
Correct.
> And what about DAC_RSET?
DAC_RSET is not a reset, it is a pin for a 10K omh resistor.
> > +
> > + reset-gpios:
> > + maxItems: 2
> > + description:
> > + GPIOs controlling the device reset and the I/O_RESET pins. This is only
> > + used if resets property is not defined.
> > +
> > + powerdown-gpios:
> > + maxItems: 1
> > + description:
> > + GPIO controlling the EXT_PWR_DWN pin.
> > +
> > + update-gpios:
> > + maxItems: 1
> > + description:
> > + GPIO controlling the I/O_UPDATE pin.
> > +
> > + profile-gpios:
> > + minItems: 3
> > + maxItems: 3
> > + description:
> > + GPIOs controlling the PROFILE[2:0] pins for profile selection.
> > +
>
> Looks like possibly some interrupts as well: RAM_SWP_OVR and SYNC_SMP_ERR
Interrupts are not handled by the driver at this point, so they were not added
here. The device is meant to have some features exposed through SPI, but to
extract the most of it needs to interface with an FPGA. For that, an IIO
backend is in the works.
> > + adi,pll-multiplier:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 12
> > + maximum: 127
> > + description:
> > + PLL feedback divider value (N). The system clock frequency is
> > + REFCLK * N. When not specified, the PLL is bypassed.
>
> What determines the value that should be selected here? This doesn't seem like
> something we would normally put in the devicetree. (Smells like configuration
> rather than describing what is wired up.)
The sysclk frequency is something we want fixed because it derives the sync_clk
and pd_clk which can be constraints for FPGA desing or when interfacing with
the parallel port (high-speed up to 250 MHz). Also, when using the PLL path to
derive the sysclk, the external loop filter will pretty much define the values
to be used here, as it would be optimized for a certain frequency range.
> > +
> > + adi,pll-vco-select:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 5
> > + description: |
> > + VCO frequency range selection (0-5). When not specified and the PLL
> > + is enabled, the VCO range is automatically selected based on the
> > + computed system clock frequency. Typical VCO frequency ranges are:
> > + - Range 0: 370 MHz to 510 MHz (Auto-selected when <= 465 MHz)
> > + - Range 1: 420 MHz to 590 MHz (Auto-selected when > 465 MHz and <= 545 MHz)
> > + - Range 2: 500 MHz to 700 MHz (Auto-selected when > 545 MHz and <= 650 MHz)
> > + - Range 3: 600 MHz to 880 MHz (Auto-selected when > 650 MHz and <= 790 MHz)
> > + - Range 4: 700 MHz to 950 MHz (Auto-selected when > 790 MHz and <= 885 MHz)
> > + - Range 5: 820 MHz to 1050 MHz (Auto-selected when > 885 MHz)
> > +
>
> Same here. How does this depend on how the chip is wired up?
That applies to when the PLL path is used. FPGA interface and loop filter design.
I can write those details as part of the description.
> > + adi,charge-pump-current-microamp:
> > + minimum: 212
> > + maximum: 387
> > + default: 387
> > + description:
> > + PLL charge pump current in microamps. Only applicable when the PLL
> > + is enabled. The value is rounded to the nearest supported step.
> > +
> > + adi,refclk-out-drive-strength:
> > + $ref: /schemas/types.yaml#/definitions/string
> > + enum: [ disabled, low, medium, high ]
> > + default: disabled
> > + description:
> > + Reference clock output (DRV0) drive strength. Only applicable when
> > + the PLL is enabled.
> > +
> > + adi,reference-div2-enable:
> > + type: boolean
> > + description:
> > + Enable the reference clock input divider. When enabled, the input
> > + reference frequency is halved before deriving the system clock.
> > + This is only applicable when the PLL is bypassed.
> > +
> > + adi,inverse-sinc-enable:
> > + type: boolean
> > + description:
> > + Enable the inverse sinc filter that compensates for the sinc roll-off
> > + of the DAC output. When it is enabled, the filter introduces up to 3 dB
> > + of insertion loss.
> > +
> > + adi,sine-output-enable:
> > + type: boolean
> > + description:
> > + Select sine wave output from the DDS core. When not set, the
> > + output is a cosine wave.
>
> And these last few are really getting into things that are typically
> controlled by the IIO driver and not hard-coded.
indeed, adi,sine-output-enable could be removed from here.
> > +
> > + adi,sync-clk-disable:
> > + type: boolean
> > + description:
> > + Disable the SYNC_CLK output pin. SYNC_CLK runs at one quarter
> > + of the system clock frequency.
>
> Clock outputs should be described as clock-controller and #clock-cells.
> The actual enabling/disabling can be done at runtime.
I thought of that, but when interfacing with an FPGA, the clock consumer
will be the IIO backend itself, which this device driver would depend on.
It would create a cyclic dependency during the probe of the drivers:
- This device being a clock provider and an IIO backend consumer
- The FPGA IP being a IIO backend provider and a clock consumer.
This would be just save some power when not interfacing with an FPGA,
there would not be a clock consumer to get the clock disabled.
Normally, clock consumers would want to have clock enabled, which is
already the case by default.
I would add the FPGA/IIO backend support in a separate patch series,
as it would bring more stuff here.
> > +
> > + adi,pdclk-disable:
> > + type: boolean
> > + description:
> > + Disable the parallel data clock (PDCLK) output. PDCLK runs at
> > + one quarter of the system clock frequency.
> > +
> > + adi,pdclk-invert:
> > + type: boolean
> > + description:
> > + Invert the polarity of the PDCLK output.
> > +
> > + adi,tx-enable-invert:
> > + type: boolean
> > + description:
> > + Invert the polarity of the TX_ENABLE input pin.
> > +
> > + adi,dac-output-current-microamp:
> > + minimum: 8640
> > + maximum: 31590
> > + default: 20070
> > + description:
> > + DAC full-scale output current in microamps.
> > +
> > +dependencies:
> > + adi,pll-vco-select: [ 'adi,pll-multiplier' ]
> > + adi,charge-pump-current-microamp: [ 'adi,pll-multiplier' ]
> > + adi,refclk-out-drive-strength: [ 'adi,pll-multiplier' ]
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - clocks
> > + - dvdd-io33-supply
> > + - avdd33-supply
> > + - dvdd18-supply
> > + - avdd18-supply
> > +
> > +dependentSchemas:
> > + resets:
> > + properties:
> > + reset-gpios: false
> > + reset-gpios:
> > + properties:
> > + resets: false
>
> This seems too strict. Couldn't we have some resets from a reset
> controller and others from gpios?
I suppose that would be confusing. using reset-gpios is the case where
we are NOT interfacing with an FPGA. Otherwise, using resets would be the
case when the IIO backend also implements the reset controller interface.
> Or maybe the reset bindings are enough and we don't need the gpio
> bindings for the same pin?
normally reset-gpios are enough as it can be used to instantiate a
reset controller too. I could introduce resets when adding the IIO backend
support.
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910
2026-02-22 10:47 ` Rodrigo Alencar
@ 2026-02-22 20:28 ` David Lechner
2026-02-22 20:31 ` Conor Dooley
1 sibling, 0 replies; 46+ messages in thread
From: David Lechner @ 2026-02-22 20:28 UTC (permalink / raw)
To: Rodrigo Alencar, rodrigo.alencar, linux-iio, devicetree,
linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 2/22/26 4:47 AM, Rodrigo Alencar wrote:
> On 26/02/21 02:43PM, David Lechner wrote:
>> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
>>> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
...
>>> +
>>> + reset-gpios:
>>> + maxItems: 2
>>> + description:
>>> + GPIOs controlling the device reset and the I/O_RESET pins. This is only
>>> + used if resets property is not defined.
>>> +
>>> + powerdown-gpios:
>>> + maxItems: 1
>>> + description:
>>> + GPIO controlling the EXT_PWR_DWN pin.
>>> +
>>> + update-gpios:
>>> + maxItems: 1
>>> + description:
>>> + GPIO controlling the I/O_UPDATE pin.
>>> +
>>> + profile-gpios:
>>> + minItems: 3
>>> + maxItems: 3
>>> + description:
>>> + GPIOs controlling the PROFILE[2:0] pins for profile selection.
>>> +
>>
>> Looks like possibly some interrupts as well: RAM_SWP_OVR and SYNC_SMP_ERR
>
> Interrupts are not handled by the driver at this point, so they were not added
> here. The device is meant to have some features exposed through SPI, but to
> extract the most of it needs to interface with an FPGA. For that, an IIO
> backend is in the works.
DT bindings should aim to be complete. It doesn't matter what the driver
implements or not.
We make exceptions for things that haven't been seen before where the
bindings might not be obvious, but output pins like this (at least the
error one) are pretty much always connected to interrupts.
Also, the interrupt properties should not be required. So if the output
line is connected to an io-backend instead of an interrupt, that is fine.
The bindings should cover all ways this could possibly be wired up.
>>> +
>>> + adi,sync-clk-disable:
>>> + type: boolean
>>> + description:
>>> + Disable the SYNC_CLK output pin. SYNC_CLK runs at one quarter
>>> + of the system clock frequency.
>>
>> Clock outputs should be described as clock-controller and #clock-cells.
>> The actual enabling/disabling can be done at runtime.
>
> I thought of that, but when interfacing with an FPGA, the clock consumer
> will be the IIO backend itself, which this device driver would depend on.
> It would create a cyclic dependency during the probe of the drivers:
> - This device being a clock provider and an IIO backend consumer
> - The FPGA IP being a IIO backend provider and a clock consumer.
As above, the binding should not depend on what the driver does. There is
a standard binding for this, so we should use it. I'm sure we could find
a way to make it work in the driver even if it is just manually parsing the
properties instead of going through the clock framework. I.e. if the
clock-controller property is present, turn on the clock output, otherwise
turn off the clock output.
>
> This would be just save some power when not interfacing with an FPGA,
> there would not be a clock consumer to get the clock disabled.
> Normally, clock consumers would want to have clock enabled, which is
> already the case by default.
>
> I would add the FPGA/IIO backend support in a separate patch series,
> as it would bring more stuff here.
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910
2026-02-22 10:47 ` Rodrigo Alencar
2026-02-22 20:28 ` David Lechner
@ 2026-02-22 20:31 ` Conor Dooley
1 sibling, 0 replies; 46+ messages in thread
From: Conor Dooley @ 2026-02-22 20:31 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: David Lechner, rodrigo.alencar, linux-iio, devicetree,
linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
[-- Attachment #1: Type: text/plain, Size: 6839 bytes --]
On Sun, Feb 22, 2026 at 10:47:41AM +0000, Rodrigo Alencar wrote:
> On 26/02/21 02:43PM, David Lechner wrote:
> > On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > > +
> > > + reset-gpios:
> > > + maxItems: 2
> > > + description:
> > > + GPIOs controlling the device reset and the I/O_RESET pins. This is only
> > > + used if resets property is not defined.
> > > +
> > > + powerdown-gpios:
> > > + maxItems: 1
> > > + description:
> > > + GPIO controlling the EXT_PWR_DWN pin.
> > > +
> > > + update-gpios:
> > > + maxItems: 1
> > > + description:
> > > + GPIO controlling the I/O_UPDATE pin.
> > > +
> > > + profile-gpios:
> > > + minItems: 3
> > > + maxItems: 3
> > > + description:
> > > + GPIOs controlling the PROFILE[2:0] pins for profile selection.
> > > +
> >
> > Looks like possibly some interrupts as well: RAM_SWP_OVR and SYNC_SMP_ERR
>
> Interrupts are not handled by the driver at this point, so they were not added
> here. The device is meant to have some features exposed through SPI, but to
> extract the most of it needs to interface with an FPGA. For that, an IIO
> backend is in the works.
The binding should be complete, if there are interrupts then document
them even if the driver doesn't use them. If this is where you say you
mentioned the io-backends, that's not what I meant - you need to do it
in the patch itself to explain why you have the odd setup with resets
and reset-gpios.
That said, if you don't know what the io-backends stuff is going to look
like for this device, you should probably exclude those resets until the
design for the io-backend IP is complete. reset-gpios are usually
optional anyway (since they are often just wired high/low), so there
shouldn't be any downside there.
> > > + adi,pll-multiplier:
> > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > + minimum: 12
> > > + maximum: 127
> > > + description:
> > > + PLL feedback divider value (N). The system clock frequency is
> > > + REFCLK * N. When not specified, the PLL is bypassed.
> >
> > What determines the value that should be selected here? This doesn't seem like
> > something we would normally put in the devicetree. (Smells like configuration
> > rather than describing what is wired up.)
>
> The sysclk frequency is something we want fixed because it derives the sync_clk
> and pd_clk which can be constraints for FPGA desing or when interfacing with
btw, the datasheet for this device (rev e) says:
| SYNC_CLK is a rising edge active signal. It is derived from the
| system clock and a divide-by-4 frequency divider. SYNC_CLK,
| which is externally provided, can be used to synchronize external
| hardware to the AD9910 internal clocks.
It's not externally provided, so the third sentence here is confusing.
> the parallel port (high-speed up to 250 MHz). Also, when using the PLL path to
> derive the sysclk, the external loop filter will pretty much define the values
> to be used here, as it would be optimized for a certain frequency range.
>
> > > +
> > > + adi,pll-vco-select:
> > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > + minimum: 0
> > > + maximum: 5
> > > + description: |
> > > + VCO frequency range selection (0-5). When not specified and the PLL
> > > + is enabled, the VCO range is automatically selected based on the
> > > + computed system clock frequency. Typical VCO frequency ranges are:
> > > + - Range 0: 370 MHz to 510 MHz (Auto-selected when <= 465 MHz)
> > > + - Range 1: 420 MHz to 590 MHz (Auto-selected when > 465 MHz and <= 545 MHz)
> > > + - Range 2: 500 MHz to 700 MHz (Auto-selected when > 545 MHz and <= 650 MHz)
> > > + - Range 3: 600 MHz to 880 MHz (Auto-selected when > 650 MHz and <= 790 MHz)
> > > + - Range 4: 700 MHz to 950 MHz (Auto-selected when > 790 MHz and <= 885 MHz)
> > > + - Range 5: 820 MHz to 1050 MHz (Auto-selected when > 885 MHz)
> > > +
> >
> > Same here. How does this depend on how the chip is wired up?
>
> That applies to when the PLL path is used. FPGA interface and loop filter design.
> I can write those details as part of the description.
This property definitely needs justification given that it looks like
the device will just do the right thing without the property.
> > > +
> > > + adi,sync-clk-disable:
> > > + type: boolean
> > > + description:
> > > + Disable the SYNC_CLK output pin. SYNC_CLK runs at one quarter
> > > + of the system clock frequency.
> >
> > Clock outputs should be described as clock-controller and #clock-cells.
> > The actual enabling/disabling can be done at runtime.
>
> I thought of that, but when interfacing with an FPGA, the clock consumer
> will be the IIO backend itself, which this device driver would depend on.
> It would create a cyclic dependency during the probe of the drivers:
> - This device being a clock provider and an IIO backend consumer
> - The FPGA IP being a IIO backend provider and a clock consumer.
>
> This would be just save some power when not interfacing with an FPGA,
> there would not be a clock consumer to get the clock disabled.
> Normally, clock consumers would want to have clock enabled, which is
> already the case by default.
>
> I would add the FPGA/IIO backend support in a separate patch series,
> as it would bring more stuff here.
Honestly, this binding design seems so tightly integrated with the
io-backend that you're shooting yourself in the foot, and making it hard
for us to review, by not including it. I think this really should be a
clock provider and the io-backend a clock consumer, even if it causes a
cyclical dependency. One of the two can probably be a post-init-provider
to the other, probably something like sync_clk doesn't need to be set at
probe, and setting the rate can be delayed until we start needing data
from the device? Since both sync_clk and pdclk are provided by the
ad9910 to the io-backend, your driver will have control over what the
rates of these clocks are and you shouldn't need to hard code the
multipliers etc. You just need to know if the parallel bus is in use or
not. I'm not sure if that's a given if there's an io-backend and you're
not on a serial bus or if would need a property to identify.
Most of the clocking related properties here seem like they go away if
you make the device a clock-controller, for example the driver should be
able to decide if the needs to enable reference-div2 to achieve the
internal frequencies that it needs. sync-clk-disable would be done
automatically if there's no consumer of sync_clk etc.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910
2026-02-20 16:46 ` [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910 Rodrigo Alencar via B4 Relay
2026-02-21 20:43 ` David Lechner
@ 2026-03-01 12:50 ` Jonathan Cameron
1 sibling, 0 replies; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-01 12:50 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Fri, 20 Feb 2026 16:46:05 +0000
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> DT-bindings for AD9910, a 1 GSPS DDS with 14-bit DAC. It includes
> configurations for the reference clock path, DAC current, reset and basic
> GPIO control.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
Hi Rodrigo,
A few comments from me inline.
Thanks,
Jonathan
> ---
> .../bindings/iio/frequency/adi,ad9910.yaml | 236 +++++++++++++++++++++
> MAINTAINERS | 7 +
> 2 files changed, 243 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
> new file mode 100644
> index 000000000000..43b21d1428ba
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
> + adi,pll-vco-select:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 0
> + maximum: 5
> + description: |
> + VCO frequency range selection (0-5). When not specified and the PLL
> + is enabled, the VCO range is automatically selected based on the
> + computed system clock frequency. Typical VCO frequency ranges are:
Given this automatic mode. Why would need to override it?
> + - Range 0: 370 MHz to 510 MHz (Auto-selected when <= 465 MHz)
> + - Range 1: 420 MHz to 590 MHz (Auto-selected when > 465 MHz and <= 545 MHz)
> + - Range 2: 500 MHz to 700 MHz (Auto-selected when > 545 MHz and <= 650 MHz)
> + - Range 3: 600 MHz to 880 MHz (Auto-selected when > 650 MHz and <= 790 MHz)
> + - Range 4: 700 MHz to 950 MHz (Auto-selected when > 790 MHz and <= 885 MHz)
> + - Range 5: 820 MHz to 1050 MHz (Auto-selected when > 885 MHz)
> +
> + adi,charge-pump-current-microamp:
> + minimum: 212
> + maximum: 387
> + default: 387
> + description:
> + PLL charge pump current in microamps. Only applicable when the PLL
> + is enabled. The value is rounded to the nearest supported step.
> +
> + adi,refclk-out-drive-strength:
> + $ref: /schemas/types.yaml#/definitions/string
> + enum: [ disabled, low, medium, high ]
> + default: disabled
> + description:
> + Reference clock output (DRV0) drive strength. Only applicable when
> + the PLL is enabled.
> +
> + adi,reference-div2-enable:
> + type: boolean
> + description:
> + Enable the reference clock input divider. When enabled, the input
> + reference frequency is halved before deriving the system clock.
> + This is only applicable when the PLL is bypassed.
Often for these they can be derived from what the desired output
frequencies are. There tends to be a right answer for any combination on
input clocks and output frequency so we don't normally need them
in DT. If there is a reason we do here, needs more explanation.
> +
> + adi,inverse-sinc-enable:
> + type: boolean
> + description:
> + Enable the inverse sinc filter that compensates for the sinc roll-off
> + of the DAC output. When it is enabled, the filter introduces up to 3 dB
> + of insertion loss.
We probably need to improve our userspace filter controls for this one.
Why would it need to be in DT? Maybe we even just decide to always
enable this one despite the attenuation. I'm not sure how the term
insertion loss applies to a filter being enabled or not.
> +
> + adi,sine-output-enable:
> + type: boolean
> + description:
> + Select sine wave output from the DDS core. When not set, the
> + output is a cosine wave.
That sounds like a userspace thing probably represented as a phase
offset.
> +
> + adi,sync-clk-disable:
> + type: boolean
> + description:
> + Disable the SYNC_CLK output pin. SYNC_CLK runs at one quarter
> + of the system clock frequency.
> +
> + adi,pdclk-disable:
> + type: boolean
> + description:
> + Disable the parallel data clock (PDCLK) output. PDCLK runs at
> + one quarter of the system clock frequency.
> +
> + adi,pdclk-invert:
> + type: boolean
> + description:
> + Invert the polarity of the PDCLK output.
Others have commented on these as clock providers. I think that
will make more sense.
> +
> + adi,tx-enable-invert:
> + type: boolean
> + description:
> + Invert the polarity of the TX_ENABLE input pin.
It's an input, so I assume you are referring to some chip control
that allows us to change what is expected on this pin from
active high to active low or similar?
Is this hooked up to the backend fpga signal and that has some
fixed output polarity?
> +
> + adi,dac-output-current-microamp:
> + minimum: 8640
> + maximum: 31590
> + default: 20070
> + description:
> + DAC full-scale output current in microamps.
Needs an explanation of why this is in DT.
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH RFC 2/8] iio: frequency: ad9910: initial driver implementation
2026-02-20 16:46 [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
2026-02-20 16:46 ` [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910 Rodrigo Alencar via B4 Relay
@ 2026-02-20 16:46 ` Rodrigo Alencar via B4 Relay
2026-03-01 13:20 ` Jonathan Cameron
2026-02-20 16:46 ` [PATCH RFC 3/8] iio: frequency: ad9910: add simple parallel port mode support Rodrigo Alencar via B4 Relay
` (6 subsequent siblings)
8 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-02-20 16:46 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add the core AD9910 DDS driver infrastructure with single tone mode
support. This includes SPI register access, profile management via
GPIO pins, PLL/DAC configuration from firmware properties, and
single tone frequency/phase/amplitude control through IIO channels.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
MAINTAINERS | 1 +
drivers/iio/frequency/Kconfig | 18 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/ad9910.c | 931 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 951 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 79b4180e2334..4967e4ef73dd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1616,6 +1616,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
+F: drivers/iio/frequency/ad9910.c
ANALOG DEVICES INC MAX22007 DRIVER
M: Janani Sunil <janani.sunil@analog.com>
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index 583cbdf4e8cd..180e74f62d11 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -23,6 +23,24 @@ config AD9523
endmenu
+menu "Direct Digital Synthesis"
+
+config AD9910
+ tristate "Analog Devices AD9910 Direct Digital Synthesizer"
+ depends on SPI
+ depends on GPIOLIB
+ help
+ Say yes here to build support for Analog Devices AD9910
+ 1 GSPS, 14-Bit DDS with integrated DAC.
+
+ Supports single tone mode with 8 configurable profiles
+ and digital ramp generation.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad9910.
+
+endmenu
+
#
# Phase-Locked Loop (PLL) frequency synthesizers
#
diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
index 70d0e0b70e80..39271dd209ca 100644
--- a/drivers/iio/frequency/Makefile
+++ b/drivers/iio/frequency/Makefile
@@ -5,6 +5,7 @@
# When adding new entries keep the list in alphabetical order
obj-$(CONFIG_AD9523) += ad9523.o
+obj-$(CONFIG_AD9910) += ad9910.o
obj-$(CONFIG_ADF4350) += adf4350.o
obj-$(CONFIG_ADF4371) += adf4371.o
obj-$(CONFIG_ADF4377) += adf4377.o
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
new file mode 100644
index 000000000000..82b817c05975
--- /dev/null
+++ b/drivers/iio/frequency/ad9910.c
@@ -0,0 +1,931 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD9910 SPI DDS (Direct Digital Synthesizer) driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/log2.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/units.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+/* Register addresses */
+#define AD9910_REG_CFR1 0x00
+#define AD9910_REG_CFR2 0x01
+#define AD9910_REG_CFR3 0x02
+#define AD9910_REG_AUX_DAC 0x03
+#define AD9910_REG_IO_UPDATE_RATE 0x04
+#define AD9910_REG_FTW 0x07
+#define AD9910_REG_POW 0x08
+#define AD9910_REG_ASF 0x09
+#define AD9910_REG_MULTICHIP_SYNC 0x0A
+#define AD9910_REG_DRG_LIMIT 0x0B
+#define AD9910_REG_DRG_STEP 0x0C
+#define AD9910_REG_DRG_RATE 0x0D
+#define AD9910_REG_PROFILE0 0x0E
+#define AD9910_REG_PROFILE1 0x0F
+#define AD9910_REG_PROFILE2 0x10
+#define AD9910_REG_PROFILE3 0x11
+#define AD9910_REG_PROFILE4 0x12
+#define AD9910_REG_PROFILE5 0x13
+#define AD9910_REG_PROFILE6 0x14
+#define AD9910_REG_PROFILE7 0x15
+#define AD9910_REG_RAM 0x16
+
+#define AD9910_REG_NUM_CACHED 0x16
+
+#define AD9910_REG_PROFILE(x) (AD9910_REG_PROFILE0 + (x))
+#define AD9910_REG_HIGH32_FLAG 0x100
+#define AD9910_REG_HIGH32_MSK GENMASK_ULL(63, 32)
+#define AD9910_REG_LOW32_MSK GENMASK_ULL(31, 0)
+
+/* CFR1 bit definitions */
+#define AD9910_CFR1_RAM_ENABLE_MSK BIT(31)
+#define AD9910_CFR1_RAM_PLAYBACK_DEST_MSK GENMASK(30, 29)
+#define AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK BIT(23)
+#define AD9910_CFR1_INV_SINC_EN_MSK BIT(22)
+#define AD9910_CFR1_INT_PROFILE_CTL_MSK GENMASK(20, 17)
+#define AD9910_CFR1_SELECT_SINE_MSK BIT(16)
+#define AD9910_CFR1_LOAD_LRR_IO_UPDATE_MSK BIT(15)
+#define AD9910_CFR1_AUTOCLR_DIG_RAMP_ACCUM_MSK BIT(14)
+#define AD9910_CFR1_AUTOCLR_PHASE_ACCUM_MSK BIT(13)
+#define AD9910_CFR1_CLEAR_DIG_RAMP_ACCUM_MSK BIT(12)
+#define AD9910_CFR1_CLEAR_PHASE_ACCUM_MSK BIT(11)
+#define AD9910_CFR1_LOAD_ARR_IO_UPDATE_MSK BIT(10)
+#define AD9910_CFR1_OSK_ENABLE_MSK BIT(9)
+#define AD9910_CFR1_SELECT_AUTO_OSK_MSK BIT(8)
+#define AD9910_CFR1_DIGITAL_POWER_DOWN_MSK BIT(7)
+#define AD9910_CFR1_DAC_POWER_DOWN_MSK BIT(6)
+#define AD9910_CFR1_REFCLK_INPUT_POWER_DOWN_MSK BIT(5)
+#define AD9910_CFR1_AUX_DAC_POWER_DOWN_MSK BIT(4)
+#define AD9910_CFR1_SOFT_POWER_DOWN_MSK GENMASK(7, 4)
+#define AD9910_CFR1_EXT_POWER_DOWN_CTL_MSK BIT(3)
+#define AD9910_CFR1_SDIO_INPUT_ONLY_MSK BIT(1)
+#define AD9910_CFR1_LSB_FIRST_MSK BIT(0)
+
+/* CFR2 bit definitions */
+#define AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK BIT(24)
+#define AD9910_CFR2_INTERNAL_IO_UPDATE_MSK BIT(23)
+#define AD9910_CFR2_SYNC_CLK_EN_MSK BIT(22)
+#define AD9910_CFR2_DRG_DEST_MSK GENMASK(21, 20)
+#define AD9910_CFR2_DRG_ENABLE_MSK BIT(19)
+#define AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK BIT(18)
+#define AD9910_CFR2_DRG_NO_DWELL_LOW_MSK BIT(17)
+#define AD9910_CFR2_DRG_NO_DWELL_MSK GENMASK(18, 17)
+#define AD9910_CFR2_READ_EFFECTIVE_FTW_MSK BIT(16)
+#define AD9910_CFR2_IO_UPDATE_RATE_CTL_MSK GENMASK(15, 14)
+#define AD9910_CFR2_PDCLK_ENABLE_MSK BIT(11)
+#define AD9910_CFR2_PDCLK_INVERT_MSK BIT(10)
+#define AD9910_CFR2_TXENABLE_INVERT_MSK BIT(9)
+#define AD9910_CFR2_MATCHED_LATENCY_EN_MSK BIT(7)
+#define AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK BIT(6)
+#define AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK BIT(5)
+#define AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK BIT(4)
+#define AD9910_CFR2_FM_GAIN_MSK GENMASK(3, 0)
+
+/* CFR3 bit definitions */
+#define AD9910_CFR3_OPEN_MSK 0x08070000
+#define AD9910_CFR3_DRV0_MSK GENMASK(29, 28)
+#define AD9910_CFR3_VCO_SEL_MSK GENMASK(26, 24)
+#define AD9910_CFR3_ICP_MSK GENMASK(21, 19)
+#define AD9910_CFR3_REFCLK_DIV_BYPASS_MSK BIT(15)
+#define AD9910_CFR3_REFCLK_DIV_RESETB_MSK BIT(14)
+#define AD9910_CFR3_PFD_RESET_MSK BIT(10)
+#define AD9910_CFR3_PLL_EN_MSK BIT(8)
+#define AD9910_CFR3_N_MSK GENMASK(7, 1)
+
+/* Auxiliary DAC Control Register Bits */
+#define AD9910_AUX_DAC_FSC_MSK GENMASK(7, 0)
+
+/* ASF Register Bits */
+#define AD9910_ASF_RAMP_RATE_MSK GENMASK(31, 16)
+#define AD9910_ASF_SCALE_FACTOR_MSK GENMASK(15, 2)
+#define AD9910_ASF_STEP_SIZE_MSK GENMASK(1, 0)
+
+/* Multichip Sync Register Bits */
+#define AD9910_MC_SYNC_VALIDATION_DELAY_MSK GENMASK(31, 28)
+#define AD9910_MC_SYNC_RECEIVER_ENABLE_MSK BIT(27)
+#define AD9910_MC_SYNC_GENERATOR_ENABLE_MSK BIT(26)
+#define AD9910_MC_SYNC_GENERATOR_POLARITY_MSK BIT(25)
+#define AD9910_MC_SYNC_STATE_PRESET_MSK GENMASK(23, 18)
+#define AD9910_MC_SYNC_OUTPUT_DELAY_MSK GENMASK(15, 11)
+#define AD9910_MC_SYNC_INPUT_DELAY_MSK GENMASK(7, 3)
+
+/* Profile Register Format (Single Tone Mode) */
+#define AD9910_PROFILE_ST_ASF_MSK GENMASK_ULL(61, 48)
+#define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32)
+#define AD9910_PROFILE_ST_FTW_MSK AD9910_REG_LOW32_MSK
+
+/* Device constants */
+#define AD9910_PI_NANORAD 3141592653UL
+
+#define AD9910_MAX_SYSCLK_HZ (1000 * HZ_PER_MHZ)
+#define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500)
+
+#define AD9910_ASF_MAX (BIT(14) - 1)
+#define AD9910_POW_MAX (BIT(16) - 1)
+#define AD9910_NUM_PROFILES 8
+
+/* PLL constants */
+#define AD9910_PLL_MIN_N 12
+#define AD9910_PLL_MAX_N 127
+
+#define AD9910_PLL_IN_MIN_FREQ_HZ (3200 * HZ_PER_KHZ)
+#define AD9910_PLL_IN_MAX_FREQ_HZ (60 * HZ_PER_MHZ)
+
+#define AD9910_PLL_OUT_MIN_FREQ_HZ (420 * HZ_PER_MHZ)
+#define AD9910_PLL_OUT_MAX_FREQ_HZ (1000 * HZ_PER_MHZ)
+
+#define AD9910_VCO0_RANGE_AUTO_MAX_HZ (465 * HZ_PER_MHZ)
+#define AD9910_VCO1_RANGE_AUTO_MAX_HZ (545 * HZ_PER_MHZ)
+#define AD9910_VCO2_RANGE_AUTO_MAX_HZ (650 * HZ_PER_MHZ)
+#define AD9910_VCO3_RANGE_AUTO_MAX_HZ (790 * HZ_PER_MHZ)
+#define AD9910_VCO4_RANGE_AUTO_MAX_HZ (885 * HZ_PER_MHZ)
+#define AD9910_VCO_RANGE_NUM 6
+
+#define AD9910_REFCLK_OUT_DRV_DISABLED 0
+
+#define AD9910_ICP_MIN_uA 212
+#define AD9910_ICP_MAX_uA 387
+#define AD9910_ICP_STEP_uA 25
+
+#define AD9910_DAC_IOUT_MAX_uA 31590
+#define AD9910_DAC_IOUT_DEFAULT_uA 20070
+#define AD9910_DAC_IOUT_MIN_uA 8640
+
+#define AD9910_REFDIV2_MIN_FREQ_HZ (120 * HZ_PER_MHZ)
+#define AD9910_REFDIV2_MAX_FREQ_HZ (1900 * HZ_PER_MHZ)
+
+#define AD9910_SPI_DATA_IDX 1
+#define AD9910_SPI_DATA_LEN_MAX sizeof(__be64)
+#define AD9910_SPI_MESSAGE_LEN_MAX (AD9910_SPI_DATA_IDX + AD9910_SPI_DATA_LEN_MAX)
+#define AD9910_SPI_READ BIT(7)
+#define AD9910_SPI_ADDR_MASK GENMASK(4, 0)
+
+/**
+ * enum ad9910_channel - AD9910 channel identifiers in priority order
+ */
+enum ad9910_channel {
+ AD9910_CHANNEL_SINGLE_TONE,
+};
+
+enum {
+ AD9910_PROFILE,
+ AD9910_POWERDOWN,
+};
+
+struct ad9910_data {
+ u32 sysclk_freq_hz;
+ u32 dac_output_current;
+
+ /* PLL configuration */
+ u16 pll_charge_pump_current;
+ u8 pll_multiplier;
+ u8 pll_vco_range;
+
+ bool ref_div2_en;
+ u8 refclk_out_drv;
+
+ /* Feature flags */
+ bool inverse_sinc_enable;
+ bool sine_output_enable;
+ bool sync_clk_enable;
+ bool pdclk_enable;
+ bool pdclk_invert;
+ bool tx_enable_invert;
+};
+
+struct ad9910_state {
+ struct spi_device *spi;
+ struct clk *refclk;
+
+ struct gpio_desc *gpio_pwdown;
+ struct gpio_desc *gpio_update;
+ struct gpio_descs *gpio_profile;
+
+ /* cached registers */
+ union {
+ u64 val64;
+ u32 val32;
+ u16 val16;
+ } reg[AD9910_REG_NUM_CACHED];
+
+ /*
+ * Lock for accessing device registers and state variables.
+ */
+ struct mutex lock;
+
+ struct ad9910_data data;
+ u8 profile;
+
+ /*
+ * DMA (thus cache coherency maintenance) requires the transfer
+ * buffers to live in their own cache lines.
+ */
+ u8 buf[AD9910_SPI_MESSAGE_LEN_MAX] __aligned(IIO_DMA_MINALIGN);
+};
+
+static const char * const ad9910_power_supplies[] = {
+ "dvdd-io33", "avdd33", "dvdd18", "avdd18",
+};
+
+static const char * const ad9910_refclk_out_drv0[] = {
+ "disabled", "low", "medium", "high",
+};
+
+/**
+ * ad9910_rational_scale() - Perform scaling of input given a reference.
+ * @input: The input value to be scaled.
+ * @scale: The numerator of the scaling factor.
+ * @reference: The denominator of the scaling factor.
+ *
+ * Closest rounding with mul_u64_add_u64_div_u64
+ *
+ * Return: The scaled value.
+ */
+#define ad9910_rational_scale(input, scale, reference) ({ \
+ u64 _tmp = (reference); \
+ mul_u64_add_u64_div_u64(input, scale, _tmp >> 1, _tmp); \
+})
+
+static int ad9910_io_update(struct ad9910_state *st)
+{
+ if (st->gpio_update) {
+ gpiod_set_value_cansleep(st->gpio_update, 1);
+ udelay(1);
+ gpiod_set_value_cansleep(st->gpio_update, 0);
+ }
+
+ return 0;
+}
+
+static inline int ad9910_spi_read(struct ad9910_state *st, u8 reg, size_t len)
+{
+ st->buf[0] = AD9910_SPI_READ | (reg & AD9910_SPI_ADDR_MASK);
+ return spi_write_then_read(st->spi, &st->buf[0], 1,
+ &st->buf[AD9910_SPI_DATA_IDX], len);
+}
+
+static inline int ad9910_spi_write(struct ad9910_state *st, u8 reg, size_t len,
+ bool update)
+{
+ int ret;
+
+ st->buf[0] = reg & AD9910_SPI_ADDR_MASK;
+ ret = spi_write(st->spi, st->buf, AD9910_SPI_DATA_IDX + len);
+ if (!ret && update)
+ return ad9910_io_update(st);
+
+ return ret;
+}
+
+#define AD9910_REG_READ_FN(nb) \
+static inline int ad9910_reg##nb##_read(struct ad9910_state *st, \
+ u8 reg, u##nb * data) \
+{ \
+ int ret; \
+ \
+ ret = ad9910_spi_read(st, reg, sizeof(*data)); \
+ if (ret) \
+ return ret; \
+ \
+ *data = get_unaligned_be##nb(&st->buf[AD9910_SPI_DATA_IDX]); \
+ return ret; \
+}
+
+AD9910_REG_READ_FN(16)
+AD9910_REG_READ_FN(32)
+AD9910_REG_READ_FN(64)
+
+#define AD9910_REG_WRITE_FN(nb) \
+static inline int ad9910_reg##nb##_write(struct ad9910_state *st, \
+ u8 reg, u##nb data, \
+ bool update) \
+{ \
+ int ret; \
+ \
+ put_unaligned_be##nb(data, &st->buf[AD9910_SPI_DATA_IDX]); \
+ ret = ad9910_spi_write(st, reg, sizeof(data), update); \
+ if (ret) \
+ return ret; \
+ \
+ st->reg[reg].val##nb = data; \
+ return ret; \
+}
+
+AD9910_REG_WRITE_FN(16)
+AD9910_REG_WRITE_FN(32)
+AD9910_REG_WRITE_FN(64)
+
+#define AD9910_REG_UPDATE_FN(nb) \
+static int ad9910_reg##nb##_update(struct ad9910_state *st, \
+ u8 reg, u##nb mask, \
+ u##nb data, bool update) \
+{ \
+ u##nb reg_val = (st->reg[reg].val##nb & ~mask) | (data & mask); \
+ \
+ if (reg_val == st->reg[reg].val##nb && !update) \
+ return 0; \
+ \
+ return ad9910_reg##nb##_write(st, reg, reg_val, update); \
+}
+
+AD9910_REG_UPDATE_FN(16)
+AD9910_REG_UPDATE_FN(32)
+AD9910_REG_UPDATE_FN(64)
+
+static int ad9910_profile_set(struct ad9910_state *st, u8 profile)
+{
+ DECLARE_BITMAP(values, BITS_PER_TYPE(profile));
+
+ if (profile >= AD9910_NUM_PROFILES)
+ return -EINVAL;
+
+ st->profile = profile;
+ values[0] = profile;
+ gpiod_multi_set_value_cansleep(st->gpio_profile, values);
+ return 0;
+}
+
+static int ad9910_powerdown_set(struct ad9910_state *st, bool enable)
+{
+ return gpiod_set_value_cansleep(st->gpio_pwdown, enable);
+}
+
+static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int val;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_PROFILE:
+ val = st->profile;
+ break;
+ case AD9910_POWERDOWN:
+ val = !!FIELD_GET(AD9910_CFR1_SOFT_POWER_DOWN_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return iio_format_value(buf, IIO_VAL_INT, 1, &val);
+}
+
+static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+
+ u32 val32;
+ int ret;
+
+ ret = kstrtou32(buf, 10, &val32);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_PROFILE:
+ if (val32 >= AD9910_NUM_PROFILES)
+ return -EINVAL;
+ ret = ad9910_profile_set(st, val32);
+ break;
+ case AD9910_POWERDOWN:
+ val32 = val32 ? AD9910_CFR1_SOFT_POWER_DOWN_MSK : 0;
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SOFT_POWER_DOWN_MSK,
+ val32, true);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret ?: len;
+}
+
+#define AD9910_EXT_INFO(_name, _ident, _shared) { \
+ .name = _name, \
+ .read = ad9910_ext_info_read, \
+ .write = ad9910_ext_info_write, \
+ .private = _ident, \
+ .shared = _shared, \
+}
+
+static const struct iio_chan_spec_ext_info ad9910_shared_ext_info[] = {
+ AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SHARED_BY_TYPE),
+ AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SHARED_BY_TYPE),
+ { },
+};
+
+static const struct iio_chan_spec ad9910_channels[] = {
+ [AD9910_CHANNEL_SINGLE_TONE] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_SINGLE_TONE,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY) |
+ BIT(IIO_CHAN_INFO_PHASE) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .ext_info = ad9910_shared_ext_info,
+ },
+};
+
+static int ad9910_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u64 tmp64;
+ u32 tmp32;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ tmp64 = (u64)tmp32 * st->data.sysclk_freq_hz;
+ *val = upper_32_bits(tmp64);
+ *val2 = upper_32_bits((u64)lower_32_bits(tmp64) * MICRO);
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_PHASE:
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ tmp32 = ((u64)tmp32 * AD9910_MAX_PHASE_MICRORAD) >> 16;
+ *val = tmp32 / MICRO;
+ *val2 = tmp32 % MICRO;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ *val = 0;
+ *val2 = (u64)tmp32 * MICRO >> 14;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9910_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u64 tmp64;
+ u32 tmp32;
+ u16 tmp16;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ if (val < 0 || val >= st->data.sysclk_freq_hz / 2)
+ return -EINVAL;
+
+ tmp32 = ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32),
+ (u64)MICRO * st->data.sysclk_freq_hz);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_ST_FTW_MSK,
+ FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp32),
+ true);
+ case IIO_CHAN_INFO_PHASE:
+ tmp64 = (u64)val * MICRO + val2;
+ if (val < 0 || val2 < 0 || tmp64 >= AD9910_MAX_PHASE_MICRORAD)
+ return -EINVAL;
+
+ tmp32 = DIV_U64_ROUND_CLOSEST(tmp64 << 16, AD9910_MAX_PHASE_MICRORAD);
+ tmp16 = min(tmp32, AD9910_POW_MAX);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_ST_POW_MSK,
+ FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp16),
+ true);
+ case IIO_CHAN_INFO_SCALE:
+ if (val < 0 || val > 1 || (val == 1 && val2 > 0))
+ return -EINVAL;
+
+ tmp64 = ((u64)val * MICRO + val2) << 14;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, MICRO);
+ tmp16 = min(tmp64, AD9910_ASF_MAX);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_ST_ASF_MSK,
+ FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp16),
+ true);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ case IIO_CHAN_INFO_PHASE:
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9910_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int writeval,
+ unsigned int *readval)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int ret;
+ u64 tmp64;
+ u32 tmp32;
+ u16 tmp16;
+ bool high32 = !!(reg & AD9910_REG_HIGH32_FLAG);
+
+ reg &= ~AD9910_REG_HIGH32_FLAG;
+ if (reg >= AD9910_REG_RAM)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ switch (reg) {
+ case AD9910_REG_DRG_LIMIT:
+ case AD9910_REG_DRG_STEP:
+ case AD9910_REG_PROFILE0:
+ case AD9910_REG_PROFILE1:
+ case AD9910_REG_PROFILE2:
+ case AD9910_REG_PROFILE3:
+ case AD9910_REG_PROFILE4:
+ case AD9910_REG_PROFILE5:
+ case AD9910_REG_PROFILE6:
+ case AD9910_REG_PROFILE7:
+ if (readval) {
+ ret = ad9910_reg64_read(st, reg, &tmp64);
+ if (ret < 0)
+ return ret;
+
+ if (high32)
+ *readval = upper_32_bits(tmp64);
+ else
+ *readval = lower_32_bits(tmp64);
+ } else {
+ tmp64 = st->reg[reg].val64;
+ if (high32)
+ FIELD_MODIFY(AD9910_REG_HIGH32_MSK, &tmp64, writeval);
+ else
+ FIELD_MODIFY(AD9910_REG_LOW32_MSK, &tmp64, writeval);
+
+ return ad9910_reg64_write(st, reg, tmp64, true);
+ }
+ break;
+ case AD9910_REG_POW:
+ if (!readval)
+ return ad9910_reg16_write(st, reg, writeval, true);
+
+ ret = ad9910_reg16_read(st, reg, &tmp16);
+ if (ret < 0)
+ return ret;
+ *readval = tmp16;
+ break;
+ default:
+ if (!readval)
+ return ad9910_reg32_write(st, reg, writeval, true);
+
+ ret = ad9910_reg32_read(st, reg, &tmp32);
+ if (ret < 0)
+ return ret;
+ *readval = tmp32;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct iio_info ad9910_info = {
+ .read_raw = ad9910_read_raw,
+ .write_raw = ad9910_write_raw,
+ .write_raw_get_fmt = ad9910_write_raw_get_fmt,
+ .debugfs_reg_access = &ad9910_reg_access,
+};
+
+static int ad9910_set_dac_current(struct ad9910_state *st, bool update)
+{
+ u32 fsc_code;
+
+ /* FSC = (86.4 / Rset) * (1 + CODE/256) where Rset = 10k ohms */
+ fsc_code = DIV_ROUND_CLOSEST(st->data.dac_output_current, 90) - 96;
+ fsc_code &= 0xFFU;
+
+ return ad9910_reg32_write(st, AD9910_REG_AUX_DAC, fsc_code, update);
+}
+
+static int ad9910_cfg_sysclk(struct ad9910_state *st, bool update)
+{
+ u32 cp_index, cfr3 = AD9910_CFR3_OPEN_MSK;
+
+ cfr3 |= FIELD_PREP(AD9910_CFR3_DRV0_MSK, st->data.refclk_out_drv);
+ st->data.sysclk_freq_hz = clk_get_rate(st->refclk);
+
+ if (st->data.pll_multiplier) {
+ st->data.sysclk_freq_hz *= st->data.pll_multiplier;
+ if (st->data.sysclk_freq_hz < AD9910_PLL_OUT_MIN_FREQ_HZ ||
+ st->data.sysclk_freq_hz > AD9910_PLL_OUT_MAX_FREQ_HZ) {
+ dev_err(&st->spi->dev, "invalid vco frequency: %u Hz\n",
+ st->data.sysclk_freq_hz);
+ return -ERANGE;
+ }
+
+ if (st->data.pll_vco_range >= AD9910_VCO_RANGE_NUM) {
+ if (st->data.sysclk_freq_hz <= AD9910_VCO0_RANGE_AUTO_MAX_HZ)
+ st->data.pll_vco_range = 0;
+ else if (st->data.sysclk_freq_hz <= AD9910_VCO1_RANGE_AUTO_MAX_HZ)
+ st->data.pll_vco_range = 1;
+ else if (st->data.sysclk_freq_hz <= AD9910_VCO2_RANGE_AUTO_MAX_HZ)
+ st->data.pll_vco_range = 2;
+ else if (st->data.sysclk_freq_hz <= AD9910_VCO3_RANGE_AUTO_MAX_HZ)
+ st->data.pll_vco_range = 3;
+ else if (st->data.sysclk_freq_hz <= AD9910_VCO4_RANGE_AUTO_MAX_HZ)
+ st->data.pll_vco_range = 4;
+ else
+ st->data.pll_vco_range = 5;
+ dev_dbg(&st->spi->dev, "auto-selected VCO range: %u\n",
+ st->data.pll_vco_range);
+ }
+
+ cp_index = st->data.pll_charge_pump_current - AD9910_ICP_MIN_uA;
+ cp_index = DIV_ROUND_CLOSEST(cp_index, AD9910_ICP_STEP_uA);
+ cfr3 |= FIELD_PREP(AD9910_CFR3_VCO_SEL_MSK, st->data.pll_vco_range) |
+ FIELD_PREP(AD9910_CFR3_ICP_MSK, cp_index) |
+ FIELD_PREP(AD9910_CFR3_N_MSK, st->data.pll_multiplier) |
+ AD9910_CFR3_PLL_EN_MSK;
+ } else {
+ cfr3 |= AD9910_CFR3_VCO_SEL_MSK |
+ AD9910_CFR3_ICP_MSK |
+ AD9910_CFR3_REFCLK_DIV_RESETB_MSK |
+ FIELD_PREP(AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, !st->data.ref_div2_en) |
+ AD9910_CFR3_PFD_RESET_MSK;
+ if (st->data.ref_div2_en)
+ st->data.sysclk_freq_hz >>= 1;
+ }
+
+ return ad9910_reg32_write(st, AD9910_REG_CFR3, cfr3, update);
+}
+
+static int ad9910_parse_fw(struct ad9910_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ u32 tmp;
+ int ret;
+
+ ret = device_property_read_u32(dev, "adi,pll-multiplier", &tmp);
+ if (!ret) {
+ if (tmp < AD9910_PLL_MIN_N || tmp > AD9910_PLL_MAX_N)
+ return dev_err_probe(dev, -ERANGE,
+ "invalid PLL multiplier %u\n", tmp);
+ st->data.pll_multiplier = tmp;
+
+ tmp = AD9910_VCO_RANGE_NUM;
+ ret = device_property_read_u32(dev, "adi,pll-vco-select", &tmp);
+ if (!ret && tmp >= AD9910_VCO_RANGE_NUM)
+ return dev_err_probe(dev, -ERANGE,
+ "invalid VCO range: %u\n", tmp);
+ st->data.pll_vco_range = tmp;
+
+ tmp = AD9910_ICP_MAX_uA;
+ device_property_read_u32(dev, "adi,charge-pump-current-microamp", &tmp);
+ if (tmp < AD9910_ICP_MIN_uA || tmp > AD9910_ICP_MAX_uA)
+ return dev_err_probe(dev, -ERANGE,
+ "invalid charge pump current %u\n", tmp);
+ st->data.pll_charge_pump_current = tmp;
+
+ st->data.refclk_out_drv = AD9910_REFCLK_OUT_DRV_DISABLED;
+ ret = device_property_match_property_string(dev,
+ "adi,refclk-out-drive-strength",
+ ad9910_refclk_out_drv0,
+ ARRAY_SIZE(ad9910_refclk_out_drv0));
+ if (ret >= 0)
+ st->data.refclk_out_drv = ret;
+ }
+
+ st->data.ref_div2_en = device_property_read_bool(dev, "adi,reference-div2-enable");
+ st->data.inverse_sinc_enable = device_property_read_bool(dev, "adi,inverse-sinc-enable");
+ st->data.sine_output_enable = device_property_read_bool(dev, "adi,sine-output-enable");
+ st->data.sync_clk_enable = !device_property_read_bool(dev, "adi,sync-clk-disable");
+ st->data.pdclk_enable = !device_property_read_bool(dev, "adi,pdclk-disable");
+ st->data.pdclk_invert = device_property_read_bool(dev, "adi,pdclk-invert");
+ st->data.tx_enable_invert = device_property_read_bool(dev, "adi,tx-enable-invert");
+
+ tmp = AD9910_DAC_IOUT_DEFAULT_uA;
+ device_property_read_u32(dev, "adi,dac-output-current-microamp", &tmp);
+ if (tmp < AD9910_DAC_IOUT_MIN_uA || tmp > AD9910_DAC_IOUT_MAX_uA)
+ return dev_err_probe(dev, -ERANGE,
+ "Invalid DAC output current %u uA\n", tmp);
+ st->data.dac_output_current = tmp;
+
+ return 0;
+}
+
+static int ad9910_setup(struct ad9910_state *st, struct reset_control *dev_rst)
+{
+ u32 reg32;
+ int ret;
+
+ ret = reset_control_deassert(dev_rst);
+ if (ret)
+ return ret;
+
+ reg32 = AD9910_CFR1_SDIO_INPUT_ONLY_MSK;
+ reg32 |= FIELD_PREP(AD9910_CFR1_INV_SINC_EN_MSK, st->data.inverse_sinc_enable) |
+ FIELD_PREP(AD9910_CFR1_SELECT_SINE_MSK, st->data.sine_output_enable);
+
+ ret = ad9910_reg32_write(st, AD9910_REG_CFR1, reg32, false);
+ if (ret)
+ return ret;
+
+ reg32 = AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK;
+ reg32 |= AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK |
+ AD9910_CFR2_DRG_NO_DWELL_MSK |
+ AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK |
+ FIELD_PREP(AD9910_CFR2_SYNC_CLK_EN_MSK, st->data.sync_clk_enable) |
+ FIELD_PREP(AD9910_CFR2_PDCLK_ENABLE_MSK, st->data.pdclk_enable) |
+ FIELD_PREP(AD9910_CFR2_PDCLK_INVERT_MSK, st->data.pdclk_invert) |
+ FIELD_PREP(AD9910_CFR2_TXENABLE_INVERT_MSK, st->data.tx_enable_invert);
+
+ ret = ad9910_reg32_write(st, AD9910_REG_CFR2, reg32, false);
+ if (ret)
+ return ret;
+
+ ret = ad9910_cfg_sysclk(st, false);
+ if (ret)
+ return ret;
+
+ ret = ad9910_set_dac_current(st, false);
+ if (ret)
+ return ret;
+
+ return ad9910_io_update(st);
+}
+
+static void ad9910_power_down(void *data)
+{
+ struct ad9910_state *st = data;
+
+ if (!ad9910_powerdown_set(st, true))
+ return;
+
+ ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SOFT_POWER_DOWN_MSK,
+ AD9910_CFR1_SOFT_POWER_DOWN_MSK,
+ true);
+}
+
+static int ad9910_probe(struct spi_device *spi)
+{
+ struct reset_control *dev_rst, *io_rst;
+ struct gpio_desc *io_rst_gpio;
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ad9910_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+
+ spi_set_drvdata(spi, indio_dev);
+
+ st->refclk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(st->refclk))
+ return dev_err_probe(dev, PTR_ERR(st->refclk),
+ "Failed to get reference clock\n");
+
+ ret = devm_regulator_bulk_get_enable(dev,
+ ARRAY_SIZE(ad9910_power_supplies),
+ ad9910_power_supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get regulators\n");
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ indio_dev->name = "ad9910";
+ indio_dev->info = &ad9910_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ad9910_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ad9910_channels);
+
+ dev_rst = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(dev_rst))
+ return dev_err_probe(dev, PTR_ERR(dev_rst),
+ "failed to get device reset control\n");
+
+ ret = reset_control_assert(dev_rst);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to assert device reset control\n");
+
+ /*
+ * 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
+ * sure it is not floating. We can use either a reset controller or a
+ * GPIO for this.
+ */
+ io_rst = devm_reset_control_get_optional_exclusive_deasserted(dev, "io");
+ if (IS_ERR(io_rst))
+ return dev_err_probe(dev, PTR_ERR(io_rst),
+ "failed to get io reset control\n");
+
+ io_rst_gpio = devm_gpiod_get_index_optional(dev, "reset", 1,
+ GPIOD_OUT_LOW);
+ if (IS_ERR(io_rst_gpio))
+ return dev_err_probe(dev, PTR_ERR(io_rst_gpio),
+ "failed to get io reset gpio\n");
+
+ st->gpio_pwdown = devm_gpiod_get_optional(dev, "powerdown",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_pwdown))
+ return dev_err_probe(dev, PTR_ERR(st->gpio_pwdown),
+ "failed to get powerdown gpio\n");
+
+ st->gpio_update = devm_gpiod_get_optional(dev, "update", GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_update))
+ return dev_err_probe(dev, PTR_ERR(st->gpio_update),
+ "failed to get update gpio\n");
+
+ st->gpio_profile = devm_gpiod_get_array_optional(dev, "profile",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_profile))
+ return dev_err_probe(dev, PTR_ERR(st->gpio_profile),
+ "failed to get profile gpios\n");
+
+ ret = ad9910_parse_fw(st);
+ if (ret)
+ return ret;
+
+ ret = ad9910_setup(st, dev_rst);
+ if (ret)
+ return dev_err_probe(dev, ret, "device setup failed\n");
+
+ ret = devm_add_action_or_reset(dev, ad9910_power_down, st);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add power down action\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct spi_device_id ad9910_id[] = {
+ {"ad9910", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ad9910_id);
+
+static const struct of_device_id ad9910_of_match[] = {
+ { .compatible = "adi,ad9910" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad9910_of_match);
+
+static struct spi_driver ad9910_driver = {
+ .driver = {
+ .name = "ad9910",
+ .of_match_table = ad9910_of_match,
+ },
+ .probe = ad9910_probe,
+ .id_table = ad9910_id,
+};
+module_spi_driver(ad9910_driver);
+
+MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD9910 DDS driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread* Re: [PATCH RFC 2/8] iio: frequency: ad9910: initial driver implementation
2026-02-20 16:46 ` [PATCH RFC 2/8] iio: frequency: ad9910: initial driver implementation Rodrigo Alencar via B4 Relay
@ 2026-03-01 13:20 ` Jonathan Cameron
0 siblings, 0 replies; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-01 13:20 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Fri, 20 Feb 2026 16:46:06 +0000
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> Add the core AD9910 DDS driver infrastructure with single tone mode
> support. This includes SPI register access, profile management via
> GPIO pins, PLL/DAC configuration from firmware properties, and
> single tone frequency/phase/amplitude control through IIO channels.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
Fairly superficial review below.
Obviously lots of open questions on this driver but I'm still
trying to get a basic understanding on what is here.
Thanks,
Jonathan
> diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
> new file mode 100644
> index 000000000000..82b817c05975
> --- /dev/null
> +++ b/drivers/iio/frequency/ad9910.c
> @@ -0,0 +1,931 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * AD9910 SPI DDS (Direct Digital Synthesizer) driver
> + *
> + * Copyright 2026 Analog Devices Inc.
> + */
> +
> +/* Register addresses */
> +#define AD9910_REG_CFR1 0x00
> +#define AD9910_REG_CFR2 0x01
> +#define AD9910_REG_CFR3 0x02
> +#define AD9910_REG_AUX_DAC 0x03
> +#define AD9910_REG_IO_UPDATE_RATE 0x04
> +#define AD9910_REG_FTW 0x07
> +#define AD9910_REG_POW 0x08
> +#define AD9910_REG_ASF 0x09
> +#define AD9910_REG_MULTICHIP_SYNC 0x0A
> +#define AD9910_REG_DRG_LIMIT 0x0B
> +#define AD9910_REG_DRG_STEP 0x0C
> +#define AD9910_REG_DRG_RATE 0x0D
> +#define AD9910_REG_PROFILE0 0x0E
> +#define AD9910_REG_PROFILE1 0x0F
> +#define AD9910_REG_PROFILE2 0x10
> +#define AD9910_REG_PROFILE3 0x11
> +#define AD9910_REG_PROFILE4 0x12
> +#define AD9910_REG_PROFILE5 0x13
> +#define AD9910_REG_PROFILE6 0x14
> +#define AD9910_REG_PROFILE7 0x15
> +#define AD9910_REG_RAM 0x16
> +
> +#define AD9910_REG_NUM_CACHED 0x16
> +
> +#define AD9910_REG_PROFILE(x) (AD9910_REG_PROFILE0 + (x))
> +#define AD9910_REG_HIGH32_FLAG 0x100
> +#define AD9910_REG_HIGH32_MSK GENMASK_ULL(63, 32)
> +#define AD9910_REG_LOW32_MSK GENMASK_ULL(31, 0)
I'd rather see the GENMASK_ULL inline for these so it is really obvious
what is going on.
> +struct ad9910_state {
> + struct spi_device *spi;
> + struct clk *refclk;
> +
> + struct gpio_desc *gpio_pwdown;
> + struct gpio_desc *gpio_update;
> + struct gpio_descs *gpio_profile;
> +
> + /* cached registers */
> + union {
> + u64 val64;
> + u32 val32;
> + u16 val16;
> + } reg[AD9910_REG_NUM_CACHED];
> +
> + /*
> + * Lock for accessing device registers and state variables.
Single line comment should be fine here.
> + */
> + struct mutex lock;
> +
> + struct ad9910_data data;
> + u8 profile;
> +
> + /*
> + * DMA (thus cache coherency maintenance) requires the transfer
> + * buffers to live in their own cache lines.
> + */
> + u8 buf[AD9910_SPI_MESSAGE_LEN_MAX] __aligned(IIO_DMA_MINALIGN);
> +};
> +static inline int ad9910_spi_read(struct ad9910_state *st, u8 reg, size_t len)
> +{
> + st->buf[0] = AD9910_SPI_READ | (reg & AD9910_SPI_ADDR_MASK);
Use FIELD_PREP() for that reg & ... as then we don't have to care about the
shift of that mask (which I assume is 0).
> + return spi_write_then_read(st->spi, &st->buf[0], 1,
I'd use a pair of buffers. Tiny one for rx and the buf you have already for the
tx. As you are using spi_write_then_read() neither has to be dma safe
(will be bounced anyway). So the tx buffer can be on the stack.
I'm not that keen on the rx value having to be extracted from the storage
in st->buf[1] etc by the caller. I think it would be more elegant
to copy it out in here.
> + &st->buf[AD9910_SPI_DATA_IDX], len);
> +}
> +
> +static inline int ad9910_spi_write(struct ad9910_state *st, u8 reg, size_t len,
> + bool update)
> +{
> + int ret;
> +
> + st->buf[0] = reg & AD9910_SPI_ADDR_MASK;
> + ret = spi_write(st->spi, st->buf, AD9910_SPI_DATA_IDX + len);
The buffers are small so you can just use spi_write_then_read() here with
no need for DMA safe buffers (rx length can be 0).
> + if (!ret && update)
> + return ad9910_io_update(st);
> +
> + return ret;
> +}
> +
> +#define AD9910_REG_READ_FN(nb) \
> +static inline int ad9910_reg##nb##_read(struct ad9910_state *st, \
> + u8 reg, u##nb * data) \
> +{ \
> + int ret; \
> + \
> + ret = ad9910_spi_read(st, reg, sizeof(*data)); \
> + if (ret) \
> + return ret; \
> + \
> + *data = get_unaligned_be##nb(&st->buf[AD9910_SPI_DATA_IDX]); \
with the suggested split of the buffer storage above (into tx and rx)
this can then be aligned data.
I'm not that keen on this set of macros but a triple regmap solution isn't
really clean either.
> + return ret; \
> +}
> +
> +AD9910_REG_READ_FN(16)
> +AD9910_REG_READ_FN(32)
> +AD9910_REG_READ_FN(64)
> +
> +#define AD9910_EXT_INFO(_name, _ident, _shared) { \
> + .name = _name, \
> + .read = ad9910_ext_info_read, \
> + .write = ad9910_ext_info_write, \
> + .private = _ident, \
> + .shared = _shared, \
> +}
For me this macro doesn't feel worthwhile. I'd rather see the assignments below
even if it takes a few more lines.
> +
> +static const struct iio_chan_spec_ext_info ad9910_shared_ext_info[] = {
> + AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SHARED_BY_TYPE),
This needs documentation as new ABI. That will make it easier to discuss
how this might otherwise be done.
Something in Documentation/ABI/testing/sysfs-bus-iio-ad9910
> + AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SHARED_BY_TYPE),
> + { },
No trailing comma as it terminates this array.
> +};
> +static int ad9910_reg_access(struct iio_dev *indio_dev,
> + unsigned int reg,
> + unsigned int writeval,
> + unsigned int *readval)
> +{
> + struct ad9910_state *st = iio_priv(indio_dev);
> + int ret;
> + u64 tmp64;
> + u32 tmp32;
> + u16 tmp16;
> + bool high32 = !!(reg & AD9910_REG_HIGH32_FLAG);
I'd use FIELD_GET() but not really important.
This splitting 64 bit registers in two for the debugfs read/write interface
needs to be documented somewhere.
> +
> + reg &= ~AD9910_REG_HIGH32_FLAG;
> + if (reg >= AD9910_REG_RAM)
> + return -EINVAL;
> +
> + guard(mutex)(&st->lock);
> +
> + switch (reg) {
> + case AD9910_REG_DRG_LIMIT:
> + case AD9910_REG_DRG_STEP:
> + case AD9910_REG_PROFILE0:
> + case AD9910_REG_PROFILE1:
> + case AD9910_REG_PROFILE2:
> + case AD9910_REG_PROFILE3:
> + case AD9910_REG_PROFILE4:
> + case AD9910_REG_PROFILE5:
> + case AD9910_REG_PROFILE6:
> + case AD9910_REG_PROFILE7:
> + if (readval) {
> + ret = ad9910_reg64_read(st, reg, &tmp64);
> + if (ret < 0)
> + return ret;
> +
> + if (high32)
> + *readval = upper_32_bits(tmp64);
> + else
> + *readval = lower_32_bits(tmp64);
> + } else {
> + tmp64 = st->reg[reg].val64;
> + if (high32)
> + FIELD_MODIFY(AD9910_REG_HIGH32_MSK, &tmp64, writeval);
> + else
> + FIELD_MODIFY(AD9910_REG_LOW32_MSK, &tmp64, writeval);
> +
> + return ad9910_reg64_write(st, reg, tmp64, true);
> + }
> + break;
> + case AD9910_REG_POW:
> + if (!readval)
> + return ad9910_reg16_write(st, reg, writeval, true);
> +
> + ret = ad9910_reg16_read(st, reg, &tmp16);
> + if (ret < 0)
> + return ret;
> + *readval = tmp16;
> + break;
> + default:
> + if (!readval)
> + return ad9910_reg32_write(st, reg, writeval, true);
> +
> + ret = ad9910_reg32_read(st, reg, &tmp32);
> + if (ret < 0)
> + return ret;
> + *readval = tmp32;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int ad9910_probe(struct spi_device *spi)
> +{
> + struct reset_control *dev_rst, *io_rst;
> + struct gpio_desc *io_rst_gpio;
> + struct device *dev = &spi->dev;
> + struct iio_dev *indio_dev;
> + struct ad9910_state *st;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + st = iio_priv(indio_dev);
> + st->spi = spi;
> +
> + spi_set_drvdata(spi, indio_dev);
I'm not immediately spotting where this is used. If it is only needed
after some later patch, bring it in there.
> +
> + st->refclk = devm_clk_get_enabled(dev, NULL);
> + if (IS_ERR(st->refclk))
> + return dev_err_probe(dev, PTR_ERR(st->refclk),
> + "Failed to get reference clock\n");
...
> +
> +static const struct spi_device_id ad9910_id[] = {
> + {"ad9910", 0},
> + {}
{ "ad9910" },
{ }
Both because that's the preferred formatting for IIO and because
it's consistent with the of_device_id table below.
> +};
> +MODULE_DEVICE_TABLE(spi, ad9910_id);
> +
> +static const struct of_device_id ad9910_of_match[] = {
> + { .compatible = "adi,ad9910" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, ad9910_of_match);
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH RFC 3/8] iio: frequency: ad9910: add simple parallel port mode support
2026-02-20 16:46 [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
2026-02-20 16:46 ` [PATCH RFC 1/8] dt-bindings: iio: frequency: add ad9910 Rodrigo Alencar via B4 Relay
2026-02-20 16:46 ` [PATCH RFC 2/8] iio: frequency: ad9910: initial driver implementation Rodrigo Alencar via B4 Relay
@ 2026-02-20 16:46 ` Rodrigo Alencar via B4 Relay
2026-02-23 8:27 ` Andy Shevchenko
2026-03-01 13:22 ` Jonathan Cameron
2026-02-20 16:46 ` [PATCH RFC 4/8] iio: frequency: ad9910: expose sysclk_frequency device attribute Rodrigo Alencar via B4 Relay
` (5 subsequent siblings)
8 siblings, 2 replies; 46+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-02-20 16:46 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add parallel port channel with frequency scale, frequency offset, phase
offset, and amplitude offset extended attributes for configuring the
parallel data path.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/ad9910.c | 167 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 164 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 82b817c05975..bb280972e84c 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -115,9 +115,13 @@
/* Auxiliary DAC Control Register Bits */
#define AD9910_AUX_DAC_FSC_MSK GENMASK(7, 0)
+/* POW Register Bits */
+#define AD9910_POW_PP_LSB_MSK GENMASK(7, 0)
+
/* ASF Register Bits */
#define AD9910_ASF_RAMP_RATE_MSK GENMASK(31, 16)
#define AD9910_ASF_SCALE_FACTOR_MSK GENMASK(15, 2)
+#define AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK GENMASK(7, 2)
#define AD9910_ASF_STEP_SIZE_MSK GENMASK(1, 0)
/* Multichip Sync Register Bits */
@@ -141,7 +145,9 @@
#define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500)
#define AD9910_ASF_MAX (BIT(14) - 1)
+#define AD9910_ASF_PP_LSB_MAX (BIT(6) - 1)
#define AD9910_POW_MAX (BIT(16) - 1)
+#define AD9910_POW_PP_LSB_MAX (BIT(8) - 1)
#define AD9910_NUM_PROFILES 8
/* PLL constants */
@@ -185,11 +191,16 @@
*/
enum ad9910_channel {
AD9910_CHANNEL_SINGLE_TONE,
+ AD9910_CHANNEL_PARALLEL_PORT,
};
enum {
AD9910_PROFILE,
AD9910_POWERDOWN,
+ AD9910_PP_FREQ_SCALE,
+ AD9910_PP_FREQ_OFFSET,
+ AD9910_PP_PHASE_OFFSET,
+ AD9910_PP_AMP_OFFSET,
};
struct ad9910_data {
@@ -388,6 +399,10 @@ static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
val = !!FIELD_GET(AD9910_CFR1_SOFT_POWER_DOWN_MSK,
st->reg[AD9910_REG_CFR1].val32);
break;
+ case AD9910_PP_FREQ_SCALE:
+ val = BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
+ st->reg[AD9910_REG_CFR2].val32));
+ break;
default:
return -EINVAL;
}
@@ -423,6 +438,15 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
AD9910_CFR1_SOFT_POWER_DOWN_MSK,
val32, true);
break;
+ case AD9910_PP_FREQ_SCALE:
+ if (val32 > BIT(15) || !is_power_of_2(val32))
+ return -EINVAL;
+
+ val32 = FIELD_PREP(AD9910_CFR2_FM_GAIN_MSK, ilog2(val32));
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_FM_GAIN_MSK,
+ val32, true);
+ break;
default:
return -EINVAL;
}
@@ -430,20 +454,126 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
return ret ?: len;
}
-#define AD9910_EXT_INFO(_name, _ident, _shared) { \
+static ssize_t ad9910_pp_attrs_read(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int vals[2];
+ u32 tmp32;
+ u64 tmp64;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_PP_FREQ_OFFSET:
+ tmp64 = (u64)st->reg[AD9910_REG_FTW].val32 * st->data.sysclk_freq_hz;
+ vals[0] = upper_32_bits(tmp64);
+ vals[1] = upper_32_bits((u64)lower_32_bits(tmp64) * MICRO);
+ break;
+ case AD9910_PP_PHASE_OFFSET:
+ tmp32 = FIELD_GET(AD9910_POW_PP_LSB_MSK,
+ st->reg[AD9910_REG_POW].val16);
+ tmp32 = (tmp32 * AD9910_MAX_PHASE_MICRORAD) >> 16;
+ vals[0] = tmp32 / MICRO;
+ vals[1] = tmp32 % MICRO;
+ break;
+ case AD9910_PP_AMP_OFFSET:
+ tmp32 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ vals[0] = 0;
+ vals[1] = (u64)tmp32 * MICRO >> 14;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals), vals);
+}
+
+static ssize_t ad9910_pp_attrs_write(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int val, val2;
+ u32 tmp32;
+ int ret;
+
+ ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_PP_FREQ_OFFSET:
+ if (val < 0 || val >= st->data.sysclk_freq_hz / 2)
+ return -EINVAL;
+
+ tmp32 = ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32),
+ (u64)MICRO * st->data.sysclk_freq_hz);
+ ret = ad9910_reg32_write(st, AD9910_REG_FTW, tmp32, true);
+ break;
+ case AD9910_PP_PHASE_OFFSET:
+ if (val != 0 || val2 < 0 || val2 >= (AD9910_MAX_PHASE_MICRORAD >> 8))
+ return -EINVAL;
+
+ tmp32 = DIV_ROUND_CLOSEST((u32)val2 << 16, AD9910_MAX_PHASE_MICRORAD);
+ tmp32 = min(tmp32, AD9910_POW_PP_LSB_MAX);
+ tmp32 = FIELD_PREP(AD9910_POW_PP_LSB_MSK, tmp32);
+ ret = ad9910_reg16_update(st, AD9910_REG_POW,
+ AD9910_POW_PP_LSB_MSK,
+ tmp32, true);
+ break;
+ case AD9910_PP_AMP_OFFSET:
+ if (val != 0 || val2 < 0 || val2 >= (MICRO >> 8))
+ return -EINVAL;
+
+ tmp32 = DIV_ROUND_CLOSEST((u32)val2 << 14, MICRO);
+ tmp32 = min(tmp32, AD9910_ASF_PP_LSB_MAX);
+ tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, tmp32);
+ ret = ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
+ tmp32, true);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret ?: len;
+}
+
+#define AD9910_EXT_INFO_TMPL(_name, _ident, _shared, _fn_desc) { \
.name = _name, \
- .read = ad9910_ext_info_read, \
- .write = ad9910_ext_info_write, \
+ .read = ad9910_ ## _fn_desc ## _read, \
+ .write = ad9910_ ## _fn_desc ## _write, \
.private = _ident, \
.shared = _shared, \
}
+#define AD9910_EXT_INFO(_name, _ident, _shared) \
+ AD9910_EXT_INFO_TMPL(_name, _ident, _shared, ext_info)
+
+#define AD9910_PP_EXT_INFO(_name, _ident) \
+ AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, pp_attrs)
+
static const struct iio_chan_spec_ext_info ad9910_shared_ext_info[] = {
AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SHARED_BY_TYPE),
AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SHARED_BY_TYPE),
{ },
};
+static const struct iio_chan_spec_ext_info ad9910_pp_ext_info[] = {
+ AD9910_EXT_INFO("frequency_scale", AD9910_PP_FREQ_SCALE, IIO_SEPARATE),
+ AD9910_PP_EXT_INFO("frequency_offset", AD9910_PP_FREQ_OFFSET),
+ AD9910_PP_EXT_INFO("phase_offset", AD9910_PP_PHASE_OFFSET),
+ AD9910_PP_EXT_INFO("scale_offset", AD9910_PP_AMP_OFFSET),
+ { },
+};
+
static const struct iio_chan_spec ad9910_channels[] = {
[AD9910_CHANNEL_SINGLE_TONE] = {
.type = IIO_ALTVOLTAGE,
@@ -456,6 +586,14 @@ static const struct iio_chan_spec ad9910_channels[] = {
BIT(IIO_CHAN_INFO_SCALE),
.ext_info = ad9910_shared_ext_info,
},
+ [AD9910_CHANNEL_PARALLEL_PORT] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL_PORT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE),
+ .ext_info = ad9910_pp_ext_info,
+ },
};
static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -469,6 +607,16 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
guard(mutex)(&st->lock);
switch (info) {
+ case IIO_CHAN_INFO_ENABLE:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PARALLEL_PORT:
+ *val = FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return IIO_VAL_INT;
case IIO_CHAN_INFO_FREQUENCY:
tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
st->reg[AD9910_REG_PROFILE(st->profile)].val64);
@@ -506,6 +654,17 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
guard(mutex)(&st->lock);
switch (info) {
+ case IIO_CHAN_INFO_ENABLE:
+ val = val ? 1 : 0;
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PARALLEL_PORT:
+ tmp32 = FIELD_PREP(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, val);
+ return ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+ tmp32, true);
+ default:
+ return -EINVAL;
+ }
case IIO_CHAN_INFO_FREQUENCY:
if (val < 0 || val >= st->data.sysclk_freq_hz / 2)
return -EINVAL;
@@ -548,6 +707,8 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
long mask)
{
switch (mask) {
+ case IIO_CHAN_INFO_ENABLE:
+ return IIO_VAL_INT;
case IIO_CHAN_INFO_FREQUENCY:
case IIO_CHAN_INFO_PHASE:
case IIO_CHAN_INFO_SCALE:
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread* Re: [PATCH RFC 3/8] iio: frequency: ad9910: add simple parallel port mode support
2026-02-20 16:46 ` [PATCH RFC 3/8] iio: frequency: ad9910: add simple parallel port mode support Rodrigo Alencar via B4 Relay
@ 2026-02-23 8:27 ` Andy Shevchenko
2026-03-01 13:22 ` Jonathan Cameron
1 sibling, 0 replies; 46+ messages in thread
From: Andy Shevchenko @ 2026-02-23 8:27 UTC (permalink / raw)
To: rodrigo.alencar
Cc: linux-iio, devicetree, linux-kernel, Lars-Peter Clausen,
Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Fri, Feb 20, 2026 at 04:46:07PM +0000, Rodrigo Alencar via B4 Relay wrote:
> Add parallel port channel with frequency scale, frequency offset, phase
> offset, and amplitude offset extended attributes for configuring the
> parallel data path.
...
> + case AD9910_PP_FREQ_SCALE:
> + if (val32 > BIT(15) || !is_power_of_2(val32))
> + return -EINVAL;
> + val32 = FIELD_PREP(AD9910_CFR2_FM_GAIN_MSK, ilog2(val32));
My gut feelings here that this can be simplified to avoid some checks,
or make it more like an expression.
In any case this is an edge between code readability and the size of
the generated binary object.
> + ret = ad9910_reg32_update(st, AD9910_REG_CFR2,
> + AD9910_CFR2_FM_GAIN_MSK,
> + val32, true);
> + break;
...
> +static ssize_t ad9910_pp_attrs_read(struct iio_dev *indio_dev,
> + uintptr_t private,
Hmm... Is it a requirement to have uintptr_t? Linus was clear that this is
the type that shouldn't be used in the kernel. unsigned long does the same.
Jonathan, is there any plans to get rid of uintptr_t in IIO?
> + const struct iio_chan_spec *chan,
> + char *buf)
> +{
> + struct ad9910_state *st = iio_priv(indio_dev);
> + int vals[2];
> + u32 tmp32;
> + u64 tmp64;
> + guard(mutex)(&st->lock);
I don't see the need to have vals assignment followed by iio_format_value()
call be under the lock. OTOH, I understand that this makes code simple...
> + switch (private) {
> + case AD9910_PP_FREQ_OFFSET:
> + tmp64 = (u64)st->reg[AD9910_REG_FTW].val32 * st->data.sysclk_freq_hz;
> + vals[0] = upper_32_bits(tmp64);
> + vals[1] = upper_32_bits((u64)lower_32_bits(tmp64) * MICRO);
> + break;
> + case AD9910_PP_PHASE_OFFSET:
> + tmp32 = FIELD_GET(AD9910_POW_PP_LSB_MSK,
> + st->reg[AD9910_REG_POW].val16);
> + tmp32 = (tmp32 * AD9910_MAX_PHASE_MICRORAD) >> 16;
> + vals[0] = tmp32 / MICRO;
> + vals[1] = tmp32 % MICRO;
> + break;
> + case AD9910_PP_AMP_OFFSET:
> + tmp32 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
> + st->reg[AD9910_REG_ASF].val32);
> + vals[0] = 0;
> + vals[1] = (u64)tmp32 * MICRO >> 14;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals), vals);
> +}
...
> +static ssize_t ad9910_pp_attrs_write(struct iio_dev *indio_dev,
> + uintptr_t private,
> + const struct iio_chan_spec *chan,
> + const char *buf, size_t len)
> +{
> + struct ad9910_state *st = iio_priv(indio_dev);
> + int val, val2;
> + u32 tmp32;
> + int ret;
> +
> + ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
> + if (ret)
> + return ret;
> +
> + guard(mutex)(&st->lock);
> +
> + switch (private) {
> + case AD9910_PP_FREQ_OFFSET:
> + if (val < 0 || val >= st->data.sysclk_freq_hz / 2)
in_range() ?
> + return -EINVAL;
> +
> + tmp32 = ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32),
> + (u64)MICRO * st->data.sysclk_freq_hz);
> + ret = ad9910_reg32_write(st, AD9910_REG_FTW, tmp32, true);
> + break;
> + case AD9910_PP_PHASE_OFFSET:
> + if (val != 0 || val2 < 0 || val2 >= (AD9910_MAX_PHASE_MICRORAD >> 8))
if (val)
return -EINVAL;
if (!in_range(val2, ...))
> + return -EINVAL;
?
> + tmp32 = DIV_ROUND_CLOSEST((u32)val2 << 16, AD9910_MAX_PHASE_MICRORAD);
> + tmp32 = min(tmp32, AD9910_POW_PP_LSB_MAX);
> + tmp32 = FIELD_PREP(AD9910_POW_PP_LSB_MSK, tmp32);
> + ret = ad9910_reg16_update(st, AD9910_REG_POW,
> + AD9910_POW_PP_LSB_MSK,
> + tmp32, true);
> + break;
> + case AD9910_PP_AMP_OFFSET:
> + if (val != 0 || val2 < 0 || val2 >= (MICRO >> 8))
> + return -EINVAL;
> +
> + tmp32 = DIV_ROUND_CLOSEST((u32)val2 << 14, MICRO);
> + tmp32 = min(tmp32, AD9910_ASF_PP_LSB_MAX);
> + tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, tmp32);
> + ret = ad9910_reg32_update(st, AD9910_REG_ASF,
> + AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
> + tmp32, true);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return ret ?: len;
> +}
...
> +static const struct iio_chan_spec_ext_info ad9910_pp_ext_info[] = {
> + AD9910_EXT_INFO("frequency_scale", AD9910_PP_FREQ_SCALE, IIO_SEPARATE),
> + AD9910_PP_EXT_INFO("frequency_offset", AD9910_PP_FREQ_OFFSET),
> + AD9910_PP_EXT_INFO("phase_offset", AD9910_PP_PHASE_OFFSET),
> + AD9910_PP_EXT_INFO("scale_offset", AD9910_PP_AMP_OFFSET),
> + { },
Please, use IIO accepted style for terminator entry.
(Yes, for the consistency's sake it might require another cleanup patch.)
> +};
...
> + val = val ? 1 : 0;
Replaces this...
> + switch (chan->channel) {
> + case AD9910_CHANNEL_PARALLEL_PORT:
> + tmp32 = FIELD_PREP(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, val);
...by !!val here.
> + return ad9910_reg32_update(st, AD9910_REG_CFR2,
> + AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
> + tmp32, true);
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 46+ messages in thread* Re: [PATCH RFC 3/8] iio: frequency: ad9910: add simple parallel port mode support
2026-02-20 16:46 ` [PATCH RFC 3/8] iio: frequency: ad9910: add simple parallel port mode support Rodrigo Alencar via B4 Relay
2026-02-23 8:27 ` Andy Shevchenko
@ 2026-03-01 13:22 ` Jonathan Cameron
1 sibling, 0 replies; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-01 13:22 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Fri, 20 Feb 2026 16:46:07 +0000
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> Add parallel port channel with frequency scale, frequency offset, phase
> offset, and amplitude offset extended attributes for configuring the
> parallel data path.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
One trivial comment below.
> +#define AD9910_EXT_INFO(_name, _ident, _shared) \
> + AD9910_EXT_INFO_TMPL(_name, _ident, _shared, ext_info)
> +
> +#define AD9910_PP_EXT_INFO(_name, _ident) \
> + AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, pp_attrs)
> +
> static const struct iio_chan_spec_ext_info ad9910_shared_ext_info[] = {
> AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SHARED_BY_TYPE),
> AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SHARED_BY_TYPE),
> { },
> };
>
> +static const struct iio_chan_spec_ext_info ad9910_pp_ext_info[] = {
> + AD9910_EXT_INFO("frequency_scale", AD9910_PP_FREQ_SCALE, IIO_SEPARATE),
> + AD9910_PP_EXT_INFO("frequency_offset", AD9910_PP_FREQ_OFFSET),
> + AD9910_PP_EXT_INFO("phase_offset", AD9910_PP_PHASE_OFFSET),
> + AD9910_PP_EXT_INFO("scale_offset", AD9910_PP_AMP_OFFSET),
> + { },
No comma
> +};
> +
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH RFC 4/8] iio: frequency: ad9910: expose sysclk_frequency device attribute
2026-02-20 16:46 [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (2 preceding siblings ...)
2026-02-20 16:46 ` [PATCH RFC 3/8] iio: frequency: ad9910: add simple parallel port mode support Rodrigo Alencar via B4 Relay
@ 2026-02-20 16:46 ` Rodrigo Alencar via B4 Relay
2026-03-01 13:23 ` Jonathan Cameron
2026-02-20 16:46 ` [PATCH RFC 5/8] iio: frequency: ad9910: add digital ramp generator support Rodrigo Alencar via B4 Relay
` (4 subsequent siblings)
8 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-02-20 16:46 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add read-only sysclk_frequency sysfs attribute. This value is important
for userspace to calculate values to populate the Parallel Port or the
RAM data buffer.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/ad9910.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index bb280972e84c..a72e3685f676 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -789,10 +789,31 @@ static int ad9910_reg_access(struct iio_dev *indio_dev,
return ret;
}
+static ssize_t sysclk_frequency_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ad9910_state *st = iio_priv(dev_to_iio_dev(dev));
+
+ return sysfs_emit(buf, "%u\n", st->data.sysclk_freq_hz);
+}
+
+static IIO_DEVICE_ATTR_RO(sysclk_frequency, 0);
+
+static struct attribute *ad9910_attrs[] = {
+ &iio_dev_attr_sysclk_frequency.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group ad9910_attrs_group = {
+ .attrs = ad9910_attrs,
+};
+
static const struct iio_info ad9910_info = {
.read_raw = ad9910_read_raw,
.write_raw = ad9910_write_raw,
.write_raw_get_fmt = ad9910_write_raw_get_fmt,
+ .attrs = &ad9910_attrs_group,
.debugfs_reg_access = &ad9910_reg_access,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread* Re: [PATCH RFC 4/8] iio: frequency: ad9910: expose sysclk_frequency device attribute
2026-02-20 16:46 ` [PATCH RFC 4/8] iio: frequency: ad9910: expose sysclk_frequency device attribute Rodrigo Alencar via B4 Relay
@ 2026-03-01 13:23 ` Jonathan Cameron
0 siblings, 0 replies; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-01 13:23 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Fri, 20 Feb 2026 16:46:08 +0000
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> Add read-only sysclk_frequency sysfs attribute. This value is important
> for userspace to calculate values to populate the Parallel Port or the
> RAM data buffer.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
Can we not use sampling_frequency (kind of backwards but IIRC we've done it
before) to provide information on what the frequency of the DAC updating is?
Jonathan
> ---
> drivers/iio/frequency/ad9910.c | 21 +++++++++++++++++++++
> 1 file changed, 21 insertions(+)
>
> diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
> index bb280972e84c..a72e3685f676 100644
> --- a/drivers/iio/frequency/ad9910.c
> +++ b/drivers/iio/frequency/ad9910.c
> @@ -789,10 +789,31 @@ static int ad9910_reg_access(struct iio_dev *indio_dev,
> return ret;
> }
>
> +static ssize_t sysclk_frequency_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct ad9910_state *st = iio_priv(dev_to_iio_dev(dev));
> +
> + return sysfs_emit(buf, "%u\n", st->data.sysclk_freq_hz);
> +}
> +
> +static IIO_DEVICE_ATTR_RO(sysclk_frequency, 0);
> +
> +static struct attribute *ad9910_attrs[] = {
> + &iio_dev_attr_sysclk_frequency.dev_attr.attr,
> + NULL
> +};
> +
> +static const struct attribute_group ad9910_attrs_group = {
> + .attrs = ad9910_attrs,
> +};
> +
> static const struct iio_info ad9910_info = {
> .read_raw = ad9910_read_raw,
> .write_raw = ad9910_write_raw,
> .write_raw_get_fmt = ad9910_write_raw_get_fmt,
> + .attrs = &ad9910_attrs_group,
> .debugfs_reg_access = &ad9910_reg_access,
> };
>
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH RFC 5/8] iio: frequency: ad9910: add digital ramp generator support
2026-02-20 16:46 [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (3 preceding siblings ...)
2026-02-20 16:46 ` [PATCH RFC 4/8] iio: frequency: ad9910: expose sysclk_frequency device attribute Rodrigo Alencar via B4 Relay
@ 2026-02-20 16:46 ` Rodrigo Alencar via B4 Relay
2026-03-01 13:26 ` Jonathan Cameron
2026-02-20 16:46 ` [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support Rodrigo Alencar via B4 Relay
` (3 subsequent siblings)
8 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-02-20 16:46 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add DRG channel with destination selection (frequency, phase, or
amplitude), operating mode control, configurable upper/lower limits,
increment/decrement step sizes, and step rate settings for the digital
ramp generator.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/ad9910.c | 454 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 454 insertions(+)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index a72e3685f676..84698bf2dc4e 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -133,6 +133,18 @@
#define AD9910_MC_SYNC_OUTPUT_DELAY_MSK GENMASK(15, 11)
#define AD9910_MC_SYNC_INPUT_DELAY_MSK GENMASK(7, 3)
+/* Digital Ramp Limit Register */
+#define AD9910_DRG_LIMIT_UPPER_MSK AD9910_REG_HIGH32_MSK
+#define AD9910_DRG_LIMIT_LOWER_MSK AD9910_REG_LOW32_MSK
+
+/* Digital Ramp Step Register */
+#define AD9910_DRG_STEP_DEC_MSK AD9910_REG_HIGH32_MSK
+#define AD9910_DRG_STEP_INC_MSK AD9910_REG_LOW32_MSK
+
+/* Digital Ramp Rate Register */
+#define AD9910_DRG_RATE_DEC_MSK GENMASK(31, 16)
+#define AD9910_DRG_RATE_INC_MSK GENMASK(15, 0)
+
/* Profile Register Format (Single Tone Mode) */
#define AD9910_PROFILE_ST_ASF_MSK GENMASK_ULL(61, 48)
#define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32)
@@ -148,8 +160,11 @@
#define AD9910_ASF_PP_LSB_MAX (BIT(6) - 1)
#define AD9910_POW_MAX (BIT(16) - 1)
#define AD9910_POW_PP_LSB_MAX (BIT(8) - 1)
+#define AD9910_STEP_RATE_MAX (BIT(16) - 1)
#define AD9910_NUM_PROFILES 8
+#define AD9910_DRG_DEST_NUM 3
+
/* PLL constants */
#define AD9910_PLL_MIN_N 12
#define AD9910_PLL_MAX_N 127
@@ -192,6 +207,32 @@
enum ad9910_channel {
AD9910_CHANNEL_SINGLE_TONE,
AD9910_CHANNEL_PARALLEL_PORT,
+ AD9910_CHANNEL_DRG,
+};
+
+/**
+ * enum ad9910_destination - AD9910 DDS core parameter destination
+ */
+enum ad9910_destination {
+ AD9910_DEST_FREQUENCY,
+ AD9910_DEST_PHASE,
+ AD9910_DEST_AMPLITUDE,
+ AD9910_DEST_POLAR,
+};
+
+/**
+ * enum ad9910_drg_oper_mode - Digital Ramp Generator Operating Mode
+ *
+ * @AD9910_DRG_OPER_MODE_BIDIR: Normal Ramp Generation
+ * @AD9910_DRG_OPER_MODE_RAMP_DOWN: No-dwell Low only operation
+ * @AD9910_DRG_OPER_MODE_RAMP_UP: No-dwell High only operation
+ * @AD9910_DRG_OPER_MODE_BIDIR_CONT: Both No-dwell High/Low operation
+ */
+enum ad9910_drg_oper_mode {
+ AD9910_DRG_OPER_MODE_BIDIR,
+ AD9910_DRG_OPER_MODE_RAMP_DOWN,
+ AD9910_DRG_OPER_MODE_RAMP_UP,
+ AD9910_DRG_OPER_MODE_BIDIR_CONT,
};
enum {
@@ -201,6 +242,20 @@ enum {
AD9910_PP_FREQ_OFFSET,
AD9910_PP_PHASE_OFFSET,
AD9910_PP_AMP_OFFSET,
+ AD9910_DRG_FREQ_UPPER_LIMIT,
+ AD9910_DRG_FREQ_LOWER_LIMIT,
+ AD9910_DRG_FREQ_INC_STEP,
+ AD9910_DRG_FREQ_DEC_STEP,
+ AD9910_DRG_PHASE_UPPER_LIMIT,
+ AD9910_DRG_PHASE_LOWER_LIMIT,
+ AD9910_DRG_PHASE_INC_STEP,
+ AD9910_DRG_PHASE_DEC_STEP,
+ AD9910_DRG_AMP_UPPER_LIMIT,
+ AD9910_DRG_AMP_LOWER_LIMIT,
+ AD9910_DRG_AMP_INC_STEP,
+ AD9910_DRG_AMP_DEC_STEP,
+ AD9910_DRG_INC_STEP_RATE,
+ AD9910_DRG_DEC_STEP_RATE,
};
struct ad9910_data {
@@ -262,6 +317,20 @@ static const char * const ad9910_refclk_out_drv0[] = {
"disabled", "low", "medium", "high",
};
+static const char * const ad9910_destination_str[] = {
+ [AD9910_DEST_FREQUENCY] = "frequency",
+ [AD9910_DEST_PHASE] = "phase",
+ [AD9910_DEST_AMPLITUDE] = "amplitude",
+ [AD9910_DEST_POLAR] = "polar",
+};
+
+static const char * const ad9910_drg_oper_mode_str[] = {
+ [AD9910_DRG_OPER_MODE_BIDIR] = "bidirectional",
+ [AD9910_DRG_OPER_MODE_RAMP_DOWN] = "ramp_down",
+ [AD9910_DRG_OPER_MODE_RAMP_UP] = "ramp_up",
+ [AD9910_DRG_OPER_MODE_BIDIR_CONT] = "bidirectional_continuous",
+};
+
/**
* ad9910_rational_scale() - Perform scaling of input given a reference.
* @input: The input value to be scaled.
@@ -381,6 +450,66 @@ static int ad9910_powerdown_set(struct ad9910_state *st, bool enable)
return gpiod_set_value_cansleep(st->gpio_pwdown, enable);
}
+static int ad9910_chan_destination_set(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int val)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG:
+ return ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_DRG_DEST_MSK,
+ FIELD_PREP(AD9910_CFR2_DRG_DEST_MSK, val),
+ true);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9910_chan_destination_get(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG:
+ return FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9910_drg_oper_mode_set(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int val)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ return ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_DRG_NO_DWELL_MSK,
+ FIELD_PREP(AD9910_CFR2_DRG_NO_DWELL_MSK, val),
+ true);
+}
+
+static int ad9910_drg_oper_mode_get(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ return FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+}
+
static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
uintptr_t private,
const struct iio_chan_spec *chan,
@@ -546,6 +675,264 @@ static ssize_t ad9910_pp_attrs_write(struct iio_dev *indio_dev,
return ret ?: len;
}
+static ssize_t ad9910_step_rate_read(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int vals[2];
+ u32 tmp32;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_DRG_INC_STEP_RATE:
+ tmp32 = FIELD_GET(AD9910_DRG_RATE_INC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ break;
+ case AD9910_DRG_DEC_STEP_RATE:
+ tmp32 = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (tmp32 == 0)
+ return -ERANGE;
+
+ tmp32 *= 4;
+ vals[0] = st->data.sysclk_freq_hz / tmp32;
+ vals[1] = div_u64((u64)(st->data.sysclk_freq_hz % tmp32) * MICRO, tmp32);
+
+ return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals), vals);
+}
+
+static ssize_t ad9910_step_rate_write(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int val, val2;
+ u64 rate_val;
+ u64 sysclk_uhz;
+ int ret;
+
+ ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
+ if (ret)
+ return ret;
+
+ sysclk_uhz = (u64)st->data.sysclk_freq_hz * MICROHZ_PER_HZ;
+ rate_val = ((u64)val * MICROHZ_PER_HZ + val2) * 4;
+ if (rate_val == 0 || rate_val > sysclk_uhz)
+ return -EINVAL;
+
+ rate_val = min(DIV64_U64_ROUND_CLOSEST(sysclk_uhz, rate_val),
+ AD9910_STEP_RATE_MAX);
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_DRG_INC_STEP_RATE:
+ ret = ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
+ AD9910_DRG_RATE_INC_MSK,
+ FIELD_PREP(AD9910_DRG_RATE_INC_MSK, rate_val),
+ true);
+ break;
+ case AD9910_DRG_DEC_STEP_RATE:
+ ret = ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
+ AD9910_DRG_RATE_DEC_MSK,
+ FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, rate_val), true);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret ?: len;
+}
+
+static ssize_t ad9910_drg_attrs_read(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ unsigned int type;
+ int vals[2];
+ u64 tmp64;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_DRG_FREQ_UPPER_LIMIT:
+ case AD9910_DRG_PHASE_UPPER_LIMIT:
+ case AD9910_DRG_AMP_UPPER_LIMIT:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ break;
+ case AD9910_DRG_FREQ_LOWER_LIMIT:
+ case AD9910_DRG_PHASE_LOWER_LIMIT:
+ case AD9910_DRG_AMP_LOWER_LIMIT:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ break;
+ case AD9910_DRG_FREQ_INC_STEP:
+ case AD9910_DRG_PHASE_INC_STEP:
+ case AD9910_DRG_AMP_INC_STEP:
+ tmp64 = FIELD_GET(AD9910_DRG_STEP_INC_MSK,
+ st->reg[AD9910_REG_DRG_STEP].val64);
+ break;
+ case AD9910_DRG_FREQ_DEC_STEP:
+ case AD9910_DRG_PHASE_DEC_STEP:
+ case AD9910_DRG_AMP_DEC_STEP:
+ tmp64 = FIELD_GET(AD9910_DRG_STEP_DEC_MSK,
+ st->reg[AD9910_REG_DRG_STEP].val64);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (private) {
+ case AD9910_DRG_FREQ_UPPER_LIMIT:
+ case AD9910_DRG_FREQ_LOWER_LIMIT:
+ case AD9910_DRG_FREQ_INC_STEP:
+ case AD9910_DRG_FREQ_DEC_STEP:
+ type = IIO_VAL_INT_PLUS_MICRO;
+ tmp64 *= st->data.sysclk_freq_hz;
+ vals[0] = upper_32_bits(tmp64);
+ vals[1] = upper_32_bits((u64)lower_32_bits(tmp64) * MICRO);
+ break;
+ case AD9910_DRG_PHASE_UPPER_LIMIT:
+ case AD9910_DRG_PHASE_LOWER_LIMIT:
+ case AD9910_DRG_PHASE_INC_STEP:
+ case AD9910_DRG_PHASE_DEC_STEP:
+ type = IIO_VAL_INT_PLUS_NANO;
+ tmp64 *= AD9910_PI_NANORAD;
+ tmp64 >>= 31;
+ vals[0] = div_u64_rem(tmp64, NANO, &vals[1]);
+ break;
+ case AD9910_DRG_AMP_UPPER_LIMIT:
+ case AD9910_DRG_AMP_LOWER_LIMIT:
+ case AD9910_DRG_AMP_INC_STEP:
+ case AD9910_DRG_AMP_DEC_STEP:
+ type = IIO_VAL_INT_PLUS_NANO;
+ vals[0] = 0;
+ vals[1] = tmp64 * NANO >> 32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return iio_format_value(buf, type, ARRAY_SIZE(vals), vals);
+}
+
+static ssize_t ad9910_drg_attrs_write(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int val, val2;
+ u64 tmp64;
+ int ret;
+
+ switch (private) {
+ case AD9910_DRG_FREQ_UPPER_LIMIT:
+ case AD9910_DRG_FREQ_LOWER_LIMIT:
+ case AD9910_DRG_FREQ_INC_STEP:
+ case AD9910_DRG_FREQ_DEC_STEP:
+ ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
+ if (ret)
+ return ret;
+
+ if (val >= st->data.sysclk_freq_hz / 2)
+ return -EINVAL;
+
+ tmp64 = (u64)val * MICRO + val2;
+ tmp64 = ad9910_rational_scale(tmp64, BIT_ULL(32),
+ (u64)MICRO * st->data.sysclk_freq_hz);
+ break;
+ case AD9910_DRG_PHASE_UPPER_LIMIT:
+ case AD9910_DRG_PHASE_LOWER_LIMIT:
+ case AD9910_DRG_PHASE_INC_STEP:
+ case AD9910_DRG_PHASE_DEC_STEP:
+ ret = iio_str_to_fixpoint(buf, NANO / 10, &val, &val2);
+ if (ret)
+ return ret;
+
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ tmp64 = (u64)val * NANO + val2;
+ if (tmp64 > 2ULL * AD9910_PI_NANORAD)
+ return -EINVAL;
+
+ tmp64 <<= 31;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_PI_NANORAD);
+ break;
+ case AD9910_DRG_AMP_UPPER_LIMIT:
+ case AD9910_DRG_AMP_LOWER_LIMIT:
+ case AD9910_DRG_AMP_INC_STEP:
+ case AD9910_DRG_AMP_DEC_STEP:
+ ret = iio_str_to_fixpoint(buf, NANO / 10, &val, &val2);
+ if (ret)
+ return ret;
+
+ if (val < 0 || val > 1 || (val == 1 && val2 > 0))
+ return -EINVAL;
+
+ tmp64 = ((u64)val * NANO + val2) << 32;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, NANO);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ tmp64 = min(tmp64, U32_MAX);
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_DRG_FREQ_UPPER_LIMIT:
+ case AD9910_DRG_PHASE_UPPER_LIMIT:
+ case AD9910_DRG_AMP_UPPER_LIMIT:
+ ret = ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_UPPER_MSK,
+ FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64),
+ true);
+ break;
+ case AD9910_DRG_FREQ_LOWER_LIMIT:
+ case AD9910_DRG_PHASE_LOWER_LIMIT:
+ case AD9910_DRG_AMP_LOWER_LIMIT:
+ ret = ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_LOWER_MSK,
+ FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64),
+ true);
+ break;
+ case AD9910_DRG_FREQ_INC_STEP:
+ case AD9910_DRG_PHASE_INC_STEP:
+ case AD9910_DRG_AMP_INC_STEP:
+ ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP,
+ AD9910_DRG_STEP_INC_MSK,
+ FIELD_PREP(AD9910_DRG_STEP_INC_MSK, tmp64),
+ true);
+ break;
+ case AD9910_DRG_FREQ_DEC_STEP:
+ case AD9910_DRG_PHASE_DEC_STEP:
+ case AD9910_DRG_AMP_DEC_STEP:
+ ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP,
+ AD9910_DRG_STEP_DEC_MSK,
+ FIELD_PREP(AD9910_DRG_STEP_DEC_MSK, tmp64),
+ true);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret ?: len;
+}
+
#define AD9910_EXT_INFO_TMPL(_name, _ident, _shared, _fn_desc) { \
.name = _name, \
.read = ad9910_ ## _fn_desc ## _read, \
@@ -560,6 +947,26 @@ static ssize_t ad9910_pp_attrs_write(struct iio_dev *indio_dev,
#define AD9910_PP_EXT_INFO(_name, _ident) \
AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, pp_attrs)
+#define AD9910_STEP_RATE_EXT_INFO(_name, _ident) \
+ AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, step_rate)
+
+#define AD9910_DRG_EXT_INFO(_name, _ident) \
+ AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, drg_attrs)
+
+static const struct iio_enum ad9910_drg_destination_enum = {
+ .items = ad9910_destination_str,
+ .num_items = AD9910_DRG_DEST_NUM,
+ .set = ad9910_chan_destination_set,
+ .get = ad9910_chan_destination_get,
+};
+
+static const struct iio_enum ad9910_drg_oper_mode_enum = {
+ .items = ad9910_drg_oper_mode_str,
+ .num_items = ARRAY_SIZE(ad9910_drg_oper_mode_str),
+ .set = ad9910_drg_oper_mode_set,
+ .get = ad9910_drg_oper_mode_get,
+};
+
static const struct iio_chan_spec_ext_info ad9910_shared_ext_info[] = {
AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SHARED_BY_TYPE),
AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SHARED_BY_TYPE),
@@ -574,6 +981,28 @@ static const struct iio_chan_spec_ext_info ad9910_pp_ext_info[] = {
{ },
};
+static const struct iio_chan_spec_ext_info ad9910_drg_ext_info[] = {
+ IIO_ENUM("destination", IIO_SEPARATE, &ad9910_drg_destination_enum),
+ IIO_ENUM_AVAILABLE("destination", IIO_SEPARATE, &ad9910_drg_destination_enum),
+ IIO_ENUM("operating_mode", IIO_SEPARATE, &ad9910_drg_oper_mode_enum),
+ IIO_ENUM_AVAILABLE("operating_mode", IIO_SEPARATE, &ad9910_drg_oper_mode_enum),
+ AD9910_DRG_EXT_INFO("frequency_max", AD9910_DRG_FREQ_UPPER_LIMIT),
+ AD9910_DRG_EXT_INFO("frequency_min", AD9910_DRG_FREQ_LOWER_LIMIT),
+ AD9910_DRG_EXT_INFO("frequency_increment", AD9910_DRG_FREQ_INC_STEP),
+ AD9910_DRG_EXT_INFO("frequency_decrement", AD9910_DRG_FREQ_DEC_STEP),
+ AD9910_DRG_EXT_INFO("phase_max", AD9910_DRG_PHASE_UPPER_LIMIT),
+ AD9910_DRG_EXT_INFO("phase_min", AD9910_DRG_PHASE_LOWER_LIMIT),
+ AD9910_DRG_EXT_INFO("phase_increment", AD9910_DRG_PHASE_INC_STEP),
+ AD9910_DRG_EXT_INFO("phase_decrement", AD9910_DRG_PHASE_DEC_STEP),
+ AD9910_DRG_EXT_INFO("scale_max", AD9910_DRG_AMP_UPPER_LIMIT),
+ AD9910_DRG_EXT_INFO("scale_min", AD9910_DRG_AMP_LOWER_LIMIT),
+ AD9910_DRG_EXT_INFO("scale_increment", AD9910_DRG_AMP_INC_STEP),
+ AD9910_DRG_EXT_INFO("scale_decrement", AD9910_DRG_AMP_DEC_STEP),
+ AD9910_STEP_RATE_EXT_INFO("increment_sampling_frequency", AD9910_DRG_INC_STEP_RATE),
+ AD9910_STEP_RATE_EXT_INFO("decrement_sampling_frequency", AD9910_DRG_DEC_STEP_RATE),
+ { },
+};
+
static const struct iio_chan_spec ad9910_channels[] = {
[AD9910_CHANNEL_SINGLE_TONE] = {
.type = IIO_ALTVOLTAGE,
@@ -594,6 +1023,15 @@ static const struct iio_chan_spec ad9910_channels[] = {
.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE),
.ext_info = ad9910_pp_ext_info,
},
+ [AD9910_CHANNEL_DRG] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE),
+ .ext_info = ad9910_drg_ext_info,
+ },
};
static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -613,6 +1051,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
*val = FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
st->reg[AD9910_REG_CFR2].val32);
break;
+ case AD9910_CHANNEL_DRG:
+ *val = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ break;
default:
return -EINVAL;
}
@@ -662,6 +1104,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg32_update(st, AD9910_REG_CFR2,
AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
tmp32, true);
+ case AD9910_CHANNEL_DRG:
+ tmp32 = FIELD_PREP(AD9910_CFR2_DRG_ENABLE_MSK, val);
+ return ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_DRG_ENABLE_MSK,
+ tmp32, true);
default:
return -EINVAL;
}
@@ -972,6 +1419,13 @@ static int ad9910_setup(struct ad9910_state *st, struct reset_control *dev_rst)
if (ret)
return ret;
+ /* configure step rate with default values */
+ reg32 = FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) |
+ FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1);
+ ret = ad9910_reg32_write(st, AD9910_REG_DRG_RATE, reg32, false);
+ if (ret)
+ return ret;
+
return ad9910_io_update(st);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread* Re: [PATCH RFC 5/8] iio: frequency: ad9910: add digital ramp generator support
2026-02-20 16:46 ` [PATCH RFC 5/8] iio: frequency: ad9910: add digital ramp generator support Rodrigo Alencar via B4 Relay
@ 2026-03-01 13:26 ` Jonathan Cameron
0 siblings, 0 replies; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-01 13:26 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Fri, 20 Feb 2026 16:46:09 +0000
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> Add DRG channel with destination selection (frequency, phase, or
> amplitude), operating mode control, configurable upper/lower limits,
> increment/decrement step sizes, and step rate settings for the digital
> ramp generator.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
There is a lot of custom ABI in here. All that needs description in
Documentation/ABI/testing before we an consider it properly.
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-02-20 16:46 [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (4 preceding siblings ...)
2026-02-20 16:46 ` [PATCH RFC 5/8] iio: frequency: ad9910: add digital ramp generator support Rodrigo Alencar via B4 Relay
@ 2026-02-20 16:46 ` Rodrigo Alencar via B4 Relay
2026-03-01 13:31 ` Jonathan Cameron
2026-02-20 16:46 ` [PATCH RFC 7/8] iio: frequency: ad9910: add output shift keying support Rodrigo Alencar via B4 Relay
` (2 subsequent siblings)
8 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-02-20 16:46 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add RAM channel with support for profile-based control. This includes:
- RAM data loading via binary sysfs attribute (ram_data);
- Per-profile RAM configuration (start/end address, step rate, operating
mode, dwell control);
- RAM destination control (frequency, phase, amplitude, polar);
- RAM operating modes (direct switch, ramp up, bidirectional ramp,
continuous bidirectional, continuous recirculate);
- Profile switching for RAM playback;
- Sampling frequency control via profile step rate;
- ram_en-aware read/write paths that redirect single tone
frequency/phase/amplitude access through reg_profile cache when RAM is
active;
When RAM is enabled, the DDS core parameters (frequency, phase, amplitude)
for the single tone channel are sourced from a shadow register cache
(reg_profile[]) since the profile registers are repurposed for RAM control.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/ad9910.c | 474 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 455 insertions(+), 19 deletions(-)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 84698bf2dc4e..8fd7ebe7e6b0 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -150,6 +150,15 @@
#define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32)
#define AD9910_PROFILE_ST_FTW_MSK AD9910_REG_LOW32_MSK
+/* Profile Register Format (RAM Mode) */
+#define AD9910_PROFILE_RAM_OPEN_MSK GENMASK_ULL(61, 57)
+#define AD9910_PROFILE_RAM_STEP_RATE_MSK GENMASK_ULL(55, 40)
+#define AD9910_PROFILE_RAM_END_ADDR_MSK GENMASK_ULL(39, 30)
+#define AD9910_PROFILE_RAM_START_ADDR_MSK GENMASK_ULL(23, 14)
+#define AD9910_PROFILE_RAM_NO_DWELL_HIGH_MSK BIT_ULL(5)
+#define AD9910_PROFILE_RAM_ZERO_CROSSING_MSK BIT_ULL(3)
+#define AD9910_PROFILE_RAM_MODE_CONTROL_MSK GENMASK_ULL(2, 0)
+
/* Device constants */
#define AD9910_PI_NANORAD 3141592653UL
@@ -164,6 +173,14 @@
#define AD9910_NUM_PROFILES 8
#define AD9910_DRG_DEST_NUM 3
+#define AD9910_RAM_DEST_NUM 4
+
+#define AD9910_RAM_SIZE_MAX_WORDS 1024
+#define AD9910_RAM_WORD_SIZE sizeof(u32)
+#define AD9910_RAM_SIZE_MAX_BYTES (AD9910_RAM_SIZE_MAX_WORDS * AD9910_RAM_WORD_SIZE)
+#define AD9910_RAM_ADDR_MAX (AD9910_RAM_SIZE_MAX_WORDS - 1)
+
+#define AD9910_RAM_PROFILE_CTL_CONT_MSK BIT(4)
/* PLL constants */
#define AD9910_PLL_MIN_N 12
@@ -208,6 +225,7 @@ enum ad9910_channel {
AD9910_CHANNEL_SINGLE_TONE,
AD9910_CHANNEL_PARALLEL_PORT,
AD9910_CHANNEL_DRG,
+ AD9910_CHANNEL_RAM,
};
/**
@@ -235,6 +253,27 @@ enum ad9910_drg_oper_mode {
AD9910_DRG_OPER_MODE_BIDIR_CONT,
};
+/**
+ * enum ad9910_ram_oper_mode - AD9910 RAM Playback Operating Mode
+ *
+ * @AD9910_RAM_MODE_DIRECT_SWITCH: Direct profile switching between profiles
+ * @AD9910_RAM_MODE_RAMP_UP: Ramp up for current profile
+ * @AD9910_RAM_MODE_BIDIR: Ramp up/down for profile 0
+ * @AD9910_RAM_MODE_BIDIR_CONT: Continuous ramp up/down for current profile
+ * @AD9910_RAM_MODE_RAMP_UP_CONT: Continuous ramp up for current profile
+ * @AD9910_RAM_MODE_SEQ: Sequenced playback of RAM profiles up to target profile
+ * @AD9910_RAM_MODE_SEQ_CONT: Continuous sequenced playback of RAM profiles
+ */
+enum ad9910_ram_oper_mode {
+ AD9910_RAM_MODE_DIRECT_SWITCH,
+ AD9910_RAM_MODE_RAMP_UP,
+ AD9910_RAM_MODE_BIDIR,
+ AD9910_RAM_MODE_BIDIR_CONT,
+ AD9910_RAM_MODE_RAMP_UP_CONT,
+ AD9910_RAM_MODE_SEQ,
+ AD9910_RAM_MODE_SEQ_CONT,
+};
+
enum {
AD9910_PROFILE,
AD9910_POWERDOWN,
@@ -256,6 +295,8 @@ enum {
AD9910_DRG_AMP_DEC_STEP,
AD9910_DRG_INC_STEP_RATE,
AD9910_DRG_DEC_STEP_RATE,
+ AD9910_RAM_START_ADDR,
+ AD9910_RAM_END_ADDR,
};
struct ad9910_data {
@@ -294,6 +335,13 @@ struct ad9910_state {
u16 val16;
} reg[AD9910_REG_NUM_CACHED];
+ /*
+ * alternate profile registers used to store RAM profile settings when
+ * RAM mode is disabled and Single Tone profile settings when RAM mode
+ * is enabled.
+ */
+ u64 reg_profile[AD9910_NUM_PROFILES];
+
/*
* Lock for accessing device registers and state variables.
*/
@@ -331,6 +379,16 @@ static const char * const ad9910_drg_oper_mode_str[] = {
[AD9910_DRG_OPER_MODE_BIDIR_CONT] = "bidirectional_continuous",
};
+static const char * const ad9910_ram_oper_mode_str[] = {
+ [AD9910_RAM_MODE_DIRECT_SWITCH] = "direct_switch",
+ [AD9910_RAM_MODE_RAMP_UP] = "ramp_up",
+ [AD9910_RAM_MODE_BIDIR] = "bidirectional",
+ [AD9910_RAM_MODE_BIDIR_CONT] = "bidirectional_continuous",
+ [AD9910_RAM_MODE_RAMP_UP_CONT] = "ramp_up_continuous",
+ [AD9910_RAM_MODE_SEQ] = "sequenced",
+ [AD9910_RAM_MODE_SEQ_CONT] = "sequenced_continuous",
+};
+
/**
* ad9910_rational_scale() - Perform scaling of input given a reference.
* @input: The input value to be scaled.
@@ -377,6 +435,18 @@ static inline int ad9910_spi_write(struct ad9910_state *st, u8 reg, size_t len,
return ret;
}
+static inline int ad9910_ram_load(struct ad9910_state *st, void *data,
+ size_t count)
+{
+ struct spi_transfer t[] = {
+ { .tx_buf = st->buf, .len = 1, },
+ { .tx_buf = data, .len = count, },
+ };
+
+ st->buf[0] = AD9910_REG_RAM;
+ return spi_sync_transfer(st->spi, t, ARRAY_SIZE(t));
+}
+
#define AD9910_REG_READ_FN(nb) \
static inline int ad9910_reg##nb##_read(struct ad9910_state *st, \
u8 reg, u##nb * data) \
@@ -464,6 +534,14 @@ static int ad9910_chan_destination_set(struct iio_dev *indio_dev,
AD9910_CFR2_DRG_DEST_MSK,
FIELD_PREP(AD9910_CFR2_DRG_DEST_MSK, val),
true);
+ case AD9910_CHANNEL_RAM:
+ if (FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32))
+ return -EBUSY;
+
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_RAM_PLAYBACK_DEST_MSK,
+ FIELD_PREP(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, val),
+ true);
default:
return -EINVAL;
}
@@ -480,6 +558,9 @@ static int ad9910_chan_destination_get(struct iio_dev *indio_dev,
case AD9910_CHANNEL_DRG:
return FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
st->reg[AD9910_REG_CFR2].val32);
+ case AD9910_CHANNEL_RAM:
+ return FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
default:
return -EINVAL;
}
@@ -510,6 +591,93 @@ static int ad9910_drg_oper_mode_get(struct iio_dev *indio_dev,
st->reg[AD9910_REG_CFR2].val32);
}
+static int ad9910_ram_oper_mode_set(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int val)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u32 profile_ctl;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ /*
+ * RAM sequenced modes use the internal profile control:
+ * - Sequence mode takes precedence over regular profile modes
+ * - Active profile defines the internal profile control target
+ * - Profile 0 cannot be used as sequenced mode target
+ * - Profile X cannot be set as sequenced mode target if another
+ * profile is currently set.
+ */
+ profile_ctl = FIELD_GET(AD9910_CFR1_INT_PROFILE_CTL_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (AD9910_RAM_PROFILE_CTL_CONT_MSK & profile_ctl)
+ profile_ctl = (profile_ctl & ~AD9910_RAM_PROFILE_CTL_CONT_MSK) + 1;
+
+ if (val >= AD9910_RAM_MODE_SEQ) {
+ if (!st->profile)
+ return -EINVAL;
+
+ if (profile_ctl && profile_ctl != st->profile)
+ return -EBUSY;
+
+ /* update profile control */
+ profile_ctl = st->profile;
+ if (val == AD9910_RAM_MODE_SEQ_CONT)
+ profile_ctl = AD9910_RAM_PROFILE_CTL_CONT_MSK | (profile_ctl - 1);
+ profile_ctl = FIELD_PREP(AD9910_CFR1_INT_PROFILE_CTL_MSK, profile_ctl);
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_INT_PROFILE_CTL_MSK,
+ profile_ctl, true);
+ }
+
+ if (profile_ctl && profile_ctl == st->profile) {
+ /* clear internal profile control */
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_INT_PROFILE_CTL_MSK,
+ 0, true);
+ if (ret)
+ return ret;
+ }
+
+ if (FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32))
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_RAM_MODE_CONTROL_MSK,
+ FIELD_PREP(AD9910_PROFILE_RAM_MODE_CONTROL_MSK, val),
+ true);
+
+ FIELD_MODIFY(AD9910_PROFILE_RAM_MODE_CONTROL_MSK,
+ &st->reg_profile[st->profile], val);
+ return 0;
+}
+
+static int ad9910_ram_oper_mode_get(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u32 profile_ctl;
+ bool seq_cont = false;
+
+ guard(mutex)(&st->lock);
+
+ profile_ctl = FIELD_GET(AD9910_CFR1_INT_PROFILE_CTL_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (AD9910_RAM_PROFILE_CTL_CONT_MSK & profile_ctl) {
+ seq_cont = true;
+ profile_ctl = (profile_ctl & ~AD9910_RAM_PROFILE_CTL_CONT_MSK) + 1;
+ }
+
+ if (profile_ctl && profile_ctl == st->profile)
+ return (seq_cont) ? AD9910_RAM_MODE_SEQ_CONT : AD9910_RAM_MODE_SEQ;
+
+ if (FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32))
+ return FIELD_GET(AD9910_PROFILE_RAM_MODE_CONTROL_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ else
+ return FIELD_GET(AD9910_PROFILE_RAM_MODE_CONTROL_MSK,
+ st->reg_profile[st->profile]);
+}
+
static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
uintptr_t private,
const struct iio_chan_spec *chan,
@@ -532,6 +700,22 @@ static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
val = BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
st->reg[AD9910_REG_CFR2].val32));
break;
+ case AD9910_RAM_START_ADDR:
+ if (FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32))
+ val = FIELD_GET(AD9910_PROFILE_RAM_START_ADDR_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ else
+ val = FIELD_GET(AD9910_PROFILE_RAM_START_ADDR_MSK,
+ st->reg_profile[st->profile]);
+ break;
+ case AD9910_RAM_END_ADDR:
+ if (FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32))
+ val = FIELD_GET(AD9910_PROFILE_RAM_END_ADDR_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ else
+ val = FIELD_GET(AD9910_PROFILE_RAM_END_ADDR_MSK,
+ st->reg_profile[st->profile]);
+ break;
default:
return -EINVAL;
}
@@ -576,6 +760,33 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
AD9910_CFR2_FM_GAIN_MSK,
val32, true);
break;
+ case AD9910_RAM_START_ADDR:
+ if (FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32))
+ return -EBUSY;
+
+ if (val32 > AD9910_RAM_ADDR_MAX)
+ return -EINVAL;
+
+ if (val32 > FIELD_GET(AD9910_PROFILE_RAM_END_ADDR_MSK,
+ st->reg_profile[st->profile]))
+ FIELD_MODIFY(AD9910_PROFILE_RAM_END_ADDR_MSK,
+ &st->reg_profile[st->profile], val32);
+
+ FIELD_MODIFY(AD9910_PROFILE_RAM_START_ADDR_MSK,
+ &st->reg_profile[st->profile], val32);
+ break;
+ case AD9910_RAM_END_ADDR:
+ if (FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32))
+ return -EBUSY;
+
+ if (val32 > AD9910_RAM_ADDR_MAX ||
+ val32 < FIELD_GET(AD9910_PROFILE_RAM_START_ADDR_MSK,
+ st->reg_profile[st->profile]))
+ return -EINVAL;
+
+ FIELD_MODIFY(AD9910_PROFILE_RAM_END_ADDR_MSK,
+ &st->reg_profile[st->profile], val32);
+ break;
default:
return -EINVAL;
}
@@ -967,6 +1178,20 @@ static const struct iio_enum ad9910_drg_oper_mode_enum = {
.get = ad9910_drg_oper_mode_get,
};
+static const struct iio_enum ad9910_ram_destination_enum = {
+ .items = ad9910_destination_str,
+ .num_items = AD9910_RAM_DEST_NUM,
+ .set = ad9910_chan_destination_set,
+ .get = ad9910_chan_destination_get,
+};
+
+static const struct iio_enum ad9910_ram_oper_mode_enum = {
+ .items = ad9910_ram_oper_mode_str,
+ .num_items = ARRAY_SIZE(ad9910_ram_oper_mode_str),
+ .set = ad9910_ram_oper_mode_set,
+ .get = ad9910_ram_oper_mode_get,
+};
+
static const struct iio_chan_spec_ext_info ad9910_shared_ext_info[] = {
AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SHARED_BY_TYPE),
AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SHARED_BY_TYPE),
@@ -1003,6 +1228,16 @@ static const struct iio_chan_spec_ext_info ad9910_drg_ext_info[] = {
{ },
};
+static const struct iio_chan_spec_ext_info ad9910_ram_ext_info[] = {
+ IIO_ENUM("destination", IIO_SEPARATE, &ad9910_ram_destination_enum),
+ IIO_ENUM_AVAILABLE("destination", IIO_SEPARATE, &ad9910_ram_destination_enum),
+ IIO_ENUM("operating_mode", IIO_SEPARATE, &ad9910_ram_oper_mode_enum),
+ IIO_ENUM_AVAILABLE("operating_mode", IIO_SEPARATE, &ad9910_ram_oper_mode_enum),
+ AD9910_EXT_INFO("address_start", AD9910_RAM_START_ADDR, IIO_SEPARATE),
+ AD9910_EXT_INFO("address_end", AD9910_RAM_END_ADDR, IIO_SEPARATE),
+ { },
+};
+
static const struct iio_chan_spec ad9910_channels[] = {
[AD9910_CHANNEL_SINGLE_TONE] = {
.type = IIO_ALTVOLTAGE,
@@ -1032,6 +1267,18 @@ static const struct iio_chan_spec ad9910_channels[] = {
.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE),
.ext_info = ad9910_drg_ext_info,
},
+ [AD9910_CHANNEL_RAM] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_RAM,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_FREQUENCY) |
+ BIT(IIO_CHAN_INFO_PHASE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_ram_ext_info,
+ },
};
static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -1040,10 +1287,13 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
{
struct ad9910_state *st = iio_priv(indio_dev);
u64 tmp64;
- u32 tmp32;
+ u32 tmp32, ram_en;
guard(mutex)(&st->lock);
+ ram_en = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+
switch (info) {
case IIO_CHAN_INFO_ENABLE:
switch (chan->channel) {
@@ -1055,30 +1305,77 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
*val = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
st->reg[AD9910_REG_CFR2].val32);
break;
+ case AD9910_CHANNEL_RAM:
+ *val = ram_en;
+ break;
default:
return -EINVAL;
}
return IIO_VAL_INT;
case IIO_CHAN_INFO_FREQUENCY:
- tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
- st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ if (chan->channel == AD9910_CHANNEL_SINGLE_TONE) {
+ if (!ram_en)
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ else
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
+ st->reg_profile[st->profile]);
+ } else {
+ tmp32 = st->reg[AD9910_REG_FTW].val32;
+ }
tmp64 = (u64)tmp32 * st->data.sysclk_freq_hz;
*val = upper_32_bits(tmp64);
*val2 = upper_32_bits((u64)lower_32_bits(tmp64) * MICRO);
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_PHASE:
- tmp32 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
- st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ if (chan->channel == AD9910_CHANNEL_SINGLE_TONE) {
+ if (!ram_en)
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ else
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
+ st->reg_profile[st->profile]);
+ } else {
+ tmp32 = st->reg[AD9910_REG_POW].val16;
+ }
tmp32 = ((u64)tmp32 * AD9910_MAX_PHASE_MICRORAD) >> 16;
*val = tmp32 / MICRO;
*val2 = tmp32 % MICRO;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_SCALE:
- tmp32 = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
- st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ if (chan->channel == AD9910_CHANNEL_SINGLE_TONE) {
+ if (!ram_en)
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ else
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
+ st->reg_profile[st->profile]);
+ } else {
+ tmp32 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ }
*val = 0;
*val2 = (u64)tmp32 * MICRO >> 14;
return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_RAM:
+ if (ram_en)
+ tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ else
+ tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK,
+ st->reg_profile[st->profile]);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (!tmp32)
+ return -ERANGE;
+ tmp32 *= 4;
+ *val = st->data.sysclk_freq_hz / tmp32;
+ *val2 = div_u64((u64)(st->data.sysclk_freq_hz % tmp32) * MICRO, tmp32);
+ return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
@@ -1092,9 +1389,13 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
u64 tmp64;
u32 tmp32;
u16 tmp16;
+ int ram_en, ret = 0;
guard(mutex)(&st->lock);
+ ram_en = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+
switch (info) {
case IIO_CHAN_INFO_ENABLE:
val = val ? 1 : 0;
@@ -1109,6 +1410,26 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg32_update(st, AD9910_REG_CFR2,
AD9910_CFR2_DRG_ENABLE_MSK,
tmp32, true);
+ case AD9910_CHANNEL_RAM:
+ if (ram_en == val)
+ return 0;
+
+ /* switch profile configs */
+ for (int i = 0; i < AD9910_NUM_PROFILES; i++) {
+ tmp64 = st->reg[AD9910_REG_PROFILE(i)].val64;
+ ret = ad9910_reg64_write(st,
+ AD9910_REG_PROFILE(i),
+ st->reg_profile[i],
+ false);
+ if (ret)
+ return ret;
+ st->reg_profile[i] = tmp64;
+ }
+
+ tmp32 = FIELD_PREP(AD9910_CFR1_RAM_ENABLE_MSK, val);
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_RAM_ENABLE_MSK,
+ tmp32, true);
default:
return -EINVAL;
}
@@ -1118,10 +1439,18 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
tmp32 = ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32),
(u64)MICRO * st->data.sysclk_freq_hz);
- return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
- AD9910_PROFILE_ST_FTW_MSK,
- FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp32),
- true);
+ if (chan->channel != AD9910_CHANNEL_SINGLE_TONE)
+ return ad9910_reg32_write(st, AD9910_REG_FTW, tmp32, true);
+
+ if (!ram_en)
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_ST_FTW_MSK,
+ FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp32),
+ true);
+
+ FIELD_MODIFY(AD9910_PROFILE_ST_FTW_MSK,
+ &st->reg_profile[st->profile], tmp32);
+ break;
case IIO_CHAN_INFO_PHASE:
tmp64 = (u64)val * MICRO + val2;
if (val < 0 || val2 < 0 || tmp64 >= AD9910_MAX_PHASE_MICRORAD)
@@ -1129,10 +1458,19 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
tmp32 = DIV_U64_ROUND_CLOSEST(tmp64 << 16, AD9910_MAX_PHASE_MICRORAD);
tmp16 = min(tmp32, AD9910_POW_MAX);
- return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
- AD9910_PROFILE_ST_POW_MSK,
- FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp16),
- true);
+
+ if (chan->channel != AD9910_CHANNEL_SINGLE_TONE)
+ return ad9910_reg16_write(st, AD9910_REG_POW, tmp16, true);
+
+ if (!ram_en)
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_ST_POW_MSK,
+ FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp16),
+ true);
+
+ FIELD_MODIFY(AD9910_PROFILE_ST_POW_MSK,
+ &st->reg_profile[st->profile], tmp16);
+ break;
case IIO_CHAN_INFO_SCALE:
if (val < 0 || val > 1 || (val == 1 && val2 > 0))
return -EINVAL;
@@ -1140,13 +1478,51 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
tmp64 = ((u64)val * MICRO + val2) << 14;
tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, MICRO);
tmp16 = min(tmp64, AD9910_ASF_MAX);
- return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
- AD9910_PROFILE_ST_ASF_MSK,
- FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp16),
- true);
+
+ if (chan->channel != AD9910_CHANNEL_SINGLE_TONE)
+ return ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_SCALE_FACTOR_MSK,
+ FIELD_PREP(AD9910_ASF_SCALE_FACTOR_MSK, tmp16),
+ true);
+
+ if (!ram_en)
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_ST_ASF_MSK,
+ FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp16),
+ true);
+
+ FIELD_MODIFY(AD9910_PROFILE_ST_ASF_MSK,
+ &st->reg_profile[st->profile], tmp16);
+ break;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ tmp64 = ((u64)val * MICRO + val2) * 4;
+ if (!tmp64)
+ return -EINVAL;
+
+ tmp64 = DIV64_U64_ROUND_CLOSEST((u64)st->data.sysclk_freq_hz * MICRO, tmp64);
+ tmp32 = clamp(tmp64, 1U, AD9910_STEP_RATE_MAX);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_RAM:
+ if (ram_en) {
+ tmp64 = FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, tmp32);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_RAM_STEP_RATE_MSK,
+ tmp64, true);
+ }
+
+ FIELD_MODIFY(AD9910_PROFILE_RAM_STEP_RATE_MSK,
+ &st->reg_profile[st->profile], tmp32);
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
default:
return -EINVAL;
}
+
+ return ret;
}
static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
@@ -1159,6 +1535,7 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_FREQUENCY:
case IIO_CHAN_INFO_PHASE:
case IIO_CHAN_INFO_SCALE:
+ case IIO_CHAN_INFO_SAMP_FREQ:
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
@@ -1247,13 +1624,65 @@ static ssize_t sysclk_frequency_show(struct device *dev,
static IIO_DEVICE_ATTR_RO(sysclk_frequency, 0);
+static ssize_t ram_data_write(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct ad9910_state *st = iio_priv(dev_to_iio_dev(kobj_to_dev(kobj)));
+ u64 tmp64, backup;
+ u32 start, end;
+ int ret, ret2;
+
+ if (off + count > AD9910_RAM_SIZE_MAX_BYTES || !count ||
+ off % AD9910_RAM_WORD_SIZE != 0 ||
+ count % AD9910_RAM_WORD_SIZE != 0)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ if (FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32))
+ return -EBUSY;
+
+ /* ensure profile is selected */
+ ret = ad9910_profile_set(st, st->profile);
+ if (ret)
+ return ret;
+
+ /* backup profile register */
+ backup = st->reg[AD9910_REG_PROFILE(st->profile)].val64;
+ start = off / AD9910_RAM_WORD_SIZE;
+ end = (off + count) / AD9910_RAM_WORD_SIZE - 1;
+ tmp64 = AD9910_PROFILE_RAM_STEP_RATE_MSK |
+ FIELD_PREP(AD9910_PROFILE_RAM_START_ADDR_MSK, start) |
+ FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK, end);
+ ret = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), tmp64, true);
+ if (ret)
+ return ret;
+
+ /* write ram data and restore profile register */
+ ret = ad9910_ram_load(st, buf, count);
+ ret2 = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), backup, true);
+ if (!ret)
+ ret = ret2;
+
+ return ret ?: count;
+}
+
+static const BIN_ATTR_WO(ram_data, AD9910_RAM_SIZE_MAX_BYTES);
+
static struct attribute *ad9910_attrs[] = {
&iio_dev_attr_sysclk_frequency.dev_attr.attr,
NULL
};
+static const struct bin_attribute *const ad9910_bin_attrs[] = {
+ &bin_attr_ram_data,
+ NULL
+};
+
static const struct attribute_group ad9910_attrs_group = {
.attrs = ad9910_attrs,
+ .bin_attrs = ad9910_bin_attrs,
};
static const struct iio_info ad9910_info = {
@@ -1426,6 +1855,13 @@ static int ad9910_setup(struct ad9910_state *st, struct reset_control *dev_rst)
if (ret)
return ret;
+ for (int i = 0; i < AD9910_NUM_PROFILES; i++) {
+ st->reg_profile[i] = AD9910_PROFILE_RAM_OPEN_MSK;
+ st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, 1);
+ st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK,
+ AD9910_RAM_ADDR_MAX);
+ }
+
return ad9910_io_update(st);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-02-20 16:46 ` [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support Rodrigo Alencar via B4 Relay
@ 2026-03-01 13:31 ` Jonathan Cameron
2026-03-03 15:32 ` Rodrigo Alencar
2026-03-07 14:07 ` Jonathan Cameron
0 siblings, 2 replies; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-01 13:31 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Fri, 20 Feb 2026 16:46:10 +0000
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> Add RAM channel with support for profile-based control. This includes:
> - RAM data loading via binary sysfs attribute (ram_data);
I'm not sure that's a long term viable path. We either need
to figure out how to do it as firmware file load, or via an output buffer.
Firmware load would probably be too static and I'm not sure quite
how we map these to IIO output buffers.
> - Per-profile RAM configuration (start/end address, step rate, operating
> mode, dwell control);
> - RAM destination control (frequency, phase, amplitude, polar);
> - RAM operating modes (direct switch, ramp up, bidirectional ramp,
> continuous bidirectional, continuous recirculate);
> - Profile switching for RAM playback;
> - Sampling frequency control via profile step rate;
> - ram_en-aware read/write paths that redirect single tone
> frequency/phase/amplitude access through reg_profile cache when RAM is
> active;
>
> When RAM is enabled, the DDS core parameters (frequency, phase, amplitude)
> for the single tone channel are sourced from a shadow register cache
> (reg_profile[]) since the profile registers are repurposed for RAM control.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> ---
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-03-01 13:31 ` Jonathan Cameron
@ 2026-03-03 15:32 ` Rodrigo Alencar
2026-03-03 17:16 ` Nuno Sá
2026-03-07 14:07 ` Jonathan Cameron
1 sibling, 1 reply; 46+ messages in thread
From: Rodrigo Alencar @ 2026-03-03 15:32 UTC (permalink / raw)
To: Jonathan Cameron, Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 26/03/01 01:31PM, Jonathan Cameron wrote:
> On Fri, 20 Feb 2026 16:46:10 +0000
> Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
>
> > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> >
> > Add RAM channel with support for profile-based control. This includes:
> > - RAM data loading via binary sysfs attribute (ram_data);
>
> I'm not sure that's a long term viable path. We either need
> to figure out how to do it as firmware file load, or via an output buffer.
Could you develop on this? it is not viable because iio would drop that
support? using sysfs_create_bin_file() directly would be better?
> Firmware load would probably be too static and I'm not sure quite
> how we map these to IIO output buffers.
will investigate this buffer route. At this point, we can have multiple
buffers, right? I have the DMA engine buffer working with the parallel port.
...
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-03-03 15:32 ` Rodrigo Alencar
@ 2026-03-03 17:16 ` Nuno Sá
2026-03-03 17:32 ` Rodrigo Alencar
0 siblings, 1 reply; 46+ messages in thread
From: Nuno Sá @ 2026-03-03 17:16 UTC (permalink / raw)
To: Rodrigo Alencar, Jonathan Cameron, Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Tue, 2026-03-03 at 15:32 +0000, Rodrigo Alencar wrote:
> On 26/03/01 01:31PM, Jonathan Cameron wrote:
> > On Fri, 20 Feb 2026 16:46:10 +0000
> > Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> >
> > > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > >
> > > Add RAM channel with support for profile-based control. This includes:
> > > - RAM data loading via binary sysfs attribute (ram_data);
> >
> > I'm not sure that's a long term viable path. We either need
> > to figure out how to do it as firmware file load, or via an output buffer.
>
> Could you develop on this? it is not viable because iio would drop that
> support? using sysfs_create_bin_file() directly would be better?
>
> > Firmware load would probably be too static and I'm not sure quite
> > how we map these to IIO output buffers.
>
> will investigate this buffer route. At this point, we can have multiple
> buffers, right? I have the DMA engine buffer working with the parallel port.
>
> ...
In theory yes but we do have some issues with the implementation. I have some
fixes but for code that, unfortunately, cannot land upstream anytime soon and with
no users, these fixes can be an hard sell. So if we go the multi buffer support it
could be a great opportunity for these.
That said, the fixes are only meaningful if we do need to restrict channels to a specific
buffer. Not sure if that will be the case here.
- Nuno Sá
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-03-03 17:16 ` Nuno Sá
@ 2026-03-03 17:32 ` Rodrigo Alencar
0 siblings, 0 replies; 46+ messages in thread
From: Rodrigo Alencar @ 2026-03-03 17:32 UTC (permalink / raw)
To: Nuno Sá, Rodrigo Alencar, Jonathan Cameron,
Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 26/03/03 05:16PM, Nuno Sá wrote:
> On Tue, 2026-03-03 at 15:32 +0000, Rodrigo Alencar wrote:
> > On 26/03/01 01:31PM, Jonathan Cameron wrote:
> > > On Fri, 20 Feb 2026 16:46:10 +0000
> > > Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> > >
> > > > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > > >
> > > > Add RAM channel with support for profile-based control. This includes:
> > > > - RAM data loading via binary sysfs attribute (ram_data);
> > >
> > > I'm not sure that's a long term viable path. We either need
> > > to figure out how to do it as firmware file load, or via an output buffer.
> >
> > Could you develop on this? it is not viable because iio would drop that
> > support? using sysfs_create_bin_file() directly would be better?
> >
> > > Firmware load would probably be too static and I'm not sure quite
> > > how we map these to IIO output buffers.
> >
> > will investigate this buffer route. At this point, we can have multiple
> > buffers, right? I have the DMA engine buffer working with the parallel port.
> >
> > ...
>
> In theory yes but we do have some issues with the implementation. I have some
> fixes but for code that, unfortunately, cannot land upstream anytime soon and with
> no users, these fixes can be an hard sell. So if we go the multi buffer support it
> could be a great opportunity for these.
>
> That said, the fixes are only meaningful if we do need to restrict channels to a specific
> buffer. Not sure if that will be the case here.
Without design changes, that is exactly the case. A triggered buffer
for the RAM control channel, and a DMA engine buffer for the parallel port
channel.
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-03-01 13:31 ` Jonathan Cameron
2026-03-03 15:32 ` Rodrigo Alencar
@ 2026-03-07 14:07 ` Jonathan Cameron
2026-03-10 17:40 ` Rodrigo Alencar
1 sibling, 1 reply; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-07 14:07 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Sun, 1 Mar 2026 13:31:53 +0000
Jonathan Cameron <jic23@kernel.org> wrote:
> On Fri, 20 Feb 2026 16:46:10 +0000
> Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
>
> > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> >
> > Add RAM channel with support for profile-based control. This includes:
> > - RAM data loading via binary sysfs attribute (ram_data);
>
> I'm not sure that's a long term viable path. We either need
> to figure out how to do it as firmware file load, or via an output buffer.
>
> Firmware load would probably be too static and I'm not sure quite
> how we map these to IIO output buffers.
We would have to carry it for ever which is very much not ideal.
The firmware approach has the same issue, but can be thought of
as defaults at boot time forever. If no defaults then we use whatever
we come up with as the long term solution.
>
> > - Per-profile RAM configuration (start/end address, step rate, operating
> > mode, dwell control);
> > - RAM destination control (frequency, phase, amplitude, polar);
> > - RAM operating modes (direct switch, ramp up, bidirectional ramp,
> > continuous bidirectional, continuous recirculate);
> > - Profile switching for RAM playback;
> > - Sampling frequency control via profile step rate;
> > - ram_en-aware read/write paths that redirect single tone
> > frequency/phase/amplitude access through reg_profile cache when RAM is
> > active;
> >
> > When RAM is enabled, the DDS core parameters (frequency, phase, amplitude)
> > for the single tone channel are sourced from a shadow register cache
> > (reg_profile[]) since the profile registers are repurposed for RAM control.
> >
> > Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > ---
>
>
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-03-07 14:07 ` Jonathan Cameron
@ 2026-03-10 17:40 ` Rodrigo Alencar
2026-03-11 0:11 ` David Lechner
0 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar @ 2026-03-10 17:40 UTC (permalink / raw)
To: Jonathan Cameron, Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 26/03/07 02:07PM, Jonathan Cameron wrote:
> On Sun, 1 Mar 2026 13:31:53 +0000
> Jonathan Cameron <jic23@kernel.org> wrote:
>
> > On Fri, 20 Feb 2026 16:46:10 +0000
> > Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> >
> > > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > >
> > > Add RAM channel with support for profile-based control. This includes:
> > > - RAM data loading via binary sysfs attribute (ram_data);
> >
> > I'm not sure that's a long term viable path. We either need
> > to figure out how to do it as firmware file load, or via an output buffer.
> >
> > Firmware load would probably be too static and I'm not sure quite
> > how we map these to IIO output buffers.
>
> We would have to carry it for ever which is very much not ideal.
> The firmware approach has the same issue, but can be thought of
> as defaults at boot time forever. If no defaults then we use whatever
> we come up with as the long term solution.
I was thinking about the firmware approach:
- Normally a driver would request the firmware during probe and the
filename would be pre-defined.
- Less statically, It could have an attribute that once written, it would
request the RAM contents (e.g. under /lib/firmware/ad9910_ram.bin).
It could be the enable attribute itself (but that would not be effective
when the binary would not change), or a separate one (e.g. destination),
or a new one (e.g. load_en)
- Alternatively we can also have an attribute (like 'filename') that once written,
loads a firmware file under /lib/firmware/<value-of-filename>.bin
I am currently using libiio to write the sysfs binary attribute. Thus,
the downside of the firmware approach is that I would have to use something
else when changing a firmware file remotely (as libiio would not support this).
Also, the buffer approach has its problems, mostly because triggered buffers
are not really designed for multi-buffer support, even though it could work
along side the DMA engine one (that I am using with the parallel port).
Additionally, userspace tools are not yet ready for multi-buffer support.
Also, an IIO buffer might give a sense of data streaming capabilities,
not a one-shot load that I need here.
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-03-10 17:40 ` Rodrigo Alencar
@ 2026-03-11 0:11 ` David Lechner
2026-03-11 13:11 ` Rodrigo Alencar
0 siblings, 1 reply; 46+ messages in thread
From: David Lechner @ 2026-03-11 0:11 UTC (permalink / raw)
To: Rodrigo Alencar, Jonathan Cameron, Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
On 3/10/26 12:40 PM, Rodrigo Alencar wrote:
> On 26/03/07 02:07PM, Jonathan Cameron wrote:
>> On Sun, 1 Mar 2026 13:31:53 +0000
>> Jonathan Cameron <jic23@kernel.org> wrote:
>>
>>> On Fri, 20 Feb 2026 16:46:10 +0000
>>> Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
>>>
>>>> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>>>>
>>>> Add RAM channel with support for profile-based control. This includes:
>>>> - RAM data loading via binary sysfs attribute (ram_data);
>>>
>>> I'm not sure that's a long term viable path. We either need
>>> to figure out how to do it as firmware file load, or via an output buffer.
>>>
>>> Firmware load would probably be too static and I'm not sure quite
>>> how we map these to IIO output buffers.
>>
>> We would have to carry it for ever which is very much not ideal.
>> The firmware approach has the same issue, but can be thought of
>> as defaults at boot time forever. If no defaults then we use whatever
>> we come up with as the long term solution.
>
> I was thinking about the firmware approach:
> - Normally a driver would request the firmware during probe and the
> filename would be pre-defined.
> - Less statically, It could have an attribute that once written, it would
> request the RAM contents (e.g. under /lib/firmware/ad9910_ram.bin).
> It could be the enable attribute itself (but that would not be effective
> when the binary would not change), or a separate one (e.g. destination),
> or a new one (e.g. load_en)
Have you looked at firmware_upload_register()? It looks like it provides
something along these lines.
> - Alternatively we can also have an attribute (like 'filename') that once written,
> loads a firmware file under /lib/firmware/<value-of-filename>.bin
>
> I am currently using libiio to write the sysfs binary attribute. Thus,
> the downside of the firmware approach is that I would have to use something
> else when changing a firmware file remotely (as libiio would not support this).
>
> Also, the buffer approach has its problems, mostly because triggered buffers
> are not really designed for multi-buffer support, even though it could work
> along side the DMA engine one (that I am using with the parallel port).
> Additionally, userspace tools are not yet ready for multi-buffer support.
> Also, an IIO buffer might give a sense of data streaming capabilities,
> not a one-shot load that I need here.
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-03-11 0:11 ` David Lechner
@ 2026-03-11 13:11 ` Rodrigo Alencar
2026-03-11 16:54 ` Nuno Sá
0 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar @ 2026-03-11 13:11 UTC (permalink / raw)
To: David Lechner, Rodrigo Alencar, Jonathan Cameron,
Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
On 26/03/10 07:11PM, David Lechner wrote:
> On 3/10/26 12:40 PM, Rodrigo Alencar wrote:
> > On 26/03/07 02:07PM, Jonathan Cameron wrote:
> >> On Sun, 1 Mar 2026 13:31:53 +0000
> >> Jonathan Cameron <jic23@kernel.org> wrote:
> >>
> >>> On Fri, 20 Feb 2026 16:46:10 +0000
> >>> Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> >>>
> >>>> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> >>>>
> >>>> Add RAM channel with support for profile-based control. This includes:
> >>>> - RAM data loading via binary sysfs attribute (ram_data);
> >>>
> >>> I'm not sure that's a long term viable path. We either need
> >>> to figure out how to do it as firmware file load, or via an output buffer.
> >>>
> >>> Firmware load would probably be too static and I'm not sure quite
> >>> how we map these to IIO output buffers.
> >>
> >> We would have to carry it for ever which is very much not ideal.
> >> The firmware approach has the same issue, but can be thought of
> >> as defaults at boot time forever. If no defaults then we use whatever
> >> we come up with as the long term solution.
> >
> > I was thinking about the firmware approach:
> > - Normally a driver would request the firmware during probe and the
> > filename would be pre-defined.
> > - Less statically, It could have an attribute that once written, it would
> > request the RAM contents (e.g. under /lib/firmware/ad9910_ram.bin).
> > It could be the enable attribute itself (but that would not be effective
> > when the binary would not change), or a separate one (e.g. destination),
> > or a new one (e.g. load_en)
>
> Have you looked at firmware_upload_register()? It looks like it provides
> something along these lines.
Thanks! this is in fact perfect!
It might be ugly, but for the sake of debugging I managed to get libiio to
interface with this by exposing it in the debugfs.
debugfs_create_symlink("ram_loading",
iio_get_debugfs_dentry(indio_dev),
"/sys/class/firmware/ad9910-ram/loading");
debugfs_create_symlink("ram_data",
iio_get_debugfs_dentry(indio_dev),
"/sys/class/firmware/ad9910-ram/data");
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-03-11 13:11 ` Rodrigo Alencar
@ 2026-03-11 16:54 ` Nuno Sá
2026-03-11 17:08 ` Rodrigo Alencar
0 siblings, 1 reply; 46+ messages in thread
From: Nuno Sá @ 2026-03-11 16:54 UTC (permalink / raw)
To: Rodrigo Alencar, David Lechner, Jonathan Cameron,
Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
On Wed, 2026-03-11 at 13:11 +0000, Rodrigo Alencar wrote:
> On 26/03/10 07:11PM, David Lechner wrote:
> > On 3/10/26 12:40 PM, Rodrigo Alencar wrote:
> > > On 26/03/07 02:07PM, Jonathan Cameron wrote:
> > > > On Sun, 1 Mar 2026 13:31:53 +0000
> > > > Jonathan Cameron <jic23@kernel.org> wrote:
> > > >
> > > > > On Fri, 20 Feb 2026 16:46:10 +0000
> > > > > Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> > > > >
> > > > > > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > > > > >
> > > > > > Add RAM channel with support for profile-based control. This includes:
> > > > > > - RAM data loading via binary sysfs attribute (ram_data);
> > > > >
> > > > > I'm not sure that's a long term viable path. We either need
> > > > > to figure out how to do it as firmware file load, or via an output buffer.
> > > > >
> > > > > Firmware load would probably be too static and I'm not sure quite
> > > > > how we map these to IIO output buffers.
> > > >
> > > > We would have to carry it for ever which is very much not ideal.
> > > > The firmware approach has the same issue, but can be thought of
> > > > as defaults at boot time forever. If no defaults then we use whatever
> > > > we come up with as the long term solution.
> > >
> > > I was thinking about the firmware approach:
> > > - Normally a driver would request the firmware during probe and the
> > > filename would be pre-defined.
> > > - Less statically, It could have an attribute that once written, it would
> > > request the RAM contents (e.g. under /lib/firmware/ad9910_ram.bin).
> > > It could be the enable attribute itself (but that would not be effective
> > > when the binary would not change), or a separate one (e.g. destination),
> > > or a new one (e.g. load_en)
> >
> > Have you looked at firmware_upload_register()? It looks like it provides
> > something along these lines.
>
> Thanks! this is in fact perfect!
> It might be ugly, but for the sake of debugging I managed to get libiio to
> interface with this by exposing it in the debugfs.
>
> debugfs_create_symlink("ram_loading",
> iio_get_debugfs_dentry(indio_dev),
> "/sys/class/firmware/ad9910-ram/loading");
> debugfs_create_symlink("ram_data",
> iio_get_debugfs_dentry(indio_dev),
> "/sys/class/firmware/ad9910-ram/data");
Or for the sake of future proving, maybe let's make the sysfs a bit more generic. At least the path,
/sys/class/firmware/dev_name(iio_dev)/*
I guess userspace tools could work with the above to match the fw blob with the right device. Also,
hardcoding the name as it seems you have done will be a problem if we have multiple devices, no?
Not sure about the above though :)
- Nuno Sá
^ permalink raw reply [flat|nested] 46+ messages in thread* Re: [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support
2026-03-11 16:54 ` Nuno Sá
@ 2026-03-11 17:08 ` Rodrigo Alencar
0 siblings, 0 replies; 46+ messages in thread
From: Rodrigo Alencar @ 2026-03-11 17:08 UTC (permalink / raw)
To: Nuno Sá, Rodrigo Alencar, David Lechner, Jonathan Cameron,
Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
On 26/03/11 04:54PM, Nuno Sá wrote:
> On Wed, 2026-03-11 at 13:11 +0000, Rodrigo Alencar wrote:
> > On 26/03/10 07:11PM, David Lechner wrote:
> > > On 3/10/26 12:40 PM, Rodrigo Alencar wrote:
> > > > On 26/03/07 02:07PM, Jonathan Cameron wrote:
> > > > > On Sun, 1 Mar 2026 13:31:53 +0000
> > > > > Jonathan Cameron <jic23@kernel.org> wrote:
> > > > >
> > > > > > On Fri, 20 Feb 2026 16:46:10 +0000
> > > > > > Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> > > > > >
> > > > > > > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > > > > > >
> > > > > > > Add RAM channel with support for profile-based control. This includes:
> > > > > > > - RAM data loading via binary sysfs attribute (ram_data);
> > > > > >
> > > > > > I'm not sure that's a long term viable path. We either need
> > > > > > to figure out how to do it as firmware file load, or via an output buffer.
> > > > > >
> > > > > > Firmware load would probably be too static and I'm not sure quite
> > > > > > how we map these to IIO output buffers.
> > > > >
> > > > > We would have to carry it for ever which is very much not ideal.
> > > > > The firmware approach has the same issue, but can be thought of
> > > > > as defaults at boot time forever. If no defaults then we use whatever
> > > > > we come up with as the long term solution.
> > > >
> > > > I was thinking about the firmware approach:
> > > > - Normally a driver would request the firmware during probe and the
> > > > filename would be pre-defined.
> > > > - Less statically, It could have an attribute that once written, it would
> > > > request the RAM contents (e.g. under /lib/firmware/ad9910_ram.bin).
> > > > It could be the enable attribute itself (but that would not be effective
> > > > when the binary would not change), or a separate one (e.g. destination),
> > > > or a new one (e.g. load_en)
> > >
> > > Have you looked at firmware_upload_register()? It looks like it provides
> > > something along these lines.
> >
> > Thanks! this is in fact perfect!
> > It might be ugly, but for the sake of debugging I managed to get libiio to
> > interface with this by exposing it in the debugfs.
> >
> > debugfs_create_symlink("ram_loading",
> > iio_get_debugfs_dentry(indio_dev),
> > "/sys/class/firmware/ad9910-ram/loading");
> > debugfs_create_symlink("ram_data",
> > iio_get_debugfs_dentry(indio_dev),
> > "/sys/class/firmware/ad9910-ram/data");
>
> Or for the sake of future proving, maybe let's make the sysfs a bit more generic. At least the path,
>
> /sys/class/firmware/dev_name(iio_dev)/*
>
> I guess userspace tools could work with the above to match the fw blob with the right device. Also,
> hardcoding the name as it seems you have done will be a problem if we have multiple devices, no?
You are right. I am using /sys/class/firmware/iio:deviceX:ram/* at this point.
I suppose some complex transceivers you may know about may need different fw_upload
instances for different CPUs or profiles.
I like the idea for userspace tools to expose those. For now, using symlink into
debufs is working just fine.
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH RFC 7/8] iio: frequency: ad9910: add output shift keying support
2026-02-20 16:46 [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (5 preceding siblings ...)
2026-02-20 16:46 ` [PATCH RFC 6/8] iio: frequency: ad9910: add RAM mode support Rodrigo Alencar via B4 Relay
@ 2026-02-20 16:46 ` Rodrigo Alencar via B4 Relay
2026-02-20 16:46 ` [PATCH RFC 8/8] iio: frequency: ad9910: add channel labels Rodrigo Alencar via B4 Relay
2026-02-21 20:16 ` [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer David Lechner
8 siblings, 0 replies; 46+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-02-20 16:46 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add OSK channel with amplitude envelope control capabilities:
- OSK enable/disable via IIO_CHAN_INFO_ENABLE;
- Amplitude ramp rate control via IIO_CHAN_INFO_SAMP_FREQ;
- Amplitude scale readback via IIO_CHAN_INFO_SCALE (ASF register);
- Manual/external pin control via pinctrl_en ext_info attribute;
- Automatic OSK step size configuration via scale_increment ext_info;
attribute with selectable step sizes (61, 122, 244, 488 micro-units)
The ASF register is initialized with a default amplitude ramp rate during
device setup to ensure valid SAMP_FREQ readback.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/ad9910.c | 134 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 134 insertions(+)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 8fd7ebe7e6b0..b1540b157a0e 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -226,6 +226,7 @@ enum ad9910_channel {
AD9910_CHANNEL_PARALLEL_PORT,
AD9910_CHANNEL_DRG,
AD9910_CHANNEL_RAM,
+ AD9910_CHANNEL_OSK,
};
/**
@@ -297,6 +298,8 @@ enum {
AD9910_DRG_DEC_STEP_RATE,
AD9910_RAM_START_ADDR,
AD9910_RAM_END_ADDR,
+ AD9910_OSK_MANUAL_EXTCTL,
+ AD9910_OSK_AUTO_STEP,
};
struct ad9910_data {
@@ -389,6 +392,10 @@ static const char * const ad9910_ram_oper_mode_str[] = {
[AD9910_RAM_MODE_SEQ_CONT] = "sequenced_continuous",
};
+static const u16 ad9910_osk_ustep[] = {
+ 0, 61, 122, 244, 488,
+};
+
/**
* ad9910_rational_scale() - Perform scaling of input given a reference.
* @input: The input value to be scaled.
@@ -716,6 +723,10 @@ static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
val = FIELD_GET(AD9910_PROFILE_RAM_END_ADDR_MSK,
st->reg_profile[st->profile]);
break;
+ case AD9910_OSK_MANUAL_EXTCTL:
+ val = FIELD_GET(AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ break;
default:
return -EINVAL;
}
@@ -787,6 +798,12 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
FIELD_MODIFY(AD9910_PROFILE_RAM_END_ADDR_MSK,
&st->reg_profile[st->profile], val32);
break;
+ case AD9910_OSK_MANUAL_EXTCTL:
+ val32 = val32 ? AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK : 0;
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK,
+ val32, true);
+ break;
default:
return -EINVAL;
}
@@ -1144,6 +1161,80 @@ static ssize_t ad9910_drg_attrs_write(struct iio_dev *indio_dev,
return ret ?: len;
}
+static ssize_t ad9910_osk_attrs_read(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int vals[2];
+ bool auto_en;
+ u32 raw_val;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_OSK_AUTO_STEP:
+ auto_en = FIELD_GET(AD9910_CFR1_SELECT_AUTO_OSK_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ raw_val = FIELD_GET(AD9910_ASF_STEP_SIZE_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ vals[0] = 0;
+ vals[1] = auto_en ? ad9910_osk_ustep[raw_val + 1] : 0;
+
+ return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, 2, vals);
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t ad9910_osk_attrs_write(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int val, val2;
+ int ret;
+ u32 raw_val;
+
+ ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_OSK_AUTO_STEP:
+ if (val != 0)
+ return -EINVAL;
+
+ raw_val = find_closest(val2, ad9910_osk_ustep,
+ ARRAY_SIZE(ad9910_osk_ustep));
+ if (raw_val) {
+ /* set OSK step and get automatic OSK enabled */
+ raw_val = FIELD_PREP(AD9910_ASF_STEP_SIZE_MSK,
+ raw_val - 1);
+ ret = ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_STEP_SIZE_MSK,
+ raw_val, true);
+ if (ret)
+ return ret;
+
+ raw_val = AD9910_CFR1_SELECT_AUTO_OSK_MSK;
+ }
+
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SELECT_AUTO_OSK_MSK,
+ raw_val, true);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret ?: len;
+}
+
#define AD9910_EXT_INFO_TMPL(_name, _ident, _shared, _fn_desc) { \
.name = _name, \
.read = ad9910_ ## _fn_desc ## _read, \
@@ -1164,6 +1255,9 @@ static ssize_t ad9910_drg_attrs_write(struct iio_dev *indio_dev,
#define AD9910_DRG_EXT_INFO(_name, _ident) \
AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, drg_attrs)
+#define AD9910_OSK_EXT_INFO(_name, _ident) \
+ AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, osk_attrs)
+
static const struct iio_enum ad9910_drg_destination_enum = {
.items = ad9910_destination_str,
.num_items = AD9910_DRG_DEST_NUM,
@@ -1238,6 +1332,12 @@ static const struct iio_chan_spec_ext_info ad9910_ram_ext_info[] = {
{ },
};
+static const struct iio_chan_spec_ext_info ad9910_osk_ext_info[] = {
+ AD9910_EXT_INFO("pinctrl_en", AD9910_OSK_MANUAL_EXTCTL, IIO_SEPARATE),
+ AD9910_OSK_EXT_INFO("scale_increment", AD9910_OSK_AUTO_STEP),
+ { },
+};
+
static const struct iio_chan_spec ad9910_channels[] = {
[AD9910_CHANNEL_SINGLE_TONE] = {
.type = IIO_ALTVOLTAGE,
@@ -1279,6 +1379,17 @@ static const struct iio_chan_spec ad9910_channels[] = {
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.ext_info = ad9910_ram_ext_info,
},
+ [AD9910_CHANNEL_OSK] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_OSK,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_osk_ext_info,
+ },
};
static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -1308,6 +1419,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
case AD9910_CHANNEL_RAM:
*val = ram_en;
break;
+ case AD9910_CHANNEL_OSK:
+ *val = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ break;
default:
return -EINVAL;
}
@@ -1367,6 +1482,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK,
st->reg_profile[st->profile]);
break;
+ case AD9910_CHANNEL_OSK:
+ tmp32 = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ break;
default:
return -EINVAL;
}
@@ -1430,6 +1549,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg32_update(st, AD9910_REG_CFR1,
AD9910_CFR1_RAM_ENABLE_MSK,
tmp32, true);
+ case AD9910_CHANNEL_OSK:
+ tmp32 = FIELD_PREP(AD9910_CFR1_OSK_ENABLE_MSK, val);
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_OSK_ENABLE_MSK,
+ tmp32, true);
default:
return -EINVAL;
}
@@ -1514,6 +1638,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
FIELD_MODIFY(AD9910_PROFILE_RAM_STEP_RATE_MSK,
&st->reg_profile[st->profile], tmp32);
break;
+ case AD9910_CHANNEL_OSK:
+ return ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_RAMP_RATE_MSK,
+ FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, tmp32),
+ true);
default:
return -EINVAL;
}
@@ -1849,6 +1978,11 @@ static int ad9910_setup(struct ad9910_state *st, struct reset_control *dev_rst)
return ret;
/* configure step rate with default values */
+ reg32 = FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, 1);
+ ret = ad9910_reg32_write(st, AD9910_REG_ASF, reg32, false);
+ if (ret)
+ return ret;
+
reg32 = FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) |
FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1);
ret = ad9910_reg32_write(st, AD9910_REG_DRG_RATE, reg32, false);
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread* [PATCH RFC 8/8] iio: frequency: ad9910: add channel labels
2026-02-20 16:46 [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (6 preceding siblings ...)
2026-02-20 16:46 ` [PATCH RFC 7/8] iio: frequency: ad9910: add output shift keying support Rodrigo Alencar via B4 Relay
@ 2026-02-20 16:46 ` Rodrigo Alencar via B4 Relay
2026-02-21 20:16 ` [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer David Lechner
8 siblings, 0 replies; 46+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-02-20 16:46 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add human-readable labels for all AD9910 IIO channels via the read_label
callback: single_tone, parallel_port, digital_ramp_generator, ram_control,
and output_shift_keying.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/ad9910.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index b1540b157a0e..e983614805b4 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -1814,10 +1814,26 @@ static const struct attribute_group ad9910_attrs_group = {
.bin_attrs = ad9910_bin_attrs,
};
+static const char * const ad9910_channel_str[] = {
+ [AD9910_CHANNEL_SINGLE_TONE] = "single_tone",
+ [AD9910_CHANNEL_PARALLEL_PORT] = "parallel_port",
+ [AD9910_CHANNEL_DRG] = "digital_ramp_generator",
+ [AD9910_CHANNEL_RAM] = "ram_control",
+ [AD9910_CHANNEL_OSK] = "output_shift_keying",
+};
+
+static int ad9910_read_label(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ char *label)
+{
+ return sprintf(label, "%s\n", ad9910_channel_str[chan->channel]);
+}
+
static const struct iio_info ad9910_info = {
.read_raw = ad9910_read_raw,
.write_raw = ad9910_write_raw,
.write_raw_get_fmt = ad9910_write_raw_get_fmt,
+ .read_label = ad9910_read_label,
.attrs = &ad9910_attrs_group,
.debugfs_reg_access = &ad9910_reg_access,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-02-20 16:46 [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (7 preceding siblings ...)
2026-02-20 16:46 ` [PATCH RFC 8/8] iio: frequency: ad9910: add channel labels Rodrigo Alencar via B4 Relay
@ 2026-02-21 20:16 ` David Lechner
2026-02-22 10:01 ` Rodrigo Alencar
8 siblings, 1 reply; 46+ messages in thread
From: David Lechner @ 2026-02-21 20:16 UTC (permalink / raw)
To: rodrigo.alencar, linux-iio, devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> This patch series adds support for the Analog Devices AD9910 DDS.
> This is an RFC so that we can agree/discuss on the design that follows:
>
> The AD9910 DDS core can be driven through several independent mechanisms:
> single tone profiles, a digital ramp generator, an internal RAM playback
> engine, a parallel data port, and output shift keying. Each of these
This makes is sound more like a DAC than a frequency generator. altvoltage
specifically means an AC voltage (sine wave), so these arbitrary outputs
don't fit that.
> represents a distinct signal path into the DDS accumulator, so the driver
> models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
Generally IIO channels represent the physical input/output, not the
internal channels.
Ideally we would just have the one channel here with a mode selection
attribute. Documentation can tell us which modes use which attributes.
> This per-channel separation allows userspace to configure each mode
> independently through its own set of sysfs attributes, and to
> enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
> the hardware's own mode selection architecture.
>
> The AD9910 register map is not suited for the regmap framework: register
> widths vary across the map (16, 32, and 64 bits). The driver instead
Does it break things if you read/write 64 bits from/to non-64-bit registers?
In other drivers for chips like this, we've just created 2 regmaps, i.e.
one for 16-bit regs and one for 32-bit regs. Seems better than
re-implementing a reg cache.
> implements direct SPI access helpers with a software register cache, using
> type-specific read/write/update functions (ad9910_reg{16,32,64}_{read,
> write,update}) that handle endianness conversion and cache coherency.
>
> Registers are cached for several reasons. The control/function registers
> (CFR1, CFR2) are frequently queried to determine the current operating
> mode (e.g., checking RAM_ENABLE before every profile register access),
> and caching avoids repeated SPI read transactions for what are
> essentially state checks. The cache also enables efficient
> read-modify-write updates on multi-byte registers: the update functions
> merge new field values with the cached register content without issuing
> a SPI read, and skip the write entirely when the value is unchanged.
> Finally, the profile registers serve dual purposes depending on whether
> RAM mode is active -- they hold single tone parameters (FTW, POW, ASF)
> in normal operation but are repurposed for RAM playback configuration
> (start/end address, step rate, operating mode) when RAM is enabled. A
> shadow register array (reg_profile[]) preserves the inactive mode's
> settings across transitions, so no state is lost when switching between
> single tone and RAM operation.
>
> RAM data is loaded through a write-only binary sysfs attribute
> (ram_data). Userspace writes the waveform data as a raw binary buffer
> (up to 4096 bytes for the full 1024x32-bit RAM), and the driver
> transfers it to the device in a single SPI transaction. Per-profile
> start/end addresses and playback parameters (operating mode, step rate,
> no-dwell control) are configured through the RAM channel's ext_info
> attributes.
>
> Streaming data to the DDS core through the parallel data port at the
> PD_CLK rate is not covered by this series. That functionality would
> be added in a separate patch series, building on top of the IIO backend
> infrastructure to provide a proper buffered data path.
>
> As I am pushing implementation, as lot has been done already without much
> supervision or agreement, still I would be interested on hearing about
> the design choices discussed above. Here is the output for the iio_info
> at this point:
>
> 5 channels found:
> altvoltage1: (output)
> 9 channel-specific attributes found:
> attr 0: en value: 0
> attr 1: frequency_offset value: 0.000000
> attr 2: frequency_scale value: 1
> attr 3: label value: parallel_port
> attr 4: phase_offset value: 0.000000
> attr 5: powerdown value: 0
> attr 6: profile value: 0
> attr 7: sampling_frequency value: 100000000.000000
> attr 8: scale_offset value: 0.000000
> altvoltage3: (output)
> 13 channel-specific attributes found:
> attr 0: address_end value: 1023
> attr 1: address_start value: 0
> attr 2: destination value: frequency
> attr 3: destination_available value:
> frequency phase amplitude polar
> attr 4: en value: 0
> attr 5: frequency value: 0.000000
> attr 6: label value: ram_control
> attr 7: operating_mode value: direct_switch
> attr 8: operating_mode_available value:
> direct_switch ramp_up bidirectional
> bidirectional_continuous ramp_up_continuous
> sequenced sequenced_continuous
> attr 9: phase value: 0.000000
> attr 10: powerdown value: 0
> attr 11: profile value: 0
> attr 12: sampling_frequency value: 100000000.000000
> altvoltage2: (output)
> 27 channel-specific attributes found:
> attr 0: burst_count value: 0
> attr 1: burst_delay value: 0.000000030
> attr 2: control_en value: 0
> attr 3: decrement_sampling_frequency value: 100000000.000000
> attr 4: destination value: frequency
> attr 5: destination_available value: frequency phase amplitude
> attr 6: en value: 0
> attr 7: frequency_decrement value: 0.000000
> attr 8: frequency_increment value: 0.000000
> attr 9: frequency_max value: 0.000000
> attr 10: frequency_min value: 0.000000
> attr 11: increment_sampling_frequency value: 100000000.000000
> attr 12: label value: digital_ramp_generator
> attr 13: operating_mode value: bidirectional_continuous
> attr 14: operating_mode_available value:
> bidirectional ramp_down ramp_up bidirectional_continuous
> attr 15: phase_decrement value: 0.000000000
> attr 16: phase_increment value: 0.000000000
> attr 17: phase_max value: 0.000000000
> attr 18: phase_min value: 0.000000000
> attr 19: powerdown value: 0
> attr 20: profile value: 0
> attr 21: ramp_delay value: 0.000000020
> attr 22: scale_decrement value: 0.000000000
> attr 23: scale_increment value: 0.000000000
> attr 24: scale_max value: 0.000000000
> attr 25: scale_min value: 0.000000000
> attr 26: toggle_en value: 0
> altvoltage0: (output)
> 6 channel-specific attributes found:
> attr 0: frequency value: 0.000000
> attr 1: label value: single_tone
> attr 2: phase value: 0.000000
> attr 3: powerdown value: 0
> attr 4: profile value: 0
> attr 5: scale value: 0.000000
> altvoltage4: (output)
> 8 channel-specific attributes found:
> attr 0: en value: 0
> attr 1: label value: output_shift_keying
> attr 2: pinctrl_en value: 0
> attr 3: powerdown value: 0
> attr 4: profile value: 0
> attr 5: sampling_frequency value: 100000000.000000
> attr 6: scale value: 0.000000
> attr 7: scale_increment value: 0.000000
> 3 device-specific attributes found:
> attr 0: ram_data ERROR: Permission denied (13)
> attr 1: sysclk_frequency value: 400000000
> attr 2: waiting_for_supplier value: 0
> 1 debug attributes found:
> debug attr 0: direct_reg_access value: 0x2
This is a lot of custom attributes!
It looks like a lot of these are just exposing registers directly, which
usually isn't the best if we want something that can be reused. However,
this looks pretty complex so coming up with something generic is probably
not worth the effort.
Instead, I would suggest to create a firmware file format that
describes how the chip should be programmed. And in the driver call
firmware_upload_register() to create a sysfs interface where the
firmware can be loaded/replaced at runtime. This way, there is just
one attribute write needed to set all of the parameters at once.
This could probably be as simple as something that just contains
the value of each register to be programmed and the driver can
write all of the registers just before enabling the output.
>
> Kind regards,
>
> Rodrigo Alencar
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> ---
> Rodrigo Alencar (8):
> dt-bindings: iio: frequency: add ad9910
> iio: frequency: ad9910: initial driver implementation
> iio: frequency: ad9910: add simple parallel port mode support
> iio: frequency: ad9910: expose sysclk_frequency device attribute
> iio: frequency: ad9910: add digital ramp generator support
> iio: frequency: ad9910: add RAM mode support
> iio: frequency: ad9910: add output shift keying support
> iio: frequency: ad9910: add channel labels
>
> .../bindings/iio/frequency/adi,ad9910.yaml | 236 +++
> MAINTAINERS | 8 +
> drivers/iio/frequency/Kconfig | 18 +
> drivers/iio/frequency/Makefile | 1 +
> drivers/iio/frequency/ad9910.c | 2153 ++++++++++++++++++++
> 5 files changed, 2416 insertions(+)
> ---
> base-commit: cce8de7f9744a210a4441ca8a667a9950515eea7
> change-id: 20260218-ad9910-iio-driver-9b3d214c251f
>
> Best regards,
^ permalink raw reply [flat|nested] 46+ messages in thread* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-02-21 20:16 ` [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer David Lechner
@ 2026-02-22 10:01 ` Rodrigo Alencar
2026-02-22 20:32 ` David Lechner
0 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar @ 2026-02-22 10:01 UTC (permalink / raw)
To: David Lechner, rodrigo.alencar, linux-iio, devicetree,
linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 26/02/21 02:16PM, David Lechner wrote:
> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > This patch series adds support for the Analog Devices AD9910 DDS.
> > This is an RFC so that we can agree/discuss on the design that follows:
> >
> > The AD9910 DDS core can be driven through several independent mechanisms:
> > single tone profiles, a digital ramp generator, an internal RAM playback
> > engine, a parallel data port, and output shift keying. Each of these
>
> This makes is sound more like a DAC than a frequency generator. altvoltage
> specifically means an AC voltage (sine wave), so these arbitrary outputs
> don't fit that.
Most applications for this part are in fact for frequency generation, like:
- Agile local oscillator (LO) frequency synthesis
- Programmable clock generators
- FM chirp source for radar and scanning systems
- Fast frequency hopping
The device has been made to be too flexible, so that its operation modes
have sub-operation modes that allows to handle frequency, scale and phase.
But at a specific timestamp, the output signal will always be a CW, i.e.
a sine or cosine wave.
> > represents a distinct signal path into the DDS accumulator, so the driver
> > models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
>
> Generally IIO channels represent the physical input/output, not the
> internal channels.
That is part of the reason for this RFC. Dividing those top-level modes
into channels allows for better organization, as they can operate together,
i.e., phase or scale can be provided by single-tone profile, while
frequency is controlled by the digital ramp generator (see Mode Priority
section in the datasheet). Also, it allows to explore the most of standard
ABIs like, scale, frequency, phase, sampling_frequency and enable.
Putting everything into a single channel would make things a lot messy
to interface with.
> Ideally we would just have the one channel here with a mode selection
> attribute. Documentation can tell us which modes use which attributes.
>
> > This per-channel separation allows userspace to configure each mode
> > independently through its own set of sysfs attributes, and to
> > enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
> > the hardware's own mode selection architecture.
> >
> > The AD9910 register map is not suited for the regmap framework: register
> > widths vary across the map (16, 32, and 64 bits). The driver instead
>
> Does it break things if you read/write 64 bits from/to non-64-bit registers?
Yes, the exact amount of bytes needs to be sent when writing specific registers.
> In other drivers for chips like this, we've just created 2 regmaps, i.e.
> one for 16-bit regs and one for 32-bit regs. Seems better than
> re-implementing a reg cache.
I would have to have 3 configs, one of them for a single 16-bit register.
Also, regmap_spi does not seem to support 64-bit registers (maybe I am wrong).
Additionally, single tone modes and RAM control modes are profile based and
they share the same registers, so I suppose that having to control the
register cache manually would be beneficial to switch RAM mode ON/OFF.
I understand that the digital design of the chip is not one of the best,
and a lot of unneeded complications are pushed to be handled in software.
> > implements direct SPI access helpers with a software register cache, using
> > type-specific read/write/update functions (ad9910_reg{16,32,64}_{read,
> > write,update}) that handle endianness conversion and cache coherency.
> >
> > Registers are cached for several reasons. The control/function registers
> > (CFR1, CFR2) are frequently queried to determine the current operating
> > mode (e.g., checking RAM_ENABLE before every profile register access),
> > and caching avoids repeated SPI read transactions for what are
> > essentially state checks. The cache also enables efficient
> > read-modify-write updates on multi-byte registers: the update functions
> > merge new field values with the cached register content without issuing
> > a SPI read, and skip the write entirely when the value is unchanged.
> > Finally, the profile registers serve dual purposes depending on whether
> > RAM mode is active -- they hold single tone parameters (FTW, POW, ASF)
> > in normal operation but are repurposed for RAM playback configuration
> > (start/end address, step rate, operating mode) when RAM is enabled. A
> > shadow register array (reg_profile[]) preserves the inactive mode's
> > settings across transitions, so no state is lost when switching between
> > single tone and RAM operation.
> >
> > RAM data is loaded through a write-only binary sysfs attribute
> > (ram_data). Userspace writes the waveform data as a raw binary buffer
> > (up to 4096 bytes for the full 1024x32-bit RAM), and the driver
> > transfers it to the device in a single SPI transaction. Per-profile
> > start/end addresses and playback parameters (operating mode, step rate,
> > no-dwell control) are configured through the RAM channel's ext_info
> > attributes.
> >
> > Streaming data to the DDS core through the parallel data port at the
> > PD_CLK rate is not covered by this series. That functionality would
> > be added in a separate patch series, building on top of the IIO backend
> > infrastructure to provide a proper buffered data path.
> >
> > As I am pushing implementation, as lot has been done already without much
> > supervision or agreement, still I would be interested on hearing about
> > the design choices discussed above. Here is the output for the iio_info
> > at this point:
> >
> > 5 channels found:
> > altvoltage1: (output)
> > 9 channel-specific attributes found:
> > attr 0: en value: 0
> > attr 1: frequency_offset value: 0.000000
> > attr 2: frequency_scale value: 1
> > attr 3: label value: parallel_port
> > attr 4: phase_offset value: 0.000000
> > attr 7: sampling_frequency value: 100000000.000000
> > attr 8: scale_offset value: 0.000000
> > altvoltage3: (output)
> > 13 channel-specific attributes found:
> > attr 0: address_end value: 1023
> > attr 1: address_start value: 0
> > attr 2: destination value: frequency
> > attr 3: destination_available value:
> > frequency phase amplitude polar
> > attr 4: en value: 0
> > attr 5: frequency value: 0.000000
> > attr 6: label value: ram_control
> > attr 7: operating_mode value: direct_switch
> > attr 8: operating_mode_available value:
> > direct_switch ramp_up bidirectional
> > bidirectional_continuous ramp_up_continuous
> > sequenced sequenced_continuous
> > attr 9: phase value: 0.000000
> > attr 12: sampling_frequency value: 100000000.000000
> > altvoltage2: (output)
> > 27 channel-specific attributes found:
> > attr 3: decrement_sampling_frequency value: 100000000.000000
> > attr 4: destination value: frequency
> > attr 5: destination_available value: frequency phase amplitude
> > attr 6: en value: 0
> > attr 7: frequency_decrement value: 0.000000
> > attr 8: frequency_increment value: 0.000000
> > attr 9: frequency_max value: 0.000000
> > attr 10: frequency_min value: 0.000000
> > attr 11: increment_sampling_frequency value: 100000000.000000
> > attr 12: label value: digital_ramp_generator
> > attr 13: operating_mode value: bidirectional_continuous
> > attr 14: operating_mode_available value:
> > bidirectional ramp_down ramp_up bidirectional_continuous
> > attr 15: phase_decrement value: 0.000000000
> > attr 16: phase_increment value: 0.000000000
> > attr 17: phase_max value: 0.000000000
> > attr 18: phase_min value: 0.000000000
> > attr 22: scale_decrement value: 0.000000000
> > attr 23: scale_increment value: 0.000000000
> > attr 24: scale_max value: 0.000000000
> > attr 25: scale_min value: 0.000000000
> > altvoltage0: (output)
> > 6 channel-specific attributes found:
> > attr 0: frequency value: 0.000000
> > attr 1: label value: single_tone
> > attr 2: phase value: 0.000000
> > attr 5: scale value: 0.000000
> > altvoltage4: (output)
> > 8 channel-specific attributes found:
> > attr 0: en value: 0
> > attr 1: label value: output_shift_keying
> > attr 2: pinctrl_en value: 0
> > attr 5: sampling_frequency value: 100000000.000000
> > attr 6: scale value: 0.000000
> > attr 7: scale_increment value: 0.000000
> > 3 device-specific attributes found:
> > attr 0: ram_data ERROR: Permission denied (13)
> > attr 1: sysclk_frequency value: 400000000
> > 1 debug attributes found:
> > debug attr 0: direct_reg_access value: 0x2
>
>
> This is a lot of custom attributes!
yes, specially for the digital ramp generator, where we have range
sets of increment, decrement, min and max for each DDS parameter:
scale, phase, frequency.
> It looks like a lot of these are just exposing registers directly, which
> usually isn't the best if we want something that can be reused. However,
> this looks pretty complex so coming up with something generic is probably
> not worth the effort.
Not directly, there is often a conversion whenever we are dealing
with scale, phase, frequency or sampling frequency.
> Instead, I would suggest to create a firmware file format that
> describes how the chip should be programmed. And in the driver call
> firmware_upload_register() to create a sysfs interface where the
> firmware can be loaded/replaced at runtime. This way, there is just
> one attribute write needed to set all of the parameters at once.
>
> This could probably be as simple as something that just contains
> the value of each register to be programmed and the driver can
> write all of the registers just before enabling the output.
Not sure, if that makes things simpler, specially for the application
the would interface with this. I think having the attributes as is
would be the whole point of using the IIO subsystem.
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-02-22 10:01 ` Rodrigo Alencar
@ 2026-02-22 20:32 ` David Lechner
2026-02-23 10:02 ` Nuno Sá
0 siblings, 1 reply; 46+ messages in thread
From: David Lechner @ 2026-02-22 20:32 UTC (permalink / raw)
To: Rodrigo Alencar, rodrigo.alencar, linux-iio, devicetree,
linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> On 26/02/21 02:16PM, David Lechner wrote:
>> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
>>> This patch series adds support for the Analog Devices AD9910 DDS.
>>> This is an RFC so that we can agree/discuss on the design that follows:
>>>
...
>>> represents a distinct signal path into the DDS accumulator, so the driver
>>> models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
>>
>> Generally IIO channels represent the physical input/output, not the
>> internal channels.
>
> That is part of the reason for this RFC. Dividing those top-level modes
> into channels allows for better organization, as they can operate together,
> i.e., phase or scale can be provided by single-tone profile, while
> frequency is controlled by the digital ramp generator (see Mode Priority
> section in the datasheet). Also, it allows to explore the most of standard
> ABIs like, scale, frequency, phase, sampling_frequency and enable.
> Putting everything into a single channel would make things a lot messy
> to interface with.
>
>> Ideally we would just have the one channel here with a mode selection
>> attribute. Documentation can tell us which modes use which attributes.
>>
>>> This per-channel separation allows userspace to configure each mode
>>> independently through its own set of sysfs attributes, and to
>>> enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
>>> the hardware's own mode selection architecture.
>>>
Looking at Table 5 in the datasheet really helped me understand this better.
I think this series could benefit from a documentation patch that explains
more about how the driver works with some diagrams.
So really what we have here are a bunch of digital data generators rather
than a bunch of altvotlage output channels. And the same data channels can be
mixed and match as the source for up to 3 different components of the output
(frequency, phase, amplitude) depending on the priority rules defined in
Table 5.
Digital data sources are really more like a buffer in IIO terms than a
channel. And before we added the IIO backend stuff, there wasn't really
any other digital data source/sink that I am aware of other than buffers
(but there are certainly a lot of odd corners of IIO that I haven't explored
yet, so maybe I missed some).
In a recent discussion, the idea of possibly needing a way to provide
some userspace interface to be able to tweak knobs of an IIO backend
was also brought up.
Putting those ideas together, I'm wondering if we need some new channel
type or even a whole new interface (e.g. a new sysfs directory like buffers
and events) for managing these digital data sources/sinks that are not an
IIO buffer.
I think we've seen enough of these already to know that things like a
"tone generator" and a "ramp generator" are going to be common and could
share some standard attributes.
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-02-22 20:32 ` David Lechner
@ 2026-02-23 10:02 ` Nuno Sá
2026-03-01 13:38 ` Jonathan Cameron
0 siblings, 1 reply; 46+ messages in thread
From: Nuno Sá @ 2026-02-23 10:02 UTC (permalink / raw)
To: David Lechner, Rodrigo Alencar, rodrigo.alencar, linux-iio,
devicetree, linux-kernel
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
> On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> > On 26/02/21 02:16PM, David Lechner wrote:
> > > On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > > > This patch series adds support for the Analog Devices AD9910 DDS.
> > > > This is an RFC so that we can agree/discuss on the design that follows:
> > > >
>
> ...
>
> > > > represents a distinct signal path into the DDS accumulator, so the driver
> > > > models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
> > >
> > > Generally IIO channels represent the physical input/output, not the
> > > internal channels.
> >
> > That is part of the reason for this RFC. Dividing those top-level modes
> > into channels allows for better organization, as they can operate together,
> > i.e., phase or scale can be provided by single-tone profile, while
> > frequency is controlled by the digital ramp generator (see Mode Priority
> > section in the datasheet). Also, it allows to explore the most of standard
> > ABIs like, scale, frequency, phase, sampling_frequency and enable.
> > Putting everything into a single channel would make things a lot messy
> > to interface with.
> >
> > > Ideally we would just have the one channel here with a mode selection
> > > attribute. Documentation can tell us which modes use which attributes.
> > >
> > > > This per-channel separation allows userspace to configure each mode
> > > > independently through its own set of sysfs attributes, and to
> > > > enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
> > > > the hardware's own mode selection architecture.
> > > >
>
> Looking at Table 5 in the datasheet really helped me understand this better.
> I think this series could benefit from a documentation patch that explains
> more about how the driver works with some diagrams.
>
> So really what we have here are a bunch of digital data generators rather
> than a bunch of altvotlage output channels. And the same data channels can be
> mixed and match as the source for up to 3 different components of the output
> (frequency, phase, amplitude) depending on the priority rules defined in
> Table 5.
More bellow... But note that all of the (or most of it) generators are going to
be feed into a DAC. Your output is altvoltage but maybe we can treat the
internals as voltage. Not sure.
>
> Digital data sources are really more like a buffer in IIO terms than a
> channel. And before we added the IIO backend stuff, there wasn't really
> any other digital data source/sink that I am aware of other than buffers
> (but there are certainly a lot of odd corners of IIO that I haven't explored
> yet, so maybe I missed some).
>
> In a recent discussion, the idea of possibly needing a way to provide
> some userspace interface to be able to tweak knobs of an IIO backend
> was also brought up.
>
> Putting those ideas together, I'm wondering if we need some new channel
> type or even a whole new interface (e.g. a new sysfs directory like buffers
> and events) for managing these digital data sources/sinks that are not an
> IIO buffer.
>
But what would be that channel? In the end of the day, we typically have voltage or
current DACs and a DDS primary function is indeed to generate alternating waveforms
that you then typically feed into a DAC (and in some cases from the DAC into a
power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
on the new type and I think we already have the "blocks" in IIO for dealing with this:
. frequency
. phase
. amplitude (raw + scale + offset)
But you're right that maybe it's time to think in a better way to fit them together.
Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
And spoiler alert, as you might have guessed already, the parallel port stuff is to be
used with DMA buffers (and IIO backends). At least, that was the plan IIRC. But Rodrigo
can confirm it.
> I think we've seen enough of these already to know that things like a
> "tone generator" and a "ramp generator" are going to be common and could
> share some standard attributes.
>
I tend to agree. For example, there already some DACs (with dithering) that make use of a similar
interface (but with a custom prefix). Though the end goal is different, the interface is not that
far off:
https://elixir.bootlin.com/linux/v6.19.3/source/Documentation/ABI/testing/sysfs-bus-iio-dac-ltc2688
Anyways, I knew this one would be an interesting one for upstream :)
- Nuno Sá
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-02-23 10:02 ` Nuno Sá
@ 2026-03-01 13:38 ` Jonathan Cameron
2026-03-02 10:22 ` Rodrigo Alencar
0 siblings, 1 reply; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-01 13:38 UTC (permalink / raw)
To: Nuno Sá
Cc: David Lechner, Rodrigo Alencar, rodrigo.alencar, linux-iio,
devicetree, linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Mon, 23 Feb 2026 10:02:00 +0000
Nuno Sá <noname.nuno@gmail.com> wrote:
> On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
> > On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> > > On 26/02/21 02:16PM, David Lechner wrote:
> > > > On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > > > > This patch series adds support for the Analog Devices AD9910 DDS.
> > > > > This is an RFC so that we can agree/discuss on the design that follows:
> > > > >
> >
> > ...
> >
> > > > > represents a distinct signal path into the DDS accumulator, so the driver
> > > > > models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
> > > >
> > > > Generally IIO channels represent the physical input/output, not the
> > > > internal channels.
> > >
> > > That is part of the reason for this RFC. Dividing those top-level modes
> > > into channels allows for better organization, as they can operate together,
> > > i.e., phase or scale can be provided by single-tone profile, while
> > > frequency is controlled by the digital ramp generator (see Mode Priority
> > > section in the datasheet). Also, it allows to explore the most of standard
> > > ABIs like, scale, frequency, phase, sampling_frequency and enable.
> > > Putting everything into a single channel would make things a lot messy
> > > to interface with.
> > >
> > > > Ideally we would just have the one channel here with a mode selection
> > > > attribute. Documentation can tell us which modes use which attributes.
> > > >
> > > > > This per-channel separation allows userspace to configure each mode
> > > > > independently through its own set of sysfs attributes, and to
> > > > > enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
> > > > > the hardware's own mode selection architecture.
> > > > >
> >
> > Looking at Table 5 in the datasheet really helped me understand this better.
> > I think this series could benefit from a documentation patch that explains
> > more about how the driver works with some diagrams.
> >
> > So really what we have here are a bunch of digital data generators rather
> > than a bunch of altvotlage output channels. And the same data channels can be
> > mixed and match as the source for up to 3 different components of the output
> > (frequency, phase, amplitude) depending on the priority rules defined in
> > Table 5.
>
> More bellow... But note that all of the (or most of it) generators are going to
> be feed into a DAC. Your output is altvoltage but maybe we can treat the
> internals as voltage. Not sure.
>
> >
> > Digital data sources are really more like a buffer in IIO terms than a
> > channel. And before we added the IIO backend stuff, there wasn't really
> > any other digital data source/sink that I am aware of other than buffers
> > (but there are certainly a lot of odd corners of IIO that I haven't explored
> > yet, so maybe I missed some).
> >
> > In a recent discussion, the idea of possibly needing a way to provide
> > some userspace interface to be able to tweak knobs of an IIO backend
> > was also brought up.
> >
> > Putting those ideas together, I'm wondering if we need some new channel
> > type or even a whole new interface (e.g. a new sysfs directory like buffers
> > and events) for managing these digital data sources/sinks that are not an
> > IIO buffer.
> >
>
> But what would be that channel? In the end of the day, we typically have voltage or
> current DACs and a DDS primary function is indeed to generate alternating waveforms
> that you then typically feed into a DAC (and in some cases from the DAC into a
> power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
> on the new type and I think we already have the "blocks" in IIO for dealing with this:
>
> . frequency
> . phase
> . amplitude (raw + scale + offset)
>
> But you're right that maybe it's time to think in a better way to fit them together.
> Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
> elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
>
> And spoiler alert, as you might have guessed already, the parallel port stuff is to be
> used with DMA buffers (and IIO backends). At least, that was the plan IIRC. But Rodrigo
> can confirm it.
>
> > I think we've seen enough of these already to know that things like a
> > "tone generator" and a "ramp generator" are going to be common and could
> > share some standard attributes.
> >
>
> I tend to agree. For example, there already some DACs (with dithering) that make use of a similar
> interface (but with a custom prefix). Though the end goal is different, the interface is not that
> far off:
>
>
> https://elixir.bootlin.com/linux/v6.19.3/source/Documentation/ABI/testing/sysfs-bus-iio-dac-ltc2688
>
> Anyways, I knew this one would be an interesting one for upstream :)
For history buffs, we had a bunch of DDS chips in staging at one point and never
manage to figure out the questions being raised here :( They are complex
beasts. Clarity of ABI proposal and documentation is going to be key to driving
this series forwards. In a sense the code is the easy part.
Jonathan
>
> - Nuno Sá
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-03-01 13:38 ` Jonathan Cameron
@ 2026-03-02 10:22 ` Rodrigo Alencar
2026-03-07 14:09 ` Jonathan Cameron
0 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar @ 2026-03-02 10:22 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá
Cc: David Lechner, Rodrigo Alencar, rodrigo.alencar, linux-iio,
devicetree, linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 26/03/01 01:38PM, Jonathan Cameron wrote:
> On Mon, 23 Feb 2026 10:02:00 +0000
> Nuno Sá <noname.nuno@gmail.com> wrote:
>
> > On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
> > > On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> > > > On 26/02/21 02:16PM, David Lechner wrote:
> > > > > On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > > > > > This patch series adds support for the Analog Devices AD9910 DDS.
> > > > > > This is an RFC so that we can agree/discuss on the design that follows:
> > > > > >
> > >
> > > ...
> > >
> > > > > > represents a distinct signal path into the DDS accumulator, so the driver
> > > > > > models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
> > > > >
> > > > > Generally IIO channels represent the physical input/output, not the
> > > > > internal channels.
> > > >
> > > > That is part of the reason for this RFC. Dividing those top-level modes
> > > > into channels allows for better organization, as they can operate together,
> > > > i.e., phase or scale can be provided by single-tone profile, while
> > > > frequency is controlled by the digital ramp generator (see Mode Priority
> > > > section in the datasheet). Also, it allows to explore the most of standard
> > > > ABIs like, scale, frequency, phase, sampling_frequency and enable.
> > > > Putting everything into a single channel would make things a lot messy
> > > > to interface with.
> > > >
> > > > > Ideally we would just have the one channel here with a mode selection
> > > > > attribute. Documentation can tell us which modes use which attributes.
> > > > >
> > > > > > This per-channel separation allows userspace to configure each mode
> > > > > > independently through its own set of sysfs attributes, and to
> > > > > > enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
> > > > > > the hardware's own mode selection architecture.
> > > > > >
> > >
> > > Looking at Table 5 in the datasheet really helped me understand this better.
> > > I think this series could benefit from a documentation patch that explains
> > > more about how the driver works with some diagrams.
> > >
> > > So really what we have here are a bunch of digital data generators rather
> > > than a bunch of altvotlage output channels. And the same data channels can be
> > > mixed and match as the source for up to 3 different components of the output
> > > (frequency, phase, amplitude) depending on the priority rules defined in
> > > Table 5.
> >
> > More bellow... But note that all of the (or most of it) generators are going to
> > be feed into a DAC. Your output is altvoltage but maybe we can treat the
> > internals as voltage. Not sure.
> >
> > >
> > > Digital data sources are really more like a buffer in IIO terms than a
> > > channel. And before we added the IIO backend stuff, there wasn't really
> > > any other digital data source/sink that I am aware of other than buffers
> > > (but there are certainly a lot of odd corners of IIO that I haven't explored
> > > yet, so maybe I missed some).
> > >
> > > In a recent discussion, the idea of possibly needing a way to provide
> > > some userspace interface to be able to tweak knobs of an IIO backend
> > > was also brought up.
> > >
> > > Putting those ideas together, I'm wondering if we need some new channel
> > > type or even a whole new interface (e.g. a new sysfs directory like buffers
> > > and events) for managing these digital data sources/sinks that are not an
> > > IIO buffer.
> > >
> >
> > But what would be that channel? In the end of the day, we typically have voltage or
> > current DACs and a DDS primary function is indeed to generate alternating waveforms
> > that you then typically feed into a DAC (and in some cases from the DAC into a
> > power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
> > on the new type and I think we already have the "blocks" in IIO for dealing with this:
> >
> > . frequency
> > . phase
> > . amplitude (raw + scale + offset)
> >
> > But you're right that maybe it's time to think in a better way to fit them together.
> > Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
> > elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
> >
> > And spoiler alert, as you might have guessed already, the parallel port stuff is to be
> > used with DMA buffers (and IIO backends). At least, that was the plan IIRC. But Rodrigo
> > can confirm it.
> >
> > > I think we've seen enough of these already to know that things like a
> > > "tone generator" and a "ramp generator" are going to be common and could
> > > share some standard attributes.
> > >
> >
> > I tend to agree. For example, there already some DACs (with dithering) that make use of a similar
> > interface (but with a custom prefix). Though the end goal is different, the interface is not that
> > far off:
> >
> >
> > https://elixir.bootlin.com/linux/v6.19.3/source/Documentation/ABI/testing/sysfs-bus-iio-dac-ltc2688
> >
> > Anyways, I knew this one would be an interesting one for upstream :)
>
> For history buffs, we had a bunch of DDS chips in staging at one point and never
> manage to figure out the questions being raised here :( They are complex
> beasts. Clarity of ABI proposal and documentation is going to be key to driving
> this series forwards. In a sense the code is the easy part.
Does that mean that once good documentation is provided, the presented design can
be accepted? Even though data generators/sources might not be interpreted as
altvoltage channels?
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-03-02 10:22 ` Rodrigo Alencar
@ 2026-03-07 14:09 ` Jonathan Cameron
2026-03-07 16:50 ` David Lechner
0 siblings, 1 reply; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-07 14:09 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: Nuno Sá, David Lechner, rodrigo.alencar, linux-iio,
devicetree, linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Mon, 2 Mar 2026 10:22:47 +0000
Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
> On 26/03/01 01:38PM, Jonathan Cameron wrote:
> > On Mon, 23 Feb 2026 10:02:00 +0000
> > Nuno Sá <noname.nuno@gmail.com> wrote:
> >
> > > On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
> > > > On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> > > > > On 26/02/21 02:16PM, David Lechner wrote:
> > > > > > On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > > > > > > This patch series adds support for the Analog Devices AD9910 DDS.
> > > > > > > This is an RFC so that we can agree/discuss on the design that follows:
> > > > > > >
> > > >
> > > > ...
> > > >
> > > > > > > represents a distinct signal path into the DDS accumulator, so the driver
> > > > > > > models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
> > > > > >
> > > > > > Generally IIO channels represent the physical input/output, not the
> > > > > > internal channels.
> > > > >
> > > > > That is part of the reason for this RFC. Dividing those top-level modes
> > > > > into channels allows for better organization, as they can operate together,
> > > > > i.e., phase or scale can be provided by single-tone profile, while
> > > > > frequency is controlled by the digital ramp generator (see Mode Priority
> > > > > section in the datasheet). Also, it allows to explore the most of standard
> > > > > ABIs like, scale, frequency, phase, sampling_frequency and enable.
> > > > > Putting everything into a single channel would make things a lot messy
> > > > > to interface with.
> > > > >
> > > > > > Ideally we would just have the one channel here with a mode selection
> > > > > > attribute. Documentation can tell us which modes use which attributes.
> > > > > >
> > > > > > > This per-channel separation allows userspace to configure each mode
> > > > > > > independently through its own set of sysfs attributes, and to
> > > > > > > enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
> > > > > > > the hardware's own mode selection architecture.
> > > > > > >
> > > >
> > > > Looking at Table 5 in the datasheet really helped me understand this better.
> > > > I think this series could benefit from a documentation patch that explains
> > > > more about how the driver works with some diagrams.
> > > >
> > > > So really what we have here are a bunch of digital data generators rather
> > > > than a bunch of altvotlage output channels. And the same data channels can be
> > > > mixed and match as the source for up to 3 different components of the output
> > > > (frequency, phase, amplitude) depending on the priority rules defined in
> > > > Table 5.
> > >
> > > More bellow... But note that all of the (or most of it) generators are going to
> > > be feed into a DAC. Your output is altvoltage but maybe we can treat the
> > > internals as voltage. Not sure.
> > >
> > > >
> > > > Digital data sources are really more like a buffer in IIO terms than a
> > > > channel. And before we added the IIO backend stuff, there wasn't really
> > > > any other digital data source/sink that I am aware of other than buffers
> > > > (but there are certainly a lot of odd corners of IIO that I haven't explored
> > > > yet, so maybe I missed some).
> > > >
> > > > In a recent discussion, the idea of possibly needing a way to provide
> > > > some userspace interface to be able to tweak knobs of an IIO backend
> > > > was also brought up.
> > > >
> > > > Putting those ideas together, I'm wondering if we need some new channel
> > > > type or even a whole new interface (e.g. a new sysfs directory like buffers
> > > > and events) for managing these digital data sources/sinks that are not an
> > > > IIO buffer.
> > > >
> > >
> > > But what would be that channel? In the end of the day, we typically have voltage or
> > > current DACs and a DDS primary function is indeed to generate alternating waveforms
> > > that you then typically feed into a DAC (and in some cases from the DAC into a
> > > power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
> > > on the new type and I think we already have the "blocks" in IIO for dealing with this:
> > >
> > > . frequency
> > > . phase
> > > . amplitude (raw + scale + offset)
> > >
> > > But you're right that maybe it's time to think in a better way to fit them together.
> > > Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
> > > elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
> > >
> > > And spoiler alert, as you might have guessed already, the parallel port stuff is to be
> > > used with DMA buffers (and IIO backends). At least, that was the plan IIRC. But Rodrigo
> > > can confirm it.
> > >
> > > > I think we've seen enough of these already to know that things like a
> > > > "tone generator" and a "ramp generator" are going to be common and could
> > > > share some standard attributes.
> > > >
> > >
> > > I tend to agree. For example, there already some DACs (with dithering) that make use of a similar
> > > interface (but with a custom prefix). Though the end goal is different, the interface is not that
> > > far off:
> > >
> > >
> > > https://elixir.bootlin.com/linux/v6.19.3/source/Documentation/ABI/testing/sysfs-bus-iio-dac-ltc2688
> > >
> > > Anyways, I knew this one would be an interesting one for upstream :)
> >
> > For history buffs, we had a bunch of DDS chips in staging at one point and never
> > manage to figure out the questions being raised here :( They are complex
> > beasts. Clarity of ABI proposal and documentation is going to be key to driving
> > this series forwards. In a sense the code is the easy part.
>
> Does that mean that once good documentation is provided, the presented design can
> be accepted? Even though data generators/sources might not be interpreted as
> altvoltage channels?
I'm not sure yet :( It's a pretty complex design and we haven't really come to a conclusion
on how to handle this channel 'mixing' case.
If we did go this way, we'd need to figure out a way to describe the mixing part.
So either we describe it as one channel (which is going to be really complex)
or we describe it as multiple channels but add extra ABI to make it clear they
are mixed into a single 'physical' channel.
Jonathan
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-03-07 14:09 ` Jonathan Cameron
@ 2026-03-07 16:50 ` David Lechner
2026-03-07 16:58 ` Jonathan Cameron
0 siblings, 1 reply; 46+ messages in thread
From: David Lechner @ 2026-03-07 16:50 UTC (permalink / raw)
To: Jonathan Cameron, Rodrigo Alencar
Cc: Nuno Sá, rodrigo.alencar, linux-iio, devicetree,
linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 3/7/26 8:09 AM, Jonathan Cameron wrote:
> On Mon, 2 Mar 2026 10:22:47 +0000
> Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
>
>> On 26/03/01 01:38PM, Jonathan Cameron wrote:
>>> On Mon, 23 Feb 2026 10:02:00 +0000
>>> Nuno Sá <noname.nuno@gmail.com> wrote:
>>>
>>>> On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
>>>>> On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
>>>>>> On 26/02/21 02:16PM, David Lechner wrote:
>>>>>>> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
>>>>>>>> This patch series adds support for the Analog Devices AD9910 DDS.
>>>>>>>> This is an RFC so that we can agree/discuss on the design that follows:
>>>>>>>>
>>>>>
>>>>> ...
>>>>>
>>>>>>>> represents a distinct signal path into the DDS accumulator, so the driver
>>>>>>>> models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
>>>>>>>
>>>>>>> Generally IIO channels represent the physical input/output, not the
>>>>>>> internal channels.
>>>>>>
>>>>>> That is part of the reason for this RFC. Dividing those top-level modes
>>>>>> into channels allows for better organization, as they can operate together,
>>>>>> i.e., phase or scale can be provided by single-tone profile, while
>>>>>> frequency is controlled by the digital ramp generator (see Mode Priority
>>>>>> section in the datasheet). Also, it allows to explore the most of standard
>>>>>> ABIs like, scale, frequency, phase, sampling_frequency and enable.
>>>>>> Putting everything into a single channel would make things a lot messy
>>>>>> to interface with.
>>>>>>
>>>>>>> Ideally we would just have the one channel here with a mode selection
>>>>>>> attribute. Documentation can tell us which modes use which attributes.
>>>>>>>
>>>>>>>> This per-channel separation allows userspace to configure each mode
>>>>>>>> independently through its own set of sysfs attributes, and to
>>>>>>>> enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
>>>>>>>> the hardware's own mode selection architecture.
>>>>>>>>
>>>>>
>>>>> Looking at Table 5 in the datasheet really helped me understand this better.
>>>>> I think this series could benefit from a documentation patch that explains
>>>>> more about how the driver works with some diagrams.
>>>>>
>>>>> So really what we have here are a bunch of digital data generators rather
>>>>> than a bunch of altvotlage output channels. And the same data channels can be
>>>>> mixed and match as the source for up to 3 different components of the output
>>>>> (frequency, phase, amplitude) depending on the priority rules defined in
>>>>> Table 5.
>>>>
>>>> More bellow... But note that all of the (or most of it) generators are going to
>>>> be feed into a DAC. Your output is altvoltage but maybe we can treat the
>>>> internals as voltage. Not sure.
>>>>
>>>>>
>>>>> Digital data sources are really more like a buffer in IIO terms than a
>>>>> channel. And before we added the IIO backend stuff, there wasn't really
>>>>> any other digital data source/sink that I am aware of other than buffers
>>>>> (but there are certainly a lot of odd corners of IIO that I haven't explored
>>>>> yet, so maybe I missed some).
>>>>>
>>>>> In a recent discussion, the idea of possibly needing a way to provide
>>>>> some userspace interface to be able to tweak knobs of an IIO backend
>>>>> was also brought up.
>>>>>
>>>>> Putting those ideas together, I'm wondering if we need some new channel
>>>>> type or even a whole new interface (e.g. a new sysfs directory like buffers
>>>>> and events) for managing these digital data sources/sinks that are not an
>>>>> IIO buffer.
>>>>>
>>>>
>>>> But what would be that channel? In the end of the day, we typically have voltage or
>>>> current DACs and a DDS primary function is indeed to generate alternating waveforms
>>>> that you then typically feed into a DAC (and in some cases from the DAC into a
>>>> power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
>>>> on the new type and I think we already have the "blocks" in IIO for dealing with this:
>>>>
>>>> . frequency
>>>> . phase
>>>> . amplitude (raw + scale + offset)
>>>>
>>>> But you're right that maybe it's time to think in a better way to fit them together.
>>>> Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
>>>> elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
>>>>
>>>> And spoiler alert, as you might have guessed already, the parallel port stuff is to be
>>>> used with DMA buffers (and IIO backends). At least, that was the plan IIRC. But Rodrigo
>>>> can confirm it.
>>>>
>>>>> I think we've seen enough of these already to know that things like a
>>>>> "tone generator" and a "ramp generator" are going to be common and could
>>>>> share some standard attributes.
>>>>>
>>>>
>>>> I tend to agree. For example, there already some DACs (with dithering) that make use of a similar
>>>> interface (but with a custom prefix). Though the end goal is different, the interface is not that
>>>> far off:
>>>>
>>>>
>>>> https://elixir.bootlin.com/linux/v6.19.3/source/Documentation/ABI/testing/sysfs-bus-iio-dac-ltc2688
>>>>
>>>> Anyways, I knew this one would be an interesting one for upstream :)
>>>
>>> For history buffs, we had a bunch of DDS chips in staging at one point and never
>>> manage to figure out the questions being raised here :( They are complex
>>> beasts. Clarity of ABI proposal and documentation is going to be key to driving
>>> this series forwards. In a sense the code is the easy part.
>>
>> Does that mean that once good documentation is provided, the presented design can
>> be accepted? Even though data generators/sources might not be interpreted as
>> altvoltage channels?
>
> I'm not sure yet :( It's a pretty complex design and we haven't really come to a conclusion
> on how to handle this channel 'mixing' case.
>
> If we did go this way, we'd need to figure out a way to describe the mixing part.
> So either we describe it as one channel (which is going to be really complex)
> or we describe it as multiple channels but add extra ABI to make it clear they
> are mixed into a single 'physical' channel.
>
> Jonathan
>
>>
>
Some ideas have crossed my mind, like adding new option to the in_/out_
prefix for "internal" channels. But I it would take a long time to teach
existing generic userspace libraries/tools about this.
What has popped into my head just now is that perhaps we could do like
Rodrigo is proposing here reusing existing channels and standard attributes
as much as possible and add a new "subcomponent_of" attribute to provide
the link, similar to "current_trigger" for triggers.
This way, it would still work with existing userspace tools (even if it
looks a bit confusing). And userspace tools could eventually be taught
to present the channels as a tree-like structure with the main channel
and subcomponents nested under it.
We would want to spell out up front what all of the anticipated ways of
using it are. For example, I suspect eventually someone will want this
attribute to be writeable to assign a specific limited resource to a
specific channel. An I expect that we would eventually see something were
a single subcomponent is shared between multiple physical channels. In
this case, we would want the value of the "subcomponent_of" attribute to
be able to be a list.
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-03-07 16:50 ` David Lechner
@ 2026-03-07 16:58 ` Jonathan Cameron
2026-03-07 18:54 ` Rodrigo Alencar
0 siblings, 1 reply; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-07 16:58 UTC (permalink / raw)
To: David Lechner
Cc: Rodrigo Alencar, Nuno Sá, rodrigo.alencar, linux-iio,
devicetree, linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Sat, 7 Mar 2026 10:50:14 -0600
David Lechner <dlechner@baylibre.com> wrote:
> On 3/7/26 8:09 AM, Jonathan Cameron wrote:
> > On Mon, 2 Mar 2026 10:22:47 +0000
> > Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
> >
> >> On 26/03/01 01:38PM, Jonathan Cameron wrote:
> >>> On Mon, 23 Feb 2026 10:02:00 +0000
> >>> Nuno Sá <noname.nuno@gmail.com> wrote:
> >>>
> >>>> On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
> >>>>> On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> >>>>>> On 26/02/21 02:16PM, David Lechner wrote:
> >>>>>>> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> >>>>>>>> This patch series adds support for the Analog Devices AD9910 DDS.
> >>>>>>>> This is an RFC so that we can agree/discuss on the design that follows:
> >>>>>>>>
> >>>>>
> >>>>> ...
> >>>>>
> >>>>>>>> represents a distinct signal path into the DDS accumulator, so the driver
> >>>>>>>> models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
> >>>>>>>
> >>>>>>> Generally IIO channels represent the physical input/output, not the
> >>>>>>> internal channels.
> >>>>>>
> >>>>>> That is part of the reason for this RFC. Dividing those top-level modes
> >>>>>> into channels allows for better organization, as they can operate together,
> >>>>>> i.e., phase or scale can be provided by single-tone profile, while
> >>>>>> frequency is controlled by the digital ramp generator (see Mode Priority
> >>>>>> section in the datasheet). Also, it allows to explore the most of standard
> >>>>>> ABIs like, scale, frequency, phase, sampling_frequency and enable.
> >>>>>> Putting everything into a single channel would make things a lot messy
> >>>>>> to interface with.
> >>>>>>
> >>>>>>> Ideally we would just have the one channel here with a mode selection
> >>>>>>> attribute. Documentation can tell us which modes use which attributes.
> >>>>>>>
> >>>>>>>> This per-channel separation allows userspace to configure each mode
> >>>>>>>> independently through its own set of sysfs attributes, and to
> >>>>>>>> enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
> >>>>>>>> the hardware's own mode selection architecture.
> >>>>>>>>
> >>>>>
> >>>>> Looking at Table 5 in the datasheet really helped me understand this better.
> >>>>> I think this series could benefit from a documentation patch that explains
> >>>>> more about how the driver works with some diagrams.
> >>>>>
> >>>>> So really what we have here are a bunch of digital data generators rather
> >>>>> than a bunch of altvotlage output channels. And the same data channels can be
> >>>>> mixed and match as the source for up to 3 different components of the output
> >>>>> (frequency, phase, amplitude) depending on the priority rules defined in
> >>>>> Table 5.
> >>>>
> >>>> More bellow... But note that all of the (or most of it) generators are going to
> >>>> be feed into a DAC. Your output is altvoltage but maybe we can treat the
> >>>> internals as voltage. Not sure.
> >>>>
> >>>>>
> >>>>> Digital data sources are really more like a buffer in IIO terms than a
> >>>>> channel. And before we added the IIO backend stuff, there wasn't really
> >>>>> any other digital data source/sink that I am aware of other than buffers
> >>>>> (but there are certainly a lot of odd corners of IIO that I haven't explored
> >>>>> yet, so maybe I missed some).
> >>>>>
> >>>>> In a recent discussion, the idea of possibly needing a way to provide
> >>>>> some userspace interface to be able to tweak knobs of an IIO backend
> >>>>> was also brought up.
> >>>>>
> >>>>> Putting those ideas together, I'm wondering if we need some new channel
> >>>>> type or even a whole new interface (e.g. a new sysfs directory like buffers
> >>>>> and events) for managing these digital data sources/sinks that are not an
> >>>>> IIO buffer.
> >>>>>
> >>>>
> >>>> But what would be that channel? In the end of the day, we typically have voltage or
> >>>> current DACs and a DDS primary function is indeed to generate alternating waveforms
> >>>> that you then typically feed into a DAC (and in some cases from the DAC into a
> >>>> power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
> >>>> on the new type and I think we already have the "blocks" in IIO for dealing with this:
> >>>>
> >>>> . frequency
> >>>> . phase
> >>>> . amplitude (raw + scale + offset)
> >>>>
> >>>> But you're right that maybe it's time to think in a better way to fit them together.
> >>>> Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
> >>>> elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
> >>>>
> >>>> And spoiler alert, as you might have guessed already, the parallel port stuff is to be
> >>>> used with DMA buffers (and IIO backends). At least, that was the plan IIRC. But Rodrigo
> >>>> can confirm it.
> >>>>
> >>>>> I think we've seen enough of these already to know that things like a
> >>>>> "tone generator" and a "ramp generator" are going to be common and could
> >>>>> share some standard attributes.
> >>>>>
> >>>>
> >>>> I tend to agree. For example, there already some DACs (with dithering) that make use of a similar
> >>>> interface (but with a custom prefix). Though the end goal is different, the interface is not that
> >>>> far off:
> >>>>
> >>>>
> >>>> https://elixir.bootlin.com/linux/v6.19.3/source/Documentation/ABI/testing/sysfs-bus-iio-dac-ltc2688
> >>>>
> >>>> Anyways, I knew this one would be an interesting one for upstream :)
> >>>
> >>> For history buffs, we had a bunch of DDS chips in staging at one point and never
> >>> manage to figure out the questions being raised here :( They are complex
> >>> beasts. Clarity of ABI proposal and documentation is going to be key to driving
> >>> this series forwards. In a sense the code is the easy part.
> >>
> >> Does that mean that once good documentation is provided, the presented design can
> >> be accepted? Even though data generators/sources might not be interpreted as
> >> altvoltage channels?
> >
> > I'm not sure yet :( It's a pretty complex design and we haven't really come to a conclusion
> > on how to handle this channel 'mixing' case.
> >
> > If we did go this way, we'd need to figure out a way to describe the mixing part.
> > So either we describe it as one channel (which is going to be really complex)
> > or we describe it as multiple channels but add extra ABI to make it clear they
> > are mixed into a single 'physical' channel.
> >
> > Jonathan
> >
> >>
> >
>
> Some ideas have crossed my mind, like adding new option to the in_/out_
> prefix for "internal" channels. But I it would take a long time to teach
> existing generic userspace libraries/tools about this.
>
> What has popped into my head just now is that perhaps we could do like
> Rodrigo is proposing here reusing existing channels and standard attributes
> as much as possible and add a new "subcomponent_of" attribute to provide
> the link, similar to "current_trigger" for triggers.
>
> This way, it would still work with existing userspace tools (even if it
> looks a bit confusing). And userspace tools could eventually be taught
> to present the channels as a tree-like structure with the main channel
> and subcomponents nested under it.
>
> We would want to spell out up front what all of the anticipated ways of
> using it are. For example, I suspect eventually someone will want this
> attribute to be writeable to assign a specific limited resource to a
> specific channel. An I expect that we would eventually see something were
> a single subcomponent is shared between multiple physical channels. In
> this case, we would want the value of the "subcomponent_of" attribute to
> be able to be a list.
Something along those lines might work. I'd not thought about the case
of one 'internal' going to multiple 'external'. Otherwise I was wondering
if something informal related to labels would work. We've done that where
we've been associating things like voltage and power measurement from a single
pin. It's rather adhoc though. Possibly we could roll it into a newer
more general scheme.
If the association is done as meta data attributes alongside existing
channels then as you say existing tools will kind of work, just need some
human understanding of what is actually being controlled until they catch
up with the newer schemes.
Lots of ways we could actually represent the graphs. Going to take some
figuring out!
J
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-03-07 16:58 ` Jonathan Cameron
@ 2026-03-07 18:54 ` Rodrigo Alencar
2026-03-07 20:01 ` David Lechner
0 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar @ 2026-03-07 18:54 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner
Cc: Rodrigo Alencar, Nuno Sá, rodrigo.alencar, linux-iio,
devicetree, linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 26/03/07 04:58PM, Jonathan Cameron wrote:
> On Sat, 7 Mar 2026 10:50:14 -0600
> David Lechner <dlechner@baylibre.com> wrote:
>
> > On 3/7/26 8:09 AM, Jonathan Cameron wrote:
> > > On Mon, 2 Mar 2026 10:22:47 +0000
> > > Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
> > >
> > >> On 26/03/01 01:38PM, Jonathan Cameron wrote:
> > >>> On Mon, 23 Feb 2026 10:02:00 +0000
> > >>> Nuno Sá <noname.nuno@gmail.com> wrote:
> > >>>
> > >>>> On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
> > >>>>> On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> > >>>>>> On 26/02/21 02:16PM, David Lechner wrote:
> > >>>>>>> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > >>>>>>>> This patch series adds support for the Analog Devices AD9910 DDS.
> > >>>>>>>> This is an RFC so that we can agree/discuss on the design that follows:
> > >>>>>>>>
> > >>>>>
> > >>>>> ...
> > >>>>>
> > >>>>>>>> represents a distinct signal path into the DDS accumulator, so the driver
> > >>>>>>>> models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
> > >>>>>>>
> > >>>>>>> Generally IIO channels represent the physical input/output, not the
> > >>>>>>> internal channels.
> > >>>>>>
> > >>>>>> That is part of the reason for this RFC. Dividing those top-level modes
> > >>>>>> into channels allows for better organization, as they can operate together,
> > >>>>>> i.e., phase or scale can be provided by single-tone profile, while
> > >>>>>> frequency is controlled by the digital ramp generator (see Mode Priority
> > >>>>>> section in the datasheet). Also, it allows to explore the most of standard
> > >>>>>> ABIs like, scale, frequency, phase, sampling_frequency and enable.
> > >>>>>> Putting everything into a single channel would make things a lot messy
> > >>>>>> to interface with.
> > >>>>>>
> > >>>>>>> Ideally we would just have the one channel here with a mode selection
> > >>>>>>> attribute. Documentation can tell us which modes use which attributes.
> > >>>>>>>
> > >>>>>>>> This per-channel separation allows userspace to configure each mode
> > >>>>>>>> independently through its own set of sysfs attributes, and to
> > >>>>>>>> enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
> > >>>>>>>> the hardware's own mode selection architecture.
> > >>>>>>>>
> > >>>>>
> > >>>>> Looking at Table 5 in the datasheet really helped me understand this better.
> > >>>>> I think this series could benefit from a documentation patch that explains
> > >>>>> more about how the driver works with some diagrams.
> > >>>>>
> > >>>>> So really what we have here are a bunch of digital data generators rather
> > >>>>> than a bunch of altvotlage output channels. And the same data channels can be
> > >>>>> mixed and match as the source for up to 3 different components of the output
> > >>>>> (frequency, phase, amplitude) depending on the priority rules defined in
> > >>>>> Table 5.
> > >>>>
> > >>>> More bellow... But note that all of the (or most of it) generators are going to
> > >>>> be feed into a DAC. Your output is altvoltage but maybe we can treat the
> > >>>> internals as voltage. Not sure.
> > >>>>
> > >>>>>
> > >>>>> Digital data sources are really more like a buffer in IIO terms than a
> > >>>>> channel. And before we added the IIO backend stuff, there wasn't really
> > >>>>> any other digital data source/sink that I am aware of other than buffers
> > >>>>> (but there are certainly a lot of odd corners of IIO that I haven't explored
> > >>>>> yet, so maybe I missed some).
> > >>>>>
> > >>>>> In a recent discussion, the idea of possibly needing a way to provide
> > >>>>> some userspace interface to be able to tweak knobs of an IIO backend
> > >>>>> was also brought up.
> > >>>>>
> > >>>>> Putting those ideas together, I'm wondering if we need some new channel
> > >>>>> type or even a whole new interface (e.g. a new sysfs directory like buffers
> > >>>>> and events) for managing these digital data sources/sinks that are not an
> > >>>>> IIO buffer.
> > >>>>>
> > >>>>
> > >>>> But what would be that channel? In the end of the day, we typically have voltage or
> > >>>> current DACs and a DDS primary function is indeed to generate alternating waveforms
> > >>>> that you then typically feed into a DAC (and in some cases from the DAC into a
> > >>>> power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
> > >>>> on the new type and I think we already have the "blocks" in IIO for dealing with this:
> > >>>>
> > >>>> . frequency
> > >>>> . phase
> > >>>> . amplitude (raw + scale + offset)
> > >>>>
> > >>>> But you're right that maybe it's time to think in a better way to fit them together.
> > >>>> Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
> > >>>> elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
> > >>>>
> > >>>> And spoiler alert, as you might have guessed already, the parallel port stuff is to be
> > >>>> used with DMA buffers (and IIO backends). At least, that was the plan IIRC. But Rodrigo
> > >>>> can confirm it.
> > >>>>
> > >>>>> I think we've seen enough of these already to know that things like a
> > >>>>> "tone generator" and a "ramp generator" are going to be common and could
> > >>>>> share some standard attributes.
> > >>>>>
> > >>>>
> > >>>> I tend to agree. For example, there already some DACs (with dithering) that make use of a similar
> > >>>> interface (but with a custom prefix). Though the end goal is different, the interface is not that
> > >>>> far off:
> > >>>>
> > >>>>
> > >>>> https://elixir.bootlin.com/linux/v6.19.3/source/Documentation/ABI/testing/sysfs-bus-iio-dac-ltc2688
> > >>>>
> > >>>> Anyways, I knew this one would be an interesting one for upstream :)
> > >>>
> > >>> For history buffs, we had a bunch of DDS chips in staging at one point and never
> > >>> manage to figure out the questions being raised here :( They are complex
> > >>> beasts. Clarity of ABI proposal and documentation is going to be key to driving
> > >>> this series forwards. In a sense the code is the easy part.
> > >>
> > >> Does that mean that once good documentation is provided, the presented design can
> > >> be accepted? Even though data generators/sources might not be interpreted as
> > >> altvoltage channels?
> > >
> > > I'm not sure yet :( It's a pretty complex design and we haven't really come to a conclusion
> > > on how to handle this channel 'mixing' case.
> > >
> > > If we did go this way, we'd need to figure out a way to describe the mixing part.
> > > So either we describe it as one channel (which is going to be really complex)
> > > or we describe it as multiple channels but add extra ABI to make it clear they
> > > are mixed into a single 'physical' channel.
> > >
> > > Jonathan
> > >
> > >>
> > >
> >
> > Some ideas have crossed my mind, like adding new option to the in_/out_
> > prefix for "internal" channels. But I it would take a long time to teach
> > existing generic userspace libraries/tools about this.
> >
> > What has popped into my head just now is that perhaps we could do like
> > Rodrigo is proposing here reusing existing channels and standard attributes
> > as much as possible and add a new "subcomponent_of" attribute to provide
> > the link, similar to "current_trigger" for triggers.
> >
> > This way, it would still work with existing userspace tools (even if it
> > looks a bit confusing). And userspace tools could eventually be taught
> > to present the channels as a tree-like structure with the main channel
> > and subcomponents nested under it.
> >
> > We would want to spell out up front what all of the anticipated ways of
> > using it are. For example, I suspect eventually someone will want this
> > attribute to be writeable to assign a specific limited resource to a
> > specific channel. An I expect that we would eventually see something were
> > a single subcomponent is shared between multiple physical channels. In
> > this case, we would want the value of the "subcomponent_of" attribute to
> > be able to be a list.
>
> Something along those lines might work. I'd not thought about the case
> of one 'internal' going to multiple 'external'. Otherwise I was wondering
> if something informal related to labels would work. We've done that where
> we've been associating things like voltage and power measurement from a single
> pin. It's rather adhoc though. Possibly we could roll it into a newer
> more general scheme.
>
> If the association is done as meta data attributes alongside existing
> channels then as you say existing tools will kind of work, just need some
> human understanding of what is actually being controlled until they catch
> up with the newer schemes.
>
> Lots of ways we could actually represent the graphs. Going to take some
> figuring out!
I like the idea of subchannels to create logical tree-structures. It opens
up for other possibilities.
I wonder if the varying channel index would still confuse a user, even
with this metadata attribute, like:
- out_altvoltage0
- out_altvoltage1
- out_altvoltage1_subcomponent_of = out_altvoltage0
would there be a different way to name the full_postfix in
__iio_device_attr_init() that would allow to keep channel index the same
(e.g. altvoltage0 for multiple internal sub-channel) and still be
compatible with userspace tools? I don't know, something like:
- out_altvoltage0
- out_altvoltage0_0
- out_altvoltage0_1
- out_altvoltage0_2
userspace tools would understand out_altvoltage0_0_frequency as:
- direction: out
- type: altvoltage
- channel idx: 0
- attr name: 0_frequency
and that would be a problem?
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-03-07 18:54 ` Rodrigo Alencar
@ 2026-03-07 20:01 ` David Lechner
2026-03-08 18:12 ` Jonathan Cameron
0 siblings, 1 reply; 46+ messages in thread
From: David Lechner @ 2026-03-07 20:01 UTC (permalink / raw)
To: Rodrigo Alencar, Jonathan Cameron
Cc: Nuno Sá, rodrigo.alencar, linux-iio, devicetree,
linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 3/7/26 12:54 PM, Rodrigo Alencar wrote:
> On 26/03/07 04:58PM, Jonathan Cameron wrote:
>> On Sat, 7 Mar 2026 10:50:14 -0600
>> David Lechner <dlechner@baylibre.com> wrote:
>>
>>> On 3/7/26 8:09 AM, Jonathan Cameron wrote:
>>>> On Mon, 2 Mar 2026 10:22:47 +0000
>>>> Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
>>>>
>>>>> On 26/03/01 01:38PM, Jonathan Cameron wrote:
>>>>>> On Mon, 23 Feb 2026 10:02:00 +0000
>>>>>> Nuno Sá <noname.nuno@gmail.com> wrote:
>>>>>>
>>>>>>> On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
>>>>>>>> On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
>>>>>>>>> On 26/02/21 02:16PM, David Lechner wrote:
>>>>>>>>>> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
>>>>>>>>>>> This patch series adds support for the Analog Devices AD9910 DDS.
>>>>>>>>>>> This is an RFC so that we can agree/discuss on the design that follows:
>>>>>>>>>>>
>>>>>>>>
>>>>>>>> ...
>>>>>>>>
>>>>>>>>>>> represents a distinct signal path into the DDS accumulator, so the driver
>>>>>>>>>>> models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
>>>>>>>>>>
>>>>>>>>>> Generally IIO channels represent the physical input/output, not the
>>>>>>>>>> internal channels.
>>>>>>>>>
>>>>>>>>> That is part of the reason for this RFC. Dividing those top-level modes
>>>>>>>>> into channels allows for better organization, as they can operate together,
>>>>>>>>> i.e., phase or scale can be provided by single-tone profile, while
>>>>>>>>> frequency is controlled by the digital ramp generator (see Mode Priority
>>>>>>>>> section in the datasheet). Also, it allows to explore the most of standard
>>>>>>>>> ABIs like, scale, frequency, phase, sampling_frequency and enable.
>>>>>>>>> Putting everything into a single channel would make things a lot messy
>>>>>>>>> to interface with.
>>>>>>>>>
>>>>>>>>>> Ideally we would just have the one channel here with a mode selection
>>>>>>>>>> attribute. Documentation can tell us which modes use which attributes.
>>>>>>>>>>
>>>>>>>>>>> This per-channel separation allows userspace to configure each mode
>>>>>>>>>>> independently through its own set of sysfs attributes, and to
>>>>>>>>>>> enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
>>>>>>>>>>> the hardware's own mode selection architecture.
>>>>>>>>>>>
>>>>>>>>
>>>>>>>> Looking at Table 5 in the datasheet really helped me understand this better.
>>>>>>>> I think this series could benefit from a documentation patch that explains
>>>>>>>> more about how the driver works with some diagrams.
>>>>>>>>
>>>>>>>> So really what we have here are a bunch of digital data generators rather
>>>>>>>> than a bunch of altvotlage output channels. And the same data channels can be
>>>>>>>> mixed and match as the source for up to 3 different components of the output
>>>>>>>> (frequency, phase, amplitude) depending on the priority rules defined in
>>>>>>>> Table 5.
>>>>>>>
>>>>>>> More bellow... But note that all of the (or most of it) generators are going to
>>>>>>> be feed into a DAC. Your output is altvoltage but maybe we can treat the
>>>>>>> internals as voltage. Not sure.
>>>>>>>
>>>>>>>>
>>>>>>>> Digital data sources are really more like a buffer in IIO terms than a
>>>>>>>> channel. And before we added the IIO backend stuff, there wasn't really
>>>>>>>> any other digital data source/sink that I am aware of other than buffers
>>>>>>>> (but there are certainly a lot of odd corners of IIO that I haven't explored
>>>>>>>> yet, so maybe I missed some).
>>>>>>>>
>>>>>>>> In a recent discussion, the idea of possibly needing a way to provide
>>>>>>>> some userspace interface to be able to tweak knobs of an IIO backend
>>>>>>>> was also brought up.
>>>>>>>>
>>>>>>>> Putting those ideas together, I'm wondering if we need some new channel
>>>>>>>> type or even a whole new interface (e.g. a new sysfs directory like buffers
>>>>>>>> and events) for managing these digital data sources/sinks that are not an
>>>>>>>> IIO buffer.
>>>>>>>>
>>>>>>>
>>>>>>> But what would be that channel? In the end of the day, we typically have voltage or
>>>>>>> current DACs and a DDS primary function is indeed to generate alternating waveforms
>>>>>>> that you then typically feed into a DAC (and in some cases from the DAC into a
>>>>>>> power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
>>>>>>> on the new type and I think we already have the "blocks" in IIO for dealing with this:
>>>>>>>
>>>>>>> . frequency
>>>>>>> . phase
>>>>>>> . amplitude (raw + scale + offset)
>>>>>>>
>>>>>>> But you're right that maybe it's time to think in a better way to fit them together.
>>>>>>> Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
>>>>>>> elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
>>>>>>>
>>>>>>> And spoiler alert, as you might have guessed already, the parallel port stuff is to be
>>>>>>> used with DMA buffers (and IIO backends). At least, that was the plan IIRC. But Rodrigo
>>>>>>> can confirm it.
>>>>>>>
>>>>>>>> I think we've seen enough of these already to know that things like a
>>>>>>>> "tone generator" and a "ramp generator" are going to be common and could
>>>>>>>> share some standard attributes.
>>>>>>>>
>>>>>>>
>>>>>>> I tend to agree. For example, there already some DACs (with dithering) that make use of a similar
>>>>>>> interface (but with a custom prefix). Though the end goal is different, the interface is not that
>>>>>>> far off:
>>>>>>>
>>>>>>>
>>>>>>> https://elixir.bootlin.com/linux/v6.19.3/source/Documentation/ABI/testing/sysfs-bus-iio-dac-ltc2688
>>>>>>>
>>>>>>> Anyways, I knew this one would be an interesting one for upstream :)
>>>>>>
>>>>>> For history buffs, we had a bunch of DDS chips in staging at one point and never
>>>>>> manage to figure out the questions being raised here :( They are complex
>>>>>> beasts. Clarity of ABI proposal and documentation is going to be key to driving
>>>>>> this series forwards. In a sense the code is the easy part.
>>>>>
>>>>> Does that mean that once good documentation is provided, the presented design can
>>>>> be accepted? Even though data generators/sources might not be interpreted as
>>>>> altvoltage channels?
>>>>
>>>> I'm not sure yet :( It's a pretty complex design and we haven't really come to a conclusion
>>>> on how to handle this channel 'mixing' case.
>>>>
>>>> If we did go this way, we'd need to figure out a way to describe the mixing part.
>>>> So either we describe it as one channel (which is going to be really complex)
>>>> or we describe it as multiple channels but add extra ABI to make it clear they
>>>> are mixed into a single 'physical' channel.
>>>>
>>>> Jonathan
>>>>
>>>>>
>>>>
>>>
>>> Some ideas have crossed my mind, like adding new option to the in_/out_
>>> prefix for "internal" channels. But I it would take a long time to teach
>>> existing generic userspace libraries/tools about this.
>>>
>>> What has popped into my head just now is that perhaps we could do like
>>> Rodrigo is proposing here reusing existing channels and standard attributes
>>> as much as possible and add a new "subcomponent_of" attribute to provide
>>> the link, similar to "current_trigger" for triggers.
>>>
>>> This way, it would still work with existing userspace tools (even if it
>>> looks a bit confusing). And userspace tools could eventually be taught
>>> to present the channels as a tree-like structure with the main channel
>>> and subcomponents nested under it.
>>>
>>> We would want to spell out up front what all of the anticipated ways of
>>> using it are. For example, I suspect eventually someone will want this
>>> attribute to be writeable to assign a specific limited resource to a
>>> specific channel. An I expect that we would eventually see something were
>>> a single subcomponent is shared between multiple physical channels. In
>>> this case, we would want the value of the "subcomponent_of" attribute to
>>> be able to be a list.
>>
>> Something along those lines might work. I'd not thought about the case
>> of one 'internal' going to multiple 'external'. Otherwise I was wondering
>> if something informal related to labels would work. We've done that where
>> we've been associating things like voltage and power measurement from a single
>> pin. It's rather adhoc though. Possibly we could roll it into a newer
>> more general scheme.
Hmm... in this case, it sounds more flat where there isn't a clear channel
that would be the "root" of a tree relation.
>>
>> If the association is done as meta data attributes alongside existing
>> channels then as you say existing tools will kind of work, just need some
>> human understanding of what is actually being controlled until they catch
>> up with the newer schemes.
>>
>> Lots of ways we could actually represent the graphs. Going to take some
>> figuring out!
>
> I like the idea of subchannels to create logical tree-structures. It opens
> up for other possibilities.
>
> I wonder if the varying channel index would still confuse a user, even
> with this metadata attribute, like:
> - out_altvoltage0
> - out_altvoltage1
> - out_altvoltage1_subcomponent_of = out_altvoltage0
>
> would there be a different way to name the full_postfix in
> __iio_device_attr_init() that would allow to keep channel index the same
> (e.g. altvoltage0 for multiple internal sub-channel) and still be
> compatible with userspace tools? I don't know, something like:
> - out_altvoltage0
> - out_altvoltage0_0
> - out_altvoltage0_1
> - out_altvoltage0_2
>
> userspace tools would understand out_altvoltage0_0_frequency as:
> - direction: out
> - type: altvoltage
> - channel idx: 0
> - attr name: 0_frequency
>
> and that would be a problem?
>
I have a feeling that could be problematic for existing attribute
parsers.
How about using higher numbered channel indexes instead?
out_altvoltage100_* = physical channel
out_altvoltage101_* = subcomponent
out_altvoltage102_* = subcomponent
...
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-03-07 20:01 ` David Lechner
@ 2026-03-08 18:12 ` Jonathan Cameron
2026-03-09 9:52 ` Rodrigo Alencar
0 siblings, 1 reply; 46+ messages in thread
From: Jonathan Cameron @ 2026-03-08 18:12 UTC (permalink / raw)
To: David Lechner
Cc: Rodrigo Alencar, Nuno Sá, rodrigo.alencar, linux-iio,
devicetree, linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On Sat, 7 Mar 2026 14:01:14 -0600
David Lechner <dlechner@baylibre.com> wrote:
> On 3/7/26 12:54 PM, Rodrigo Alencar wrote:
> > On 26/03/07 04:58PM, Jonathan Cameron wrote:
> >> On Sat, 7 Mar 2026 10:50:14 -0600
> >> David Lechner <dlechner@baylibre.com> wrote:
> >>
> >>> On 3/7/26 8:09 AM, Jonathan Cameron wrote:
> >>>> On Mon, 2 Mar 2026 10:22:47 +0000
> >>>> Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
> >>>>
> >>>>> On 26/03/01 01:38PM, Jonathan Cameron wrote:
> >>>>>> On Mon, 23 Feb 2026 10:02:00 +0000
> >>>>>> Nuno Sá <noname.nuno@gmail.com> wrote:
> >>>>>>
> >>>>>>> On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
> >>>>>>>> On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> >>>>>>>>> On 26/02/21 02:16PM, David Lechner wrote:
> >>>>>>>>>> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> >>>>>>>>>>> This patch series adds support for the Analog Devices AD9910 DDS.
> >>>>>>>>>>> This is an RFC so that we can agree/discuss on the design that follows:
> >>>>>>>>>>>
> >>>>>>>>
> >>>>>>>> ...
> >>>>>>>>
> >>>>>>>>>>> represents a distinct signal path into the DDS accumulator, so the driver
> >>>>>>>>>>> models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
> >>>>>>>>>>
> >>>>>>>>>> Generally IIO channels represent the physical input/output, not the
> >>>>>>>>>> internal channels.
> >>>>>>>>>
> >>>>>>>>> That is part of the reason for this RFC. Dividing those top-level modes
> >>>>>>>>> into channels allows for better organization, as they can operate together,
> >>>>>>>>> i.e., phase or scale can be provided by single-tone profile, while
> >>>>>>>>> frequency is controlled by the digital ramp generator (see Mode Priority
> >>>>>>>>> section in the datasheet). Also, it allows to explore the most of standard
> >>>>>>>>> ABIs like, scale, frequency, phase, sampling_frequency and enable.
> >>>>>>>>> Putting everything into a single channel would make things a lot messy
> >>>>>>>>> to interface with.
> >>>>>>>>>
> >>>>>>>>>> Ideally we would just have the one channel here with a mode selection
> >>>>>>>>>> attribute. Documentation can tell us which modes use which attributes.
> >>>>>>>>>>
> >>>>>>>>>>> This per-channel separation allows userspace to configure each mode
> >>>>>>>>>>> independently through its own set of sysfs attributes, and to
> >>>>>>>>>>> enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
> >>>>>>>>>>> the hardware's own mode selection architecture.
> >>>>>>>>>>>
> >>>>>>>>
> >>>>>>>> Looking at Table 5 in the datasheet really helped me understand this better.
> >>>>>>>> I think this series could benefit from a documentation patch that explains
> >>>>>>>> more about how the driver works with some diagrams.
> >>>>>>>>
> >>>>>>>> So really what we have here are a bunch of digital data generators rather
> >>>>>>>> than a bunch of altvotlage output channels. And the same data channels can be
> >>>>>>>> mixed and match as the source for up to 3 different components of the output
> >>>>>>>> (frequency, phase, amplitude) depending on the priority rules defined in
> >>>>>>>> Table 5.
> >>>>>>>
> >>>>>>> More bellow... But note that all of the (or most of it) generators are going to
> >>>>>>> be feed into a DAC. Your output is altvoltage but maybe we can treat the
> >>>>>>> internals as voltage. Not sure.
> >>>>>>>
> >>>>>>>>
> >>>>>>>> Digital data sources are really more like a buffer in IIO terms than a
> >>>>>>>> channel. And before we added the IIO backend stuff, there wasn't really
> >>>>>>>> any other digital data source/sink that I am aware of other than buffers
> >>>>>>>> (but there are certainly a lot of odd corners of IIO that I haven't explored
> >>>>>>>> yet, so maybe I missed some).
> >>>>>>>>
> >>>>>>>> In a recent discussion, the idea of possibly needing a way to provide
> >>>>>>>> some userspace interface to be able to tweak knobs of an IIO backend
> >>>>>>>> was also brought up.
> >>>>>>>>
> >>>>>>>> Putting those ideas together, I'm wondering if we need some new channel
> >>>>>>>> type or even a whole new interface (e.g. a new sysfs directory like buffers
> >>>>>>>> and events) for managing these digital data sources/sinks that are not an
> >>>>>>>> IIO buffer.
> >>>>>>>>
> >>>>>>>
> >>>>>>> But what would be that channel? In the end of the day, we typically have voltage or
> >>>>>>> current DACs and a DDS primary function is indeed to generate alternating waveforms
> >>>>>>> that you then typically feed into a DAC (and in some cases from the DAC into a
> >>>>>>> power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
> >>>>>>> on the new type and I think we already have the "blocks" in IIO for dealing with this:
> >>>>>>>
> >>>>>>> . frequency
> >>>>>>> . phase
> >>>>>>> . amplitude (raw + scale + offset)
> >>>>>>>
> >>>>>>> But you're right that maybe it's time to think in a better way to fit them together.
> >>>>>>> Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
> >>>>>>> elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
> >>>>>>>
> >>>>>>> And spoiler alert, as you might have guessed already, the parallel port stuff is to be
> >>>>>>> used with DMA buffers (and IIO backends). At least, that was the plan IIRC. But Rodrigo
> >>>>>>> can confirm it.
> >>>>>>>
> >>>>>>>> I think we've seen enough of these already to know that things like a
> >>>>>>>> "tone generator" and a "ramp generator" are going to be common and could
> >>>>>>>> share some standard attributes.
> >>>>>>>>
> >>>>>>>
> >>>>>>> I tend to agree. For example, there already some DACs (with dithering) that make use of a similar
> >>>>>>> interface (but with a custom prefix). Though the end goal is different, the interface is not that
> >>>>>>> far off:
> >>>>>>>
> >>>>>>>
> >>>>>>> https://elixir.bootlin.com/linux/v6.19.3/source/Documentation/ABI/testing/sysfs-bus-iio-dac-ltc2688
> >>>>>>>
> >>>>>>> Anyways, I knew this one would be an interesting one for upstream :)
> >>>>>>
> >>>>>> For history buffs, we had a bunch of DDS chips in staging at one point and never
> >>>>>> manage to figure out the questions being raised here :( They are complex
> >>>>>> beasts. Clarity of ABI proposal and documentation is going to be key to driving
> >>>>>> this series forwards. In a sense the code is the easy part.
> >>>>>
> >>>>> Does that mean that once good documentation is provided, the presented design can
> >>>>> be accepted? Even though data generators/sources might not be interpreted as
> >>>>> altvoltage channels?
> >>>>
> >>>> I'm not sure yet :( It's a pretty complex design and we haven't really come to a conclusion
> >>>> on how to handle this channel 'mixing' case.
> >>>>
> >>>> If we did go this way, we'd need to figure out a way to describe the mixing part.
> >>>> So either we describe it as one channel (which is going to be really complex)
> >>>> or we describe it as multiple channels but add extra ABI to make it clear they
> >>>> are mixed into a single 'physical' channel.
> >>>>
> >>>> Jonathan
> >>>>
> >>>>>
> >>>>
> >>>
> >>> Some ideas have crossed my mind, like adding new option to the in_/out_
> >>> prefix for "internal" channels. But I it would take a long time to teach
> >>> existing generic userspace libraries/tools about this.
> >>>
> >>> What has popped into my head just now is that perhaps we could do like
> >>> Rodrigo is proposing here reusing existing channels and standard attributes
> >>> as much as possible and add a new "subcomponent_of" attribute to provide
> >>> the link, similar to "current_trigger" for triggers.
> >>>
> >>> This way, it would still work with existing userspace tools (even if it
> >>> looks a bit confusing). And userspace tools could eventually be taught
> >>> to present the channels as a tree-like structure with the main channel
> >>> and subcomponents nested under it.
> >>>
> >>> We would want to spell out up front what all of the anticipated ways of
> >>> using it are. For example, I suspect eventually someone will want this
> >>> attribute to be writeable to assign a specific limited resource to a
> >>> specific channel. An I expect that we would eventually see something were
> >>> a single subcomponent is shared between multiple physical channels. In
> >>> this case, we would want the value of the "subcomponent_of" attribute to
> >>> be able to be a list.
> >>
> >> Something along those lines might work. I'd not thought about the case
> >> of one 'internal' going to multiple 'external'. Otherwise I was wondering
> >> if something informal related to labels would work. We've done that where
> >> we've been associating things like voltage and power measurement from a single
> >> pin. It's rather adhoc though. Possibly we could roll it into a newer
> >> more general scheme.
>
> Hmm... in this case, it sounds more flat where there isn't a clear channel
> that would be the "root" of a tree relation.
>
> >>
> >> If the association is done as meta data attributes alongside existing
> >> channels then as you say existing tools will kind of work, just need some
> >> human understanding of what is actually being controlled until they catch
> >> up with the newer schemes.
> >>
> >> Lots of ways we could actually represent the graphs. Going to take some
> >> figuring out!
> >
> > I like the idea of subchannels to create logical tree-structures. It opens
> > up for other possibilities.
> >
> > I wonder if the varying channel index would still confuse a user, even
> > with this metadata attribute, like:
> > - out_altvoltage0
> > - out_altvoltage1
> > - out_altvoltage1_subcomponent_of = out_altvoltage0
> >
> > would there be a different way to name the full_postfix in
> > __iio_device_attr_init() that would allow to keep channel index the same
> > (e.g. altvoltage0 for multiple internal sub-channel) and still be
> > compatible with userspace tools? I don't know, something like:
> > - out_altvoltage0
> > - out_altvoltage0_0
> > - out_altvoltage0_1
> > - out_altvoltage0_2
> >
> > userspace tools would understand out_altvoltage0_0_frequency as:
> > - direction: out
> > - type: altvoltage
> > - channel idx: 0
> > - attr name: 0_frequency
> >
> > and that would be a problem?
> >
>
> I have a feeling that could be problematic for existing attribute
> parsers.
Agreed. This smells like extend_name and that caused all sorts
of annoying problems for the userspace folk.
>
> How about using higher numbered channel indexes instead?
>
> out_altvoltage100_* = physical channel
>
> out_altvoltage101_* = subcomponent
> out_altvoltage102_* = subcomponent
> ...
Have to be careful we don't run out of space for events.
#define IIO_EVENT_CODE_EXTRACT_CHAN(mask) ((__s16)(mask & 0xFFFF))
So we do have 16 bits hence this might work.
I'm not sure we want to make rules around this though.
Jonathan
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-03-08 18:12 ` Jonathan Cameron
@ 2026-03-09 9:52 ` Rodrigo Alencar
2026-03-09 13:14 ` Nuno Sá
0 siblings, 1 reply; 46+ messages in thread
From: Rodrigo Alencar @ 2026-03-09 9:52 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner
Cc: Rodrigo Alencar, Nuno Sá, rodrigo.alencar, linux-iio,
devicetree, linux-kernel, Lars-Peter Clausen, Michael Hennerich,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
On 26/03/08 06:12PM, Jonathan Cameron wrote:
> On Sat, 7 Mar 2026 14:01:14 -0600
> David Lechner <dlechner@baylibre.com> wrote:
>
> > On 3/7/26 12:54 PM, Rodrigo Alencar wrote:
> > > On 26/03/07 04:58PM, Jonathan Cameron wrote:
> > >> On Sat, 7 Mar 2026 10:50:14 -0600
> > >> David Lechner <dlechner@baylibre.com> wrote:
> > >>
> > >>> On 3/7/26 8:09 AM, Jonathan Cameron wrote:
> > >>>> On Mon, 2 Mar 2026 10:22:47 +0000
> > >>>> Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
> > >>>>
> > >>>>> On 26/03/01 01:38PM, Jonathan Cameron wrote:
> > >>>>>> On Mon, 23 Feb 2026 10:02:00 +0000
> > >>>>>> Nuno Sá <noname.nuno@gmail.com> wrote:
> > >>>>>>
> > >>>>>>> On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
> > >>>>>>>> On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> > >>>>>>>>> On 26/02/21 02:16PM, David Lechner wrote:
> > >>>>>>>>>> On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > >>>>>>>>>>> This patch series adds support for the Analog Devices AD9910 DDS.
> > >>>>>>>>>>> This is an RFC so that we can agree/discuss on the design that follows:
...
> > >>>>>>>>>>> represents a distinct signal path into the DDS accumulator, so the driver
> > >>>>>>>>>>> models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
> > >>>>>>>>>>
> > >>>>>>>>>> Generally IIO channels represent the physical input/output, not the
> > >>>>>>>>>> internal channels.
> > >>>>>>>>>
> > >>>>>>>>> That is part of the reason for this RFC. Dividing those top-level modes
> > >>>>>>>>> into channels allows for better organization, as they can operate together,
> > >>>>>>>>> i.e., phase or scale can be provided by single-tone profile, while
> > >>>>>>>>> frequency is controlled by the digital ramp generator (see Mode Priority
> > >>>>>>>>> section in the datasheet). Also, it allows to explore the most of standard
> > >>>>>>>>> ABIs like, scale, frequency, phase, sampling_frequency and enable.
> > >>>>>>>>> Putting everything into a single channel would make things a lot messy
> > >>>>>>>>> to interface with.
...
> > >>>>>>>> Looking at Table 5 in the datasheet really helped me understand this better.
> > >>>>>>>> I think this series could benefit from a documentation patch that explains
> > >>>>>>>> more about how the driver works with some diagrams.
> > >>>>>>>>
> > >>>>>>>> So really what we have here are a bunch of digital data generators rather
> > >>>>>>>> than a bunch of altvotlage output channels. And the same data channels can be
> > >>>>>>>> mixed and match as the source for up to 3 different components of the output
> > >>>>>>>> (frequency, phase, amplitude) depending on the priority rules defined in
> > >>>>>>>> Table 5.
> > >>>>>>>
> > >>>>>>> More bellow... But note that all of the (or most of it) generators are going to
> > >>>>>>> be feed into a DAC. Your output is altvoltage but maybe we can treat the
> > >>>>>>> internals as voltage. Not sure.
> > >>>>>>>
> > >>>>>>>>
> > >>>>>>>> Digital data sources are really more like a buffer in IIO terms than a
> > >>>>>>>> channel. And before we added the IIO backend stuff, there wasn't really
> > >>>>>>>> any other digital data source/sink that I am aware of other than buffers
> > >>>>>>>> (but there are certainly a lot of odd corners of IIO that I haven't explored
> > >>>>>>>> yet, so maybe I missed some).
> > >>>>>>>>
> > >>>>>>>> In a recent discussion, the idea of possibly needing a way to provide
> > >>>>>>>> some userspace interface to be able to tweak knobs of an IIO backend
> > >>>>>>>> was also brought up.
> > >>>>>>>>
> > >>>>>>>> Putting those ideas together, I'm wondering if we need some new channel
> > >>>>>>>> type or even a whole new interface (e.g. a new sysfs directory like buffers
> > >>>>>>>> and events) for managing these digital data sources/sinks that are not an
> > >>>>>>>> IIO buffer.
> > >>>>>>>>
> > >>>>>>>
> > >>>>>>> But what would be that channel? In the end of the day, we typically have voltage or
> > >>>>>>> current DACs and a DDS primary function is indeed to generate alternating waveforms
> > >>>>>>> that you then typically feed into a DAC (and in some cases from the DAC into a
> > >>>>>>> power amplifier). So the DDS is just part of the data/signal path. Anyways, not sure
> > >>>>>>> on the new type and I think we already have the "blocks" in IIO for dealing with this:
> > >>>>>>>
> > >>>>>>> . frequency
> > >>>>>>> . phase
> > >>>>>>> . amplitude (raw + scale + offset)
> > >>>>>>>
> > >>>>>>> But you're right that maybe it's time to think in a better way to fit them together.
> > >>>>>>> Maybe a new type (as buffers or events) can make sense where the above are treated as, example, scan
> > >>>>>>> elements. Maybe it's overcomplicating, not sure. It surely needs discussion and thinking :).
...
> > >>>>>> For history buffs, we had a bunch of DDS chips in staging at one point and never
> > >>>>>> manage to figure out the questions being raised here :( They are complex
> > >>>>>> beasts. Clarity of ABI proposal and documentation is going to be key to driving
> > >>>>>> this series forwards. In a sense the code is the easy part.
> > >>>>>
> > >>>>> Does that mean that once good documentation is provided, the presented design can
> > >>>>> be accepted? Even though data generators/sources might not be interpreted as
> > >>>>> altvoltage channels?
> > >>>>
> > >>>> I'm not sure yet :( It's a pretty complex design and we haven't really come to a conclusion
> > >>>> on how to handle this channel 'mixing' case.
> > >>>>
> > >>>> If we did go this way, we'd need to figure out a way to describe the mixing part.
> > >>>> So either we describe it as one channel (which is going to be really complex)
> > >>>> or we describe it as multiple channels but add extra ABI to make it clear they
> > >>>> are mixed into a single 'physical' channel.
> > >>>>
...
> > >>> Some ideas have crossed my mind, like adding new option to the in_/out_
> > >>> prefix for "internal" channels. But I it would take a long time to teach
> > >>> existing generic userspace libraries/tools about this.
> > >>>
> > >>> What has popped into my head just now is that perhaps we could do like
> > >>> Rodrigo is proposing here reusing existing channels and standard attributes
> > >>> as much as possible and add a new "subcomponent_of" attribute to provide
> > >>> the link, similar to "current_trigger" for triggers.
> > >>>
> > >>> This way, it would still work with existing userspace tools (even if it
> > >>> looks a bit confusing). And userspace tools could eventually be taught
> > >>> to present the channels as a tree-like structure with the main channel
> > >>> and subcomponents nested under it.
> > >>>
> > >>> We would want to spell out up front what all of the anticipated ways of
> > >>> using it are. For example, I suspect eventually someone will want this
> > >>> attribute to be writeable to assign a specific limited resource to a
> > >>> specific channel. An I expect that we would eventually see something were
> > >>> a single subcomponent is shared between multiple physical channels. In
> > >>> this case, we would want the value of the "subcomponent_of" attribute to
> > >>> be able to be a list.
> > >>
> > >> Something along those lines might work. I'd not thought about the case
> > >> of one 'internal' going to multiple 'external'. Otherwise I was wondering
> > >> if something informal related to labels would work. We've done that where
> > >> we've been associating things like voltage and power measurement from a single
> > >> pin. It's rather adhoc though. Possibly we could roll it into a newer
> > >> more general scheme.
> >
> > Hmm... in this case, it sounds more flat where there isn't a clear channel
> > that would be the "root" of a tree relation.
> >
> > >>
> > >> If the association is done as meta data attributes alongside existing
> > >> channels then as you say existing tools will kind of work, just need some
> > >> human understanding of what is actually being controlled until they catch
> > >> up with the newer schemes.
> > >>
> > >> Lots of ways we could actually represent the graphs. Going to take some
> > >> figuring out!
> > >
> > > I like the idea of subchannels to create logical tree-structures. It opens
> > > up for other possibilities.
> > >
> > > I wonder if the varying channel index would still confuse a user, even
> > > with this metadata attribute, like:
> > > - out_altvoltage0
> > > - out_altvoltage1
> > > - out_altvoltage1_subcomponent_of = out_altvoltage0
> > >
> > > would there be a different way to name the full_postfix in
> > > __iio_device_attr_init() that would allow to keep channel index the same
> > > (e.g. altvoltage0 for multiple internal sub-channel) and still be
> > > compatible with userspace tools? I don't know, something like:
> > > - out_altvoltage0
> > > - out_altvoltage0_0
> > > - out_altvoltage0_1
> > > - out_altvoltage0_2
> > >
> > > userspace tools would understand out_altvoltage0_0_frequency as:
> > > - direction: out
> > > - type: altvoltage
> > > - channel idx: 0
> > > - attr name: 0_frequency
> > >
> > > and that would be a problem?
> > >
> >
> > I have a feeling that could be problematic for existing attribute
> > parsers.
>
> Agreed. This smells like extend_name and that caused all sorts
> of annoying problems for the userspace folk.
>
> >
> > How about using higher numbered channel indexes instead?
> >
> > out_altvoltage100_* = physical channel
> >
> > out_altvoltage101_* = subcomponent
> > out_altvoltage102_* = subcomponent
> > ...
> Have to be careful we don't run out of space for events.
> #define IIO_EVENT_CODE_EXTRACT_CHAN(mask) ((__s16)(mask & 0xFFFF))
> So we do have 16 bits hence this might work.
>
> I'm not sure we want to make rules around this though.
Ok, I think we are reaching an agreement. With this, I think there
is room for a small redesign, i.e., having a root channel to represent
the physical DAC and child channels as follows (* on custom ABI):
- out_altvoltage100: root/physical channel
- profile*: for profile selection
- sampling_frequency: sysclk frequency value
- out_altvoltage110: single tone channel
- frequency: single tone profile FTW
- phase: single tone profile POW
- scale: single tone profile ASF
- out_altvoltage120: parallel port channel
- en
- frequency_scale* or frequency_gain*: for FM gain
- frequency or frequency_offset*
- phase or phase_offset*
- scale or scale_offset*
- out_altvoltage130: drg channel
- en
- destination*
- operating_mode*
- out_altvoltage131: drg upper limit channel
- frequency
- frequency_step*
- phase
- phase_step*
- scale
- scale_step*
- sampling_frequency
- out_altvoltage132: drg lower limit channel
- frequency
- frequency_step*
- phase
- phase_step*
- scale
- scale_step*
- sampling_frequency
- out_altvoltage140: ram channel
- en
- frequency: global FTW
- phase: global POW
- sampling_frequency
- destination*
- operating_mode*
- address_start*
- address_end*
- out_altvoltage150: osk channel
- en
- scale: global ASF
- scale_step*
- sampling_frequency
- pinctrl_en*
so I ended up with 8 channels (rather than the initial 5). Breaking up the
DRG channel, following the tree structure allows to explore more of the
standard ABI. I suppose that even with this, documentation is no less
important. How does it look?
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH RFC 0/8] AD9910 Direct Digital Synthesizer
2026-03-09 9:52 ` Rodrigo Alencar
@ 2026-03-09 13:14 ` Nuno Sá
0 siblings, 0 replies; 46+ messages in thread
From: Nuno Sá @ 2026-03-09 13:14 UTC (permalink / raw)
To: Rodrigo Alencar, Jonathan Cameron, David Lechner
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel,
Lars-Peter Clausen, Michael Hennerich, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
On Mon, 2026-03-09 at 09:52 +0000, Rodrigo Alencar wrote:
> On 26/03/08 06:12PM, Jonathan Cameron wrote:
> > On Sat, 7 Mar 2026 14:01:14 -0600
> > David Lechner <dlechner@baylibre.com> wrote:
> >
> > > On 3/7/26 12:54 PM, Rodrigo Alencar wrote:
> > > > On 26/03/07 04:58PM, Jonathan Cameron wrote:
> > > > > On Sat, 7 Mar 2026 10:50:14 -0600
> > > > > David Lechner <dlechner@baylibre.com> wrote:
> > > > >
> > > > > > On 3/7/26 8:09 AM, Jonathan Cameron wrote:
> > > > > > > On Mon, 2 Mar 2026 10:22:47 +0000
> > > > > > > Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
> > > > > > >
> > > > > > > > On 26/03/01 01:38PM, Jonathan Cameron wrote:
> > > > > > > > > On Mon, 23 Feb 2026 10:02:00 +0000
> > > > > > > > > Nuno Sá <noname.nuno@gmail.com> wrote:
> > > > > > > > >
> > > > > > > > > > On Sun, 2026-02-22 at 14:32 -0600, David Lechner wrote:
> > > > > > > > > > > On 2/22/26 4:01 AM, Rodrigo Alencar wrote:
> > > > > > > > > > > > On 26/02/21 02:16PM, David Lechner wrote:
> > > > > > > > > > > > > On 2/20/26 10:46 AM, Rodrigo Alencar via B4 Relay wrote:
> > > > > > > > > > > > > > This patch series adds support for the Analog Devices AD9910 DDS.
> > > > > > > > > > > > > > This is an RFC so that we can agree/discuss on the design that follows:
>
> ...
>
> > > > > > > > > > > > > > represents a distinct signal path into the DDS accumulator, so the
> > > > > > > > > > > > > > driver
> > > > > > > > > > > > > > models them as separate IIO output channels (all IIO_ALTVOLTAGE
> > > > > > > > > > > > > > type).
> > > > > > > > > > > > >
> > > > > > > > > > > > > Generally IIO channels represent the physical input/output, not the
> > > > > > > > > > > > > internal channels.
> > > > > > > > > > > >
> > > > > > > > > > > > That is part of the reason for this RFC. Dividing those top-level modes
> > > > > > > > > > > > into channels allows for better organization, as they can operate together,
> > > > > > > > > > > > i.e., phase or scale can be provided by single-tone profile, while
> > > > > > > > > > > > frequency is controlled by the digital ramp generator (see Mode Priority
> > > > > > > > > > > > section in the datasheet). Also, it allows to explore the most of standard
> > > > > > > > > > > > ABIs like, scale, frequency, phase, sampling_frequency and enable.
> > > > > > > > > > > > Putting everything into a single channel would make things a lot messy
> > > > > > > > > > > > to interface with.
>
> ...
>
> > > > > > > > > > > Looking at Table 5 in the datasheet really helped me understand this better.
> > > > > > > > > > > I think this series could benefit from a documentation patch that explains
> > > > > > > > > > > more about how the driver works with some diagrams.
> > > > > > > > > > >
> > > > > > > > > > > So really what we have here are a bunch of digital data generators rather
> > > > > > > > > > > than a bunch of altvotlage output channels. And the same data channels can be
> > > > > > > > > > > mixed and match as the source for up to 3 different components of the output
> > > > > > > > > > > (frequency, phase, amplitude) depending on the priority rules defined in
> > > > > > > > > > > Table 5.
> > > > > > > > > >
> > > > > > > > > > More bellow... But note that all of the (or most of it) generators are going to
> > > > > > > > > > be feed into a DAC. Your output is altvoltage but maybe we can treat the
> > > > > > > > > > internals as voltage. Not sure.
> > > > > > > > > >
> > > > > > > > > > >
> > > > > > > > > > > Digital data sources are really more like a buffer in IIO terms than a
> > > > > > > > > > > channel. And before we added the IIO backend stuff, there wasn't really
> > > > > > > > > > > any other digital data source/sink that I am aware of other than buffers
> > > > > > > > > > > (but there are certainly a lot of odd corners of IIO that I haven't explored
> > > > > > > > > > > yet, so maybe I missed some).
> > > > > > > > > > >
> > > > > > > > > > > In a recent discussion, the idea of possibly needing a way to provide
> > > > > > > > > > > some userspace interface to be able to tweak knobs of an IIO backend
> > > > > > > > > > > was also brought up.
> > > > > > > > > > >
> > > > > > > > > > > Putting those ideas together, I'm wondering if we need some new channel
> > > > > > > > > > > type or even a whole new interface (e.g. a new sysfs directory like buffers
> > > > > > > > > > > and events) for managing these digital data sources/sinks that are not an
> > > > > > > > > > > IIO buffer.
> > > > > > > > > > >
> > > > > > > > > >
> > > > > > > > > > But what would be that channel? In the end of the day, we typically have voltage
> > > > > > > > > > or
> > > > > > > > > > current DACs and a DDS primary function is indeed to generate alternating
> > > > > > > > > > waveforms
> > > > > > > > > > that you then typically feed into a DAC (and in some cases from the DAC into a
> > > > > > > > > > power amplifier). So the DDS is just part of the data/signal path. Anyways, not
> > > > > > > > > > sure
> > > > > > > > > > on the new type and I think we already have the "blocks" in IIO for dealing with
> > > > > > > > > > this:
> > > > > > > > > >
> > > > > > > > > > . frequency
> > > > > > > > > > . phase
> > > > > > > > > > . amplitude (raw + scale + offset)
> > > > > > > > > >
> > > > > > > > > > But you're right that maybe it's time to think in a better way to fit them
> > > > > > > > > > together.
> > > > > > > > > > Maybe a new type (as buffers or events) can make sense where the above are
> > > > > > > > > > treated as, example, scan
> > > > > > > > > > elements. Maybe it's overcomplicating, not sure. It surely needs discussion and
> > > > > > > > > > thinking :).
>
> ...
>
> > > > > > > > > For history buffs, we had a bunch of DDS chips in staging at one point and never
> > > > > > > > > manage to figure out the questions being raised here :( They are complex
> > > > > > > > > beasts. Clarity of ABI proposal and documentation is going to be key to driving
> > > > > > > > > this series forwards. In a sense the code is the easy part.
> > > > > > > >
> > > > > > > > Does that mean that once good documentation is provided, the presented design can
> > > > > > > > be accepted? Even though data generators/sources might not be interpreted as
> > > > > > > > altvoltage channels?
> > > > > > >
> > > > > > > I'm not sure yet :( It's a pretty complex design and we haven't really come to a
> > > > > > > conclusion
> > > > > > > on how to handle this channel 'mixing' case.
> > > > > > >
> > > > > > > If we did go this way, we'd need to figure out a way to describe the mixing part.
> > > > > > > So either we describe it as one channel (which is going to be really complex)
> > > > > > > or we describe it as multiple channels but add extra ABI to make it clear they
> > > > > > > are mixed into a single 'physical' channel.
> > > > > > >
>
> ...
>
> > > > > > Some ideas have crossed my mind, like adding new option to the in_/out_
> > > > > > prefix for "internal" channels. But I it would take a long time to teach
> > > > > > existing generic userspace libraries/tools about this.
> > > > > >
> > > > > > What has popped into my head just now is that perhaps we could do like
> > > > > > Rodrigo is proposing here reusing existing channels and standard attributes
> > > > > > as much as possible and add a new "subcomponent_of" attribute to provide
> > > > > > the link, similar to "current_trigger" for triggers.
> > > > > >
> > > > > > This way, it would still work with existing userspace tools (even if it
> > > > > > looks a bit confusing). And userspace tools could eventually be taught
> > > > > > to present the channels as a tree-like structure with the main channel
> > > > > > and subcomponents nested under it.
> > > > > >
> > > > > > We would want to spell out up front what all of the anticipated ways of
> > > > > > using it are. For example, I suspect eventually someone will want this
> > > > > > attribute to be writeable to assign a specific limited resource to a
> > > > > > specific channel. An I expect that we would eventually see something were
> > > > > > a single subcomponent is shared between multiple physical channels. In
> > > > > > this case, we would want the value of the "subcomponent_of" attribute to
> > > > > > be able to be a list.
> > > > >
> > > > > Something along those lines might work. I'd not thought about the case
> > > > > of one 'internal' going to multiple 'external'. Otherwise I was wondering
> > > > > if something informal related to labels would work. We've done that where
> > > > > we've been associating things like voltage and power measurement from a single
> > > > > pin. It's rather adhoc though. Possibly we could roll it into a newer
> > > > > more general scheme.
> > >
> > > Hmm... in this case, it sounds more flat where there isn't a clear channel
> > > that would be the "root" of a tree relation.
> > >
> > > > >
> > > > > If the association is done as meta data attributes alongside existing
> > > > > channels then as you say existing tools will kind of work, just need some
> > > > > human understanding of what is actually being controlled until they catch
> > > > > up with the newer schemes.
> > > > >
> > > > > Lots of ways we could actually represent the graphs. Going to take some
> > > > > figuring out!
> > > >
> > > > I like the idea of subchannels to create logical tree-structures. It opens
> > > > up for other possibilities.
> > > >
> > > > I wonder if the varying channel index would still confuse a user, even
> > > > with this metadata attribute, like:
> > > > - out_altvoltage0
> > > > - out_altvoltage1
> > > > - out_altvoltage1_subcomponent_of = out_altvoltage0
> > > >
> > > > would there be a different way to name the full_postfix in
> > > > __iio_device_attr_init() that would allow to keep channel index the same
> > > > (e.g. altvoltage0 for multiple internal sub-channel) and still be
> > > > compatible with userspace tools? I don't know, something like:
> > > > - out_altvoltage0
> > > > - out_altvoltage0_0
> > > > - out_altvoltage0_1
> > > > - out_altvoltage0_2
> > > >
> > > > userspace tools would understand out_altvoltage0_0_frequency as:
> > > > - direction: out
> > > > - type: altvoltage
> > > > - channel idx: 0
> > > > - attr name: 0_frequency
> > > >
> > > > and that would be a problem?
> > > >
> > >
> > > I have a feeling that could be problematic for existing attribute
> > > parsers.
> >
> > Agreed. This smells like extend_name and that caused all sorts
> > of annoying problems for the userspace folk.
> >
> > >
> > > How about using higher numbered channel indexes instead?
> > >
> > > out_altvoltage100_* = physical channel
> > >
> > > out_altvoltage101_* = subcomponent
> > > out_altvoltage102_* = subcomponent
> > > ...
> > Have to be careful we don't run out of space for events.
> > #define IIO_EVENT_CODE_EXTRACT_CHAN(mask) ((__s16)(mask & 0xFFFF))
> > So we do have 16 bits hence this might work.
> >
> > I'm not sure we want to make rules around this though.
>
> Ok, I think we are reaching an agreement. With this, I think there
> is room for a small redesign, i.e., having a root channel to represent
> the physical DAC and child channels as follows (* on custom ABI):
> - out_altvoltage100: root/physical channel
> - profile*: for profile selection
> - sampling_frequency: sysclk frequency value
> - out_altvoltage110: single tone channel
> - frequency: single tone profile FTW
> - phase: single tone profile POW
> - scale: single tone profile ASF
> - out_altvoltage120: parallel port channel
> - en
> - frequency_scale* or frequency_gain*: for FM gain
> - frequency or frequency_offset*
> - phase or phase_offset*
> - scale or scale_offset*
> - out_altvoltage130: drg channel
> - en
> - destination*
> - operating_mode*
> - out_altvoltage131: drg upper limit channel
> - frequency
> - frequency_step*
> - phase
> - phase_step*
> - scale
> - scale_step*
> - sampling_frequency
> - out_altvoltage132: drg lower limit channel
> - frequency
> - frequency_step*
> - phase
> - phase_step*
> - scale
> - scale_step*
> - sampling_frequency
> - out_altvoltage140: ram channel
> - en
> - frequency: global FTW
> - phase: global POW
> - sampling_frequency
> - destination*
> - operating_mode*
> - address_start*
> - address_end*
> - out_altvoltage150: osk channel
> - en
> - scale: global ASF
> - scale_step*
> - sampling_frequency
> - pinctrl_en*
>
> so I ended up with 8 channels (rather than the initial 5). Breaking up the
> DRG channel, following the tree structure allows to explore more of the
> standard ABI. I suppose that even with this, documentation is no less
> important. How does it look?
FWIW, I kind of like this "child" channel concept and think it might be useful
for other things to. One thing that comes to mind is how do we want to handle this
in terms of scan_elements? Do we have it completely independent or do we want to play some
games with it? Like allowing arbitrary sizes (not power of 2) in child channels as long as the
sum matches what was defined in the parent/physical channel.
One usecase for the above would be the ad4030 ADC where we went with two channels (for the common
mode voltage) for one physical one and we define two scan elements where the total storage does
not directly map the actual physical size. And while we can get away with it for SW buffers, for HW
buffers it's not "so simple".
Can be a bit tricky to implement but the above concept might open the door for it.
- Nuno Sá
^ permalink raw reply [flat|nested] 46+ messages in thread