From d85028afefad94b4b2f9f7f36997f18d7e8984d2 Mon Sep 17 00:00:00 2001 From: Shubhrajyoti Datta Date: Mon, 29 Jun 2026 11:21:46 +0530 Subject: [PATCH] clk: clocking-wizard: Program PLL CP/RES and lock parameters on reconfig 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 Signed-off-by: Brian Masney --- 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);