
Add support for TI K3 SoC PLLs. This clock type supports enabling/disabling/setting and querying the clock rate for the PLL. The euclidean library routine is used to calculate divider/multiplier rates for the PLLs.
Signed-off-by: Tero Kristo t-kristo@ti.com --- drivers/clk/Kconfig | 12 ++ drivers/clk/Makefile | 1 + drivers/clk/clk-k3-pll.c | 273 +++++++++++++++++++++++++++++++++++++++ include/k3-clk.h | 15 +++ 4 files changed, 301 insertions(+) create mode 100644 drivers/clk/clk-k3-pll.c create mode 100644 include/k3-clk.h
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 4dfbad7986..b2e9458f85 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -106,6 +106,18 @@ config CLK_TI_SCI available on some new TI's SoCs. If you wish to use clock resources managed by the TI System Controller, say Y here. Otherwise, say N.
+config CLK_K3_PLL + bool "PLL clock support for K3 SoC family of devices" + depends on CLK && LIB_RATIONAL + help + Enables PLL clock support for K3 SoC family of devices. + +config SPL_CLK_K3_PLL + bool "PLL clock support for K3 SoC family of devices" + depends on CLK && LIB_RATIONAL && SPL + help + Enables PLL clock support for K3 SoC family of devices. + config CLK_HSDK bool "Enable cgu clock driver for HSDK boards" depends on CLK && TARGET_HSDK diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index d1e295ac7c..6009eab800 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -48,5 +48,6 @@ obj-$(CONFIG_SANDBOX) += clk_sandbox_test.o obj-$(CONFIG_SANDBOX_CLK_CCF) += clk_sandbox_ccf.o obj-$(CONFIG_STM32H7) += clk_stm32h7.o obj-$(CONFIG_CLK_TI_SCI) += clk-ti-sci.o +obj-$(CONFIG_$(SPL_TPL_)CLK_K3_PLL) += clk-k3-pll.o obj-$(CONFIG_CLK_VERSAL) += clk_versal.o obj-$(CONFIG_CLK_CDCE9XX) += clk-cdce9xx.o diff --git a/drivers/clk/clk-k3-pll.c b/drivers/clk/clk-k3-pll.c new file mode 100644 index 0000000000..2240f13c7a --- /dev/null +++ b/drivers/clk/clk-k3-pll.c @@ -0,0 +1,273 @@ +// 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 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); + + return parent_freq * pllm / plld + + ((parent_freq * pllfm / plld) >> + PLL_16FFT_FREQ_CTRL1_FB_DIV_FRAC_BITS); +} + +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, +}; diff --git a/include/k3-clk.h b/include/k3-clk.h new file mode 100644 index 0000000000..fc84378d03 --- /dev/null +++ b/include/k3-clk.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2020 - Texas Instruments Incorporated - http://www.ti.com + * Tero Kristo t-kristo@ti.com + */ + +#ifndef __K3_CLK_H__ +#define __K3_CLK_H__ + +#include <linux/clk-provider.h> + +struct clk *clk_register_ti_pll(const char *name, const char *parent_name, + void __iomem *reg); + +#endif /* __K3_CLK_H__ */