From: Alexandru Tachici <alexandru.tachici@xxxxxxxxxx> Add support for AD9545 Quad Input, 10-Output, Dual DPLL/IEEE 1588, 1 pps Synchronizer and Jitter Cleaner. Signed-off-by: Alexandru Tachici <alexandru.tachici@xxxxxxxxxx> --- drivers/clk/Kconfig | 6 + drivers/clk/Makefile | 1 + drivers/clk/adi/Kconfig | 29 + drivers/clk/adi/Makefile | 9 + drivers/clk/adi/clk-ad9545-i2c.c | 62 + drivers/clk/adi/clk-ad9545-spi.c | 76 + drivers/clk/adi/clk-ad9545.c | 2428 ++++++++++++++++++++++++++++++ drivers/clk/adi/clk-ad9545.h | 16 + 8 files changed, 2627 insertions(+) create mode 100644 drivers/clk/adi/Kconfig create mode 100644 drivers/clk/adi/Makefile create mode 100644 drivers/clk/adi/clk-ad9545-i2c.c create mode 100644 drivers/clk/adi/clk-ad9545-spi.c create mode 100644 drivers/clk/adi/clk-ad9545.c create mode 100644 drivers/clk/adi/clk-ad9545.h diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index e80918be8e9c..c39dfb6a6b66 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -38,6 +38,11 @@ menuconfig COMMON_CLK if COMMON_CLK +config COMMON_CLK_ADI + def_bool COMMON_CLK + help + Support for Analog Devices clock providers. + config COMMON_CLK_WM831X tristate "Clock driver for WM831x/2x PMICs" depends on MFD_WM831X @@ -377,6 +382,7 @@ config COMMON_CLK_K210 Support for the Canaan Kendryte K210 RISC-V SoC clocks. source "drivers/clk/actions/Kconfig" +source "drivers/clk/adi/Kconfig" source "drivers/clk/analogbits/Kconfig" source "drivers/clk/baikal-t1/Kconfig" source "drivers/clk/bcm/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 5f06879d7fe9..181064b6e593 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o # please keep this section sorted lexicographically by directory path name obj-y += actions/ +obj-$(CONFIG_COMMON_CLK_ADI) += adi/ obj-y += analogbits/ obj-$(CONFIG_COMMON_CLK_AT91) += at91/ obj-$(CONFIG_ARCH_ARTPEC) += axis/ diff --git a/drivers/clk/adi/Kconfig b/drivers/clk/adi/Kconfig new file mode 100644 index 000000000000..768b8227f336 --- /dev/null +++ b/drivers/clk/adi/Kconfig @@ -0,0 +1,29 @@ +# +# Analog Devices Clock Drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Analog Devices Clock Drivers" + +config COMMON_CLK_AD9545 + tristate + +config COMMON_CLK_AD9545_I2C + tristate "Analog Devices AD9545 via I2C" + depends on REGMAP_I2C + select COMMON_CLK_AD9545 + help + Say yes here to build support for Analog Devices AD9545 + Quad Input, 10-Output, Dual DPLL/IEEE 1588, + 1 pps Synchronizer and Jitter Cleaner via I2C + +config COMMON_CLK_AD9545_SPI + tristate "Analog Devices AD9545 via SPI" + depends on REGMAP_SPI + select COMMON_CLK_AD9545 + help + Say yes here to build support for Analog Devices AD9545 + Quad Input, 10-Output, Dual DPLL/IEEE 1588, + 1 pps Synchronizer and Jitter Cleaner via SPI + +endmenu diff --git a/drivers/clk/adi/Makefile b/drivers/clk/adi/Makefile new file mode 100644 index 000000000000..7ba1fded3013 --- /dev/null +++ b/drivers/clk/adi/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +# +# Makefile for AD9545 Network Clock Generator/Synchronizer +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_COMMON_CLK_AD9545) += clk-ad9545.o +obj-$(CONFIG_COMMON_CLK_AD9545_I2C) += clk-ad9545-i2c.o +obj-$(CONFIG_COMMON_CLK_AD9545_SPI) += clk-ad9545-spi.o diff --git a/drivers/clk/adi/clk-ad9545-i2c.c b/drivers/clk/adi/clk-ad9545-i2c.c new file mode 100644 index 000000000000..c85326b3cdd2 --- /dev/null +++ b/drivers/clk/adi/clk-ad9545-i2c.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * AD9545 Network Clock Generator/Synchronizer + * + * Copyright 2020 Analog Devices Inc. + */ + +#include "clk-ad9545.h" +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +static const struct regmap_config ad9545_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x3A3B, + .use_single_read = true, + .use_single_write = true, +}; + +static int ad9545_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &ad9545_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "devm_regmap_init_i2c failed!\n"); + return PTR_ERR(regmap); + } + + return ad9545_probe(&client->dev, regmap); +} + +static const struct of_device_id ad9545_i2c_of_match[] = { + { .compatible = "adi,ad9545" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad9545_i2c_of_match); + +static const struct i2c_device_id ad9545_i2c_id[] = { + {"ad9545"}, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad9545_i2c_id); + +static struct i2c_driver ad9545_i2c_driver = { + .driver = { + .name = "ad9545", + .of_match_table = ad9545_i2c_of_match, + }, + .probe_new = ad9545_i2c_probe, + .id_table = ad9545_i2c_id, +}; +module_i2c_driver(ad9545_i2c_driver); + +MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Analog Devices AD9545 I2C"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/clk/adi/clk-ad9545-spi.c b/drivers/clk/adi/clk-ad9545-spi.c new file mode 100644 index 000000000000..b0f9f59e6dea --- /dev/null +++ b/drivers/clk/adi/clk-ad9545-spi.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * AD9545 Network Clock Generator/Synchronizer + * + * Copyright 2020 Analog Devices Inc. + */ + +#include "clk-ad9545.h" +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/iio/iio.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> + +#define AD9545_CONFIG_0 0x0000 + +#define AD9545_4WIRE_SPI 0x3 +#define AD9545_4WIRE_SPI_MSK GENMASK(4, 3) + +static const struct regmap_config ad9545_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x3A3B, + .use_single_read = true, + .use_single_write = true, +}; + +static int ad9545_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_spi(spi, &ad9545_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "devm_regmap_init_spi failed!\n"); + return PTR_ERR(regmap); + } + + if (!(spi->mode & SPI_3WIRE)) { + ret = regmap_write(regmap, AD9545_CONFIG_0, + FIELD_PREP(AD9545_4WIRE_SPI_MSK, AD9545_4WIRE_SPI)); + if (ret < 0) + return ret; + } + + return ad9545_probe(&spi->dev, regmap); +} + +static const struct of_device_id ad9545_spi_of_match[] = { + { .compatible = "adi,ad9545" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad9545_spi_of_match); + +static const struct spi_device_id ad9545_spi_id[] = { + {"ad9545", 0}, + { } +}; +MODULE_DEVICE_TABLE(spi, ad9545_spi_id); + +static struct spi_driver ad9545_spi_driver = { + .driver = { + .name = "ad9545", + .of_match_table = ad9545_spi_of_match, + }, + .probe = ad9545_spi_probe, + .id_table = ad9545_spi_id, +}; +module_spi_driver(ad9545_spi_driver); + +MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Analog Devices AD9545 SPI"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/clk/adi/clk-ad9545.c b/drivers/clk/adi/clk-ad9545.c new file mode 100644 index 000000000000..b678b11fcd15 --- /dev/null +++ b/drivers/clk/adi/clk-ad9545.c @@ -0,0 +1,2428 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * AD9545 Network Clock Generator/Synchronizer + * + * Copyright 2020 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/rational.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <dt-bindings/clock/ad9545.h> + +#define AD9545_CONFIG_0 0x0000 +#define AD9545_PRODUCT_ID_LOW 0x0004 +#define AD9545_PRODUCT_ID_HIGH 0x0005 +#define AD9545_IO_UPDATE 0x000F +#define AD9545_M0_PIN 0x0102 +#define AD9545_CHIP_ID 0x0121 +#define AD9545_SYS_CLK_FB_DIV 0x0200 +#define AD9545_SYS_CLK_INPUT 0x0201 +#define AD9545_SYS_CLK_REF_FREQ 0x0202 +#define AD9545_SYS_STABILITY_T 0x0207 +#define AD9545_COMPENSATE_TDCS 0x0280 +#define AD9545_COMPENSATE_DPLL 0x0282 +#define AD9545_AUX_DPLL_CHANGE_LIMIT 0x0283 +#define AD9545_AUX_DPLL_SOURCE 0x0284 +#define AD9545_AUX_DPLL_LOOP_BW 0x0285 +#define AD9545_REF_A_CTRL 0x0300 +#define AD9545_REF_A_RDIV 0x0400 +#define AD9545_REF_A_PERIOD 0x0404 +#define AD9545_REF_A_OFFSET_LIMIT 0x040C +#define AD9545_REF_A_MONITOR_HYST 0x040F +#define AD9545_PHASE_LOCK_THRESH 0x0800 +#define AD9545_PHASE_LOCK_FILL_RATE 0x0803 +#define AD9545_PHASE_LOCK_DRAIN_RATE 0x0804 +#define AD9545_FREQ_LOCK_THRESH 0x0805 +#define AD9545_FREQ_LOCK_FILL_RATE 0x0808 +#define AD9545_FREQ_LOCK_DRAIN_RATE 0x0809 +#define AD9545_DPLL0_FTW 0x1000 +#define AD9545_DRIVER_0A_CONF 0x10D7 +#define AD9545_SYNC_CTRL0 0x10DB +#define AD9545_APLL0_M_DIV 0x1081 +#define AD9545_Q0A_DIV 0x1100 +#define AD9545_Q0A_PHASE 0x1104 +#define AD9545_Q0A_PHASE_CONF 0x1108 +#define AD9545_DPLL0_EN 0x1200 +#define AD9545_DPLL0_SOURCE 0x1201 +#define AD9545_DPLL0_LOOP_BW 0x1204 +#define AD9545_DPLL0_N_DIV 0x120C +#define AD9545_DPLL0_FRAC 0x1210 +#define AD9545_DPLL0_MOD 0x1213 +#define AD9545_DPLL0_FAST_L1 0x1216 +#define AD9545_DPLL0_FAST_L2 0x1217 +#define AD9545_DRIVER_1A_CONF 0x14D7 +#define AD9545_Q1A_DIV 0x1500 +#define AD9545_Q1A_PHASE 0x1504 +#define AD9545_Q1A_PHASE_CONF 0x1508 +#define AD9545_CALIB_CLK 0x2000 +#define AD9545_POWER_DOWN_REF 0x2001 +#define AD9545_PWR_CALIB_CH0 0x2100 +#define AD9545_CTRL_CH0 0x2101 +#define AD9545_DIV_OPS_Q0A 0x2102 +#define AD9545_DPLL0_MODE 0x2105 +#define AD9545_DPLL0_FAST_MODE 0x2106 +#define AD9545_DIV_OPS_Q1A 0x2202 +#define AD9545_NCO0_FREQ 0x2805 +#define AD9545_TDC0_DIV 0x2A00 +#define AD9545_TDC0_PERIOD 0x2A01 +#define AD9545_PLL_STATUS 0x3001 +#define AD9545_MISC 0x3002 +#define AD9545_REFA_STATUS 0x3005 +#define AD9545_PLL0_STATUS 0x3100 + +#define AD9545_REF_CTRL_DIF_MSK GENMASK(3, 2) +#define AD9545_REF_CTRL_REFA_MSK GENMASK(5, 4) +#define AD9545_REF_CTRL_REFAA_MSK GENMASK(7, 6) + +#define AD9545_UPDATE_REGS 0x1 +#define AD9545_RESET_REGS 0x81 + +#define AD9545_MX_PIN(x) (AD9545_M0_PIN + (x)) + +#define AD9545_SYNC_CTRLX(x) (AD9545_SYNC_CTRL0 + ((x) * 0x400)) +#define AD9545_REF_X_RDIV(x) (AD9545_REF_A_RDIV + ((x) * 0x20)) +#define AD9545_REF_X_PERIOD(x) (AD9545_REF_A_PERIOD + ((x) * 0x20)) +#define AD9545_REF_X_OFFSET_LIMIT(x) (AD9545_REF_A_OFFSET_LIMIT + ((x) * 0x20)) +#define AD9545_REF_X_MONITOR_HYST(x) (AD9545_REF_A_MONITOR_HYST + ((x) * 0x20)) +#define AD9545_REF_X_PHASE_LOCK_FILL(x) (AD9545_PHASE_LOCK_FILL_RATE + ((x) * 0x20)) +#define AD9545_REF_X_PHASE_LOCK_DRAIN(x) (AD9545_PHASE_LOCK_DRAIN_RATE + ((x) * 0x20)) +#define AD9545_REF_X_FREQ_LOCK_FILL(x) (AD9545_FREQ_LOCK_FILL_RATE + ((x) * 0x20)) +#define AD9545_REF_X_FREQ_LOCK_DRAIN(x) (AD9545_FREQ_LOCK_DRAIN_RATE + ((x) * 0x20)) + +#define AD9545_SOURCEX_PHASE_THRESH(x) (AD9545_PHASE_LOCK_THRESH + ((x) * 0x20)) +#define AD9545_SOURCEX_FREQ_THRESH(x) (AD9545_FREQ_LOCK_THRESH + ((x) * 0x20)) +#define AD9545_NCOX_PHASE_THRESH(x) (AD9545_SOURCEX_PHASE_THRESH((x) + 4)) +#define AD9545_NCOX_FREQ_THRESH(x) (AD9545_SOURCEX_FREQ_THRESH((x) + 4)) + +#define AD9545_APLLX_M_DIV(x) (AD9545_APLL0_M_DIV + ((x) * 0x400)) + +#define AD9545_Q0_DIV(x) (AD9545_Q0A_DIV + ((x) * 0x9)) +#define AD9545_Q1_DIV(x) (AD9545_Q1A_DIV + ((x) * 0x9)) +#define AD9545_QX_DIV(x) ({ \ + typeof(x) x_ = (x); \ + \ + (x_ > 5) ? AD9545_Q1_DIV(x_ - 6) : AD9545_Q0_DIV(x_); \ +}) + +#define AD9545_Q0_PHASE(x) (AD9545_Q0A_PHASE + ((x) * 0x9)) +#define AD9545_Q1_PHASE(x) (AD9545_Q1A_PHASE + ((x) * 0x9)) +#define AD9545_QX_PHASE(x) ({ \ + typeof(x) x_ = (x); \ + \ + (x_ > 5) ? AD9545_Q1_PHASE(x_ - 6) : AD9545_Q0_PHASE(x_); \ +}) + +#define AD9545_Q0_PHASE_CONF(x) (AD9545_Q0A_PHASE_CONF + ((x) * 0x9)) +#define AD9545_Q1_PHASE_CONF(x) (AD9545_Q1A_PHASE_CONF + ((x) * 0x9)) +#define AD9545_QX_PHASE_CONF(x) ({ \ + typeof(x) x_ = (x); \ + \ + (x_ > 5) ? AD9545_Q1_PHASE_CONF(x_ - 6) : AD9545_Q0_PHASE_CONF(x_); \ +}) + +#define AD9545_DPLLX_FTW(x) (AD9545_DPLL0_FTW + ((x) * 0x400)) +#define AD9545_DPLLX_EN(x, y) (AD9545_DPLL0_EN + ((x) * 0x400) + ((y) * 0x20)) +#define AD9545_DPLLX_SOURCE(x, y) (AD9545_DPLL0_SOURCE + ((x) * 0x400) + ((y) * 0x20)) +#define AD9545_DPLLX_LOOP_BW(x, y) (AD9545_DPLL0_LOOP_BW + ((x) * 0x400) + ((y) * 0x20)) +#define AD9545_DPLLX_N_DIV(x, y) (AD9545_DPLL0_N_DIV + ((x) * 0x400) + ((y) * 0x20)) +#define AD9545_DPLLX_FRAC_DIV(x, y) (AD9545_DPLL0_FRAC + ((x) * 0x400) + ((y) * 0x20)) +#define AD9545_DPLLX_MOD_DIV(x, y) (AD9545_DPLL0_MOD + ((x) * 0x400) + ((y) * 0x20)) +#define AD9545_DPLLX_FAST_L1(x, y) (AD9545_DPLL0_FAST_L1 + ((x) * 0x400) + ((y) * 0x20)) +#define AD9545_DPLLX_FAST_L2(x, y) (AD9545_DPLL0_FAST_L2 + ((x) * 0x400) + ((y) * 0x20)) + +#define AD9545_DIV_OPS_Q0(x) (AD9545_DIV_OPS_Q0A + (x)) +#define AD9545_DIV_OPS_Q1(x) (AD9545_DIV_OPS_Q1A + (x)) +#define AD9545_DIV_OPS_QX(x) ({ \ + typeof(x) x_ = (x) / 2; \ + \ + (x_ > 2) ? AD9545_DIV_OPS_Q1(x_ - 3) : AD9545_DIV_OPS_Q0(x_); \ +}) + +#define AD9545_PWR_CALIB_CHX(x) (AD9545_PWR_CALIB_CH0 + ((x) * 0x100)) +#define AD9545_PLLX_STATUS(x) (AD9545_PLL0_STATUS + ((x) * 0x100)) +#define AD9545_DPLLX_FAST_MODE(x) (AD9545_DPLL0_FAST_MODE + ((x) * 0x100)) +#define AD9545_REFX_STATUS(x) (AD9545_REFA_STATUS + (x)) + +#define AD9545_PROFILE_SEL_MODE_MSK GENMASK(3, 2) +#define AD9545_PROFILE_SEL_MODE(x) FIELD_PREP(AD9545_PROFILE_SEL_MODE_MSK, x) + +#define AD9545_NCOX_FREQ(x) (AD9545_NCO0_FREQ + ((x) * 0x40)) + +#define AD9545_TDCX_DIV(x) (AD9545_TDC0_DIV + ((x) * 0x9)) +#define AD9545_TDCX_PERIOD(x) (AD9545_TDC0_PERIOD + ((x) * 0x9)) + +/* AD9545 MX PIN bitfields */ +#define AD9545_MX_TO_TDCX(x) (0x30 + (x)) + +/* AD9545 COMPENSATE TDCS bitfields */ +#define AD9545_COMPENSATE_TDCS_VIA_AUX_DPLL 0x4 + +/* AD9545 COMPENSATE DPLL bitfields */ +#define AD9545_COMPNESATE_VIA_AUX_DPLL 0x44 + +/* define AD9545_DPLLX_EN bitfields */ +#define AD9545_EN_PROFILE_MSK BIT(0) +#define AD9545_SEL_PRIORITY_MSK GENMASK(5, 1) + +/* AD9545_PWR_CALIB_CHX bitfields */ +#define AD9545_PWR_DOWN_CH BIT(0) +#define AD9545_CALIB_APLL BIT(1) + +/* AD9545_SYNC_CTRLX bitfields */ +#define AD9545_SYNC_CTRL_DPLL_REF_MSK BIT(2) +#define AD9545_SYNC_CTRL_MODE_MSK GENMASK(1, 0) + +/* AD9545_QX_PHASE_CONF bitfields */ +#define AD9545_QX_HALF_DIV_MSK BIT(5) +#define AD9545_QX_PHASE_32_MSK BIT(6) + +/* AD9545_DIV_OPS_QX bitfields */ +#define AD9545_DIV_OPS_MUTE_A_MSK BIT(2) +#define AD9545_DIV_OPS_MUTE_AA_MSK BIT(3) + +/* AD9545_PLL_STATUS bitfields */ +#define AD9545_PLLX_LOCK(x, y) ((1 << (4 + (x))) & (y)) + +/* AD9545_MISC bitfields */ +#define AD9545_MISC_AUX_NC0_ERR_MSK GENMASK(5, 4) +#define AD9545_MISC_AUX_NC1_ERR_MSK GENMASK(7, 6) +#define AD9545_AUX_DPLL_LOCK_MSK BIT(1) +#define AD9545_AUX_DPLL_REF_FAULT BIT(2) + +/* AD9545_REFX_STATUS bitfields */ +#define AD9545_REFX_VALID_MSK BIT(4) + +#define AD9545_SYS_PLL_STABLE_MSK GENMASK(1, 0) +#define AD9545_SYS_PLL_STABLE(x) (((x) & AD9545_SYS_PLL_STABLE_MSK) == 0x3) + +#define AD9545_APLL_LOCKED(x) ((x) & BIT(3)) + +#define AD9545_SYS_CLK_STABILITY_MS 50 + +#define AD9545_R_DIV_MAX 0x40000000 +#define AD9545_IN_MAX_TDC_FREQ_HZ 200000 + +#define AD9545_MAX_REFS 4 + +#define AD9545_APLL_M_DIV_MIN 14 +#define AD9545_APLL_M_DIV_MAX 255 + +#define AD9545_DPLL_MAX_N 1073741823 +#define AD9545_DPLL_MAX_FRAC 116777215 +#define AD9545_DPLL_MAX_MOD 116777215 +#define AD9545_MAX_DPLL_PROFILES 6 + +#define AD9545_NCO_MAX_FREQ 65535 + +static const unsigned int ad9545_apll_rate_ranges_hz[2][2] = { + {2400000000U, 3200000000U}, {3200000000U, 4000000000U} +}; + +static const unsigned int ad9545_apll_pfd_rate_ranges_hz[2] = { + 162000000U, 300000000U +}; + +static const unsigned short ad9545_vco_calibration_op[][2] = { + {AD9545_CALIB_CLK, 0}, + {AD9545_IO_UPDATE, AD9545_UPDATE_REGS}, + {AD9545_CALIB_CLK, BIT(2)}, + {AD9545_IO_UPDATE, AD9545_UPDATE_REGS}, +}; + +static const u8 ad9545_tdc_source_mapping[] = { + 0, 1, 2, 3, 8, 9, +}; + +static const u32 ad9545_fast_acq_excess_bw_map[] = { + 0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, +}; + +static const u32 ad9545_fast_acq_timeout_map[] = { + 1, 10, 50, 100, 500, 1000, 10000, 50000, +}; + +static const u32 ad9545_hyst_scales_bp[] = { + 0, 3125, 6250, 12500, 25000, 50000, 75000, 87500 +}; + +static const u32 ad9545_out_source_ua[] = { + 7500, 12500, 15000 +}; + +static const u32 ad9545_rate_change_limit_map[] = { + 715, 1430, 2860, 5720, 11440, 22880, 45760, +}; + +static const char * const ad9545_ref_m_clk_names[] = { + "Ref-M0", "Ref-M1", "Ref-M2", +}; + +static const char * const ad9545_ref_clk_names[] = { + "Ref-A", "Ref-AA", "Ref-B", "Ref-BB", +}; + +static const char * const ad9545_in_clk_names[] = { + "Ref-A-Div", "Ref-AA-Div", "Ref-B-Div", "Ref-BB-Div", +}; + +static const char * const ad9545_out_clk_names[] = { + "Q0A-div", "Q0AA-div", "Q0B-div", "Q0BB-div", "Q0C-div", "Q0CC-div", "Q1A-div", "Q1AA-div", + "Q1B-div", "Q1BB-div", +}; + +static const char * const ad9545_pll_clk_names[] = { + "PLL0", "PLL1", +}; + +static const char * const ad9545_aux_nco_clk_names[] = { + "AUX_NCO0", "AUX_NCO1", +}; + +static const char * const ad9545_aux_tdc_clk_names[] = { + "AUX_TDC0", "AUX_TDC1", +}; + +static const char *ad9545_aux_dpll_name = "AUX_DPLL"; + +enum ad9545_ref_mode { + AD9545_SINGLE_ENDED = 0, + AD9545_DIFFERENTIAL, +}; + +enum ad9545_single_ended_config { + AD9545_AC_COUPLED_IF = 0, + AD9545_DC_COUPLED_1V2, + AD9545_DC_COUPLED_1V8, + AD9545_IN_PULL_UP, +}; + +enum ad9545_diferential_config { + AD9545_AC_COUPLED = 0, + AD9545_DC_COUPLED, + AD9545_DC_COUPLED_LVDS, +}; + +enum ad9545_output_mode { + AD9545_SINGLE_DIV_DIF = 0, + AD9545_SINGLE_DIV, + AD9545_DUAL_DIV, +}; + +struct ad9545_out_clk { + struct ad9545_state *st; + bool output_used; + bool source_current; + enum ad9545_output_mode output_mode; + u32 source_ua; + struct clk_hw hw; + unsigned int address; +}; + +struct ad9545_dpll_profile { + unsigned int address; + unsigned int parent_index; + unsigned int priority; + unsigned int loop_bw_uhz; + unsigned int fast_acq_excess_bw; + unsigned int fast_acq_timeout_ms; + unsigned int fast_acq_settle_ms; + bool en; + u8 tdc_source; +}; + +struct ad9545_ppl_clk { + struct ad9545_state *st; + bool pll_used; + unsigned int address; + struct clk_hw hw; + unsigned int num_parents; + const struct clk_hw **parents; + struct ad9545_dpll_profile profiles[AD9545_MAX_DPLL_PROFILES]; + unsigned int free_run_freq; + unsigned int fast_acq_trigger_mode; +}; + +struct ad9545_ref_in_clk { + struct clk_hw hw; + struct ad9545_state *st; + u32 r_div_ratio; + bool ref_used; + u32 d_tol_ppb; + u8 monitor_hyst_scale; + u32 valid_t_ms; + struct clk *parent_clk; + unsigned int address; + enum ad9545_ref_mode mode; + unsigned int freq_thresh_ps; + unsigned int phase_thresh_ps; + unsigned int phase_lock_fill_rate; + unsigned int phase_lock_drain_rate; + unsigned int freq_lock_fill_rate; + unsigned int freq_lock_drain_rate; + union { + enum ad9545_single_ended_config s_conf; + enum ad9545_diferential_config d_conf; + }; +}; + +struct ad9545_aux_nco_clk { + struct clk_hw hw; + bool nco_used; + struct ad9545_state *st; + unsigned int address; + unsigned int freq_thresh_ps; + unsigned int phase_thresh_ps; +}; + +struct ad9545_aux_tdc_clk { + struct clk_hw hw; + bool tdc_used; + struct ad9545_state *st; + unsigned int address; + unsigned int pin_nr; +}; + +struct ad9545_aux_dpll_clk { + struct clk_hw hw; + bool dpll_used; + struct ad9545_state *st; + unsigned int source; + unsigned int loop_bw_mhz; + unsigned int rate_change_limit; +}; + +struct ad9545_sys_clk { + bool sys_clk_freq_doubler; + bool sys_clk_crystal; + u32 ref_freq_hz; + u32 sys_freq_hz; +}; + +struct ad9545_state { + struct device *dev; + struct regmap *regmap; + struct ad9545_sys_clk sys_clk; + struct ad9545_aux_dpll_clk aux_dpll_clk; + struct ad9545_ppl_clk pll_clks[ARRAY_SIZE(ad9545_pll_clk_names)]; + struct ad9545_ref_in_clk ref_in_clks[ARRAY_SIZE(ad9545_ref_clk_names)]; + struct ad9545_out_clk out_clks[ARRAY_SIZE(ad9545_out_clk_names)]; + struct ad9545_aux_nco_clk aux_nco_clks[ARRAY_SIZE(ad9545_aux_nco_clk_names)]; + struct ad9545_aux_tdc_clk aux_tdc_clks[ARRAY_SIZE(ad9545_aux_tdc_clk_names)]; + struct clk **clks[4]; +}; + +#define to_ref_in_clk(_hw) container_of(_hw, struct ad9545_ref_in_clk, hw) +#define to_pll_clk(_hw) container_of(_hw, struct ad9545_ppl_clk, hw) +#define to_out_clk(_hw) container_of(_hw, struct ad9545_out_clk, hw) +#define to_nco_clk(_hw) container_of(_hw, struct ad9545_aux_nco_clk, hw) +#define to_tdc_clk(_hw) container_of(_hw, struct ad9545_aux_tdc_clk, hw) + +static int ad9545_parse_dt_inputs(struct ad9545_state *st) +{ + struct fwnode_handle *inputs_node; + struct fwnode_handle *fwnode; + struct fwnode_handle *child; + struct clk *clk; + bool prop_found; + int ref_ind; + u32 val; + int ret; + int i; + + fwnode = dev_fwnode(st->dev); + inputs_node = fwnode_get_named_child_node(fwnode, "ref-input-clks"); + if (!inputs_node) + return 0; + + prop_found = false; + fwnode_for_each_available_child_node(inputs_node, child) { + ret = fwnode_property_read_u32(child, "reg", &ref_ind); + if (ret < 0) { + dev_err(st->dev, "reg not specified in ref node."); + return ret; + } + + if (ref_ind > 3) + return -EINVAL; + + st->ref_in_clks[ref_ind].ref_used = true; + st->ref_in_clks[ref_ind].address = ref_ind; + st->ref_in_clks[ref_ind].st = st; + + prop_found = fwnode_property_present(child, "adi,single-ended-mode"); + if (prop_found) { + st->ref_in_clks[ref_ind].mode = AD9545_SINGLE_ENDED; + ret = fwnode_property_read_u32(child, "adi,single-ended-mode", &val); + if (ret < 0) + return ret; + + st->ref_in_clks[ref_ind].s_conf = val; + } else { + st->ref_in_clks[ref_ind].mode = AD9545_DIFFERENTIAL; + ret = fwnode_property_read_u32(child, "adi,differential-mode", &val); + if (ret < 0) + return ret; + + st->ref_in_clks[ref_ind].d_conf = val; + } + + ret = fwnode_property_read_u32(child, "adi,r-divider-ratio", &val); + if (ret < 0) { + dev_err(st->dev, "No r-divider-ratio specified for ref: %d", ref_ind); + return ret; + } + + st->ref_in_clks[ref_ind].r_div_ratio = val; + + ret = fwnode_property_read_u32(child, "adi,ref-dtol-pbb", &val); + if (ret < 0) { + dev_err(st->dev, "No ref-dtol-pbb specified for ref: %d", ref_ind); + return ret; + } + + st->ref_in_clks[ref_ind].d_tol_ppb = val; + + ret = fwnode_property_read_u32(child, "adi,ref-monitor-hysteresis-pbb", &val); + if (ret < 0) { + dev_err(st->dev, "No ref-monitor-hysteresis-pbb specified for ref: %d", + ref_ind); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(ad9545_hyst_scales_bp); i++) { + if (ad9545_hyst_scales_bp[i] == val) { + st->ref_in_clks[ref_ind].monitor_hyst_scale = i; + break; + } + } + + if (i == ARRAY_SIZE(ad9545_hyst_scales_bp)) + return -EINVAL; + + ret = fwnode_property_read_u32(child, "adi,ref-validation-timer-ms", &val); + if (ret < 0) { + dev_err(st->dev, "No ref-validation-timer-ms specified for ref: %d", + ref_ind); + return ret; + } + + st->ref_in_clks[ref_ind].valid_t_ms = val; + + ret = fwnode_property_read_u32(child, "adi,freq-lock-threshold-ps", &val); + if (ret < 0) { + dev_err(st->dev, "No freq-lock-threshold-ps specified for ref: %d", + ref_ind); + return ret; + } + + st->ref_in_clks[ref_ind].freq_thresh_ps = val; + + ret = fwnode_property_read_u32(child, "adi,phase-lock-threshold-ps", &val); + if (ret < 0) { + dev_err(st->dev, "No phase-lock-threshold-ps specified for ref: %d", + ref_ind); + return ret; + } + + st->ref_in_clks[ref_ind].phase_thresh_ps = val; + + ret = fwnode_property_read_u32(child, "adi,phase-lock-fill-rate", &val); + if (!ret) + st->ref_in_clks[ref_ind].phase_lock_fill_rate = val; + + ret = fwnode_property_read_u32(child, "adi,phase-lock-drain-rate", &val); + if (!ret) + st->ref_in_clks[ref_ind].phase_lock_drain_rate = val; + + ret = fwnode_property_read_u32(child, "adi,freq-lock-fill-rate", &val); + if (!ret) + st->ref_in_clks[ref_ind].freq_lock_fill_rate = val; + + ret = fwnode_property_read_u32(child, "adi,freq-lock-drain-rate", &val); + if (!ret) + st->ref_in_clks[ref_ind].freq_lock_drain_rate = val; + + clk = devm_clk_get(st->dev, ad9545_ref_clk_names[ref_ind]); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + st->ref_in_clks[ref_ind].parent_clk = clk; + } + + return 0; +} + +static int ad9545_parse_dt_plls(struct ad9545_state *st) +{ + struct fwnode_handle *profile_node; + struct fwnode_handle *pll_clks; + struct fwnode_handle *fwnode; + struct fwnode_handle *child; + u32 profile_addr; + u32 val; + u32 addr; + int ret; + + fwnode = dev_fwnode(st->dev); + pll_clks = fwnode_get_named_child_node(fwnode, "pll-clks"); + if (!pll_clks) + return 0; + + fwnode_for_each_available_child_node(pll_clks, child) { + ret = fwnode_property_read_u32(child, "reg", &addr); + if (ret < 0) + return ret; + + if (addr > 1) + return -EINVAL; + + st->pll_clks[addr].pll_used = true; + st->pll_clks[addr].address = addr; + + ret = fwnode_property_read_u32(profile_node, "adi,fast-acq-trigger-mode", &val); + if (!ret) + st->pll_clks[addr].fast_acq_trigger_mode = val; + + /* parse DPLL profiles */ + fwnode_for_each_available_child_node(child, profile_node) { + ret = fwnode_property_read_u32(profile_node, "reg", &profile_addr); + if (ret < 0) { + dev_err(st->dev, "Could not read Profile reg property."); + return ret; + } + + if (profile_addr >= AD9545_MAX_DPLL_PROFILES) + return -EINVAL; + + st->pll_clks[addr].profiles[profile_addr].en = true; + st->pll_clks[addr].profiles[profile_addr].address = profile_addr; + + ret = fwnode_property_read_u32(profile_node, "adi,pll-loop-bandwidth-uhz", &val); + if (ret < 0) { + dev_err(st->dev, "Could not read Profile %d, pll-loop-bandwidth.", + profile_addr); + return ret; + } + + st->pll_clks[addr].profiles[profile_addr].loop_bw_uhz = val; + + ret = fwnode_property_read_u32(profile_node, "adi,fast-acq-excess-bw", + &val); + if (!ret) + st->pll_clks[addr].profiles[profile_addr].fast_acq_excess_bw = val; + + ret = fwnode_property_read_u32(profile_node, "adi,fast-acq-timeout-ms", + &val); + if (!ret) + st->pll_clks[addr].profiles[profile_addr].fast_acq_timeout_ms = val; + + ret = fwnode_property_read_u32(profile_node, "adi,fast-acq-lock-settle-ms", + &val); + if (!ret) + st->pll_clks[addr].profiles[profile_addr].fast_acq_settle_ms = val; + + ret = fwnode_property_read_u32(profile_node, "adi,profile-priority", &val); + if (!ret) + st->pll_clks[addr].profiles[profile_addr].priority = val; + + ret = fwnode_property_read_u32(profile_node, "adi,pll-source", &val); + if (ret < 0) { + dev_err(st->dev, "Could not read Profile %d, pll-loop-bandwidth.", + profile_addr); + return ret; + } + + if (val > 5) + return -EINVAL; + + st->pll_clks[addr].profiles[profile_addr].tdc_source = val; + } + } + + return 0; +} + +static int ad9545_parse_dt_outputs(struct ad9545_state *st) +{ + struct fwnode_handle *output_clks; + struct fwnode_handle *fwnode; + struct fwnode_handle *child; + int out_ind; + u32 val; + int ret; + + fwnode = dev_fwnode(st->dev); + output_clks = fwnode_get_named_child_node(fwnode, "output-clks"); + if (!output_clks) + return 0; + + fwnode_for_each_available_child_node(output_clks, child) { + ret = fwnode_property_read_u32(child, "reg", &out_ind); + if (ret < 0) { + dev_err(st->dev, "No reg specified for output."); + return ret; + } + + if (out_ind > 9) + return -EINVAL; + + st->out_clks[out_ind].output_used = true; + st->out_clks[out_ind].address = out_ind; + + if (fwnode_property_present(child, "adi,current-source")) + st->out_clks[out_ind].source_current = true; + + ret = fwnode_property_read_u32(child, "adi,current-source-microamp", &val); + if (ret < 0) { + dev_err(st->dev, "No current-source-microamp specified for output: %d", + out_ind); + return ret; + } + + st->out_clks[out_ind].source_ua = val; + + ret = fwnode_property_read_u32(child, "adi,output-mode", &val); + if (ret < 0) { + dev_err(st->dev, "No output-mode specified for output: %d", out_ind); + return ret; + } + + st->out_clks[out_ind].output_mode = val; + } + + return 0; +} + +static int ad9545_parse_dt_ncos(struct ad9545_state *st) +{ + struct fwnode_handle *aux_ncos; + struct fwnode_handle *fwnode; + struct fwnode_handle *child; + u32 val; + u32 addr; + int ret; + + fwnode = dev_fwnode(st->dev); + aux_ncos = fwnode_get_named_child_node(fwnode, "aux-ncos"); + if (!aux_ncos) + return 0; + + fwnode_for_each_available_child_node(aux_ncos, child) { + ret = fwnode_property_read_u32(child, "reg", &addr); + if (ret < 0) { + dev_err(st->dev, "No reg specified for aux. NCO."); + return ret; + } + + if (addr > 1) + return -EINVAL; + + st->aux_nco_clks[addr].nco_used = true; + st->aux_nco_clks[addr].address = addr; + st->aux_nco_clks[addr].st = st; + + ret = fwnode_property_read_u32(child, "adi,freq-lock-threshold-ps", &val); + if (ret < 0) { + dev_err(st->dev, "No freq-lock-threshold-ps specified for aux. NCO: %d", + addr); + return ret; + } + + st->aux_nco_clks[addr].freq_thresh_ps = val; + + ret = fwnode_property_read_u32(child, "adi,phase-lock-threshold-ps", &val); + if (ret < 0) { + dev_err(st->dev, "No phase-lock-threshold-ps specified for aux. NCO: %d", + addr); + return ret; + } + + st->aux_nco_clks[addr].phase_thresh_ps = val; + } + + return 0; +} + +static int ad9545_parse_dt_tdcs(struct ad9545_state *st) +{ + struct fwnode_handle *aux_tdc_clks; + struct fwnode_handle *fwnode; + struct fwnode_handle *child; + u32 val; + u32 addr; + int ret; + + fwnode = dev_fwnode(st->dev); + aux_tdc_clks = fwnode_get_named_child_node(fwnode, "aux-tdc-clks"); + if (!aux_tdc_clks) + return 0; + + fwnode_for_each_available_child_node(aux_tdc_clks, child) { + ret = fwnode_property_read_u32(child, "reg", &addr); + if (ret < 0) { + dev_err(st->dev, "No reg specified for aux. TDC."); + return ret; + } + + if (addr > 1) { + dev_err(st->dev, "Invalid address: %u for aux. TDC.", addr); + return -EINVAL; + } + + st->aux_tdc_clks[addr].tdc_used = true; + st->aux_tdc_clks[addr].address = addr; + st->aux_tdc_clks[addr].st = st; + + ret = fwnode_property_read_u32(child, "adi,pin-nr", &val); + if (ret < 0) { + dev_err(st->dev, "No source pin specified for aux. TDC: %d", addr); + return ret; + } + + if (val >= ARRAY_SIZE(ad9545_ref_m_clk_names)) { + dev_err(st->dev, "Invalid Mx pin-nr: %d", val); + return -EINVAL; + } + + st->aux_tdc_clks[addr].pin_nr = val; + } + + return 0; +} + +static int ad9545_parse_dt_aux_dpll(struct ad9545_state *st) +{ + struct fwnode_handle *fwnode; + struct fwnode_handle *child; + u32 val; + int ret; + + fwnode = dev_fwnode(st->dev); + child = fwnode_get_named_child_node(fwnode, "aux-dpll"); + if (!child) + return 0; + + st->aux_dpll_clk.dpll_used = true; + st->aux_dpll_clk.st = st; + + ret = fwnode_property_read_u32(child, "adi,compensation-source", &val); + if (ret < 0) { + dev_err(st->dev, "No TDC source specified for aux. DPLL."); + return ret; + } + + st->aux_dpll_clk.source = val; + + ret = fwnode_property_read_u32(child, "adi,aux-dpll-bw-mhz", &val); + if (ret < 0) { + dev_err(st->dev, "No loop bw specified for aux. DPLL."); + return ret; + } + + st->aux_dpll_clk.loop_bw_mhz = val; + + ret = fwnode_property_read_u32(child, "adi,rate-change-limit", &val); + if (!ret) + st->aux_dpll_clk.rate_change_limit = val; + + return 0; +} + +static int ad9545_parse_dt(struct ad9545_state *st) +{ + struct fwnode_handle *fwnode; + int ret; + + fwnode = dev_fwnode(st->dev); + + ret = fwnode_property_read_u32(fwnode, "adi,ref-frequency-hz", &st->sys_clk.ref_freq_hz); + if (ret < 0) { + dev_err(st->dev, "No ref-frequency-hz specified."); + return ret; + } + + st->sys_clk.sys_clk_crystal = fwnode_property_present(fwnode, "adi,ref-crystal"); + st->sys_clk.sys_clk_freq_doubler = fwnode_property_present(fwnode, "adi,freq-doubler"); + + ret = ad9545_parse_dt_inputs(st); + if (ret < 0) + return ret; + + ret = ad9545_parse_dt_plls(st); + if (ret < 0) + return ret; + + ret = ad9545_parse_dt_outputs(st); + if (ret < 0) + return ret; + + ret = ad9545_parse_dt_ncos(st); + if (ret < 0) + return ret; + + ret = ad9545_parse_dt_tdcs(st); + if (ret < 0) + return ret; + + ret = ad9545_parse_dt_aux_dpll(st); + if (ret < 0) + return ret; + + return 0; +} + +static int ad9545_check_id(struct ad9545_state *st) +{ + u32 chip_id; + u32 val; + int ret; + + ret = regmap_read(st->regmap, AD9545_PRODUCT_ID_LOW, &val); + if (ret < 0) + return ret; + + chip_id = val; + ret = regmap_read(st->regmap, AD9545_PRODUCT_ID_HIGH, &val); + if (ret < 0) + return ret; + + chip_id += val << 8; + if (chip_id != AD9545_CHIP_ID) { + dev_err(st->dev, "Unrecognized CHIP_ID 0x%X\n", chip_id); + return -ENODEV; + } + + return 0; +} + +static int ad9545_io_update(struct ad9545_state *st) +{ + return regmap_write(st->regmap, AD9545_IO_UPDATE, AD9545_UPDATE_REGS); +} + +static int ad9545_sys_clk_setup(struct ad9545_state *st) +{ + u64 ref_freq_milihz; + __le64 regval64; + u8 div_ratio; + u32 fosc; + int ret; + u8 val; + u32 fs; + int i; + + /* + * System frequency must be between 2250 MHz and 2415 MHz. + * fs = fosc * K / j + * K - feedback divider ratio [4, 255] + * j = 1/2 if frequency doubler is enabled + */ + fosc = DIV_ROUND_UP(st->sys_clk.ref_freq_hz, 1000000); + + if (st->sys_clk.sys_clk_freq_doubler) + fosc *= 2; + + div_ratio = 0; + for (i = 4; i < 256; i++) { + fs = i * fosc; + + if (fs > 2250 && fs < 2415) { + div_ratio = i; + break; + } + } + + if (!div_ratio) { + dev_err(st->dev, "No feedback divider ratio for sys clk PLL found.\n"); + return -EINVAL; + } + + st->sys_clk.sys_freq_hz = st->sys_clk.ref_freq_hz * div_ratio; + if (st->sys_clk.sys_clk_freq_doubler) + st->sys_clk.sys_freq_hz *= 2; + + ret = regmap_write(st->regmap, AD9545_SYS_CLK_FB_DIV, div_ratio); + if (ret < 0) + return ret; + + /* enable crystal maintaining amplifier */ + val = 0; + if (st->sys_clk.sys_clk_crystal) + val |= BIT(3); + + if (st->sys_clk.sys_clk_freq_doubler) + val |= BIT(0); + + ret = regmap_write(st->regmap, AD9545_SYS_CLK_INPUT, val); + if (ret < 0) + return ret; + + /* write reference frequency provided at XOA, XOB in milliherz */ + ref_freq_milihz = mul_u32_u32(st->sys_clk.ref_freq_hz, 1000); + regval64 = cpu_to_le64(ref_freq_milihz); + + ret = regmap_bulk_write(st->regmap, AD9545_SYS_CLK_REF_FREQ, ®val64, 5); + if (ret < 0) + return ret; + + return regmap_write(st->regmap, AD9545_SYS_STABILITY_T, AD9545_SYS_CLK_STABILITY_MS); +} + +static int ad9545_get_q_div(struct ad9545_state *st, int addr, u32 *q_div) +{ + __le32 regval; + int ret; + + ret = regmap_bulk_read(st->regmap, AD9545_QX_DIV(addr), ®val, 4); + if (ret < 0) + return ret; + + *q_div = le32_to_cpu(regval); + + return 0; +} + +static int ad9545_set_q_div(struct ad9545_state *st, int addr, u32 q_div) +{ + __le32 regval; + int ret; + + regval = cpu_to_le32(q_div); + ret = regmap_bulk_write(st->regmap, AD9545_QX_DIV(addr), ®val, 4); + if (ret < 0) + return ret; + + return ad9545_io_update(st); +} + +static unsigned long ad95452_out_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct ad9545_out_clk *clk = to_out_clk(hw); + u32 qdiv; + int ret; + + ret = ad9545_get_q_div(clk->st, clk->address, &qdiv); + if (ret < 0) { + dev_err(clk->st->dev, "Could not read Q div value."); + return 0; + } + + return div_u64(parent_rate, qdiv); +} + +static long ad9545_out_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + u32 out_rate; + u32 qdiv; + + qdiv = div64_u64(*parent_rate, rate); + if (!qdiv) + out_rate = *parent_rate; + else + out_rate = div_u64(*parent_rate, qdiv); + + return out_rate; +} + +static int ad9545_out_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) +{ + struct ad9545_out_clk *clk = to_out_clk(hw); + u32 qdiv = div64_u64(parent_rate, rate); + + if (!qdiv) + qdiv = 1; + + return ad9545_set_q_div(clk->st, clk->address, qdiv); +} + +static int ad9545_out_clk_set_phase(struct clk_hw *hw, int degrees) +{ + struct ad9545_out_clk *clk = to_out_clk(hw); + u64 phase_code; + u32 phase_conf; + __le64 regval; + u32 half_div; + u32 qdiv; + int ret; + + ret = ad9545_get_q_div(clk->st, clk->address, &qdiv); + if (ret < 0) + return ret; + + ret = regmap_read(clk->st->regmap, AD9545_QX_PHASE_CONF(clk->address), &phase_conf); + if (ret < 0) + return ret; + + half_div = !!(phase_conf & AD9545_QX_HALF_DIV_MSK); + + /* Qxy phase bitfield depends on the current Q div value */ + phase_code = qdiv; + phase_code = div_u64((phase_code * 2 + half_div) * degrees, 360); + + /* Qxy phase bitfield is 33 bits long, with last bit in PHASE_CONF reg */ + regval = cpu_to_le64(phase_code & 0xFFFFFFFF); + ret = regmap_bulk_write(clk->st->regmap, AD9545_QX_PHASE(clk->address), ®val, 4); + if (ret < 0) + return ret; + + if (phase_code > U32_MAX) { + ret = regmap_update_bits(clk->st->regmap, AD9545_QX_PHASE_CONF(clk->address), + AD9545_QX_PHASE_32_MSK, AD9545_QX_PHASE_32_MSK); + if (ret < 0) + return ret; + } + + return ad9545_io_update(clk->st); +} + +static int ad9545_out_clk_get_phase(struct clk_hw *hw) +{ + struct ad9545_out_clk *clk = to_out_clk(hw); + u64 input_edges_nr; + u64 phase_code; + __le32 regval; + u32 phase_conf; + u32 qdiv; + int ret; + + ret = ad9545_get_q_div(clk->st, clk->address, &qdiv); + if (ret < 0) + return ret; + + ret = regmap_read(clk->st->regmap, AD9545_QX_PHASE_CONF(clk->address), &phase_conf); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(clk->st->regmap, AD9545_QX_PHASE(clk->address), ®val, 4); + if (ret < 0) + return ret; + + /* Qxy phase bitfield is 33 bits long, with last bit in PHASE_CONF reg */ + phase_code = !!(phase_conf & AD9545_QX_PHASE_32_MSK); + phase_code = (phase_code >> 32) + cpu_to_le32(regval); + + input_edges_nr = 2 * qdiv + !!(phase_conf & AD9545_QX_HALF_DIV_MSK); + + /* + * phase = 360 * (Qxy Phase / E) where: + * E is the total number of input edges per output period of the Q-divider. + */ + return div64_u64(phase_code * 360, input_edges_nr); +} + +static int ad9545_output_muting(struct ad9545_out_clk *clk, bool mute) +{ + u8 regval = 0; + int ret; + u8 mask; + + if (clk->address % 2) + mask = AD9545_DIV_OPS_MUTE_AA_MSK; + else + mask = AD9545_DIV_OPS_MUTE_A_MSK; + + if (mute) + regval = mask; + + ret = regmap_update_bits(clk->st->regmap, AD9545_DIV_OPS_QX(clk->address), mask, regval); + if (ret < 0) + return ret; + + return ad9545_io_update(clk->st); +} + +static int ad9545_out_clk_enable(struct clk_hw *hw) +{ + struct ad9545_out_clk *clk = to_out_clk(hw); + + return ad9545_output_muting(clk, false); +} + +static void ad9545_out_clk_disable(struct clk_hw *hw) +{ + struct ad9545_out_clk *clk = to_out_clk(hw); + + ad9545_output_muting(clk, true); +} + +static int ad9545_out_clk_is_enabled(struct clk_hw *hw) +{ + struct ad9545_out_clk *clk = to_out_clk(hw); + u32 regval; + int ret; + u8 mask; + + if (clk->address % 2) + mask = AD9545_DIV_OPS_MUTE_AA_MSK; + else + mask = AD9545_DIV_OPS_MUTE_A_MSK; + + ret = regmap_read(clk->st->regmap, AD9545_DIV_OPS_QX(clk->address), ®val); + if (ret < 0) + return ret; + + return !!(mask & regval); +} + +static const struct clk_ops ad9545_out_clk_ops = { + .enable = ad9545_out_clk_enable, + .disable = ad9545_out_clk_disable, + .is_enabled = ad9545_out_clk_is_enabled, + .recalc_rate = ad95452_out_clk_recalc_rate, + .round_rate = ad9545_out_clk_round_rate, + .set_rate = ad9545_out_clk_set_rate, + .set_phase = ad9545_out_clk_set_phase, + .get_phase = ad9545_out_clk_get_phase, +}; + +static int ad9545_outputs_setup(struct ad9545_state *st) +{ + struct clk_init_data init[ARRAY_SIZE(ad9545_out_clk_names)] = {0}; + int out_i; + u16 addr; + int ret; + u8 reg; + int i; + int j; + + /* configure current sources */ + for (i = 0; i < ARRAY_SIZE(ad9545_out_clk_names) / 2; i++) { + st->out_clks[i * 2].st = st; + st->out_clks[i * 2 + 1].st = st; + + if (st->out_clks[i * 2].output_used) + out_i = i * 2; + else if (st->out_clks[i * 2 + 1].output_used) + out_i = i * 2 + 1; + else + continue; + + reg = 0; + if (st->out_clks[out_i].source_current) + reg = 1; + + for (j = 0; j < ARRAY_SIZE(ad9545_out_source_ua); j++) + if (ad9545_out_source_ua[j] == st->out_clks[out_i].source_ua) + reg |= FIELD_PREP(GENMASK(2, 1), i); + + reg |= FIELD_PREP(GENMASK(4, 3), st->out_clks[out_i].output_mode); + + if (i < 3) + addr = AD9545_DRIVER_0A_CONF + i; + else + addr = AD9545_DRIVER_1A_CONF + (i - 3); + + ret = regmap_write(st->regmap, addr, reg); + if (ret < 0) + return ret; + } + + st->clks[AD9545_CLK_OUT] = devm_kzalloc(st->dev, ARRAY_SIZE(ad9545_out_clk_names) * + sizeof(struct clk *), GFP_KERNEL); + if (!st->clks[AD9545_CLK_OUT]) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(ad9545_out_clk_names); i++) { + if (!st->out_clks[i].output_used) + continue; + + init[i].name = ad9545_out_clk_names[i]; + init[i].ops = &ad9545_out_clk_ops; + + if (i > 5) + init[i].parent_names = &ad9545_pll_clk_names[1]; + else + init[i].parent_names = &ad9545_pll_clk_names[0]; + + init[i].num_parents = 1; + + st->out_clks[i].hw.init = &init[i]; + ret = devm_clk_hw_register(st->dev, &st->out_clks[i].hw); + if (ret < 0) + return ret; + + st->clks[AD9545_CLK_OUT][i] = st->out_clks[i].hw.clk; + } + + /* set to autosync to trigger on DPLL freq lock */ + for (i = 0; i < ARRAY_SIZE(st->pll_clks); i++) { + reg = FIELD_PREP(AD9545_SYNC_CTRL_MODE_MSK, 3); + ret = regmap_write(st->regmap, AD9545_SYNC_CTRLX(i), reg); + if (ret < 0) + return ret; + } + + return ad9545_io_update(st); +} + +static int ad9545_set_r_div(struct ad9545_state *st, u32 div, int addr) +{ + int ret; + u8 reg; + int i; + + if (div > AD9545_R_DIV_MAX) + return -EINVAL; + + /* r-div ratios are mapped from 0 onward */ + div -= 1; + for (i = 0; i < 4; i++) { + reg = (div >> (i * 8)) & 0xFF; + + ret = regmap_write(st->regmap, AD9545_REF_X_RDIV(addr) + i, reg); + if (ret < 0) + return ret; + } + + return ad9545_io_update(st); +} + +static int ad9545_get_r_div(struct ad9545_state *st, int addr, u32 *r_div) +{ + int ret; + u32 div; + u32 reg; + int i; + + div = 0; + for (i = 0; i < 4; i++) { + ret = regmap_read(st->regmap, AD9545_REF_X_RDIV(addr) + i, ®); + if (ret < 0) + return ret; + + div += (reg << (i * 8)); + } + + /* r-div ratios are mapped from 0 onward */ + *r_div = ++div; + + return 0; +} + +static unsigned long ad95452_in_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct ad9545_ref_in_clk *clk = to_ref_in_clk(hw); + u32 div; + int ret; + + ret = ad9545_get_r_div(clk->st, clk->address, &div); + if (ret < 0) { + dev_err(clk->st->dev, "Could not read r div value."); + return 1; + } + + return DIV_ROUND_CLOSEST(parent_rate, div); +} + +static const struct clk_ops ad9545_in_clk_ops = { + .recalc_rate = ad95452_in_clk_recalc_rate, +}; + +static int ad9545_input_refs_setup(struct ad9545_state *st) +{ + struct clk_init_data init[4] = {0}; + __le32 regval; + __le64 regval64; + u64 period_es; + int ret; + u32 val; + u8 reg; + int i; + + /* configure input references */ + for (i = 0; i < ARRAY_SIZE(st->ref_in_clks); i += 2) { + if (st->ref_in_clks[i].mode == AD9545_DIFFERENTIAL) { + reg = BIT(0); + reg |= FIELD_PREP(AD9545_REF_CTRL_DIF_MSK, st->ref_in_clks[i].d_conf); + } else { + reg = 0; + reg |= FIELD_PREP(AD9545_REF_CTRL_REFA_MSK, st->ref_in_clks[i].s_conf); + reg |= FIELD_PREP(AD9545_REF_CTRL_REFAA_MSK, st->ref_in_clks[i + 1].s_conf); + } + + ret = regmap_write(st->regmap, AD9545_REF_A_CTRL + i * 2, reg); + if (ret < 0) + return ret; + } + + /* configure refs r dividers */ + for (i = 0; i < ARRAY_SIZE(st->ref_in_clks); i++) { + ret = ad9545_set_r_div(st, st->ref_in_clks[i].r_div_ratio, i); + if (ret < 0) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(st->ref_in_clks); i++) { + if (!st->ref_in_clks[i].ref_used) + continue; + + /* write nominal period in attoseconds */ + period_es = 1000000000000000000ULL; + val = clk_get_rate(st->ref_in_clks[i].parent_clk); + if (!val) + return -EINVAL; + + period_es = div_u64(period_es, val); + + regval = cpu_to_le32(st->ref_in_clks[i].d_tol_ppb); + ret = regmap_bulk_write(st->regmap, AD9545_REF_X_OFFSET_LIMIT(i), + ®val, 3); + if (ret < 0) + return ret; + + regval64 = cpu_to_le64(period_es); + ret = regmap_bulk_write(st->regmap, AD9545_REF_X_PERIOD(i), ®val64, 8); + if (ret < 0) + return ret; + + ret = regmap_write(st->regmap, AD9545_REF_X_MONITOR_HYST(i), + st->ref_in_clks[i].monitor_hyst_scale); + if (ret < 0) + return ret; + + regval = cpu_to_le32(st->ref_in_clks[i].freq_thresh_ps); + ret = regmap_bulk_write(st->regmap, AD9545_SOURCEX_FREQ_THRESH(i), + ®val, 3); + if (ret < 0) + return ret; + + regval = cpu_to_le32(st->ref_in_clks[i].phase_thresh_ps); + ret = regmap_bulk_write(st->regmap, AD9545_SOURCEX_PHASE_THRESH(i), + ®val, 3); + if (ret < 0) + return ret; + + regval = st->ref_in_clks[i].freq_lock_fill_rate; + if (regval) { + ret = regmap_write(st->regmap, AD9545_REF_X_FREQ_LOCK_FILL(i), regval); + if (ret < 0) + return ret; + } + + regval = st->ref_in_clks[i].freq_lock_drain_rate; + if (regval) { + ret = regmap_write(st->regmap, AD9545_REF_X_FREQ_LOCK_DRAIN(i), regval); + if (ret < 0) + return ret; + } + + regval = st->ref_in_clks[i].phase_lock_fill_rate; + if (regval) { + ret = regmap_write(st->regmap, AD9545_REF_X_PHASE_LOCK_FILL(i), regval); + if (ret < 0) + return ret; + } + + regval = st->ref_in_clks[i].phase_lock_drain_rate; + if (regval) { + ret = regmap_write(st->regmap, AD9545_REF_X_PHASE_LOCK_DRAIN(i), regval); + if (ret < 0) + return ret; + } + + init[i].name = ad9545_in_clk_names[i]; + init[i].ops = &ad9545_in_clk_ops; + init[i].parent_names = &ad9545_ref_clk_names[i]; + init[i].num_parents = 1; + + st->ref_in_clks[i].hw.init = &init[i]; + ret = devm_clk_hw_register(st->dev, &st->ref_in_clks[i].hw); + if (ret < 0) + return ret; + } + + /* disable unused references */ + reg = 0; + for (i = 0; i < ARRAY_SIZE(st->ref_in_clks); i++) { + if (!st->ref_in_clks[i].ref_used) + reg |= (1 << i); + } + + return regmap_write(st->regmap, AD9545_POWER_DOWN_REF, reg); +} + +static int ad9545_calc_ftw(struct ad9545_ppl_clk *clk, u32 freq, u64 *tuning_word) +{ + u64 ftw = 1; + u32 ftw_frac; + u32 ftw_int; + + /* + * In case of unlock event the DPLL will go in open-loop mode and output + * the freq given by the freerun tuning word. + * DPLLx Freerun TW = (2 ^ 48) × (f NCO /f System ) + */ + ftw = mul_u64_u32_div(ftw << 48, freq, clk->st->sys_clk.sys_freq_hz); + + /* + * Check if FTW is valid: + * (2 ^ 48) / FTW = INT.FRAC where: + * 7 ≤ INT ≤ 13 and 0.05 ≤ FRAC ≤ 0.95 + */ + ftw_int = div64_u64(1ULL << 48, ftw); + if (ftw_int < 7 || ftw_int > 13) + return -EINVAL; + + div_u64_rem(div64_u64(100 * (1ULL << 48), ftw), 100, &ftw_frac); + if (ftw_frac < 5 || ftw_frac > 95) + return -EINVAL; + + *tuning_word = ftw; + + return 0; +} + +static int ad9545_set_freerun_freq(struct ad9545_ppl_clk *clk, u32 freq) +{ + __le64 regval; + u64 ftw; + int ret; + + ret = ad9545_calc_ftw(clk, freq, &ftw); + if (ret < 0) + return ret; + + regval = cpu_to_le64(ftw); + ret = regmap_bulk_write(clk->st->regmap, AD9545_DPLLX_FTW(clk->address), ®val, 6); + if (ret < 0) + return ret; + + clk->free_run_freq = freq; + return ad9545_io_update(clk->st); +} + +static u32 ad9545_calc_m_div(unsigned long rate) +{ + u32 m_div; + + /* + * PFD of APLL has input frequency limits in 162 - 350 Mghz range. + * Use APLL to upconvert this freq to Ghz range. + */ + m_div = div_u64(rate, ad9545_apll_pfd_rate_ranges_hz[0] / 2 + + ad9545_apll_pfd_rate_ranges_hz[1] / 2); + m_div = clamp_t(u8, m_div, AD9545_APLL_M_DIV_MIN, AD9545_APLL_M_DIV_MAX); + + return m_div; +} + +static u64 ad9545_calc_pll_params(struct ad9545_ppl_clk *clk, unsigned long rate, + unsigned long parent_rate, u32 *m, u32 *n, + unsigned long *frac, unsigned long *mod) +{ + u32 min_dpll_n_div; + u64 output_rate; + u32 dpll_n_div; + u32 m_div; + u64 den; + u64 num; + + /* half divider at output requires APLL to generate twice the frequency demanded */ + rate *= 2; + + m_div = ad9545_calc_m_div(rate); + + /* + * If N + FRAC / MOD = rate / (m_div * parent_rate) + * and N = [rate / (m_div * past_rate)]: + * We get: FRAC/MOD = (rate / (m_div * parent_rate)) - N + */ + dpll_n_div = div64_u64(rate, parent_rate * m_div); + + /* + * APLL has to be able to satisfy output freq bounds + * thus output of DPLL has a lower bound + */ + min_dpll_n_div = div_u64(ad9545_apll_rate_ranges_hz[clk->address][0], + AD9545_APLL_M_DIV_MAX * parent_rate); + dpll_n_div = clamp_t(u32, dpll_n_div, min_dpll_n_div, AD9545_DPLL_MAX_N); + + num = rate - (dpll_n_div * m_div * parent_rate); + den = m_div * parent_rate; + + rational_best_approximation(num, den, AD9545_DPLL_MAX_FRAC, AD9545_DPLL_MAX_MOD, frac, mod); + *m = m_div; + *n = dpll_n_div; + + output_rate = mul_u64_u32_div(*frac * parent_rate, m_div, *mod); + output_rate += parent_rate * dpll_n_div * m_div; + + return (u32)DIV_ROUND_CLOSEST(output_rate, 2); +} + +static int ad9545_tdc_source_valid(struct ad9545_ppl_clk *clk, unsigned int tdc_source) +{ + unsigned int regval; + int ret; + + if (tdc_source >= AD9545_MAX_REFS) { + ret = regmap_read(clk->st->regmap, AD9545_MISC, ®val); + if (ret < 0) + return ret; + + if (tdc_source == AD9545_MAX_REFS) + return !(regval & AD9545_MISC_AUX_NC0_ERR_MSK); + else + return !(regval & AD9545_MISC_AUX_NC1_ERR_MSK); + } else { + ret = regmap_read(clk->st->regmap, AD9545_REFX_STATUS(tdc_source), ®val); + if (ret < 0) + return ret; + + return !!(regval & AD9545_REFX_VALID_MSK); + } +} + +static u8 ad9545_pll_get_parent(struct clk_hw *hw) +{ + struct ad9545_ppl_clk *clk = to_pll_clk(hw); + struct ad9545_dpll_profile *profile; + u8 best_prio = 0xFF; + u8 best_parent; + int ret; + int i; + + ret = ad9545_io_update(clk->st); + if (ret < 0) + return ret; + + /* + * A DPLL will pick a parent clock depending + * on the priorities and if it is a valid timestamp source. + */ + for (i = 0; i < AD9545_MAX_DPLL_PROFILES; i++) { + profile = &clk->profiles[i]; + if (!profile->en) + continue; + + ret = ad9545_tdc_source_valid(clk, profile->tdc_source); + if (ret < 0) + return clk->num_parents; + + if (ret > 0 && profile->priority < best_prio) { + best_prio = profile->priority; + best_parent = profile->parent_index; + } + } + + if (best_prio != 0xFF) + return best_parent; + + return clk->num_parents; +} + +static unsigned long ad9545_pll_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct ad9545_ppl_clk *clk = to_pll_clk(hw); + unsigned long output_rate; + __le32 regval; + u32 frac; + u32 mod; + int ret; + u32 m; + u32 n; + int i; + + m = 0; + ret = regmap_read(clk->st->regmap, AD9545_APLLX_M_DIV(clk->address), &m); + if (ret < 0) + return ret; + + /* + * If no ref is valid, pll will run in free run mode. + * At this point the NCO of the DPLL will output a free run frequency + * thus the output frequency of the PLL block will be: + * f NCO * M-div / 2 + */ + i = ad9545_pll_get_parent(hw); + if (i == clk->num_parents) + return div_u64(clk->free_run_freq * m, 2); + + parent_rate = clk_hw_get_rate(clk->parents[i]); + + ret = regmap_bulk_read(clk->st->regmap, AD9545_DPLLX_N_DIV(clk->address, i), ®val, 4); + if (ret < 0) + return ret; + + n = le32_to_cpu(regval) + 1; + + regval = 0; + ret = regmap_bulk_read(clk->st->regmap, AD9545_DPLLX_FRAC_DIV(clk->address, i), ®val, 3); + if (ret < 0) + return ret; + + frac = le32_to_cpu(regval); + + regval = 0; + ret = regmap_bulk_read(clk->st->regmap, AD9545_DPLLX_MOD_DIV(clk->address, i), ®val, 3); + if (ret < 0) + return ret; + + mod = le32_to_cpu(regval); + + /* Output rate of APLL = parent_rate * (N + (Frac / Mod)) * M */ + output_rate = mul_u64_u32_div(frac * parent_rate, m, mod); + output_rate += parent_rate * n * m; + + return output_rate / 2; +} + +static long ad9545_pll_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ad9545_ppl_clk *clk = to_pll_clk(hw); + unsigned long frac; + unsigned long mod; + int ret; + u64 ftw; + int i; + u32 m; + u32 n; + + /* if no ref is valid, check if requested rate can be set in free run mode */ + i = ad9545_pll_get_parent(hw); + if (i == clk->num_parents) { + /* in free run mode output freq is given by f NCO * m / 2 */ + m = ad9545_calc_m_div(rate * 2); + ret = ad9545_calc_ftw(clk, div_u64(rate * 2, m), &ftw); + if (ret < 0) + return 0; + + return rate; + } + + return ad9545_calc_pll_params(clk, rate, *parent_rate, &m, &n, &frac, &mod); +} + +static int ad9545_pll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) +{ + struct ad9545_ppl_clk *clk = to_pll_clk(hw); + unsigned long out_rate; + unsigned long frac; + unsigned long mod; + __le32 regval; + int ret; + u32 m; + u32 n; + int i; + + /* + * When setting a PLL rate, precalculate params for all enabled profiles. + * At this point there may or may not be a valid reference. + */ + for (i = 0; i < clk->num_parents; i++) { + parent_rate = clk_hw_get_rate(clk->parents[i]); + + out_rate = ad9545_calc_pll_params(clk, rate, parent_rate, &m, &n, &frac, &mod); + if (out_rate != rate) + return -EINVAL; + + regval = cpu_to_le32(n - 1); + ret = regmap_bulk_write(clk->st->regmap, AD9545_DPLLX_N_DIV(clk->address, i), + ®val, 4); + if (ret < 0) + return ret; + + ret = regmap_write(clk->st->regmap, AD9545_APLLX_M_DIV(clk->address), m); + if (ret < 0) + return ret; + + regval = cpu_to_le32(frac); + ret = regmap_bulk_write(clk->st->regmap, AD9545_DPLLX_FRAC_DIV(clk->address, i), + ®val, 3); + if (ret < 0) + return ret; + + regval = cpu_to_le32(mod); + ret = regmap_bulk_write(clk->st->regmap, AD9545_DPLLX_MOD_DIV(clk->address, i), + ®val, 3); + if (ret < 0) + return ret; + } + + return ad9545_set_freerun_freq(clk, div_u64(rate * 2, m)); +} + +static const struct clk_ops ad9545_pll_clk_ops = { + .recalc_rate = ad9545_pll_clk_recalc_rate, + .round_rate = ad9545_pll_clk_round_rate, + .set_rate = ad9545_pll_set_rate, + .get_parent = ad9545_pll_get_parent, +}; + +static int ad9545_pll_fast_acq_setup(struct ad9545_ppl_clk *pll, int profile) +{ + struct ad9545_state *st = pll->st; + unsigned int tmp; + int ret; + u8 reg; + int i; + + tmp = pll->profiles[profile].fast_acq_excess_bw; + for (i = 0; i < ARRAY_SIZE(ad9545_fast_acq_excess_bw_map); i++) + if (tmp == ad9545_fast_acq_excess_bw_map[i]) + break; + + if (i < ARRAY_SIZE(ad9545_fast_acq_excess_bw_map)) { + ret = regmap_write(st->regmap, AD9545_DPLLX_FAST_L1(pll->address, profile), i); + if (ret < 0) + return ret; + } else { + dev_err(st->dev, "Wrong fast_acq_excess_bw value for DPLL %u, profile: %d.", + pll->address, profile); + return -EINVAL; + } + + tmp = pll->profiles[profile].fast_acq_settle_ms; + for (i = 0; i < ARRAY_SIZE(ad9545_fast_acq_timeout_map); i++) + if (tmp == ad9545_fast_acq_timeout_map[i]) + break; + + if (i == ARRAY_SIZE(ad9545_fast_acq_timeout_map)) { + dev_err(st->dev, "Wrong fast_acq_settle_ms value for DPLL %u, profile %d.", + pll->address, profile); + return -EINVAL; + } + + reg = i; + + tmp = pll->profiles[profile].fast_acq_timeout_ms; + for (i = 0; i < ARRAY_SIZE(ad9545_fast_acq_timeout_map); i++) + if (tmp == ad9545_fast_acq_timeout_map[i]) + break; + + if (i == ARRAY_SIZE(ad9545_fast_acq_timeout_map)) { + dev_err(st->dev, "Wrong fast_acq_timeout_ms value for DPLL %u, profile %d.", + pll->address, profile); + return -EINVAL; + } + + reg |= i << 4; + + ret = regmap_write(st->regmap, AD9545_DPLLX_FAST_L2(pll->address, profile), reg); + if (ret < 0) + return ret; + + return regmap_write(st->regmap, AD9545_DPLLX_FAST_MODE(pll->address), + pll->fast_acq_trigger_mode); +} + +static int ad9545_plls_setup(struct ad9545_state *st) +{ + struct clk_init_data init[2] = {0}; + struct ad9545_ppl_clk *pll; + struct clk_hw *hw; + int tdc_source; + __le32 regval; + int ret; + u8 reg; + int i; + int j; + + st->clks[AD9545_CLK_PLL] = devm_kzalloc(st->dev, ARRAY_SIZE(ad9545_pll_clk_names) * + sizeof(struct clk *), GFP_KERNEL); + if (!st->clks[AD9545_CLK_PLL]) + return -ENOMEM; + + for (i = 0; i < 2; i++) { + pll = &st->pll_clks[i]; + if (!pll->pll_used) + continue; + + pll->st = st; + pll->address = i; + + init[i].name = ad9545_pll_clk_names[i]; + init[i].ops = &ad9545_pll_clk_ops; + init[i].num_parents = 0; + for (j = 0; j < AD9545_MAX_DPLL_PROFILES; j++) + if (pll->profiles[j].en) + init[i].num_parents++; + + init[i].parent_hws = devm_kzalloc(st->dev, init[i].num_parents * + sizeof(*init[i].parent_hws), GFP_KERNEL); + if (!init[i].parent_hws) + return -ENOMEM; + + pll->num_parents = init[i].num_parents; + pll->parents = init[i].parent_hws; + + for (j = 0; j < AD9545_MAX_DPLL_PROFILES; j++) { + if (!pll->profiles[j].en) + continue; + + pll->profiles[j].parent_index = j; + + /* enable pll profile */ + reg = AD9545_EN_PROFILE_MSK | + FIELD_PREP(AD9545_SEL_PRIORITY_MSK, pll->profiles[j].priority); + ret = regmap_write(st->regmap, AD9545_DPLLX_EN(i, j), reg); + if (ret < 0) + return ret; + + /* set TDC source */ + tdc_source = pll->profiles[j].tdc_source; + ret = regmap_write(st->regmap, AD9545_DPLLX_SOURCE(i, j), + ad9545_tdc_source_mapping[tdc_source]); + if (ret < 0) + return ret; + + regval = cpu_to_le32(pll->profiles[j].loop_bw_uhz); + ret = regmap_bulk_write(st->regmap, AD9545_DPLLX_LOOP_BW(i, j), ®val, 4); + if (ret < 0) + return ret; + + if (pll->profiles[j].tdc_source >= ARRAY_SIZE(ad9545_ref_clk_names)) + hw = &st->aux_nco_clks[tdc_source - ARRAY_SIZE(ad9545_ref_clk_names)].hw; + else + hw = &st->ref_in_clks[tdc_source].hw; + init[i].parent_hws[j] = hw; + + if (pll->profiles[j].fast_acq_excess_bw > 0) { + ret = ad9545_pll_fast_acq_setup(pll, j); + if (ret < 0) + return ret; + } + } + + pll->hw.init = &init[i]; + ret = devm_clk_hw_register(st->dev, &pll->hw); + if (ret < 0) + return ret; + + st->clks[AD9545_CLK_PLL][i] = pll->hw.clk; + } + + return 0; +} + +static int ad9545_get_nco_freq(struct ad9545_state *st, int addr, u32 *freq) +{ + __le16 regval; + int ret; + + ret = regmap_bulk_read(st->regmap, AD9545_NCOX_FREQ(addr), ®val, 2); + if (ret < 0) + return ret; + + *freq = le16_to_cpu(regval); + return 0; +} + +static int ad9545_set_nco_freq(struct ad9545_state *st, int addr, u32 freq) +{ + __le32 regval; + int ret; + + regval = cpu_to_le32(freq); + ret = regmap_bulk_write(st->regmap, AD9545_NCOX_FREQ(addr), ®val, 2); + if (ret < 0) + return ret; + + return ad9545_io_update(st); +} + +static unsigned long ad95452_nco_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct ad9545_aux_nco_clk *clk = to_nco_clk(hw); + u32 rate; + int ret; + + ret = ad9545_get_nco_freq(clk->st, clk->address, &rate); + if (ret < 0) { + dev_err(clk->st->dev, "Could not read NCO freq."); + return 0; + } + + return rate; +} + +static long ad9545_nco_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return clamp_t(u16, rate, 1, AD9545_NCO_MAX_FREQ); +} + +static int ad9545_nco_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) +{ + struct ad9545_aux_nco_clk *clk = to_nco_clk(hw); + + return ad9545_set_nco_freq(clk->st, clk->address, rate); +} + +static const struct clk_ops ad9545_nco_clk_ops = { + .recalc_rate = ad95452_nco_clk_recalc_rate, + .round_rate = ad9545_nco_clk_round_rate, + .set_rate = ad9545_nco_clk_set_rate, +}; + +static int ad9545_aux_ncos_setup(struct ad9545_state *st) +{ + struct clk_init_data init[2] = {0}; + struct ad9545_aux_nco_clk *nco; + __le32 regval; + int ret; + int i; + + st->clks[AD9545_CLK_NCO] = devm_kzalloc(st->dev, ARRAY_SIZE(ad9545_aux_nco_clk_names) * + sizeof(struct clk *), GFP_KERNEL); + if (!st->clks[AD9545_CLK_NCO]) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(st->aux_nco_clks); i++) { + nco = &st->aux_nco_clks[i]; + if (!nco->nco_used) + continue; + + regval = cpu_to_le32(nco->freq_thresh_ps); + ret = regmap_bulk_write(st->regmap, AD9545_NCOX_FREQ_THRESH(i), ®val, 3); + if (ret < 0) + return ret; + + regval = cpu_to_le32(nco->phase_thresh_ps); + ret = regmap_bulk_write(st->regmap, AD9545_NCOX_PHASE_THRESH(i), ®val, 3); + if (ret < 0) + return ret; + + init[i].name = ad9545_aux_nco_clk_names[i]; + init[i].ops = &ad9545_nco_clk_ops; + + nco->hw.init = &init[i]; + ret = devm_clk_hw_register(st->dev, &nco->hw); + if (ret < 0) + return ret; + + st->clks[AD9545_CLK_NCO][i] = nco->hw.clk; + } + + return 0; +} + +static int ad9545_set_tdc_div(struct ad9545_aux_tdc_clk *clk, u32 div) +{ + int ret; + + if (!div) + return -EINVAL; + + ret = regmap_write(clk->st->regmap, AD9545_TDCX_DIV(clk->address), --div); + if (ret < 0) + return ret; + + return ad9545_io_update(clk->st); +} + +static unsigned long ad9545_tdc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct ad9545_aux_tdc_clk *clk = to_tdc_clk(hw); + int ret; + u32 div; + + ret = regmap_read(clk->st->regmap, AD9545_TDCX_DIV(clk->address), &div); + if (ret < 0) { + dev_err(clk->st->dev, "Could not read TDC freq."); + return ret; + } + + div++; + + return DIV_ROUND_UP_ULL((u64)parent_rate, div); +} + +static long ad9545_tdc_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + u32 div; + + if (!*parent_rate || !rate) + return 0; + + div = clamp_t(u8, DIV_ROUND_UP_ULL((u64)*parent_rate, rate), 1, U8_MAX); + + return DIV_ROUND_UP_ULL((u64)*parent_rate, div); +} + +static int ad9545_tdc_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) +{ + struct ad9545_aux_tdc_clk *clk = to_tdc_clk(hw); + __le64 regval64; + u64 period_es; + u32 div; + int ret; + + if (!parent_rate || !rate) + return 0; + + /* write parent rate received at the Mx pin in attoseconds */ + period_es = div_u64(1000000000000000000ULL, parent_rate); + regval64 = cpu_to_le64(period_es); + ret = regmap_bulk_write(clk->st->regmap, AD9545_TDCX_PERIOD(clk->address), ®val64, 8); + if (ret < 0) + return ret; + + div = clamp_t(u8, DIV_ROUND_UP_ULL((u64)parent_rate, rate), 1, U8_MAX); + + return ad9545_set_tdc_div(clk, div); +} + +static const struct clk_ops ad9545_aux_tdc_clk_ops = { + .recalc_rate = ad9545_tdc_clk_recalc_rate, + .round_rate = ad9545_tdc_clk_round_rate, + .set_rate = ad9545_tdc_clk_set_rate, +}; + +static int ad9545_aux_tdcs_setup(struct ad9545_state *st) +{ + struct clk_init_data init[ARRAY_SIZE(ad9545_aux_tdc_clk_names)] = {0}; + struct ad9545_aux_tdc_clk *tdc; + int ret; + int i; + + st->clks[AD9545_CLK_AUX_TDC] = devm_kcalloc(st->dev, ARRAY_SIZE(ad9545_aux_tdc_clk_names), + sizeof(*st->clks[AD9545_CLK_AUX_TDC]), + GFP_KERNEL); + if (!st->clks[AD9545_CLK_AUX_TDC]) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(st->aux_tdc_clks); i++) { + tdc = &st->aux_tdc_clks[i]; + if (!tdc->tdc_used) + continue; + + /* redirect Mx pin to this TDC */ + ret = regmap_write(st->regmap, AD9545_MX_PIN(tdc->pin_nr), + AD9545_MX_TO_TDCX(tdc->address)); + if (ret < 0) + return ret; + + init[i].name = ad9545_aux_tdc_clk_names[i]; + init[i].ops = &ad9545_aux_tdc_clk_ops; + init[i].parent_names = &ad9545_ref_m_clk_names[tdc->pin_nr]; + init[i].num_parents = 1; + tdc->hw.init = &init[i]; + ret = devm_clk_hw_register(st->dev, &tdc->hw); + if (ret < 0) + return ret; + + st->clks[AD9545_CLK_AUX_TDC][i] = tdc->hw.clk; + } + + return 0; +} + +static unsigned long ad9545_aux_dpll_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + return parent_rate; +} + +static const struct clk_ops ad9545_aux_dpll_clk_ops = { + .recalc_rate = ad9545_aux_dpll_clk_recalc_rate, +}; + +static int ad9545_aux_dpll_setup(struct ad9545_state *st) +{ + struct ad9545_aux_dpll_clk *clk; + struct clk_init_data init; + __le16 regval; + int ret; + u32 val; + int i; + + clk = &st->aux_dpll_clk; + if (!clk->dpll_used) + return 0; + + memset(&init, 0, sizeof(struct clk_init_data)); + + if (clk->source < ARRAY_SIZE(ad9545_in_clk_names)) { + val = clk->source; + init.parent_names = &ad9545_in_clk_names[val]; + } else { + val = clk->source - ARRAY_SIZE(ad9545_in_clk_names); + if (val > ARRAY_SIZE(ad9545_aux_tdc_clk_names)) + return -EINVAL; + init.parent_names = &ad9545_aux_tdc_clk_names[val]; + val = val + 0x6; + } + + ret = regmap_write(st->regmap, AD9545_AUX_DPLL_SOURCE, val); + if (ret < 0) + return ret; + + /* write loop bandwidth in dHz */ + regval = cpu_to_le16(clk->loop_bw_mhz / 100); + ret = regmap_bulk_write(st->regmap, AD9545_AUX_DPLL_LOOP_BW, ®val, 2); + if (ret < 0) + return ret; + + val = 0; + for (i = 0; i < ARRAY_SIZE(ad9545_rate_change_limit_map); i++) { + if (ad9545_rate_change_limit_map[i] == clk->rate_change_limit) { + val = i; + break; + } + } + + ret = regmap_write(st->regmap, AD9545_AUX_DPLL_CHANGE_LIMIT, val); + if (ret < 0) + return ret; + + /* add compensation destination */ + ret = regmap_write(st->regmap, AD9545_COMPENSATE_DPLL, AD9545_COMPNESATE_VIA_AUX_DPLL); + if (ret < 0) + return ret; + + ret = regmap_write(st->regmap, AD9545_COMPENSATE_TDCS, AD9545_COMPENSATE_TDCS_VIA_AUX_DPLL); + if (ret < 0) + return ret; + + init.name = ad9545_aux_dpll_name; + init.ops = &ad9545_aux_dpll_clk_ops; + init.num_parents = 1; + + clk->hw.init = &init; + return devm_clk_hw_register(st->dev, &clk->hw); +} + +static int ad9545_calib_system_clock(struct ad9545_state *st) +{ + int ret; + u32 reg; + int i; + int j; + + for (i = 0; i < 2; i++) { + for (j = 0; j < ARRAY_SIZE(ad9545_vco_calibration_op); j++) { + ret = regmap_write(st->regmap, ad9545_vco_calibration_op[j][0], + ad9545_vco_calibration_op[j][1]); + if (ret < 0) + return ret; + } + + /* wait for sys pll to lock and become stable */ + msleep(50 + AD9545_SYS_CLK_STABILITY_MS); + + ret = regmap_read(st->regmap, AD9545_PLL_STATUS, ®); + if (ret < 0) + return ret; + + if (AD9545_SYS_PLL_STABLE(reg)) { + ret = regmap_write(st->regmap, AD9545_CALIB_CLK, 0); + if (ret < 0) + return ret; + + return ad9545_io_update(st); + } + } + + dev_err(st->dev, "System PLL unlocked.\n"); + return -EIO; +} + +static int ad9545_calib_apll(struct ad9545_state *st, int i) +{ + int cal_count; + u32 reg; + int ret; + + /* APLL VCO calibration operation */ + cal_count = 0; + while (cal_count < 2) { + ret = regmap_write(st->regmap, AD9545_PWR_CALIB_CHX(i), 0); + if (ret < 0) + return ret; + + ret = ad9545_io_update(st); + if (ret < 0) + return ret; + + ret = regmap_write(st->regmap, AD9545_PWR_CALIB_CHX(i), + AD9545_CALIB_APLL); + if (ret < 0) + return ret; + + ret = ad9545_io_update(st); + if (ret < 0) + return ret; + + cal_count += 1; + msleep(100); + + ret = regmap_read(st->regmap, AD9545_PLLX_STATUS(i), ®); + if (ret < 0) + return ret; + + if (AD9545_APLL_LOCKED(reg)) { + ret = regmap_write(st->regmap, AD9545_PWR_CALIB_CHX(i), 0); + if (ret < 0) + return ret; + + ret = ad9545_io_update(st); + if (ret < 0) + return ret; + + cal_count = 2; + break; + } + } + + return ret; +} + +static int ad9545_calib_aplls(struct ad9545_state *st) +{ + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(ad9545_pll_clk_names); i++) { + if (!st->pll_clks[i].pll_used) + continue; + + ret = ad9545_calib_apll(st, i); + if (ret < 0) + return ret; + } + + return 0; +} + +static struct clk *ad9545_clk_src_twocell_get(struct of_phandle_args *clkspec, void *data) +{ + unsigned int clk_address = clkspec->args[1]; + unsigned int clk_type = clkspec->args[0]; + struct clk ***clks = data; + + if (clk_type > AD9545_CLK_AUX_TDC) { + pr_err("%s: invalid clock type %u\n", __func__, clk_type); + return ERR_PTR(-EINVAL); + } + if ((clk_type == AD9545_CLK_PLL && clk_address > AD9545_PLL1) || + (clk_type == AD9545_CLK_OUT && clk_address > AD9545_Q1BB) || + (clk_type == AD9545_CLK_NCO && clk_address > AD9545_NCO1) || + (clk_type == AD9545_CLK_AUX_TDC && clk_address > AD9545_CLK_AUX_TDC1)) { + pr_err("%s: invalid clock address %u\n", __func__, clk_address); + return ERR_PTR(-EINVAL); + } + + return clks[clk_type][clk_address]; +} + +static int ad9545_setup(struct ad9545_state *st) +{ + int ret; + u32 val; + int i; + + ret = regmap_update_bits(st->regmap, AD9545_CONFIG_0, AD9545_RESET_REGS, AD9545_RESET_REGS); + if (ret < 0) + return ret; + + ret = ad9545_sys_clk_setup(st); + if (ret < 0) + return ret; + + ret = ad9545_input_refs_setup(st); + if (ret < 0) + return ret; + + ret = ad9545_aux_ncos_setup(st); + if (ret < 0) + return ret; + + ret = ad9545_calib_system_clock(st); + if (ret < 0) + return ret; + + ret = ad9545_aux_tdcs_setup(st); + if (ret < 0) + return ret; + + ret = ad9545_aux_dpll_setup(st); + if (ret < 0) + return ret; + + ret = ad9545_calib_aplls(st); + if (ret < 0) + return ret; + + ret = ad9545_io_update(st); + if (ret < 0) + return ret; + + ret = ad9545_plls_setup(st); + if (ret < 0) + return ret; + + ret = ad9545_outputs_setup(st); + if (ret < 0) + return ret; + + ret = of_clk_add_provider(st->dev->of_node, ad9545_clk_src_twocell_get, + &st->clks[AD9545_CLK_OUT]); + if (ret < 0) + return ret; + + ret = ad9545_calib_aplls(st); + if (ret < 0) + return ret; + + /* check locks */ + ret = regmap_read(st->regmap, AD9545_PLL_STATUS, &val); + for (i = 0; i < ARRAY_SIZE(st->pll_clks); i++) + if (st->pll_clks[i].pll_used && !AD9545_PLLX_LOCK(i, val)) + dev_warn(st->dev, "PLL%d unlocked.\n", i); + + if (st->aux_dpll_clk.dpll_used) { + ret = regmap_read(st->regmap, AD9545_MISC, &val); + if (ret < 0) + return ret; + + if (!(val & AD9545_AUX_DPLL_LOCK_MSK)) + dev_warn(st->dev, "Aux DPLL unlocked.\n"); + + if (val & AD9545_AUX_DPLL_REF_FAULT) + dev_warn(st->dev, "Aux DPLL reference fault.\n"); + } + + return 0; +} + +int ad9545_probe(struct device *dev, struct regmap *regmap) +{ + struct ad9545_state *st; + int ret; + + st = devm_kzalloc(dev, sizeof(struct ad9545_state), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->dev = dev; + st->regmap = regmap; + + ret = ad9545_check_id(st); + if (ret < 0) + return ret; + + ret = ad9545_parse_dt(st); + if (ret < 0) + return ret; + + return ad9545_setup(st); +} +EXPORT_SYMBOL_GPL(ad9545_probe); + +MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Analog Devices AD9545"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/clk/adi/clk-ad9545.h b/drivers/clk/adi/clk-ad9545.h new file mode 100644 index 000000000000..641acabe9c8e --- /dev/null +++ b/drivers/clk/adi/clk-ad9545.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ +/* + * AD9545 Network Clock Generator/Synchronizer + * + * Copyright 2020 Analog Devices Inc. + */ + +#ifndef _CLK_AD9545_H_ +#define _CLK_AD9545_H_ + +struct device; +struct regmap; + +int ad9545_probe(struct device *dev, struct regmap *regmap); + +#endif /* _CLK_AD9545_H_ */ -- 2.25.1