linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: <alexandru.tachici@analog.com>
To: <linux-clk@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<devicetree@vger.kernel.org>
Cc: <robh+dt@kernel.org>, <sboyd@kernel.org>,
	<mturquette@baylibre.com>, <petre.minciunescu@analog.com>,
	Alexandru Tachici <alexandru.tachici@analog.com>
Subject: [PATCH v2 1/2] clk: ad9545: Add support
Date: Mon, 14 Jun 2021 10:07:17 +0300	[thread overview]
Message-ID: <20210614070718.78041-2-alexandru.tachici@analog.com> (raw)
In-Reply-To: <20210614070718.78041-1-alexandru.tachici@analog.com>

From: Alexandru Tachici <alexandru.tachici@analog.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@analog.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 |   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@analog.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..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@analog.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..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, &regval64, 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), &regval, 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), &regval, 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), &regval, 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), &regval, 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), &regval);
+	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, &reg);
+		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),
+					&regval, 3);
+		if (ret < 0)
+			return ret;
+
+		regval64 = cpu_to_le64(period_es);
+		ret = regmap_bulk_write(st->regmap, AD9545_REF_X_PERIOD(i), &regval64, 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),
+					&regval, 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),
+					&regval, 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), &regval, 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, &regval);
+		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), &regval);
+		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), &regval, 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), &regval, 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), &regval, 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),
+					&regval, 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),
+					&regval, 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),
+					&regval, 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), &regval, 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), &regval, 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), &regval, 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), &regval, 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), &regval, 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), &regval64, 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, &regval, 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, &reg);
+		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), &reg);
+		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@analog.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_ */
-- 
2.25.1


  reply	other threads:[~2021-06-14  6:59 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-06-14  7:07 [PATCH v2 0/2] clk: ad9545: Add support alexandru.tachici
2021-06-14  7:07 ` alexandru.tachici [this message]
2021-06-14  7:07 ` [PATCH v2 2/2] dt-bindings: clock: ad9545: Add documentation alexandru.tachici
2021-06-14 13:39   ` Rob Herring
2021-06-15 22:50   ` Rob Herring
2021-06-16 18:53   ` kernel test robot
2021-06-17  7:55   ` kernel test robot
2021-06-18  4:17   ` kernel test robot
2021-06-22  7:02   ` kernel test robot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210614070718.78041-2-alexandru.tachici@analog.com \
    --to=alexandru.tachici@analog.com \
    --cc=devicetree@vger.kernel.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mturquette@baylibre.com \
    --cc=petre.minciunescu@analog.com \
    --cc=robh+dt@kernel.org \
    --cc=sboyd@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).