// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2024, Kongyang Liu */ #include #include #include #include #include #include #include "clk-common.h" #include "clk-pll.h" #define PLL_PRE_DIV_MIN 1 #define PLL_PRE_DIV_MAX 127 #define PLL_POST_DIV_MIN 1 #define PLL_POST_DIV_MAX 127 #define PLL_DIV_MIN 6 #define PLL_DIV_MAX 127 #define PLL_ICTRL_MIN 0 #define PLL_ICTRL_MAX 7 #define PLL_MODE_MIN 0 #define PLL_MODE_MAX 3 #define FOR_RANGE(x, RANGE) for (x = RANGE##_MIN; x <= RANGE##_MAX; x++) #define PLL_ICTRL GENMASK(26, 24) #define PLL_DIV_SEL GENMASK(23, 17) #define PLL_SEL_MODE GENMASK(16, 15) #define PLL_POST_DIV_SEL GENMASK(14, 8) #define PLL_PRE_DIV_SEL GENMASK(6, 0) #define PLL_MASK_ALL (PLL_ICTRL | PLL_DIV_SEL | PLL_SEL_MODE | PLL_POST_DIV_SEL | PLL_PRE_DIV_SEL) /* IPLL */ #define to_clk_ipll(dev) container_of(dev, struct cv1800b_clk_ipll, clk) static int cv1800b_ipll_enable(struct clk *clk) { struct cv1800b_clk_ipll *pll = to_clk_ipll(clk); cv1800b_clk_clrbit(pll->base, &pll->pll_pwd); return 0; } static int cv1800b_ipll_disable(struct clk *clk) { struct cv1800b_clk_ipll *pll = to_clk_ipll(clk); cv1800b_clk_setbit(pll->base, &pll->pll_pwd); return 0; } static ulong cv1800b_ipll_get_rate(struct clk *clk) { struct cv1800b_clk_ipll *pll = to_clk_ipll(clk); ulong parent_rate = clk_get_parent_rate(clk); u32 reg = readl(pll->base + pll->pll_reg); u32 pre_div = FIELD_GET(PLL_PRE_DIV_SEL, reg); u32 post_div = FIELD_GET(PLL_POST_DIV_SEL, reg); u32 div = FIELD_GET(PLL_DIV_SEL, reg); return DIV_ROUND_DOWN_ULL(parent_rate * div, pre_div * post_div); } static ulong cv1800b_ipll_set_rate(struct clk *clk, ulong rate) { struct cv1800b_clk_ipll *pll = to_clk_ipll(clk); ulong parent_rate = clk_get_parent_rate(clk); u32 pre_div, post_div, div; u32 pre_div_sel, post_div_sel, div_sel; ulong new_rate, best_rate = 0; u32 mode, ictrl; u32 test, val; FOR_RANGE(pre_div, PLL_PRE_DIV) { FOR_RANGE(post_div, PLL_POST_DIV) { FOR_RANGE(div, PLL_DIV) { new_rate = DIV_ROUND_DOWN_ULL(parent_rate * div, pre_div * post_div); if (rate - new_rate < rate - best_rate) { best_rate = new_rate; pre_div_sel = pre_div; post_div_sel = post_div; div_sel = div; } } } } FOR_RANGE(mode, PLL_MODE) { FOR_RANGE(ictrl, PLL_ICTRL) { test = 184 * (1 + mode) * (1 + ictrl) / 2; if (test > 20 * div_sel && test < 35 * div_sel) { val = FIELD_PREP(PLL_PRE_DIV_SEL, pre_div_sel) | FIELD_PREP(PLL_POST_DIV_SEL, post_div_sel) | FIELD_PREP(PLL_DIV_SEL, div_sel) | FIELD_PREP(PLL_ICTRL, ictrl) | FIELD_PREP(PLL_SEL_MODE, mode); clrsetbits_le32(pll->base + pll->pll_reg, PLL_MASK_ALL, val); return best_rate; } } } return -EINVAL; } const struct clk_ops cv1800b_ipll_ops = { .enable = cv1800b_ipll_enable, .disable = cv1800b_ipll_disable, .get_rate = cv1800b_ipll_get_rate, .set_rate = cv1800b_ipll_set_rate, }; U_BOOT_DRIVER(cv1800b_clk_ipll) = { .name = "cv1800b_clk_ipll", .id = UCLASS_CLK, .ops = &cv1800b_ipll_ops, .flags = DM_FLAG_PRE_RELOC, }; /* FPLL */ #define to_clk_fpll(dev) container_of(dev, struct cv1800b_clk_fpll, ipll.clk) static ulong cv1800b_fpll_get_rate(struct clk *clk) { struct cv1800b_clk_fpll *pll = to_clk_fpll(clk); u32 val, syn_set; u32 pre_div, post_div, div; u8 mult = 1; ulong divisor, remainder, rate; if (!cv1800b_clk_getbit(pll->ipll.base, &pll->syn.en)) return cv1800b_ipll_get_rate(clk); syn_set = readl(pll->ipll.base + pll->syn.set); if (syn_set == 0) return 0; val = readl(pll->ipll.base + pll->ipll.pll_reg); pre_div = FIELD_GET(PLL_PRE_DIV_SEL, val); post_div = FIELD_GET(PLL_POST_DIV_SEL, val); div = FIELD_GET(PLL_DIV_SEL, val); if (cv1800b_clk_getbit(pll->ipll.base, &pll->syn.clk_half)) mult = 2; divisor = (ulong)pre_div * post_div * syn_set; rate = (clk_get_parent_rate(clk) * div) << 25; remainder = rate % divisor; rate /= divisor; return rate * mult + DIV_ROUND_CLOSEST_ULL(remainder * mult, divisor); } static ulong cv1800b_find_syn(ulong rate, ulong parent_rate, ulong pre_div, ulong post_div, ulong div, u32 *syn) { u32 syn_min = (4 << 26) + 1; u32 syn_max = U32_MAX; u32 mid; ulong new_rate; u32 mult = 1; ulong divisor, remainder; while (syn_min < syn_max) { mid = ((ulong)syn_min + syn_max) / 2; divisor = pre_div * post_div * mid; new_rate = (parent_rate * div) << 25; remainder = do_div(new_rate, divisor); new_rate = new_rate * mult + DIV_ROUND_CLOSEST_ULL(remainder * mult, divisor); if (new_rate > rate) { syn_max = mid + 1; } else if (new_rate < rate) { syn_min = mid - 1; } else { syn_min = mid; break; } } *syn = syn_min; return new_rate; } static ulong cv1800b_fpll_set_rate(struct clk *clk, ulong rate) { struct cv1800b_clk_fpll *pll = to_clk_fpll(clk); ulong parent_rate = clk_get_parent_rate(clk); u32 pre_div, post_div, div; u32 pre_div_sel, post_div_sel, div_sel; u32 syn, syn_sel; ulong new_rate, best_rate = 0; u32 mult = 1; u32 mode, ictrl; if (!cv1800b_clk_getbit(pll->ipll.base, &pll->syn.en)) return cv1800b_ipll_set_rate(clk, rate); if (cv1800b_clk_getbit(pll->ipll.base, &pll->syn.clk_half)) mult = 2; FOR_RANGE(pre_div, PLL_PRE_DIV) { FOR_RANGE(post_div, PLL_POST_DIV) { FOR_RANGE(div, PLL_DIV) { new_rate = cv1800b_find_syn(rate, parent_rate, pre_div, post_div, div, &syn); if (rate - new_rate < rate - best_rate) { best_rate = new_rate; pre_div_sel = pre_div; post_div_sel = post_div; div_sel = div; syn_sel = syn; } } } } FOR_RANGE(mode, PLL_MODE) { FOR_RANGE(ictrl, PLL_ICTRL) { u32 test = 184 * (1 + mode) * (1 + ictrl) / 2; if (test > 10 * div_sel && test <= 24 * div_sel) { u32 val = FIELD_PREP(PLL_PRE_DIV_SEL, pre_div_sel) | FIELD_PREP(PLL_POST_DIV_SEL, post_div_sel) | FIELD_PREP(PLL_DIV_SEL, div_sel) | FIELD_PREP(PLL_ICTRL, ictrl) | FIELD_PREP(PLL_SEL_MODE, mode); clrsetbits_le32(pll->ipll.base + pll->ipll.pll_reg, PLL_MASK_ALL, val); writel(syn_sel, pll->ipll.base + pll->syn.set); return best_rate; } } } return -EINVAL; } static int cv1800b_fpll_set_parent(struct clk *clk, struct clk *parent) { struct cv1800b_clk_fpll *pll = to_clk_fpll(clk); if (parent->id == CV1800B_CLK_BYPASS) cv1800b_clk_setbit(pll->ipll.base, &pll->syn.en); else cv1800b_clk_clrbit(pll->ipll.base, &pll->syn.en); return 0; } const struct clk_ops cv1800b_fpll_ops = { .enable = cv1800b_ipll_enable, .disable = cv1800b_ipll_disable, .get_rate = cv1800b_fpll_get_rate, .set_rate = cv1800b_fpll_set_rate, .set_parent = cv1800b_fpll_set_parent, }; U_BOOT_DRIVER(cv1800b_clk_fpll) = { .name = "cv1800b_clk_fpll", .id = UCLASS_CLK, .ops = &cv1800b_fpll_ops, .flags = DM_FLAG_PRE_RELOC, };