summaryrefslogtreecommitdiff
path: root/drivers/iio/frequency/ad9910.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-24 15:44:26 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-24 15:44:26 -1000
commit330517c63aa9b48227b6ab45f01003effac77ae4 (patch)
tree51b6e38214a50c0253eba6dc05eeed6ae0f5b995 /drivers/iio/frequency/ad9910.c
parent168151cdb0f93d26557f9f3c7aae659b8d6b1863 (diff)
parent985955d8e6b2b15cfd65b7ac6893e3a5a90c5870 (diff)
downloadlwn-master.tar.gz
lwn-master.zip
Merge tag 'char-misc-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-miscHEADmaster
Pull Char/Misc/IIO/others driver updates from Greg KH: "Here is the big set of char/misc/android/iio and other small driver subsystem patches for 7.4-rc1. Loads of stuff in here, larger than normal. Highlights include: - removal of the C binder code, the rust version is stable and will be used going forward in a few billion devices. Great job by those developers - loads and loads of IIO driver updates and feature additions - speakup driver fixes and updates - mhi driver updates - mei driver updates - xillybus driver fixes - comedi driver fixes - lots of coresight driver updates - Interconnect driver updates - nvmem driver updates and fixes - slimbus driver updates and fixes - spmi driver updates and fixes - lots of tiny misc and char driver fixes based on people scanning for things All of these have been in linux-next for a while with no reported issues, other than a merge conflict with one of the rust tree files, which should be simple to resolve" * tag 'char-misc-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (446 commits) rust_binder: wake epoll on self-work queued to poll thread rust_binder: avoid overflow in get_node_debug_info() MAINTAINERS: Add Abel as SPMI co-maintainer spmi: mtk-pmif: Fix clock reference leak in mtk_spmi_bus_probe() iio: adc: qcom-spmi-iadc: Remove regmap R/W wrapper functions iio: adc: qcom-spmi-iadc: Migrate to devm_spmi_subdevice_alloc_and_add() misc: qcom-coincell: Migrate to devm_spmi_subdevice_alloc_and_add() phy: qualcomm: eusb2-repeater: Migrate to devm_spmi_subdevice_alloc_and_add() power: reset: qcom-pon: Migrate to devm_spmi_subdevice_alloc_and_add() nvmem: qcom-spmi-sdam: Migrate to devm_spmi_subdevice_alloc_and_add() spmi: Add helper to get a parent SPMI device spmi: Implement spmi_subdevice_alloc_and_add() and devm variant spmi: Remove unneeded goto in spmi_device_add() error path spmi: Print error status with %pe format spmi: Remove redundant dev_name() print in spmi_device_add() spmi: Fix potential use-after-free by grabbing of_node reference spmi: mtk-pmif: Add workaround for FSM lockup/error in read operation MAINTAINERS: Add Qualcomm SPMI controller entry spmi: pmic-arb: return 0 in spmi_pmic_arb_register_buses() on success comedi: addi_apci_3xxx: fix subdevice count underallocation ...
Diffstat (limited to 'drivers/iio/frequency/ad9910.c')
-rw-r--r--drivers/iio/frequency/ad9910.c2347
1 files changed, 2347 insertions, 0 deletions
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
new file mode 100644
index 000000000000..04a4e184b873
--- /dev/null
+++ b/drivers/iio/frequency/ad9910.c
@@ -0,0 +1,2347 @@
+// 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/crc32.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device-id/of.h>
+#include <linux/device-id/spi.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/log2.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/seq_file.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))
+
+/* 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_EXT_POWER_DOWN_CTL_MSK BIT(3)
+#define AD9910_CFR1_SDIO_INPUT_ONLY_MSK BIT(1)
+#define AD9910_CFR1_LSB_FIRST_MSK BIT(0)
+
+/* DIGITAL and REFCLK_INPUT powerdown bits are effective without I/O update */
+#define AD9910_CFR1_SW0_POWER_DOWN_MSK (AD9910_CFR1_DIGITAL_POWER_DOWN_MSK | \
+ AD9910_CFR1_REFCLK_INPUT_POWER_DOWN_MSK)
+/* DAC and AUX_DAC powerdown require I/O update */
+#define AD9910_CFR1_SW1_POWER_DOWN_MSK (AD9910_CFR1_DAC_POWER_DOWN_MSK | \
+ AD9910_CFR1_AUX_DAC_POWER_DOWN_MSK)
+
+#define AD9910_CFR1_SW_POWER_DOWN_MSK (AD9910_CFR1_SW0_POWER_DOWN_MSK | \
+ AD9910_CFR1_SW1_POWER_DOWN_MSK)
+
+/* 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 (BIT(27) | GENMASK(18, 16))
+#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)
+
+/* 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 */
+#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)
+
+/* 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)
+#define AD9910_PROFILE_ST_FTW_MSK GENMASK_ULL(31, 0)
+
+/* Profile Register Format (RAM Mode) */
+#define AD9910_PROFILE_RAM_OPEN_MSK GENMASK_ULL(61, 56)
+#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
+#define AD9910_PI_PICORAD 3141592653590ULL
+
+#define AD9910_MAX_SYSCLK_HZ (1000 * HZ_PER_MHZ)
+#define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500)
+
+#define AD9910_ASF_MAX FIELD_MAX(AD9910_PROFILE_ST_ASF_MSK)
+#define AD9910_ASF_PP_LSB_MAX FIELD_MAX(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK)
+#define AD9910_POW_MAX FIELD_MAX(AD9910_PROFILE_ST_POW_MSK)
+#define AD9910_POW_PP_LSB_MAX FIELD_MAX(AD9910_POW_PP_LSB_MSK)
+#define AD9910_STEP_RATE_MAX FIELD_MAX(AD9910_DRG_RATE_DEC_MSK)
+#define AD9910_NUM_PROFILES 8
+
+#define AD9910_RAM_FW_MAGIC 0x00AD9910
+#define AD9910_RAM_FW_V1 0x0001
+#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_ENABLED(st) \
+ FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, (st)->reg[AD9910_REG_CFR1].val32)
+
+#define AD9910_DEST_FREQUENCY 0
+#define AD9910_DEST_PHASE 1
+#define AD9910_DEST_AMPLITUDE 2
+#define AD9910_DEST_POLAR 3
+
+/* PLL constants */
+#define AD9910_PLL_MIN_N 12
+#define AD9910_PLL_MAX_N 127
+
+#define AD9910_PLL_IN_MIN_FREQ_HZ (3307 * 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 AD9910_MAX_SYSCLK_HZ
+
+#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_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
+
+/* altcurrent ABI is in mA */
+#define AD9910_NANO_MILLIAMP_PER_MICROAMP 1000000UL
+#define AD9910_PICO_MILLIAMP_PER_MICROAMP 1000000000UL
+
+#define AD9910_REFDIV2_MIN_FREQ_HZ (25 * HZ_PER_MHZ)
+#define AD9910_REFDIV2_MAX_FREQ_HZ (1900 * HZ_PER_MHZ)
+
+#define AD9910_WAKEUP_DELAY_us 1000 /* 1ms: datasheet Table 1 */
+#define AD9910_RESET_DELAY_us 1 /* 5 sysclk cycles: < 1us */
+
+#define AD9910_SPI_DATA_IDX 1
+#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)
+
+/**
+ * enum ad9910_channel - AD9910 channel identifiers in priority order
+ *
+ * @AD9910_CHANNEL_PHY: Physical output channel
+ * @AD9910_CHANNEL_PROFILE_0: Profile 0 output channel
+ * @AD9910_CHANNEL_PROFILE_1: Profile 1 output channel
+ * @AD9910_CHANNEL_PROFILE_2: Profile 2 output channel
+ * @AD9910_CHANNEL_PROFILE_3: Profile 3 output channel
+ * @AD9910_CHANNEL_PROFILE_4: Profile 4 output channel
+ * @AD9910_CHANNEL_PROFILE_5: Profile 5 output channel
+ * @AD9910_CHANNEL_PROFILE_6: Profile 6 output channel
+ * @AD9910_CHANNEL_PROFILE_7: Profile 7 output channel
+ * @AD9910_CHANNEL_PARALLEL: Parallel Data output channel
+ * @AD9910_CHANNEL_PARALLEL_POLAR: Parallel Polar Data 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
+ * @AD9910_CHANNEL_RAM: RAM control output channel
+ * @AD9910_CHANNEL_OSK: Output Shift Keying output channel
+ */
+enum ad9910_channel {
+ AD9910_CHANNEL_PHY = 100,
+ AD9910_CHANNEL_PROFILE_0 = 110,
+ AD9910_CHANNEL_PROFILE_1 = 111,
+ AD9910_CHANNEL_PROFILE_2 = 112,
+ AD9910_CHANNEL_PROFILE_3 = 113,
+ AD9910_CHANNEL_PROFILE_4 = 114,
+ AD9910_CHANNEL_PROFILE_5 = 115,
+ AD9910_CHANNEL_PROFILE_6 = 116,
+ AD9910_CHANNEL_PROFILE_7 = 117,
+ AD9910_CHANNEL_PARALLEL = 120,
+ AD9910_CHANNEL_PARALLEL_POLAR = 121,
+ AD9910_CHANNEL_DRG = 130,
+ AD9910_CHANNEL_DRG_RAMP_UP = 131,
+ AD9910_CHANNEL_DRG_RAMP_DOWN = 132,
+ AD9910_CHANNEL_RAM = 140,
+ AD9910_CHANNEL_OSK = 150,
+};
+
+/**
+ * struct ad9910_ram_fw - AD9910 RAM firmware format
+ * @magic: Magic number for RAM firmware validation
+ * @version: Firmware version number
+ * @wcount: Number of RAM words to be written
+ * @crc: CRC32 checksum of the RAM data for integrity verification
+ * @cfr1: Value of CFR1 register to be configured (not all fields are
+ * used, but this is included here for convenience)
+ * @profiles: Array of RAM profile configurations
+ * @words: Array of RAM words to be written. Data pattern should be set in
+ * reverse order and wcount specifies the number of words in this
+ * array
+ */
+struct ad9910_ram_fw {
+ __be32 magic;
+ __be16 version;
+ __be16 wcount;
+ __be32 crc;
+ __be32 cfr1;
+ __be64 profiles[AD9910_NUM_PROFILES];
+ __be32 words[] __counted_by_be(wcount);
+} __packed;
+
+enum {
+ AD9910_CHAN_IDX_PHY,
+ AD9910_CHAN_IDX_PROFILE_0,
+ AD9910_CHAN_IDX_PROFILE_1,
+ AD9910_CHAN_IDX_PROFILE_2,
+ AD9910_CHAN_IDX_PROFILE_3,
+ AD9910_CHAN_IDX_PROFILE_4,
+ AD9910_CHAN_IDX_PROFILE_5,
+ AD9910_CHAN_IDX_PROFILE_6,
+ AD9910_CHAN_IDX_PROFILE_7,
+ AD9910_CHAN_IDX_PARALLEL_AMP,
+ AD9910_CHAN_IDX_PARALLEL_PHASE,
+ AD9910_CHAN_IDX_PARALLEL_FREQ,
+ AD9910_CHAN_IDX_PARALLEL_POLAR_AMP,
+ AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE,
+ AD9910_CHAN_IDX_DRG_FREQ,
+ AD9910_CHAN_IDX_DRG_PHASE,
+ AD9910_CHAN_IDX_DRG_AMP,
+ AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP,
+ AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN,
+ AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP,
+ AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN,
+ AD9910_CHAN_IDX_DRG_AMP_RAMP_UP,
+ AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN,
+ AD9910_CHAN_IDX_RAM,
+ AD9910_CHAN_IDX_OSK,
+};
+
+enum {
+ AD9910_POWERDOWN,
+ AD9910_DWELL_EN,
+ AD9910_ROC,
+ AD9910_ROC_AVAIL,
+};
+
+struct ad9910_data {
+ u32 sysclk_freq_hz;
+ u32 output_current_uA;
+
+ u16 pll_charge_pump_current;
+ u8 refclk_out_drv;
+ bool pll_enabled;
+};
+
+union ad9910_reg {
+ u64 val64;
+ u32 val32;
+ u16 val16;
+};
+
+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;
+ struct gpio_descs *gpio_profile;
+
+ /* cached registers */
+ union ad9910_reg 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];
+
+ /*
+ * 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);
+};
+
+/**
+ * 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.
+ */
+static u64 ad9910_rational_scale(u64 input, u64 scale, u64 reference)
+{
+ return mul_u64_add_u64_div_u64(input, scale, reference >> 1, reference);
+}
+
+static inline u64 ad9910_ram_profile_val(struct ad9910_state *st)
+{
+ if (AD9910_RAM_ENABLED(st))
+ return st->reg[AD9910_REG_PROFILE(st->profile)].val64;
+ else
+ return st->reg_profile[st->profile];
+}
+
+static inline u64 ad9910_st_profile_val(struct ad9910_state *st, u8 profile)
+{
+ if (AD9910_RAM_ENABLED(st))
+ return st->reg_profile[profile];
+ else
+ return st->reg[AD9910_REG_PROFILE(profile)].val64;
+}
+
+static int ad9910_io_update(struct ad9910_state *st)
+{
+ if (st->gpio_update) {
+ gpiod_set_value_cansleep(st->gpio_update, 1);
+ fsleep(1);
+ gpiod_set_value_cansleep(st->gpio_update, 0);
+ }
+
+ return 0;
+}
+
+static int ad9910_spi_read(struct ad9910_state *st, u8 reg, void *data,
+ size_t len)
+{
+ u8 inst = AD9910_SPI_READ_MSK | FIELD_PREP(AD9910_SPI_ADDR_MSK, reg);
+
+ return spi_write_then_read(st->spi, &inst, sizeof(inst), data, len);
+}
+
+static 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)
+ return ret;
+
+ if (update)
+ return ad9910_io_update(st);
+
+ return 0;
+}
+
+#define AD9910_REG_READ_FN(nb) \
+static int ad9910_reg##nb##_read(struct ad9910_state *st, u8 reg, \
+ u##nb * data) \
+{ \
+ __be##nb be_data; \
+ int ret; \
+ \
+ ret = ad9910_spi_read(st, reg, &be_data, sizeof(be_data)); \
+ if (ret) \
+ return ret; \
+ \
+ *data = be##nb##_to_cpu(be_data); \
+ 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, u32 val_uA,
+ bool update)
+{
+ u32 code;
+
+ /* FSC = (86.4 / Rset) * (1 + CODE/96) where Rset = 10k ohms */
+ val_uA = clamp(val_uA, AD9910_DAC_IOUT_MIN_uA, AD9910_DAC_IOUT_MAX_uA);
+ code = DIV_ROUND_CLOSEST(val_uA - AD9910_DAC_IOUT_MIN_uA, 90);
+ st->data.output_current_uA = AD9910_DAC_IOUT_MIN_uA + code * 90;
+
+ return ad9910_reg32_write(st, AD9910_REG_AUX_DAC, code, update);
+}
+
+static int ad9910_set_sysclk_freq(struct ad9910_state *st, u32 freq_hz,
+ bool update)
+{
+ struct device *dev = &st->spi->dev;
+ unsigned long refclk_freq_hz;
+ u32 sysclk_freq_hz;
+ u32 tmp32, vco_sel;
+ int ret;
+
+ if (!freq_hz || freq_hz > AD9910_MAX_SYSCLK_HZ)
+ return -EINVAL;
+
+ refclk_freq_hz = clk_get_rate(st->refclk);
+ 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(dev,
+ "REF_CLK freq %lu 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, DIV_ROUND_UP(AD9910_PLL_OUT_MIN_FREQ_HZ, refclk_freq_hz),
+ AD9910_PLL_OUT_MAX_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_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 {
+ if (refclk_freq_hz < AD9910_REFDIV2_MIN_FREQ_HZ ||
+ refclk_freq_hz > AD9910_REFDIV2_MAX_FREQ_HZ) {
+ dev_err(dev,
+ "REF_CLK freq %lu Hz is out of divider range\n",
+ refclk_freq_hz);
+ return -ERANGE;
+ }
+
+ tmp32 = DIV_ROUND_CLOSEST(refclk_freq_hz, freq_hz);
+ tmp32 = clamp(tmp32, 1U, 2U);
+ sysclk_freq_hz = refclk_freq_hz / tmp32;
+ tmp32 = AD9910_CFR3_VCO_SEL_MSK |
+ FIELD_PREP(AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, tmp32 % 2);
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR3,
+ AD9910_CFR3_VCO_SEL_MSK |
+ 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));
+
+ st->profile = profile;
+ values[0] = profile;
+ gpiod_multi_set_value_cansleep(st->gpio_profile, values);
+
+ return 0;
+}
+
+static bool ad9910_sw_powerdown_get(struct ad9910_state *st)
+{
+ return FIELD_GET(AD9910_CFR1_SW_POWER_DOWN_MSK,
+ st->reg[AD9910_REG_CFR1].val32) ? true : false;
+}
+
+static int ad9910_sw_powerdown_set(struct ad9910_state *st, bool enable)
+{
+ int ret;
+
+ if (ad9910_sw_powerdown_get(st) == enable)
+ return 0;
+
+ /*
+ * When powering down, the DAC and AUX_DAC (SW1) must be powered down
+ * first, as they require an I/O update to take effect. The opposite is
+ * true when powering up, the DAC and AUX_DAC must be powered up last,
+ * i.e., after the DIGITAL and REFCLK_INPUT (SW0) power up.
+ */
+ if (enable) {
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SW1_POWER_DOWN_MSK,
+ AD9910_CFR1_SW1_POWER_DOWN_MSK,
+ true);
+ if (ret)
+ return ret;
+
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SW0_POWER_DOWN_MSK,
+ AD9910_CFR1_SW0_POWER_DOWN_MSK,
+ false);
+ }
+
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SW0_POWER_DOWN_MSK, 0, false);
+ if (ret)
+ return ret;
+
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SW1_POWER_DOWN_MSK, 0, true);
+}
+
+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_POWERDOWN:
+ val = ad9910_sw_powerdown_get(st);
+ break;
+ case AD9910_DWELL_EN:
+ if (chan->channel == AD9910_CHANNEL_DRG_RAMP_UP)
+ val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK,
+ st->reg[AD9910_REG_CFR2].val32) ? 0 : 1;
+ else
+ val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
+ st->reg[AD9910_REG_CFR2].val32) ? 0 : 1;
+ 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_POWERDOWN:
+ ret = ad9910_sw_powerdown_set(st, val32 ? true : false);
+ if (ret)
+ return ret;
+ break;
+ case AD9910_DWELL_EN:
+ if (chan->channel == AD9910_CHANNEL_DRG_RAMP_UP) {
+ val32 = val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK;
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK,
+ val32, true);
+ if (ret)
+ return ret;
+ } else {
+ val32 = val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_LOW_MSK;
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
+ val32, true);
+ if (ret)
+ return ret;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return len;
+}
+
+static ssize_t ad9910_drg_roc_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);
+ u64 roc64;
+ u32 rate;
+
+ guard(mutex)(&st->lock);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ roc64 = FIELD_GET(AD9910_DRG_STEP_INC_MSK,
+ st->reg[AD9910_REG_DRG_STEP].val64);
+ rate = FIELD_GET(AD9910_DRG_RATE_INC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ break;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ roc64 = FIELD_GET(AD9910_DRG_STEP_DEC_MSK,
+ st->reg[AD9910_REG_DRG_STEP].val64);
+ rate = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!rate)
+ return -ERANGE;
+
+ roc64 *= st->data.sysclk_freq_hz;
+ return sysfs_emit(buf, "%llu\n", div_u64(roc64, 4 * rate));
+}
+
+static ssize_t ad9910_drg_roc_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);
+ u64 tmp64;
+ u32 rate;
+ int ret;
+
+ ret = kstrtou64(buf, 10, &tmp64);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ rate = FIELD_GET(AD9910_DRG_RATE_INC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ if (!rate)
+ return -ERANGE;
+
+ tmp64 = ad9910_rational_scale(tmp64, 4 * rate, st->data.sysclk_freq_hz);
+ tmp64 = min_t(u64, tmp64, U32_MAX);
+
+ ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP,
+ AD9910_DRG_STEP_INC_MSK,
+ FIELD_PREP(AD9910_DRG_STEP_INC_MSK, tmp64),
+ true);
+ if (ret)
+ return ret;
+ break;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ rate = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ if (!rate)
+ return -ERANGE;
+
+ tmp64 = ad9910_rational_scale(tmp64, 4 * rate, st->data.sysclk_freq_hz);
+ tmp64 = min_t(u64, tmp64, U32_MAX);
+
+ ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP,
+ AD9910_DRG_STEP_DEC_MSK,
+ FIELD_PREP(AD9910_DRG_STEP_DEC_MSK, tmp64),
+ true);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return len;
+}
+
+static const u32 ad9910_osk_raw_step[] = {
+ 0, /* no step: manual mode (NOT pin controlled) */
+ 1, /* step size 1: automatic mode (pin controlled) */
+ 2, /* step size 2: automatic mode (pin controlled) */
+ 4, /* step size 4: automatic mode (pin controlled) */
+ 8, /* step size 8: automatic mode (pin controlled) */
+ GENMASK(13, 0), /* max step: manual mode (pin controlled) */
+};
+
+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);
+ bool auto_en, pinctrl_en;
+ u32 rate, step;
+ u64 roc64;
+
+ guard(mutex)(&st->lock);
+
+ rate = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, st->reg[AD9910_REG_ASF].val32);
+ if (!rate)
+ return -ERANGE;
+
+ switch (private) {
+ case AD9910_ROC:
+ auto_en = FIELD_GET(AD9910_CFR1_SELECT_AUTO_OSK_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ pinctrl_en = FIELD_GET(AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (auto_en) {
+ step = FIELD_GET(AD9910_ASF_STEP_SIZE_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ step = ad9910_osk_raw_step[step + 1];
+ } else if (pinctrl_en) {
+ step = ad9910_osk_raw_step[ARRAY_SIZE(ad9910_osk_raw_step) - 1];
+ } else {
+ step = ad9910_osk_raw_step[0];
+ }
+
+ roc64 = div_u64((u64)step * st->data.sysclk_freq_hz, 4 * rate);
+ return sysfs_emit(buf, "%llu\n", roc64);
+ case AD9910_ROC_AVAIL: {
+ ssize_t len = 0;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(ad9910_osk_raw_step); i++) {
+ roc64 = div_u64((u64)ad9910_osk_raw_step[i] * st->data.sysclk_freq_hz,
+ 4 * rate);
+ len += sysfs_emit_at(buf, len, "%llu ", roc64);
+ }
+
+ buf[len - 1] = '\n'; /* replace last space with a newline */
+ return len;
+ }
+ 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);
+ u32 idx, reg_val, rate;
+ u64 step;
+ int ret;
+
+ ret = kstrtou64(buf, 10, &step);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ rate = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, st->reg[AD9910_REG_ASF].val32);
+ if (!rate)
+ return -ERANGE;
+
+ switch (private) {
+ case AD9910_ROC:
+ step = ad9910_rational_scale(step, 4 * rate,
+ st->data.sysclk_freq_hz);
+ step = min(step, AD9910_ASF_MAX);
+ idx = find_closest(step, ad9910_osk_raw_step,
+ ARRAY_SIZE(ad9910_osk_raw_step));
+ if (idx == ARRAY_SIZE(ad9910_osk_raw_step) - 1) {
+ /* manual mode: pin-controlled */
+ reg_val = AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK;
+ } else if (idx == 0) {
+ /* manual mode, NOT pin-controlled */
+ reg_val = 0;
+ } else {
+ /* auto mode: pin-controlled */
+ reg_val = FIELD_PREP(AD9910_ASF_STEP_SIZE_MSK, idx - 1);
+ ret = ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_STEP_SIZE_MSK,
+ reg_val, false);
+ if (ret)
+ return ret;
+
+ reg_val = AD9910_CFR1_SELECT_AUTO_OSK_MSK;
+ }
+
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SELECT_AUTO_OSK_MSK |
+ AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK,
+ reg_val, true);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return len;
+}
+
+static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
+ {
+ .name = "powerdown",
+ .read = ad9910_ext_info_read,
+ .write = ad9910_ext_info_write,
+ .private = AD9910_POWERDOWN,
+ .shared = IIO_SEPARATE,
+ },
+ { }
+};
+
+static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = {
+ {
+ .name = "dwell_en",
+ .read = ad9910_ext_info_read,
+ .write = ad9910_ext_info_write,
+ .private = AD9910_DWELL_EN,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "raw_roc",
+ .read = ad9910_drg_roc_read,
+ .write = ad9910_drg_roc_write,
+ .private = AD9910_ROC,
+ .shared = IIO_SEPARATE,
+ },
+ { }
+};
+
+static const struct iio_chan_spec_ext_info ad9910_osk_ext_info[] = {
+ {
+ .name = "raw_roc",
+ .read = ad9910_osk_attrs_read,
+ .write = ad9910_osk_attrs_write,
+ .private = AD9910_ROC,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "raw_roc_available",
+ .read = ad9910_osk_attrs_read,
+ .private = AD9910_ROC_AVAIL,
+ .shared = IIO_SEPARATE,
+ },
+ { }
+};
+
+#define AD9910_PROFILE_CHAN(idx) { \
+ .type = IIO_ALTCURRENT, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = AD9910_CHANNEL_PROFILE_ ## idx, \
+ .address = AD9910_CHAN_IDX_PROFILE_ ## idx, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \
+ BIT(IIO_CHAN_INFO_FREQUENCY) | \
+ BIT(IIO_CHAN_INFO_PHASE) | \
+ BIT(IIO_CHAN_INFO_RAW), \
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], \
+}
+
+static const struct iio_chan_spec ad9910_channels[] = {
+ [AD9910_CHAN_IDX_PHY] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PHY,
+ .address = AD9910_CHAN_IDX_PHY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .ext_info = ad9910_phy_ext_info,
+ },
+ [AD9910_CHAN_IDX_PROFILE_0] = AD9910_PROFILE_CHAN(0),
+ [AD9910_CHAN_IDX_PROFILE_1] = AD9910_PROFILE_CHAN(1),
+ [AD9910_CHAN_IDX_PROFILE_2] = AD9910_PROFILE_CHAN(2),
+ [AD9910_CHAN_IDX_PROFILE_3] = AD9910_PROFILE_CHAN(3),
+ [AD9910_CHAN_IDX_PROFILE_4] = AD9910_PROFILE_CHAN(4),
+ [AD9910_CHAN_IDX_PROFILE_5] = AD9910_PROFILE_CHAN(5),
+ [AD9910_CHAN_IDX_PROFILE_6] = AD9910_PROFILE_CHAN(6),
+ [AD9910_CHAN_IDX_PROFILE_7] = AD9910_PROFILE_CHAN(7),
+ [AD9910_CHAN_IDX_PARALLEL_AMP] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL,
+ .address = AD9910_CHAN_IDX_PARALLEL_AMP,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_PARALLEL_PHASE] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL,
+ .address = AD9910_CHAN_IDX_PARALLEL_PHASE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_PARALLEL_FREQ] = {
+ .type = IIO_FREQUENCY,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL,
+ .address = AD9910_CHAN_IDX_PARALLEL_FREQ,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_PARALLEL_POLAR_AMP] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL_POLAR,
+ .address = AD9910_CHAN_IDX_PARALLEL_POLAR_AMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL_POLAR,
+ .address = AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_DRG_FREQ] = {
+ .type = IIO_FREQUENCY,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG,
+ .address = AD9910_CHAN_IDX_DRG_FREQ,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_DRG_PHASE] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG,
+ .address = AD9910_CHAN_IDX_DRG_PHASE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_DRG_AMP] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG,
+ .address = AD9910_CHAN_IDX_DRG_AMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP] = {
+ .type = IIO_FREQUENCY,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_UP,
+ .address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ],
+ },
+ [AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN] = {
+ .type = IIO_FREQUENCY,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
+ .address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ],
+ },
+ [AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_UP,
+ .address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE],
+ },
+ [AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
+ .address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE],
+ },
+ [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_UP,
+ .address = AD9910_CHAN_IDX_DRG_AMP_RAMP_UP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP],
+ },
+ [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
+ .address = AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP],
+ },
+ [AD9910_CHAN_IDX_RAM] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_RAM,
+ .address = AD9910_CHAN_IDX_RAM,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_FREQUENCY) |
+ BIT(IIO_CHAN_INFO_PHASE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_OSK] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_OSK,
+ .address = AD9910_CHAN_IDX_OSK,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_osk_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+};
+
+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_ENABLE:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ if (ad9910_sw_powerdown_get(st)) {
+ *val = 0;
+ } else {
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ *val = (tmp32 == st->profile);
+ }
+ break;
+ case AD9910_CHANNEL_DRG:
+ tmp32 = FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ if (tmp32 == (chan->address - AD9910_CHAN_IDX_DRG_FREQ))
+ *val = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ else
+ *val = 0;
+ break;
+ case AD9910_CHANNEL_RAM:
+ *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;
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_FREQUENCY:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ tmp64 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
+ ad9910_st_profile_val(st, tmp32));
+ break;
+ case AD9910_CHANNEL_RAM:
+ tmp64 = st->reg[AD9910_REG_FTW].val32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ tmp64 *= st->data.sysclk_freq_hz;
+ *val = tmp64 >> 32;
+ *val2 = ((tmp64 & GENMASK_ULL(31, 0)) * MICRO) >> 32;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_PHASE:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
+ ad9910_st_profile_val(st, tmp32));
+ break;
+ case AD9910_CHANNEL_RAM:
+ tmp64 = st->reg[AD9910_REG_POW].val16;
+ break;
+ default:
+ return -EINVAL;
+ }
+ tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16;
+ *val = tmp32 / MICRO;
+ *val2 = tmp32 % MICRO;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ *val = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
+ ad9910_st_profile_val(st, tmp32));
+ return IIO_VAL_INT;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_INT_64;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_INT_64;
+ case AD9910_CHANNEL_OSK:
+ *val = FIELD_GET(AD9910_ASF_SCALE_FACTOR_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ return IIO_VAL_INT;
+ 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;
+ 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;
+ case AD9910_CHANNEL_RAM:
+ 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;
+ }
+ 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;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->address) {
+ case AD9910_CHAN_IDX_PHY:
+ tmp64 = (u64)st->data.output_current_uA *
+ AD9910_NANO_MILLIAMP_PER_MICROAMP;
+ *val = 0;
+ *val2 = tmp64 >> 14;
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_PARALLEL_PHASE:
+ *val = 0;
+ *val2 = AD9910_PI_NANORAD >> 15;
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_PARALLEL_FREQ:
+ tmp32 = FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ tmp64 = (u64)st->data.sysclk_freq_hz << tmp32;
+ tmp64 = ad9910_rational_scale(tmp64, NANO, BIT_ULL(32));
+ *val = div_s64_rem(tmp64, NANO, val2);
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP:
+ tmp64 = (u64)st->data.output_current_uA *
+ AD9910_NANO_MILLIAMP_PER_MICROAMP;
+ *val = 0;
+ *val2 = tmp64 >> 8;
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+ *val = 0;
+ *val2 = AD9910_PI_NANORAD >> 7;
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_DRG_FREQ:
+ tmp64 = ad9910_rational_scale(st->data.sysclk_freq_hz,
+ PICO, BIT_ULL(32));
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_PICO;
+ case AD9910_CHAN_IDX_DRG_PHASE:
+ tmp64 = DIV_U64_ROUND_CLOSEST(AD9910_PI_PICORAD, BIT(31));
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_PICO;
+ case AD9910_CHAN_IDX_DRG_AMP:
+ tmp64 = (u64)st->data.output_current_uA *
+ AD9910_PICO_MILLIAMP_PER_MICROAMP;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64 >> 1, BIT(31));
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_PICO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->address) {
+ case AD9910_CHAN_IDX_PARALLEL_FREQ:
+ tmp64 = (u64)st->reg[AD9910_REG_FTW].val32 * MICRO;
+ tmp64 >>= FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_MICRO;
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP:
+ tmp32 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ iio_val_s64_decompose(MICRO * tmp32 >> 6, val, val2);
+ return IIO_VAL_DECIMAL64_MICRO;
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+ tmp32 = FIELD_GET(AD9910_POW_PP_LSB_MSK,
+ st->reg[AD9910_REG_POW].val16);
+ iio_val_s64_decompose(MICRO * tmp32 >> 8, val, val2);
+ return IIO_VAL_DECIMAL64_MICRO;
+ 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;
+ int ret, i;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_ENABLE:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ if (!val) {
+ if (tmp32 != st->profile)
+ return 0; /* nothing to do */
+
+ return ad9910_sw_powerdown_set(st, true);
+ }
+
+ ret = ad9910_sw_powerdown_set(st, false);
+ if (ret)
+ return ret;
+
+ return ad9910_profile_set(st, tmp32);
+ case AD9910_CHANNEL_DRG:
+ tmp32 = chan->address - AD9910_CHAN_IDX_DRG_FREQ;
+ if (val) {
+ tmp32 = AD9910_CFR2_DRG_ENABLE_MSK |
+ FIELD_PREP(AD9910_CFR2_DRG_DEST_MSK, tmp32);
+ } else {
+ if (tmp32 != FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32))
+ return 0; /* nothing to do */
+ tmp32 = 0;
+ }
+
+ return ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_DRG_DEST_MSK |
+ AD9910_CFR2_DRG_ENABLE_MSK,
+ tmp32, true);
+ case AD9910_CHANNEL_RAM:
+ if (AD9910_RAM_ENABLED(st) == !!val)
+ return 0;
+
+ /* swap 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)
+ break;
+ st->reg_profile[i] = tmp64;
+ }
+
+ if (ret) {
+ /*
+ * After the write failure, profiles 0..i-1 were
+ * already swapped in SW, but Hw registers are
+ * still pending an IO update, so swap them back
+ * in SW to keep the state consistent.
+ */
+ while (i--) {
+ tmp64 = st->reg[AD9910_REG_PROFILE(i)].val64;
+ st->reg[AD9910_REG_PROFILE(i)].val64 = st->reg_profile[i];
+ st->reg_profile[i] = tmp64;
+ }
+ return ret;
+ }
+
+ tmp32 = FIELD_PREP(AD9910_CFR1_RAM_ENABLE_MSK, !!val);
+ 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;
+ }
+ case IIO_CHAN_INFO_FREQUENCY:
+ if (val < 0 || val2 < 0 || val >= 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_t(u64, tmp64, U32_MAX);
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ if (AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_ST_FTW_MSK,
+ &st->reg_profile[tmp32], tmp64);
+ return 0;
+ }
+ tmp64 = FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
+ AD9910_PROFILE_ST_FTW_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_RAM:
+ return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_PHASE:
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ tmp64 = (u64)val * MICRO + val2;
+ if (tmp64 >= AD9910_MAX_PHASE_MICRORAD)
+ return -EINVAL;
+
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64 << 16, AD9910_MAX_PHASE_MICRORAD);
+ tmp64 = min(tmp64, AD9910_POW_MAX);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+
+ if (AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_ST_POW_MSK,
+ &st->reg_profile[tmp32], tmp64);
+ return 0;
+ }
+
+ tmp64 = FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
+ AD9910_PROFILE_ST_POW_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_RAM:
+ return ad9910_reg16_write(st, AD9910_REG_POW, tmp64, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ if (val < 0)
+ return -EINVAL;
+
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ tmp64 = min_t(u64, val, AD9910_ASF_MAX);
+
+ if (AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_ST_ASF_MSK,
+ &st->reg_profile[tmp32], tmp64);
+ return 0;
+ }
+
+ tmp64 = FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
+ AD9910_PROFILE_ST_ASF_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_DRG_RAMP_UP: {
+ s64 val64 = iio_val_s64_compose(val, val2);
+
+ if (val64 < 0)
+ return -EINVAL;
+
+ tmp64 = min_t(u64, val64, 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: {
+ s64 val64 = iio_val_s64_compose(val, val2);
+
+ if (val64 < 0)
+ return -EINVAL;
+
+ tmp64 = min_t(u64, val64, 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);
+ }
+ case AD9910_CHANNEL_OSK:
+ if (val < 0)
+ return -EINVAL;
+
+ tmp32 = min_t(u32, val, 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;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (chan->channel == AD9910_CHANNEL_PHY)
+ return ad9910_set_sysclk_freq(st, val, true);
+
+ if (val < 0 || val2 < 0 || val > st->data.sysclk_freq_hz / 4)
+ return -EINVAL;
+
+ 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);
+ 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);
+ 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;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->address) {
+ case AD9910_CHAN_IDX_PHY:
+ if (val != 0 || val2 < 0)
+ return -EINVAL;
+
+ tmp32 = DIV_U64_ROUND_CLOSEST((u64)val2 << 14,
+ AD9910_NANO_MILLIAMP_PER_MICROAMP);
+ return ad9910_set_dac_current(st, tmp32, true);
+ case AD9910_CHAN_IDX_PARALLEL_FREQ:
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ tmp64 = ad9910_rational_scale((u64)val * NANO + val2, BIT_ULL(32),
+ (u64)st->data.sysclk_freq_hz * NANO);
+ tmp64 = roundup_pow_of_two(clamp(tmp64, 1ULL, BIT_ULL(15)));
+ tmp32 = FIELD_PREP(AD9910_CFR2_FM_GAIN_MSK, ilog2(tmp64));
+ return ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_FM_GAIN_MSK,
+ tmp32, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET: {
+ s64 val64 = iio_val_s64_compose(val, val2);
+
+ if (val64 < 0)
+ return -EINVAL;
+
+ tmp64 = val64;
+ switch (chan->address) {
+ case AD9910_CHAN_IDX_PARALLEL_FREQ:
+ tmp32 = BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
+ st->reg[AD9910_REG_CFR2].val32));
+ tmp64 = ad9910_rational_scale(tmp64, tmp32, MICRO);
+ tmp64 = min_t(u64, tmp64, U32_MAX);
+ return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true);
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP:
+ tmp64 = ad9910_rational_scale(tmp64, BIT(6), MICRO);
+ tmp64 = min_t(u64, tmp64, AD9910_ASF_PP_LSB_MAX);
+ tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, tmp64);
+ return ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
+ tmp32, true);
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+ tmp64 = ad9910_rational_scale(tmp64, BIT(8), MICRO);
+ tmp64 = min_t(u64, tmp64, AD9910_POW_PP_LSB_MAX);
+ tmp32 = FIELD_PREP(AD9910_POW_PP_LSB_MSK, tmp64);
+ return ad9910_reg16_update(st, AD9910_REG_POW,
+ AD9910_POW_PP_LSB_MSK,
+ tmp32, true);
+ default:
+ return -EINVAL;
+ }
+ }
+ 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:
+ case IIO_CHAN_INFO_PHASE:
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ case AD9910_CHANNEL_OSK:
+ return IIO_VAL_INT;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ return IIO_VAL_INT_64;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (chan->channel == AD9910_CHANNEL_PHY)
+ return IIO_VAL_INT;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_OFFSET:
+ return IIO_VAL_DECIMAL64_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9910_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, u64 writeval,
+ u64 *readval)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ union ad9910_reg tmp;
+ int ret;
+
+ 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 ... AD9910_REG_PROFILE7:
+ if (!readval)
+ return ad9910_reg64_write(st, reg, writeval, true);
+
+ ret = ad9910_reg64_read(st, reg, &tmp.val64);
+ if (ret)
+ return ret;
+ *readval = tmp.val64;
+ return 0;
+ case AD9910_REG_POW:
+ if (!readval)
+ return ad9910_reg16_write(st, reg, writeval, true);
+
+ ret = ad9910_reg16_read(st, reg, &tmp.val16);
+ if (ret)
+ return ret;
+ *readval = tmp.val16;
+ return 0;
+ default:
+ if (!readval)
+ return ad9910_reg32_write(st, reg, writeval, true);
+
+ ret = ad9910_reg32_read(st, reg, &tmp.val32);
+ if (ret)
+ return ret;
+ *readval = tmp.val32;
+ return 0;
+ }
+}
+
+static const char * const ad9910_channel_str[] = {
+ [AD9910_CHAN_IDX_PHY] = "phy",
+ [AD9910_CHAN_IDX_PROFILE_0] = "profile0",
+ [AD9910_CHAN_IDX_PROFILE_1] = "profile1",
+ [AD9910_CHAN_IDX_PROFILE_2] = "profile2",
+ [AD9910_CHAN_IDX_PROFILE_3] = "profile3",
+ [AD9910_CHAN_IDX_PROFILE_4] = "profile4",
+ [AD9910_CHAN_IDX_PROFILE_5] = "profile5",
+ [AD9910_CHAN_IDX_PROFILE_6] = "profile6",
+ [AD9910_CHAN_IDX_PROFILE_7] = "profile7",
+ [AD9910_CHAN_IDX_PARALLEL_AMP] = "parallel_amplitude",
+ [AD9910_CHAN_IDX_PARALLEL_PHASE] = "parallel_phase",
+ [AD9910_CHAN_IDX_PARALLEL_FREQ] = "parallel_frequency",
+ [AD9910_CHAN_IDX_PARALLEL_POLAR_AMP] = "parallel_polar_amplitude",
+ [AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE] = "parallel_polar_phase",
+ [AD9910_CHAN_IDX_DRG_FREQ] = "drg_frequency",
+ [AD9910_CHAN_IDX_DRG_PHASE] = "drg_phase",
+ [AD9910_CHAN_IDX_DRG_AMP] = "drg_amplitude",
+ [AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP] = "drg_rising_frequency",
+ [AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN] = "drg_falling_frequency",
+ [AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP] = "drg_rising_phase",
+ [AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN] = "drg_falling_phase",
+ [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = "drg_rising_amplitude",
+ [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = "drg_falling_amplitude",
+ [AD9910_CHAN_IDX_RAM] = "ram",
+ [AD9910_CHAN_IDX_OSK] = "osk",
+};
+
+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 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;
+ const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data;
+ size_t wcount, bcount;
+
+ scoped_guard(mutex, &st->lock) {
+ /* cancel requests before prepare() are ignored */
+ st->ram_fwu_cancel = false;
+ }
+
+ if (size < sizeof(struct ad9910_ram_fw))
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+
+ if (get_unaligned_be32(&fw_data->magic) != AD9910_RAM_FW_MAGIC)
+ return FW_UPLOAD_ERR_FW_INVALID;
+
+ if (get_unaligned_be16(&fw_data->version) != AD9910_RAM_FW_V1)
+ return FW_UPLOAD_ERR_FW_INVALID;
+
+ wcount = get_unaligned_be16(&fw_data->wcount);
+ bcount = size - sizeof(struct ad9910_ram_fw);
+ if (wcount > AD9910_RAM_SIZE_MAX_WORDS ||
+ bcount != (wcount * AD9910_RAM_WORD_SIZE))
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+
+ bcount += sizeof(fw_data->cfr1) + sizeof(fw_data->profiles);
+ if (crc32(0, &fw_data->cfr1, bcount) != get_unaligned_be32(&fw_data->crc))
+ return FW_UPLOAD_ERR_FW_INVALID;
+
+ 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)
+{
+ const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data;
+ struct ad9910_state *st = fw_upload->dd_handle;
+ int ret, ret2, idx, wcount;
+ u64 tmp64, backup;
+
+ 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;
+
+ for (idx = 0; idx < AD9910_NUM_PROFILES; idx++)
+ st->reg_profile[idx] = get_unaligned_be64(&fw_data->profiles[idx]) |
+ AD9910_PROFILE_RAM_OPEN_MSK;
+
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_RAM_PLAYBACK_DEST_MSK |
+ AD9910_CFR1_INT_PROFILE_CTL_MSK,
+ get_unaligned_be32(&fw_data->cfr1), true);
+ if (ret)
+ return FW_UPLOAD_ERR_RW_ERROR;
+
+ wcount = get_unaligned_be16(&fw_data->wcount);
+ if (!wcount) {
+ *written = size;
+ return FW_UPLOAD_ERR_NONE; /* nothing else to write */
+ }
+
+ ret = ad9910_profile_set(st, st->profile);
+ if (ret)
+ return FW_UPLOAD_ERR_HW_ERROR;
+
+ /* backup profile register and update it with required address range */
+ 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, wcount - 1);
+ ret = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), tmp64, true);
+ if (ret)
+ return FW_UPLOAD_ERR_RW_ERROR;
+
+ /* copy data words into spi buffer and transfer */
+ memcpy(&st->tx_buf[1], fw_data->words, wcount * AD9910_RAM_WORD_SIZE);
+ ret = ad9910_spi_write(st, AD9910_REG_RAM,
+ wcount * AD9910_RAM_WORD_SIZE, false);
+
+ /* restore active single tone profile regardless */
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64 = backup;
+ 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 void ad9910_ram_fwu_unregister(void *data)
+{
+ firmware_upload_unregister(data);
+}
+
+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,
+ .write_raw_get_fmt = ad9910_write_raw_get_fmt,
+ .read_label = ad9910_read_label,
+ .debugfs_reg64_access = &ad9910_debugfs_reg_access,
+};
+
+static int ad9910_cfg_sysclk(struct ad9910_state *st, bool update)
+{
+ u32 cfr3 = AD9910_CFR3_OPEN_MSK;
+ u32 tmp32;
+
+ cfr3 |= 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_MAX_SYSCLK_HZ, update);
+}
+
+static int ad9910_parse_fw(struct ad9910_state *st)
+{
+ static const char * const refclk_out_drv0[] = {
+ "disabled", "low", "medium", "high",
+ };
+ struct device *dev = &st->spi->dev;
+ const char *prop;
+ u32 tmp;
+ int ret;
+
+ st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable");
+ if (st->data.pll_enabled) {
+ prop = "adi,charge-pump-current-microamp";
+ if (device_property_present(dev, prop)) {
+ ret = device_property_read_u32(dev, prop, &tmp);
+ if (ret)
+ return dev_err_probe(dev, ret, "property read: %s\n", prop);
+
+ if (tmp < AD9910_ICP_MIN_uA || tmp > AD9910_ICP_MAX_uA)
+ return dev_err_probe(dev, -ERANGE,
+ "invalid charge pump current %u\n", tmp);
+ } else {
+ tmp = AD9910_ICP_MIN_uA;
+ }
+ st->data.pll_charge_pump_current = tmp;
+
+ prop = "adi,refclk-out-drive-strength";
+ if (device_property_present(dev, prop)) {
+ ret = device_property_match_property_string(dev, prop,
+ refclk_out_drv0,
+ ARRAY_SIZE(refclk_out_drv0));
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "property read: %s\n", prop);
+
+ st->data.refclk_out_drv = ret;
+ }
+ }
+
+ return 0;
+}
+
+static void ad9910_sw_powerdown_action(void *data)
+{
+ ad9910_sw_powerdown_set(data, true);
+}
+
+static void ad9910_hw_powerdown_action(void *data)
+{
+ struct ad9910_state *st = data;
+
+ gpiod_set_value_cansleep(st->gpio_pwdown, 1);
+}
+
+static int ad9910_setup(struct device *dev, struct ad9910_state *st,
+ struct reset_control *dev_rst)
+{
+ int ret;
+
+ ret = reset_control_assert(dev_rst);
+ if (ret)
+ return ret;
+
+ fsleep(AD9910_RESET_DELAY_us);
+
+ ret = reset_control_deassert(dev_rst);
+ if (ret)
+ return ret;
+
+ ret = ad9910_reg32_write(st, AD9910_REG_CFR1,
+ (st->spi->mode & SPI_3WIRE ? 0 :
+ AD9910_CFR1_SDIO_INPUT_ONLY_MSK), false);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, ad9910_sw_powerdown_action, st);
+ if (ret)
+ return ret;
+
+ ret = ad9910_reg32_write(st, AD9910_REG_CFR2,
+ AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK |
+ 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, false);
+ if (ret)
+ return ret;
+
+ ret = ad9910_cfg_sysclk(st, false);
+ if (ret)
+ return ret;
+
+ ret = ad9910_set_dac_current(st, AD9910_DAC_IOUT_DEFAULT_uA, false);
+ if (ret)
+ return ret;
+
+ /* configure step rate with default values */
+ ret = ad9910_reg32_write(st, AD9910_REG_ASF,
+ FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, 1),
+ false);
+ if (ret)
+ return ret;
+
+ ret = ad9910_reg32_write(st, AD9910_REG_DRG_RATE,
+ FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) |
+ FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1),
+ false);
+ if (ret)
+ return ret;
+
+ for (unsigned 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);
+}
+
+static inline const char *ad9910_frequency_source_get(struct ad9910_state *st)
+{
+ bool ram_en, mode_en;
+
+ guard(mutex)(&st->lock);
+
+ /* RAM enabled and data destination is frequency */
+ ram_en = AD9910_RAM_ENABLED(st);
+ if (ram_en && AD9910_DEST_FREQUENCY ==
+ FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK,
+ st->reg[AD9910_REG_CFR1].val32))
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+
+ /* DRG enabled and data destination is frequency */
+ mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ if (mode_en && AD9910_DEST_FREQUENCY ==
+ FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32))
+ return ad9910_channel_str[AD9910_CHAN_IDX_DRG_FREQ];
+
+ /* FTW: RAM enabled and data destination is phase, amplitude, or polar */
+ if (ram_en)
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+
+ /* single tone profiles */
+ return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile];
+}
+
+static int ad9910_frequency_source_show(struct seq_file *s, void *ignored)
+{
+ seq_printf(s, "%s\n", ad9910_frequency_source_get(s->private));
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ad9910_frequency_source);
+
+static inline const char *ad9910_phase_source_get(struct ad9910_state *st)
+{
+ bool ram_en, mode_en;
+ u32 destination;
+
+ guard(mutex)(&st->lock);
+
+ /* RAM enabled and data destination is phase or polar */
+ ram_en = AD9910_RAM_ENABLED(st);
+ if (ram_en) {
+ destination = FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (destination == AD9910_DEST_PHASE ||
+ destination == AD9910_DEST_POLAR)
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+ }
+
+ /* DRG enabled and data destination is phase */
+ mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ if (mode_en && AD9910_DEST_PHASE ==
+ FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32))
+ return ad9910_channel_str[AD9910_CHAN_IDX_DRG_PHASE];
+
+ /* POW: RAM enabled and data destination is frequency or amplitude */
+ if (ram_en)
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+
+ /* single tone profiles */
+ return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile];
+}
+
+static int ad9910_phase_source_show(struct seq_file *s, void *ignored)
+{
+ seq_printf(s, "%s\n", ad9910_phase_source_get(s->private));
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ad9910_phase_source);
+
+static inline const char *ad9910_amplitude_source_get(struct ad9910_state *st)
+{
+ bool ram_en, mode_en;
+ u32 destination;
+
+ guard(mutex)(&st->lock);
+
+ /* OSK enabled */
+ mode_en = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (mode_en)
+ return ad9910_channel_str[AD9910_CHAN_IDX_OSK];
+
+ /* RAM enabled and data destination is amplitude or polar */
+ ram_en = AD9910_RAM_ENABLED(st);
+ if (ram_en) {
+ destination = FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (destination == AD9910_DEST_AMPLITUDE ||
+ destination == AD9910_DEST_POLAR)
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+ }
+
+ /* DRG enabled and data destination is amplitude */
+ mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ if (mode_en && AD9910_DEST_AMPLITUDE ==
+ FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32))
+ return ad9910_channel_str[AD9910_CHAN_IDX_DRG_AMP];
+
+ /* only way to control amplitude at this point is through OSK */
+ if (ram_en)
+ return ad9910_channel_str[AD9910_CHAN_IDX_OSK];
+
+ /* single tone profiles */
+ return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile];
+}
+
+static int ad9910_amplitude_source_show(struct seq_file *s, void *ignored)
+{
+ seq_printf(s, "%s\n", ad9910_amplitude_source_get(s->private));
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ad9910_amplitude_source);
+
+static inline void ad9910_debugfs_init(struct ad9910_state *st,
+ struct iio_dev *indio_dev)
+{
+ struct dentry *d = iio_get_debugfs_dentry(indio_dev);
+
+ debugfs_create_file("frequency_source", 0400, d, st,
+ &ad9910_frequency_source_fops);
+ debugfs_create_file("phase_source", 0400, d, st,
+ &ad9910_phase_source_fops);
+ debugfs_create_file("amplitude_source", 0400, d, st,
+ &ad9910_amplitude_source_fops);
+}
+
+static int ad9910_probe(struct spi_device *spi)
+{
+ static const char * const supplies[] = {
+ "dvdd-io33", "avdd33", "dvdd18", "avdd18",
+ };
+ struct device *dev = &spi->dev;
+ struct reset_control *dev_rst;
+ struct gpio_desc *io_rst_gpio;
+ 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;
+
+ 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);
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supplies), supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get regulators\n");
+
+ 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");
+
+ 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");
+
+ /*
+ * 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_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");
+
+ if (st->gpio_profile && st->gpio_profile->ndescs != 3)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid number of profile gpios\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");
+
+ ret = devm_add_action_or_reset(dev, ad9910_hw_powerdown_action, st);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to add hw powerdown action\n");
+
+ fsleep(AD9910_WAKEUP_DELAY_us);
+
+ ret = ad9910_parse_fw(st);
+ if (ret)
+ return ret;
+
+ ret = ad9910_setup(dev, st, dev_rst);
+ if (ret)
+ return dev_err_probe(dev, ret, "device setup failed\n");
+
+ 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 ram upload ops\n");
+
+ ret = devm_add_action_or_reset(dev, ad9910_ram_fwu_unregister, st->ram_fwu);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to add ram upload unregister action\n");
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return ret;
+
+ ad9910_debugfs_init(st, indio_dev);
+
+ return 0;
+}
+
+static const struct spi_device_id ad9910_id[] = {
+ { .name = "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");