]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
clk: exynos: Add Samsung clock framework
authorSam Protsenko <semen.protsenko@linaro.org>
Thu, 11 Jan 2024 03:09:03 +0000 (21:09 -0600)
committerMinkyu Kang <mk7.kang@samsung.com>
Wed, 24 Jan 2024 02:23:20 +0000 (11:23 +0900)
Heavily based on Linux kernel Samsung clock framework, with some changes
to accommodate the differences in U-Boot CCF implementation. It's also
quite minimal as compared to the Linux version.

Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org>
Reviewed-by: Chanho Park <chanho61.park@samsung.com>
Signed-off-by: Minkyu Kang <mk7.kang@samsung.com>
drivers/clk/exynos/Makefile
drivers/clk/exynos/clk-pll.c [new file with mode: 0644]
drivers/clk/exynos/clk-pll.h [new file with mode: 0644]
drivers/clk/exynos/clk.c [new file with mode: 0644]
drivers/clk/exynos/clk.h [new file with mode: 0644]

index 7faf238571ef191cebe0590787161b8ea92d1fb4..04c5b9a39e16af912ef2dd135b450022514768e1 100644 (file)
@@ -1,6 +1,11 @@
 # SPDX-License-Identifier: GPL-2.0+
 #
 # Copyright (C) 2016 Samsung Electronics
-# Thomas Abraham <thomas.ab@samsung.com>
+# Copyright (C) 2023 Linaro Ltd.
+#
+# Authors:
+#   Thomas Abraham <thomas.ab@samsung.com>
+#   Sam Protsenko <semen.protsenko@linaro.org>
 
-obj-$(CONFIG_CLK_EXYNOS7420)   += clk-exynos7420.o
+obj-$(CONFIG_$(SPL_TPL_)CLK_CCF)       += clk.o clk-pll.o
+obj-$(CONFIG_CLK_EXYNOS7420)           += clk-exynos7420.o
diff --git a/drivers/clk/exynos/clk-pll.c b/drivers/clk/exynos/clk-pll.c
new file mode 100644 (file)
index 0000000..4aacbc2
--- /dev/null
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2016 Samsung Electronics
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Authors:
+ *   Thomas Abraham <thomas.ab@samsung.com>
+ *   Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * This file contains the utility functions to register the pll clocks.
+ */
+
+#include <asm/io.h>
+#include <div64.h>
+#include <malloc.h>
+#include <clk-uclass.h>
+#include <dm/device.h>
+#include <clk.h>
+#include "clk.h"
+
+#define UBOOT_DM_CLK_SAMSUNG_PLL0822X  "samsung_clk_pll0822x"
+#define UBOOT_DM_CLK_SAMSUNG_PLL0831X  "samsung_clk_pll0831x"
+
+struct samsung_clk_pll {
+       struct clk              clk;
+       void __iomem            *con_reg;
+       enum samsung_pll_type   type;
+};
+
+#define to_clk_pll(_clk) container_of(_clk, struct samsung_clk_pll, clk)
+
+/*
+ * PLL0822x Clock Type
+ */
+
+#define PLL0822X_MDIV_MASK             0x3ff
+#define PLL0822X_PDIV_MASK             0x3f
+#define PLL0822X_SDIV_MASK             0x7
+#define PLL0822X_MDIV_SHIFT            16
+#define PLL0822X_PDIV_SHIFT            8
+#define PLL0822X_SDIV_SHIFT            0
+
+static unsigned long samsung_pll0822x_recalc_rate(struct clk *clk)
+{
+       struct samsung_clk_pll *pll = to_clk_pll(clk);
+       u32 mdiv, pdiv, sdiv, pll_con3;
+       u64 fvco = clk_get_parent_rate(clk);
+
+       pll_con3 = readl_relaxed(pll->con_reg);
+       mdiv = (pll_con3 >> PLL0822X_MDIV_SHIFT) & PLL0822X_MDIV_MASK;
+       pdiv = (pll_con3 >> PLL0822X_PDIV_SHIFT) & PLL0822X_PDIV_MASK;
+       sdiv = (pll_con3 >> PLL0822X_SDIV_SHIFT) & PLL0822X_SDIV_MASK;
+
+       fvco *= mdiv;
+       do_div(fvco, (pdiv << sdiv));
+       return (unsigned long)fvco;
+}
+
+static const struct clk_ops samsung_pll0822x_clk_min_ops = {
+       .get_rate = samsung_pll0822x_recalc_rate,
+};
+
+/*
+ * PLL0831x Clock Type
+ */
+
+#define PLL0831X_KDIV_MASK             0xffff
+#define PLL0831X_MDIV_MASK             0x1ff
+#define PLL0831X_PDIV_MASK             0x3f
+#define PLL0831X_SDIV_MASK             0x7
+#define PLL0831X_MDIV_SHIFT            16
+#define PLL0831X_PDIV_SHIFT            8
+#define PLL0831X_SDIV_SHIFT            0
+#define PLL0831X_KDIV_SHIFT            0
+
+static unsigned long samsung_pll0831x_recalc_rate(struct clk *clk)
+{
+       struct samsung_clk_pll *pll = to_clk_pll(clk);
+       u32 mdiv, pdiv, sdiv, pll_con3, pll_con5;
+       s16 kdiv;
+       u64 fvco = clk_get_parent_rate(clk);
+
+       pll_con3 = readl_relaxed(pll->con_reg);
+       pll_con5 = readl_relaxed(pll->con_reg + 8);
+       mdiv = (pll_con3 >> PLL0831X_MDIV_SHIFT) & PLL0831X_MDIV_MASK;
+       pdiv = (pll_con3 >> PLL0831X_PDIV_SHIFT) & PLL0831X_PDIV_MASK;
+       sdiv = (pll_con3 >> PLL0831X_SDIV_SHIFT) & PLL0831X_SDIV_MASK;
+       kdiv = (s16)((pll_con5 >> PLL0831X_KDIV_SHIFT) & PLL0831X_KDIV_MASK);
+
+       fvco *= (mdiv << 16) + kdiv;
+       do_div(fvco, (pdiv << sdiv));
+       fvco >>= 16;
+
+       return (unsigned long)fvco;
+}
+
+static const struct clk_ops samsung_pll0831x_clk_min_ops = {
+       .get_rate = samsung_pll0831x_recalc_rate,
+};
+
+static struct clk *_samsung_clk_register_pll(void __iomem *base,
+                                       const struct samsung_pll_clock *pll_clk)
+{
+       struct samsung_clk_pll *pll;
+       struct clk *clk;
+       const char *drv_name;
+       int ret;
+
+       pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+       if (!pll)
+               return ERR_PTR(-ENOMEM);
+
+       pll->con_reg = base + pll_clk->con_offset;
+       pll->type = pll_clk->type;
+       clk = &pll->clk;
+       clk->flags = pll_clk->flags;
+
+       switch (pll_clk->type) {
+       case pll_0822x:
+               drv_name = UBOOT_DM_CLK_SAMSUNG_PLL0822X;
+               break;
+       case pll_0831x:
+               drv_name = UBOOT_DM_CLK_SAMSUNG_PLL0831X;
+               break;
+       default:
+               kfree(pll);
+               return ERR_PTR(-ENODEV);
+       }
+
+       ret = clk_register(clk, drv_name, pll_clk->name, pll_clk->parent_name);
+       if (ret) {
+               kfree(pll);
+               return ERR_PTR(ret);
+       }
+
+       return clk;
+}
+
+void samsung_clk_register_pll(void __iomem *base,
+                             const struct samsung_pll_clock *clk_list,
+                             unsigned int nr_clk)
+{
+       unsigned int cnt;
+
+       for (cnt = 0; cnt < nr_clk; cnt++) {
+               struct clk *clk;
+               const struct samsung_pll_clock *pll_clk;
+
+               pll_clk = &clk_list[cnt];
+               clk = _samsung_clk_register_pll(base, pll_clk);
+               clk_dm(pll_clk->id, clk);
+       }
+}
+
+U_BOOT_DRIVER(samsung_pll0822x_clk) = {
+       .name   = UBOOT_DM_CLK_SAMSUNG_PLL0822X,
+       .id     = UCLASS_CLK,
+       .ops    = &samsung_pll0822x_clk_min_ops,
+       .flags  = DM_FLAG_PRE_RELOC,
+};
+
+U_BOOT_DRIVER(samsung_pll0831x_clk) = {
+       .name   = UBOOT_DM_CLK_SAMSUNG_PLL0831X,
+       .id     = UCLASS_CLK,
+       .ops    = &samsung_pll0831x_clk_min_ops,
+       .flags  = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/clk/exynos/clk-pll.h b/drivers/clk/exynos/clk-pll.h
new file mode 100644 (file)
index 0000000..bd79309
--- /dev/null
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2016 Samsung Electronics
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Authors:
+ *   Thomas Abraham <thomas.ab@samsung.com>
+ *   Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Common Clock Framework support for all PLL's in Samsung platforms.
+ */
+
+#ifndef __EXYNOS_CLK_PLL_H
+#define __EXYNOS_CLK_PLL_H
+
+#include <linux/clk-provider.h>
+
+enum samsung_pll_type {
+       pll_0822x,
+       pll_0831x,
+};
+
+#endif /* __EXYNOS_CLK_PLL_H */
diff --git a/drivers/clk/exynos/clk.c b/drivers/clk/exynos/clk.c
new file mode 100644 (file)
index 0000000..430767f
--- /dev/null
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ * Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * This file includes utility functions to register clocks to common
+ * clock framework for Samsung platforms.
+ */
+
+#include <dm.h>
+#include "clk.h"
+
+void samsung_clk_register_mux(void __iomem *base,
+                             const struct samsung_mux_clock *clk_list,
+                             unsigned int nr_clk)
+{
+       unsigned int cnt;
+
+       for (cnt = 0; cnt < nr_clk; cnt++) {
+               struct clk *clk;
+               const struct samsung_mux_clock *m;
+
+               m = &clk_list[cnt];
+               clk = clk_register_mux(NULL, m->name, m->parent_names,
+                       m->num_parents, m->flags, base + m->offset, m->shift,
+                       m->width, m->mux_flags);
+               clk_dm(m->id, clk);
+       }
+}
+
+void samsung_clk_register_div(void __iomem *base,
+                             const struct samsung_div_clock *clk_list,
+                             unsigned int nr_clk)
+{
+       unsigned int cnt;
+
+       for (cnt = 0; cnt < nr_clk; cnt++) {
+               struct clk *clk;
+               const struct samsung_div_clock *d;
+
+               d = &clk_list[cnt];
+               clk = clk_register_divider(NULL, d->name, d->parent_name,
+                       d->flags, base + d->offset, d->shift,
+                       d->width, d->div_flags);
+               clk_dm(d->id, clk);
+       }
+}
+
+void samsung_clk_register_gate(void __iomem *base,
+                              const struct samsung_gate_clock *clk_list,
+                              unsigned int nr_clk)
+{
+       unsigned int cnt;
+
+       for (cnt = 0; cnt < nr_clk; cnt++) {
+               struct clk *clk;
+               const struct samsung_gate_clock *g;
+
+               g = &clk_list[cnt];
+               clk = clk_register_gate(NULL, g->name, g->parent_name,
+                       g->flags, base + g->offset, g->bit_idx,
+                       g->gate_flags, NULL);
+               clk_dm(g->id, clk);
+       }
+}
+
+typedef void (*samsung_clk_register_fn)(void __iomem *base,
+                                       const void *clk_list,
+                                       unsigned int nr_clk);
+
+static const samsung_clk_register_fn samsung_clk_register_fns[] = {
+       [S_CLK_MUX]     = (samsung_clk_register_fn)samsung_clk_register_mux,
+       [S_CLK_DIV]     = (samsung_clk_register_fn)samsung_clk_register_div,
+       [S_CLK_GATE]    = (samsung_clk_register_fn)samsung_clk_register_gate,
+       [S_CLK_PLL]     = (samsung_clk_register_fn)samsung_clk_register_pll,
+};
+
+/**
+ * samsung_cmu_register_clocks() - Register provided clock groups
+ * @base: Base address of CMU registers
+ * @clk_groups: list of clock groups
+ * @nr_groups: count of clock groups in @clk_groups
+ *
+ * Having the array of clock groups @clk_groups makes it possible to keep a
+ * correct clocks registration order.
+ */
+void samsung_cmu_register_clocks(void __iomem *base,
+                                const struct samsung_clk_group *clk_groups,
+                                unsigned int nr_groups)
+{
+       unsigned int i;
+
+       for (i = 0; i < nr_groups; i++) {
+               const struct samsung_clk_group *g = &clk_groups[i];
+
+               samsung_clk_register_fns[g->type](base, g->clk_list, g->nr_clk);
+       }
+}
+
+/**
+ * samsung_cmu_register_one - Register all CMU clocks
+ * @dev: CMU device
+ * @clk_groups: list of CMU clock groups
+ * @nr_groups: count of CMU clock groups in @clk_groups
+ *
+ * Return: 0 on success or negative value on error.
+ */
+int samsung_cmu_register_one(struct udevice *dev,
+                            const struct samsung_clk_group *clk_groups,
+                            unsigned int nr_groups)
+{
+       void __iomem *base;
+
+       base = dev_read_addr_ptr(dev);
+       if (!base)
+               return -EINVAL;
+
+       samsung_cmu_register_clocks(base, clk_groups, nr_groups);
+
+       return 0;
+}
diff --git a/drivers/clk/exynos/clk.h b/drivers/clk/exynos/clk.h
new file mode 100644 (file)
index 0000000..91a51b8
--- /dev/null
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023 Linaro Ltd.
+ * Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Common Clock Framework support for all Samsung platforms.
+ */
+
+#ifndef __EXYNOS_CLK_H
+#define __EXYNOS_CLK_H
+
+#include <errno.h>
+#include <linux/clk-provider.h>
+#include "clk-pll.h"
+
+/**
+ * struct samsung_mux_clock - information about mux clock
+ * @id: platform specific id of the clock
+ * @name: name of this mux clock
+ * @parent_names: array of pointer to parent clock names
+ * @num_parents: number of parents listed in @parent_names
+ * @flags: optional flags for basic clock
+ * @offset: offset of the register for configuring the mux
+ * @shift: starting bit location of the mux control bit-field in @reg
+ * @width: width of the mux control bit-field in @reg
+ * @mux_flags: flags for mux-type clock
+ */
+struct samsung_mux_clock {
+       unsigned int            id;
+       const char              *name;
+       const char              * const *parent_names;
+       u8                      num_parents;
+       unsigned long           flags;
+       unsigned long           offset;
+       u8                      shift;
+       u8                      width;
+       u8                      mux_flags;
+};
+
+#define PNAME(x) static const char * const x[]
+
+#define __MUX(_id, cname, pnames, o, s, w, f, mf)                      \
+       {                                                               \
+               .id             = _id,                                  \
+               .name           = cname,                                \
+               .parent_names   = pnames,                               \
+               .num_parents    = ARRAY_SIZE(pnames),                   \
+               .flags          = (f) | CLK_SET_RATE_NO_REPARENT,       \
+               .offset         = o,                                    \
+               .shift          = s,                                    \
+               .width          = w,                                    \
+               .mux_flags      = mf,                                   \
+       }
+
+#define MUX(_id, cname, pnames, o, s, w)                               \
+       __MUX(_id, cname, pnames, o, s, w, 0, 0)
+
+#define MUX_F(_id, cname, pnames, o, s, w, f, mf)                      \
+       __MUX(_id, cname, pnames, o, s, w, f, mf)
+
+/**
+ * struct samsung_div_clock - information about div clock
+ * @id: platform specific id of the clock
+ * @name: name of this div clock
+ * @parent_name: name of the parent clock
+ * @flags: optional flags for basic clock
+ * @offset: offset of the register for configuring the div
+ * @shift: starting bit location of the div control bit-field in @reg
+ * @width: width of the bitfield
+ * @div_flags: flags for div-type clock
+ */
+struct samsung_div_clock {
+       unsigned int            id;
+       const char              *name;
+       const char              *parent_name;
+       unsigned long           flags;
+       unsigned long           offset;
+       u8                      shift;
+       u8                      width;
+       u8                      div_flags;
+};
+
+#define __DIV(_id, cname, pname, o, s, w, f, df)       \
+       {                                               \
+               .id             = _id,                  \
+               .name           = cname,                \
+               .parent_name    = pname,                \
+               .flags          = f,                    \
+               .offset         = o,                    \
+               .shift          = s,                    \
+               .width          = w,                    \
+               .div_flags      = df,                   \
+       }
+
+#define DIV(_id, cname, pname, o, s, w)                        \
+       __DIV(_id, cname, pname, o, s, w, 0, 0)
+
+#define DIV_F(_id, cname, pname, o, s, w, f, df)       \
+       __DIV(_id, cname, pname, o, s, w, f, df)
+
+/**
+ * struct samsung_gate_clock - information about gate clock
+ * @id: platform specific id of the clock
+ * @name: name of this gate clock
+ * @parent_name: name of the parent clock
+ * @flags: optional flags for basic clock
+ * @offset: offset of the register for configuring the gate
+ * @bit_idx: bit index of the gate control bit-field in @reg
+ * @gate_flags: flags for gate-type clock
+ */
+struct samsung_gate_clock {
+       unsigned int            id;
+       const char              *name;
+       const char              *parent_name;
+       unsigned long           flags;
+       unsigned long           offset;
+       u8                      bit_idx;
+       u8                      gate_flags;
+};
+
+#define __GATE(_id, cname, pname, o, b, f, gf)                 \
+       {                                                       \
+               .id             = _id,                          \
+               .name           = cname,                        \
+               .parent_name    = pname,                        \
+               .flags          = f,                            \
+               .offset         = o,                            \
+               .bit_idx        = b,                            \
+               .gate_flags     = gf,                           \
+       }
+
+#define GATE(_id, cname, pname, o, b, f, gf)                   \
+       __GATE(_id, cname, pname, o, b, f, gf)
+
+/**
+ * struct samsung_pll_clock - information about pll clock
+ * @id: platform specific id of the clock
+ * @name: name of this pll clock
+ * @parent_name: name of the parent clock
+ * @flags: optional flags for basic clock
+ * @con_offset: offset of the register for configuring the PLL
+ * @type: type of PLL to be registered
+ */
+struct samsung_pll_clock {
+       unsigned int            id;
+       const char              *name;
+       const char              *parent_name;
+       unsigned long           flags;
+       int                     con_offset;
+       enum samsung_pll_type   type;
+};
+
+#define PLL(_typ, _id, _name, _pname, _con)            \
+       {                                               \
+               .id             = _id,                  \
+               .name           = _name,                \
+               .parent_name    = _pname,               \
+               .flags          = CLK_GET_RATE_NOCACHE, \
+               .con_offset     = _con,                 \
+               .type           = _typ,                 \
+       }
+
+enum samsung_clock_type {
+       S_CLK_MUX,
+       S_CLK_DIV,
+       S_CLK_GATE,
+       S_CLK_PLL,
+};
+
+/**
+ * struct samsung_clock_group - contains a list of clocks of one type
+ * @type: type of clocks this structure contains
+ * @clk_list: list of clocks
+ * @nr_clk: count of clocks in @clk_list
+ */
+struct samsung_clk_group {
+       enum samsung_clock_type type;
+       const void *clk_list;
+       unsigned int nr_clk;
+};
+
+void samsung_clk_register_mux(void __iomem *base,
+                             const struct samsung_mux_clock *clk_list,
+                             unsigned int nr_clk);
+void samsung_clk_register_div(void __iomem *base,
+                             const struct samsung_div_clock *clk_list,
+                             unsigned int nr_clk);
+void samsung_clk_register_gate(void __iomem *base,
+                              const struct samsung_gate_clock *clk_list,
+                              unsigned int nr_clk);
+void samsung_clk_register_pll(void __iomem *base,
+                             const struct samsung_pll_clock *clk_list,
+                             unsigned int nr_clk);
+
+void samsung_cmu_register_clocks(void __iomem *base,
+                                const struct samsung_clk_group *clk_groups,
+                                unsigned int nr_groups);
+int samsung_cmu_register_one(struct udevice *dev,
+                            const struct samsung_clk_group *clk_groups,
+                            unsigned int nr_groups);
+
+/**
+ * samsung_register_cmu - Register CMU clocks ensuring parent CMU is present
+ * @dev: CMU device
+ * @clk_groups: list of CMU clock groups
+ * @parent_drv: name of parent CMU driver
+ *
+ * Register provided CMU clocks, but make sure CMU_TOP driver is instantiated
+ * first.
+ *
+ * Return: 0 on success or negative value on error.
+ */
+#define samsung_register_cmu(dev, clk_groups, parent_drv)              \
+({                                                                     \
+       struct udevice *__parent;                                       \
+       int __ret;                                                      \
+                                                                       \
+       __ret = uclass_get_device_by_driver(UCLASS_CLK,                 \
+               DM_DRIVER_GET(parent_drv), &__parent);                  \
+       if (__ret || !__parent)                                         \
+               __ret = -ENOENT;                                        \
+       else                                                            \
+               __ret = samsung_cmu_register_one(dev, clk_groups,       \
+                       ARRAY_SIZE(clk_groups));                        \
+       __ret;                                                          \
+})
+
+#endif /* __EXYNOS_CLK_H */