From: Tero Kristo <t-kri...@ti.com>

Add driver to support TI K3 generation SoC clocks. This driver registers
the clocks provided via platform data, and adds support for controlling
the clocks via DT handles.

Signed-off-by: Tero Kristo <t-kri...@ti.com>
Signed-off-by: Tero Kristo <kri...@kernel.org>
---
 drivers/clk/ti/Kconfig  |  12 ++
 drivers/clk/ti/Makefile |   1 +
 drivers/clk/ti/clk-k3.c | 374 ++++++++++++++++++++++++++++++++++++++++
 include/k3-clk.h        | 162 +++++++++++++++++
 4 files changed, 549 insertions(+)
 create mode 100644 drivers/clk/ti/clk-k3.c

diff --git a/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig
index a8ec4f541a..fbcdefd889 100644
--- a/drivers/clk/ti/Kconfig
+++ b/drivers/clk/ti/Kconfig
@@ -53,3 +53,15 @@ config SPL_CLK_K3_PLL
        depends on CLK && LIB_RATIONAL && SPL
        help
          Enables PLL clock support for K3 SoC family of devices.
+
+config CLK_K3
+       bool "Clock support for K3 SoC family of devices"
+       depends on CLK
+       help
+         Enables the clock translation layer from DT to device clocks.
+
+config SPL_CLK_K3
+       bool "Clock support for K3 SoC family of devices"
+       depends on CLK && SPL
+       help
+         Enables the clock translation layer from DT to device clocks.
diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile
index 47839213e5..07aa9a53e0 100644
--- a/drivers/clk/ti/Makefile
+++ b/drivers/clk/ti/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_CLK_TI_GATE) += clk-gate.o
 obj-$(CONFIG_CLK_TI_MUX) += clk-mux.o
 obj-$(CONFIG_CLK_TI_SCI) += clk-sci.o
 obj-$(CONFIG_$(SPL_TPL_)CLK_K3_PLL) += clk-k3-pll.o
+obj-$(CONFIG_$(SPL_TPL_)CLK_K3) += clk-k3.o
diff --git a/drivers/clk/ti/clk-k3.c b/drivers/clk/ti/clk-k3.c
new file mode 100644
index 0000000000..e921894e7a
--- /dev/null
+++ b/drivers/clk/ti/clk-k3.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Texas Instruments K3 clock driver
+ *
+ * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
+ *     Tero Kristo <t-kri...@ti.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <soc.h>
+#include <clk-uclass.h>
+#include "k3-clk.h"
+
+#define PLL_MIN_FREQ   800000000
+#define PLL_MAX_FREQ   3200000000UL
+#define PLL_MAX_DIV    127
+
+/**
+ * struct clk_map - mapping from dev/clk id tuples towards physical clocks
+ * @dev_id: device ID for the clock
+ * @clk_id: clock ID for the clock
+ * @clk: pointer to the registered clock entry for the mapping
+ */
+struct clk_map {
+       u16 dev_id;
+       u32 clk_id;
+       struct clk *clk;
+};
+
+/**
+ * struct ti_clk_data - clock controller information structure
+ * @map: mapping from dev/clk id tuples to physical clock entries
+ * @size: number of entries in the map
+ */
+struct ti_clk_data {
+       struct clk_map *map;
+       int size;
+};
+
+static ulong osc_freq;
+
+static void clk_add_map(struct ti_clk_data *data, struct clk *clk,
+                       u32 dev_id, u32 clk_id)
+{
+       struct clk_map *map;
+
+       debug("%s: added clk=%p, data=%p, dev=%d, clk=%d\n", __func__,
+             clk, data, dev_id, clk_id);
+       if (!clk)
+               return;
+
+       map = data->map + data->size++;
+
+       map->dev_id = dev_id;
+       map->clk_id = clk_id;
+       map->clk = clk;
+}
+
+static const struct soc_attr ti_k3_soc_clk_data[] = {
+#if IS_ENABLED(CONFIG_SOC_K3_J721E)
+       {
+               .family = "J721E",
+               .data = &j721e_clk_platdata,
+       },
+       {
+               .family = "J7200",
+               .data = &j7200_clk_platdata,
+       },
+#endif
+       { /* sentinel */ }
+};
+
+static int ti_clk_probe(struct udevice *dev)
+{
+       struct ti_clk_data *data = dev_get_priv(dev);
+       struct clk *clk;
+       const char *name;
+       const struct clk_data *ti_clk_data;
+       int i, j;
+       const struct soc_attr *soc_match_data;
+       const struct ti_k3_clk_platdata *pdata;
+
+       debug("%s(dev=%p)\n", __func__, dev);
+
+       soc_match_data = soc_device_match(ti_k3_soc_clk_data);
+       if (!soc_match_data)
+               return -ENODEV;
+
+       pdata = (const struct ti_k3_clk_platdata *)soc_match_data->data;
+
+       data->map = kcalloc(pdata->soc_dev_clk_data_cnt, sizeof(*data->map),
+                           GFP_KERNEL);
+       data->size = 0;
+
+       for (i = 0; i < pdata->clk_list_cnt; i++) {
+               ti_clk_data = &pdata->clk_list[i];
+
+               switch (ti_clk_data->type) {
+               case CLK_TYPE_FIXED_RATE:
+                       name = ti_clk_data->clk.fixed_rate.name;
+                       clk = clk_register_fixed_rate(NULL,
+                                                     name,
+                                                     
ti_clk_data->clk.fixed_rate.rate);
+                       break;
+               case CLK_TYPE_DIV:
+                       name = ti_clk_data->clk.div.name;
+                       clk = clk_register_divider(NULL, name,
+                                                  ti_clk_data->clk.div.parent,
+                                                  ti_clk_data->clk.div.flags,
+                                                  
map_physmem(ti_clk_data->clk.div.reg, 0, MAP_NOCACHE),
+                                                  ti_clk_data->clk.div.shift,
+                                                  ti_clk_data->clk.div.width,
+                                                  0);
+                       break;
+               case CLK_TYPE_MUX:
+                       name = ti_clk_data->clk.mux.name;
+                       clk = clk_register_mux(NULL, name,
+                                              ti_clk_data->clk.mux.parents,
+                                              ti_clk_data->clk.mux.num_parents,
+                                              ti_clk_data->clk.mux.flags,
+                                              
map_physmem(ti_clk_data->clk.mux.reg, 0, MAP_NOCACHE),
+                                              ti_clk_data->clk.mux.shift,
+                                              ti_clk_data->clk.mux.width,
+                                              0);
+                       break;
+               case CLK_TYPE_PLL:
+                       name = ti_clk_data->clk.pll.name;
+                       clk = clk_register_ti_pll(name,
+                                                 ti_clk_data->clk.pll.parent,
+                                                 
map_physmem(ti_clk_data->clk.pll.reg, 0, MAP_NOCACHE));
+
+                       if (!osc_freq)
+                               osc_freq = clk_get_rate(clk_get_parent(clk));
+                       break;
+               default:
+                       name = NULL;
+                       clk = NULL;
+                       printf("WARNING: %s has encountered unknown clk type 
%d\n",
+                              __func__, ti_clk_data->type);
+               }
+
+               if (clk && ti_clk_data->default_freq)
+                       clk_set_rate(clk, ti_clk_data->default_freq);
+
+               if (clk && name) {
+                       for (j = 0; j < pdata->soc_dev_clk_data_cnt; j++) {
+                               if (!strcmp(name, 
pdata->soc_dev_clk_data[j].clk_name)) {
+                                       clk_add_map(data, clk, 
pdata->soc_dev_clk_data[j].dev_id,
+                                                   
pdata->soc_dev_clk_data[j].clk_id);
+                               }
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static int _clk_cmp(u32 dev_id, u32 clk_id, const struct clk_map *map)
+{
+       if (map->dev_id == dev_id && map->clk_id == clk_id)
+               return 0;
+       if (map->dev_id > dev_id ||
+           (map->dev_id == dev_id && map->clk_id > clk_id))
+               return -1;
+       return 1;
+}
+
+static int bsearch(u32 dev_id, u32 clk_id, struct clk_map *map, int num)
+{
+       int result;
+       int idx;
+
+       for (idx = 0; idx < num; idx++) {
+               result = _clk_cmp(dev_id, clk_id, &map[idx]);
+
+               if (result == 0)
+                       return idx;
+       }
+
+       return -ENOENT;
+}
+
+static int ti_clk_of_xlate(struct clk *clk,
+                          struct ofnode_phandle_args *args)
+{
+       struct ti_clk_data *data = dev_get_priv(clk->dev);
+       int idx;
+
+       debug("%s(clk=%p, args_count=%d [0]=%d [1]=%d)\n", __func__, clk,
+             args->args_count, args->args[0], args->args[1]);
+
+       if (args->args_count != 2) {
+               debug("Invalid args_count: %d\n", args->args_count);
+               return -EINVAL;
+       }
+
+       if (!data->size)
+               return -EPROBE_DEFER;
+
+       idx = bsearch(args->args[0], args->args[1], data->map, data->size);
+       if (idx < 0)
+               return idx;
+
+       clk->id = idx;
+
+       return 0;
+}
+
+static ulong ti_clk_get_rate(struct clk *clk)
+{
+       struct ti_clk_data *data = dev_get_priv(clk->dev);
+       struct clk *clkp = data->map[clk->id].clk;
+
+       return clk_get_rate(clkp);
+}
+
+static ulong ti_clk_set_rate(struct clk *clk, ulong rate)
+{
+       struct ti_clk_data *data = dev_get_priv(clk->dev);
+       struct clk *clkp = data->map[clk->id].clk;
+       int div = 1;
+       ulong child_rate;
+       const struct clk_ops *ops;
+       ulong new_rate, rem;
+       ulong diff, new_diff;
+
+       /*
+        * We must propagate rate change to parent if current clock type
+        * does not allow setting it.
+        */
+       while (clkp) {
+               ops = clkp->dev->driver->ops;
+               if (ops->set_rate)
+                       break;
+
+               /*
+                * Store child rate so we can calculate the clock rate
+                * that must be passed to parent
+                */
+               child_rate = clk_get_rate(clkp);
+               clkp = clk_get_parent(clkp);
+               if (clkp) {
+                       debug("%s: propagating rate change to parent %s, 
rate=%u.\n",
+                             __func__, clkp->dev->name, (u32)rate / div);
+                       div *= clk_get_rate(clkp) / child_rate;
+               }
+       }
+
+       if (!clkp)
+               return -ENOSYS;
+
+       child_rate = clk_get_rate(clkp);
+
+       new_rate = clk_set_rate(clkp, rate / div);
+
+       diff = abs(new_rate - rate / div);
+
+       debug("%s: clk=%s, div=%d, rate=%u, new_rate=%u, diff=%u\n", __func__,
+             clkp->dev->name, div, (u32)rate, (u32)new_rate, (u32)diff);
+
+       /*
+        * If the new rate differs by 50% of the target,
+        * modify parent. This handles typical cases where we have a hsdiv
+        * following directly a PLL
+        */
+
+       if (diff > rate / div / 2) {
+               ulong pll_tgt;
+               int pll_div = 0;
+
+               clk = clkp;
+
+               debug("%s: propagating rate change to parent, rate=%u.\n",
+                     __func__, (u32)rate / div);
+
+               clkp = clk_get_parent(clkp);
+
+               if (rate > osc_freq) {
+                       if (rate > PLL_MAX_FREQ / 2 && rate < PLL_MAX_FREQ) {
+                               pll_tgt = rate;
+                               pll_div = 1;
+                       } else {
+                               for (pll_div = 2; pll_div < PLL_MAX_DIV; 
pll_div++) {
+                                       pll_tgt = rate / div * pll_div;
+                                       if (pll_tgt >= PLL_MIN_FREQ && pll_tgt 
<= PLL_MAX_FREQ)
+                                               break;
+                               }
+                       }
+               } else {
+                       pll_tgt = osc_freq;
+                       pll_div = rate / div / osc_freq;
+               }
+
+               debug("%s: pll_tgt=%u, rate=%u, div=%u\n", __func__,
+                     (u32)pll_tgt, (u32)rate, pll_div);
+
+               clk_set_rate(clkp, pll_tgt);
+
+               return clk_set_rate(clk, rate / div) * div;
+       }
+
+       /*
+        * If the new rate differs by at least 5% of the target,
+        * we must check for rounding error in a divider, so try
+        * set rate with rate + (parent_freq % rate).
+        */
+
+       if (diff > rate / div / 20) {
+               u64 parent_freq = clk_get_parent_rate(clkp);
+
+               rem = parent_freq % rate;
+               new_rate = clk_set_rate(clkp, (rate / div) + rem);
+               new_diff = abs(new_rate - rate / div);
+
+               if (new_diff > diff) {
+                       new_rate = clk_set_rate(clkp, rate / div);
+               } else {
+                       debug("%s: Using better rate %lu that gives diff %lu\n",
+                             __func__, new_rate, new_diff);
+               }
+       }
+
+       return new_rate;
+}
+
+static int ti_clk_set_parent(struct clk *clk, struct clk *parent)
+{
+       struct ti_clk_data *data = dev_get_priv(clk->dev);
+       struct clk *clkp = data->map[clk->id].clk;
+       struct clk *parentp = data->map[parent->id].clk;
+
+       return clk_set_parent(clkp, parentp);
+}
+
+static int ti_clk_enable(struct clk *clk)
+{
+       struct ti_clk_data *data = dev_get_priv(clk->dev);
+       struct clk *clkp = data->map[clk->id].clk;
+
+       return clk_enable(clkp);
+}
+
+static int ti_clk_disable(struct clk *clk)
+{
+       struct ti_clk_data *data = dev_get_priv(clk->dev);
+       struct clk *clkp = data->map[clk->id].clk;
+
+       return clk_disable(clkp);
+}
+
+static const struct udevice_id ti_clk_of_match[] = {
+       { .compatible = "ti,k2g-sci-clk" },
+       { /* sentinel */ },
+};
+
+static const struct clk_ops ti_clk_ops = {
+       .of_xlate = ti_clk_of_xlate,
+       .set_rate = ti_clk_set_rate,
+       .get_rate = ti_clk_get_rate,
+       .enable = ti_clk_enable,
+       .disable = ti_clk_disable,
+       .set_parent = ti_clk_set_parent,
+};
+
+U_BOOT_DRIVER(ti_clk) = {
+       .name = "ti-clk",
+       .id = UCLASS_CLK,
+       .of_match = ti_clk_of_match,
+       .probe = ti_clk_probe,
+       .priv_auto = sizeof(struct ti_clk_data),
+       .ops = &ti_clk_ops,
+};
diff --git a/include/k3-clk.h b/include/k3-clk.h
index fc84378d03..36f0ddf7cc 100644
--- a/include/k3-clk.h
+++ b/include/k3-clk.h
@@ -7,7 +7,168 @@
 #ifndef __K3_CLK_H__
 #define __K3_CLK_H__
 
+#include <asm/io.h>
+#include <linux/bitops.h>
 #include <linux/clk-provider.h>
+#include <linux/types.h>
+#include <stdint.h>
+
+struct dev_clk {
+       int dev_id;
+       int clk_id;
+       const char *clk_name;
+};
+
+#define DEV_CLK(_dev_id, _clk_id, _clk_name) { .dev_id = _dev_id,              
\
+                                       .clk_id = _clk_id, .clk_name = 
_clk_name, }
+
+#define CLK_TYPE_MUX           0x01
+#define CLK_TYPE_DIV           0x02
+#define CLK_TYPE_PLL           0x03
+#define CLK_TYPE_HFOSC         0x04
+#define CLK_TYPE_POSTDIV       0x05
+#define CLK_TYPE_MUX_PLLCTRL   0x06
+#define CLK_TYPE_FIXED_RATE    0x07
+
+struct pll_data {
+       u32 reg;
+       const char *name;
+       const char *parent;
+       u32 flags;
+};
+
+struct mux_data {
+       u32 reg;
+       const char *name;
+       const char * const *parents;
+       int num_parents;
+       u32 flags;
+       int shift;
+       int width;
+};
+
+struct div_data {
+       u32 reg;
+       const char *name;
+       const char *parent;
+       u32 flags;
+       int shift;
+       int width;
+};
+
+struct hfosc_data {
+       const char *name;
+       u32 flags;
+};
+
+struct fixed_rate_data {
+       const char *name;
+       u64 rate;
+       u32 flags;
+};
+
+struct postdiv_data {
+       const char *name;
+       const char *parent;
+       int width;
+       u32 flags;
+};
+
+struct mux_pllctrl_data {
+       u32 reg;
+       const char *name;
+       const char * const *parents;
+       int num_parents;
+       u32 flags;
+};
+
+struct clk_data {
+       int type;
+       u32 default_freq;
+       union {
+               struct pll_data pll;
+               struct mux_data mux;
+               struct div_data div;
+               struct hfosc_data hfosc;
+               struct postdiv_data postdiv;
+               struct mux_pllctrl_data mux_pllctrl;
+               struct fixed_rate_data fixed_rate;
+       } clk;
+};
+
+#define CLK_MUX(_name, _parents, _num_parents, _reg, _shift, _width, _flags) \
+       {                                                               \
+               .type = CLK_TYPE_MUX,                                   \
+               .clk.mux = { .name = _name, .parents = _parents,        \
+               .reg = _reg,                                            \
+               .num_parents = _num_parents, .shift = _shift,           \
+               .width = _width, .flags = _flags }                      \
+       }
+
+#define CLK_DIV(_name, _parent, _reg, _shift, _width, _flags)  \
+       {                                                       \
+               .type = CLK_TYPE_DIV,                           \
+               .clk.div = {.name = _name, .parent = _parent, .reg = _reg, 
.shift = _shift, .width = _width, .flags = _flags } \
+       }
+
+#define CLK_DIV_DEFFREQ(_name, _parent, _reg, _shift, _width, _flags, _freq) \
+       {                                                       \
+               .type = CLK_TYPE_DIV,                           \
+               .default_freq = _freq,                          \
+               .clk.div = {                                    \
+                       .name = _name, .parent = _parent,       \
+                       .reg = _reg, .shift = _shift,           \
+                       .width = _width, .flags = _flags }      \
+       }
+
+#define CLK_PLL(_name, _parent, _reg,  _flags) \
+       {                                       \
+               .type = CLK_TYPE_PLL,           \
+               .clk.pll = {.name = _name, .parent = _parent, .reg = _reg, 
.flags = _flags } \
+       }
+
+#define CLK_PLL_DEFFREQ(_name, _parent, _reg, _flags, _freq)   \
+       {                                                       \
+               .type = CLK_TYPE_PLL,                           \
+               .default_freq = _freq,                          \
+               .clk.pll = { .name = _name, .parent = _parent,  \
+                               .reg = _reg, .flags = _flags }  \
+       }
+
+#define CLK_HFOSC(_name, _flags)               \
+       {                                       \
+               .type = CLK_TYPE_HFOSC,         \
+               .clk.hfosc = { .name = _name, .flags = _flags } \
+       }
+
+#define CLK_FIXED_RATE(_name, _rate, _flags)           \
+       {                                               \
+               .type = CLK_TYPE_FIXED_RATE,            \
+               .clk.fixed_rate = { .name = _name, .rate = _rate, .flags = 
_flags } \
+       }
+
+#define CLK_POSTDIV(_name, _parent, _width, _flags)    \
+       {                                               \
+               .type = CLK_TYPE_POSTDIV,               \
+               .clk.postdiv = {.name = _name, .parent = _parent, .width = 
_width, .flags = _flags } \
+       }
+
+#define CLK_MUX_PLLCTRL(_name, _parents, _num_parents, _reg, _flags)   \
+       {                                                               \
+               .type = CLK_TYPE_MUX,                                   \
+               .clk.mux_pllctrl = { .name = _name, .parents = _parents,\
+               .num_parents = _num_parents, .flags = _flags }          \
+       }
+
+struct ti_k3_clk_platdata {
+       const struct clk_data *clk_list;
+       int clk_list_cnt;
+       const struct dev_clk *soc_dev_clk_data;
+       int soc_dev_clk_data_cnt;
+};
+
+extern const struct ti_k3_clk_platdata j721e_clk_platdata;
+extern const struct ti_k3_clk_platdata j7200_clk_platdata;
 
 struct clk *clk_register_ti_pll(const char *name, const char *parent_name,
                                void __iomem *reg);
-- 
2.17.1

Reply via email to