// SPDX-License-Identifier: GPL-2.0+ /* * Texas Instruments K3 SoC PLL clock driver * * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/ * Tero Kristo */ #include #include #include #include #include #include #include #include "k3-clk.h" #include /* 16FFT register offsets */ #define PLL_16FFT_CFG 0x08 #define PLL_KICK0 0x10 #define PLL_KICK1 0x14 #define PLL_16FFT_CTRL 0x20 #define PLL_16FFT_STAT 0x24 #define PLL_16FFT_FREQ_CTRL0 0x30 #define PLL_16FFT_FREQ_CTRL1 0x34 #define PLL_16FFT_DIV_CTRL 0x38 /* CTRL register bits */ #define PLL_16FFT_CTRL_BYPASS_EN BIT(31) #define PLL_16FFT_CTRL_PLL_EN BIT(15) #define PLL_16FFT_CTRL_DSM_EN BIT(1) /* STAT register bits */ #define PLL_16FFT_STAT_LOCK BIT(0) /* FREQ_CTRL0 bits */ #define PLL_16FFT_FREQ_CTRL0_FB_DIV_INT_MASK 0xfff /* DIV CTRL register bits */ #define PLL_16FFT_DIV_CTRL_REF_DIV_MASK 0x3f #define PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_BITS 24 #define PLL_16FFT_HSDIV_CTRL_CLKOUT_EN BIT(15) /* KICK register magic values */ #define PLL_KICK0_VALUE 0x68ef3490 #define PLL_KICK1_VALUE 0xd172bc5a /** * struct ti_pll_clk - TI PLL clock data info structure * @clk: core clock structure * @reg: memory address of the PLL controller */ struct ti_pll_clk { struct clk clk; void __iomem *reg; }; #define to_clk_pll(_clk) container_of(_clk, struct ti_pll_clk, clk) static int ti_pll_wait_for_lock(struct clk *clk) { struct ti_pll_clk *pll = to_clk_pll(clk); u32 stat; int i; for (i = 0; i < 100000; i++) { stat = readl(pll->reg + PLL_16FFT_STAT); if (stat & PLL_16FFT_STAT_LOCK) return 0; } printf("%s: pll (%s) failed to lock\n", __func__, clk->dev->name); return -EBUSY; } static ulong ti_pll_clk_get_rate(struct clk *clk) { struct ti_pll_clk *pll = to_clk_pll(clk); u64 current_freq; u64 parent_freq = clk_get_parent_rate(clk); u32 pllm; u32 plld; u32 pllfm; u32 ctrl; /* Check if we are in bypass */ ctrl = readl(pll->reg + PLL_16FFT_CTRL); if (ctrl & PLL_16FFT_CTRL_BYPASS_EN) return parent_freq; pllm = readl(pll->reg + PLL_16FFT_FREQ_CTRL0); pllfm = readl(pll->reg + PLL_16FFT_FREQ_CTRL1); plld = readl(pll->reg + PLL_16FFT_DIV_CTRL) & PLL_16FFT_DIV_CTRL_REF_DIV_MASK; current_freq = parent_freq * pllm / plld; if (pllfm) { u64 tmp; tmp = parent_freq * pllfm; do_div(tmp, plld); tmp >>= PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_BITS; current_freq += tmp; } return current_freq; } static ulong ti_pll_clk_set_rate(struct clk *clk, ulong rate) { struct ti_pll_clk *pll = to_clk_pll(clk); u64 current_freq; u64 parent_freq = clk_get_parent_rate(clk); int ret; u32 ctrl; unsigned long pllm; u32 pllfm = 0; unsigned long plld; u32 rem; int shift; debug("%s(clk=%p, rate=%u)\n", __func__, clk, (u32)rate); if (ti_pll_clk_get_rate(clk) == rate) return rate; if (rate != parent_freq) /* * Attempt with higher max multiplier value first to give * some space for fractional divider to kick in. */ for (shift = 8; shift >= 0; shift -= 8) { rational_best_approximation(rate, parent_freq, ((PLL_16FFT_FREQ_CTRL0_FB_DIV_INT_MASK + 1) << shift) - 1, PLL_16FFT_DIV_CTRL_REF_DIV_MASK, &pllm, &plld); if (pllm / plld <= PLL_16FFT_FREQ_CTRL0_FB_DIV_INT_MASK) break; } /* Put PLL to bypass mode */ ctrl = readl(pll->reg + PLL_16FFT_CTRL); ctrl |= PLL_16FFT_CTRL_BYPASS_EN; writel(ctrl, pll->reg + PLL_16FFT_CTRL); if (rate == parent_freq) { debug("%s: put %s to bypass\n", __func__, clk->dev->name); return rate; } debug("%s: pre-frac-calc: rate=%u, parent_freq=%u, plld=%u, pllm=%u\n", __func__, (u32)rate, (u32)parent_freq, (u32)plld, (u32)pllm); /* Check if we need fractional config */ if (plld > 1) { pllfm = pllm % plld; pllfm <<= PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_BITS; rem = pllfm % plld; pllfm /= plld; if (rem) pllfm++; pllm /= plld; plld = 1; } if (pllfm) ctrl |= PLL_16FFT_CTRL_DSM_EN; else ctrl &= ~PLL_16FFT_CTRL_DSM_EN; writel(pllm, pll->reg + PLL_16FFT_FREQ_CTRL0); writel(pllfm, pll->reg + PLL_16FFT_FREQ_CTRL1); writel(plld, pll->reg + PLL_16FFT_DIV_CTRL); ctrl &= ~PLL_16FFT_CTRL_BYPASS_EN; ctrl |= PLL_16FFT_CTRL_PLL_EN; writel(ctrl, pll->reg + PLL_16FFT_CTRL); ret = ti_pll_wait_for_lock(clk); if (ret) return ret; debug("%s: pllm=%u, plld=%u, pllfm=%u, parent_freq=%u\n", __func__, (u32)pllm, (u32)plld, (u32)pllfm, (u32)parent_freq); current_freq = parent_freq * pllm / plld; if (pllfm) { u64 tmp; tmp = parent_freq * pllfm; do_div(tmp, plld); tmp >>= PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_BITS; current_freq += tmp; } return current_freq; } static int ti_pll_clk_enable(struct clk *clk) { struct ti_pll_clk *pll = to_clk_pll(clk); u32 ctrl; ctrl = readl(pll->reg + PLL_16FFT_CTRL); ctrl &= ~PLL_16FFT_CTRL_BYPASS_EN; ctrl |= PLL_16FFT_CTRL_PLL_EN; writel(ctrl, pll->reg + PLL_16FFT_CTRL); return ti_pll_wait_for_lock(clk); } static int ti_pll_clk_disable(struct clk *clk) { struct ti_pll_clk *pll = to_clk_pll(clk); u32 ctrl; ctrl = readl(pll->reg + PLL_16FFT_CTRL); ctrl |= PLL_16FFT_CTRL_BYPASS_EN; writel(ctrl, pll->reg + PLL_16FFT_CTRL); return 0; } static const struct clk_ops ti_pll_clk_ops = { .get_rate = ti_pll_clk_get_rate, .set_rate = ti_pll_clk_set_rate, .enable = ti_pll_clk_enable, .disable = ti_pll_clk_disable, }; struct clk *clk_register_ti_pll(const char *name, const char *parent_name, void __iomem *reg) { struct ti_pll_clk *pll; int ret; int i; u32 cfg, ctrl, hsdiv_presence_bit, hsdiv_ctrl_offs; pll = kzalloc(sizeof(*pll), GFP_KERNEL); if (!pll) return ERR_PTR(-ENOMEM); pll->reg = reg; ret = clk_register(&pll->clk, "ti-pll-clk", name, parent_name); if (ret) { printf("%s: failed to register: %d\n", __func__, ret); kfree(pll); return ERR_PTR(ret); } /* Unlock the PLL registers */ writel(PLL_KICK0_VALUE, pll->reg + PLL_KICK0); writel(PLL_KICK1_VALUE, pll->reg + PLL_KICK1); /* Enable all HSDIV outputs */ cfg = readl(pll->reg + PLL_16FFT_CFG); for (i = 0; i < 16; i++) { hsdiv_presence_bit = BIT(16 + i); hsdiv_ctrl_offs = 0x80 + (i * 4); /* Enable HSDIV output if present */ if ((hsdiv_presence_bit & cfg) != 0UL) { ctrl = readl(pll->reg + hsdiv_ctrl_offs); ctrl |= PLL_16FFT_HSDIV_CTRL_CLKOUT_EN; writel(ctrl, pll->reg + hsdiv_ctrl_offs); } } return &pll->clk; } U_BOOT_DRIVER(ti_pll_clk) = { .name = "ti-pll-clk", .id = UCLASS_CLK, .ops = &ti_pll_clk_ops, .flags = DM_FLAG_PRE_RELOC, };