* [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer
@ 2026-03-18 17:56 Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910 Rodrigo Alencar via B4 Relay
` (8 more replies)
0 siblings, 9 replies; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
Rodrigo Alencar
This patch series adds support for the Analog Devices AD9910 DDS.
This is a RFC so that we can agree/discuss on the design that follows:
This is a follow-up of the V1 discussion. We are reaching into this
channel composition agreement where physical channels may have
sub-channels. That adds the flexibility necessary for this design.
Nothing has been changed to iio-core yet, so I've just addressed
comments, aiming to keep the discussion going forward.
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
represents a distinct signal path into the DDS accumulator, so the driver
models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
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
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 firmware upload infrastructure. Userspace
writes the waveform data as a raw binary buffer (up to 4096 bytes for
the full 1024x32-bit RAM), and the driver reverses the byte array and
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:
iio:device3: ad9910
8 channels found:
altvoltage120: (output)
7 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: sampling_frequency value: 250000000.000000
attr 6: scale_offset value: 0.000000
altvoltage140: (output)
11 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: sampling_frequency value: 250000000.000000
altvoltage130: (output)
11 channel-specific attributes found:
attr 0: destination value: frequency
attr 1: destination_available value: frequency phase amplitude
attr 2: en value: 0
attr 3: label value: digital_ramp_generator
attr 4: operating_mode value: bidirectional_continuous
attr 5: operating_mode_available value: bidirectional ramp_down
ramp_up bidirectional_continuous
altvoltage110: (output)
4 channel-specific attributes found:
attr 0: frequency value: 0.000000
attr 1: label value: single_tone
attr 2: phase value: 0.000000
attr 3: scale value: 0.000000
altvoltage132: (output)
8 channel-specific attributes found:
attr 0: frequency value: 0.000000
attr 1: frequency_step value: 0.000000
attr 2: label value: digital_ramp_down
attr 3: phase value: 0.000000000
attr 4: phase_step value: 0.000000000
attr 5: sampling_frequency value: 250000000.000000
attr 6: scale value: 0.000000000
attr 7: scale_step value: 0.000000000
altvoltage131: (output)
8 channel-specific attributes found:
attr 0: frequency value: 0.000000
attr 1: frequency_step value: 0.000000
attr 2: label value: digital_ramp_up
attr 3: phase value: 0.000000000
attr 4: phase_step value: 0.000000000
attr 5: sampling_frequency value: 250000000.000000
attr 6: scale value: 0.000000000
attr 7: scale_step value: 0.000000000
altvoltage100: (output)
4 channel-specific attributes found:
attr 0: label value: phy
attr 1: powerdown value: 0
attr 2: profile value: 0
attr 3: sampling_frequency value: 1000000000
altvoltage150: (output)
6 channel-specific attributes found:
attr 0: en value: 0
attr 1: label value: output_shift_keying
attr 2: pinctrl_en value: 0
attr 3: sampling_frequency value: 250000000.000000
attr 4: scale value: 0.000000
attr 5: scale_step value: 0.000000
4 debug attributes found:
debug attr 1: ram_data ERROR: Input/output error (5)
debug attr 2: ram_loading value: 0
debug attr 3: direct_reg_access value: 0x2
Kind regards,
Rodrigo Alencar
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Changes in v2:
- Device-tree bindings changes.
- RAM loading to use firmware update interface.
- Rearrange of channels into a hierarchy.
- Link to v1: https://lore.kernel.org/r/20260220-ad9910-iio-driver-v1-0-3b264aa48a10@analog.com
---
Rodrigo Alencar (9):
dt-bindings: iio: frequency: add ad9910
iio: frequency: ad9910: initial driver implementation
iio: frequency: ad9910: add simple parallel port mode support
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
Documentation: ABI: testing: add docs for ad9910 sysfs entries
docs: iio: add documentation for ad9910 driver
.../ABI/testing/sysfs-bus-iio-frequency-ad9910 | 182 ++
.../bindings/iio/frequency/adi,ad9910.yaml | 189 ++
Documentation/iio/ad9910.rst | 654 ++++++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 10 +
drivers/iio/frequency/Kconfig | 20 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/ad9910.c | 2261 ++++++++++++++++++++
8 files changed, 3318 insertions(+)
---
base-commit: ff0843ceb1fb11a6b73e0e77b932ef7967aecd4b
change-id: 20260218-ad9910-iio-driver-9b3d214c251f
Best regards,
--
Rodrigo Alencar <rodrigo.alencar@analog.com>
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
@ 2026-03-18 17:56 ` Rodrigo Alencar via B4 Relay
2026-03-19 17:25 ` Conor Dooley
2026-03-18 17:56 ` [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation Rodrigo Alencar via B4 Relay
` (7 subsequent siblings)
8 siblings, 1 reply; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
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 clocks, DAC current, reset and basic GPIO control.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
.../bindings/iio/frequency/adi,ad9910.yaml | 189 +++++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 196 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..68eaefea3f5a
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
@@ -0,0 +1,189 @@
+# 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:
+ minItems: 1
+ maxItems: 2
+ description:
+ First clock is always the reference clock (REF_CLK), while the second
+ clock is an optional synchronization clock (SYNC_IN).
+
+ clock-names:
+ oneOf:
+ - items:
+ - const: ref_clk
+ - items:
+ - const: ref_clk
+ - const: sync_in
+
+ '#clock-cells':
+ const: 1
+
+ clock-output-names:
+ minItems: 1
+ maxItems: 3
+ items:
+ enum: [ sync_clk, pdclk, sync_out ]
+
+ interrupts:
+ minItems: 1
+ maxItems: 2
+
+ interrupt-names:
+ minItems: 1
+ maxItems: 2
+ items:
+ enum: [ drover, ram_swp_ovr ]
+
+ 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.
+
+ reset-gpios:
+ description:
+ GPIOs controlling the Main Device reset.
+
+ io-reset-gpios:
+ maxItems: 1
+ description:
+ GPIO controlling the I/O_RESET pin.
+
+ 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.
+
+ sync-err-gpios:
+ maxItems: 1
+ description:
+ GPIO used to read SYNC_SMP_ERR pin status.
+
+ adi,pll-enable:
+ type: boolean
+ description:
+ Indicates that a loop filter is connected and the internal PLL is enabled.
+ Often used when the reference clock is provided by a crystal or by a
+ single-ended on-board oscillator.
+
+ adi,charge-pump-current-microamp:
+ minimum: 212
+ maximum: 387
+ default: 387
+ description:
+ PLL charge pump current in microamps. Only applicable when the internal
+ PLL is enabled. The value is rounded to the nearest supported step. This
+ value depends mostly on the loop filter design.
+
+ 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 internal PLL is enabled.
+
+ adi,dac-output-current-microamp:
+ minimum: 8640
+ maximum: 31590
+ default: 20070
+ description:
+ DAC full-scale output current in microamps.
+
+dependencies:
+ adi,charge-pump-current-microamp: [ 'adi,pll-enable' ]
+ adi,refclk-out-drive-strength: [ 'adi,pll-enable' ]
+ interrupts: [ interrupt-names ]
+ clocks: [ clock-names ]
+ '#clock-cells': [ clock-output-names ]
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - dvdd-io33-supply
+ - avdd33-supply
+ - dvdd18-supply
+ - avdd18-supply
+
+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>;
+ clock-names = "ref_clk";
+
+ dvdd-io33-supply = <&vdd_io33>;
+ avdd33-supply = <&vdd_a33>;
+ dvdd18-supply = <&vdd_d18>;
+ avdd18-supply = <&vdd_a18>;
+
+ reset-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+ io-reset-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
+ powerdown-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
+ update-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
+ profile-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>,
+ <&gpio 5 GPIO_ACTIVE_HIGH>,
+ <&gpio 6 GPIO_ACTIVE_HIGH>;
+
+ adi,pll-enable;
+ adi,charge-pump-current-microamp = <387>;
+ adi,refclk-out-drive-strength = "disabled";
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 08d8ddf4ef68..2ca8b68e5daa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1630,6 +1630,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] 27+ messages in thread
* [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910 Rodrigo Alencar via B4 Relay
@ 2026-03-18 17:56 ` Rodrigo Alencar via B4 Relay
2026-03-22 16:50 ` Jonathan Cameron
2026-03-18 17:56 ` [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support Rodrigo Alencar via B4 Relay
` (6 subsequent siblings)
8 siblings, 1 reply; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
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 attributes.
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 | 1006 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 1026 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 2ca8b68e5daa..6403439b530d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1636,6 +1636,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..a362d96cf651
--- /dev/null
+++ b/drivers/iio/frequency/ad9910.c
@@ -0,0 +1,1006 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD9910 SPI DDS (Direct Digital Synthesizer) driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/array_size.h>
+#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_MSK BIT(8)
+
+/* 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 GENMASK_ULL(31, 0)
+
+/* 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 (457 * HZ_PER_MHZ)
+#define AD9910_VCO1_RANGE_AUTO_MAX_HZ (530 * HZ_PER_MHZ)
+#define AD9910_VCO2_RANGE_AUTO_MAX_HZ (632 * HZ_PER_MHZ)
+#define AD9910_VCO3_RANGE_AUTO_MAX_HZ (775 * HZ_PER_MHZ)
+#define AD9910_VCO4_RANGE_AUTO_MAX_HZ (897 * 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_MSK BIT(7)
+#define AD9910_SPI_ADDR_MSK GENMASK(4, 0)
+
+/**
+ * enum ad9910_channel - AD9910 channel identifiers in priority order
+ *
+ * @AD9910_CHANNEL_PHY: Physical output channel
+ * @AD9910_CHANNEL_SINGLE_TONE: Single tone output channel
+ */
+enum ad9910_channel {
+ AD9910_CHANNEL_PHY = 100,
+ AD9910_CHANNEL_SINGLE_TONE = 110,
+};
+
+enum {
+ AD9910_CHAN_IDX_PHY,
+ AD9910_CHAN_IDX_SINGLE_TONE,
+ AD9910_CHAN_IDX_PARALLEL_PORT,
+ AD9910_CHAN_IDX_DRG,
+ AD9910_CHAN_IDX_DRG_RAMP_UP,
+ AD9910_CHAN_IDX_DRG_RAMP_DOWN,
+ AD9910_CHAN_IDX_RAM,
+ AD9910_CHAN_IDX_OSK,
+};
+
+enum {
+ AD9910_PROFILE,
+ AD9910_POWERDOWN,
+};
+
+struct ad9910_data {
+ u32 sysclk_freq_hz;
+ u32 dac_output_current;
+
+ u16 pll_charge_pump_current;
+ u8 refclk_out_drv;
+ bool pll_enabled;
+};
+
+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;
+
+ union {
+ __be64 be64;
+ __be32 be32;
+ __be16 be16;
+ } rx_buf;
+ /*
+ * RAM loading requires a reasonable amount of bytes, at the same time
+ * DMA capable SPI drivers requires the transfer buffers to live in
+ * their own cache lines.
+ */
+ u8 tx_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->tx_buf[0] = AD9910_SPI_READ_MSK |
+ FIELD_PREP(AD9910_SPI_ADDR_MSK, reg);
+ return spi_write_then_read(st->spi, st->tx_buf, 1, &st->rx_buf, len);
+}
+
+static inline int ad9910_spi_write(struct ad9910_state *st, u8 reg, size_t len,
+ bool update)
+{
+ int ret;
+
+ st->tx_buf[0] = FIELD_PREP(AD9910_SPI_ADDR_MSK, reg);
+ ret = spi_write(st->spi, st->tx_buf, AD9910_SPI_DATA_IDX + len);
+ if (!ret && update)
+ return ad9910_io_update(st);
+
+ return ret;
+}
+
+#define AD9910_REG_READ_FN(nb) \
+static 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 = be##nb##_to_cpu(st->rx_buf.be##nb); \
+ return ret; \
+}
+
+AD9910_REG_READ_FN(16)
+AD9910_REG_READ_FN(32)
+AD9910_REG_READ_FN(64)
+
+#define AD9910_REG_WRITE_FN(nb) \
+static int ad9910_reg##nb##_write(struct ad9910_state *st, u8 reg, \
+ u##nb data, bool update) \
+{ \
+ int ret; \
+ \
+ put_unaligned_be##nb(data, &st->tx_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_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_set_sysclk_freq(struct ad9910_state *st, u32 freq_hz,
+ bool update)
+{
+ u32 sysclk_freq_hz, refclk_freq_hz = clk_get_rate(st->refclk);
+ u32 tmp32, vco_sel;
+ int ret;
+
+ if (st->data.pll_enabled) {
+ if (refclk_freq_hz < AD9910_PLL_IN_MIN_FREQ_HZ ||
+ refclk_freq_hz > AD9910_PLL_IN_MAX_FREQ_HZ) {
+ dev_err(&st->spi->dev,
+ "REF_CLK frequency %u Hz is out of PLL input range\n",
+ refclk_freq_hz);
+ return -ERANGE;
+ }
+
+ tmp32 = DIV_ROUND_CLOSEST(freq_hz, refclk_freq_hz);
+ tmp32 = clamp(tmp32, AD9910_PLL_MIN_N, AD9910_PLL_MAX_N);
+ sysclk_freq_hz = refclk_freq_hz * tmp32;
+
+ if (sysclk_freq_hz < AD9910_PLL_OUT_MIN_FREQ_HZ ||
+ sysclk_freq_hz > AD9910_PLL_OUT_MAX_FREQ_HZ) {
+ dev_err(&st->spi->dev,
+ "PLL output frequency %u Hz is out of range\n",
+ sysclk_freq_hz);
+ return -ERANGE;
+ }
+
+ if (sysclk_freq_hz <= AD9910_VCO0_RANGE_AUTO_MAX_HZ)
+ vco_sel = 0;
+ else if (sysclk_freq_hz <= AD9910_VCO1_RANGE_AUTO_MAX_HZ)
+ vco_sel = 1;
+ else if (sysclk_freq_hz <= AD9910_VCO2_RANGE_AUTO_MAX_HZ)
+ vco_sel = 2;
+ else if (sysclk_freq_hz <= AD9910_VCO3_RANGE_AUTO_MAX_HZ)
+ vco_sel = 3;
+ else if (sysclk_freq_hz <= AD9910_VCO4_RANGE_AUTO_MAX_HZ)
+ vco_sel = 4;
+ else
+ vco_sel = 5;
+
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR3,
+ AD9910_CFR3_N_MSK | AD9910_CFR3_VCO_SEL_MSK,
+ FIELD_PREP(AD9910_CFR3_N_MSK, tmp32) |
+ FIELD_PREP(AD9910_CFR3_VCO_SEL_MSK, vco_sel),
+ update);
+ if (ret)
+ return ret;
+ } else {
+ tmp32 = DIV_ROUND_CLOSEST(refclk_freq_hz, freq_hz);
+ tmp32 = clamp(tmp32, 1, 2);
+ sysclk_freq_hz = refclk_freq_hz / tmp32;
+ tmp32 = FIELD_PREP(AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, tmp32 % 2);
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR3,
+ AD9910_CFR3_REFCLK_DIV_BYPASS_MSK,
+ tmp32, update);
+ if (ret)
+ return ret;
+ }
+
+ st->data.sysclk_freq_hz = sysclk_freq_hz;
+ return 0;
+}
+
+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_phy_ext_info[] = {
+ AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SEPARATE),
+ AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
+ { }
+};
+
+static const struct iio_chan_spec ad9910_channels[] = {
+ [AD9910_CHAN_IDX_PHY] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PHY,
+ .address = AD9910_CHAN_IDX_PHY,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_phy_ext_info,
+ },
+ [AD9910_CHAN_IDX_SINGLE_TONE] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_SINGLE_TONE,
+ .address = AD9910_CHAN_IDX_SINGLE_TONE,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY) |
+ BIT(IIO_CHAN_INFO_PHASE) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+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:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_SINGLE_TONE:
+ tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ break;
+ default:
+ return -EINVAL;
+ }
+ 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:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_SINGLE_TONE:
+ tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16;
+ *val = tmp32 / MICRO;
+ *val2 = tmp32 % MICRO;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_SINGLE_TONE:
+ tmp64 = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ *val = 0;
+ *val2 = tmp64 * MICRO >> 14;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PHY:
+ *val = st->data.sysclk_freq_hz;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ 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;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ if (!in_range(val, 0, st->data.sysclk_freq_hz / 2))
+ return -EINVAL;
+
+ tmp64 = ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32),
+ (u64)MICRO * st->data.sysclk_freq_hz);
+ tmp64 = min(tmp64, U32_MAX);
+ switch (chan->channel) {
+ case AD9910_CHANNEL_SINGLE_TONE:
+ tmp64 = FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_ST_FTW_MSK,
+ tmp64, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_PHASE:
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_SINGLE_TONE:
+ tmp64 = (u64)val * MICRO + val2;
+ if (tmp64 >= AD9910_MAX_PHASE_MICRORAD)
+ return -EINVAL;
+
+ tmp64 <<= 16;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_MAX_PHASE_MICRORAD);
+ tmp64 = min(tmp64, AD9910_POW_MAX);
+ tmp64 = FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_ST_POW_MSK,
+ tmp64, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ if (val < 0 || val > 1 || (val == 1 && val2 > 0))
+ return -EINVAL;
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_SINGLE_TONE:
+ tmp64 = ((u64)val * MICRO + val2) << 14;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, MICRO);
+ tmp64 = min(tmp64, AD9910_ASF_MAX);
+ tmp64 = FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_ST_ASF_MSK,
+ tmp64, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad9910_set_sysclk_freq(st, val, 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_ENABLE:
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_FREQUENCY:
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_PHASE:
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_SINGLE_TONE:
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT;
+ 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 = FIELD_GET(AD9910_REG_HIGH32_FLAG_MSK, reg);
+
+ /*
+ * HIGH32 flag is a workaround to allow access to upper 32 bits of
+ * 64-bit registers one at a time due to debugfs_reg_access limitations
+ * of only supporting 32-bit values.
+ */
+ reg &= ~AD9910_REG_HIGH32_FLAG_MSK;
+ 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(GENMASK_ULL(63, 32), &tmp64, writeval);
+ else
+ FIELD_MODIFY(GENMASK_ULL(31, 0), &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_cfg_sysclk(struct ad9910_state *st, bool update)
+{
+ u32 tmp32, cfr3 = AD9910_CFR3_OPEN_MSK;
+
+ cfr3 |= AD9910_CFR3_VCO_SEL_MSK |
+ FIELD_PREP(AD9910_CFR3_DRV0_MSK, st->data.refclk_out_drv);
+
+ if (st->data.pll_enabled) {
+ tmp32 = st->data.pll_charge_pump_current - AD9910_ICP_MIN_uA;
+ tmp32 = DIV_ROUND_CLOSEST(tmp32, AD9910_ICP_STEP_uA);
+ cfr3 |= FIELD_PREP(AD9910_CFR3_ICP_MSK, tmp32) |
+ AD9910_CFR3_PLL_EN_MSK;
+ } else {
+ cfr3 |= AD9910_CFR3_ICP_MSK |
+ AD9910_CFR3_REFCLK_DIV_RESETB_MSK |
+ AD9910_CFR3_PFD_RESET_MSK;
+ }
+ st->reg[AD9910_REG_CFR3].val32 = cfr3;
+
+ return ad9910_set_sysclk_freq(st, AD9910_PLL_OUT_MAX_FREQ_HZ, update);
+}
+
+static int ad9910_parse_fw(struct ad9910_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ u32 tmp;
+ int ret;
+
+ st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable");
+ if (st->data.pll_enabled) {
+ 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;
+ }
+
+ 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;
+ 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 |
+ AD9910_CFR2_SYNC_CLK_EN_MSK |
+ AD9910_CFR2_PDCLK_ENABLE_MSK;
+ 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_release(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;
+ 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;
+
+ st->refclk = devm_clk_get_enabled(dev, "ref_clk");
+ 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_gpio = devm_gpiod_get_optional(dev, "io-reset", 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_iio_device_register(dev, indio_dev);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, ad9910_release, st);
+}
+
+static const struct spi_device_id ad9910_id[] = {
+ { "ad9910" },
+ { }
+};
+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] 27+ messages in thread
* [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910 Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation Rodrigo Alencar via B4 Relay
@ 2026-03-18 17:56 ` Rodrigo Alencar via B4 Relay
2026-03-18 18:28 ` Andy Shevchenko
2026-03-18 17:56 ` [PATCH RFC v2 4/9] iio: frequency: ad9910: add digital ramp generator support Rodrigo Alencar via B4 Relay
` (5 subsequent siblings)
8 siblings, 1 reply; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
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 | 173 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 170 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index a362d96cf651..726fac0b9fc1 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -114,9 +114,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 */
@@ -140,7 +144,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 */
@@ -184,10 +190,12 @@
*
* @AD9910_CHANNEL_PHY: Physical output channel
* @AD9910_CHANNEL_SINGLE_TONE: Single tone output channel
+ * @AD9910_CHANNEL_PARALLEL_PORT: Parallel port output channel
*/
enum ad9910_channel {
AD9910_CHANNEL_PHY = 100,
AD9910_CHANNEL_SINGLE_TONE = 110,
+ AD9910_CHANNEL_PARALLEL_PORT = 120,
};
enum {
@@ -204,6 +212,10 @@ enum {
enum {
AD9910_PROFILE,
AD9910_POWERDOWN,
+ AD9910_PP_FREQ_SCALE,
+ AD9910_PP_FREQ_OFFSET,
+ AD9910_PP_PHASE_OFFSET,
+ AD9910_PP_AMP_OFFSET,
};
struct ad9910_data {
@@ -468,6 +480,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;
}
@@ -503,6 +519,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;
}
@@ -510,20 +535,132 @@ 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 (!in_range(val, 0, 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)
+ return -EINVAL;
+
+ if (!in_range(val2, 0, (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)
+ return -EINVAL;
+
+ if (!in_range(val2, 0, (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_phy_ext_info[] = {
AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SEPARATE),
AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
{ }
};
+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_CHAN_IDX_PHY] = {
.type = IIO_ALTVOLTAGE,
@@ -546,6 +683,15 @@ static const struct iio_chan_spec ad9910_channels[] = {
BIT(IIO_CHAN_INFO_PHASE) |
BIT(IIO_CHAN_INFO_SCALE),
},
+ [AD9910_CHAN_IDX_PARALLEL_PORT] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL_PORT,
+ .address = AD9910_CHAN_IDX_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,
@@ -559,6 +705,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:
switch (chan->channel) {
case AD9910_CHANNEL_SINGLE_TONE:
@@ -619,6 +775,17 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
guard(mutex)(&st->lock);
switch (info) {
+ case IIO_CHAN_INFO_ENABLE:
+ val = !!val;
+ 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 (!in_range(val, 0, st->data.sysclk_freq_hz / 2))
return -EINVAL;
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH RFC v2 4/9] iio: frequency: ad9910: add digital ramp generator support
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (2 preceding siblings ...)
2026-03-18 17:56 ` [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support Rodrigo Alencar via B4 Relay
@ 2026-03-18 17:56 ` Rodrigo Alencar via B4 Relay
2026-03-18 19:14 ` Andy Shevchenko
2026-03-18 17:56 ` [PATCH RFC v2 5/9] iio: frequency: ad9910: add RAM mode support Rodrigo Alencar via B4 Relay
` (4 subsequent siblings)
8 siblings, 1 reply; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add DRG channels 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 | 467 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 465 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 726fac0b9fc1..d3367e211dcf 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -132,6 +132,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 GENMASK_ULL(63, 32)
+#define AD9910_DRG_LIMIT_LOWER_MSK GENMASK_ULL(31, 0)
+
+/* Digital Ramp Step Register */
+#define AD9910_DRG_STEP_DEC_MSK GENMASK_ULL(63, 32)
+#define AD9910_DRG_STEP_INC_MSK GENMASK_ULL(31, 0)
+
+/* 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)
@@ -147,8 +159,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
@@ -191,11 +206,47 @@
* @AD9910_CHANNEL_PHY: Physical output channel
* @AD9910_CHANNEL_SINGLE_TONE: Single tone output channel
* @AD9910_CHANNEL_PARALLEL_PORT: Parallel port output channel
+ * @AD9910_CHANNEL_DRG: Digital Ramp Generator output channel
+ * @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel
+ * @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel
*/
enum ad9910_channel {
AD9910_CHANNEL_PHY = 100,
AD9910_CHANNEL_SINGLE_TONE = 110,
AD9910_CHANNEL_PARALLEL_PORT = 120,
+ AD9910_CHANNEL_DRG = 130,
+ AD9910_CHANNEL_DRG_RAMP_UP = 131,
+ AD9910_CHANNEL_DRG_RAMP_DOWN = 132,
+};
+
+/**
+ * enum ad9910_destination - AD9910 DDS core parameter destination
+ *
+ * @AD9910_DEST_FREQUENCY: Frequency destination
+ * @AD9910_DEST_PHASE: Phase destination
+ * @AD9910_DEST_AMPLITUDE: Amplitude destination
+ * @AD9910_DEST_POLAR: Polar 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 {
@@ -216,6 +267,9 @@ enum {
AD9910_PP_FREQ_OFFSET,
AD9910_PP_PHASE_OFFSET,
AD9910_PP_AMP_OFFSET,
+ AD9910_DRG_FREQ_STEP,
+ AD9910_DRG_PHASE_STEP,
+ AD9910_DRG_AMP_STEP,
};
struct ad9910_data {
@@ -269,6 +323,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.
@@ -462,6 +530,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,
@@ -633,6 +761,133 @@ static ssize_t ad9910_pp_attrs_write(struct iio_dev *indio_dev,
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 (chan->channel) {
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp64 = FIELD_GET(AD9910_DRG_STEP_INC_MSK,
+ st->reg[AD9910_REG_DRG_STEP].val64);
+ break;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ 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_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_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_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;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_DRG_FREQ_STEP:
+ ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
+ if (ret)
+ return ret;
+
+ if (!in_range(val, 0, 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_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_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);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ 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_CHANNEL_DRG_RAMP_DOWN:
+ 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, \
@@ -647,6 +902,23 @@ 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_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_phy_ext_info[] = {
AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SEPARATE),
AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
@@ -661,6 +933,21 @@ 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),
+ { }
+};
+
+static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = {
+ AD9910_DRG_EXT_INFO("frequency_step", AD9910_DRG_FREQ_STEP),
+ AD9910_DRG_EXT_INFO("phase_step", AD9910_DRG_PHASE_STEP),
+ AD9910_DRG_EXT_INFO("scale_step", AD9910_DRG_AMP_STEP),
+ { }
+};
+
static const struct iio_chan_spec ad9910_channels[] = {
[AD9910_CHAN_IDX_PHY] = {
.type = IIO_ALTVOLTAGE,
@@ -692,6 +979,42 @@ static const struct iio_chan_spec ad9910_channels[] = {
.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE),
.ext_info = ad9910_pp_ext_info,
},
+ [AD9910_CHAN_IDX_DRG] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG,
+ .address = AD9910_CHAN_IDX_DRG,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE),
+ .ext_info = ad9910_drg_ext_info,
+ },
+ [AD9910_CHAN_IDX_DRG_RAMP_UP] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_UP,
+ .address = AD9910_CHAN_IDX_DRG_RAMP_UP,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY) |
+ BIT(IIO_CHAN_INFO_PHASE) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ },
+ [AD9910_CHAN_IDX_DRG_RAMP_DOWN] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
+ .address = AD9910_CHAN_IDX_DRG_RAMP_DOWN,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY) |
+ BIT(IIO_CHAN_INFO_PHASE) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ },
};
static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -711,6 +1034,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;
}
@@ -721,6 +1048,14 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
st->reg[AD9910_REG_PROFILE(st->profile)].val64);
break;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp32 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ break;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp32 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ break;
default:
return -EINVAL;
}
@@ -737,6 +1072,18 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
*val = tmp32 / MICRO;
*val2 = tmp32 % MICRO;
return IIO_VAL_INT_PLUS_MICRO;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ tmp64 = (tmp64 * AD9910_PI_NANORAD) >> 31;
+ *val = div_u64_rem(tmp64, NANO, val2);
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ tmp64 = (tmp64 * AD9910_PI_NANORAD) >> 31;
+ *val = div_u64_rem(tmp64, NANO, val2);
+ return IIO_VAL_INT_PLUS_NANO;
default:
return -EINVAL;
}
@@ -748,6 +1095,18 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
*val = 0;
*val2 = tmp64 * MICRO >> 14;
return IIO_VAL_INT_PLUS_MICRO;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ *val = 0;
+ *val2 = tmp64 * NANO >> 32;
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ *val = 0;
+ *val2 = tmp64 * NANO >> 32;
+ return IIO_VAL_INT_PLUS_NANO;
default:
return -EINVAL;
}
@@ -756,9 +1115,23 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
case AD9910_CHANNEL_PHY:
*val = st->data.sysclk_freq_hz;
return IIO_VAL_INT;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp32 = FIELD_GET(AD9910_DRG_RATE_INC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ break;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp32 = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ 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;
}
@@ -783,6 +1156,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;
}
@@ -799,6 +1177,16 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
AD9910_PROFILE_ST_FTW_MSK,
tmp64, true);
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_UPPER_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_LOWER_MSK,
+ tmp64, true);
default:
return -EINVAL;
}
@@ -819,6 +1207,30 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
AD9910_PROFILE_ST_POW_MSK,
tmp64, true);
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp64 = (u64)val * NANO + val2;
+ if (tmp64 > 2ULL * AD9910_PI_NANORAD)
+ return -EINVAL;
+
+ tmp64 <<= 31;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_PI_NANORAD);
+ tmp64 = min(tmp64, U32_MAX);
+ tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_UPPER_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp64 = (u64)val * NANO + val2;
+ if (tmp64 > 2ULL * AD9910_PI_NANORAD)
+ return -EINVAL;
+
+ tmp64 <<= 31;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_PI_NANORAD);
+ tmp64 = min(tmp64, U32_MAX);
+ tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_LOWER_MSK,
+ tmp64, true);
default:
return -EINVAL;
}
@@ -835,11 +1247,50 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
AD9910_PROFILE_ST_ASF_MSK,
tmp64, true);
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp64 = ((u64)val * NANO + val2) << 32;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, NANO);
+ tmp64 = min(tmp64, U32_MAX);
+ tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_UPPER_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp64 = ((u64)val * NANO + val2) << 32;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, NANO);
+ tmp64 = min(tmp64, U32_MAX);
+ tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_LOWER_MSK,
+ tmp64, true);
default:
return -EINVAL;
}
case IIO_CHAN_INFO_SAMP_FREQ:
- return ad9910_set_sysclk_freq(st, val, true);
+ if (chan->channel == AD9910_CHANNEL_PHY)
+ return ad9910_set_sysclk_freq(st, val, true);
+
+ 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_DRG_RAMP_UP:
+ tmp32 = FIELD_PREP(AD9910_DRG_RATE_INC_MSK, tmp32);
+ return ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
+ AD9910_DRG_RATE_INC_MSK,
+ tmp32, true);
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp32 = FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, tmp32);
+ return ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
+ AD9910_DRG_RATE_DEC_MSK,
+ tmp32, true);
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -859,11 +1310,16 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
switch (chan->channel) {
case AD9910_CHANNEL_SINGLE_TONE:
return IIO_VAL_INT_PLUS_MICRO;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ return IIO_VAL_INT_PLUS_NANO;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_SAMP_FREQ:
- return IIO_VAL_INT;
+ if (chan->channel == AD9910_CHANNEL_PHY)
+ return IIO_VAL_INT;
+ return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
@@ -1040,6 +1496,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] 27+ messages in thread
* [PATCH RFC v2 5/9] iio: frequency: ad9910: add RAM mode support
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (3 preceding siblings ...)
2026-03-18 17:56 ` [PATCH RFC v2 4/9] iio: frequency: ad9910: add digital ramp generator support Rodrigo Alencar via B4 Relay
@ 2026-03-18 17:56 ` Rodrigo Alencar via B4 Relay
2026-03-22 17:05 ` Jonathan Cameron
2026-03-18 17:56 ` [PATCH RFC v2 6/9] iio: frequency: ad9910: add output shift keying support Rodrigo Alencar via B4 Relay
` (3 subsequent siblings)
8 siblings, 1 reply; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add RAM channel with support for profile-based control. This includes:
- RAM data loading via firmware upload interface;
- 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-enable-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/Kconfig | 2 +
drivers/iio/frequency/ad9910.c | 464 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 462 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index 180e74f62d11..a5b2e5cb5269 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -29,6 +29,8 @@ config AD9910
tristate "Analog Devices AD9910 Direct Digital Synthesizer"
depends on SPI
depends on GPIOLIB
+ select FW_LOADER
+ select FW_UPLOAD
help
Say yes here to build support for Analog Devices AD9910
1 GSPS, 14-Bit DDS with integrated DAC.
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index d3367e211dcf..747f4f407536 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -8,9 +8,11 @@
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/clk.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/firmware.h>
#include <linux/gpio/consumer.h>
#include <linux/log2.h>
#include <linux/math64.h>
@@ -149,6 +151,15 @@
#define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32)
#define AD9910_PROFILE_ST_FTW_MSK GENMASK_ULL(31, 0)
+/* 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
@@ -163,6 +174,17 @@
#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)
+
+#define AD9910_RAM_ENABLED(st) \
+ FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, (st)->reg[AD9910_REG_CFR1].val32)
/* PLL constants */
#define AD9910_PLL_MIN_N 12
@@ -195,7 +217,7 @@
#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_DATA_LEN_MAX AD9910_RAM_SIZE_MAX_BYTES
#define AD9910_SPI_MESSAGE_LEN_MAX (AD9910_SPI_DATA_IDX + AD9910_SPI_DATA_LEN_MAX)
#define AD9910_SPI_READ_MSK BIT(7)
#define AD9910_SPI_ADDR_MSK GENMASK(4, 0)
@@ -209,6 +231,7 @@
* @AD9910_CHANNEL_DRG: Digital Ramp Generator output channel
* @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel
* @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel
+ * @AD9910_CHANNEL_RAM: RAM control output channel
*/
enum ad9910_channel {
AD9910_CHANNEL_PHY = 100,
@@ -217,6 +240,7 @@ enum ad9910_channel {
AD9910_CHANNEL_DRG = 130,
AD9910_CHANNEL_DRG_RAMP_UP = 131,
AD9910_CHANNEL_DRG_RAMP_DOWN = 132,
+ AD9910_CHANNEL_RAM = 140,
};
/**
@@ -249,6 +273,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_CHAN_IDX_PHY,
AD9910_CHAN_IDX_SINGLE_TONE,
@@ -270,6 +315,8 @@ enum {
AD9910_DRG_FREQ_STEP,
AD9910_DRG_PHASE_STEP,
AD9910_DRG_AMP_STEP,
+ AD9910_RAM_START_ADDR,
+ AD9910_RAM_END_ADDR,
};
struct ad9910_data {
@@ -284,6 +331,7 @@ struct ad9910_data {
struct ad9910_state {
struct spi_device *spi;
struct clk *refclk;
+ struct fw_upload *ram_fwu;
struct gpio_desc *gpio_pwdown;
struct gpio_desc *gpio_update;
@@ -296,12 +344,22 @@ 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 */
struct mutex lock;
struct ad9910_data data;
u8 profile;
+ bool ram_fwu_cancel;
+ char ram_fwu_name[20];
+
union {
__be64 be64;
__be32 be32;
@@ -337,6 +395,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.
@@ -352,6 +420,22 @@ static const char * const ad9910_drg_oper_mode_str[] = {
mul_u64_add_u64_div_u64(input, scale, _tmp >> 1, _tmp); \
})
+static inline u64 ad9910_ram_profile_val(struct ad9910_state *st)
+{
+ if (AD9910_RAM_ENABLED(st))
+ return st->reg_profile[st->profile];
+ else
+ return st->reg[st->profile].val64;
+}
+
+static inline u64 ad9910_st_profile_val(struct ad9910_state *st)
+{
+ if (AD9910_RAM_ENABLED(st))
+ return st->reg[st->profile].val64;
+ else
+ return st->reg_profile[st->profile];
+}
+
static int ad9910_io_update(struct ad9910_state *st)
{
if (st->gpio_update) {
@@ -544,6 +628,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 (AD9910_RAM_ENABLED(st))
+ 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;
}
@@ -560,6 +652,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;
}
@@ -590,6 +685,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 (AD9910_RAM_ENABLED(st))
+ 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 (AD9910_RAM_ENABLED(st))
+ return FIELD_GET(AD9910_PROFILE_RAM_MODE_CONTROL_MSK,
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+
+ 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,
@@ -612,6 +794,14 @@ 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:
+ val = FIELD_GET(AD9910_PROFILE_RAM_START_ADDR_MSK,
+ ad9910_ram_profile_val(st));
+ break;
+ case AD9910_RAM_END_ADDR:
+ val = FIELD_GET(AD9910_PROFILE_RAM_END_ADDR_MSK,
+ ad9910_ram_profile_val(st));
+ break;
default:
return -EINVAL;
}
@@ -656,6 +846,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 (AD9910_RAM_ENABLED(st))
+ 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 (AD9910_RAM_ENABLED(st))
+ 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;
}
@@ -919,6 +1136,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_phy_ext_info[] = {
AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SEPARATE),
AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
@@ -948,6 +1179,16 @@ static const struct iio_chan_spec_ext_info ad9910_drg_ramp_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_CHAN_IDX_PHY] = {
.type = IIO_ALTVOLTAGE,
@@ -1015,6 +1256,19 @@ static const struct iio_chan_spec ad9910_channels[] = {
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.ext_info = ad9910_drg_ramp_ext_info,
},
+ [AD9910_CHAN_IDX_RAM] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_RAM,
+ .address = AD9910_CHAN_IDX_RAM,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_FREQUENCY) |
+ BIT(IIO_CHAN_INFO_PHASE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_ram_ext_info,
+ },
};
static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -1038,6 +1292,10 @@ 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 = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ break;
default:
return -EINVAL;
}
@@ -1046,7 +1304,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
switch (chan->channel) {
case AD9910_CHANNEL_SINGLE_TONE:
tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
- st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ ad9910_st_profile_val(st));
break;
case AD9910_CHANNEL_DRG_RAMP_UP:
tmp32 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
@@ -1056,6 +1314,9 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
tmp32 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
st->reg[AD9910_REG_DRG_LIMIT].val64);
break;
+ case AD9910_CHANNEL_RAM:
+ tmp32 = st->reg[AD9910_REG_FTW].val32;
+ break;
default:
return -EINVAL;
}
@@ -1067,7 +1328,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
switch (chan->channel) {
case AD9910_CHANNEL_SINGLE_TONE:
tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
- st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ ad9910_st_profile_val(st));
tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16;
*val = tmp32 / MICRO;
*val2 = tmp32 % MICRO;
@@ -1084,6 +1345,12 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
tmp64 = (tmp64 * AD9910_PI_NANORAD) >> 31;
*val = div_u64_rem(tmp64, NANO, val2);
return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHANNEL_RAM:
+ tmp64 = st->reg[AD9910_REG_POW].val16;
+ tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16;
+ *val = tmp32 / MICRO;
+ *val2 = tmp32 % MICRO;
+ return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
@@ -1091,7 +1358,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
switch (chan->channel) {
case AD9910_CHANNEL_SINGLE_TONE:
tmp64 = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
- st->reg[AD9910_REG_PROFILE(st->profile)].val64);
+ ad9910_st_profile_val(st));
*val = 0;
*val2 = tmp64 * MICRO >> 14;
return IIO_VAL_INT_PLUS_MICRO;
@@ -1123,6 +1390,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
tmp32 = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
st->reg[AD9910_REG_DRG_RATE].val32);
break;
+ case AD9910_CHANNEL_RAM:
+ tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK,
+ ad9910_ram_profile_val(st));
+ break;
default:
return -EINVAL;
}
@@ -1144,6 +1415,7 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
struct ad9910_state *st = iio_priv(indio_dev);
u64 tmp64;
u32 tmp32;
+ int ret, i;
guard(mutex)(&st->lock);
@@ -1161,6 +1433,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 (AD9910_RAM_ENABLED(st) == val)
+ return 0;
+
+ /* switch profile configs */
+ for (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;
}
@@ -1173,6 +1465,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
tmp64 = min(tmp64, U32_MAX);
switch (chan->channel) {
case AD9910_CHANNEL_SINGLE_TONE:
+ if (AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_ST_FTW_MSK,
+ &st->reg_profile[st->profile], tmp64);
+ return 0;
+ }
tmp64 = FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64);
return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
AD9910_PROFILE_ST_FTW_MSK,
@@ -1187,6 +1484,8 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
AD9910_DRG_LIMIT_LOWER_MSK,
tmp64, true);
+ case AD9910_CHANNEL_RAM:
+ return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true);
default:
return -EINVAL;
}
@@ -1203,6 +1502,13 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
tmp64 <<= 16;
tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_MAX_PHASE_MICRORAD);
tmp64 = min(tmp64, AD9910_POW_MAX);
+
+ if (AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_ST_POW_MSK,
+ &st->reg_profile[st->profile], tmp64);
+ return 0;
+ }
+
tmp64 = FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64);
return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
AD9910_PROFILE_ST_POW_MSK,
@@ -1231,6 +1537,15 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
AD9910_DRG_LIMIT_LOWER_MSK,
tmp64, true);
+ case AD9910_CHANNEL_RAM:
+ tmp64 = (u64)val * MICRO + val2;
+ if (tmp64 >= AD9910_MAX_PHASE_MICRORAD)
+ return -EINVAL;
+
+ tmp64 <<= 16;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_MAX_PHASE_MICRORAD);
+ tmp64 = min(tmp64, AD9910_POW_MAX);
+ return ad9910_reg16_write(st, AD9910_REG_POW, tmp64, true);
default:
return -EINVAL;
}
@@ -1243,6 +1558,13 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
tmp64 = ((u64)val * MICRO + val2) << 14;
tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, MICRO);
tmp64 = min(tmp64, AD9910_ASF_MAX);
+
+ if (AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_ST_ASF_MSK,
+ &st->reg_profile[st->profile], tmp64);
+ return 0;
+ }
+
tmp64 = FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp64);
return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
AD9910_PROFILE_ST_ASF_MSK,
@@ -1288,12 +1610,26 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
AD9910_DRG_RATE_DEC_MSK,
tmp32, true);
+ case AD9910_CHANNEL_RAM:
+ if (!AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_RAM_STEP_RATE_MSK,
+ &st->reg_profile[st->profile], tmp32);
+ return 0;
+ }
+
+ 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);
+
default:
return -EINVAL;
}
default:
return -EINVAL;
}
+
+ return ret;
}
static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
@@ -1309,6 +1645,7 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_SCALE:
switch (chan->channel) {
case AD9910_CHANNEL_SINGLE_TONE:
+ case AD9910_CHANNEL_RAM:
return IIO_VAL_INT_PLUS_MICRO;
case AD9910_CHANNEL_DRG_RAMP_UP:
case AD9910_CHANNEL_DRG_RAMP_DOWN:
@@ -1401,6 +1738,88 @@ static int ad9910_reg_access(struct iio_dev *indio_dev,
return ret;
}
+static enum fw_upload_err ad9910_ram_fwu_prepare(struct fw_upload *fw_upload,
+ const u8 *data, u32 size)
+{
+ struct ad9910_state *st = fw_upload->dd_handle;
+
+ if (size == 0 || size > AD9910_RAM_SIZE_MAX_BYTES ||
+ size % AD9910_RAM_WORD_SIZE)
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+
+ guard(mutex)(&st->lock);
+ st->ram_fwu_cancel = false;
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err ad9910_ram_fwu_write(struct fw_upload *fw_upload,
+ const u8 *data, u32 offset,
+ u32 size, u32 *written)
+{
+ struct ad9910_state *st = fw_upload->dd_handle;
+ u64 tmp64, backup;
+ int ret, ret2, idx;
+
+ if (offset != 0)
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+
+ guard(mutex)(&st->lock);
+
+ if (st->ram_fwu_cancel)
+ return FW_UPLOAD_ERR_CANCELED;
+
+ if (AD9910_RAM_ENABLED(st))
+ return FW_UPLOAD_ERR_HW_ERROR;
+
+ /* ensure profile is selected */
+ ret = ad9910_profile_set(st, st->profile);
+ if (ret)
+ return FW_UPLOAD_ERR_HW_ERROR;
+
+ /* backup profile register */
+ backup = st->reg[AD9910_REG_PROFILE(st->profile)].val64;
+ tmp64 = AD9910_PROFILE_RAM_STEP_RATE_MSK |
+ FIELD_PREP(AD9910_PROFILE_RAM_START_ADDR_MSK, 0) |
+ FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK,
+ size / AD9910_RAM_WORD_SIZE - 1);
+ ret = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), tmp64, true);
+ if (ret)
+ return FW_UPLOAD_ERR_RW_ERROR;
+
+ /* reverse data into tx_buf[1:] */
+ for (idx = 0; idx < size; idx++)
+ st->tx_buf[size - idx] = data[idx];
+
+ /* write ram data and restore profile register */
+ ret = ad9910_spi_write(st, AD9910_REG_RAM, size, false);
+ ret2 = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), backup, true);
+ if (ret || ret2)
+ return FW_UPLOAD_ERR_RW_ERROR;
+
+ *written = size;
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err ad9910_ram_fwu_poll_complete(struct fw_upload *fw_upload)
+{
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static void ad9910_ram_fwu_cancel(struct fw_upload *fw_upload)
+{
+ struct ad9910_state *st = fw_upload->dd_handle;
+
+ guard(mutex)(&st->lock);
+ st->ram_fwu_cancel = true;
+}
+
+static const struct fw_upload_ops ad9910_ram_fwu_ops = {
+ .prepare = ad9910_ram_fwu_prepare,
+ .write = ad9910_ram_fwu_write,
+ .poll_complete = ad9910_ram_fwu_poll_complete,
+ .cancel = ad9910_ram_fwu_cancel
+};
+
static const struct iio_info ad9910_info = {
.read_raw = ad9910_read_raw,
.write_raw = ad9910_write_raw,
@@ -1503,6 +1922,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);
}
@@ -1510,6 +1936,8 @@ static void ad9910_release(void *data)
{
struct ad9910_state *st = data;
+ firmware_upload_unregister(st->ram_fwu);
+
if (!ad9910_powerdown_set(st, true))
return;
@@ -1519,6 +1947,24 @@ static void ad9910_release(void *data)
true);
}
+static inline void ad9910_debugfs_init(struct ad9910_state *st,
+ struct iio_dev *indio_dev)
+{
+#ifdef CONFIG_DEBUG_FS
+ char buf[64];
+
+ /*
+ * symlinks are created here so iio userspace tools can refer to them
+ * as debug attributes.
+ */
+ snprintf(buf, sizeof(buf), "/sys/class/firmware/%s/loading", st->ram_fwu_name);
+ debugfs_create_symlink("ram_loading", iio_get_debugfs_dentry(indio_dev), buf);
+
+ snprintf(buf, sizeof(buf), "/sys/class/firmware/%s/data", st->ram_fwu_name);
+ debugfs_create_symlink("ram_data", iio_get_debugfs_dentry(indio_dev), buf);
+#endif
+}
+
static int ad9910_probe(struct spi_device *spi)
{
struct reset_control *dev_rst;
@@ -1606,6 +2052,16 @@ static int ad9910_probe(struct spi_device *spi)
if (ret)
return ret;
+ snprintf(st->ram_fwu_name, sizeof(st->ram_fwu_name), "%s:ram",
+ dev_name(&indio_dev->dev));
+ st->ram_fwu = firmware_upload_register(THIS_MODULE, dev, st->ram_fwu_name,
+ &ad9910_ram_fwu_ops, st);
+ if (IS_ERR(st->ram_fwu))
+ return dev_err_probe(dev, PTR_ERR(st->ram_fwu),
+ "failed to register to the RAM Upload\n");
+
+ ad9910_debugfs_init(st, indio_dev);
+
return devm_add_action_or_reset(dev, ad9910_release, st);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH RFC v2 6/9] iio: frequency: ad9910: add output shift keying support
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (4 preceding siblings ...)
2026-03-18 17:56 ` [PATCH RFC v2 5/9] iio: frequency: ad9910: add RAM mode support Rodrigo Alencar via B4 Relay
@ 2026-03-18 17:56 ` Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 7/9] iio: frequency: ad9910: add channel labels Rodrigo Alencar via B4 Relay
` (2 subsequent siblings)
8 siblings, 0 replies; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
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 readback.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/ad9910.c | 152 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 151 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 747f4f407536..35572d60d6d2 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -232,6 +232,7 @@
* @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel
* @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel
* @AD9910_CHANNEL_RAM: RAM control output channel
+ * @AD9910_CHANNEL_OSK: Output Shift Keying output channel
*/
enum ad9910_channel {
AD9910_CHANNEL_PHY = 100,
@@ -241,6 +242,7 @@ enum ad9910_channel {
AD9910_CHANNEL_DRG_RAMP_UP = 131,
AD9910_CHANNEL_DRG_RAMP_DOWN = 132,
AD9910_CHANNEL_RAM = 140,
+ AD9910_CHANNEL_OSK = 150,
};
/**
@@ -317,6 +319,8 @@ enum {
AD9910_DRG_AMP_STEP,
AD9910_RAM_START_ADDR,
AD9910_RAM_END_ADDR,
+ AD9910_OSK_MANUAL_EXTCTL,
+ AD9910_OSK_AUTO_STEP,
};
struct ad9910_data {
@@ -405,6 +409,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.
@@ -802,6 +810,10 @@ static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
val = FIELD_GET(AD9910_PROFILE_RAM_END_ADDR_MSK,
ad9910_ram_profile_val(st));
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;
}
@@ -873,6 +885,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;
}
@@ -1105,6 +1123,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, \
@@ -1122,6 +1214,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,
@@ -1189,6 +1284,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_step", AD9910_OSK_AUTO_STEP),
+ { }
+};
+
static const struct iio_chan_spec ad9910_channels[] = {
[AD9910_CHAN_IDX_PHY] = {
.type = IIO_ALTVOLTAGE,
@@ -1269,6 +1370,18 @@ static const struct iio_chan_spec ad9910_channels[] = {
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.ext_info = ad9910_ram_ext_info,
},
+ [AD9910_CHAN_IDX_OSK] = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_OSK,
+ .address = AD9910_CHAN_IDX_OSK,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_osk_ext_info,
+ },
};
static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -1296,6 +1409,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
*val = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK,
st->reg[AD9910_REG_CFR1].val32);
break;
+ case AD9910_CHANNEL_OSK:
+ *val = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ break;
default:
return -EINVAL;
}
@@ -1374,6 +1491,12 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
*val = 0;
*val2 = tmp64 * NANO >> 32;
return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHANNEL_OSK:
+ tmp64 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ *val = 0;
+ *val2 = tmp64 * MICRO >> 14;
+ return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
@@ -1394,6 +1517,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK,
ad9910_ram_profile_val(st));
break;
+ case AD9910_CHANNEL_OSK:
+ tmp32 = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ break;
default:
return -EINVAL;
}
@@ -1453,6 +1580,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;
}
@@ -1585,6 +1717,14 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
AD9910_DRG_LIMIT_LOWER_MSK,
tmp64, true);
+ case AD9910_CHANNEL_OSK:
+ tmp64 = ((u64)val * MICRO + val2) << 14;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, MICRO);
+ tmp32 = min(tmp64, AD9910_ASF_MAX);
+ tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_MSK, tmp32);
+ return ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_SCALE_FACTOR_MSK,
+ tmp32, true);
default:
return -EINVAL;
}
@@ -1621,7 +1761,12 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
AD9910_PROFILE_RAM_STEP_RATE_MSK,
tmp64, true);
-
+ 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;
}
@@ -1916,6 +2061,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] 27+ messages in thread
* [PATCH RFC v2 7/9] iio: frequency: ad9910: add channel labels
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (5 preceding siblings ...)
2026-03-18 17:56 ` [PATCH RFC v2 6/9] iio: frequency: ad9910: add output shift keying support Rodrigo Alencar via B4 Relay
@ 2026-03-18 17:56 ` Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 8/9] Documentation: ABI: testing: add docs for ad9910 sysfs entries Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 9/9] docs: iio: add documentation for ad9910 driver Rodrigo Alencar via B4 Relay
8 siblings, 0 replies; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add human-readable labels for all AD9910 IIO channels via the read_label
callback.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/ad9910.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 35572d60d6d2..8ff2a9d69265 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -1965,10 +1965,29 @@ static const struct fw_upload_ops ad9910_ram_fwu_ops = {
.cancel = ad9910_ram_fwu_cancel
};
+static const char * const ad9910_channel_str[] = {
+ [AD9910_CHAN_IDX_PHY] = "phy",
+ [AD9910_CHAN_IDX_SINGLE_TONE] = "single_tone",
+ [AD9910_CHAN_IDX_PARALLEL_PORT] = "parallel_port",
+ [AD9910_CHAN_IDX_DRG] = "digital_ramp_generator",
+ [AD9910_CHAN_IDX_DRG_RAMP_UP] = "digital_ramp_up",
+ [AD9910_CHAN_IDX_DRG_RAMP_DOWN] = "digital_ramp_down",
+ [AD9910_CHAN_IDX_RAM] = "ram_control",
+ [AD9910_CHAN_IDX_OSK] = "output_shift_keying",
+};
+
+static int ad9910_read_label(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ char *label)
+{
+ return sysfs_emit(label, "%s\n", ad9910_channel_str[chan->address]);
+}
+
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,
.debugfs_reg_access = &ad9910_reg_access,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH RFC v2 8/9] Documentation: ABI: testing: add docs for ad9910 sysfs entries
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (6 preceding siblings ...)
2026-03-18 17:56 ` [PATCH RFC v2 7/9] iio: frequency: ad9910: add channel labels Rodrigo Alencar via B4 Relay
@ 2026-03-18 17:56 ` Rodrigo Alencar via B4 Relay
2026-03-22 17:22 ` Jonathan Cameron
2026-03-18 17:56 ` [PATCH RFC v2 9/9] docs: iio: add documentation for ad9910 driver Rodrigo Alencar via B4 Relay
8 siblings, 1 reply; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add ABI documentation file for the DDS AD9910 with sysfs entries to
control Parallel Port, Digital Ramp Generator, RAM and OSK parameters.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
.../ABI/testing/sysfs-bus-iio-frequency-ad9910 | 182 +++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 183 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 b/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
new file mode 100644
index 000000000000..120de494f6b1
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
@@ -0,0 +1,182 @@
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_profile
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the active profile index [0, 7] from/to the physical
+ channel. The AD9910 supports 8 profiles, each storing a complete
+ set of single tone (frequency, phase, amplitude) and RAM playback
+ parameters.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_offset
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the parallel port frequency offset in Hz. This is the
+ base frequency tuning word (FTW register) to which the scaled
+ parallel port data is added during parallel data port modulation.
+ Valid range is [0, SYSCLK/2).
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_scale
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the parallel port frequency modulation gain. The value
+ must be a power of 2 in the [1, 2^15] range. This value scales the
+ 16-bit parallel data port input before adding it to the
+ frequency_offset value.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_phase_offset
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the parallel port phase offset in radians. Valid range
+ is [0, 2*pi/256). This sets the lower 8 bits of the phase offset
+ word (POW register) used as a base during parallel port polar
+ modulation.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_offset
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the parallel port amplitude scale offset. Valid range
+ is [0, 1/256). This sets the lower 6 bits of the amplitude scale
+ factor (ASF register) used as a base during parallel port polar
+ modulation.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_destination
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the digital ramp generator (DRG) or the RAM control
+ destination parameter. Determines which DDS core parameter is to
+ be modulated when the child mode channel is enabled.
+
+ Available values can be read from the corresponding
+ out_altvoltageY_destination_available attribute.
+
+ Valid values: "polar" (only for RAM control), "frequency", "phase"
+ and "amplitude"
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_destination_available
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Lists the available destination values for the DRG channel:
+ "frequency phase amplitude"; or for the RAM control channel:
+ "frequency phase amplitude polar".
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_operating_mode
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the DRG or RAM control operating mode. For the DRG
+ channel it controls the no-dwell behavior of the ramp.
+
+ Available values can be read from the corresponding
+ out_altvoltageY_operating_mode_available attribute.
+
+ Valid values for DRG channel:
+
+ - "bidirectional": Normal ramp generation (ramp up then
+ down, dwelling at limits).
+ - "ramp_down": No-dwell low; the ramp resets to upper
+ limit upon reaching the lower limit.
+ - "ramp_up": No-dwell high; the ramp resets to lower
+ limit upon reaching the upper limit.
+ - "bidirectional_continuous": Both no-dwell high and low;
+ the ramp continuously sweeps without dwelling.
+
+ Valid values for RAM control channel:
+
+ - "direct_switch": start address defines fixed word to be used
+ by the selected profile.
+ - "ramp_up": One-shot ramp up through current profile's address
+ range.
+ - "bidirectional": Ramp up then down through PROFILE0 pin.
+ - "bidirectional_continuous": Continuous ramp up/down
+ through current profile's address range.
+ - "ramp_up_continuous": Continuous ramp up through
+ current profile's address range.
+ - "sequenced": Sequenced playback of RAM profiles up to
+ the active profile. Requires active profile > 0.
+ - "sequenced_continuous": Continuous sequenced playback
+ of RAM profiles up to the active profile. Requires
+ active profile > 0.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_operating_mode_available
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ For the DRG channel it lists the available operating mode values:
+ "bidirectional ramp_down ramp_up bidirectional_continuous".
+
+ For the RAM control channel it lists the available operating mode
+ values:
+ "direct_switch ramp_up bidirectional bidirectional_continuous
+ ramp_up_continuous sequenced sequenced_continuous".
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_step
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the DRG frequency step size in Hz for ramp up and ramp
+ down DRG channels. This is the increment/decrement step applied to
+ the DRG frequency value, which is input to the DDS core and it is
+ updated at each ramp clock tick when the DRG destination is
+ set to "frequency". Valid range is [0, sysclk/2).
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_phase_step
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the DRG phase step size in radians for ramp up and ramp
+ down DRG channels. This is the increment/decrement step applied to
+ the DRG phase value, which is input to the DDS core and it is
+ updated at each ramp clock tick when the DRG destination is
+ set to "phase". Valid range is [0, 2*pi).
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_step
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ For the DRG ramp up/down channels this is used to read/write the
+ DRG amplitude step size, which is applied as an
+ increment/decrement to the DRG amplitude value, input to the DDS
+ core, updated at each ramp clock tick when the DRG destination is
+ set to "amplitude". Valid range is [0, 1].
+
+ For the OSK channel this is used to read/write the automatic OSK
+ amplitude ramp step size. Writing a non-zero value enables
+ automatic OSK mode and sets the amplitude step size. Writing "0"
+ disables automatic OSK mode. The value is rounded to the nearest
+ hardware supported step: 0.000061, 0.000122, 0.000244, or
+ 0.000488.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_address_start
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the RAM start address for the active profile. Defines
+ the first RAM word address used during playback. Cannot be
+ changed while RAM mode is enabled. Valid range is [0, 1023].
+ If set above the current end address, the end address is
+ automatically adjusted to match.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_address_end
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the RAM end address for the active profile. Defines
+ the last RAM word address used during playback. Cannot be
+ changed while RAM mode is enabled. Valid range is
+ [address_start, 1023].
+
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_pinctrl_en
+KernelVersion:
+Contact: linux-iio@vger.kernel.org
+Description:
+ Read/write the OSK manual external control enable. Writing '1'
+ enables manual control of the output amplitude envelope via an
+ external pin. Writing '0' disables it. When enabled, the OSK pin
+ directly controls the amplitude on/off state rather than using
+ the automatic OSK ramp.
diff --git a/MAINTAINERS b/MAINTAINERS
index 6403439b530d..edd87ee7da5f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1635,6 +1635,7 @@ 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/ABI/testing/sysfs-bus-iio-frequency-ad9910
F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
F: drivers/iio/frequency/ad9910.c
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH RFC v2 9/9] docs: iio: add documentation for ad9910 driver
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
` (7 preceding siblings ...)
2026-03-18 17:56 ` [PATCH RFC v2 8/9] Documentation: ABI: testing: add docs for ad9910 sysfs entries Rodrigo Alencar via B4 Relay
@ 2026-03-18 17:56 ` Rodrigo Alencar via B4 Relay
2026-03-22 17:34 ` Jonathan Cameron
8 siblings, 1 reply; 27+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-03-18 17:56 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel, linux-doc
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
Rodrigo Alencar
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add documentation for the AD9910 DDS IIO driver, which describes channels,
DDS modes, attributes and ABI usage examples.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Documentation/iio/ad9910.rst | 654 +++++++++++++++++++++++++++++++++++++++++++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 1 +
3 files changed, 656 insertions(+)
diff --git a/Documentation/iio/ad9910.rst b/Documentation/iio/ad9910.rst
new file mode 100644
index 000000000000..116f6af4bc2e
--- /dev/null
+++ b/Documentation/iio/ad9910.rst
@@ -0,0 +1,654 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+=============
+AD9910 driver
+=============
+
+DDS (Direct Digital Synthesizer) driver for the Analog Devices Inc. AD9910.
+The module name is ``ad9910``.
+
+* `AD9910 <https://www.analog.com/en/products/ad9910.html>`_
+
+The AD9910 is a 1 GSPS DDS with a 14-bit DAC, driven over SPI. The driver
+exposes the device through the IIO ``altvoltage`` channel type and supports
+five DDS operating modes: single tone, parallel port modulation, digital ramp
+generation (DRG), RAM playback and output shift keying (OSK). The device has
+8 hardware profiles, each capable of storing independent single tone and RAM
+playback parameters.
+
+
+Channel hierarchy
+=================
+
+The driver exposes the following IIO output channels, each identified by a
+unique channel number and a human-readable label:
+
+* ``out_altvoltage100``: ``phy``: Physical output: system clock and profile control
+
+ * ``out_altvoltage110``: ``single_tone``: Single tone mode: per-profile
+ frequency, phase, amplitude
+
+ * ``out_altvoltage120``: ``parallel_port``: Parallel port modulation: enable
+ and offset/scale parameters
+
+ * ``out_altvoltage130``: ``digital_ramp_generator``: DRG control: enable,
+ destination, operating mode
+
+ * ``out_altvoltage131``: ``digital_ramp_up``: DRG ramp-up parameters:
+ limits, step sizes, ramp rate
+ * ``out_altvoltage132``: ``digital_ramp_down``: DRG ramp-down parameters:
+ limits, step sizes, ramp rate
+
+ * ``out_altvoltage140``: ``ram_control``: RAM playback: enable, destination,
+ operating mode, address range
+
+ * ``out_altvoltage150``: ``output_shift_keying``: OSK: enable, amplitude
+ scale, ramp rate, auto/manual control
+
+The ``phy`` channel is the root of the hierarchy. Changing its
+``sampling_frequency`` reconfigures the system clock (SYSCLK) which affects all
+other channels. The ``profile`` attribute on this channel selects the active
+hardware profile (0-7) used by the single tone and RAM channels.
+
+All mode-specific channels (parallel port, DRG, RAM, OSK) have an ``enable``
+attribute. The DRG and RAM channels additionally have ``destination`` and
+``operating_mode`` attributes that configure which DDS core parameter is
+modulated and how.
+
+DDS modes
+=========
+
+The AD9910 supports multiple modes of operation that can be configured
+independently or in combination. Such modes and their corresponding IIO channels
+are described in this section. The following tables are extracted from the
+AD9910 datasheet and summarizes the control parameters for each mode and their
+priority when multiple sources are enabled simultaneously:
+
+.. flat-table:: DDS Frequency Control
+ :header-rows: 1
+
+ * - Priority
+ - Data Source
+ - Conditions
+
+ * - Highest Priority
+ - RAM
+ - RAM enabled and data destination is frequency
+
+ * -
+ - DRG
+ - DRG enabled and data destination is frequency
+
+ * -
+ - Parallel data and FTW (frequency_offset)
+ - Parallel data port enabled and data destination is frequency
+
+ * -
+ - FTW (frequency)
+ - RAM enabled and data destination is not frequency
+
+ * -
+ - FTW (frequency) in single tone channel for the active profile
+ - DRG enabled and data destination is not frequency
+
+ * -
+ - FTW (frequency) in single tone channel for the active profile
+ - Parallel data port enabled and data destination is not frequency
+
+ * - Lowest Priority
+ - FTW (frequency) in single tone channel for the active profile
+ - None
+
+.. flat-table:: DDS Phase Control
+ :header-rows: 1
+
+ * - Priority
+ - Data Source
+ - Conditions
+
+ * - Highest Priority
+ - RAM
+ - RAM enabled and data destination is phase or polar
+
+ * -
+ - DRG
+ - DRG enabled and data destination is phase
+
+ * -
+ - Parallel data port
+ - Parallel data port enabled and data destination is phase
+
+ * -
+ - Parallel data port and POW register LSBs (phase_offset)
+ - Parallel data port enabled and data destination is polar
+
+ * -
+ - POW (phase)
+ - RAM enabled and destination is not phase nor polar
+
+ * -
+ - POW (phase) in single tone channel for the active profile
+ - DRG enabled and data destination is not phase
+
+ * -
+ - POW (phase) in single tone channel for the active profile
+ - Parallel data port enabled and data destination is not phase nor polar
+
+ * - Lowest Priority
+ - POW (phase) in single tone channel for the active profile
+ - None
+
+.. flat-table:: DDS Amplitude Control
+ :header-rows: 1
+
+ * - Priority
+ - Data Source
+ - Conditions
+
+ * - Highest Priority
+ - OSK generator
+ - OSK enabled (auto mode)
+
+ * -
+ - ASF register
+ - OSK enabled (manual mode)
+
+ * -
+ - RAM
+ - RAM enabled and data destination is amplitude or polar
+
+ * -
+ - DRG
+ - DRG enabled and data destination is amplitude
+
+ * -
+ - Parallel data port
+ - Parallel data port enabled and data destination is amplitude
+
+ * -
+ - Parallel data port and ASF register LSBs (scale_offset)
+ - Parallel data port enabled and data destination is polar
+
+ * - Lowest Priority
+ - ASF (scale) in single tone channel for the active profile
+ - (Amplitude scale is already enabled by default)
+
+Single tone mode
+----------------
+
+Single tone is the baseline operating mode. The ``single_tone`` channel
+provides per-profile frequency, phase and amplitude control:
+
+.. flat-table::
+ :header-rows: 1
+
+ * - Attribute
+ - Unit
+ - Description
+
+ * - ``out_altvoltage110_frequency``
+ - Hz
+ - Output frequency. Range [0, SYSCLK/2). Stored in the active profile's
+ frequency tuning word (FTW).
+
+ * - ``out_altvoltage110_phase``
+ - rad
+ - Phase offset. Range [0, 2*pi). Stored in the active profile's phase
+ offset word (POW).
+
+ * - ``out_altvoltage110_scale``
+ - fractional
+ - Amplitude scale factor. Range [0, 1]. Stored in the active profile's
+ amplitude scale factor (ASF).
+
+When RAM mode is enabled, single tone parameters are stored in a shadow
+register and are not written to hardware until RAM mode is disabled.
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Set the active profile to 2 and configure a 100 MHz tone:
+
+.. code-block:: bash
+
+ echo 2 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_profile
+ echo 100000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage110_frequency
+ echo 0.5 > /sys/bus/iio/devices/iio:device0/out_altvoltage110_scale
+ echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage110_phase
+
+Read back the current single tone frequency:
+
+.. code-block:: bash
+
+ cat /sys/bus/iio/devices/iio:device0/out_altvoltage110_frequency
+
+Parallel port mode
+------------------
+
+When enabled, the parallel port allows real-time modulation of DDS parameters
+through a 16-bit external data bus.
+
+.. flat-table::
+ :header-rows: 1
+
+ * - Attribute
+ - Unit
+ - Description
+
+ * - ``out_altvoltage120_en``
+ - boolean
+ - Enable/disable the parallel data port.
+
+ * - ``out_altvoltage120_frequency_scale``
+ - power-of-2
+ - FM gain multiplier applied to 16-bit parallel input. Range [1, 32768],
+ must be a power of 2.
+
+ * - ``out_altvoltage120_frequency_offset``
+ - Hz
+ - Base FTW to which scaled parallel data is added. Range [0, SYSCLK/2).
+
+ * - ``out_altvoltage120_phase_offset``
+ - rad
+ - Base phase for polar modulation. Lower 8 bits of POW register.
+ Range [0, 2*pi/256).
+
+ * - ``out_altvoltage120_scale_offset``
+ - fractional
+ - Base amplitude for polar modulation. Lower 6 bits of ASF register.
+ Range [0, 1/256).
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Enable parallel port with a frequency scale of 16 and a 50 MHz offset:
+
+.. code-block:: bash
+
+ echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_en
+ echo 16 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_frequency_scale
+ echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_frequency_offset
+
+Digital ramp generator (DRG)
+----------------------------
+
+The DRG produces linear frequency, phase or amplitude sweeps using dedicated
+hardware. It is controlled through three channels: a parent control channel
+(``digital_ramp_generator``) and two child ramp channels
+(``digital_ramp_up``, ``digital_ramp_down``).
+
+Control channel attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. flat-table::
+ :header-rows: 1
+
+ * - Attribute
+ - Unit
+ - Description
+
+ * - ``out_altvoltage130_en``
+ - boolean
+ - Enable/disable the DRG.
+
+ * - ``out_altvoltage130_destination``
+ - enum
+ - Which DDS parameter is swept: ``frequency``, ``phase`` or
+ ``amplitude``.
+
+ * - ``out_altvoltage130_destination_available``
+ - string
+ - Lists available destination values.
+
+ * - ``out_altvoltage130_operating_mode``
+ - enum
+ - Ramp behavior (see table below).
+
+ * - ``out_altvoltage130_operating_mode_available``
+ - string
+ - Lists available operating mode values.
+
+DRG operating modes:
+
+.. flat-table::
+ :header-rows: 1
+
+ * - Mode
+ - Description
+
+ * - ``bidirectional``
+ - Ramp up then down, dwelling at limits.
+
+ * - ``ramp_down``
+ - No-dwell low; resets to upper limit at lower limit.
+
+ * - ``ramp_up``
+ - No-dwell high; resets to lower limit at upper limit.
+
+ * - ``bidirectional_continuous``
+ - Continuous sweep without dwelling at either limit.
+
+Ramp channel attributes
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``digital_ramp_up`` (channel 131) and ``digital_ramp_down`` (channel 132)
+channels share the same attribute set but configure ascending and descending
+ramp parameters independently:
+
+.. flat-table::
+ :header-rows: 1
+
+ * - Attribute
+ - Unit
+ - Description
+
+ * - ``frequency``
+ - Hz
+ - Ramp limit when destination is ``frequency``. Range [0, SYSCLK/2).
+
+ * - ``phase``
+ - rad
+ - Ramp limit when destination is ``phase``. Range [0, 2*pi).
+
+ * - ``scale``
+ - fractional
+ - Ramp limit when destination is ``amplitude``. Range [0, 1).
+
+ * - ``sampling_frequency``
+ - Hz
+ - Ramp clock rate: SYSCLK / (4 * divider).
+
+ * - ``frequency_step``
+ - Hz
+ - Per-tick frequency increment/decrement when destination is
+ ``frequency``.
+
+ * - ``phase_step``
+ - rad
+ - Per-tick phase increment/decrement when destination is ``phase``.
+
+ * - ``scale_step``
+ - fractional
+ - Per-tick amplitude increment/decrement when destination is
+ ``amplitude``. Range [0, 1).
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Configure a frequency sweep from 10 MHz to 100 MHz at a 1 MHz step:
+
+.. code-block:: bash
+
+ # Set DRG destination to frequency
+ echo frequency > /sys/bus/iio/devices/iio:device0/out_altvoltage130_destination
+
+ # Set operating mode
+ echo bidirectional_continuous > /sys/bus/iio/devices/iio:device0/out_altvoltage130_operating_mode
+
+ # Set ramp limits
+ echo 60000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_frequency
+ echo 40000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage132_frequency
+
+ # Set ramp step size to 1 MHz
+ echo 1000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_frequency_step
+ echo 1000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage132_frequency_step
+
+ # Set ramp clock rate
+ echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_sampling_frequency
+
+ # Enable the DRG
+ echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage130_en
+
+Read the current DRG operating mode:
+
+.. code-block:: bash
+
+ cat /sys/bus/iio/devices/iio:device0/out_altvoltage130_operating_mode
+
+RAM mode
+--------
+
+The AD9910 contains a 1024 x 32-bit RAM that can be loaded with waveform data
+and played back to modulate frequency, phase, amplitude, or polar (phase +
+amplitude) parameters.
+
+RAM control channel attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. flat-table::
+ :header-rows: 1
+
+ * - Attribute
+ - Unit
+ - Description
+
+ * - ``out_altvoltage140_en``
+ - boolean
+ - Enable/disable RAM playback. Toggling swaps profile registers between
+ single tone and RAM configurations across all 8 profiles.
+
+ * - ``out_altvoltage140_destination``
+ - enum
+ - RAM data target: ``frequency``, ``phase``, ``amplitude`` or ``polar``.
+ Cannot be changed while RAM mode is enabled.
+
+ * - ``out_altvoltage140_destination_available``
+ - string
+ - Lists available destination values.
+
+ * - ``out_altvoltage140_operating_mode``
+ - enum
+ - Playback behavior (see table below).
+
+ * - ``out_altvoltage140_operating_mode_available``
+ - string
+ - Lists available operating mode values.
+
+ * - ``out_altvoltage140_frequency``
+ - Hz
+ - Frequency tuning word used as the single tone frequency when
+ RAM destination is not ``frequency``. Range [0, SYSCLK/2).
+
+ * - ``out_altvoltage140_phase``
+ - rad
+ - Phase offset word used as the single tone phase when RAM destination
+ is not ``phase``. Range [0, 2*pi).
+
+ * - ``out_altvoltage140_sampling_frequency``
+ - Hz
+ - RAM playback step rate controlling how fast the address counter
+ advances: SYSCLK / (4 * step_rate). Stored per-profile.
+
+ * - ``out_altvoltage140_address_start``
+ - integer
+ - Start address for the active profile. Range [0, 1023]. Cannot be
+ changed while RAM mode is enabled. If set above current end address,
+ end address is automatically adjusted.
+
+ * - ``out_altvoltage140_address_end``
+ - integer
+ - End address for the active profile. Range [address_start, 1023].
+ Cannot be changed while RAM mode is enabled.
+
+RAM operating modes:
+
+.. flat-table::
+ :header-rows: 1
+
+ * - Mode
+ - Description
+
+ * - ``direct_switch``
+ - Start address defines a fixed word used by the selected profile.
+
+ * - ``ramp_up``
+ - One-shot ramp through the current profile's address range.
+
+ * - ``bidirectional``
+ - Ramp up then down through profile 0's address range.
+
+ * - ``bidirectional_continuous``
+ - Continuous ramp up/down through current profile's address range.
+
+ * - ``ramp_up_continuous``
+ - Continuous ramp up through current profile's address range.
+
+ * - ``sequenced``
+ - Sequential playback from profile 0 to the active profile.
+ Requires active profile > 0.
+
+ * - ``sequenced_continuous``
+ - Continuous sequential playback. Requires active profile > 0.
+
+Loading RAM data
+^^^^^^^^^^^^^^^^
+
+RAM data is loaded through the firmware upload framework. The driver registers
+a firmware upload device named ``iio_deviceX:ram``. Data must be a multiple of
+4 bytes (32-bit words) and at most 4096 bytes (1024 words).
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Configure RAM mode with frequency destination and load a waveform:
+
+.. code-block:: bash
+
+ # Set RAM address range for profile 0
+ echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage140_address_start
+ echo 999 > /sys/bus/iio/devices/iio:device0/out_altvoltage140_address_end
+
+ # Set destination and operating mode
+ echo frequency > /sys/bus/iio/devices/iio:device0/out_altvoltage140_destination
+ echo ramp_up_continuous > /sys/bus/iio/devices/iio:device0/out_altvoltage140_operating_mode
+
+ # Set playback rate
+ echo 250000 > /sys/bus/iio/devices/iio:device0/out_altvoltage140_sampling_frequency
+
+ # Load RAM data via firmware upload
+ echo 1 > /sys/class/firmware/iio\:device0\:ram/loading
+ cat waveform.bin > /sys/class/firmware/iio\:device0\:ram/data
+ echo 0 > /sys/class/firmware/iio\:device0\:ram/loading
+
+ # Enable RAM mode
+ echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage140_en
+
+Read the current RAM operating mode:
+
+.. code-block:: bash
+
+ cat /sys/bus/iio/devices/iio:device0/out_altvoltage140_operating_mode
+
+Output shift keying (OSK)
+-------------------------
+
+OSK controls the output amplitude envelope, allowing the output to be ramped
+on/off rather than switched abruptly.
+
+.. flat-table::
+ :header-rows: 1
+
+ * - Attribute
+ - Unit
+ - Description
+
+ * - ``out_altvoltage150_en``
+ - boolean
+ - Enable/disable OSK.
+
+ * - ``out_altvoltage150_scale``
+ - fractional
+ - Target amplitude for the OSK ramp. 14-bit ASF field. Range [0, 1).
+
+ * - ``out_altvoltage150_sampling_frequency``
+ - Hz
+ - OSK ramp rate: SYSCLK / (4 * divider).
+
+ * - ``out_altvoltage150_pinctrl_en``
+ - boolean
+ - Enable manual external pin control. When enabled, the OSK pin directly
+ gates the output on/off instead of using the automatic ramp.
+
+ * - ``out_altvoltage150_scale_step``
+ - fractional
+ - Automatic OSK amplitude step. Writing non-zero enables automatic OSK
+ and sets the per-tick increment. Writing ``0`` disables it. Rounded to
+ nearest hardware step: 0.000061, 0.000122, 0.000244 or 0.000488.
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Enable OSK with automatic ramping:
+
+.. code-block:: bash
+
+ # Set ramp rate
+ echo 1000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_sampling_frequency
+
+ # Enable automatic OSK with step size
+ echo 0.000244 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_scale_step
+
+ # Enable OSK
+ echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_en
+
+Enable manual pin-controlled OSK:
+
+.. code-block:: bash
+
+ # Set target amplitude to full scale
+ echo 1.0 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_scale
+
+ # Enable manual pin control
+ echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_pinctrl_en
+ echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_en
+
+
+Physical channel
+================
+
+The ``phy`` channel provides device-level control:
+
+.. flat-table::
+ :header-rows: 1
+
+ * - Attribute
+ - Unit
+ - Description
+
+ * - ``out_altvoltage100_sampling_frequency``
+ - Hz
+ - System clock (SYSCLK) frequency. With PLL enabled, configures the PLL
+ multiplier (range 420-1000 MHz). Without PLL, ref clock can only be
+ divided by 2.
+
+ * - ``out_altvoltage100_profile``
+ - integer
+ - Active profile index [0, 7]. Selected via GPIO pins. Each profile
+ stores an independent set of single tone and RAM playback parameters.
+
+ * - ``out_altvoltage100_powerdown``
+ - boolean
+ - Software power-down. Writing 1 powers down the digital core, DAC,
+ reference clock input and auxiliary DAC simultaneously.
+
+Usage examples
+--------------
+
+Set the system clock to 1 GHz and select profile 3:
+
+.. code-block:: bash
+
+ echo 1000000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_sampling_frequency
+ echo 3 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_profile
+
+Read current system clock frequency:
+
+.. code-block:: bash
+
+ cat /sys/bus/iio/devices/iio:device0/out_altvoltage100_sampling_frequency
+
+Power down the device:
+
+.. code-block:: bash
+
+ echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_powerdown
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index ba3e609c6a13..55cb1ce84ba8 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -29,6 +29,7 @@ Industrial I/O Kernel Drivers
ad7606
ad7625
ad7944
+ ad9910
ade9000
adis16475
adis16480
diff --git a/MAINTAINERS b/MAINTAINERS
index edd87ee7da5f..14e4272357ce 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1637,6 +1637,7 @@ S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
+F: Documentation/iio/ad9910.rst
F: drivers/iio/frequency/ad9910.c
ANALOG DEVICES INC MAX22007 DRIVER
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support
2026-03-18 17:56 ` [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support Rodrigo Alencar via B4 Relay
@ 2026-03-18 18:28 ` Andy Shevchenko
2026-03-22 16:52 ` Jonathan Cameron
2026-03-23 10:39 ` Rodrigo Alencar
0 siblings, 2 replies; 27+ messages in thread
From: Andy Shevchenko @ 2026-03-18 18:28 UTC (permalink / raw)
To: rodrigo.alencar
Cc: linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
On Wed, Mar 18, 2026 at 05:56:03PM +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.
...
> + ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
I think here we just use 100000 as it's in so many drivers de facto use.
ideally this should be fixed on API level.
> + if (ret)
> + return ret;
...
> -#define AD9910_EXT_INFO(_name, _ident, _shared) { \
> +#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)
I don't see why you should have so many - lines. This TMPL should be introduced
in the first patch.
...
> + case IIO_CHAN_INFO_ENABLE:
> + val = !!val;
Only used once, why do we need this...
> + switch (chan->channel) {
> + case AD9910_CHANNEL_PARALLEL_PORT:
> + tmp32 = FIELD_PREP(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, val);
...and not just here?
> + return ad9910_reg32_update(st, AD9910_REG_CFR2,
> + AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
> + tmp32, true);
> + default:
> + return -EINVAL;
> + }
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 4/9] iio: frequency: ad9910: add digital ramp generator support
2026-03-18 17:56 ` [PATCH RFC v2 4/9] iio: frequency: ad9910: add digital ramp generator support Rodrigo Alencar via B4 Relay
@ 2026-03-18 19:14 ` Andy Shevchenko
0 siblings, 0 replies; 27+ messages in thread
From: Andy Shevchenko @ 2026-03-18 19:14 UTC (permalink / raw)
To: rodrigo.alencar
Cc: linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
On Wed, Mar 18, 2026 at 05:56:04PM +0000, Rodrigo Alencar via B4 Relay wrote:
> Add DRG channels 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.
...
> +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 (chan->channel) {
> + case AD9910_CHANNEL_DRG_RAMP_UP:
> + tmp64 = FIELD_GET(AD9910_DRG_STEP_INC_MSK,
> + st->reg[AD9910_REG_DRG_STEP].val64);
> + break;
> + case AD9910_CHANNEL_DRG_RAMP_DOWN:
> + 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_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);
Not sure if wordparts.h fits here, esp. taking into account...
> + break;
> + case AD9910_DRG_PHASE_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_STEP:
> + type = IIO_VAL_INT_PLUS_NANO;
> + vals[0] = 0;
> + vals[1] = tmp64 * NANO >> 32;
...open coded approach here. I think the open coded calculations.
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return iio_format_value(buf, type, ARRAY_SIZE(vals), vals);
> +}
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910
2026-03-18 17:56 ` [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910 Rodrigo Alencar via B4 Relay
@ 2026-03-19 17:25 ` Conor Dooley
2026-03-20 11:21 ` Rodrigo Alencar
0 siblings, 1 reply; 27+ messages in thread
From: Conor Dooley @ 2026-03-19 17:25 UTC (permalink / raw)
To: rodrigo.alencar
Cc: linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
[-- Attachment #1: Type: text/plain, Size: 7654 bytes --]
On Wed, Mar 18, 2026 at 05:56:01PM +0000, 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 clocks, DAC current, reset and basic GPIO control.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> ---
> .../bindings/iio/frequency/adi,ad9910.yaml | 189 +++++++++++++++++++++
> MAINTAINERS | 7 +
> 2 files changed, 196 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..68eaefea3f5a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
> @@ -0,0 +1,189 @@
> +# 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:
> + minItems: 1
> + maxItems: 2
> + description:
> + First clock is always the reference clock (REF_CLK), while the second
> + clock is an optional synchronization clock (SYNC_IN).
This should be an items list, like:
items:
- description: foo
- description: bar
> +
> + clock-names:
> + oneOf:
> + - items:
> + - const: ref_clk
s/_clk//, not like it can be anything else!
> + - items:
> + - const: ref_clk
> + - const: sync_in
> +
> + '#clock-cells':
> + const: 1
> +
> + clock-output-names:
> + minItems: 1
> + maxItems: 3
> + items:
> + enum: [ sync_clk, pdclk, sync_out ]
I'd say same here, but then you've got some issues with differentiation,
so idk.
> +
> + interrupts:
> + minItems: 1
> + maxItems: 2
Items list here please, the -names property shouldn't be the only place
one can figure out what goes where.
> +
> + interrupt-names:
> + minItems: 1
> + maxItems: 2
> + items:
> + enum: [ drover, ram_swp_ovr ]
> +
> + 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.
> +
> + reset-gpios:
> + description:
> + GPIOs controlling the Main Device reset.
> +
> + io-reset-gpios:
> + maxItems: 1
> + description:
> + GPIO controlling the I/O_RESET pin.
> +
> + 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.
> +
> + sync-err-gpios:
> + maxItems: 1
> + description:
> + GPIO used to read SYNC_SMP_ERR pin status.
> +
> + adi,pll-enable:
> + type: boolean
> + description:
> + Indicates that a loop filter is connected and the internal PLL is enabled.
> + Often used when the reference clock is provided by a crystal or by a
> + single-ended on-board oscillator.
> +
> + adi,charge-pump-current-microamp:
> + minimum: 212
> + maximum: 387
> + default: 387
> + description:
> + PLL charge pump current in microamps. Only applicable when the internal
> + PLL is enabled. The value is rounded to the nearest supported step. This
> + value depends mostly on the loop filter design.
> +
> + 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 internal PLL is enabled.
> +
> + adi,dac-output-current-microamp:
> + minimum: 8640
> + maximum: 31590
> + default: 20070
> + description:
> + DAC full-scale output current in microamps.
> +
> +dependencies:
> + adi,charge-pump-current-microamp: [ 'adi,pll-enable' ]
> + adi,refclk-out-drive-strength: [ 'adi,pll-enable' ]
> + interrupts: [ interrupt-names ]
> + clocks: [ clock-names ]
> + '#clock-cells': [ clock-output-names ]
> +
> +required:
> + - compatible
> + - reg
> + - clocks
Worth pointing out, you haven't made either clock-names or
interrupt-names (when interrupts are used) mandatory, so the properties
cannot be used by a driver. I suggest you make clock-names mandatory and
interrupts depend on interrupt-names.
pw-bot: changes-requested
Cheers,
Conor.
> + - dvdd-io33-supply
> + - avdd33-supply
> + - dvdd18-supply
> + - avdd18-supply
> +
> +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>;
> + clock-names = "ref_clk";
> +
> + dvdd-io33-supply = <&vdd_io33>;
> + avdd33-supply = <&vdd_a33>;
> + dvdd18-supply = <&vdd_d18>;
> + avdd18-supply = <&vdd_a18>;
> +
> + reset-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
> + io-reset-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
> + powerdown-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
> + update-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
> + profile-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>,
> + <&gpio 5 GPIO_ACTIVE_HIGH>,
> + <&gpio 6 GPIO_ACTIVE_HIGH>;
> +
> + adi,pll-enable;
> + adi,charge-pump-current-microamp = <387>;
> + adi,refclk-out-drive-strength = "disabled";
> + };
> + };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 08d8ddf4ef68..2ca8b68e5daa 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1630,6 +1630,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
>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910
2026-03-19 17:25 ` Conor Dooley
@ 2026-03-20 11:21 ` Rodrigo Alencar
2026-03-20 14:00 ` Conor.Dooley
2026-03-20 17:14 ` Conor Dooley
0 siblings, 2 replies; 27+ messages in thread
From: Rodrigo Alencar @ 2026-03-20 11:21 UTC (permalink / raw)
To: Conor Dooley, rodrigo.alencar
Cc: linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
On 26/03/19 05:25PM, Conor Dooley wrote:
> On Wed, Mar 18, 2026 at 05:56:01PM +0000, 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 clocks, DAC current, reset and basic GPIO control.
> >
> > Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
...
> > +
> > + clock-names:
> > + oneOf:
> > + - items:
> > + - const: ref_clk
>
> s/_clk//, not like it can be anything else!
>
> > + - items:
> > + - const: ref_clk
> > + - const: sync_in
> > +
> > + '#clock-cells':
> > + const: 1
> > +
> > + clock-output-names:
> > + minItems: 1
> > + maxItems: 3
> > + items:
> > + enum: [ sync_clk, pdclk, sync_out ]
>
> I'd say same here, but then you've got some issues with differentiation,
> so idk.
so I've got the names as they are referred in the device pins in the datasheet
...
> > +dependencies:
> > + adi,charge-pump-current-microamp: [ 'adi,pll-enable' ]
> > + adi,refclk-out-drive-strength: [ 'adi,pll-enable' ]
> > + interrupts: [ interrupt-names ]
> > + clocks: [ clock-names ]
> > + '#clock-cells': [ clock-output-names ]
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - clocks
>
> Worth pointing out, you haven't made either clock-names or
> interrupt-names (when interrupts are used) mandatory, so the properties
> cannot be used by a driver. I suggest you make clock-names mandatory and
> interrupts depend on interrupt-names.
the dependecies is not enought make them required then? understood!
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910
2026-03-20 11:21 ` Rodrigo Alencar
@ 2026-03-20 14:00 ` Conor.Dooley
2026-03-20 17:14 ` Conor Dooley
1 sibling, 0 replies; 27+ messages in thread
From: Conor.Dooley @ 2026-03-20 14:00 UTC (permalink / raw)
To: 455.rodrigo.alencar, conor, rodrigo.alencar
Cc: linux-iio, devicetree, linux-kernel, linux-doc, lars,
Michael.Hennerich, jic23, dlechner, andy, robh, krzk+dt, conor+dt,
p.zabel, corbet, skhan
On 20/03/2026 11:21, Rodrigo Alencar wrote:
>>> +dependencies:
>>> + adi,charge-pump-current-microamp: [ 'adi,pll-enable' ]
>>> + adi,refclk-out-drive-strength: [ 'adi,pll-enable' ]
>>> + interrupts: [ interrupt-names ]
>>> + clocks: [ clock-names ]
>>> + '#clock-cells': [ clock-output-names ]
>>> +
>>> +required:
>>> + - compatible
>>> + - reg
>>> + - clocks
>>
>> Worth pointing out, you haven't made either clock-names or
>> interrupt-names (when interrupts are used) mandatory, so the properties
>> cannot be used by a driver. I suggest you make clock-names mandatory and
>> interrupts depend on interrupt-names.
>
> the dependecies is not enought make them required then? understood!
lol, clearly I am blind.
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910
2026-03-20 11:21 ` Rodrigo Alencar
2026-03-20 14:00 ` Conor.Dooley
@ 2026-03-20 17:14 ` Conor Dooley
1 sibling, 0 replies; 27+ messages in thread
From: Conor Dooley @ 2026-03-20 17:14 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
[-- Attachment #1: Type: text/plain, Size: 1320 bytes --]
On Fri, Mar 20, 2026 at 11:21:37AM +0000, Rodrigo Alencar wrote:
> On 26/03/19 05:25PM, Conor Dooley wrote:
> > On Wed, Mar 18, 2026 at 05:56:01PM +0000, 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 clocks, DAC current, reset and basic GPIO control.
> > >
> > > Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> ...
>
> > > +
> > > + clock-names:
> > > + oneOf:
> > > + - items:
> > > + - const: ref_clk
> >
> > s/_clk//, not like it can be anything else!
> >
> > > + - items:
> > > + - const: ref_clk
> > > + - const: sync_in
> > > +
> > > + '#clock-cells':
> > > + const: 1
> > > +
> > > + clock-output-names:
> > > + minItems: 1
> > > + maxItems: 3
> > > + items:
> > > + enum: [ sync_clk, pdclk, sync_out ]
> >
> > I'd say same here, but then you've got some issues with differentiation,
> > so idk.
>
> so I've got the names as they are referred in the device pins in the datasheet
Coming back to this one, ye I think it just is less confusing to keep
the _clk ultimately. This looks good then, I think, modulo the RFC-state
of the series.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation
2026-03-18 17:56 ` [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation Rodrigo Alencar via B4 Relay
@ 2026-03-22 16:50 ` Jonathan Cameron
2026-03-23 10:34 ` Rodrigo Alencar
0 siblings, 1 reply; 27+ messages in thread
From: Jonathan Cameron @ 2026-03-22 16:50 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
On Wed, 18 Mar 2026 17:56:02 +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 attributes.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
Hi Rodrigo
I want some time for the discussion on the ABI to take place and haven't
made any real comments on that here. Focus was more on the code.
So various minor things inline,
Thanks,
Jonathan
> 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
> diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
> new file mode 100644
> index 000000000000..a362d96cf651
> --- /dev/null
> +++ b/drivers/iio/frequency/ad9910.c
> @@ -0,0 +1,1006 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * AD9910 SPI DDS (Direct Digital Synthesizer) driver
> + *
> + * Copyright 2026 Analog Devices Inc.
> + */
> +
> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
Generally can avoid including device.h in favour of more specific
headers. There are a few exceptions where we can't such as actual
dereferencing of struct device, but I don't recall seeing a case in here.
> +
> +#define AD9910_ASF_MAX (BIT(14) - 1)
> +#define AD9910_POW_MAX (BIT(16) - 1)
GENMASK() tends to be clearer for max values.
> +
> +static const char * const ad9910_power_supplies[] = {
> + "dvdd-io33", "avdd33", "dvdd18", "avdd18",
> +};
> +
> +static const char * const ad9910_refclk_out_drv0[] = {
> + "disabled", "low", "medium", "high",
> +};
These are only used in very localized bits of code. I'd move them down near
them rather than having them up here at the top of the file.
> +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);
Trivial but blank line here. Having one before a simple return statement
just makes it a little easier to read.
> + return 0;
> +}
>
> +
> +#define AD9910_EXT_INFO(_name, _ident, _shared) { \
> + .name = _name, \
> + .read = ad9910_ext_info_read, \
> + .write = ad9910_ext_info_write, \
> + .private = _ident, \
> + .shared = _shared, \
If there are only a few of these, I'd put it long hand rather than
using a macro. Tends to end up easier to read.
> +}
> +
> +static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
> + AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SEPARATE),
> + AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
> + { }
> +};
> +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:
> + switch (chan->channel) {
> + case AD9910_CHANNEL_SINGLE_TONE:
I haven't read on yet, but if you never have any other cases in here,
perhaps us an if() as it will reduce indent of the code that follows.
> + tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
> + st->reg[AD9910_REG_PROFILE(st->profile)].val64);
> + break;
> + default:
> + return -EINVAL;
> + }
> + tmp64 = (u64)tmp32 * st->data.sysclk_freq_hz;
> + *val = upper_32_bits(tmp64);
> + *val2 = upper_32_bits((u64)lower_32_bits(tmp64) * MICRO);
I've no idea how this *val2 assignment works... Perhaps some comments?
> + return IIO_VAL_INT_PLUS_MICRO;
> + case IIO_CHAN_INFO_PHASE:
> + switch (chan->channel) {
> + case AD9910_CHANNEL_SINGLE_TONE:
> + tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
> +
> +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 = FIELD_GET(AD9910_REG_HIGH32_FLAG_MSK, reg);
> +
> + /*
> + * HIGH32 flag is a workaround to allow access to upper 32 bits of
> + * 64-bit registers one at a time due to debugfs_reg_access limitations
> + * of only supporting 32-bit values.
> + */
> + reg &= ~AD9910_REG_HIGH32_FLAG_MSK;
> + if (reg >= AD9910_REG_RAM)
> + return -EINVAL;
> +
> + guard(mutex)(&st->lock);
> +
> + switch (reg) {
Split this in to two helpers. It's rather hard to follow with read and
write paths mixed up in the code flow.
> + case AD9910_REG_DRG_LIMIT:
> + case AD9910_REG_DRG_STEP:
> + case AD9910_REG_PROFILE0:
Can you do
case AD9910_REG_PROFILE0 ... AD9910_REG_PROFILE7:
here to help readability?
> + 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);
Might as well return 0 here.
> + } else {
> + tmp64 = st->reg[reg].val64;
> + if (high32)
> + FIELD_MODIFY(GENMASK_ULL(63, 32), &tmp64, writeval);
> + else
> + FIELD_MODIFY(GENMASK_ULL(31, 0), &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;
return 0;
> + 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;
and return 0; here. No point in break to go to as return and nothing else.
> + }
> +
> + return ret;
> +}
> +
> +static int ad9910_cfg_sysclk(struct ad9910_state *st, bool update)
> +{
> + u32 tmp32, cfr3 = AD9910_CFR3_OPEN_MSK;
> +
> + cfr3 |= AD9910_CFR3_VCO_SEL_MSK |
> + FIELD_PREP(AD9910_CFR3_DRV0_MSK, st->data.refclk_out_drv);
> +
> + if (st->data.pll_enabled) {
> + tmp32 = st->data.pll_charge_pump_current - AD9910_ICP_MIN_uA;
> + tmp32 = DIV_ROUND_CLOSEST(tmp32, AD9910_ICP_STEP_uA);
> + cfr3 |= FIELD_PREP(AD9910_CFR3_ICP_MSK, tmp32) |
> + AD9910_CFR3_PLL_EN_MSK;
> + } else {
> + cfr3 |= AD9910_CFR3_ICP_MSK |
For this, be explicit what value you are setting, probably be defining a max value
that the field can take. Whilst just setting the mask is the same it doesn't
convey the same meaning to someone reading the code.
> + AD9910_CFR3_REFCLK_DIV_RESETB_MSK |
> + AD9910_CFR3_PFD_RESET_MSK;
> + }
> + st->reg[AD9910_REG_CFR3].val32 = cfr3;
> +
> + return ad9910_set_sysclk_freq(st, AD9910_PLL_OUT_MAX_FREQ_HZ, update);
> +}
> +
> +static int ad9910_parse_fw(struct ad9910_state *st)
> +{
> + struct device *dev = &st->spi->dev;
> + u32 tmp;
> + int ret;
> +
> + st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable");
> + if (st->data.pll_enabled) {
> + tmp = AD9910_ICP_MAX_uA;
Defaulting to max current seems unusual. What's the motivation? Normal instinct is
go minimum if no other info.
> + 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;
> + }
> +
> + 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;
> + ret = ad9910_reg32_write(st, AD9910_REG_CFR1, reg32, false);
Trivial but I'd not bother using the local variable for simple values.
ret = ad9910_reg32_write(st, AD9910_REG_CFR1,
AD9910_CFR1_SDIO_INPUT_ONLY_MSK, false);
is fine.
> + if (ret)
> + return ret;
> +
> + reg32 = AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK;
This split seems odd. Why not combine above and the next block?
> + reg32 |= AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK |
> + AD9910_CFR2_DRG_NO_DWELL_MSK |
> + AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK |
> + AD9910_CFR2_SYNC_CLK_EN_MSK |
> + AD9910_CFR2_PDCLK_ENABLE_MSK;
> + 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_release(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;
> + 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;
> +
> + st->refclk = devm_clk_get_enabled(dev, "ref_clk");
> + 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);
Maybe we can just call it ad9910_supplies without loss of meaning and have
slightly shorter lines? Could also drag that const array into scope of this
function perhaps as we only need it in here.
> + 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);
Do we need this? I 'think' that the gpio reset controller will ensure that
the reset line is GPIOD_OUT_HIGH, on registering it.
https://elixir.bootlin.com/linux/v6.19.9/source/drivers/reset/reset-gpio.c#L81
and that should I think correspond to asserted.
I was curious why there were _deasserted() variants of the get but not
_asserted() ones and went looking. Seems assumption is that in general
should already be in asserted state as firmware deals with that (here the dt
binding / gpio reset driver).
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to assert device reset control\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_iio_device_register(dev, indio_dev);
> + if (ret)
> + return ret;
> +
> + return devm_add_action_or_reset(dev, ad9910_release, st);
What is this undoing? Very unusual to have a register write that you
want to happen 'before' the userspace interfaces go away in the remove()
path. Perhaps it is paired with ad9910_setup()?
> +}
>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support
2026-03-18 18:28 ` Andy Shevchenko
@ 2026-03-22 16:52 ` Jonathan Cameron
2026-03-23 10:39 ` Rodrigo Alencar
1 sibling, 0 replies; 27+ messages in thread
From: Jonathan Cameron @ 2026-03-22 16:52 UTC (permalink / raw)
To: Andy Shevchenko
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
On Wed, 18 Mar 2026 20:28:34 +0200
Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> On Wed, Mar 18, 2026 at 05:56:03PM +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.
>
> ...
>
> > + ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
>
> I think here we just use 100000 as it's in so many drivers de facto use.
> ideally this should be fixed on API level.
I wouldn't mind a series tidying this up, but if anyone proposes to do
that we'll want to not use the same naming so it is obvious if any
new drivers assume the old scaling.
I can't really remember why we ended up with the odd interface :(
Jonathan
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 5/9] iio: frequency: ad9910: add RAM mode support
2026-03-18 17:56 ` [PATCH RFC v2 5/9] iio: frequency: ad9910: add RAM mode support Rodrigo Alencar via B4 Relay
@ 2026-03-22 17:05 ` Jonathan Cameron
0 siblings, 0 replies; 27+ messages in thread
From: Jonathan Cameron @ 2026-03-22 17:05 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
On Wed, 18 Mar 2026 17:56:05 +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 firmware upload interface;
> - 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-enable-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>
A few minor things. Again I've left discussion of interfaces for docs patches.
> ---
> drivers/iio/frequency/Kconfig | 2 +
> drivers/iio/frequency/ad9910.c | 464 ++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 462 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
> index 180e74f62d11..a5b2e5cb5269 100644
> --- a/drivers/iio/frequency/Kconfig
> +++ b/drivers/iio/frequency/Kconfig
> @@ -29,6 +29,8 @@ config AD9910
> tristate "Analog Devices AD9910 Direct Digital Synthesizer"
> depends on SPI
> depends on GPIOLIB
> + select FW_LOADER
> + select FW_UPLOAD
> help
> Say yes here to build support for Analog Devices AD9910
> 1 GSPS, 14-Bit DDS with integrated DAC.
> diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
> index d3367e211dcf..747f4f407536 100644
> --- a/drivers/iio/frequency/ad9910.c
> +++ b/drivers/iio/frequency/ad9910.c
> @@ -1288,12 +1610,26 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
> return ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
> AD9910_DRG_RATE_DEC_MSK,
> tmp32, true);
> + case AD9910_CHANNEL_RAM:
> + if (!AD9910_RAM_ENABLED(st)) {
> + FIELD_MODIFY(AD9910_PROFILE_RAM_STEP_RATE_MSK,
> + &st->reg_profile[st->profile], tmp32);
> + return 0;
> + }
> +
> + 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);
> +
> default:
> return -EINVAL;
> }
> default:
> return -EINVAL;
> }
> +
> + return ret;
Seems a bit odd if you can now get here. Probably means some return missing
that would make more sense than a break somewhere above this.
> }
> +
> static const struct iio_info ad9910_info = {
> .read_raw = ad9910_read_raw,
> .write_raw = ad9910_write_raw,
> @@ -1503,6 +1922,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;
Add a definition for maximum value and explicitly write that via FIELD_PREP()
as that will make it easier to see what is going on here.
> + 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);
> + }
> @@ -1519,6 +1947,24 @@ static void ad9910_release(void *data)
> true);
> }
>
> +static inline void ad9910_debugfs_init(struct ad9910_state *st,
> + struct iio_dev *indio_dev)
> +{
> +#ifdef CONFIG_DEBUG_FS
Why? There are stubs for debugfs_create_symlink() and the compiler
should tidyup the rest if that's stubbed out.
Whether this interfaces makes sense is a question I'll leave for ABI docs.
> + char buf[64];
> +
> + /*
> + * symlinks are created here so iio userspace tools can refer to them
> + * as debug attributes.
> + */
> + snprintf(buf, sizeof(buf), "/sys/class/firmware/%s/loading", st->ram_fwu_name);
> + debugfs_create_symlink("ram_loading", iio_get_debugfs_dentry(indio_dev), buf);
> +
> + snprintf(buf, sizeof(buf), "/sys/class/firmware/%s/data", st->ram_fwu_name);
> + debugfs_create_symlink("ram_data", iio_get_debugfs_dentry(indio_dev), buf);
> +#endif
> +}
>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 8/9] Documentation: ABI: testing: add docs for ad9910 sysfs entries
2026-03-18 17:56 ` [PATCH RFC v2 8/9] Documentation: ABI: testing: add docs for ad9910 sysfs entries Rodrigo Alencar via B4 Relay
@ 2026-03-22 17:22 ` Jonathan Cameron
2026-03-23 11:36 ` Rodrigo Alencar
0 siblings, 1 reply; 27+ messages in thread
From: Jonathan Cameron @ 2026-03-22 17:22 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
On Wed, 18 Mar 2026 17:56:08 +0000
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> Add ABI documentation file for the DDS AD9910 with sysfs entries to
> control Parallel Port, Digital Ramp Generator, RAM and OSK parameters.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> ---
Thanks for writing this up. Let's see how others view it. There is a lot
going on here! My main comment has been around trying to write the
docs as a generic thing rather than focusing on device details.
Jonathan
> .../ABI/testing/sysfs-bus-iio-frequency-ad9910 | 182 +++++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 183 insertions(+)
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 b/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
> new file mode 100644
> index 000000000000..120de494f6b1
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
> @@ -0,0 +1,182 @@
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_profile
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the active profile index [0, 7] from/to the physical
> + channel. The AD9910 supports 8 profiles, each storing a complete
> + set of single tone (frequency, phase, amplitude) and RAM playback
> + parameters.
This one is interesting. Can we treat them as symbols that we are picking
between? We have similar DAC ABIs for that already.
Is this picking between them for purposes of configuration or setting which one is
in being output currently?
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_offset
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the parallel port frequency offset in Hz. This is the
> + base frequency tuning word (FTW register) to which the scaled
> + parallel port data is added during parallel data port modulation.
> + Valid range is [0, SYSCLK/2).
Ideally think about how these controls generalize (if they do) and avoid
device specific descriptions.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_scale
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the parallel port frequency modulation gain. The value
> + must be a power of 2 in the [1, 2^15] range. This value scales the
> + 16-bit parallel data port input before adding it to the
> + frequency_offset value.
Can we provide an _available for this with all 16 values? Then avoid the specific
device nature of the documentation instead saying see the _available for what is possible.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_phase_offset
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the parallel port phase offset in radians. Valid range
> + is [0, 2*pi/256). This sets the lower 8 bits of the phase offset
> + word (POW register) used as a base during parallel port polar
> + modulation.
Given it does full phase shift, I don't think for userspace docs we care about the 8
bits of whatever register.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_offset
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the parallel port amplitude scale offset. Valid range
> + is [0, 1/256). This sets the lower 6 bits of the amplitude scale
> + factor (ASF register) used as a base during parallel port polar
> + modulation.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_destination
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the digital ramp generator (DRG) or the RAM control
> + destination parameter. Determines which DDS core parameter is to
> + be modulated when the child mode channel is enabled.
> +
> + Available values can be read from the corresponding
> + out_altvoltageY_destination_available attribute.
> +
> + Valid values: "polar" (only for RAM control), "frequency", "phase"
> + and "amplitude"
This is very device specific. Maybe we are better representing these as separate
channels each with their own controls for DRG. No problem if changing one changes
another.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_destination_available
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Lists the available destination values for the DRG channel:
> + "frequency phase amplitude"; or for the RAM control channel:
> + "frequency phase amplitude polar".
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_operating_mode
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the DRG or RAM control operating mode. For the DRG
> + channel it controls the no-dwell behavior of the ramp.
> +
> + Available values can be read from the corresponding
> + out_altvoltageY_operating_mode_available attribute.
> +
> + Valid values for DRG channel:
> +
> + - "bidirectional": Normal ramp generation (ramp up then
> + down, dwelling at limits).
Some sort of trapezium wave? Maybe this and continuous forms are combined
and we have a separate dwell time control?
> + - "ramp_down": No-dwell low; the ramp resets to upper
> + limit upon reaching the lower limit.
> + - "ramp_up": No-dwell high; the ramp resets to lower
> + limit upon reaching the upper limit.
> + - "bidirectional_continuous": Both no-dwell high and low;
> + the ramp continuously sweeps without dwelling.
Triangle wave? bidirectional continuous is a rather confusing term so maybe
we should rethink this one.
> +
> + Valid values for RAM control channel:
> +
> + - "direct_switch": start address defines fixed word to be used
> + by the selected profile.
> + - "ramp_up": One-shot ramp up through current profile's address
> + range.
> + - "bidirectional": Ramp up then down through PROFILE0 pin.
Avoid specifics like this. Can we call external control pin or something like that?
> + - "bidirectional_continuous": Continuous ramp up/down
> + through current profile's address range.
> + - "ramp_up_continuous": Continuous ramp up through
> + current profile's address range.
I guess this goes back to start on finishing ramping up?
> + - "sequenced": Sequenced playback of RAM profiles up to
> + the active profile. Requires active profile > 0.
Is this just running through each profile one after another? (other than profile 0)?
> + - "sequenced_continuous": Continuous sequenced playback
> + of RAM profiles up to the active profile. Requires
> + active profile > 0.
Similar to above, maybe separate out dwell time if that's the difference between
sequenced and sequenced_continuous.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_operating_mode_available
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + For the DRG channel it lists the available operating mode values:
> + "bidirectional ramp_down ramp_up bidirectional_continuous".
> +
> + For the RAM control channel it lists the available operating mode
> + values:
> + "direct_switch ramp_up bidirectional bidirectional_continuous
> + ramp_up_continuous sequenced sequenced_continuous".
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_step
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the DRG frequency step size in Hz for ramp up and ramp
> + down DRG channels. This is the increment/decrement step applied to
> + the DRG frequency value, which is input to the DDS core and it is
> + updated at each ramp clock tick when the DRG destination is
> + set to "frequency". Valid range is [0, sysclk/2).
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_phase_step
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the DRG phase step size in radians for ramp up and ramp
> + down DRG channels. This is the increment/decrement step applied to
> + the DRG phase value, which is input to the DDS core and it is
> + updated at each ramp clock tick when the DRG destination is
> + set to "phase". Valid range is [0, 2*pi).
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_step
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + For the DRG ramp up/down channels this is used to read/write the
> + DRG amplitude step size, which is applied as an
> + increment/decrement to the DRG amplitude value, input to the DDS
> + core, updated at each ramp clock tick when the DRG destination is
> + set to "amplitude". Valid range is [0, 1].
> +
> + For the OSK channel this is used to read/write the automatic OSK
> + amplitude ramp step size. Writing a non-zero value enables
> + automatic OSK mode and sets the amplitude step size. Writing "0"
> + disables automatic OSK mode. The value is rounded to the nearest
> + hardware supported step: 0.000061, 0.000122, 0.000244, or
> + 0.000488.
Those need to come from available attribute. Don't belong in the docs as if we
have them here there is little chance of generalizing later to cover more devices.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_address_start
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the RAM start address for the active profile. Defines
> + the first RAM word address used during playback. Cannot be
> + changed while RAM mode is enabled. Valid range is [0, 1023].
> + If set above the current end address, the end address is
> + automatically adjusted to match.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_address_end
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the RAM end address for the active profile. Defines
> + the last RAM word address used during playback. Cannot be
> + changed while RAM mode is enabled. Valid range is
> + [address_start, 1023].
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_pinctrl_en
> +KernelVersion:
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Read/write the OSK manual external control enable. Writing '1'
> + enables manual control of the output amplitude envelope via an
> + external pin. Writing '0' disables it. When enabled, the OSK pin
> + directly controls the amplitude on/off state rather than using
> + the automatic OSK ramp.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6403439b530d..edd87ee7da5f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1635,6 +1635,7 @@ 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/ABI/testing/sysfs-bus-iio-frequency-ad9910
> F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
> F: drivers/iio/frequency/ad9910.c
>
>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 9/9] docs: iio: add documentation for ad9910 driver
2026-03-18 17:56 ` [PATCH RFC v2 9/9] docs: iio: add documentation for ad9910 driver Rodrigo Alencar via B4 Relay
@ 2026-03-22 17:34 ` Jonathan Cameron
2026-03-23 11:58 ` Rodrigo Alencar
0 siblings, 1 reply; 27+ messages in thread
From: Jonathan Cameron @ 2026-03-22 17:34 UTC (permalink / raw)
To: Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
On Wed, 18 Mar 2026 17:56:09 +0000
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> Add documentation for the AD9910 DDS IIO driver, which describes channels,
> DDS modes, attributes and ABI usage examples.
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
A few things inline. I've not cropped as there is a lot here and I'd like
it all to remain visible in the reply.
Overall this is a very interesting device so whilst I think we are making
progress it might still take a while to come to an overall conclusion
on the ABI!
> ---
> Documentation/iio/ad9910.rst | 654 +++++++++++++++++++++++++++++++++++++++++++
> Documentation/iio/index.rst | 1 +
> MAINTAINERS | 1 +
> 3 files changed, 656 insertions(+)
>
> diff --git a/Documentation/iio/ad9910.rst b/Documentation/iio/ad9910.rst
> new file mode 100644
> index 000000000000..116f6af4bc2e
> --- /dev/null
> +++ b/Documentation/iio/ad9910.rst
> @@ -0,0 +1,654 @@
> +.. SPDX-License-Identifier: GPL-2.0-only
> +
> +=============
> +AD9910 driver
> +=============
> +
> +DDS (Direct Digital Synthesizer) driver for the Analog Devices Inc. AD9910.
> +The module name is ``ad9910``.
> +
> +* `AD9910 <https://www.analog.com/en/products/ad9910.html>`_
> +
> +The AD9910 is a 1 GSPS DDS with a 14-bit DAC, driven over SPI. The driver
> +exposes the device through the IIO ``altvoltage`` channel type and supports
> +five DDS operating modes: single tone, parallel port modulation, digital ramp
> +generation (DRG), RAM playback and output shift keying (OSK). The device has
> +8 hardware profiles, each capable of storing independent single tone and RAM
> +playback parameters.
> +
> +
> +Channel hierarchy
> +=================
> +
> +The driver exposes the following IIO output channels, each identified by a
> +unique channel number and a human-readable label:
> +
> +* ``out_altvoltage100``: ``phy``: Physical output: system clock and profile control
> +
> + * ``out_altvoltage110``: ``single_tone``: Single tone mode: per-profile
> + frequency, phase, amplitude
> +
> + * ``out_altvoltage120``: ``parallel_port``: Parallel port modulation: enable
> + and offset/scale parameters
> +
> + * ``out_altvoltage130``: ``digital_ramp_generator``: DRG control: enable,
> + destination, operating mode
> +
> + * ``out_altvoltage131``: ``digital_ramp_up``: DRG ramp-up parameters:
> + limits, step sizes, ramp rate
> + * ``out_altvoltage132``: ``digital_ramp_down``: DRG ramp-down parameters:
> + limits, step sizes, ramp rate
> +
> + * ``out_altvoltage140``: ``ram_control``: RAM playback: enable, destination,
> + operating mode, address range
> +
> + * ``out_altvoltage150``: ``output_shift_keying``: OSK: enable, amplitude
> + scale, ramp rate, auto/manual control
> +
> +The ``phy`` channel is the root of the hierarchy. Changing its
> +``sampling_frequency`` reconfigures the system clock (SYSCLK) which affects all
> +other channels. The ``profile`` attribute on this channel selects the active
> +hardware profile (0-7) used by the single tone and RAM channels.
I asked out this profile thing in one of the other patches. Key to me is
that how we write non active profiles? The most similar thing we've seen
in the past has been setting other frequencies for FSK or phases for PSK or
more mundane DC DAC output that are symbol based. (often an external signal)
For those we have added an additional index so we can see which symbol we
are changing parameters for. Here it might need to be done in the channel
numbering. I'm not sure.
> +
> +All mode-specific channels (parallel port, DRG, RAM, OSK) have an ``enable``
> +attribute. The DRG and RAM channels additionally have ``destination`` and
> +``operating_mode`` attributes that configure which DDS core parameter is
> +modulated and how.
I wonder if we flatten things out and have separate channels for each type
of modulation. Might lead to a more standard looking interfaces. We don't really
have a standard path to control one type of thing feeding another, whereas
we do have simple 'enable' interfaces.
> +
> +DDS modes
> +=========
> +
> +The AD9910 supports multiple modes of operation that can be configured
> +independently or in combination. Such modes and their corresponding IIO channels
> +are described in this section. The following tables are extracted from the
> +AD9910 datasheet and summarizes the control parameters for each mode and their
> +priority when multiple sources are enabled simultaneously:
> +
> +.. flat-table:: DDS Frequency Control
> + :header-rows: 1
> +
> + * - Priority
> + - Data Source
> + - Conditions
> +
> + * - Highest Priority
> + - RAM
> + - RAM enabled and data destination is frequency
> +
> + * -
> + - DRG
> + - DRG enabled and data destination is frequency
> +
> + * -
> + - Parallel data and FTW (frequency_offset)
> + - Parallel data port enabled and data destination is frequency
> +
> + * -
> + - FTW (frequency)
> + - RAM enabled and data destination is not frequency
> +
> + * -
> + - FTW (frequency) in single tone channel for the active profile
> + - DRG enabled and data destination is not frequency
> +
> + * -
> + - FTW (frequency) in single tone channel for the active profile
> + - Parallel data port enabled and data destination is not frequency
> +
> + * - Lowest Priority
> + - FTW (frequency) in single tone channel for the active profile
> + - None
> +
> +.. flat-table:: DDS Phase Control
> + :header-rows: 1
> +
> + * - Priority
> + - Data Source
> + - Conditions
> +
> + * - Highest Priority
> + - RAM
> + - RAM enabled and data destination is phase or polar
> +
> + * -
> + - DRG
> + - DRG enabled and data destination is phase
> +
> + * -
> + - Parallel data port
> + - Parallel data port enabled and data destination is phase
> +
> + * -
> + - Parallel data port and POW register LSBs (phase_offset)
> + - Parallel data port enabled and data destination is polar
> +
> + * -
> + - POW (phase)
> + - RAM enabled and destination is not phase nor polar
> +
> + * -
> + - POW (phase) in single tone channel for the active profile
> + - DRG enabled and data destination is not phase
> +
> + * -
> + - POW (phase) in single tone channel for the active profile
> + - Parallel data port enabled and data destination is not phase nor polar
> +
> + * - Lowest Priority
> + - POW (phase) in single tone channel for the active profile
> + - None
> +
> +.. flat-table:: DDS Amplitude Control
> + :header-rows: 1
> +
> + * - Priority
> + - Data Source
> + - Conditions
> +
> + * - Highest Priority
> + - OSK generator
> + - OSK enabled (auto mode)
> +
> + * -
> + - ASF register
> + - OSK enabled (manual mode)
> +
> + * -
> + - RAM
> + - RAM enabled and data destination is amplitude or polar
> +
> + * -
> + - DRG
> + - DRG enabled and data destination is amplitude
> +
> + * -
> + - Parallel data port
> + - Parallel data port enabled and data destination is amplitude
> +
> + * -
> + - Parallel data port and ASF register LSBs (scale_offset)
> + - Parallel data port enabled and data destination is polar
> +
> + * - Lowest Priority
> + - ASF (scale) in single tone channel for the active profile
> + - (Amplitude scale is already enabled by default)
> +
> +Single tone mode
> +----------------
> +
> +Single tone is the baseline operating mode. The ``single_tone`` channel
> +provides per-profile frequency, phase and amplitude control:
> +
> +.. flat-table::
> + :header-rows: 1
> +
> + * - Attribute
> + - Unit
> + - Description
> +
> + * - ``out_altvoltage110_frequency``
> + - Hz
> + - Output frequency. Range [0, SYSCLK/2). Stored in the active profile's
> + frequency tuning word (FTW).
> +
> + * - ``out_altvoltage110_phase``
> + - rad
> + - Phase offset. Range [0, 2*pi). Stored in the active profile's phase
> + offset word (POW).
> +
> + * - ``out_altvoltage110_scale``
> + - fractional
> + - Amplitude scale factor. Range [0, 1]. Stored in the active profile's
> + amplitude scale factor (ASF).
> +
> +When RAM mode is enabled, single tone parameters are stored in a shadow
> +register and are not written to hardware until RAM mode is disabled.
> +
> +Usage examples
> +^^^^^^^^^^^^^^
> +
> +Set the active profile to 2 and configure a 100 MHz tone:
> +
> +.. code-block:: bash
> +
> + echo 2 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_profile
> + echo 100000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage110_frequency
> + echo 0.5 > /sys/bus/iio/devices/iio:device0/out_altvoltage110_scale
> + echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage110_phase
> +
> +Read back the current single tone frequency:
> +
> +.. code-block:: bash
> +
> + cat /sys/bus/iio/devices/iio:device0/out_altvoltage110_frequency
> +
> +Parallel port mode
> +------------------
> +
> +When enabled, the parallel port allows real-time modulation of DDS parameters
> +through a 16-bit external data bus.
> +
> +.. flat-table::
> + :header-rows: 1
> +
> + * - Attribute
> + - Unit
> + - Description
> +
> + * - ``out_altvoltage120_en``
> + - boolean
> + - Enable/disable the parallel data port.
> +
> + * - ``out_altvoltage120_frequency_scale``
> + - power-of-2
> + - FM gain multiplier applied to 16-bit parallel input. Range [1, 32768],
> + must be a power of 2.
> +
> + * - ``out_altvoltage120_frequency_offset``
> + - Hz
> + - Base FTW to which scaled parallel data is added. Range [0, SYSCLK/2).
> +
> + * - ``out_altvoltage120_phase_offset``
> + - rad
> + - Base phase for polar modulation. Lower 8 bits of POW register.
> + Range [0, 2*pi/256).
> +
> + * - ``out_altvoltage120_scale_offset``
> + - fractional
> + - Base amplitude for polar modulation. Lower 6 bits of ASF register.
> + Range [0, 1/256).
> +
> +Usage examples
> +^^^^^^^^^^^^^^
> +
> +Enable parallel port with a frequency scale of 16 and a 50 MHz offset:
> +
> +.. code-block:: bash
> +
> + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_en
> + echo 16 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_frequency_scale
> + echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_frequency_offset
> +
> +Digital ramp generator (DRG)
> +----------------------------
> +
> +The DRG produces linear frequency, phase or amplitude sweeps using dedicated
> +hardware. It is controlled through three channels: a parent control channel
> +(``digital_ramp_generator``) and two child ramp channels
> +(``digital_ramp_up``, ``digital_ramp_down``).
> +
> +Control channel attributes
> +^^^^^^^^^^^^^^^^^^^^^^^^^^
> +
> +.. flat-table::
> + :header-rows: 1
> +
> + * - Attribute
> + - Unit
> + - Description
> +
> + * - ``out_altvoltage130_en``
> + - boolean
> + - Enable/disable the DRG.
> +
> + * - ``out_altvoltage130_destination``
> + - enum
> + - Which DDS parameter is swept: ``frequency``, ``phase`` or
> + ``amplitude``.
> +
> + * - ``out_altvoltage130_destination_available``
> + - string
> + - Lists available destination values.
> +
> + * - ``out_altvoltage130_operating_mode``
> + - enum
> + - Ramp behavior (see table below).
> +
> + * - ``out_altvoltage130_operating_mode_available``
> + - string
> + - Lists available operating mode values.
> +
> +DRG operating modes:
> +
> +.. flat-table::
> + :header-rows: 1
> +
> + * - Mode
> + - Description
> +
> + * - ``bidirectional``
> + - Ramp up then down, dwelling at limits.
> +
> + * - ``ramp_down``
> + - No-dwell low; resets to upper limit at lower limit.
> +
> + * - ``ramp_up``
> + - No-dwell high; resets to lower limit at upper limit.
> +
> + * - ``bidirectional_continuous``
> + - Continuous sweep without dwelling at either limit.
> +
> +Ramp channel attributes
> +^^^^^^^^^^^^^^^^^^^^^^^^
> +
> +The ``digital_ramp_up`` (channel 131) and ``digital_ramp_down`` (channel 132)
> +channels share the same attribute set but configure ascending and descending
> +ramp parameters independently:
> +
> +.. flat-table::
> + :header-rows: 1
> +
> + * - Attribute
> + - Unit
> + - Description
> +
> + * - ``frequency``
> + - Hz
> + - Ramp limit when destination is ``frequency``. Range [0, SYSCLK/2).
> +
> + * - ``phase``
> + - rad
> + - Ramp limit when destination is ``phase``. Range [0, 2*pi).
> +
> + * - ``scale``
> + - fractional
> + - Ramp limit when destination is ``amplitude``. Range [0, 1).
> +
> + * - ``sampling_frequency``
> + - Hz
> + - Ramp clock rate: SYSCLK / (4 * divider).
> +
> + * - ``frequency_step``
> + - Hz
> + - Per-tick frequency increment/decrement when destination is
> + ``frequency``.
> +
> + * - ``phase_step``
> + - rad
> + - Per-tick phase increment/decrement when destination is ``phase``.
> +
> + * - ``scale_step``
> + - fractional
> + - Per-tick amplitude increment/decrement when destination is
> + ``amplitude``. Range [0, 1).
> +
> +Usage examples
> +^^^^^^^^^^^^^^
> +
> +Configure a frequency sweep from 10 MHz to 100 MHz at a 1 MHz step:
> +
> +.. code-block:: bash
> +
> + # Set DRG destination to frequency
> + echo frequency > /sys/bus/iio/devices/iio:device0/out_altvoltage130_destination
> +
> + # Set operating mode
> + echo bidirectional_continuous > /sys/bus/iio/devices/iio:device0/out_altvoltage130_operating_mode
> +
> + # Set ramp limits
> + echo 60000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_frequency
> + echo 40000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage132_frequency
> +
> + # Set ramp step size to 1 MHz
> + echo 1000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_frequency_step
> + echo 1000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage132_frequency_step
> +
> + # Set ramp clock rate
> + echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_sampling_frequency
> +
> + # Enable the DRG
> + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage130_en
> +
> +Read the current DRG operating mode:
> +
> +.. code-block:: bash
> +
> + cat /sys/bus/iio/devices/iio:device0/out_altvoltage130_operating_mode
> +
> +RAM mode
> +--------
> +
> +The AD9910 contains a 1024 x 32-bit RAM that can be loaded with waveform data
> +and played back to modulate frequency, phase, amplitude, or polar (phase +
> +amplitude) parameters.
> +
> +RAM control channel attributes
> +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> +
> +.. flat-table::
> + :header-rows: 1
> +
> + * - Attribute
> + - Unit
> + - Description
> +
> + * - ``out_altvoltage140_en``
> + - boolean
> + - Enable/disable RAM playback. Toggling swaps profile registers between
> + single tone and RAM configurations across all 8 profiles.
> +
> + * - ``out_altvoltage140_destination``
> + - enum
> + - RAM data target: ``frequency``, ``phase``, ``amplitude`` or ``polar``.
> + Cannot be changed while RAM mode is enabled.
> +
> + * - ``out_altvoltage140_destination_available``
> + - string
> + - Lists available destination values.
> +
> + * - ``out_altvoltage140_operating_mode``
> + - enum
> + - Playback behavior (see table below).
> +
> + * - ``out_altvoltage140_operating_mode_available``
> + - string
> + - Lists available operating mode values.
> +
> + * - ``out_altvoltage140_frequency``
> + - Hz
> + - Frequency tuning word used as the single tone frequency when
> + RAM destination is not ``frequency``. Range [0, SYSCLK/2).
> +
> + * - ``out_altvoltage140_phase``
> + - rad
> + - Phase offset word used as the single tone phase when RAM destination
> + is not ``phase``. Range [0, 2*pi).
> +
> + * - ``out_altvoltage140_sampling_frequency``
> + - Hz
> + - RAM playback step rate controlling how fast the address counter
> + advances: SYSCLK / (4 * step_rate). Stored per-profile.
> +
> + * - ``out_altvoltage140_address_start``
Do we need this flexibility to set the start? We needed a length, but
if we want different effective start can just load a different image.
> + - integer
> + - Start address for the active profile. Range [0, 1023]. Cannot be
> + changed while RAM mode is enabled. If set above current end address,
> + end address is automatically adjusted.
> +
> + * - ``out_altvoltage140_address_end``
> + - integer
> + - End address for the active profile. Range [address_start, 1023].
> + Cannot be changed while RAM mode is enabled.
> +
> +RAM operating modes:
> +
> +.. flat-table::
> + :header-rows: 1
> +
> + * - Mode
> + - Description
> +
> + * - ``direct_switch``
> + - Start address defines a fixed word used by the selected profile.
> +
> + * - ``ramp_up``
> + - One-shot ramp through the current profile's address range.
> +
> + * - ``bidirectional``
> + - Ramp up then down through profile 0's address range.
> +
> + * - ``bidirectional_continuous``
> + - Continuous ramp up/down through current profile's address range.
> +
> + * - ``ramp_up_continuous``
> + - Continuous ramp up through current profile's address range.
> +
> + * - ``sequenced``
> + - Sequential playback from profile 0 to the active profile.
> + Requires active profile > 0.
> +
> + * - ``sequenced_continuous``
> + - Continuous sequential playback. Requires active profile > 0.
> +
> +Loading RAM data
> +^^^^^^^^^^^^^^^^
> +
> +RAM data is loaded through the firmware upload framework. The driver registers
> +a firmware upload device named ``iio_deviceX:ram``. Data must be a multiple of
> +4 bytes (32-bit words) and at most 4096 bytes (1024 words).
> +
> +Usage examples
> +^^^^^^^^^^^^^^
> +
> +Configure RAM mode with frequency destination and load a waveform:
> +
> +.. code-block:: bash
> +
> + # Set RAM address range for profile 0
> + echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage140_address_start
> + echo 999 > /sys/bus/iio/devices/iio:device0/out_altvoltage140_address_end
> +
> + # Set destination and operating mode
> + echo frequency > /sys/bus/iio/devices/iio:device0/out_altvoltage140_destination
> + echo ramp_up_continuous > /sys/bus/iio/devices/iio:device0/out_altvoltage140_operating_mode
> +
> + # Set playback rate
> + echo 250000 > /sys/bus/iio/devices/iio:device0/out_altvoltage140_sampling_frequency
> +
> + # Load RAM data via firmware upload
> + echo 1 > /sys/class/firmware/iio\:device0\:ram/loading
> + cat waveform.bin > /sys/class/firmware/iio\:device0\:ram/data
> + echo 0 > /sys/class/firmware/iio\:device0\:ram/loading
> +
> + # Enable RAM mode
> + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage140_en
> +
> +Read the current RAM operating mode:
> +
> +.. code-block:: bash
> +
> + cat /sys/bus/iio/devices/iio:device0/out_altvoltage140_operating_mode
> +
> +Output shift keying (OSK)
> +-------------------------
> +
> +OSK controls the output amplitude envelope, allowing the output to be ramped
> +on/off rather than switched abruptly.
> +
> +.. flat-table::
> + :header-rows: 1
> +
> + * - Attribute
> + - Unit
> + - Description
> +
> + * - ``out_altvoltage150_en``
> + - boolean
> + - Enable/disable OSK.
> +
> + * - ``out_altvoltage150_scale``
> + - fractional
> + - Target amplitude for the OSK ramp. 14-bit ASF field. Range [0, 1).
> +
> + * - ``out_altvoltage150_sampling_frequency``
> + - Hz
> + - OSK ramp rate: SYSCLK / (4 * divider).
> +
> + * - ``out_altvoltage150_pinctrl_en``
> + - boolean
> + - Enable manual external pin control. When enabled, the OSK pin directly
> + gates the output on/off instead of using the automatic ramp.
> +
> + * - ``out_altvoltage150_scale_step``
> + - fractional
> + - Automatic OSK amplitude step. Writing non-zero enables automatic OSK
> + and sets the per-tick increment. Writing ``0`` disables it. Rounded to
> + nearest hardware step: 0.000061, 0.000122, 0.000244 or 0.000488.
> +
> +Usage examples
> +^^^^^^^^^^^^^^
> +
> +Enable OSK with automatic ramping:
> +
> +.. code-block:: bash
> +
> + # Set ramp rate
> + echo 1000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_sampling_frequency
> +
> + # Enable automatic OSK with step size
> + echo 0.000244 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_scale_step
> +
> + # Enable OSK
> + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_en
> +
> +Enable manual pin-controlled OSK:
> +
> +.. code-block:: bash
> +
> + # Set target amplitude to full scale
> + echo 1.0 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_scale
> +
> + # Enable manual pin control
> + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_pinctrl_en
> + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_en
> +
> +
> +Physical channel
> +================
> +
> +The ``phy`` channel provides device-level control:
> +
> +.. flat-table::
> + :header-rows: 1
> +
> + * - Attribute
> + - Unit
> + - Description
> +
> + * - ``out_altvoltage100_sampling_frequency``
> + - Hz
> + - System clock (SYSCLK) frequency. With PLL enabled, configures the PLL
> + multiplier (range 420-1000 MHz). Without PLL, ref clock can only be
> + divided by 2.
> +
> + * - ``out_altvoltage100_profile``
> + - integer
> + - Active profile index [0, 7]. Selected via GPIO pins. Each profile
> + stores an independent set of single tone and RAM playback parameters.
> +
> + * - ``out_altvoltage100_powerdown``
> + - boolean
> + - Software power-down. Writing 1 powers down the digital core, DAC,
> + reference clock input and auxiliary DAC simultaneously.
> +
> +Usage examples
> +--------------
> +
> +Set the system clock to 1 GHz and select profile 3:
> +
> +.. code-block:: bash
> +
> + echo 1000000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_sampling_frequency
> + echo 3 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_profile
> +
> +Read current system clock frequency:
> +
> +.. code-block:: bash
> +
> + cat /sys/bus/iio/devices/iio:device0/out_altvoltage100_sampling_frequency
> +
> +Power down the device:
> +
> +.. code-block:: bash
> +
> + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_powerdown
> diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
> index ba3e609c6a13..55cb1ce84ba8 100644
> --- a/Documentation/iio/index.rst
> +++ b/Documentation/iio/index.rst
> @@ -29,6 +29,7 @@ Industrial I/O Kernel Drivers
> ad7606
> ad7625
> ad7944
> + ad9910
> ade9000
> adis16475
> adis16480
> diff --git a/MAINTAINERS b/MAINTAINERS
> index edd87ee7da5f..14e4272357ce 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1637,6 +1637,7 @@ S: Supported
> W: https://ez.analog.com/linux-software-drivers
> F: Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
> F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
> +F: Documentation/iio/ad9910.rst
> F: drivers/iio/frequency/ad9910.c
>
> ANALOG DEVICES INC MAX22007 DRIVER
>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation
2026-03-22 16:50 ` Jonathan Cameron
@ 2026-03-23 10:34 ` Rodrigo Alencar
2026-03-23 11:00 ` Andy Shevchenko
0 siblings, 1 reply; 27+ messages in thread
From: Rodrigo Alencar @ 2026-03-23 10:34 UTC (permalink / raw)
To: Jonathan Cameron, Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
On 26/03/22 04:50PM, Jonathan Cameron wrote:
> On Wed, 18 Mar 2026 17:56:02 +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 attributes.
> >
> > Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
...
> > +#include <linux/array_size.h>
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
>
> Generally can avoid including device.h in favour of more specific
> headers. There are a few exceptions where we can't such as actual
> dereferencing of struct device, but I don't recall seeing a case in here.
I understood that the usage of devm_add_action_or_reset() would justify
the header.
...
> > +#define AD9910_EXT_INFO(_name, _ident, _shared) { \
> > + .name = _name, \
> > + .read = ad9910_ext_info_read, \
> > + .write = ad9910_ext_info_write, \
> > + .private = _ident, \
> > + .shared = _shared, \
>
> If there are only a few of these, I'd put it long hand rather than
> using a macro. Tends to end up easier to read.
Next patches will leverage the macro as more ext_info attrs will be introduced.
I suppose we can build the foundation for later extension.
> > +}
> > +
> > +static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
> > + AD9910_EXT_INFO("profile", AD9910_PROFILE, IIO_SEPARATE),
> > + AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
> > + { }
> > +};
>
> > +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:
> > + switch (chan->channel) {
> > + case AD9910_CHANNEL_SINGLE_TONE:
>
> I haven't read on yet, but if you never have any other cases in here,
> perhaps us an if() as it will reduce indent of the code that follows.
Similar, other channels will be introduced here so additions are easier
to review.
> > + tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
> > + st->reg[AD9910_REG_PROFILE(st->profile)].val64);
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > + tmp64 = (u64)tmp32 * st->data.sysclk_freq_hz;
> > + *val = upper_32_bits(tmp64);
> > + *val2 = upper_32_bits((u64)lower_32_bits(tmp64) * MICRO);
...
> > +
> > +static int ad9910_cfg_sysclk(struct ad9910_state *st, bool update)
> > +{
> > + u32 tmp32, cfr3 = AD9910_CFR3_OPEN_MSK;
> > +
> > + cfr3 |= AD9910_CFR3_VCO_SEL_MSK |
> > + FIELD_PREP(AD9910_CFR3_DRV0_MSK, st->data.refclk_out_drv);
> > +
> > + if (st->data.pll_enabled) {
> > + tmp32 = st->data.pll_charge_pump_current - AD9910_ICP_MIN_uA;
> > + tmp32 = DIV_ROUND_CLOSEST(tmp32, AD9910_ICP_STEP_uA);
> > + cfr3 |= FIELD_PREP(AD9910_CFR3_ICP_MSK, tmp32) |
> > + AD9910_CFR3_PLL_EN_MSK;
> > + } else {
> > + cfr3 |= AD9910_CFR3_ICP_MSK |
>
> For this, be explicit what value you are setting, probably be defining a max value
> that the field can take. Whilst just setting the mask is the same it doesn't
> convey the same meaning to someone reading the code.
This is just the default value from the datasheet, ICP should not really matter
when the PLL is disabled, so removing this should be fine.
>
> > + AD9910_CFR3_REFCLK_DIV_RESETB_MSK |
> > + AD9910_CFR3_PFD_RESET_MSK;
> > + }
> > + st->reg[AD9910_REG_CFR3].val32 = cfr3;
> > +
> > + return ad9910_set_sysclk_freq(st, AD9910_PLL_OUT_MAX_FREQ_HZ, update);
> > +}
> > +
> > +static int ad9910_parse_fw(struct ad9910_state *st)
> > +{
> > + struct device *dev = &st->spi->dev;
> > + u32 tmp;
> > + int ret;
> > +
> > + st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable");
> > + if (st->data.pll_enabled) {
> > + tmp = AD9910_ICP_MAX_uA;
>
> Defaulting to max current seems unusual. What's the motivation? Normal instinct is
> go minimum if no other info.
ICP_MAX_uA leads to 111 in the CFR3_ICP field, which is the default value when the
device resets or when it powers on. I suppose that if we are not touching that
property, there would be no reason to change that.
...
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support
2026-03-18 18:28 ` Andy Shevchenko
2026-03-22 16:52 ` Jonathan Cameron
@ 2026-03-23 10:39 ` Rodrigo Alencar
2026-03-23 11:01 ` Andy Shevchenko
1 sibling, 1 reply; 27+ messages in thread
From: Rodrigo Alencar @ 2026-03-23 10:39 UTC (permalink / raw)
To: Andy Shevchenko, rodrigo.alencar
Cc: linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
On 26/03/18 08:28PM, Andy Shevchenko wrote:
> On Wed, Mar 18, 2026 at 05:56:03PM +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 IIO_CHAN_INFO_ENABLE:
> > + val = !!val;
>
> Only used once, why do we need this...
Next patches introduce more channels here, so the additions are easier to review.
> > + switch (chan->channel) {
> > + case AD9910_CHANNEL_PARALLEL_PORT:
> > + tmp32 = FIELD_PREP(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, val);
>
> ...and not just here?
>
> > + return ad9910_reg32_update(st, AD9910_REG_CFR2,
> > + AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
> > + tmp32, true);
> > + default:
> > + return -EINVAL;
> > + }
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation
2026-03-23 10:34 ` Rodrigo Alencar
@ 2026-03-23 11:00 ` Andy Shevchenko
0 siblings, 0 replies; 27+ messages in thread
From: Andy Shevchenko @ 2026-03-23 11:00 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: Jonathan Cameron, Rodrigo Alencar via B4 Relay, rodrigo.alencar,
linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
On Mon, Mar 23, 2026 at 10:34:37AM +0000, Rodrigo Alencar wrote:
> On 26/03/22 04:50PM, Jonathan Cameron wrote:
> > On Wed, 18 Mar 2026 17:56:02 +0000
> > Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
...
> > > +#include <linux/array_size.h>
> > > +#include <linux/bitfield.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/device.h>
> >
> > Generally can avoid including device.h in favour of more specific
> > headers. There are a few exceptions where we can't such as actual
> > dereferencing of struct device, but I don't recall seeing a case in here.
>
> I understood that the usage of devm_add_action_or_reset() would justify
> the header.
It's in the device/devres.h.
...
> > > + st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable");
> > > + if (st->data.pll_enabled) {
> > > + tmp = AD9910_ICP_MAX_uA;
> >
> > Defaulting to max current seems unusual.
Agree.
> > What's the motivation? Normal instinct is go minimum if no other info.
>
> ICP_MAX_uA leads to 111 in the CFR3_ICP field, which is the default value
> when the device resets or when it powers on. I suppose that if we are not
> touching that property, there would be no reason to change that.
I believe we should think different, id est about potential damages or
current drain. I would expect a minimum or hi-impedance (power off) state
of the related part of the device.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support
2026-03-23 10:39 ` Rodrigo Alencar
@ 2026-03-23 11:01 ` Andy Shevchenko
0 siblings, 0 replies; 27+ messages in thread
From: Andy Shevchenko @ 2026-03-23 11:01 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
On Mon, Mar 23, 2026 at 10:39:06AM +0000, Rodrigo Alencar wrote:
> On 26/03/18 08:28PM, Andy Shevchenko wrote:
> > On Wed, Mar 18, 2026 at 05:56:03PM +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 IIO_CHAN_INFO_ENABLE:
> > > + val = !!val;
> >
> > Only used once, why do we need this...
>
> Next patches introduce more channels here, so the additions are easier to review.
Yeah, but shouldn't be better to put this in each FIELD_PREP() as it will
immediately show the correctness and the value range without looking backwards
in the code?
> > > + switch (chan->channel) {
> > > + case AD9910_CHANNEL_PARALLEL_PORT:
> > > + tmp32 = FIELD_PREP(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, val);
> >
> > ...and not just here?
> >
> > > + return ad9910_reg32_update(st, AD9910_REG_CFR2,
> > > + AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
> > > + tmp32, true);
> > > + default:
> > > + return -EINVAL;
> > > + }
>
> --
> Kind regards,
>
> Rodrigo Alencar
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 8/9] Documentation: ABI: testing: add docs for ad9910 sysfs entries
2026-03-22 17:22 ` Jonathan Cameron
@ 2026-03-23 11:36 ` Rodrigo Alencar
0 siblings, 0 replies; 27+ messages in thread
From: Rodrigo Alencar @ 2026-03-23 11:36 UTC (permalink / raw)
To: Jonathan Cameron, Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
On 26/03/22 05:22PM, Jonathan Cameron wrote:
> On Wed, 18 Mar 2026 17:56:08 +0000
> Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
>
> > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> >
> > Add ABI documentation file for the DDS AD9910 with sysfs entries to
> > control Parallel Port, Digital Ramp Generator, RAM and OSK parameters.
> >
> > Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > ---
...
> > +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_profile
> > +KernelVersion:
> > +Contact: linux-iio@vger.kernel.org
> > +Description:
> > + Read/write the active profile index [0, 7] from/to the physical
> > + channel. The AD9910 supports 8 profiles, each storing a complete
> > + set of single tone (frequency, phase, amplitude) and RAM playback
> > + parameters.
>
> This one is interesting. Can we treat them as symbols that we are picking
> between? We have similar DAC ABIs for that already.
The profile concept comes from the datasheet and defines sets of configuration
for single tone and RAM control mode. I am not sure how we fit this idea into a
"symbol"
> Is this picking between them for purposes of configuration or setting which one is
> in being output currently?
Well, this is being used for configuration and activating, then, yes, you can only configure
an active profile, but I was not seeing that as an issue. I suppose that simplifies the
ABI a bit.
...
> > +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_destination
> > +KernelVersion:
> > +Contact: linux-iio@vger.kernel.org
> > +Description:
> > + Read/write the digital ramp generator (DRG) or the RAM control
> > + destination parameter. Determines which DDS core parameter is to
> > + be modulated when the child mode channel is enabled.
> > +
> > + Available values can be read from the corresponding
> > + out_altvoltageY_destination_available attribute.
> > +
> > + Valid values: "polar" (only for RAM control), "frequency", "phase"
> > + and "amplitude"
>
> This is very device specific. Maybe we are better representing these as separate
> channels each with their own controls for DRG. No problem if changing one changes
> another.
You mean removing this generic Y there? Indeed, there are separate configs for each one.
...
> > +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_operating_mode
> > +KernelVersion:
> > +Contact: linux-iio@vger.kernel.org
> > +Description:
> > + Read/write the DRG or RAM control operating mode. For the DRG
> > + channel it controls the no-dwell behavior of the ramp.
> > +
> > + Available values can be read from the corresponding
> > + out_altvoltageY_operating_mode_available attribute.
> > +
> > + Valid values for DRG channel:
> > +
> > + - "bidirectional": Normal ramp generation (ramp up then
> > + down, dwelling at limits).
>
> Some sort of trapezium wave? Maybe this and continuous forms are combined
> and we have a separate dwell time control?
Yes, sort of. Dwell control is made by an external pin (DRCTL), often controlled
by an FPGA to achieve certain required timings. I can say that software control
is not really recommended, unless only a one-shot ramp is necessary.
When adding the IIO backend support, extendend attributes will be added to
support control of dwell times.
>
> > + - "ramp_down": No-dwell low; the ramp resets to upper
> > + limit upon reaching the lower limit.
> > + - "ramp_up": No-dwell high; the ramp resets to lower
> > + limit upon reaching the upper limit.
> > + - "bidirectional_continuous": Both no-dwell high and low;
> > + the ramp continuously sweeps without dwelling.
>
> Triangle wave? bidirectional continuous is a rather confusing term so maybe
> we should rethink this one.
Mostly yes, but not only that. Sawtooth can be achieved as well by changing
the step sizes, also other weird patterns can be achieved by toggling DRCTL pin.
This mode is the most useful when one does not have an FPGA and want to save
resources on controlling the DRCTL pin. That mode name comes from the datasheet,
so I suppose it was fine.
> > +
> > + Valid values for RAM control channel:
> > +
> > + - "direct_switch": start address defines fixed word to be used
> > + by the selected profile.
> > + - "ramp_up": One-shot ramp up through current profile's address
> > + range.
> > + - "bidirectional": Ramp up then down through PROFILE0 pin.
>
> Avoid specifics like this. Can we call external control pin or something like that?
>
> > + - "bidirectional_continuous": Continuous ramp up/down
> > + through current profile's address range.
> > + - "ramp_up_continuous": Continuous ramp up through
> > + current profile's address range.
>
> I guess this goes back to start on finishing ramping up?
Yes, any dwell time should be considered when loading the "waveform" into the RAM
>
> > + - "sequenced": Sequenced playback of RAM profiles up to
> > + the active profile. Requires active profile > 0.
> Is this just running through each profile one after another? (other than profile 0)?
for this, this would be the actions:
- configure all desired profiles (0 up to X)
- Set the operating mode to sequenced (profile X would be active at this point)
- Enable RAM mode
When RAM mode is enabled, it would trigger the execution of profiles 0 up to X,
in sequence, according to the configured address range and sample rate for each profile.
When one profile ends the next starts until profile X finishes.
> > + - "sequenced_continuous": Continuous sequenced playback
> > + of RAM profiles up to the active profile. Requires
> > + active profile > 0.
> Similar to above, maybe separate out dwell time if that's the difference between
> sequenced and sequenced_continuous.
The difference here is that when Profile X finishes, Profile 0 starts again.
So the previous one is kind of an one-shot mode of multiple profiles in sequence
...
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH RFC v2 9/9] docs: iio: add documentation for ad9910 driver
2026-03-22 17:34 ` Jonathan Cameron
@ 2026-03-23 11:58 ` Rodrigo Alencar
0 siblings, 0 replies; 27+ messages in thread
From: Rodrigo Alencar @ 2026-03-23 11:58 UTC (permalink / raw)
To: Jonathan Cameron, Rodrigo Alencar via B4 Relay
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
On 26/03/22 05:34PM, Jonathan Cameron wrote:
> On Wed, 18 Mar 2026 17:56:09 +0000
> Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
>
> > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> >
> > Add documentation for the AD9910 DDS IIO driver, which describes channels,
> > DDS modes, attributes and ABI usage examples.
> >
> > Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
...
> > +Channel hierarchy
> > +=================
> > +
> > +The driver exposes the following IIO output channels, each identified by a
> > +unique channel number and a human-readable label:
> > +
> > +* ``out_altvoltage100``: ``phy``: Physical output: system clock and profile control
> > +
> > + * ``out_altvoltage110``: ``single_tone``: Single tone mode: per-profile
> > + frequency, phase, amplitude
> > +
> > + * ``out_altvoltage120``: ``parallel_port``: Parallel port modulation: enable
> > + and offset/scale parameters
> > +
> > + * ``out_altvoltage130``: ``digital_ramp_generator``: DRG control: enable,
> > + destination, operating mode
> > +
> > + * ``out_altvoltage131``: ``digital_ramp_up``: DRG ramp-up parameters:
> > + limits, step sizes, ramp rate
> > + * ``out_altvoltage132``: ``digital_ramp_down``: DRG ramp-down parameters:
> > + limits, step sizes, ramp rate
> > +
> > + * ``out_altvoltage140``: ``ram_control``: RAM playback: enable, destination,
> > + operating mode, address range
> > +
> > + * ``out_altvoltage150``: ``output_shift_keying``: OSK: enable, amplitude
> > + scale, ramp rate, auto/manual control
> > +
> > +The ``phy`` channel is the root of the hierarchy. Changing its
> > +``sampling_frequency`` reconfigures the system clock (SYSCLK) which affects all
> > +other channels. The ``profile`` attribute on this channel selects the active
> > +hardware profile (0-7) used by the single tone and RAM channels.
> I asked out this profile thing in one of the other patches. Key to me is
> that how we write non active profiles? The most similar thing we've seen
> in the past has been setting other frequencies for FSK or phases for PSK or
> more mundane DC DAC output that are symbol based. (often an external signal)
Yes, not allowing to configure a non-active profile at this point.
Initially was not seeing this as a problem, but would you think different
channels for each profiles should be created? e.g.:
- out_altvoltage111 ... out_altvoltage118 for single tone profiles; and
- out_altvoltage141 .. out_altvoltage148 for RAM profiles
That would not remove the need for something like the profile attribute.
> For those we have added an additional index so we can see which symbol we
> are changing parameters for. Here it might need to be done in the channel
> numbering. I'm not sure.
>
> > +
> > +All mode-specific channels (parallel port, DRG, RAM, OSK) have an ``enable``
> > +attribute. The DRG and RAM channels additionally have ``destination`` and
> > +``operating_mode`` attributes that configure which DDS core parameter is
> > +modulated and how.
>
> I wonder if we flatten things out and have separate channels for each type
> of modulation. Might lead to a more standard looking interfaces. We don't really
> have a standard path to control one type of thing feeding another, whereas
> we do have simple 'enable' interfaces.
...
> > +RAM mode
> > +--------
> > +
> > +The AD9910 contains a 1024 x 32-bit RAM that can be loaded with waveform data
> > +and played back to modulate frequency, phase, amplitude, or polar (phase +
> > +amplitude) parameters.
> > +
> > +RAM control channel attributes
> > +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > +
> > +.. flat-table::
> > + :header-rows: 1
> > +
> > + * - Attribute
> > + - Unit
> > + - Description
> > +
> > + * - ``out_altvoltage140_en``
> > + - boolean
> > + - Enable/disable RAM playback. Toggling swaps profile registers between
> > + single tone and RAM configurations across all 8 profiles.
...
> > + * - ``out_altvoltage140_address_start``
> Do we need this flexibility to set the start? We needed a length, but
> if we want different effective start can just load a different image.
There is one image being loaded into the entire RAM and each profile may choose
from wich sample it starts and ends its address ramp.
The profiles can have those address ranges to overlap or not, then I suppose
considering different images would just complicate things.
> > + - integer
> > + - Start address for the active profile. Range [0, 1023]. Cannot be
> > + changed while RAM mode is enabled. If set above current end address,
> > + end address is automatically adjusted.
> > +
...
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-03-23 11:59 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-18 17:56 [PATCH RFC v2 0/9] AD9910 Direct Digital Synthesizer Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 1/9] dt-bindings: iio: frequency: add ad9910 Rodrigo Alencar via B4 Relay
2026-03-19 17:25 ` Conor Dooley
2026-03-20 11:21 ` Rodrigo Alencar
2026-03-20 14:00 ` Conor.Dooley
2026-03-20 17:14 ` Conor Dooley
2026-03-18 17:56 ` [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation Rodrigo Alencar via B4 Relay
2026-03-22 16:50 ` Jonathan Cameron
2026-03-23 10:34 ` Rodrigo Alencar
2026-03-23 11:00 ` Andy Shevchenko
2026-03-18 17:56 ` [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support Rodrigo Alencar via B4 Relay
2026-03-18 18:28 ` Andy Shevchenko
2026-03-22 16:52 ` Jonathan Cameron
2026-03-23 10:39 ` Rodrigo Alencar
2026-03-23 11:01 ` Andy Shevchenko
2026-03-18 17:56 ` [PATCH RFC v2 4/9] iio: frequency: ad9910: add digital ramp generator support Rodrigo Alencar via B4 Relay
2026-03-18 19:14 ` Andy Shevchenko
2026-03-18 17:56 ` [PATCH RFC v2 5/9] iio: frequency: ad9910: add RAM mode support Rodrigo Alencar via B4 Relay
2026-03-22 17:05 ` Jonathan Cameron
2026-03-18 17:56 ` [PATCH RFC v2 6/9] iio: frequency: ad9910: add output shift keying support Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 7/9] iio: frequency: ad9910: add channel labels Rodrigo Alencar via B4 Relay
2026-03-18 17:56 ` [PATCH RFC v2 8/9] Documentation: ABI: testing: add docs for ad9910 sysfs entries Rodrigo Alencar via B4 Relay
2026-03-22 17:22 ` Jonathan Cameron
2026-03-23 11:36 ` Rodrigo Alencar
2026-03-18 17:56 ` [PATCH RFC v2 9/9] docs: iio: add documentation for ad9910 driver Rodrigo Alencar via B4 Relay
2026-03-22 17:34 ` Jonathan Cameron
2026-03-23 11:58 ` Rodrigo Alencar
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox