| // 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 <t-kristo@ti.com> |
| */ |
| |
| #include <common.h> |
| #include <asm/io.h> |
| #include <dm.h> |
| #include <div64.h> |
| #include <errno.h> |
| #include <clk-uclass.h> |
| #include <linux/clk-provider.h> |
| #include "k3-clk.h" |
| #include <linux/rational.h> |
| |
| /* 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, |
| }; |