[PATCH 07/13] clk: exynos: Add Samsung clock framework
Minkyu Kang
promsoft at gmail.com
Wed Dec 27 10:11:48 CET 2023
Hi,
2023년 12월 13일 (수) 12:27, Sam Protsenko <semen.protsenko at linaro.org>님이 작성:
> 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 at linaro.org>
> ---
> drivers/clk/exynos/Makefile | 9 +-
> drivers/clk/exynos/clk-pll.c | 167 +++++++++++++++++++++++++
> drivers/clk/exynos/clk-pll.h | 23 ++++
> drivers/clk/exynos/clk.c | 121 +++++++++++++++++++
> drivers/clk/exynos/clk.h | 228 +++++++++++++++++++++++++++++++++++
> 5 files changed, 546 insertions(+), 2 deletions(-)
> create mode 100644 drivers/clk/exynos/clk-pll.c
> create mode 100644 drivers/clk/exynos/clk-pll.h
> create mode 100644 drivers/clk/exynos/clk.c
> create mode 100644 drivers/clk/exynos/clk.h
>
> diff --git a/drivers/clk/exynos/Makefile b/drivers/clk/exynos/Makefile
> index 7faf238571ef..04c5b9a39e16 100644
> --- a/drivers/clk/exynos/Makefile
> +++ b/drivers/clk/exynos/Makefile
> @@ -1,6 +1,11 @@
> # SPDX-License-Identifier: GPL-2.0+
> #
> # Copyright (C) 2016 Samsung Electronics
> -# Thomas Abraham <thomas.ab at samsung.com>
> +# Copyright (C) 2023 Linaro Ltd.
> +#
> +# Authors:
> +# Thomas Abraham <thomas.ab at samsung.com>
> +# Sam Protsenko <semen.protsenko at 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
> index 000000000000..9e496ff83aaf
> --- /dev/null
> +++ b/drivers/clk/exynos/clk-pll.c
> @@ -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 at exynos.com>
> + * Sam Protsenko <semen.protsenko at 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
> index 000000000000..3b477369aeb8
> --- /dev/null
> +++ b/drivers/clk/exynos/clk-pll.h
> @@ -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 at exynos.com>
> + * Sam Protsenko <semen.protsenko at 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,
why don't you modify to uppercase?
> +};
> +
> +#endif /* __EXYNOS_CLK_PLL_H */
> diff --git a/drivers/clk/exynos/clk.c b/drivers/clk/exynos/clk.c
> new file mode 100644
> index 000000000000..430767f072d8
> --- /dev/null
> +++ b/drivers/clk/exynos/clk.c
> @@ -0,0 +1,121 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2023 Linaro Ltd.
> + * Sam Protsenko <semen.protsenko at 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;
wouldn't it be better if use a more meaningful name like mux?
> +
> + m = &clk_list[cnt];
Is there any possibility that the value is null or wrong (e.g. overflow)
> + 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
> index 000000000000..91a51b877a63
> --- /dev/null
> +++ b/drivers/clk/exynos/clk.h
> @@ -0,0 +1,228 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2023 Linaro Ltd.
> + * Sam Protsenko <semen.protsenko at 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 */
> --
> 2.39.2
>
Thanks.
Minkyu Kang.
More information about the U-Boot
mailing list