diff options
Diffstat (limited to 'drivers/iio/frequency')
| -rw-r--r-- | drivers/iio/frequency/Kconfig | 21 | ||||
| -rw-r--r-- | drivers/iio/frequency/Makefile | 1 | ||||
| -rw-r--r-- | drivers/iio/frequency/ad9910.c | 2347 | ||||
| -rw-r--r-- | drivers/iio/frequency/adf4350.c | 2 | ||||
| -rw-r--r-- | drivers/iio/frequency/adf4377.c | 3 |
5 files changed, 2373 insertions, 1 deletions
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig index 90c6304c4bcd..994d199080eb 100644 --- a/drivers/iio/frequency/Kconfig +++ b/drivers/iio/frequency/Kconfig @@ -23,6 +23,27 @@ config AD9523 endmenu +menu "Direct Digital Synthesis" + +config AD9910 + tristate "Analog Devices AD9910 Direct Digital Synthesizer" + depends on SPI + depends on GPIOLIB + select CRC32 + 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. + + 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 53b4d01414d8..e3b1d36ed620 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_ADF41513) += adf41513.o obj-$(CONFIG_ADF4350) += adf4350.o obj-$(CONFIG_ADF4371) += adf4371.o 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"); diff --git a/drivers/iio/frequency/adf4350.c b/drivers/iio/frequency/adf4350.c index 315317d6eec4..ec45c0f65fe3 100644 --- a/drivers/iio/frequency/adf4350.c +++ b/drivers/iio/frequency/adf4350.c @@ -461,7 +461,7 @@ static const struct clk_ops adf4350_clk_ops = { static int adf4350_clk_register(struct adf4350_state *st) { struct spi_device *spi = st->spi; - struct clk_init_data init; + struct clk_init_data init = { }; struct clk *clk; const char *parent_name; int ret; diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c index 7385be62fa6a..52266f83877a 100644 --- a/drivers/iio/frequency/adf4377.c +++ b/drivers/iio/frequency/adf4377.c @@ -1060,6 +1060,9 @@ static int adf4377_probe(struct spi_device *spi) st->regmap = regmap; st->spi = spi; st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODATA; + mutex_init(&st->lock); ret = adf4377_properties_parse(st); |
