[PATCH v2] clk: clocking-wizard: Program PLL CP/RES and lock parameters on reconfig

From: Shubhrajyoti Datta

Date: Mon Jun 29 2026 - 01:52:06 EST


When dynamically reconfiguring the PLL multiplier, the charge pump (CP),
loop filter resistance (RES), and lock timing parameters must be updated
to match the new multiplier value. Without this, the PLL may fail to lock
or exhibit jitter at certain multiply factors.

Add lookup tables for CP/RES and lock timing values indexed by multiplier
range, and program registers for chargepump and lock delay during dynamic
reconfiguration.

Signed-off-by: Shubhrajyoti Datta <shubhrajyoti.datta@xxxxxxx>
---

The algorithm is not currently documented in PG321.
The AMD documentation team is in the process of updating the relevant documentation.

drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 134 +++++++++++++++++++++
1 file changed, 134 insertions(+)

diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c
index 4a0136349f71..f5d14543167a 100644
--- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c
+++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c
@@ -35,6 +35,10 @@
#define WZRD_DIVCLK 21
#define WZRD_CLKFBOUT_4 51
#define WZRD_CLKFBOUT_3 48
+#define WZRD_CP 18
+#define WZRD_LOCK 27
+#define WZRD_LOCK_REF_DLY 28
+#define WZRD_RES 30
#define WZRD_DUTY_CYCLE 2
#define WZRD_O_DIV 4

@@ -49,6 +53,10 @@
#define WZRD_P5FEDGE_SHIFT 15
#define WZRD_CLKOUT0_PREDIV2 BIT(11)
#define WZRD_EDGE_SHIFT 8
+#define WZRD_CP_MASK GENMASK(3, 0)
+#define WZRD_RES_MASK GENMASK(4, 1)
+#define WZRD_LOCK_FB_DLY_MASK GENMASK(14, 10)
+#define WZRD_LOCK_REF_DLY_LOCK_REF_DLY_MASK GENMASK(14, 10)

#define WZRD_CLKFBOUT_MULT_SHIFT 8
#define WZRD_CLKFBOUT_MULT_MASK (0xff << WZRD_CLKFBOUT_MULT_SHIFT)
@@ -440,6 +448,130 @@ static int clk_wzrd_reconfig(struct clk_wzrd_divider *divider, void __iomem *div
WZRD_USEC_POLL, WZRD_TIMEOUT_POLL);
}

+struct wzrd_pll_filter {
+ u32 m_min;
+ u32 m_max;
+ u32 cp;
+ u32 res;
+};
+
+static const struct wzrd_pll_filter wzrd_cp_res_table[] = {
+ { 4, 4, 5, 15 },
+ { 5, 5, 6, 15 },
+ { 6, 6, 7, 15 },
+ { 7, 7, 13, 15 },
+ { 8, 8, 14, 15 },
+ { 9, 9, 15, 15 },
+ { 10, 10, 14, 7 },
+ { 11, 11, 15, 7 },
+ { 12, 13, 15, 11 },
+ { 14, 14, 15, 13 },
+ { 15, 15, 15, 3 },
+ { 16, 17, 14, 5 },
+ { 18, 19, 15, 5 },
+ { 20, 21, 15, 9 },
+ { 22, 23, 14, 14 },
+ { 24, 26, 15, 14 },
+ { 27, 28, 14, 1 },
+ { 29, 33, 15, 1 },
+ { 34, 37, 14, 6 },
+ { 38, 44, 15, 6 },
+ { 45, 57, 15, 10 },
+ { 58, 63, 13, 12 },
+ { 64, 70, 14, 12 },
+ { 71, 86, 15, 12 },
+ { 87, 94, 14, 2 },
+ { 95, 145, 15, 2 },
+ { 146, 163, 12, 4 },
+ { 164, 181, 13, 4 },
+ { 182, 200, 14, 4 },
+ { 201, 273, 15, 4 },
+ { 274, 300, 13, 8 },
+ { 301, 325, 14, 8 },
+ { 326, 432, 15, 8 },
+};
+
+struct wzrd_lock_timing {
+ u32 m_min;
+ u32 m_max;
+ u32 ref_dly;
+ u32 fb_dly;
+ u32 lock_cnt;
+};
+
+static const struct wzrd_lock_timing wzrd_lock_table[] = {
+ { 4, 4, 4, 4, 1000 },
+ { 5, 5, 6, 6, 1000 },
+ { 6, 8, 7, 7, 1000 },
+ { 9, 12, 8, 8, 1000 },
+ { 13, 13, 10, 10, 1000 },
+ { 14, 16, 13, 13, 1000 },
+ { 17, 17, 16, 16, 825 },
+ { 18, 18, 16, 16, 750 },
+ { 19, 20, 16, 16, 700 },
+ { 21, 21, 16, 16, 650 },
+ { 22, 23, 16, 16, 625 },
+ { 24, 24, 16, 16, 575 },
+ { 25, 25, 16, 16, 550 },
+ { 26, 28, 16, 16, 525 },
+ { 29, 30, 16, 16, 475 },
+ { 31, 31, 16, 16, 450 },
+ { 32, 33, 16, 16, 425 },
+ { 34, 36, 16, 16, 400 },
+ { 37, 37, 16, 16, 375 },
+ { 38, 40, 16, 16, 350 },
+ { 41, 43, 16, 16, 325 },
+ { 44, 47, 16, 16, 300 },
+ { 48, 51, 16, 16, 275 },
+ { 52, 205, 16, 16, 250 },
+ { 206, 432, 16, 16, 225 },
+};
+
+static void clk_wzrd_update_cp_res_lock(struct clk_wzrd_divider *divider, u32 m)
+{
+ u32 lock_ref_dly = 16, lock_fb_dly = 16, lock_cnt = 250, cp = 15, res = 15;
+ void __iomem *base = divider->base;
+ u32 reg;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(wzrd_cp_res_table); i++) {
+ if (m >= wzrd_cp_res_table[i].m_min &&
+ m <= wzrd_cp_res_table[i].m_max) {
+ cp = wzrd_cp_res_table[i].cp;
+ res = wzrd_cp_res_table[i].res;
+ break;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wzrd_lock_table); i++) {
+ if (m >= wzrd_lock_table[i].m_min &&
+ m <= wzrd_lock_table[i].m_max) {
+ lock_ref_dly = wzrd_lock_table[i].ref_dly;
+ lock_fb_dly = wzrd_lock_table[i].fb_dly;
+ lock_cnt = wzrd_lock_table[i].lock_cnt;
+ break;
+ }
+ }
+
+ reg = readl(base + WZRD_CLK_CFG_REG(1, WZRD_CP));
+ reg &= ~WZRD_CP_MASK;
+ reg |= FIELD_PREP(WZRD_CP_MASK, cp);
+ writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_CP));
+
+ reg = readl(base + WZRD_CLK_CFG_REG(1, WZRD_RES));
+ reg &= ~WZRD_RES_MASK;
+ reg |= FIELD_PREP(WZRD_RES_MASK, res);
+ writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_RES));
+
+ reg = lock_cnt | FIELD_PREP(WZRD_LOCK_FB_DLY_MASK, lock_fb_dly);
+ writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_LOCK));
+
+ reg = readl(base + WZRD_CLK_CFG_REG(1, WZRD_LOCK_REF_DLY));
+ reg &= ~WZRD_LOCK_REF_DLY_LOCK_REF_DLY_MASK;
+ reg |= FIELD_PREP(WZRD_LOCK_REF_DLY_LOCK_REF_DLY_MASK, lock_ref_dly);
+ writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_LOCK_REF_DLY));
+}
+
static int clk_wzrd_dynamic_ver_all_nolock(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
@@ -471,6 +603,8 @@ static int clk_wzrd_dynamic_ver_all_nolock(struct clk_hw *hw, unsigned long rate
writel(regval1, divider->base + WZRD_CLK_CFG_REG(1,
WZRD_CLKFBOUT_2));

+ clk_wzrd_update_cp_res_lock(divider, m);
+
value2 = divider->d;
edged = value2 % WZRD_DUTY_CYCLE;
regh = (value2 / WZRD_DUTY_CYCLE);
--
2.34.1