aboutsummaryrefslogtreecommitdiff
path: root/drivers/clk
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk')
-rw-r--r--drivers/clk/ti/Kconfig12
-rw-r--r--drivers/clk/ti/Makefile1
-rw-r--r--drivers/clk/ti/clk-k3.c374
3 files changed, 387 insertions, 0 deletions
diff --git a/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig
index a8ec4f541a4..fbcdefd889a 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 47839213e58..07aa9a53e08 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 00000000000..e921894e7a2
--- /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-kristo@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,
+};