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-kri...@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-kri...@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-kri...@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__ */
-- 
2.17.1

--
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. 
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

Reply via email to