| // 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"); |