[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20210124221304.51007-2-alexandru.tachici@analog.com>
Date: Mon, 25 Jan 2021 00:13:03 +0200
From: <alexandru.tachici@...log.com>
To: <linux-clk@...r.kernel.org>, <linux-kernel@...r.kernel.org>,
<devicetree@...r.kernel.org>
CC: <robh+dt@...nel.org>, <sboyd@...nel.org>,
<mturquette@...libre.com>, <petre.minciunescu@...log.com>,
Alexandru Tachici <alexandru.tachici@...log.com>
Subject: [PATCH 1/2] clk: ad9545: Add support
From: Alexandru Tachici <alexandru.tachici@...log.com>
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@...log.com>
---
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 | 61 +
drivers/clk/adi/clk-ad9545-spi.c | 75 ++
drivers/clk/adi/clk-ad9545.c | 1678 ++++++++++++++++++++++++++++
drivers/clk/adi/clk-ad9545.h | 16 +
include/dt-bindings/clock/ad9545.h | 64 ++
9 files changed, 1939 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
create mode 100644 include/dt-bindings/clock/ad9545.h
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 85856cff506c..80c3faf1f42e 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -270,6 +270,11 @@ config CLK_LS1028A_PLLDIG
features of the PLL are currently supported by the driver. By default,
configured bypass mode with this PLL.
+config COMMON_CLK_ADI
+ def_bool COMMON_CLK
+ ---help---
+ Support for Analog Devices clock providers.
+
config COMMON_CLK_XGENE
bool "Clock driver for APM XGene SoC"
default ARCH_XGENE
@@ -369,6 +374,7 @@ config COMMON_CLK_FIXED_MMIO
Support for Memory Mapped IO Fixed 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 dbdc590e7de3..3adc7db7c671 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -73,6 +73,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..923fec28ece2
--- /dev/null
+++ b/drivers/clk/adi/clk-ad9545-i2c.c
@@ -0,0 +1,61 @@
+// 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_rw = 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@...log.com>");
+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..36ded1026843
--- /dev/null
+++ b/drivers/clk/adi/clk-ad9545-spi.c
@@ -0,0 +1,75 @@
+// 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_rw = 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@...log.com>");
+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..df4008e5a5c7
--- /dev/null
+++ b/drivers/clk/adi/clk-ad9545.c
@@ -0,0 +1,1678 @@
+// 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_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_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_FREQ_LOCK_THRESH 0x0805
+#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_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_DIV_OPS_Q1A 0x2202
+#define AD9545_NCO0_FREQ 0x2805
+#define AD9545_PLL_STATUS 0x3001
+#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_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_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) (AD9545_DPLL0_EN + ((x) * 0x400))
+#define AD9545_DPLLX_SOURCE(x) (AD9545_DPLL0_SOURCE + ((x) * 0x400))
+#define AD9545_DPLLX_LOOP_BW(x) (AD9545_DPLL0_LOOP_BW + ((x) * 0x400))
+#define AD9545_DPLLX_N_DIV(x) (AD9545_DPLL0_N_DIV + ((x) * 0x400))
+#define AD9545_DPLLX_FRAC_DIV(x) (AD9545_DPLL0_FRAC + ((x) * 0x400))
+#define AD9545_DPLLX_MOD_DIV(x) (AD9545_DPLL0_MOD + ((x) * 0x400))
+
+#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_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))
+
+/* 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))
+
+#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_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_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_hyst_scales_bp[] = {
+ 0, 3125, 6250, 12500, 25000, 50000, 75000, 87500
+};
+
+static const u32 ad9545_out_source_ua[] = {
+ 7500, 12500, 15000
+};
+
+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",
+};
+
+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_ppl_clk {
+ struct ad9545_state *st;
+ bool pll_used;
+ unsigned int address;
+ unsigned int loop_bw;
+ struct clk_hw hw;
+ u8 tdc_source;
+};
+
+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;
+ 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_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_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 clk **clks[3];
+};
+
+#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)
+
+static int ad9545_parse_dt_inputs(struct ad9545_state *st)
+{
+ 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);
+
+ prop_found = false;
+ fwnode_for_each_available_child_node(fwnode, child) {
+ if (!fwnode_property_present(child, "adi,r-divider-ratio"))
+ continue;
+
+ 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)
+ st->ref_in_clks[ref_ind].r_div_ratio = val;
+
+ ret = fwnode_property_read_u32(child, "adi,ref-dtol-pbb", &val);
+ if (ret < 0)
+ 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)
+ 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)
+ 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)
+ 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)
+ return ret;
+
+ st->ref_in_clks[ref_ind].phase_thresh_ps = 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 *fwnode;
+ struct fwnode_handle *child;
+ bool prop_found;
+ u32 val;
+ u32 addr;
+ int ret;
+
+ fwnode = dev_fwnode(st->dev);
+
+ prop_found = false;
+ fwnode_for_each_available_child_node(fwnode, child) {
+ if (!fwnode_property_present(child, "adi,pll-source"))
+ continue;
+
+ 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(child, "adi,pll-source", &val);
+ if (ret < 0)
+ return ret;
+
+ if (val > 5)
+ return -EINVAL;
+
+ st->pll_clks[addr].tdc_source = val;
+
+ ret = fwnode_property_read_u32(child, "adi,pll-loop-bandwidth-hz", &val);
+ if (ret < 0)
+ return ret;
+
+ st->pll_clks[addr].loop_bw = val;
+ }
+
+ return 0;
+}
+
+static int ad9545_parse_dt_outputs(struct ad9545_state *st)
+{
+ struct fwnode_handle *fwnode;
+ struct fwnode_handle *child;
+ bool prop_found;
+ int ref_ind;
+ u32 val;
+ int ret;
+
+ fwnode = dev_fwnode(st->dev);
+
+ prop_found = false;
+ fwnode_for_each_available_child_node(fwnode, child) {
+ if (!fwnode_property_present(child, "adi,output-mode"))
+ continue;
+
+ ret = fwnode_property_read_u32(child, "reg", &ref_ind);
+ if (ret < 0)
+ return ret;
+
+ if (ref_ind > 9)
+ return -EINVAL;
+
+ st->out_clks[ref_ind].output_used = true;
+ st->out_clks[ref_ind].address = ref_ind;
+
+ if (fwnode_property_present(child, "adi,current-source"))
+ st->out_clks[ref_ind].source_current = true;
+
+ ret = fwnode_property_read_u32(child, "adi,current-source-microamp", &val);
+ if (ret < 0)
+ return ret;
+
+ st->out_clks[ref_ind].source_ua = val;
+
+ ret = fwnode_property_read_u32(child, "adi,output-mode", &val);
+ if (ret < 0)
+ return ret;
+
+ st->out_clks[ref_ind].output_mode = val;
+ }
+
+ return 0;
+}
+
+static int ad9545_parse_dt_ncos(struct ad9545_state *st)
+{
+ struct fwnode_handle *fwnode;
+ struct fwnode_handle *child;
+ bool prop_found;
+ u32 val;
+ u32 addr;
+ int ret;
+
+ fwnode = dev_fwnode(st->dev);
+
+ prop_found = false;
+ fwnode_for_each_available_child_node(fwnode, child) {
+ if (!fwnode_property_present(child, "adi,freq-lock-threshold-ps") ||
+ fwnode_property_present(child, "adi,ref-dtol-pbb"))
+ continue;
+
+ ret = fwnode_property_read_u32(child, "reg", &addr);
+ if (ret < 0)
+ 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)
+ 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)
+ return ret;
+
+ st->aux_nco_clks[addr].phase_thresh_ps = 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)
+ 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;
+
+ 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 ad9545_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 = ad9545_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 ad9545_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 = ad9545_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;
+
+ 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_set_freerun_freq(struct ad9545_ppl_clk *clk, u32 freq)
+{
+ __le64 regval;
+ u64 ftw = 1;
+ u32 ftw_frac;
+ u32 ftw_int;
+ int ret;
+
+ /*
+ * 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;
+
+ regval = cpu_to_le64(ftw);
+ ret = regmap_bulk_write(clk->st->regmap, AD9545_DPLLX_FTW(clk->address), ®val, 6);
+ if (ret < 0)
+ return ret;
+
+ return ad9545_io_update(clk->st);
+}
+
+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;
+
+ /*
+ * 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);
+
+ /*
+ * 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 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;
+
+ ret = regmap_bulk_read(clk->st->regmap, AD9545_DPLLX_N_DIV(clk->address), ®val, 4);
+ if (ret < 0)
+ return ret;
+
+ n = le32_to_cpu(regval) + 1;
+
+ m = 0;
+ ret = regmap_read(clk->st->regmap, AD9545_APLLX_M_DIV(clk->address), &m);
+ if (ret < 0)
+ return ret;
+
+ regval = 0;
+ ret = regmap_bulk_read(clk->st->regmap, AD9545_DPLLX_FRAC_DIV(clk->address), ®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), ®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;
+ u32 m;
+ u32 n;
+
+ 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;
+
+ 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), ®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), ®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), ®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,
+};
+
+static int ad9545_plls_setup(struct ad9545_state *st)
+{
+ struct clk_init_data init[2] = {0};
+ struct ad9545_ppl_clk *pll;
+ __le32 regval;
+ int ret;
+ u8 reg;
+ int i;
+
+ 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;
+
+ /* enable pll profile */
+ ret = regmap_write(st->regmap, AD9545_DPLLX_EN(i), 1);
+ if (ret < 0)
+ return ret;
+
+ /* set TDC source */
+ reg = ad9545_tdc_source_mapping[pll->tdc_source];
+ ret = regmap_write(st->regmap, AD9545_DPLLX_SOURCE(i), reg);
+ if (ret < 0)
+ return ret;
+
+ /* write loop bandwidth in microhertz */
+ regval = cpu_to_le32(pll->loop_bw * 1000000);
+ ret = regmap_bulk_write(st->regmap, AD9545_DPLLX_LOOP_BW(i), ®val, 4);
+ if (ret < 0)
+ return ret;
+
+ pll->st = st;
+ pll->address = i;
+
+ init[i].name = ad9545_pll_clk_names[i];
+ init[i].ops = &ad9545_pll_clk_ops;
+ if (pll->tdc_source > 3)
+ init[i].parent_names = &ad9545_aux_nco_clk_names[pll->tdc_source - 4];
+ else
+ init[i].parent_names = &ad9545_in_clk_names[pll->tdc_source];
+
+ init[i].num_parents = 1;
+
+ 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 ad9545_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 = ad9545_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_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_NCO) {
+ 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)) {
+ 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_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);
+
+ 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@...log.com>");
+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_ */
diff --git a/include/dt-bindings/clock/ad9545.h b/include/dt-bindings/clock/ad9545.h
new file mode 100644
index 000000000000..8b45e1b45249
--- /dev/null
+++ b/include/dt-bindings/clock/ad9545.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
+/*
+ * AD9545 Network Clock Generator/Synchronizer
+ *
+ * Copyright 2020 Analog Devices Inc.
+ */
+
+#ifndef _DT_BINDINGS_CLOCK_AD9545_H_
+#define _DT_BINDINGS_CLOCK_AD9545_H_
+
+/* Input Driver Mode
+ * Use for adi,single-ended-mode:
+ */
+#define DRIVER_MODE_AC_COUPLED_IF 0
+#define DRIVER_MODE_DC_COUPLED_1V2 1
+#define DRIVER_MODE_DC_COUPLED_1V8 2
+#define DRIVER_MODE_IN_PULL_UP 3
+
+/* Input Driver Mode
+ * Use for adi,differential-mode:
+ */
+#define DRIVER_MODE_AC_COUPLED 0
+#define DRIVER_MODE_DC_COUPLED 1
+#define DRIVER_MODE_DC_COUPLED_LVDS 2
+
+/* Output Driver Mode
+ * Use for adi,output-mode:
+ */
+#define DRIVER_MODE_SINGLE_DIV_DIF 0
+#define DRIVER_MODE_SINGLE_DIV 1
+#define DRIVER_MODE_DUAL_DIV 2
+
+/* Clock types */
+#define AD9545_CLK_OUT 0
+#define AD9545_CLK_PLL 1
+#define AD9545_CLK_NCO 2
+
+/* PLL addresses */
+#define AD9545_PLL0 0
+#define AD9545_PLL1 1
+
+/* Outputs addresses */
+#define AD9545_Q0A 0
+#define AD9545_Q0AA 1
+#define AD9545_Q0B 2
+#define AD9545_Q0BB 3
+#define AD9545_Q0C 4
+#define AD9545_Q0CC 5
+#define AD9545_Q1A 6
+#define AD9545_Q1AA 7
+#define AD9545_Q1B 8
+#define AD9545_Q1BB 9
+
+/* NCO addresses */
+#define AD9545_NCO0 0
+#define AD9545_NCO1 1
+
+/* Ex:
+ * Output Q0C clock: <&ad9545_clock AD9545_CLK_OUT AD9545_Q0C>;
+ * PLL0 clock: <&ad9545_clock AD9545_CLK_PLL AD9545_PLL0>;
+ * NCO1 clock: <&ad9545_clock AD9545_CLK_NCO AD9545_NCO1>;
+ */
+
+#endif /* _DT_BINDINGS_CLOCK_AD9545_H_ */
--
2.20.1
Powered by blists - more mailing lists