The ADF4382A is a high performance, ultralow jitter, Frac-N PLL
with integrated VCO ideally suited for LO generation for 5G applications
or data converter clock applications. The high performance
PLL has a figure of merit of -239 dBc/Hz, low 1/f Noise and
high PFD frequency of 625MHz in integer mode that can achieve
ultralow in-band noise and integrated jitter. The ADF4382A can
generate frequencies in a fundamental octave range of 11.5 GHz to
21 GHz, thereby eliminating the need for sub-harmonic filters. The
divide by 2 and 4 output dividers on the part allow frequencies to
be generated from 5.75GHz to 10.5GHz and 2.875GHz to 5.25GHz
respectively.
Signed-off-by: Ciprian Hegbeli <ciprian.hegbeli@analog.com>
---
drivers/iio/frequency/Kconfig | 11 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/adf4382.c | 1825 +++++++++++++++++++++++++++++++
3 files changed, 1837 insertions(+)
create mode 100644 drivers/iio/frequency/adf4382.c
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index 583cbdf4e8cd..c4cf1ed1bc05 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -61,6 +61,17 @@ config ADF4377
To compile this driver as a module, choose M here: the
module will be called adf4377.
+config ADF4382
+ tristate "Analog Devices ADF4382 Microwave Wideband Synthesizer"
+ depends on SPI && COMMON_CLK
+ select REGMAP_SPI
+ help
+ Say yes here to build support for Analog Devices ADF4382 Microwave
+ Wideband Synthesizer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called adf4382.
+
config ADMFM2000
tristate "Analog Devices ADMFM2000 Dual Microwave Down Converter"
depends on GPIOLIB
diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
index 70d0e0b70e80..4852a8748ce4 100644
--- a/drivers/iio/frequency/Makefile
+++ b/drivers/iio/frequency/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_AD9523) += ad9523.o
obj-$(CONFIG_ADF4350) += adf4350.o
obj-$(CONFIG_ADF4371) += adf4371.o
obj-$(CONFIG_ADF4377) += adf4377.o
+obj-$(CONFIG_ADF4382) += adf4382.o
obj-$(CONFIG_ADMFM2000) += admfm2000.o
obj-$(CONFIG_ADMV1013) += admv1013.o
obj-$(CONFIG_ADMV1014) += admv1014.o
diff --git a/drivers/iio/frequency/adf4382.c b/drivers/iio/frequency/adf4382.c
new file mode 100644
index 000000000000..734b5c197328
--- /dev/null
+++ b/drivers/iio/frequency/adf4382.c
@@ -0,0 +1,1825 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ADF4382 Microwave Wideband Synthesizer with Integrated VCO
+ *
+ * Copyright 2022-2024 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+#include <linux/iio/iio.h>
+#include <linux/regmap.h>
+#include <linux/gcd.h>
+#include <linux/math64.h>
+#include <linux/units.h>
+#include <linux/util_macros.h>
+
+/* ADF4382 REG0000 Map */
+#define ADF4382_SOFT_RESET_R_MSK BIT(7)
+#define ADF4382_LSB_FIRST_R_MSK BIT(6)
+#define ADF4382_ADDRESS_ASC_R_MSK BIT(5)
+#define ADF4382_SDO_ACTIVE_R_MSK BIT(4)
+#define ADF4382_SDO_ACTIVE_MSK BIT(3)
+#define ADF4382_ADDRESS_ASC_MSK BIT(2)
+#define ADF4382_LSB_FIRST_MSK BIT(1)
+#define ADF4382_SOFT_RESET_MSK BIT(0)
+
+/* ADF4382_REG0 */
+#define ADF4382_ADDR_ASC_MSK BIT(2)
+#define ADF4382_ADDR_ASC_R_MSK BIT(5)
+#define ADF4382_SDO_ACT_MSK BIT(3)
+#define ADF4382_SDO_ACT_R_MSK BIT(4)
+#define ADF4382_RESET_CMD 0x81
+
+/* ADF4382 REG0000 Bit Definition */
+#define ADF4382_SDO_ACTIVE_SPI_3W 0x0
+#define ADF4382_SDO_ACTIVE_SPI_4W 0x1
+
+#define ADF4382_ADDR_ASC_AUTO_DECR 0x0
+#define ADF4382_ADDR_ASC_AUTO_INCR 0x1
+
+#define ADF4382_LSB_FIRST_MSB 0x0
+#define ADF4382_LSB_FIRST_LSB 0x1
+
+#define ADF4382_SOFT_RESET_N_OP 0x0
+#define ADF4382_SOFT_RESET_EN 0x1
+
+/* ADF4382 REG0001 Map */
+#define ADF4382_SINGLE_INSTR_MSK BIT(7)
+#define ADF4382_MASTER_RB_CTRL_MSK BIT(5)
+
+/* ADF4382 REG0001 Bit Definition */
+#define ADF4382_SPI_STREAM_EN 0x0
+#define ADF4382_SPI_STREAM_DIS 0x1
+
+#define ADF4382_RB_SLAVE_REG 0x0
+#define ADF4382_RB_MASTER_REG 0x1
+
+/* ADF4382 REG0003 Bit Definition */
+#define ADF4382_CHIP_TYPE 0x06
+
+/* ADF4382 REG0004 Bit Definition */
+#define ADF4382_PRODUCT_ID_LSB 0x0005
+
+/* ADF4382 REG0005 Bit Definition */
+#define ADF4382_PRODUCT_ID_MSB 0x0005
+
+/* ADF4382 REG000A Map */
+#define ADF4382_SCRATCHPAD_MSK GENMASK(7, 0)
+
+/* ADF4382 REG000C Bit Definition */
+#define ADF4382_VENDOR_ID_LSB 0x56
+
+/* ADF4382 REG000D Bit Definition */
+#define ADF4382_VENDOR_ID_MSB 0x04
+
+/* ADF4382 REG000F Bit Definition */
+#define ADF4382_M_S_TRANSF_BIT_MSK BIT(0)
+
+/* ADF4382 REG0010 Map*/
+#define ADF4382_N_INT_LSB_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0011 Map*/
+#define ADF4382_CLKOUT_DIV_MSK GENMASK(7, 5)
+#define ADF4382_INV_CLK_OUT_MSK BIT(4)
+#define ADF4382_N_INT_MSB_MSK GENMASK(3, 0)
+
+/* ADF4382 REG0015 Map */
+#define ADF4382_M_VCO_BAND_LSB_MSK BIT(7)
+#define ADF4382_M_VCO_CORE_MSK BIT(6)
+#define ADF4382_BIAS_DEC_MODE_MSK GENMASK(5, 3)
+#define ADF4382_INT_MODE_MSK BIT(2)
+#define ADF4382_PFD_POL_MSK BIT(1)
+#define ADF4382_FRAC1WORD_MSB BIT(0)
+
+/* ADF4382 REG0016 Map */
+#define ADF4382_M_VCO_BAND_MSB_MSK GENMASK(7, 0)
+
+/* ADF4382 REG001D Map */
+#define ADF4382_BLEED_I_LSB_MSK GENMASK(7, 0)
+
+/* ADF4382 REG001E Map */
+#define ADF4382_EN_PHASE_RESYNC_MSK BIT(7)
+#define ADF4382_EN_REF_RST_MSK BIT(6)
+#define ADF4382_TIMED_SYNC_MSK BIT(5)
+#define ADF4382_BLEED_I_MSB_MSK GENMASK(4, 0)
+
+/* ADF4382 REG001F Map */
+#define ADF4382_SW_SYNC_MSK BIT(7)
+#define ADF4382_SPARE_1F_MSK BIT(6)
+#define ADF4382_BLEED_POL_MSK BIT(5)
+#define ADF4382_EN_BLEED_MSK BIT(4)
+#define ADF4382_CP_I_MSK GENMASK(3, 0)
+
+/* ADF4382 REG0020 Map */
+#define ADF4382_EN_AUTOCAL_MSK BIT(7)
+#define ADF4382_EN_RDBLR_MSK BIT(6)
+#define ADF4382_R_DIV_MSK GENMASK(5, 0)
+
+/* ADF4382 REG0021 Map */
+#define ADF4382_PHASE_WORD_LSB_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0022 Map */
+#define ADF4382_PHASE_WORD_MID_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0023 Map */
+#define ADF4382_PHASE_WORD_MSB_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0024 Map */
+#define ADF4382_SPARE_24_MSK GENMASK(7, 5)
+#define ADF4382_DCLK_DIV_SEL_MSK BIT(4)
+#define ADF4382_DNCLK_DIV1_MSK GENMASK(3, 2)
+#define ADF4382_DCLK_DIV1_MSK GENMASK(1, 0)
+
+/* ADF4382 REG0025 Map */
+#define ADF4382_RESYNC_WAIT_LSB_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0026 Map */
+#define ADF4382_RESYNC_WAIT_MSB_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0027 Map */
+#define ADF4382_CAL_BLEED_FINE_MIN_MSK GENMASK(7, 4)
+#define ADF4382_BLEED_ADJ_SCALE_MSK GENMASK(3, 0)
+
+/* ADF4382 REG0028 Map */
+#define ADF4382_PH_RESYNC_RB_SEL_MSK BIT(7)
+#define ADF4382_LSB_P1_MSK BIT(6)
+#define ADF4382_VAR_MOD_EN_MSK BIT(5)
+#define ADF4382_DITHER1_SCALE_MSK GENMASK(4, 2)
+#define ADF4382_EN_DITHER2_MSK BIT(1)
+#define ADF4382_EN_DITHER1_MSK BIT(0)
+
+/* ADF4382 REG0029 Map */
+#define ADF4382_CLK2_OPWR_MSK GENMASK(7, 4)
+#define ADF4382_CLK1_OPWR_MSK GENMASK(3, 0)
+
+/* ADF4382 REG002A Map */
+#define ADF4382_FN_DBL_MSK BIT(7)
+#define ADF4382_PD_NDIV_TL_MSK BIT(6)
+#define ADF4382_CLKOUT_BST_MSK BIT(5)
+#define ADF4382_PD_SYNC_MSK BIT(4)
+#define ADF4382_PD_CLK_MSK BIT(3)
+#define ADF4382_PD_RDET_MSK BIT(2)
+#define ADF4382_PD_ADC_MSK BIT(1)
+#define ADF4382_PD_CALGEN_MSK BIT(0)
+
+/* ADF4382 REG002B Map */
+#define ADF4382_PD_ALL_MSK BIT(7)
+#define ADF4382_PD_RDIV_TL_MSK BIT(6)
+#define ADF4382_PD_NDIV_MSK BIT(5)
+#define ADF4382_PD_VCO_MSK BIT(4)
+#define ADF4382_PD_LD_MSK BIT(3)
+#define ADF4382_PD_PFDCP_MSK BIT(2)
+#define ADF4382_PD_CLKOUT1_MSK BIT(1)
+#define ADF4382_PD_CLKOUT2_MSK BIT(0)
+
+/* ADF4382 REG002C Map */
+#define ADF4382_LDWIN_PW_MSK GENMASK(7, 4)
+#define ADF4382_LD_COUNT_OPWR_MSK GENMASK(3, 0)
+
+/* ADF4382 REG002D Map */
+#define ADF4382_EN_DNCLK_MSK BIT(7)
+#define ADF4382_EN_DRCLK_MSK BIT(6)
+#define ADF4382_EN_LOL_MSK BIT(5)
+#define ADF4382_EN_LDWIN_MSK BIT(4)
+#define ADF4382_PDET_POL_MSK BIT(3)
+#define ADF4382_RST_LD_MSK BIT(2)
+#define ADF4382_LD_O_CTRL_MSK GENMASK(1, 0)
+
+/* ADF4382 REG002E Map */
+#define ADF4382_MUXOUT_MSK GENMASK(7, 4)
+#define ADF4382_ABPW_WD_MSK BIT(3)
+#define ADF4382_EN_CPTEST_MSK BIT(2)
+#define ADF4382_CP_DOWN_MSK BIT(1)
+#define ADF4382_CP_UP_MSK BIT(0)
+
+/* ADF4382 REG002F Map*/
+#define ADF4382_BST_REF_MSK BIT(7)
+#define ADF4382_FILT_REF_MSK BIT(6)
+#define ADF4382_RDBLR_DC_MSK GENMASK(5, 0)
+
+/* ADF4382 REG0030 Map */
+#define ADF4382_MUTE_NCLK_MSK BIT(7)
+#define ADF4382_MUTE_RCLK_MSK BIT(6)
+#define ADF4382_REF_SEL_MSK BIT(5)
+#define ADF4382_INV_RDBLR_MSK BIT(4)
+#define ADF4382_RDBLR_DEL_SEL_MSK GENMASK(3, 0)
+
+/* ADF4382 REG0031 Map */
+#define ADF4382_SYNC_DEL_MSK GENMASK(7, 5)
+#define ADF4382_RST_SYS_MSK BIT(4)
+#define ADF4382_EN_ADC_CLK_MSK BIT(3)
+#define ADF4382_EN_VCAL_MSK BIT(2)
+#define ADF4382_CAL_CT_SEL_MSK BIT(1)
+#define ADF4382_DCLK_MODE_MSK BIT(0)
+
+/* ADF4382 REG0032 Map */
+#define ADF4382_SPARE_32_MSK BIT(7)
+#define ADF4382_BLEED_ADJ_CAL_MSK BIT(6)
+#define ADF4382_DEL_MODE_MSK BIT(5)
+#define ADF4382_EN_AUTO_ALIGN_MSK BIT(4)
+#define ADF4382_PHASE_ADJ_POL_MSK BIT(3)
+#define ADF4382_EFM3_MODE_MSK GENMASK(2, 0)
+
+/* ADF4382 REG0033 Map */
+#define ADF4382_PHASE_ADJUST_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0034 Map */
+#define ADF4382_PHASE_ADJ_MSK BIT(7)
+#define ADF4382_DRCLK_DEL_MSK GENMASK(6, 4)
+#define ADF4382_DNCLK_DEL_MSK GENMASK(3, 1)
+#define ADF4382_RST_CNTR_MSK BIT(0)
+
+/* ADF4382 REG0035 Map */
+#define ADF4382_SPARE_35_MSK GENMASK(7, 6)
+#define ADF4382_M_VCO_BIAS_MSK GENMASK(5, 0)
+
+/* ADF4382 REG0036 Map */
+#define ADF4382_CLKODIV_DB_MSK BIT(7)
+#define ADF4382_DCLK_DIV_DB_MSK BIT(6)
+#define ADF4382_SPARE_36_MSK GENMASK(5, 2)
+#define ADF4382_EN_LUT_GEN_MSK BIT(1)
+#define ADF4382_EN_LUT_CAL_MSK BIT(0)
+
+/* ADF4382 REG0037 Map */
+#define ADF4382_CAL_COUNT_TO_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0038 Map */
+#define ADF4382_CAL_VTUNE_TO_LSB_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0039 Map */
+#define ADF4382_O_VCO_DB_MSK BIT(7)
+#define ADF4382_CAL_VTUNE_TO_MSB_MSK GENMASK(6, 0)
+
+/* ADF4382 REG003A Map */
+#define ADF4382_CAL_VCO_TO_LSB_MSK GENMASK(7, 0)
+
+/* ADF4382 REG003B Map */
+#define ADF4382_DEL_CTRL_DB_MSK BIT(7)
+#define ADF4382_CAL_VCO_TO_MSB_MSK GENMASK(6, 0)
+
+/* ADF4382 REG003C Map */
+#define ADF4382_CNTR_DIV_WORD_MSK GENMASK(7, 0)
+
+/* ADF4382 REG003D Map */
+#define ADF4382_SPARE_3D_MSK BIT(7)
+#define ADF4382_SYNC_SP_DB_MSK BIT(6)
+#define ADF4382_CMOS_OV_MSK BIT(5)
+#define ADF4382_READ_MODE_MSK BIT(4)
+#define ADF4382_CNTR_DIV_WORD_MSB_MSK GENMASK(3, 0)
+
+/* ADF4382 REG003E Map */
+#define ADF4382_ADC_CLK_DIV_MSK GENMASK(7, 0)
+
+/* ADF4382 REG003F Map */
+#define ADF4382_EN_ADC_CNV_MSK BIT(7)
+#define ADF4382_EN_ADC_VTEST_MSK BIT(6)
+#define ADF4382_ADC_VTEST_SEL_MSK BIT(5)
+#define ADF4382_ADC_MUX_SEL_MSK BIT(4)
+#define ADF4382_ADC_F_CONV_MSK BIT(3)
+#define ADF4382_ADC_C_CONV_MSK BIT(2)
+#define ADF4382_EN_ADC_MSK BIT(1)
+#define ADF4382_SPARE_3F_MSK BIT(0)
+
+/* ADF4382 REG0040 Map */
+#define ADF4382_EXT_DIV_DEC_SEL_MSK BIT(7)
+#define ADF4382_ADC_CLK_TEST_SEL_MSK BIT(6)
+#define ADF4382_MUTE_CLKOUT2_MSK GENMASK(5, 3)
+#define ADF4382_MUTE_CLKOUT1_MSK GENMASK(2, 0)
+
+/* ADF4382 REG0041 Map */
+#define ADF4382_EXT_DIV_MSK GENMASK(7, 5)
+#define ADF4382_EN_VCO_CAP_TEST_MSK BIT(4)
+#define ADF4382_EN_CALGEN_CAP_TEST_MSK BIT(3)
+#define ADF4382_EN_CP_CAP_TEST_MSK BIT(2)
+#define ADF4382_CAP_TEST_STATE_MSK BIT(1)
+#define ADF4382_TRANS_LOOP_SEL_MSK BIT(0)
+
+/* ADF4382 REG0042 Map */
+#define ADF4382_NDIV_PWRUP_TIMEOUT_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0043 Map */
+#define ADF4382_CAL_BLEED_FINE_MAX_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0044 Map */
+#define ADF4382_VCAL_ZERO_MSK BIT(7)
+#define ADF4382_VPTAT_CALGEN_MSK GENMASK(6, 0)
+
+/* ADF4382 REG0045 Map */
+#define ADF4382_SPARE_45_MSK BIT(7)
+#define ADF4382_VCTAT_CALGEN_MSK GENMASK(6, 0)
+
+/* ADF4382 REG0046 Map */
+#define ADF4382_NVMDIN_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0047 Map */
+#define ADF4382_SPARE_47_MSK BIT(7)
+#define ADF4382_NVMADDR_MSK GENMASK(6, 3)
+#define ADF4382_NVMBIT_SEL GENMASK(2, 0)
+
+/* ADF4382 REG0048 Map */
+#define ADF4382_TRIM_LATCH_MSK BIT(7)
+#define ADF4382_NVMTEST_MSK BIT(6)
+#define ADF4382_NVMPROG_MSK BIT(5)
+#define ADF4382_NVMRD_MSK BIT(4)
+#define ADF4382_NVMSTART_MSK BIT(3)
+#define ADF4382_NVMON_MSK BIT(2)
+#define ADF4382_MARGIN_MSK GENMASK(1, 0)
+
+/* ADF4382 REG0049 Map */
+#define ADF4382_NVMDOUT_MSK GENMASK(7, 0)
+
+/* ADF4382 REG004A Map */
+#define ADF4382_SCAN_MODE_CODE_MSK GENMASK(7, 0)
+
+/* ADF4382 REG004B Map */
+#define ADF4382_TEMP_OFFSET_MSK GENMASK(7, 0)
+
+/* ADF4382 REG004C Map */
+#define ADF4382_SPARE_4C_MSK GENMASK(7, 6)
+#define ADF4382_TEMP_SLOPE_MSK GENMASK(5, 0)
+
+/* ADF4382 REG004D Map */
+#define ADF4382_VCO_FSM_TEST_MUX_MSK GENMASK(7, 5)
+#define ADF4382_SPARE_4D_MSK GENMASK(4, 3)
+#define ADF4382_O_VCO_BIAS_MSK BIT(2)
+#define ADF4382_O_VCO_BAND_MSK BIT(1)
+#define ADF4382_O_VCO_CORE_MSK BIT(0)
+
+/* ADF4382 REG004E Map */
+#define ADF4382_EN_TWO_PASS_CALL_MSK BIT(4)
+#define ADF4382_TWO_PASS_BAND_START_MSK GENMASK(3, 0)
+
+/* ADF4382 REG004F Map */
+#define ADF4382_LUT_SCALE_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0050 Map */
+#define ADF4382_SPARE0_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0051 Map */
+#define ADF4382_SPARE1_MSK GENMASK(7, 0)
+
+/* ADF4382 REG0052 Map */
+#define ADF4382_SYNC_REF_SPARE_MSK GENMASK(7, 4)
+#define ADF4382_SYNC_MON_DEL_MSK GENMASK(3, 0)
+
+/* ADF4382 REG0053 Map */
+#define ADF4382_PD_SYNC_MON_MSK BIT(6)
+#define ADF4382_SYNC_SEL_MSK BIT(5)
+#define ADF4382_RST_SYNC_MON_MSK BIT(4)
+#define ADF4382_SYNC_SH_DEL_MSK GENMASK(3, 0)
+
+/* ADF4382 REG0054 Map */
+#define ADF4382_ADC_ST_CNV_MSK BIT(0)
+
+/* ADF4382 REG0058 Map */
+#define ADF4382_PLL_LOCK_MSK BIT(0)
+
+#define ADF4382_MOD2WORD_LSB_MSK GENMASK(7, 0)
+#define ADF4382_MOD2WORD_MID_MSK GENMASK(15, 8)
+#define ADF4382_MOD2WORD_MSB_MSK GENMASK(23, 16)
+
+#define ADF4382_FRAC1WORD_LSB_MSK GENMASK(7, 0)
+#define ADF4382_FRAC1WORD_MID_MSK GENMASK(15, 8)
+#define ADF4382_FRAC1WORD_MSB_MSK GENMASK(23, 16)
+#define ADF4382_FRAC1WORD_MS_BIT_MSK BIT(24)
+
+#define ADF4382_FRAC2WORD_LSB_MSK GENMASK(7, 0)
+#define ADF4382_FRAC2WORD_MID_MSK GENMASK(15, 8)
+#define ADF4382_FRAC2WORD_MSB_MSK GENMASK(23, 16)
+
+#define ADF4382_DEL_CNT_LSB_MSK GENMASK(7, 0)
+#define ADF4382_DEL_CNT_MSB_MSK GENMASK(15, 8)
+
+#define ADF4382_DEL_CNT_FINE_MSK GENMASK(8, 0)
+#define ADF4382_DEL_CNT_COARSE_MSK GENMASK(12, 9)
+#define ADF4382_DEL_CNT_BLEED_POL_MSK BIT(15)
+
+#define ADF4382_REF_MIN 10000000ULL // 10MHz
+#define ADF4382_REF_MAX 5000000000ULL // 5GHz
+#define ADF4382_VCO_FREQ_MIN 11000000000ULL // 11GHz
+#define ADF4382_VCO_FREQ_MAX 22000000000ULL // 22GHz
+#define ADF4382A_VCO_FREQ_MIN 11500000000ULL // 11.5GHz
+#define ADF4382A_VCO_FREQ_MAX 21000000000ULL // 21GHz
+#define ADF4382_PFD_FREQ_MAX 625000000ULL // 625MHz
+#define ADF4382_PFD_FREQ_FRAC_MAX 250000000ULL // 250MHz
+#define ADF4382_PFD_FREQ_MIN 5400000ULL // 5.4MHz
+#define ADF4382_MOD1WORD 0x2000000ULL // 2^25
+#define ADF4382_MOD2WORD_MAX 0xFFFFFFU // 2^24 - 1
+#define ADF4382_PHASE_RESYNC_MOD2WORD_MAX 0x1FFFFU // 2^17 - 1
+#define ADF4382_CHANNEL_SPACING_MAX 78125U
+#define ADF4382_DCLK_DIV1_0_MAX 160000000ULL // 160MHz
+#define ADF4382_DCLK_DIV1_1_MAX 320000000ULL // 320MHz
+#define ADF4382_OUT_PWR_MAX 15
+#define ADF4382_CLKOUT_DIV_REG_VAL_MAX 4
+#define ADF4382A_CLKOUT_DIV_REG_VAL_MAX 2
+
+#define ADF4382_CP_I_DEFAULT 15
+#define ADF4382_OPOWER_DEFAULT 11
+#define ADF4382_REF_DIV_DEFAULT 1
+#define ADF4382_RFOUT_DEFAULT 2875000000ULL // 2.875GHz
+#define ADF4382_SCRATCHPAD_VAL 0xA5
+
+#define ADF4382_PHASE_BLEED_CNST_MUL 511
+#define ADF4382_PHASE_BLEED_CNST_DIV 285
+#define ADF4382_VCO_CAL_CNT 202
+#define ADF4382_VCO_CAL_VTUNE 124
+#define ADF4382_VCO_CAL_ALC 250
+
+#define FS_PER_NS MICRO
+#define NS_PER_MS MICRO
+#define MS_PER_NS MICRO
+#define NS_PER_FS MICRO
+#define PS_PER_NS 1000
+#define UA_PER_A 1000000
+
+#define PERIOD_IN_DEG 360
+#define PERIOD_IN_DEG_MS 360000
+
+#ifdef CONFIG_64BIT
+#define ADF4382_CLK_SCALE 1
+#else
+#define ADF4382_CLK_SCALE 10ULL
+#endif
+
+enum {
+ ADF4382_FREQ,
+ ADF4382_EN_AUTO_ALIGN,
+};
+
+enum {
+ ADF4382,
+ ADF4382A,
+};
+
+struct adf4382_state {
+ struct spi_device *spi;
+ struct regmap *regmap;
+ struct clk *clkin;
+ struct clk *clkout;
+ struct clk_hw clk_hw;
+ /* Protect against concurrent accesses to the device and data content */
+ struct mutex lock;
+ struct notifier_block nb;
+ unsigned int ref_freq_hz;
+ u8 cp_i;
+ u8 opwr_a;
+ u64 freq;
+ bool spi_3wire_en;
+ bool ref_doubler_en;
+ bool auto_align_en;
+ u8 ref_div;
+ u8 clkout_div_reg_val_max;
+ u16 bleed_word;
+ int phase;
+ bool cmos_3v3;
+ u64 vco_max;
+ u64 vco_min;
+};
+
+#define to_adf4382_state(_hw) container_of(_hw, struct adf4382_state, clk_hw)
+
+/* Charge pump current values expressed in uA */
+static const int adf4382_ci_ua[] = {
+ 790, 990, 1190, 1380, 1590, 1980, 2390, 2790, 3180, 3970, 4770, 5570,
+ 6330, 7910, 9510, 11100
+};
+
+static const struct reg_sequence adf4382_reg_default[] = {
+ { 0x00a, 0xA5 }, { 0x200, 0x00 }, { 0x201, 0x00 }, { 0x202, 0x00 },
+ { 0x203, 0x00 }, { 0x203, 0x00 }, { 0x203, 0x00 }, { 0x100, 0x25 },
+ { 0x101, 0x3F }, { 0x102, 0x3F }, { 0x103, 0x3F }, { 0x104, 0x3F },
+ { 0x105, 0x3F }, { 0x106, 0x3F }, { 0x107, 0x3F }, { 0x108, 0x3F },
+ { 0x109, 0x25 }, { 0x10A, 0x25 }, { 0x10B, 0x3F }, { 0x10C, 0x3F },
+ { 0x10D, 0x3F }, { 0x10E, 0x3F }, { 0x10F, 0x3F }, { 0x110, 0x3F },
+ { 0x111, 0x3F }, { 0x054, 0x00 }, { 0x053, 0x45 }, { 0x052, 0x00 },
+ { 0x051, 0x00 }, { 0x050, 0x00 }, { 0x04f, 0x08 }, { 0x04e, 0x06 },
+ { 0x04d, 0x00 }, { 0x04c, 0x2B }, { 0x04b, 0x5D }, { 0x04a, 0x00 },
+ { 0x048, 0x00 }, { 0x047, 0x00 }, { 0x046, 0x00 }, { 0x045, 0x62 },
+ { 0x044, 0x3F }, { 0x043, 0xB8 }, { 0x042, 0x01 }, { 0x041, 0x00 },
+ { 0x040, 0x00 }, { 0x03f, 0x82 }, { 0x03e, 0x4E }, { 0x03c, 0x00 },
+ { 0x03b, 0x00 }, { 0x03a, 0xFA }, { 0x039, 0x00 }, { 0x038, 0x71 },
+ { 0x037, 0x82 }, { 0x036, 0xC0 }, { 0x035, 0x00 }, { 0x034, 0x36 },
+ { 0x033, 0x00 }, { 0x032, 0x40 }, { 0x031, 0x63 }, { 0x030, 0x0F },
+ { 0x02f, 0x3F }, { 0x02e, 0x00 }, { 0x02d, 0xF1 }, { 0x02c, 0x0E },
+ { 0x02b, 0x01 }, { 0x02a, 0x30 }, { 0x029, 0x09 }, { 0x028, 0x00 },
+ { 0x027, 0xF0 }, { 0x026, 0x00 }, { 0x025, 0x01 }, { 0x024, 0x01 },
+ { 0x023, 0x00 }, { 0x022, 0x00 }, { 0x021, 0x00 }, { 0x01e, 0x20 },
+ { 0x01d, 0x00 }, { 0x01c, 0x00 }, { 0x01b, 0x00 }, { 0x01a, 0x00 },
+ { 0x019, 0x00 }, { 0x018, 0x00 }, { 0x017, 0x00 }, { 0x016, 0x00 },
+ { 0x015, 0x06 }, { 0x014, 0x00 }, { 0x013, 0x00 }, { 0x012, 0x00 },
+ { 0x011, 0x00 }, { 0x010, 0x50 }
+};
+
+static const struct regmap_config adf4382_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .read_flag_mask = BIT(7),
+};
+
+static int adf4382_pfd_compute(struct adf4382_state *st, u64 *pfd_freq_hz)
+{
+ u64 tmp;
+
+ tmp = DIV_ROUND_CLOSEST(st->ref_freq_hz, st->ref_div);
+ if (st->ref_doubler_en)
+ tmp *= 2;
+
+ if (tmp < ADF4382_PFD_FREQ_MIN || tmp > ADF4382_PFD_FREQ_MAX)
+ return -EINVAL;
+
+ *pfd_freq_hz = tmp;
+
+ return 0;
+}
+
+static int adf4382_frac2_compute(struct adf4382_state *st, u64 res,
+ unsigned int pfd_freq_hz, u32 *frac2_word,
+ u32 *mod2_word)
+{
+ u32 channel_spacing;
+ u8 en_phase_resync;
+ u32 chsp_freq;
+ u32 mod2_tmp;
+ u32 mod2_max;
+ u32 mod2_wd;
+ u32 gcd_var;
+
+ channel_spacing = 1;
+ mod2_wd = 1;
+
+ en_phase_resync = regmap_test_bits(st->regmap, 0x1E,
+ ADF4382_EN_PHASE_RESYNC_MSK);
+ if (en_phase_resync < 0)
+ return en_phase_resync;
+
+ if (en_phase_resync)
+ mod2_max = ADF4382_PHASE_RESYNC_MOD2WORD_MAX;
+ else
+ mod2_max = ADF4382_MOD2WORD_MAX;
+
+ do {
+ chsp_freq = channel_spacing * ADF4382_MOD1WORD;
+ gcd_var = gcd(chsp_freq, pfd_freq_hz);
+ mod2_tmp = DIV_ROUND_UP(pfd_freq_hz, gcd_var);
+
+ if (mod2_tmp > mod2_max) {
+ channel_spacing *= 5;
+ } else {
+ mod2_wd = mod2_tmp;
+ break;
+ }
+
+ } while (channel_spacing < ADF4382_CHANNEL_SPACING_MAX);
+
+ if (!en_phase_resync)
+ mod2_wd *= DIV_ROUND_DOWN_ULL(mod2_max, mod2_wd);
+
+ *frac2_word = DIV_ROUND_CLOSEST_ULL(res * mod2_wd, pfd_freq_hz);
+ *mod2_word = mod2_wd;
+
+ return 0;
+}
+
+static int adf4382_pll_fract_n_compute(struct adf4382_state *st, unsigned int pfd_freq_hz,
+ u16 *n_int, u32 *frac1_word, u32 *frac2_word,
+ u32 *mod2_word)
+{
+ u64 rem;
+ u64 res;
+
+ *n_int = div64_u64_rem(st->freq, pfd_freq_hz, &rem);
+
+ res = rem * ADF4382_MOD1WORD;
+ *frac1_word = (u32)div64_u64_rem(res, pfd_freq_hz, &rem);
+
+ *frac2_word = 0;
+ *mod2_word = 0;
+
+ if (pfd_freq_hz > ADF4382_PFD_FREQ_FRAC_MAX) {
+ dev_warn(&st->spi->dev, "PFD frequency exceeds 250MHz.");
+ dev_warn(&st->spi->dev, "Only integer mode available.");
+ }
+
+ if (rem > 0)
+ return adf4382_frac2_compute(st, rem, pfd_freq_hz, frac2_word,
+ mod2_word);
+
+ return 0;
+}
+
+static int _adf4382_set_freq(struct adf4382_state *st)
+{
+ u32 frac2_word = 0;
+ u32 mod2_word = 0;
+ u64 pfd_freq_hz;
+ u32 frac1_word;
+ u8 clkout_div;
+ u32 read_val;
+ u8 dclk_div1;
+ u8 int_mode;
+ u8 en_bleed;
+ u8 ldwin_pw;
+ u16 n_int;
+ u8 div1;
+ u64 tmp;
+ u64 vco;
+ int ret;
+ u8 var;
+
+ ret = adf4382_pfd_compute(st, &pfd_freq_hz);
+ if (ret) {
+ dev_err(&st->spi->dev, "PFD frequency is out of range.\n");
+ return ret;
+ }
+
+ for (clkout_div = 0; clkout_div <= st->clkout_div_reg_val_max; clkout_div++) {
+ tmp = (1 << clkout_div) * st->freq;
+ if (tmp < st->vco_min || tmp > st->vco_max)
+ continue;
+
+ vco = tmp;
+ break;
+ }
+
+ if (vco == 0) {
+ dev_err(&st->spi->dev, "Output frequency is out of range.\n");
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = adf4382_pll_fract_n_compute(st, pfd_freq_hz, &n_int, &frac1_word,
+ &frac2_word, &mod2_word);
+ if (ret)
+ return ret;
+
+ if (frac1_word || frac2_word) {
+ int_mode = 0;
+ en_bleed = 1;
+
+ if (pfd_freq_hz <= (40 * HZ_PER_MHZ)) {
+ ldwin_pw = 7;
+ } else if (pfd_freq_hz <= (50 * HZ_PER_MHZ)) {
+ ldwin_pw = 6;
+ } else if (pfd_freq_hz <= (100 * HZ_PER_MHZ)) {
+ ldwin_pw = 5;
+ } else if (pfd_freq_hz <= (200 * HZ_PER_MHZ)) {
+ ldwin_pw = 4;
+ } else if (pfd_freq_hz <= (250 * HZ_PER_MHZ)) {
+ if (st->freq >= (5000U * HZ_PER_MHZ) &&
+ st->freq < (6400U * HZ_PER_MHZ)) {
+ ldwin_pw = 3;
+ } else {
+ ldwin_pw = 2;
+ }
+ }
+ } else {
+ int_mode = 1;
+ en_bleed = 0;
+
+ tmp = DIV_ROUND_UP_ULL(pfd_freq_hz, UA_PER_A);
+ tmp *= adf4382_ci_ua[st->cp_i];
+ tmp = DIV_ROUND_UP_ULL(st->bleed_word, tmp);
+ if (tmp <= 85)
+ ldwin_pw = 0;
+ else
+ ldwin_pw = 1;
+ }
+
+ dev_dbg(&st->spi->dev,
+ "VCO=%llu PFD=%llu RFout_div=%u N=%u FRAC1=%u FRAC2=%u MOD2=%u\n",
+ vco, pfd_freq_hz, 1 << clkout_div, n_int,
+ frac1_word, frac2_word, mod2_word);
+
+ ret = regmap_update_bits(st->regmap, 0x28, ADF4382_VAR_MOD_EN_MSK,
+ frac2_word != 0 ? 0xff : 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, 0x15, ADF4382_INT_MODE_MSK,
+ FIELD_PREP(ADF4382_INT_MODE_MSK, int_mode));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x1D,
+ FIELD_GET(ADF4382_BLEED_I_LSB_MSK, st->bleed_word));
+ if (ret)
+ return ret;
+
+ var = (st->bleed_word >> 8) & ADF4382_BLEED_I_MSB_MSK;
+ ret = regmap_update_bits(st->regmap, 0x1E, ADF4382_BLEED_I_MSB_MSK, var);
+ if (ret)
+ return ret;
+ ret = regmap_update_bits(st->regmap, 0x1F, ADF4382_EN_BLEED_MSK,
+ FIELD_PREP(ADF4382_EN_BLEED_MSK, en_bleed));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x1A,
+ FIELD_GET(ADF4382_MOD2WORD_LSB_MSK, mod2_word));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x1B,
+ FIELD_GET(ADF4382_MOD2WORD_MID_MSK, mod2_word));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x1C,
+ FIELD_GET(ADF4382_MOD2WORD_MSB_MSK, mod2_word));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x12,
+ FIELD_GET(ADF4382_FRAC1WORD_LSB_MSK, frac1_word));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x13,
+ FIELD_GET(ADF4382_FRAC1WORD_MID_MSK, frac1_word));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x14,
+ FIELD_GET(ADF4382_FRAC1WORD_MSB_MSK, frac1_word));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, 0x15, ADF4382_FRAC1WORD_MSB,
+ FIELD_GET(ADF4382_FRAC1WORD_MS_BIT_MSK, frac1_word));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x17,
+ FIELD_GET(ADF4382_FRAC2WORD_LSB_MSK, frac2_word));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x18,
+ FIELD_GET(ADF4382_FRAC2WORD_MID_MSK, frac2_word));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x19,
+ FIELD_GET(ADF4382_FRAC2WORD_MSB_MSK, frac2_word));
+ if (ret)
+ return ret;
+
+ dclk_div1 = 2;
+ div1 = 8;
+ if (pfd_freq_hz <= ADF4382_DCLK_DIV1_0_MAX) {
+ dclk_div1 = 0;
+ div1 = 1;
+ } else if (pfd_freq_hz <= ADF4382_DCLK_DIV1_1_MAX) {
+ dclk_div1 = 1;
+ div1 = 2;
+ }
+
+ ret = regmap_update_bits(st->regmap, 0x24, ADF4382_DCLK_DIV1_MSK,
+ FIELD_PREP(ADF4382_DCLK_DIV1_MSK, dclk_div1));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, 0x31, ADF4382_DCLK_MODE_MSK, 0xff);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, 0x31, ADF4382_CAL_CT_SEL_MSK, 0xff);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x38, ADF4382_VCO_CAL_VTUNE);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x3a, ADF4382_VCO_CAL_ALC);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x37, ADF4382_VCO_CAL_CNT);
+ if (ret)
+ return ret;
+
+ var = DIV_ROUND_UP(div_u64(pfd_freq_hz, div1 * 400000) - 2, 4);
+ var = clamp_t(u8, var, 0U, 255U);
+ ret = regmap_write(st->regmap, 0x3e, var);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, 0x2c, ADF4382_LD_COUNT_OPWR_MSK,
+ 10);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, 0x2c, ADF4382_LDWIN_PW_MSK,
+ FIELD_PREP(ADF4382_LDWIN_PW_MSK, ldwin_pw));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, 0x11, ADF4382_CLKOUT_DIV_MSK,
+ FIELD_PREP(ADF4382_CLKOUT_DIV_MSK, clkout_div));
+ if (ret)
+ return ret;
+
+ var = (n_int >> 8) & ADF4382_N_INT_MSB_MSK;
+ ret = regmap_update_bits(st->regmap, 0x11, ADF4382_N_INT_MSB_MSK, var);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x10,
+ FIELD_PREP(ADF4382_N_INT_LSB_MSK, n_int));
+ if (ret)
+ return ret;
+
+ mdelay(1);
+
+ ret = regmap_read(st->regmap, 0x58, &read_val);
+ if (ret)
+ return ret;
+
+ if (!FIELD_GET(ADF4382_PLL_LOCK_MSK, read_val)) {
+ dev_err(&st->spi->dev, "PLL is not locked.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int adf4382_set_freq(struct adf4382_state *st)
+{
+ int ret;
+
+ mutex_lock(&st->lock);
+ ret = _adf4382_set_freq(st);
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int adf4382_get_freq(struct adf4382_state *st, u64 *val)
+{
+ unsigned int tmp;
+ u32 frac1 = 0;
+ u32 frac2 = 0;
+ u32 mod2 = 0;
+ u64 freq;
+ u64 pfd;
+ u16 n;
+ int ret;
+
+ ret = adf4382_pfd_compute(st, &pfd);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, 0x11, &tmp);
+ if (ret)
+ return ret;
+
+ n = FIELD_PREP(ADF4382_N_INT_MSB_MSK, tmp);
+ n = n << 8;
+
+ ret = regmap_read(st->regmap, 0x10, &tmp);
+ if (ret)
+ return ret;
+ n |= FIELD_PREP(ADF4382_N_INT_LSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x15, &tmp);
+ if (ret)
+ return ret;
+ frac1 |= FIELD_PREP(ADF4382_FRAC1WORD_MS_BIT_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x14, &tmp);
+ if (ret)
+ return ret;
+ frac1 |= FIELD_PREP(ADF4382_FRAC1WORD_MSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x13, &tmp);
+ if (ret)
+ return ret;
+ frac1 |= FIELD_PREP(ADF4382_FRAC1WORD_MID_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x12, &tmp);
+ if (ret)
+ return ret;
+ frac1 |= FIELD_PREP(ADF4382_FRAC1WORD_LSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x19, &tmp);
+ if (ret)
+ return ret;
+ frac2 |= FIELD_PREP(ADF4382_FRAC2WORD_MSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x18, &tmp);
+ if (ret)
+ return ret;
+ frac2 |= FIELD_PREP(ADF4382_FRAC2WORD_MID_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x17, &tmp);
+ if (ret)
+ return ret;
+ frac2 |= FIELD_PREP(ADF4382_FRAC2WORD_LSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x1c, &tmp);
+ if (ret)
+ return ret;
+ mod2 |= FIELD_PREP(ADF4382_MOD2WORD_MSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x1b, &tmp);
+ if (ret)
+ return ret;
+ mod2 |= FIELD_PREP(ADF4382_MOD2WORD_MID_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x1a, &tmp);
+ if (ret)
+ return ret;
+ mod2 |= FIELD_PREP(ADF4382_MOD2WORD_LSB_MSK, tmp);
+
+ if (mod2 == 0)
+ mod2 = 1;
+
+ freq = frac2 * pfd;
+ freq = div_u64(freq, mod2);
+ freq = freq + (frac1 * pfd);
+ freq = div_u64(freq, ADF4382_MOD1WORD);
+ freq = freq + (n * pfd);
+
+ *val = freq;
+ return 0;
+}
+
+static int adf4382_set_phase_adjust(struct adf4382_state *st, u32 phase_fs)
+{
+ u8 phase_reg_value;
+ u64 phase_deg_fs;
+ u64 phase_deg_ns;
+ u64 phase_deg_ms;
+ u64 phase_bleed;
+ u64 phase_value;
+ u64 pfd_freq_hz;
+ u64 phase_ci;
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, 0x1E, ADF4382_EN_PHASE_RESYNC_MSK,
+ 0xff);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, 0x1F, ADF4382_EN_BLEED_MSK, 0xff);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, 0x32, ADF4382_DEL_MODE_MSK, 0x0);
+ if (ret)
+ return ret;
+
+ ret = adf4382_pfd_compute(st, &pfd_freq_hz);
+ if (ret) {
+ dev_err(&st->spi->dev, "PFD frequency is out of range.\n");
+ return ret;
+ }
+
+ // Determine the phase adjustment in degrees relative the output freq.
+ phase_deg_fs = phase_fs * st->freq;
+ phase_deg_ns = div_u64(phase_deg_fs, FS_PER_NS);
+ phase_deg_ns = PERIOD_IN_DEG * phase_deg_ns;
+ phase_deg_ms = div_u64(phase_deg_ns, NS_PER_MS);
+
+ if (phase_deg_ms > PERIOD_IN_DEG_MS) {
+ dev_err(&st->spi->dev, "Phase adjustment is out of range.\n");
+ return -EINVAL;
+ }
+
+ /*
+ * The charge pump current will also need to be taken in to account
+ * as well as the Bleed constant
+ */
+ phase_ci = phase_deg_ms * adf4382_ci_ua[st->cp_i];
+ phase_bleed = phase_ci * ADF4382_PHASE_BLEED_CNST_MUL;
+ phase_bleed = div_u64(phase_bleed, ADF4382_PHASE_BLEED_CNST_DIV);
+
+ // Computation of the register value for the phase adjust
+ phase_value = phase_bleed * pfd_freq_hz;
+ phase_value = div64_u64(phase_value, st->freq);
+ phase_value = div_u64(phase_value, PERIOD_IN_DEG);
+ phase_value = DIV_ROUND_CLOSEST_ULL(phase_value, MILLI);
+
+ // Mask the value to 8 bits
+ phase_reg_value = phase_value & 0xff;
+
+ ret = regmap_write(st->regmap, 0x33, phase_reg_value);
+ if (ret)
+ return ret;
+
+ if (st->auto_align_en)
+ return regmap_update_bits(st->regmap, 0x32,
+ ADF4382_EN_AUTO_ALIGN_MSK, 0xff);
+
+ ret = regmap_update_bits(st->regmap, 0x32, ADF4382_EN_AUTO_ALIGN_MSK, 0x0);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(st->regmap, 0x34, ADF4382_PHASE_ADJ_MSK, 0xff);
+}
+
+static int adf4382_get_phase_adjust(struct adf4382_state *st, u32 *val)
+{
+ unsigned int tmp;
+ u8 phase_reg_value;
+ u64 phase_value;
+ u64 pfd_freq_hz;
+ int ret;
+
+ ret = regmap_read(st->regmap, 0x33, &tmp);
+ if (ret)
+ return ret;
+
+ phase_reg_value = tmp;
+
+ ret = adf4382_pfd_compute(st, &pfd_freq_hz);
+ if (ret) {
+ dev_err(&st->spi->dev, "PFD frequency is out of range.\n");
+ return ret;
+ }
+
+ phase_value = phase_reg_value * PERIOD_IN_DEG;
+ phase_value = phase_value * st->freq;
+ phase_value = div64_u64(phase_value, pfd_freq_hz);
+
+ phase_value = phase_value * ADF4382_PHASE_BLEED_CNST_DIV;
+ phase_value = phase_value * MS_PER_NS;
+ phase_value = div_u64(phase_value, ADF4382_PHASE_BLEED_CNST_MUL);
+ phase_value = phase_value * MILLI;
+ phase_value = div_u64(phase_value, adf4382_ci_ua[st->cp_i]);
+
+ phase_value = phase_value * NS_PER_FS;
+ phase_value = div_u64(phase_value, PERIOD_IN_DEG);
+ phase_value = div64_u64(phase_value, st->freq);
+
+ *val = (u32)phase_value;
+
+ return 0;
+}
+
+static int adf4382_set_phase_pol(struct adf4382_state *st, bool ph_pol)
+{
+ return regmap_update_bits(st->regmap, 0x32, ADF4382_PHASE_ADJ_POL_MSK,
+ FIELD_PREP(ADF4382_PHASE_ADJ_POL_MSK, ph_pol));
+}
+
+static int adf4382_get_phase_pol(struct adf4382_state *st, bool *ph_pol)
+{
+ unsigned int tmp;
+ int ret;
+
+ ret = regmap_read(st->regmap, 0x32, &tmp);
+ if (ret)
+ return ret;
+
+ *ph_pol = FIELD_GET(ADF4382_PHASE_ADJ_POL_MSK, tmp);
+
+ return 0;
+}
+
+static int adf4382_set_out_power(struct adf4382_state *st, int ch, int pwr)
+{
+ if (pwr > ADF4382_OUT_PWR_MAX)
+ pwr = ADF4382_OUT_PWR_MAX;
+
+ if (!ch) {
+ return regmap_update_bits(st->regmap, 0x29, ADF4382_CLK1_OPWR_MSK,
+ FIELD_PREP(ADF4382_CLK1_OPWR_MSK, pwr));
+ }
+
+ return regmap_update_bits(st->regmap, 0x29, ADF4382_CLK2_OPWR_MSK,
+ FIELD_PREP(ADF4382_CLK2_OPWR_MSK, pwr));
+
+};
+
+static int adf4382_get_out_power(struct adf4382_state *st, int ch, int *pwr)
+{
+ unsigned int tmp;
+ int ret;
+
+ ret = regmap_read(st->regmap, 0x29, &tmp);
+ if (ret)
+ return ret;
+
+ if (!ch)
+ *pwr = FIELD_GET(ADF4382_CLK1_OPWR_MSK, tmp);
+ else
+ *pwr = FIELD_GET(ADF4382_CLK2_OPWR_MSK, tmp);
+
+ return 0;
+}
+
+static int adf4382_set_en_chan(struct adf4382_state *st, int ch, int en)
+{
+ if (!ch) {
+ return regmap_update_bits(st->regmap, 0x2B,
+ ADF4382_PD_CLKOUT1_MSK,
+ FIELD_PREP(ADF4382_PD_CLKOUT1_MSK, !en));
+ }
+
+ return regmap_update_bits(st->regmap, 0x2B, ADF4382_PD_CLKOUT2_MSK,
+ FIELD_PREP(ADF4382_PD_CLKOUT2_MSK, !en));
+}
+
+static int adf4382_get_en_chan(struct adf4382_state *st, int ch, int *en)
+{
+ int enable;
+
+ if (!ch)
+ enable = regmap_test_bits(st->regmap, 0x2B,
+ ADF4382_PD_CLKOUT1_MSK);
+ else
+ enable = regmap_test_bits(st->regmap, 0x2B,
+ ADF4382_PD_CLKOUT2_MSK);
+ if (enable < 0)
+ return enable;
+
+ *en = !enable;
+ return 0;
+}
+
+static ssize_t adf4382_write(struct iio_dev *indio_dev, uintptr_t private,
+ const struct iio_chan_spec *chan, const char *buf,
+ size_t len)
+{
+ struct adf4382_state *st = iio_priv(indio_dev);
+ unsigned long long val;
+ int ret;
+
+ ret = kstrtoull(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ switch ((u32)private) {
+ case ADF4382_FREQ:
+ st->freq = val;
+ ret = adf4382_set_freq(st);
+ break;
+ case ADF4382_EN_AUTO_ALIGN:
+ st->auto_align_en = !!val;
+ ret = adf4382_set_phase_adjust(st, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret ? ret : len;
+}
+
+static ssize_t adf4382_read(struct iio_dev *indio_dev, uintptr_t private,
+ const struct iio_chan_spec *chan, char *buf)
+{
+ struct adf4382_state *st = iio_priv(indio_dev);
+ unsigned int val = 0;
+ u64 val_u64 = 0;
+ int ret;
+
+ switch ((u32)private) {
+ case ADF4382_FREQ:
+ ret = adf4382_get_freq(st, &val_u64);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%llu\n", val_u64);
+ case ADF4382_EN_AUTO_ALIGN:
+ ret = regmap_read(st->regmap, 0x32, &val);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%lu\n",
+ FIELD_GET(ADF4382_EN_AUTO_ALIGN_MSK, val));
+ default:
+ return -EINVAL;
+ }
+}
+
+#define _ADF4382_EXT_INFO(_name, _shared, _ident) { \
+ .name = _name, \
+ .read = adf4382_read, \
+ .write = adf4382_write, \
+ .private = _ident, \
+ .shared = _shared, \
+}
+
+static const struct iio_chan_spec_ext_info adf4382_ext_info[] = {
+ /*
+ * Usually we use IIO_CHAN_INFO_FREQUENCY, but there are
+ * values > 2^32 in order to support the entire frequency range
+ * in Hz.
+ */
+ _ADF4382_EXT_INFO("frequency", IIO_SHARED_BY_TYPE, ADF4382_FREQ),
+ _ADF4382_EXT_INFO("en_auto_align", IIO_SHARED_BY_TYPE, ADF4382_EN_AUTO_ALIGN),
+ { },
+};
+
+static int adf4382_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask)
+{
+ struct adf4382_state *st = iio_priv(indio_dev);
+ bool pol;
+ u32 tmp;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ ret = adf4382_get_out_power(st, chan->channel, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_ENABLE:
+ ret = adf4382_get_en_chan(st, chan->channel, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_PHASE:
+ ret = adf4382_get_phase_adjust(st, &tmp);
+ if (ret)
+ return ret;
+ *val = tmp;
+
+ ret = adf4382_get_phase_pol(st, &pol);
+ if (ret)
+ return ret;
+
+ if (pol)
+ *val *= -1;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adf4382_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct adf4382_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ return adf4382_set_out_power(st, chan->channel, val);
+ case IIO_CHAN_INFO_ENABLE:
+ return adf4382_set_en_chan(st, chan->channel, val);
+ case IIO_CHAN_INFO_PHASE:
+ st->phase = val;
+
+ if (val < 0)
+ ret = adf4382_set_phase_pol(st, true);
+ else
+ ret = adf4382_set_phase_pol(st, false);
+ if (ret)
+ return ret;
+
+ return adf4382_set_phase_adjust(st, abs(val));
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adf4382_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int write_val,
+ unsigned int *read_val)
+{
+ struct adf4382_state *st = iio_priv(indio_dev);
+
+ if (read_val)
+ return regmap_read(st->regmap, reg, read_val);
+
+ return regmap_write(st->regmap, reg, write_val);
+}
+
+static const struct iio_info adf4382_info = {
+ .read_raw = &adf4382_read_raw,
+ .write_raw = &adf4382_write_raw,
+ .debugfs_reg_access = &adf4382_reg_access,
+};
+
+static const struct iio_chan_spec adf4382_channels[] = {
+ {
+ .type = IIO_ALTVOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_PHASE),
+ .indexed = 1,
+ .output = 1,
+ .channel = 0,
+ .ext_info = adf4382_ext_info,
+ },
+ {
+ .type = IIO_ALTVOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_PHASE),
+ .indexed = 1,
+ .output = 1,
+ .channel = 1,
+ .ext_info = adf4382_ext_info,
+ },
+};
+
+#ifdef CONFIG_DEBUG_FS
+static int adf4382_show_del_cnt_raw(void *arg, u64 *val)
+{
+ struct iio_dev *indio_dev = arg;
+ struct adf4382_state *st = iio_priv(indio_dev);
+ unsigned int tmp;
+ u16 del_cnt = 0;
+ int ret;
+
+ ret = regmap_read(st->regmap, 0x64, &tmp);
+ if (ret)
+ return ret;
+ del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_LSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x65, &tmp);
+ if (ret)
+ return ret;
+ del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_MSB_MSK, tmp);
+
+ *val = del_cnt;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(adf4382_del_cnt_raw_fops,
+ adf4382_show_del_cnt_raw, NULL, "%llu\n");
+
+static int adf4382_show_bleed_pol(void *arg, u64 *val)
+{
+ struct iio_dev *indio_dev = arg;
+ struct adf4382_state *st = iio_priv(indio_dev);
+ unsigned int tmp;
+ u16 del_cnt = 0;
+ u8 bleed_pol = 0;
+ int ret;
+
+ ret = regmap_read(st->regmap, 0x64, &tmp);
+ if (ret)
+ return ret;
+ del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_LSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x65, &tmp);
+ if (ret)
+ return ret;
+ del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_MSB_MSK, tmp);
+
+ bleed_pol = FIELD_GET(ADF4382_DEL_CNT_BLEED_POL_MSK, del_cnt);
+
+ *val = bleed_pol;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(adf4382_bleed_pol_fops,
+ adf4382_show_bleed_pol, NULL, "%llu\n");
+
+static int adf4382_show_fine_current(void *arg, u64 *val)
+{
+ struct iio_dev *indio_dev = arg;
+ struct adf4382_state *st = iio_priv(indio_dev);
+ u8 fine_current = 0;
+ unsigned int tmp;
+ u16 del_cnt = 0;
+ int ret;
+
+ ret = regmap_read(st->regmap, 0x64, &tmp);
+ if (ret)
+ return ret;
+ del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_LSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x65, &tmp);
+ if (ret)
+ return ret;
+ del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_MSB_MSK, tmp);
+
+ fine_current = FIELD_GET(ADF4382_DEL_CNT_FINE_MSK, del_cnt);
+
+ *val = fine_current;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(adf4382_fine_current_fops,
+ adf4382_show_fine_current, NULL, "%llu\n");
+
+static int adf4382_show_coarse_current(void *arg, u64 *val)
+{
+ struct iio_dev *indio_dev = arg;
+ struct adf4382_state *st = iio_priv(indio_dev);
+ u8 coarse_current = 0;
+ unsigned int tmp;
+ u16 del_cnt = 0;
+ int ret;
+
+ ret = regmap_read(st->regmap, 0x64, &tmp);
+ if (ret)
+ return ret;
+ del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_LSB_MSK, tmp);
+
+ ret = regmap_read(st->regmap, 0x65, &tmp);
+ if (ret)
+ return ret;
+ del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_MSB_MSK, tmp);
+
+ coarse_current = FIELD_GET(ADF4382_DEL_CNT_COARSE_MSK, del_cnt);
+
+ *val = coarse_current;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(adf4382_coarse_current_fops,
+ adf4382_show_coarse_current, NULL, "%llu\n");
+
+static void adf4382_debugfs_init(struct iio_dev *indio_dev)
+{
+ struct dentry *d = iio_get_debugfs_dentry(indio_dev);
+
+ debugfs_create_file_unsafe("del_cnt_raw", 0400, d,
+ indio_dev, &adf4382_del_cnt_raw_fops);
+
+ debugfs_create_file_unsafe("bleed_pol", 0400, d,
+ indio_dev, &adf4382_bleed_pol_fops);
+
+ debugfs_create_file_unsafe("fine_current", 0400, d,
+ indio_dev, &adf4382_fine_current_fops);
+
+ debugfs_create_file_unsafe("coarse_current", 0400, d,
+ indio_dev, &adf4382_coarse_current_fops);
+}
+#else
+static void adf4382_debugfs_init(struct iio_dev *indio_dev)
+{
+}
+#endif
+
+static int adf4382_parse_device(struct adf4382_state *st)
+{
+ u32 tmp;
+ int ret;
+ int i;
+
+ ret = device_property_read_u64(&st->spi->dev, "adi,power-up-frequency",
+ &st->freq);
+ if (ret)
+ st->freq = ADF4382_RFOUT_DEFAULT;
+
+ ret = device_property_read_u32(&st->spi->dev, "adi,bleed-word",
+ &tmp);
+ if (ret)
+ st->bleed_word = 0;
+ else
+ st->bleed_word = (u16)tmp;
+
+ ret = device_property_read_u32(&st->spi->dev, "adi,charge-pump-microamp",
+ &tmp);
+ if (ret) {
+ st->cp_i = ADF4382_CP_I_DEFAULT;
+ } else {
+ i = find_closest(tmp, adf4382_ci_ua, ARRAY_SIZE(adf4382_ci_ua));
+ st->cp_i = (u8)i;
+ }
+
+ ret = device_property_read_u32(&st->spi->dev, "adi,output-power-value",
+ &tmp);
+ if (ret)
+ st->opwr_a = ADF4382_OPOWER_DEFAULT;
+ else
+ st->opwr_a = clamp_t(u8, tmp, 0, 15);
+
+ ret = device_property_read_u32(&st->spi->dev, "adi,ref-divider",
+ &tmp);
+ if (ret || !tmp)
+ st->ref_div = ADF4382_REF_DIV_DEFAULT;
+ else
+ st->ref_div = (u8)tmp;
+
+ st->spi_3wire_en = device_property_read_bool(&st->spi->dev,
+ "adi,spi-3wire-enable");
+ st->ref_doubler_en = device_property_read_bool(&st->spi->dev,
+ "adi,ref-doubler-enable");
+ st->cmos_3v3 = device_property_read_bool(&st->spi->dev, "adi,cmos-3v3");
+
+ st->clkin = devm_clk_get_enabled(&st->spi->dev, "ref_clk");
+
+ return PTR_ERR_OR_ZERO(st->clkin);
+}
+
+static int adf4382_scratchpad_check(struct adf4382_state *st)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_write(st->regmap, 0x0A, ADF4382_SCRATCHPAD_VAL);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, 0x0A, &val);
+ if (ret)
+ return ret;
+
+ if (val != ADF4382_SCRATCHPAD_VAL)
+ return dev_err_probe(&st->spi->dev, -EINVAL,
+ "Scratch pad test failed please check SPI connection");
+
+ return 0;
+}
+
+static int adf4382_init(struct adf4382_state *st)
+{
+ int ret;
+ bool en = true;
+
+ ret = regmap_write(st->regmap, 0x00, ADF4382_RESET_CMD);
+ if (ret)
+ return ret;
+
+ if (st->spi->mode & SPI_3WIRE || st->spi_3wire_en)
+ en = false;
+
+ ret = regmap_write(st->regmap, 0x00,
+ FIELD_PREP(ADF4382_SDO_ACT_MSK, en) |
+ FIELD_PREP(ADF4382_SDO_ACT_R_MSK, en));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x3D,
+ FIELD_PREP(ADF4382_CMOS_OV_MSK, st->cmos_3v3));
+ if (ret)
+ return ret;
+
+ ret = adf4382_scratchpad_check(st);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x20,
+ ADF4382_EN_AUTOCAL_MSK |
+ FIELD_PREP(ADF4382_EN_RDBLR_MSK, st->ref_doubler_en) |
+ FIELD_PREP(ADF4382_R_DIV_MSK, st->ref_div));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, 0x1f, st->cp_i);
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(st->regmap, adf4382_reg_default,
+ ARRAY_SIZE(adf4382_reg_default));
+ if (ret)
+ return ret;
+
+ st->ref_freq_hz = clk_get_rate(st->clkin);
+
+ ret = adf4382_set_out_power(st, 0, st->opwr_a);
+ if (ret)
+ return ret;
+
+ ret = adf4382_set_out_power(st, 1, st->opwr_a);
+ if (ret)
+ return ret;
+
+ return _adf4382_set_freq(st);
+}
+
+static int adf4382_freq_change(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct adf4382_state *st = container_of(nb, struct adf4382_state, nb);
+ int ret;
+
+ if (action == POST_RATE_CHANGE) {
+ mutex_lock(&st->lock);
+ ret = notifier_from_errno(adf4382_init(st));
+ mutex_unlock(&st->lock);
+ return ret;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int adf4382_clock_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct adf4382_state *st = to_adf4382_state(hw);
+
+ st->ref_freq_hz = parent_rate;
+ st->freq = rate * ADF4382_CLK_SCALE;
+
+ return adf4382_set_freq(st);
+}
+
+static void adf4382_clk_notifier_unreg(void *data)
+{
+ struct adf4382_state *st = data;
+
+ clk_notifier_unregister(st->clkin, &st->nb);
+}
+
+static unsigned long adf4382_clock_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct adf4382_state *st = to_adf4382_state(hw);
+ u64 freq = 0;
+ unsigned long rate;
+
+ adf4382_get_freq(st, &freq);
+ rate = DIV_ROUND_CLOSEST_ULL(freq, ADF4382_CLK_SCALE);
+
+ return rate;
+}
+
+static int adf4382_clock_enable(struct clk_hw *hw)
+{
+ struct adf4382_state *st = to_adf4382_state(hw);
+
+ return regmap_update_bits(st->regmap, 0x2B,
+ ADF4382_PD_CLKOUT1_MSK | ADF4382_PD_CLKOUT2_MSK,
+ 0x00);
+}
+
+static void adf4382_clock_disable(struct clk_hw *hw)
+{
+ struct adf4382_state *st = to_adf4382_state(hw);
+
+ regmap_update_bits(st->regmap, 0x2B,
+ ADF4382_PD_CLKOUT1_MSK | ADF4382_PD_CLKOUT2_MSK,
+ 0xff);
+}
+
+static long adf4382_clock_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct adf4382_state *st = to_adf4382_state(hw);
+ u64 freq = rate * ADF4382_CLK_SCALE;
+ u8 div_rate;
+ u64 tmp;
+
+ for (div_rate = 0; div_rate <= st->clkout_div_reg_val_max; div_rate++) {
+ tmp = (1 << div_rate) * freq;
+ if (tmp >= st->vco_min)
+ break;
+ }
+ div_rate = clamp_t(u8, div_rate, 0U, st->clkout_div_reg_val_max);
+ freq = clamp_t(u64, tmp, st->vco_min, st->vco_max);
+ freq = div_u64(freq, 1 << div_rate);
+
+ rate = DIV_ROUND_CLOSEST_ULL(freq, ADF4382_CLK_SCALE);
+ return rate;
+}
+
+static const struct clk_ops adf4382_clock_ops = {
+ .set_rate = adf4382_clock_set_rate,
+ .recalc_rate = adf4382_clock_recalc_rate,
+ .round_rate = adf4382_clock_round_rate,
+ .enable = adf4382_clock_enable,
+ .disable = adf4382_clock_disable,
+};
+
+static int adf4382_setup_clk(struct adf4382_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct clk_init_data init;
+ struct clk *clk;
+ const char *parent_name;
+
+ if (!device_property_present(dev, "#clock-cells"))
+ return 0;
+
+ if (device_property_read_string(dev, "clock-output-names", &init.name)) {
+ init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-clk",
+ fwnode_get_name(dev_fwnode(dev)));
+ if (!init.name)
+ return -ENOMEM;
+ }
+
+ parent_name = of_clk_get_parent_name(dev->of_node, 0);
+ if (!parent_name)
+ return -EINVAL;
+
+ init.ops = &adf4382_clock_ops;
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+
+ st->clk_hw.init = &init;
+ clk = devm_clk_register(dev, &st->clk_hw);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ st->clkout = clk;
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, clk);
+}
+
+static int adf4382_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ struct adf4382_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_spi(spi, &adf4382_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ st = iio_priv(indio_dev);
+
+ indio_dev->info = &adf4382_info;
+ indio_dev->name = "adf4382";
+
+ st->regmap = regmap;
+ st->spi = spi;
+ st->phase = 0;
+
+ st->vco_max = ADF4382_VCO_FREQ_MAX;
+ st->vco_min = ADF4382_VCO_FREQ_MIN;
+ st->clkout_div_reg_val_max = ADF4382_CLKOUT_DIV_REG_VAL_MAX;
+ if (spi_get_device_id(spi)->driver_data == ADF4382A) {
+ indio_dev->name = "adf4382a";
+ st->vco_max = ADF4382A_VCO_FREQ_MAX;
+ st->vco_min = ADF4382A_VCO_FREQ_MIN;
+ st->clkout_div_reg_val_max = ADF4382A_CLKOUT_DIV_REG_VAL_MAX;
+ }
+
+ mutex_init(&st->lock);
+
+ ret = adf4382_parse_device(st);
+ if (ret)
+ return ret;
+
+ st->nb.notifier_call = adf4382_freq_change;
+ ret = clk_notifier_register(st->clkin, &st->nb);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(&spi->dev, adf4382_clk_notifier_unreg, st);
+ if (ret)
+ return ret;
+
+ ret = adf4382_init(st);
+ if (ret)
+ return dev_err_probe(&spi->dev, ret, "adf4382 init failed\n");
+
+ ret = adf4382_setup_clk(st);
+ if (ret)
+ return ret;
+
+ if (!st->clkout) {
+ indio_dev->channels = adf4382_channels;
+ indio_dev->num_channels = ARRAY_SIZE(adf4382_channels);
+ }
+
+ ret = devm_iio_device_register(&spi->dev, indio_dev);
+ if (ret)
+ return ret;
+
+ if (IS_ENABLED(CONFIG_DEBUG_FS))
+ adf4382_debugfs_init(indio_dev);
+
+ return 0;
+}
+
+static const struct spi_device_id adf4382_id[] = {
+ { "adf4382", ADF4382 },
+ { "adf4382a", ADF4382A },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, adf4382_id);
+
+static const struct of_device_id adf4382_of_match[] = {
+ { .compatible = "adi,adf4382" },
+ { .compatible = "adi,adf4382a" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, adf4382_of_match);
+
+static struct spi_driver adf4382_driver = {
+ .driver = {
+ .name = "adf4382",
+ .of_match_table = adf4382_of_match,
+ },
+ .probe = adf4382_probe,
+ .id_table = adf4382_id,
+};
+module_spi_driver(adf4382_driver);
+
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_AUTHOR("Ciprian Hegbeli <ciprian.hegbeli@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADF4382");
+MODULE_LICENSE("GPL");
--
2.43.0
On Thu, 14 Nov 2024 15:03:11 +0200
Ciprian Hegbeli <ciprian.hegbeli@analog.com> wrote:
> The ADF4382A is a high performance, ultralow jitter, Frac-N PLL
> with integrated VCO ideally suited for LO generation for 5G applications
> or data converter clock applications. The high performance
> PLL has a figure of merit of -239 dBc/Hz, low 1/f Noise and
> high PFD frequency of 625MHz in integer mode that can achieve
> ultralow in-band noise and integrated jitter. The ADF4382A can
> generate frequencies in a fundamental octave range of 11.5 GHz to
> 21 GHz, thereby eliminating the need for sub-harmonic filters. The
> divide by 2 and 4 output dividers on the part allow frequencies to
> be generated from 5.75GHz to 10.5GHz and 2.875GHz to 5.25GHz
> respectively.
Hi Ciprian,
No real need to repeat all of this. Perhaps a shorter version
for this patch, or something about what features are implemented instead
of so much device info.
Various questions and comments inline. In some cases I have picked
out some examples of a particular code platform, please generalize and
apply to all similar code for v2.
Thanks,
Jonathan
> +#define ADF4382_CP_I_DEFAULT 15
> +#define ADF4382_OPOWER_DEFAULT 11
> +#define ADF4382_REF_DIV_DEFAULT 1
> +#define ADF4382_RFOUT_DEFAULT 2875000000ULL // 2.875GHz
Maybe express as 2875ULL * MEGA
Same for other cases. No one likes counting zeros if we an avoid it!
/* */ for comments.
If long lines, put them above the thing you are talking about.
> +#define ADF4382_SCRATCHPAD_VAL 0xA5
> +
> +#define ADF4382_PHASE_BLEED_CNST_MUL 511
Non obvious, so good to have some hints on why these values, perhaps a spec reference.
> +#define ADF4382_PHASE_BLEED_CNST_DIV 285
> +#define ADF4382_VCO_CAL_CNT 202
> +#define ADF4382_VCO_CAL_VTUNE 124
> +#define ADF4382_VCO_CAL_ALC 250
> +
> +#define FS_PER_NS MICRO
These should be in units.h if they are useful.
Or calculate them from what is there. E.g.
replace NS_PER_MS with NANO / MILLI
> +#define NS_PER_MS MICRO
> +#define MS_PER_NS MICRO
> +#define NS_PER_FS MICRO
> +#define PS_PER_NS 1000
> +#define UA_PER_A 1000000
> +
> +#define PERIOD_IN_DEG 360
> +#define PERIOD_IN_DEG_MS 360000
> +
> +#ifdef CONFIG_64BIT
> +#define ADF4382_CLK_SCALE 1
> +#else
> +#define ADF4382_CLK_SCALE 10ULL
> +#endif
No to this. Make the maths work either way as this just makes the
code hard to read. We don't really care about 32bit much any more
so just use the relevant handlers and 64 bit maths. It will slower
on a 32bit system but we don't care.
If this is about the int size of val and val2, just assume 32 bit
always and make sure what you use fits.
> +
> +enum {
> + ADF4382_FREQ,
> + ADF4382_EN_AUTO_ALIGN,
This should go away - see later.
> +};
> +
> +enum {
> + ADF4382,
> + ADF4382A,
As below, this sort of enum usually indicates that there is code to deal
with the difference between variants, which instead should be done with data
via appropriate static const struct picking.
> +};
> +
> +struct adf4382_state {
> + struct spi_device *spi;
> + struct regmap *regmap;
> + struct clk *clkin;
> + struct clk *clkout;
> + struct clk_hw clk_hw;
> + /* Protect against concurrent accesses to the device and data content */
Good to be more specific. I'm guessing there are read modify write sequences
or things that must be read sequentially that are not protected by
the regmap locking.
> + struct mutex lock;
> + struct notifier_block nb;
> + unsigned int ref_freq_hz;
> + u8 cp_i;
> + u8 opwr_a;
> + u64 freq;
> + bool spi_3wire_en;
> + bool ref_doubler_en;
> + bool auto_align_en;
> + u8 ref_div;
> + u8 clkout_div_reg_val_max;
> + u16 bleed_word;
> + int phase;
> + bool cmos_3v3;
> + u64 vco_max;
> + u64 vco_min;
> +};
> +
> +#define to_adf4382_state(_hw) container_of(_hw, struct adf4382_state, clk_hw)
> +
> +/* Charge pump current values expressed in uA */
> +static const int adf4382_ci_ua[] = {
> + 790, 990, 1190, 1380, 1590, 1980, 2390, 2790, 3180, 3970, 4770, 5570,
> + 6330, 7910, 9510, 11100
Wrap after 8 values, it makes it easier to find a particular one.
Feel free to add some spaces to align the two rows.
> +};
> +
> +static const struct reg_sequence adf4382_reg_default[] = {
> + { 0x00a, 0xA5 }, { 0x200, 0x00 }, { 0x201, 0x00 }, { 0x202, 0x00 },
> + { 0x203, 0x00 }, { 0x203, 0x00 }, { 0x203, 0x00 }, { 0x100, 0x25 },
> + { 0x101, 0x3F }, { 0x102, 0x3F }, { 0x103, 0x3F }, { 0x104, 0x3F },
> + { 0x105, 0x3F }, { 0x106, 0x3F }, { 0x107, 0x3F }, { 0x108, 0x3F },
> + { 0x109, 0x25 }, { 0x10A, 0x25 }, { 0x10B, 0x3F }, { 0x10C, 0x3F },
> + { 0x10D, 0x3F }, { 0x10E, 0x3F }, { 0x10F, 0x3F }, { 0x110, 0x3F },
> + { 0x111, 0x3F }, { 0x054, 0x00 }, { 0x053, 0x45 }, { 0x052, 0x00 },
> + { 0x051, 0x00 }, { 0x050, 0x00 }, { 0x04f, 0x08 }, { 0x04e, 0x06 },
> + { 0x04d, 0x00 }, { 0x04c, 0x2B }, { 0x04b, 0x5D }, { 0x04a, 0x00 },
> + { 0x048, 0x00 }, { 0x047, 0x00 }, { 0x046, 0x00 }, { 0x045, 0x62 },
> + { 0x044, 0x3F }, { 0x043, 0xB8 }, { 0x042, 0x01 }, { 0x041, 0x00 },
> + { 0x040, 0x00 }, { 0x03f, 0x82 }, { 0x03e, 0x4E }, { 0x03c, 0x00 },
> + { 0x03b, 0x00 }, { 0x03a, 0xFA }, { 0x039, 0x00 }, { 0x038, 0x71 },
> + { 0x037, 0x82 }, { 0x036, 0xC0 }, { 0x035, 0x00 }, { 0x034, 0x36 },
> + { 0x033, 0x00 }, { 0x032, 0x40 }, { 0x031, 0x63 }, { 0x030, 0x0F },
> + { 0x02f, 0x3F }, { 0x02e, 0x00 }, { 0x02d, 0xF1 }, { 0x02c, 0x0E },
> + { 0x02b, 0x01 }, { 0x02a, 0x30 }, { 0x029, 0x09 }, { 0x028, 0x00 },
> + { 0x027, 0xF0 }, { 0x026, 0x00 }, { 0x025, 0x01 }, { 0x024, 0x01 },
> + { 0x023, 0x00 }, { 0x022, 0x00 }, { 0x021, 0x00 }, { 0x01e, 0x20 },
> + { 0x01d, 0x00 }, { 0x01c, 0x00 }, { 0x01b, 0x00 }, { 0x01a, 0x00 },
> + { 0x019, 0x00 }, { 0x018, 0x00 }, { 0x017, 0x00 }, { 0x016, 0x00 },
> + { 0x015, 0x06 }, { 0x014, 0x00 }, { 0x013, 0x00 }, { 0x012, 0x00 },
> + { 0x011, 0x00 }, { 0x010, 0x50 }
Where possible build these up from appropriate defines of the fields.
Will take more code, but give a ready way to see what default means and
compare with functions that change it.
> +};
> +
> +static int _adf4382_set_freq(struct adf4382_state *st)
> +{
> + u32 frac2_word = 0;
> + u32 mod2_word = 0;
> + u64 pfd_freq_hz;
> + u32 frac1_word;
> + u8 clkout_div;
> + u32 read_val;
> + u8 dclk_div1;
> + u8 int_mode;
> + u8 en_bleed;
> + u8 ldwin_pw;
> + u16 n_int;
> + u8 div1;
> + u64 tmp;
> + u64 vco;
As below, combine same types to shorten the code.
> + int ret;
> + u8 var;
> +
> + ret = adf4382_pfd_compute(st, &pfd_freq_hz);
> + if (ret) {
> + dev_err(&st->spi->dev, "PFD frequency is out of range.\n");
> + return ret;
> + }
> +
> + for (clkout_div = 0; clkout_div <= st->clkout_div_reg_val_max; clkout_div++) {
> + tmp = (1 << clkout_div) * st->freq;
> + if (tmp < st->vco_min || tmp > st->vco_max)
> + continue;
> +
> + vco = tmp;
> + break;
> + }
> +
> + if (vco == 0) {
more conventional might be to check if the clk_out_dev > st->clk_out_div_reg_val_max;
Then no ned to init vco (which it seems you don't but should have).
> + dev_err(&st->spi->dev, "Output frequency is out of range.\n");
> + ret = -EINVAL;
> + return ret;
> + }
> +
> + ret = adf4382_pll_fract_n_compute(st, pfd_freq_hz, &n_int, &frac1_word,
> + &frac2_word, &mod2_word);
> + if (ret)
> + return ret;
> +
> + if (frac1_word || frac2_word) {
> + int_mode = 0;
> + en_bleed = 1;
> +
> + if (pfd_freq_hz <= (40 * HZ_PER_MHZ)) {
> + ldwin_pw = 7;
> + } else if (pfd_freq_hz <= (50 * HZ_PER_MHZ)) {
> + ldwin_pw = 6;
> + } else if (pfd_freq_hz <= (100 * HZ_PER_MHZ)) {
> + ldwin_pw = 5;
> + } else if (pfd_freq_hz <= (200 * HZ_PER_MHZ)) {
> + ldwin_pw = 4;
> + } else if (pfd_freq_hz <= (250 * HZ_PER_MHZ)) {
> + if (st->freq >= (5000U * HZ_PER_MHZ) &&
> + st->freq < (6400U * HZ_PER_MHZ)) {
> + ldwin_pw = 3;
> + } else {
> + ldwin_pw = 2;
> + }
> + }
> + } else {
> + int_mode = 1;
> + en_bleed = 0;
> +
> + tmp = DIV_ROUND_UP_ULL(pfd_freq_hz, UA_PER_A);
> + tmp *= adf4382_ci_ua[st->cp_i];
> + tmp = DIV_ROUND_UP_ULL(st->bleed_word, tmp);
> + if (tmp <= 85)
> + ldwin_pw = 0;
> + else
> + ldwin_pw = 1;
> + }
> +
> + dev_dbg(&st->spi->dev,
> + "VCO=%llu PFD=%llu RFout_div=%u N=%u FRAC1=%u FRAC2=%u MOD2=%u\n",
> + vco, pfd_freq_hz, 1 << clkout_div, n_int,
> + frac1_word, frac2_word, mod2_word);
> +
> + ret = regmap_update_bits(st->regmap, 0x28, ADF4382_VAR_MOD_EN_MSK,
> + frac2_word != 0 ? 0xff : 0);
Use MOD_EN_MSK again, not 0xff. Or use an if else with set_bits and clear_bits
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, 0x15, ADF4382_INT_MODE_MSK,
> + FIELD_PREP(ADF4382_INT_MODE_MSK, int_mode));
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, 0x1D,
> + FIELD_GET(ADF4382_BLEED_I_LSB_MSK, st->bleed_word));
> + if (ret)
> + return ret;
> +
> + var = (st->bleed_word >> 8) & ADF4382_BLEED_I_MSB_MSK;
> + ret = regmap_update_bits(st->regmap, 0x1E, ADF4382_BLEED_I_MSB_MSK, var);
> + if (ret)
> + return ret;
> + ret = regmap_update_bits(st->regmap, 0x1F, ADF4382_EN_BLEED_MSK,
> + FIELD_PREP(ADF4382_EN_BLEED_MSK, en_bleed));
> + if (ret)
> + return ret;
> +
Use a bulk write and a put_unaligned_l/be24()
> + return 0;
> +}
> +
> +static int adf4382_set_freq(struct adf4382_state *st)
> +{
> + int ret;
> +
> + mutex_lock(&st->lock);
> + ret = _adf4382_set_freq(st);
> + mutex_unlock(&st->lock);
guard(mutex)(&st->lock);
return _adf...
> +
> + return ret;
> +}
> +
> +static int adf4382_get_freq(struct adf4382_state *st, u64 *val)
> +{
> + unsigned int tmp;
> + u32 frac1 = 0;
> + u32 frac2 = 0;
> + u32 mod2 = 0;
> + u64 freq;
> + u64 pfd;
Combine same types on oneline - don't mix ones that set the value and
ones that don't however.
> + u16 n;
> + int ret;
> +
> + ret = adf4382_pfd_compute(st, &pfd);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(st->regmap, 0x11, &tmp);
> + if (ret)
> + return ret;
> +
> + n = FIELD_PREP(ADF4382_N_INT_MSB_MSK, tmp);
> + n = n << 8;
> +
> + ret = regmap_read(st->regmap, 0x10, &tmp);
> + if (ret)
> + return ret;
> + n |= FIELD_PREP(ADF4382_N_INT_LSB_MSK, tmp);
Looks like a bulk read and an endian. Check for all these
and replace them with that approach.
> + ret = regmap_read(st->regmap, 0x1c, &tmp);
Looks like a bulk read of 3 bytes then a get_unaligned_be24
or similar. Be careful with dma safety of buffers when
switching to bulkd read.
> + if (ret)
> + return ret;
> + mod2 |= FIELD_PREP(ADF4382_MOD2WORD_MSB_MSK, tmp);
> +
> + ret = regmap_read(st->regmap, 0x1b, &tmp);
> + if (ret)
> + return ret;
> + mod2 |= FIELD_PREP(ADF4382_MOD2WORD_MID_MSK, tmp);
> +
> + ret = regmap_read(st->regmap, 0x1a, &tmp);
> + if (ret)
> + return ret;
> + mod2 |= FIELD_PREP(ADF4382_MOD2WORD_LSB_MSK, tmp);
> +
> + if (mod2 == 0)
> + mod2 = 1;
> +
> + freq = frac2 * pfd;
> + freq = div_u64(freq, mod2);
> + freq = freq + (frac1 * pfd);
> + freq = div_u64(freq, ADF4382_MOD1WORD);
> + freq = freq + (n * pfd);
> +
> + *val = freq;
> + return 0;
> +}
> +
> +static int adf4382_set_phase_adjust(struct adf4382_state *st, u32 phase_fs)
> +{
> + u8 phase_reg_value;
> + u64 phase_deg_fs;
> + u64 phase_deg_ns;
> + u64 phase_deg_ms;
> + u64 phase_bleed;
> + u64 phase_value;
> + u64 pfd_freq_hz;
> + u64 phase_ci;
> + int ret;
> +
> + ret = regmap_update_bits(st->regmap, 0x1E, ADF4382_EN_PHASE_RESYNC_MSK,
> + 0xff);
regmap_set_bits? Using 0xff when only one bit set is not easy to read.
Also breaks the scripting that is out there to detect when regmap_set_bits should
be used for simpler and clearer code.
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, 0x1F, ADF4382_EN_BLEED_MSK, 0xff);
regmap_set_bits?
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(st->regmap, 0x32, ADF4382_DEL_MODE_MSK, 0x0);
regmap_clear_bits
> + if (ret)
> + return ret;
> +
> + ret = adf4382_pfd_compute(st, &pfd_freq_hz);
> + if (ret) {
> + dev_err(&st->spi->dev, "PFD frequency is out of range.\n");
> + return ret;
> + }
> +
> + // Determine the phase adjustment in degrees relative the output freq.
> + phase_deg_fs = phase_fs * st->freq;
> + phase_deg_ns = div_u64(phase_deg_fs, FS_PER_NS);
> + phase_deg_ns = PERIOD_IN_DEG * phase_deg_ns;
That PERIOD_IN_DEG rather implies that some of these were not phase_deg.
I don't really understand the steps here. Maybe add a comment with the maths
would help.
> + phase_deg_ms = div_u64(phase_deg_ns, NS_PER_MS);
> +
> + if (phase_deg_ms > PERIOD_IN_DEG_MS) {
> + dev_err(&st->spi->dev, "Phase adjustment is out of range.\n");
> + return -EINVAL;
> + }
> +
> + /*
> + * The charge pump current will also need to be taken in to account
> + * as well as the Bleed constant
> + */
> + phase_ci = phase_deg_ms * adf4382_ci_ua[st->cp_i];
> + phase_bleed = phase_ci * ADF4382_PHASE_BLEED_CNST_MUL;
> + phase_bleed = div_u64(phase_bleed, ADF4382_PHASE_BLEED_CNST_DIV);
> +
> + // Computation of the register value for the phase adjust
> + phase_value = phase_bleed * pfd_freq_hz;
> + phase_value = div64_u64(phase_value, st->freq);
> + phase_value = div_u64(phase_value, PERIOD_IN_DEG);
> + phase_value = DIV_ROUND_CLOSEST_ULL(phase_value, MILLI);
> +
> + // Mask the value to 8 bits
All comments in IIO use /* */
> + phase_reg_value = phase_value & 0xff;
> +
> + ret = regmap_write(st->regmap, 0x33, phase_reg_value);
> + if (ret)
> + return ret;
> +
> + if (st->auto_align_en)
> + return regmap_update_bits(st->regmap, 0x32,
> + ADF4382_EN_AUTO_ALIGN_MSK, 0xff);
> +
> + ret = regmap_update_bits(st->regmap, 0x32, ADF4382_EN_AUTO_ALIGN_MSK, 0x0);
> + if (ret)
> + return ret;
> +
> + return regmap_update_bits(st->regmap, 0x34, ADF4382_PHASE_ADJ_MSK, 0xff);
> +}
> +
> +static int adf4382_get_phase_adjust(struct adf4382_state *st, u32 *val)
> +{
> + unsigned int tmp;
> + u8 phase_reg_value;
> + u64 phase_value;
> + u64 pfd_freq_hz;
> + int ret;
> +
> + ret = regmap_read(st->regmap, 0x33, &tmp);
> + if (ret)
> + return ret;
> +
> + phase_reg_value = tmp;
> +
> + ret = adf4382_pfd_compute(st, &pfd_freq_hz);
> + if (ret) {
> + dev_err(&st->spi->dev, "PFD frequency is out of range.\n");
> + return ret;
> + }
> +
> + phase_value = phase_reg_value * PERIOD_IN_DEG;
Clear the intermediates are not phase_value. Can we figure a naming
scheme out that makes the intermediate steps more obvious.
> + phase_value = phase_value * st->freq;
phase_value = phase_reg_value * PERIOD_IN_DEG * st->freq;
> + phase_value = div64_u64(phase_value, pfd_freq_hz);
> +
> + phase_value = phase_value * ADF4382_PHASE_BLEED_CNST_DIV;
> + phase_value = phase_value * MS_PER_NS;
phase_value *= ADF4382_PHASE_BLEED_CNST_DIV * MS_PER_NS;
> + phase_value = div_u64(phase_value, ADF4382_PHASE_BLEED_CNST_MUL);
> + phase_value = phase_value * MILLI;
phase_value *= MILLI;
> + phase_value = div_u64(phase_value, adf4382_ci_ua[st->cp_i]);
> +
> + phase_value = phase_value * NS_PER_FS;
*= here as well.
> + phase_value = div_u64(phase_value, PERIOD_IN_DEG);
> + phase_value = div64_u64(phase_value, st->freq);
> +
> + *val = (u32)phase_value;
> +
> + return 0;
> +}
> +
> +static int adf4382_set_en_chan(struct adf4382_state *st, int ch, int en)
> +{
> + if (!ch) {
> + return regmap_update_bits(st->regmap, 0x2B,
> + ADF4382_PD_CLKOUT1_MSK,
> + FIELD_PREP(ADF4382_PD_CLKOUT1_MSK, !en));
> + }
for cases like this where it is a choice between 0 and 1, I'd use an else
even though not necessary for correctness.
> +
> + return regmap_update_bits(st->regmap, 0x2B, ADF4382_PD_CLKOUT2_MSK,
> + FIELD_PREP(ADF4382_PD_CLKOUT2_MSK, !en));
> +}
> +
> +static int adf4382_get_en_chan(struct adf4382_state *st, int ch, int *en)
> +{
> + int enable;
> +
> + if (!ch)
> + enable = regmap_test_bits(st->regmap, 0x2B,
> + ADF4382_PD_CLKOUT1_MSK);
> + else
> + enable = regmap_test_bits(st->regmap, 0x2B,
> + ADF4382_PD_CLKOUT2_MSK);
> + if (enable < 0)
> + return enable;
> +
> + *en = !enable;
That's certainly novel! Rename enable to disable.
> + return 0;
> +}
> +
> +static ssize_t adf4382_write(struct iio_dev *indio_dev, uintptr_t private,
> + const struct iio_chan_spec *chan, const char *buf,
> + size_t len)
> +{
> + struct adf4382_state *st = iio_priv(indio_dev);
> + unsigned long long val;
> + int ret;
> +
> + ret = kstrtoull(buf, 10, &val);
> + if (ret)
> + return ret;
> +
> + switch ((u32)private) {
> + case ADF4382_FREQ:
> + st->freq = val;
> + ret = adf4382_set_freq(st);
As below, overlap in code doesn't warrant having a single write function.
> + break;
> + case ADF4382_EN_AUTO_ALIGN:
> + st->auto_align_en = !!val;
> + ret = adf4382_set_phase_adjust(st, 0);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return ret ? ret : len;
> +}
> +
> +static ssize_t adf4382_read(struct iio_dev *indio_dev, uintptr_t private,
> + const struct iio_chan_spec *chan, char *buf)
> +{
> + struct adf4382_state *st = iio_priv(indio_dev);
> + unsigned int val = 0;
> + u64 val_u64 = 0;
> + int ret;
> +
> + switch ((u32)private) {
> + case ADF4382_FREQ:
> + ret = adf4382_get_freq(st, &val_u64);
There is almost no overlap, so just have separate callbacks.
> + if (ret)
> + return ret;
> + return sysfs_emit(buf, "%llu\n", val_u64);
> + case ADF4382_EN_AUTO_ALIGN:
> + ret = regmap_read(st->regmap, 0x32, &val);
> + if (ret)
> + return ret;
> + return sysfs_emit(buf, "%lu\n",
> + FIELD_GET(ADF4382_EN_AUTO_ALIGN_MSK, val));
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +#define _ADF4382_EXT_INFO(_name, _shared, _ident) { \
> + .name = _name, \
> + .read = adf4382_read, \
> + .write = adf4382_write, \
> + .private = _ident, \
> + .shared = _shared, \
> +}
For two entries? Macro is just making it harder to read so put the
struct initializer inline without the macro.
> +
> +static const struct iio_chan_spec_ext_info adf4382_ext_info[] = {
> + /*
> + * Usually we use IIO_CHAN_INFO_FREQUENCY, but there are
> + * values > 2^32 in order to support the entire frequency range
> + * in Hz.
> + */
> + _ADF4382_EXT_INFO("frequency", IIO_SHARED_BY_TYPE, ADF4382_FREQ),
We have IIO_VAL_INT64 for that. It's a bit fiddly but should work here.
> + _ADF4382_EXT_INFO("en_auto_align", IIO_SHARED_BY_TYPE, ADF4382_EN_AUTO_ALIGN),
What is this? Needs documentation in Documentation/ABI/testing/sysfs-bus-iio-adf4382
> + { },
{ }
> +};
> +
> +static int adf4382_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val,
> + int *val2,
Where they fit under 80 chars, good to combine parameters on fewer
lines. Check for other cases where this is easy to do.
It is fine to group things different, but here there is no obvious
benefit in doing one per line.
> + long mask)
> +
> +static int adf4382_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val,
> + int val2,
> + long mask)
> +{
> + struct adf4382_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_HARDWAREGAIN:
> + return adf4382_set_out_power(st, chan->channel, val);
> + case IIO_CHAN_INFO_ENABLE:
> + return adf4382_set_en_chan(st, chan->channel, val);
> + case IIO_CHAN_INFO_PHASE:
> + st->phase = val;
> +
> + if (val < 0)
> + ret = adf4382_set_phase_pol(st, true);
> + else
> + ret = adf4382_set_phase_pol(st, false);
ret = adf4382_set_phase_pol(st, val < 0);
> + if (ret)
> + return ret;
> +
> + return adf4382_set_phase_adjust(st, abs(val));
> + default:
> + return -EINVAL;
> + }
> +}
> +#ifdef CONFIG_DEBUG_FS
...
> +static int adf4382_show_coarse_current(void *arg, u64 *val)
> +{
> + struct iio_dev *indio_dev = arg;
> + struct adf4382_state *st = iio_priv(indio_dev);
> + u8 coarse_current = 0;
> + unsigned int tmp;
> + u16 del_cnt = 0;
> + int ret;
> +
> + ret = regmap_read(st->regmap, 0x64, &tmp);
> + if (ret)
> + return ret;
> + del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_LSB_MSK, tmp);
> +
> + ret = regmap_read(st->regmap, 0x65, &tmp);
> + if (ret)
> + return ret;
> + del_cnt |= FIELD_PREP(ADF4382_DEL_CNT_MSB_MSK, tmp);
How about a bulk read, endian getter and then mask? Should end up shorter
code with same result and no odd PREP.
See if similar works in other cases.
> +
> + coarse_current = FIELD_GET(ADF4382_DEL_CNT_COARSE_MSK, del_cnt);
> +
> + *val = coarse_current;
> +
> + return 0;
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(adf4382_coarse_current_fops,
> + adf4382_show_coarse_current, NULL, "%llu\n");
> +
> +static void adf4382_debugfs_init(struct iio_dev *indio_dev)
> +{
> + struct dentry *d = iio_get_debugfs_dentry(indio_dev);
> +
> + debugfs_create_file_unsafe("del_cnt_raw", 0400, d,
> + indio_dev, &adf4382_del_cnt_raw_fops);
> +
> + debugfs_create_file_unsafe("bleed_pol", 0400, d,
> + indio_dev, &adf4382_bleed_pol_fops);
> +
> + debugfs_create_file_unsafe("fine_current", 0400, d,
> + indio_dev, &adf4382_fine_current_fops);
> +
> + debugfs_create_file_unsafe("coarse_current", 0400, d,
> + indio_dev, &adf4382_coarse_current_fops);
> +}
> +#else
As below. I think you can let the compiler work it's magic as
all the debugfs calls should have stubs and the compiler should be able
to tell this code is not used and remove it if so.
> +static void adf4382_debugfs_init(struct iio_dev *indio_dev)
> +{
> +}
> +#endif
> +
> +static int adf4382_parse_device(struct adf4382_state *st)
> +{
> + u32 tmp;
> + int ret;
> + int i;
> +
> + ret = device_property_read_u64(&st->spi->dev, "adi,power-up-frequency",
Use
struct device *dev = &st->spi->dev;
to simplify all these calls a little. Do similar in any other function
that does this lots of times.
> + &st->freq);
> + if (ret)
> + st->freq = ADF4382_RFOUT_DEFAULT;
For default falbacks a neater way to code them is.
st->freq = ADF...
device_property_read_u64()
> +
> + ret = device_property_read_u32(&st->spi->dev, "adi,bleed-word",
> + &tmp);
> + if (ret)
> + st->bleed_word = 0;
> + else
> + st->bleed_word = (u16)tmp;
If it's a u16, read a u16 then
st->bleed_word = 0;
device_property_read_u16(dev, "adi,bleed-word", &st->bleed_word);
> +
> + ret = device_property_read_u32(&st->spi->dev, "adi,charge-pump-microamp",
> + &tmp);
> + if (ret) {
> + st->cp_i = ADF4382_CP_I_DEFAULT;
Consider just using the default value that you can pass to find_closest.
Given you specify the list in the dt-binding I'd just not do a closest match.
Easier to match precisely and no binding should use anything else.
> + } else {
> + i = find_closest(tmp, adf4382_ci_ua, ARRAY_SIZE(adf4382_ci_ua));
> + st->cp_i = (u8)i;
> + }
> +
> + ret = device_property_read_u32(&st->spi->dev, "adi,output-power-value",
> + &tmp);
> + if (ret)
> + st->opwr_a = ADF4382_OPOWER_DEFAULT;
I assume this won't change if you clamp it so simpler code is.
tmp = ADF4382_OPOWER_DEFAUT;
device_property_read_u32(dev, "adi,output-power-value", &tmp);
st->opwr_a = clamp_t(u8, tmp, 0, 15);
Or check it and fail if they picked and out of range value.
> + else
> + st->opwr_a = clamp_t(u8, tmp, 0, 15);
> +
> + ret = device_property_read_u32(&st->spi->dev, "adi,ref-divider",
> + &tmp);
> + if (ret || !tmp)
> + st->ref_div = ADF4382_REF_DIV_DEFAULT;
> + else
> + st->ref_div = (u8)tmp;
read a u8 if you want an u8 and define the DT binding as such.
However, I raised that I'm not yet convinced this should be in that binding.
> +
> + st->spi_3wire_en = device_property_read_bool(&st->spi->dev,
> + "adi,spi-3wire-enable");
> + st->ref_doubler_en = device_property_read_bool(&st->spi->dev,
> + "adi,ref-doubler-enable");
> + st->cmos_3v3 = device_property_read_bool(&st->spi->dev, "adi,cmos-3v3");
> +
> + st->clkin = devm_clk_get_enabled(&st->spi->dev, "ref_clk");
> +
> + return PTR_ERR_OR_ZERO(st->clkin);
> +}
> +
> +static int adf4382_init(struct adf4382_state *st)
> +{
> + int ret;
> + bool en = true;
> +
> + ret = regmap_write(st->regmap, 0x00, ADF4382_RESET_CMD);
> + if (ret)
> + return ret;
> +
> + if (st->spi->mode & SPI_3WIRE || st->spi_3wire_en)
> + en = false;
> +
> + ret = regmap_write(st->regmap, 0x00,
> + FIELD_PREP(ADF4382_SDO_ACT_MSK, en) |
Field names should provide an indication of which register they are in.
Here, given they registers aren't named, perhaps
ADF4382_REG0000_SDO_ACT_MSK etc
> + FIELD_PREP(ADF4382_SDO_ACT_R_MSK, en));
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, 0x3D,
> + FIELD_PREP(ADF4382_CMOS_OV_MSK, st->cmos_3v3));
> + if (ret)
> + return ret;
> +
> + ret = adf4382_scratchpad_check(st);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, 0x20,
> + ADF4382_EN_AUTOCAL_MSK |
> + FIELD_PREP(ADF4382_EN_RDBLR_MSK, st->ref_doubler_en) |
> + FIELD_PREP(ADF4382_R_DIV_MSK, st->ref_div));
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, 0x1f, st->cp_i);
> + if (ret)
> + return ret;
> +
> + ret = regmap_multi_reg_write(st->regmap, adf4382_reg_default,
> + ARRAY_SIZE(adf4382_reg_default));
> + if (ret)
> + return ret;
> +
> + st->ref_freq_hz = clk_get_rate(st->clkin);
> +
> + ret = adf4382_set_out_power(st, 0, st->opwr_a);
> + if (ret)
> + return ret;
> +
> + ret = adf4382_set_out_power(st, 1, st->opwr_a);
> + if (ret)
> + return ret;
> +
> + return _adf4382_set_freq(st);
> +}
> +
> +static int adf4382_freq_change(struct notifier_block *nb, unsigned long action,
> + void *data)
> +{
> + struct adf4382_state *st = container_of(nb, struct adf4382_state, nb);
> + int ret;
> +
> + if (action == POST_RATE_CHANGE) {
guard(mutex)(&st->lock);
return notifier_from_errno(adf4382_init(st));
and include cleanup.h
> + mutex_lock(&st->lock);
> + ret = notifier_from_errno(adf4382_init(st));
> + mutex_unlock(&st->lock);
> + return ret;
> + }
> +
> + return NOTIFY_OK;
> +}
> +static unsigned long adf4382_clock_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct adf4382_state *st = to_adf4382_state(hw);
> + u64 freq = 0;
> + unsigned long rate;
> +
> + adf4382_get_freq(st, &freq);
Check for errors. If you get one perhaps print a message as not
much else you can do in this callback
> + rate = DIV_ROUND_CLOSEST_ULL(freq, ADF4382_CLK_SCALE);
> +
> + return rate;
return DIV_ROUND...
> +}
> +
> +static int adf4382_clock_enable(struct clk_hw *hw)
> +{
> + struct adf4382_state *st = to_adf4382_state(hw);
> +
> + return regmap_update_bits(st->regmap, 0x2B,
> + ADF4382_PD_CLKOUT1_MSK | ADF4382_PD_CLKOUT2_MSK,
> + 0x00);
regmap_clear_bits
> +}
> +
> +static void adf4382_clock_disable(struct clk_hw *hw)
> +{
> + struct adf4382_state *st = to_adf4382_state(hw);
> +
> + regmap_update_bits(st->regmap, 0x2B,
> + ADF4382_PD_CLKOUT1_MSK | ADF4382_PD_CLKOUT2_MSK,
> + 0xff);
regmap_set_bits
> +}
> +
> +static long adf4382_clock_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + struct adf4382_state *st = to_adf4382_state(hw);
> + u64 freq = rate * ADF4382_CLK_SCALE;
> + u8 div_rate;
> + u64 tmp;
> +
> + for (div_rate = 0; div_rate <= st->clkout_div_reg_val_max; div_rate++) {
> + tmp = (1 << div_rate) * freq;
> + if (tmp >= st->vco_min)
> + break;
> + }
> + div_rate = clamp_t(u8, div_rate, 0U, st->clkout_div_reg_val_max);
> + freq = clamp_t(u64, tmp, st->vco_min, st->vco_max);
> + freq = div_u64(freq, 1 << div_rate);
> +
> + rate = DIV_ROUND_CLOSEST_ULL(freq, ADF4382_CLK_SCALE);
> + return rate;
return DIV_ROUND_CLOSEST_ULL();
> +}
> +static int adf4382_probe(struct spi_device *spi)
> +{
> + struct iio_dev *indio_dev;
> + struct regmap *regmap;
> + struct adf4382_state *st;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + regmap = devm_regmap_init_spi(spi, &adf4382_regmap_config);
> + if (IS_ERR(regmap))
> + return PTR_ERR(regmap);
> +
> + st = iio_priv(indio_dev);
> +
> + indio_dev->info = &adf4382_info;
> + indio_dev->name = "adf4382";
Do that via chip_info as suggested below.
> +
> + st->regmap = regmap;
> + st->spi = spi;
> + st->phase = 0;
st is allocated with kzalloc so no need to set a default to 0 unless
it's a non obvious default. Here I think it's fine.
> +
> + st->vco_max = ADF4382_VCO_FREQ_MAX;
> + st->vco_min = ADF4382_VCO_FREQ_MIN;
> + st->clkout_div_reg_val_max = ADF4382_CLKOUT_DIV_REG_VAL_MAX;
> + if (spi_get_device_id(spi)->driver_data == ADF4382A) {
As below, use a structure not an enum.
> + indio_dev->name = "adf4382a";
> + st->vco_max = ADF4382A_VCO_FREQ_MAX;
> + st->vco_min = ADF4382A_VCO_FREQ_MIN;
> + st->clkout_div_reg_val_max = ADF4382A_CLKOUT_DIV_REG_VAL_MAX;
All this stuff should be data in that structure and you should have
a pointer to store it in somewhere in st.
st->chip_info = spi_device_get_match_data(spi);
No need to check for errors but you can if you like.
> + }
> +
> + mutex_init(&st->lock);
ret = devm_mutex_init(&st->lock)
if (ret)
return ret;
Only makes a difference when some debug features are turned on, but it's
cheap to do so preferred in new code.
> +
> + ret = adf4382_parse_device(st);
> + if (ret)
> + return ret;
> +
> + st->nb.notifier_call = adf4382_freq_change;
> + ret = clk_notifier_register(st->clkin, &st->nb);
> + if (ret)
> + return ret;
> +
> + ret = devm_add_action_or_reset(&spi->dev, adf4382_clk_notifier_unreg, st);
> + if (ret)
> + return ret;
> +
> + ret = adf4382_init(st);
> + if (ret)
> + return dev_err_probe(&spi->dev, ret, "adf4382 init failed\n");
> +
> + ret = adf4382_setup_clk(st);
> + if (ret)
> + return ret;
> +
> + if (!st->clkout) {
If you have set clkout, does it actually make sense to register the
iio device with no channels? What is that bringing us?
> + indio_dev->channels = adf4382_channels;
> + indio_dev->num_channels = ARRAY_SIZE(adf4382_channels);
> + }
> +
> + ret = devm_iio_device_register(&spi->dev, indio_dev);
> + if (ret)
> + return ret;
> +
> + if (IS_ENABLED(CONFIG_DEBUG_FS))
I think you can skip the ifdef magic above as the compiler should be
able to remove those functions as unused. Check builds with and without
that protection and see if the module size changes.
> + adf4382_debugfs_init(indio_dev);
> +
> + return 0;
> +}
> +
> +static const struct spi_device_id adf4382_id[] = {
> + { "adf4382", ADF4382 },
> + { "adf4382a", ADF4382A },
Don't use an enum for these, use a point to a static const structure that
provides the chip specific information as data (rather than code as above).
That ends up both being a more sustainable solution and allows you to use
the more robust data accessor spi_get_device_match_data()
Note though that you should add the same data to the of_device_id table.
> + {},
As below.
> +};
> +MODULE_DEVICE_TABLE(spi, adf4382_id);
> +
> +static const struct of_device_id adf4382_of_match[] = {
> + { .compatible = "adi,adf4382" },
> + { .compatible = "adi,adf4382a" },
> + {},
No comma on terminated entrees. Also I'm trying to standardize
spacing on these in IIO to
{ }
It's an arbitrary choice, but that's the one I'm going for.
Jonathan
> +};
> +MODULE_DEVICE_TABLE(of, adf4382_of_match);
On 14/11/2024 14:03, Ciprian Hegbeli wrote:
> The ADF4382A is a high performance, ultralow jitter, Frac-N PLL
> with integrated VCO ideally suited for LO generation for 5G applications
> or data converter clock applications. The high performance
> PLL has a figure of merit of -239 dBc/Hz, low 1/f Noise and
> high PFD frequency of 625MHz in integer mode that can achieve
> ultralow in-band noise and integrated jitter. The ADF4382A can
> generate frequencies in a fundamental octave range of 11.5 GHz to
> 21 GHz, thereby eliminating the need for sub-harmonic filters. The
> divide by 2 and 4 output dividers on the part allow frequencies to
> be generated from 5.75GHz to 10.5GHz and 2.875GHz to 5.25GHz
> respectively.
>
> Signed-off-by: Ciprian Hegbeli <ciprian.hegbeli@analog.com>
> ---
> drivers/iio/frequency/Kconfig | 11 +
> drivers/iio/frequency/Makefile | 1 +
> drivers/iio/frequency/adf4382.c | 1825 +++++++++++++++++++++++++++++++
> 3 files changed, 1837 insertions(+)
> create mode 100644 drivers/iio/frequency/adf4382.c
...
> +static int _adf4382_set_freq(struct adf4382_state *st)
> +{
> + u32 frac2_word = 0;
> + u32 mod2_word = 0;
> + u64 pfd_freq_hz;
> + u32 frac1_word;
> + u8 clkout_div;
> + u32 read_val;
> + u8 dclk_div1;
> + u8 int_mode;
> + u8 en_bleed;
> + u8 ldwin_pw;
> + u16 n_int;
> + u8 div1;
> + u64 tmp;
> + u64 vco;
> + int ret;
> + u8 var;
> +
> + ret = adf4382_pfd_compute(st, &pfd_freq_hz);
> + if (ret) {
> + dev_err(&st->spi->dev, "PFD frequency is out of range.\n");
> + return ret;
> + }
> +
> + for (clkout_div = 0; clkout_div <= st->clkout_div_reg_val_max; clkout_div++) {
> + tmp = (1 << clkout_div) * st->freq;
> + if (tmp < st->vco_min || tmp > st->vco_max)
> + continue;
> +
> + vco = tmp;
> + break;
> + }
> +
(At least) LLVM/Clang complains about vco for a good reason: you may use
it without proper initialization if the for loop does not assign any
value. I guess you meant it to be initialized to zero in the declaration.
> + if (vco == 0) {
> + dev_err(&st->spi->dev, "Output frequency is out of range.\n");
> + ret = -EINVAL;
> + return ret;
> + }
> +
Best regards,
Javier Carrasco
© 2016 - 2026 Red Hat, Inc.