[U-Boot] [PATCH v3 11/11] clk: Port Linux common clock framework [CCF] for imx6q to U-boot (tag: 5.0-rc3)
Peng Fan
peng.fan at nxp.com
Fri Apr 26 02:54:00 UTC 2019
> Subject: [PATCH v3 11/11] clk: Port Linux common clock framework [CCF] for
> imx6q to U-boot (tag: 5.0-rc3)
>
> This commit brings the files from Linux kernel to provide clocks support as it is
> used on the Linux kernel with common clock framework [CCF] setup.
>
> The directory structure has been preserved. The ported code only supports
> reading information from PLL, MUX, Divider, etc and enabling/disabling the
> clocks USDHCx/ECSPIx depending on used bus. Moreover, it is agnostic to the
> alias numbering as the information about the clock is read from device tree.
>
> One needs to pay attention to the comments indicating necessary for U-boot's
> DM changes.
>
> If needed the code can be extended to support the "set" part of the clock
> management.
>
>
> Signed-off-by: Lukasz Majewski <lukma at denx.de>
> ---
>
> Changes in v3: None
>
> drivers/clk/Kconfig | 14 ++++
> drivers/clk/Makefile | 2 +
> drivers/clk/clk-divider.c | 148
> ++++++++++++++++++++++++++++++++++
> drivers/clk/clk-fixed-factor.c | 87 ++++++++++++++++++++
> drivers/clk/clk-mux.c | 164
> +++++++++++++++++++++++++++++++++++++
> drivers/clk/clk.c | 56 +++++++++++++
> drivers/clk/imx/Kconfig | 9 +++
> drivers/clk/imx/Makefile | 2 +
> drivers/clk/imx/clk-gate2.c | 113 ++++++++++++++++++++++++++
> drivers/clk/imx/clk-imx6q.c | 179
> +++++++++++++++++++++++++++++++++++++++++
> drivers/clk/imx/clk-pfd.c | 91 +++++++++++++++++++++
> drivers/clk/imx/clk-pllv3.c | 83 +++++++++++++++++++
> drivers/clk/imx/clk.h | 75 +++++++++++++++++
> include/linux/clk-provider.h | 94 ++++++++++++++++++++++
> 14 files changed, 1117 insertions(+)
> create mode 100644 drivers/clk/clk-divider.c create mode 100644
> drivers/clk/clk-fixed-factor.c create mode 100644 drivers/clk/clk-mux.c
> create mode 100644 drivers/clk/clk.c create mode 100644
> drivers/clk/imx/clk-gate2.c create mode 100644
> drivers/clk/imx/clk-imx6q.c create mode 100644 drivers/clk/imx/clk-pfd.c
> create mode 100644 drivers/clk/imx/clk-pllv3.c create mode 100644
> drivers/clk/imx/clk.h create mode 100644 include/linux/clk-provider.h
>
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index
> ff60fc5c45..9df3bc731a 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -46,6 +46,20 @@ config CLK_BOSTON
> help
> Enable this to support the clocks
>
> +config SPL_CLK_CCF
> + bool "SPL Common Clock Framework [CCF] support "
> + depends on SPL_CLK
> + help
> + Enable this option if you want to (re-)use the Linux kernel's Common
> + Clock Framework [CCF] code in U-Boot's SPL.
> +
> +config CLK_CCF
> + bool "Common Clock Framework [CCF] support "
> + depends on CLK
> + help
> + Enable this option if you want to (re-)use the Linux kernel's Common
> + Clock Framework [CCF] code in U-Boot's clock driver.
> +
> config CLK_STM32F
> bool "Enable clock driver support for STM32F family"
> depends on CLK && (STM32F7 || STM32F4) diff --git
> a/drivers/clk/Makefile b/drivers/clk/Makefile index 1d9d725cae..9fcc75e0ea
> 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -7,6 +7,8 @@
> obj-$(CONFIG_$(SPL_TPL_)CLK) += clk-uclass.o
> obj-$(CONFIG_$(SPL_TPL_)CLK) += clk_fixed_rate.o
> obj-$(CONFIG_$(SPL_TPL_)CLK) += clk_fixed_factor.o
> +obj-$(CONFIG_$(SPL_TPL_)CLK_CCF) += clk.o clk-divider.o clk-mux.o
> +obj-$(CONFIG_$(SPL_TPL_)CLK_CCF) += clk-fixed-factor.o
>
> obj-y += imx/
> obj-y += tegra/
> diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c new file mode
> 100644 index 0000000000..3841d8bfbb
> --- /dev/null
> +++ b/drivers/clk/clk-divider.c
> @@ -0,0 +1,148 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de
> + *
> + * Copyright (C) 2011 Sascha Hauer, Pengutronix
> +<s.hauer at pengutronix.de>
> + * Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao at linaro.org>
> + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd
> +<mturquette at linaro.org>
> + *
> + */
> +
> +#include <common.h>
> +#include <asm/io.h>
> +#include <malloc.h>
> +#include <clk-uclass.h>
> +#include <dm/device.h>
> +#include <dm/uclass.h>
> +#include <dm/lists.h>
> +#include <dm/device-internal.h>
> +#include <linux/clk-provider.h>
> +#include <div64.h>
> +#include <clk.h>
> +#include "clk.h"
> +
> +#define UBOOT_DM_CLK_CCF_DIVIDER "ccf_clk_divider"
> +
> +static unsigned int _get_table_div(const struct clk_div_table *table,
> + unsigned int val)
> +{
> + const struct clk_div_table *clkt;
> +
> + for (clkt = table; clkt->div; clkt++)
> + if (clkt->val == val)
> + return clkt->div;
> + return 0;
> +}
> +
> +static unsigned int _get_div(const struct clk_div_table *table,
> + unsigned int val, unsigned long flags, u8 width) {
> + if (flags & CLK_DIVIDER_ONE_BASED)
> + return val;
> + if (flags & CLK_DIVIDER_POWER_OF_TWO)
> + return 1 << val;
> + if (flags & CLK_DIVIDER_MAX_AT_ZERO)
> + return val ? val : clk_div_mask(width) + 1;
> + if (table)
> + return _get_table_div(table, val);
> + return val + 1;
> +}
> +
> +unsigned long divider_recalc_rate(struct clk *hw, unsigned long parent_rate,
> + unsigned int val,
> + const struct clk_div_table *table,
> + unsigned long flags, unsigned long width) {
> + unsigned int div;
> +
> + div = _get_div(table, val, flags, width);
> + if (!div) {
> + WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
> + "%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n",
> + clk_hw_get_name(hw));
> + return parent_rate;
> + }
> +
> + return DIV_ROUND_UP_ULL((u64)parent_rate, div); }
> +
> +static ulong clk_divider_recalc_rate(struct clk *clk) {
> + struct clk_divider *divider =
> + (struct clk_divider *)dev_get_driver_data(clk->dev);
> + unsigned long parent_rate = clk_get_parent_rate(clk);
> + unsigned int val;
> +
> + val = readl(divider->reg) >> divider->shift;
> + val &= clk_div_mask(divider->width);
> +
> + return divider_recalc_rate(clk, parent_rate, val, divider->table,
> + divider->flags, divider->width); }
> +
> +const struct clk_ops clk_divider_ops = {
> + .get_rate = clk_divider_recalc_rate,
> +};
> +
> +static struct clk *_register_divider(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + void __iomem *reg, u8 shift, u8 width,
> + u8 clk_divider_flags, const struct clk_div_table *table) {
> + struct clk_divider *div;
> + struct clk *clk;
> + int ret;
> +
> + if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
> + if (width + shift > 16) {
> + pr_warn("divider value exceeds LOWORD field\n");
> + return ERR_PTR(-EINVAL);
> + }
> + }
> +
> + /* allocate the divider */
> + div = kzalloc(sizeof(*div), GFP_KERNEL);
> + if (!div)
> + return ERR_PTR(-ENOMEM);
> +
> + /* struct clk_divider assignments */
> + div->reg = reg;
> + div->shift = shift;
> + div->width = width;
> + div->flags = clk_divider_flags;
> + div->table = table;
> +
> + /* register the clock */
> + clk = &div->clk;
> +
> + ret = clk_register(clk, UBOOT_DM_CLK_CCF_DIVIDER, (ulong)clk,
> + name, parent_name);
> + if (ret) {
> + kfree(div);
> + return ERR_PTR(ret);
> + }
> +
> + return clk;
> +}
> +
> +struct clk *clk_register_divider(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + void __iomem *reg, u8 shift, u8 width,
> + u8 clk_divider_flags)
> +{
> + struct clk *clk;
> +
> + clk = _register_divider(dev, name, parent_name, flags, reg, shift,
> + width, clk_divider_flags, NULL);
> + if (IS_ERR(clk))
> + return ERR_CAST(clk);
> + return clk;
> +}
> +
> +U_BOOT_DRIVER(ccf_clk_divider) = {
> + .name = UBOOT_DM_CLK_CCF_DIVIDER,
> + .id = UCLASS_CLK,
> + .ops = &clk_divider_ops,
> + .flags = DM_FLAG_PRE_RELOC,
> +};
> diff --git a/drivers/clk/clk-fixed-factor.c b/drivers/clk/clk-fixed-factor.c new
> file mode 100644 index 0000000000..acbc0909b4
> --- /dev/null
> +++ b/drivers/clk/clk-fixed-factor.c
> @@ -0,0 +1,87 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de
> + *
> + * Copyright (C) 2011 Sascha Hauer, Pengutronix
> +<s.hauer at pengutronix.de> */ #include <common.h> #include <malloc.h>
> +#include <clk-uclass.h> #include <dm/device.h> #include
> +<linux/clk-provider.h> #include <div64.h> #include <clk.h> #include
> +"clk.h"
> +
> +#define UBOOT_DM_CLK_IMX_FIXED_FACTOR "ccf_clk_fixed_factor"
> +
> +static ulong clk_factor_recalc_rate(struct clk *clk) {
> + struct clk_fixed_factor *fix =
> + (struct clk_fixed_factor *)dev_get_driver_data(clk->dev);
> + unsigned long parent_rate = clk_get_parent_rate(clk);
> + unsigned long long int rate;
> +
> + rate = (unsigned long long int)parent_rate * fix->mult;
> + do_div(rate, fix->div);
> + return (ulong)rate;
> +}
> +
> +const struct clk_ops ccf_clk_fixed_factor_ops = {
> + .get_rate = clk_factor_recalc_rate,
> +};
> +
> +struct clk *clk_hw_register_fixed_factor(struct device *dev,
> + const char *name, const char *parent_name, unsigned long flags,
> + unsigned int mult, unsigned int div)
> +{
> + struct clk_fixed_factor *fix;
> + struct clk *clk;
> + int ret;
> +
> + fix = kmalloc(sizeof(*fix), GFP_KERNEL);
> + if (!fix)
> + return ERR_PTR(-ENOMEM);
> +
> + /* struct clk_fixed_factor assignments */
> + fix->mult = mult;
> + fix->div = div;
> + clk = &fix->clk;
> +
> + /*
> + * We pass the struct clk *clk pointer (which is the same as
> + * clk_fixed_factor *fix - by the struct elements alignment) to DM as a
> + * driver_data, so it can be easily accessible from the udevice level.
> + * Moreover, the struct clk is only a wrapper on udevice which
> + * corresponds to the "real" clock device.
> + */
> + ret = clk_register(clk, UBOOT_DM_CLK_IMX_FIXED_FACTOR, (ulong)clk,
> + name, parent_name);
> + if (ret) {
> + kfree(fix);
> + return ERR_PTR(ret);
> + }
> +
> + return clk;
> +}
> +
> +struct clk *clk_register_fixed_factor(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + unsigned int mult, unsigned int div)
> +{
> + struct clk *clk;
> +
> + clk = clk_hw_register_fixed_factor(dev, name, parent_name, flags, mult,
> + div);
> + if (IS_ERR(clk))
> + return ERR_CAST(clk);
> + return clk;
> +}
> +
> +U_BOOT_DRIVER(imx_clk_fixed_factor) = {
> + .name = UBOOT_DM_CLK_IMX_FIXED_FACTOR,
> + .id = UCLASS_CLK,
> + .ops = &ccf_clk_fixed_factor_ops,
> + .flags = DM_FLAG_PRE_RELOC,
> +};
> diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c new file mode
> 100644 index 0000000000..2c85f2052c
> --- /dev/null
> +++ b/drivers/clk/clk-mux.c
> @@ -0,0 +1,164 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de
> + *
> + * Copyright (C) 2011 Sascha Hauer, Pengutronix
> +<s.hauer at pengutronix.de>
> + * Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao at linaro.org>
> + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd
> +<mturquette at linaro.org>
> + *
> + * Simple multiplexer clock implementation */
> +
> +/*
> + * U-Boot CCF porting node:
> + *
> + * The Linux kernel - as of tag: 5.0-rc3 is using also the
> +imx_clk_fixup_mux()
> + * version of CCF mux. It is used on e.g. imx6q to provide fixes (like
> + * imx_cscmr1_fixup) for broken HW.
> + *
> + * At least for IMX6Q (but NOT IMX6QP) it is important when we set the
> +parent
> + * clock.
> + */
> +
> +#include <common.h>
> +#include <asm/io.h>
> +#include <malloc.h>
> +#include <clk-uclass.h>
> +#include <dm/device.h>
> +#include <linux/clk-provider.h>
> +#include <clk.h>
> +#include "clk.h"
> +
> +#define UBOOT_DM_CLK_CCF_MUX "ccf_clk_mux"
> +
> +int clk_mux_val_to_index(struct clk *clk, u32 *table, unsigned int flags,
> + unsigned int val)
> +{
> + struct clk_mux *mux = to_clk_mux(clk);
> + int num_parents = mux->num_parents;
> +
> + if (table) {
> + int i;
> +
> + for (i = 0; i < num_parents; i++)
> + if (table[i] == val)
> + return i;
> + return -EINVAL;
> + }
> +
> + if (val && (flags & CLK_MUX_INDEX_BIT))
> + val = ffs(val) - 1;
> +
> + if (val && (flags & CLK_MUX_INDEX_ONE))
> + val--;
> +
> + if (val >= num_parents)
> + return -EINVAL;
> +
> + return val;
> +}
> +
> +static u8 clk_mux_get_parent(struct clk *clk) {
> + struct clk_mux *mux = to_clk_mux(clk);
> + u32 val;
> +
> + val = readl(mux->reg) >> mux->shift;
> + val &= mux->mask;
> +
> + return clk_mux_val_to_index(clk, mux->table, mux->flags, val); }
> +
> +const struct clk_ops clk_mux_ops = {
> + .get_rate = clk_generic_get_rate,
> +};
> +
> +struct clk *clk_hw_register_mux_table(struct device *dev, const char
> *name,
> + const char * const *parent_names, u8 num_parents,
> + unsigned long flags,
> + void __iomem *reg, u8 shift, u32 mask,
> + u8 clk_mux_flags, u32 *table)
> +{
> + struct clk_mux *mux;
> + struct clk *clk;
> + u8 width = 0;
> + int ret;
> +
> + if (clk_mux_flags & CLK_MUX_HIWORD_MASK) {
> + width = fls(mask) - ffs(mask) + 1;
> + if (width + shift > 16) {
> + pr_err("mux value exceeds LOWORD field\n");
> + return ERR_PTR(-EINVAL);
> + }
> + }
> +
> + /* allocate the mux */
> + mux = kzalloc(sizeof(*mux), GFP_KERNEL);
> + if (!mux)
> + return ERR_PTR(-ENOMEM);
> +
> + /* U-boot specific assignments */
> + mux->parent_names = parent_names;
> + mux->num_parents = num_parents;
> +
> + /* struct clk_mux assignments */
> + mux->reg = reg;
> + mux->shift = shift;
> + mux->mask = mask;
> + mux->flags = clk_mux_flags;
> + mux->table = table;
> +
> + clk = &mux->clk;
> +
> + /*
> + * Read the current mux setup - so we assign correct parent.
> + *
> + * Changing parent would require changing internals of udevice struct
> + * for the corresponding clock (to do that define .set_parent() method.
> + */
> + ret = clk_register(clk, UBOOT_DM_CLK_CCF_MUX, (ulong)clk, name,
> + parent_names[clk_mux_get_parent(clk)]);
> + if (ret) {
> + kfree(mux);
> + return ERR_PTR(ret);
> + }
> +
> + return clk;
> +}
> +
> +struct clk *clk_register_mux_table(struct device *dev, const char *name,
> + const char * const *parent_names, u8 num_parents,
> + unsigned long flags,
> + void __iomem *reg, u8 shift, u32 mask,
> + u8 clk_mux_flags, u32 *table)
> +{
> + struct clk *clk;
> +
> + clk = clk_hw_register_mux_table(dev, name, parent_names,
> num_parents,
> + flags, reg, shift, mask, clk_mux_flags,
> + table);
> + if (IS_ERR(clk))
> + return ERR_CAST(clk);
> + return clk;
> +}
> +
> +struct clk *clk_register_mux(struct device *dev, const char *name,
> + const char * const *parent_names, u8 num_parents,
> + unsigned long flags,
> + void __iomem *reg, u8 shift, u8 width,
> + u8 clk_mux_flags)
> +{
> + u32 mask = BIT(width) - 1;
> +
> + return clk_register_mux_table(dev, name, parent_names, num_parents,
> + flags, reg, shift, mask, clk_mux_flags,
> + NULL);
> +}
> +
> +U_BOOT_DRIVER(ccf_clk_mux) = {
> + .name = UBOOT_DM_CLK_CCF_MUX,
> + .id = UCLASS_CLK,
> + .ops = &clk_mux_ops,
> + .flags = DM_FLAG_PRE_RELOC,
> +};
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c new file mode 100644 index
> 0000000000..0a0fffb50b
> --- /dev/null
> +++ b/drivers/clk/clk.c
> @@ -0,0 +1,56 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de */
> +
> +#include <common.h>
> +#include <clk-uclass.h>
> +#include <dm/device.h>
> +#include <dm/uclass.h>
> +#include <dm/lists.h>
> +#include <dm/device-internal.h>
> +#include <clk.h>
> +
> +int clk_register(struct clk *clk, const char *drv_name,
> + ulong drv_data, const char *name,
> + const char *parent_name)
> +{
> + struct udevice *parent;
> + struct driver *drv;
> + int ret;
> +
> + ret = uclass_get_device_by_name(UCLASS_CLK, parent_name,
> &parent);
> + if (ret)
> + printf("%s: UCLASS parent: 0x%p\n", __func__, parent);
> +
> + debug("%s: name: %s parent: %s [0x%p]\n", __func__, name,
> parent->name,
> + parent);
> +
> + drv = lists_driver_lookup_name(drv_name);
> + if (!drv) {
> + printf("%s: %s is not a valid driver name\n",
> + __func__, drv_name);
> + return -ENOENT;
> + }
> +
> + ret = device_bind_with_driver_data(parent, drv, name, drv_data,
> + ofnode_null(), &clk->dev);
> + if (ret) {
> + printf("%s: CLK: %s driver bind error [%d]!\n", __func__, name,
> + ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +ulong clk_generic_get_rate(struct clk *clk) {
> + return clk_get_parent_rate(clk);
> +}
> +
> +const char *clk_hw_get_name(const struct clk *hw) {
> + return hw->dev->name;
> +}
> diff --git a/drivers/clk/imx/Kconfig b/drivers/clk/imx/Kconfig index
> a6fb58d6cf..469768b5c3 100644
> --- a/drivers/clk/imx/Kconfig
> +++ b/drivers/clk/imx/Kconfig
> @@ -1,3 +1,12 @@
> +config CLK_IMX6Q
> + bool "Clock support for i.MX6Q"
> + depends on ARCH_MX6
> + select CLK
> + select CLK_CCF
> + select SPL_CLK_CCF
> + help
> + This enables DM/DTS support for clock driver in i.MX6Q platforms.
> +
> config CLK_IMX8
> bool "Clock support for i.MX8"
> depends on ARCH_IMX8
> diff --git a/drivers/clk/imx/Makefile b/drivers/clk/imx/Makefile index
> 5505ae52e2..beba3bff39 100644
> --- a/drivers/clk/imx/Makefile
> +++ b/drivers/clk/imx/Makefile
> @@ -2,4 +2,6 @@
> #
> # SPDX-License-Identifier: GPL-2.0
>
> +obj-$(CONFIG_$(SPL_TPL_)CLK_CCF) += clk-gate2.o clk-pllv3.o clk-pfd.o
> +obj-$(CONFIG_CLK_IMX6Q) += clk-imx6q.o
> obj-$(CONFIG_CLK_IMX8) += clk-imx8.o
> diff --git a/drivers/clk/imx/clk-gate2.c b/drivers/clk/imx/clk-gate2.c new file
> mode 100644 index 0000000000..1e53e4f9db
> --- /dev/null
> +++ b/drivers/clk/imx/clk-gate2.c
> @@ -0,0 +1,113 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de
> + *
> + * Copyright (C) 2010-2011 Canonical Ltd <jeremy.kerr at canonical.com>
> + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd
> +<mturquette at linaro.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * Gated clock implementation
> + *
> + */
> +
> +#include <common.h>
> +#include <asm/io.h>
> +#include <malloc.h>
> +#include <clk-uclass.h>
> +#include <dm/device.h>
> +#include <linux/clk-provider.h>
> +#include <clk.h>
> +#include "clk.h"
> +
> +#define UBOOT_DM_CLK_IMX_GATE2 "imx_clk_gate2"
> +
> +struct clk_gate2 {
> + struct clk clk;
> + void __iomem *reg;
> + u8 bit_idx;
> + u8 cgr_val;
> + u8 flags;
> +};
> +
> +#define to_clk_gate2(_clk) container_of(_clk, struct clk_gate2, clk)
> +
> +static int clk_gate2_enable(struct clk *clk) {
> + struct clk_gate2 *gate =
> + (struct clk_gate2 *)dev_get_driver_data(clk->dev);
> + u32 reg;
> +
> + reg = readl(gate->reg);
> + reg &= ~(3 << gate->bit_idx);
> + reg |= gate->cgr_val << gate->bit_idx;
> + writel(reg, gate->reg);
> +
> + return 0;
> +}
> +
> +static int clk_gate2_disable(struct clk *clk) {
> + struct clk_gate2 *gate =
> + (struct clk_gate2 *)dev_get_driver_data(clk->dev);
> + u32 reg;
> +
> + reg = readl(gate->reg);
> + reg &= ~(3 << gate->bit_idx);
> + writel(reg, gate->reg);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops clk_gate2_ops = {
> + .enable = clk_gate2_enable,
> + .disable = clk_gate2_disable,
> + .get_rate = clk_generic_get_rate,
> +};
> +
> +struct clk *clk_register_gate2(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + void __iomem *reg, u8 bit_idx, u8 cgr_val,
> + u8 clk_gate2_flags)
> +{
> + struct clk_gate2 *gate;
> + struct clk *clk;
> + int ret;
> +
> + gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> + if (!gate)
> + return ERR_PTR(-ENOMEM);
> +
> + /* struct clk_gate2 assignments */
> + gate->reg = reg;
> + gate->bit_idx = bit_idx;
> + gate->cgr_val = cgr_val;
> + gate->flags = clk_gate2_flags;
> +
> + /*
> + * U-boot DM adjustments:
> + *
> + * clk and gate reslove to the same address - lets pass clock
> + * for better readability.
> + */
> + clk = &gate->clk;
> +
> + ret = clk_register(clk, UBOOT_DM_CLK_IMX_GATE2, (ulong)clk,
> + name, parent_name);
> + if (ret) {
> + kfree(gate);
> + return ERR_PTR(ret);
> + }
> +
> + return clk;
> +}
> +
> +U_BOOT_DRIVER(clk_gate2) = {
> + .name = UBOOT_DM_CLK_IMX_GATE2,
> + .id = UCLASS_CLK,
> + .ops = &clk_gate2_ops,
> + .flags = DM_FLAG_PRE_RELOC,
> +};
> diff --git a/drivers/clk/imx/clk-imx6q.c b/drivers/clk/imx/clk-imx6q.c new file
> mode 100644 index 0000000000..92e9337d44
> --- /dev/null
> +++ b/drivers/clk/imx/clk-imx6q.c
> @@ -0,0 +1,179 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de */
> +
> +#include <common.h>
> +#include <clk-uclass.h>
> +#include <dm.h>
> +#include <asm/arch/clock.h>
> +#include <asm/arch/imx-regs.h>
> +#include <dt-bindings/clock/imx6qdl-clock.h>
> +
> +#include "clk.h"
> +
> +static int imx6q_check_id(ulong id)
> +{
> + if (id < IMX6QDL_CLK_DUMMY || id >= IMX6QDL_CLK_END) {
> + printf("%s: Invalid clk ID #%lu\n", __func__, id);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static ulong imx6q_clk_get_rate(struct clk *clk) {
> + struct clk *c;
> + int ret;
> +
> + debug("%s(#%lu)\n", __func__, clk->id);
> +
> + ret = imx6q_check_id(clk->id);
> + if (ret)
> + return ret;
> +
> + ret = clk_get_by_id(clk->id, &c);
> + if (ret)
> + return ret;
> +
> + return clk_get_rate(c);
> +}
> +
> +static ulong imx6q_clk_set_rate(struct clk *clk, unsigned long rate) {
> + debug("%s(#%lu), rate: %lu\n", __func__, clk->id, rate);
> +
> + return rate;
> +}
> +
> +static int __imx6q_clk_enable(struct clk *clk, bool enable) {
> + struct clk *c;
> + int ret = 0;
> +
> + debug("%s(#%lu) en: %d\n", __func__, clk->id, enable);
> +
> + ret = imx6q_check_id(clk->id);
> + if (ret)
> + return ret;
> +
> + ret = clk_get_by_id(clk->id, &c);
> + if (ret)
> + return ret;
> +
> + if (enable)
> + ret = clk_enable(c);
> + else
> + ret = clk_disable(c);
> +
> + return ret;
> +}
> +
> +static int imx6q_clk_disable(struct clk *clk) {
> + return __imx6q_clk_enable(clk, 0);
> +}
> +
> +static int imx6q_clk_enable(struct clk *clk) {
> + return __imx6q_clk_enable(clk, 1);
> +}
> +
> +static struct clk_ops imx6q_clk_ops = {
> + .set_rate = imx6q_clk_set_rate,
> + .get_rate = imx6q_clk_get_rate,
> + .enable = imx6q_clk_enable,
> + .disable = imx6q_clk_disable,
> +};
> +
> +static const char *const usdhc_sels[] = { "pll2_pfd2_396m",
> +"pll2_pfd0_352m", };
> +
> +static int imx6q_clk_probe(struct udevice *dev) {
> + void *base;
> +
> + /* Anatop clocks */
> + base = (void *)ANATOP_BASE_ADDR;
> +
> + clk_dm(IMX6QDL_CLK_PLL2,
> + imx_clk_pllv3(IMX_PLLV3_GENERIC, "pll2_bus", "osc",
> + base + 0x30, 0x1));
> + clk_dm(IMX6QDL_CLK_PLL3_USB_OTG,
> + imx_clk_pllv3(IMX_PLLV3_USB, "pll3_usb_otg", "osc",
> + base + 0x10, 0x3));
> + clk_dm(IMX6QDL_CLK_PLL3_60M,
> + imx_clk_fixed_factor("pll3_60m", "pll3_usb_otg", 1, 8));
> + clk_dm(IMX6QDL_CLK_PLL2_PFD0_352M,
> + imx_clk_pfd("pll2_pfd0_352m", "pll2_bus", base + 0x100, 0));
> + clk_dm(IMX6QDL_CLK_PLL2_PFD2_396M,
> + imx_clk_pfd("pll2_pfd2_396m", "pll2_bus", base + 0x100, 2));
> +
> + /* CCM clocks */
> + base = dev_read_addr_ptr(dev);
> + if (base == (void *)FDT_ADDR_T_NONE)
> + return -EINVAL;
> +
> + clk_dm(IMX6QDL_CLK_USDHC1_SEL,
> + imx_clk_mux("usdhc1_sel", base + 0x1c, 16, 1,
> + usdhc_sels, ARRAY_SIZE(usdhc_sels)));
> + clk_dm(IMX6QDL_CLK_USDHC2_SEL,
> + imx_clk_mux("usdhc2_sel", base + 0x1c, 17, 1,
> + usdhc_sels, ARRAY_SIZE(usdhc_sels)));
> + clk_dm(IMX6QDL_CLK_USDHC3_SEL,
> + imx_clk_mux("usdhc3_sel", base + 0x1c, 18, 1,
> + usdhc_sels, ARRAY_SIZE(usdhc_sels)));
> + clk_dm(IMX6QDL_CLK_USDHC4_SEL,
> + imx_clk_mux("usdhc4_sel", base + 0x1c, 19, 1,
> + usdhc_sels, ARRAY_SIZE(usdhc_sels)));
> +
> + clk_dm(IMX6QDL_CLK_USDHC1_PODF,
> + imx_clk_divider("usdhc1_podf", "usdhc1_sel",
> + base + 0x24, 11, 3));
> + clk_dm(IMX6QDL_CLK_USDHC2_PODF,
> + imx_clk_divider("usdhc2_podf", "usdhc2_sel",
> + base + 0x24, 16, 3));
> + clk_dm(IMX6QDL_CLK_USDHC3_PODF,
> + imx_clk_divider("usdhc3_podf", "usdhc3_sel",
> + base + 0x24, 19, 3));
> + clk_dm(IMX6QDL_CLK_USDHC4_PODF,
> + imx_clk_divider("usdhc4_podf", "usdhc4_sel",
> + base + 0x24, 22, 3));
> +
> + clk_dm(IMX6QDL_CLK_ECSPI_ROOT,
> + imx_clk_divider("ecspi_root", "pll3_60m", base + 0x38, 19, 6));
> +
> + clk_dm(IMX6QDL_CLK_ECSPI1,
> + imx_clk_gate2("ecspi1", "ecspi_root", base + 0x6c, 0));
> + clk_dm(IMX6QDL_CLK_ECSPI2,
> + imx_clk_gate2("ecspi2", "ecspi_root", base + 0x6c, 2));
> + clk_dm(IMX6QDL_CLK_ECSPI3,
> + imx_clk_gate2("ecspi3", "ecspi_root", base + 0x6c, 4));
> + clk_dm(IMX6QDL_CLK_ECSPI4,
> + imx_clk_gate2("ecspi4", "ecspi_root", base + 0x6c, 6));
> + clk_dm(IMX6QDL_CLK_USDHC1,
> + imx_clk_gate2("usdhc1", "usdhc1_podf", base + 0x80, 2));
> + clk_dm(IMX6QDL_CLK_USDHC2,
> + imx_clk_gate2("usdhc2", "usdhc2_podf", base + 0x80, 4));
> + clk_dm(IMX6QDL_CLK_USDHC3,
> + imx_clk_gate2("usdhc3", "usdhc3_podf", base + 0x80, 6));
> + clk_dm(IMX6QDL_CLK_USDHC4,
> + imx_clk_gate2("usdhc4", "usdhc4_podf", base + 0x80, 8));
> +
> + return 0;
> +}
> +
> +static const struct udevice_id imx6q_clk_ids[] = {
> + { .compatible = "fsl,imx6q-ccm" },
> + { },
> +};
> +
> +U_BOOT_DRIVER(imx6q_clk) = {
> + .name = "clk_imx6q",
> + .id = UCLASS_CLK,
> + .of_match = imx6q_clk_ids,
> + .ops = &imx6q_clk_ops,
> + .probe = imx6q_clk_probe,
> + .flags = DM_FLAG_PRE_RELOC,
> +};
> diff --git a/drivers/clk/imx/clk-pfd.c b/drivers/clk/imx/clk-pfd.c new file mode
> 100644 index 0000000000..2293d481d4
> --- /dev/null
> +++ b/drivers/clk/imx/clk-pfd.c
> @@ -0,0 +1,91 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de
> + *
> + * Copyright 2012 Freescale Semiconductor, Inc.
> + * Copyright 2012 Linaro Ltd.
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + *
> +https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.
> op
> +ensource.org%2Flicenses%2Fgpl-license.html&data=02%7C01%7Cpeng
> .fan%
> +40nxp.com%7C92b89511186641d8ecf508d6c969131f%7C686ea1d3bc2b4c
> 6fa92cd99c
> +5c301635%7C0%7C0%7C636917850484120681&sdata=4wXyWrLW9k
> NgQ7YfTNYDH1d
> +tO4AEHOS50suxlzBBugY%3D&reserved=0
> + *
> +https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.
> gn
> +u.org%2Fcopyleft%2Fgpl.html&data=02%7C01%7Cpeng.fan%40nxp.co
> m%7C92b
> +89511186641d8ecf508d6c969131f%7C686ea1d3bc2b4c6fa92cd99c5c3016
> 35%7C0%7C
> +0%7C636917850484120681&sdata=EV6piw9NY0H71%2BjCCVOwnKDz
> a%2FF33189dT
> +oqBsgUKn8%3D&reserved=0
> + */
> +
> +#include <common.h>
> +#include <asm/io.h>
> +#include <malloc.h>
> +#include <clk-uclass.h>
> +#include <dm/device.h>
> +#include <linux/clk-provider.h>
> +#include <div64.h>
> +#include <clk.h>
> +#include "clk.h"
> +
> +#define UBOOT_DM_CLK_IMX_PFD "imx_clk_pfd"
> +
> +struct clk_pfd {
> + struct clk clk;
> + void __iomem *reg;
> + u8 idx;
> +};
> +
> +#define to_clk_pfd(_clk) container_of(_clk, struct clk_pfd, clk)
> +
> +#define SET 0x4
> +#define CLR 0x8
> +#define OTG 0xc
> +
> +static unsigned long clk_pfd_recalc_rate(struct clk *clk) {
> + struct clk_pfd *pfd =
> + (struct clk_pfd *)dev_get_driver_data(clk->dev);
> + unsigned long parent_rate = clk_get_parent_rate(clk);
> + u64 tmp = parent_rate;
> + u8 frac = (readl(pfd->reg) >> (pfd->idx * 8)) & 0x3f;
> +
> + tmp *= 18;
> + do_div(tmp, frac);
> +
> + return tmp;
> +}
> +
> +static const struct clk_ops clk_pfd_ops = {
> + .get_rate = clk_pfd_recalc_rate,
> +};
> +
> +struct clk *imx_clk_pfd(const char *name, const char *parent_name,
> + void __iomem *reg, u8 idx)
> +{
> + struct clk_pfd *pfd;
> + struct clk *clk;
> + int ret;
> +
> + pfd = kzalloc(sizeof(*pfd), GFP_KERNEL);
> + if (!pfd)
> + return ERR_PTR(-ENOMEM);
> +
> + pfd->reg = reg;
> + pfd->idx = idx;
> +
> + /* register the clock */
> + clk = &pfd->clk;
> +
> + ret = clk_register(clk, UBOOT_DM_CLK_IMX_PFD, (ulong)clk,
> + name, parent_name);
> + if (ret) {
> + kfree(pfd);
> + return ERR_PTR(ret);
> + }
> +
> + return clk;
> +}
> +
> +U_BOOT_DRIVER(clk_pfd) = {
> + .name = UBOOT_DM_CLK_IMX_PFD,
> + .id = UCLASS_CLK,
> + .ops = &clk_pfd_ops,
> + .flags = DM_FLAG_PRE_RELOC,
> +};
> diff --git a/drivers/clk/imx/clk-pllv3.c b/drivers/clk/imx/clk-pllv3.c new file
> mode 100644 index 0000000000..3fe9b7c03d
> --- /dev/null
> +++ b/drivers/clk/imx/clk-pllv3.c
> @@ -0,0 +1,83 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de */
> +
> +#include <common.h>
> +#include <asm/io.h>
> +#include <malloc.h>
> +#include <clk-uclass.h>
> +#include <dm/device.h>
> +#include <dm/uclass.h>
> +#include <clk.h>
> +#include "clk.h"
> +
> +#define UBOOT_DM_CLK_IMX_PLLV3 "imx_clk_pllv3"
> +
> +struct clk_pllv3 {
> + struct clk clk;
> + void __iomem *base;
> + u32 div_mask;
> + u32 div_shift;
> +};
> +
> +#define to_clk_pllv3(_clk) container_of(_clk, struct clk_pllv3, clk)
> +
> +static ulong clk_pllv3_get_rate(struct clk *clk) {
> + struct clk_pllv3 *pll =
> + (struct clk_pllv3 *)dev_get_driver_data(clk->dev);
> + unsigned long parent_rate = clk_get_parent_rate(clk);
> +
> + u32 div = (readl(pll->base) >> pll->div_shift) & pll->div_mask;
> +
> + return (div == 1) ? parent_rate * 22 : parent_rate * 20; }
> +
> +static const struct clk_ops clk_pllv3_generic_ops = {
> + .get_rate = clk_pllv3_get_rate,
> +};
> +
> +struct clk *imx_clk_pllv3(enum imx_pllv3_type type, const char *name,
> + const char *parent_name, void __iomem *base,
> + u32 div_mask)
> +{
> + struct clk_pllv3 *pll;
> + struct clk *clk;
> + char *drv_name;
> + int ret;
> +
> + pll = kzalloc(sizeof(*pll), GFP_KERNEL);
> + if (!pll)
> + return ERR_PTR(-ENOMEM);
> +
> + switch (type) {
> + case IMX_PLLV3_GENERIC:
> + case IMX_PLLV3_USB:
> + drv_name = UBOOT_DM_CLK_IMX_PLLV3;
> + break;
> + default:
> + kfree(pll);
> + return ERR_PTR(-ENOTSUPP);
> + }
> +
> + pll->base = base;
> + pll->div_mask = div_mask;
> + clk = &pll->clk;
> +
> + ret = clk_register(clk, drv_name, (ulong)clk, name, parent_name);
> + if (ret) {
> + kfree(pll);
> + return ERR_PTR(ret);
> + }
> +
> + return clk;
> +}
> +
> +U_BOOT_DRIVER(clk_pllv3_generic) = {
> + .name = UBOOT_DM_CLK_IMX_PLLV3,
> + .id = UCLASS_CLK,
> + .ops = &clk_pllv3_generic_ops,
> + .flags = DM_FLAG_PRE_RELOC,
> +};
> diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h new file mode 100644
> index 0000000000..864a215a22
> --- /dev/null
> +++ b/drivers/clk/imx/clk.h
> @@ -0,0 +1,75 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de */
> +#ifndef __MACH_IMX_CLK_H #define __MACH_IMX_CLK_H
> +
> +#include <linux/clk-provider.h>
> +
> +static inline void clk_dm(ulong id, struct clk *clk) {
> + if (!IS_ERR(clk))
> + clk->id = id;
> +}
> +
> +enum imx_pllv3_type {
> + IMX_PLLV3_GENERIC,
> + IMX_PLLV3_SYS,
> + IMX_PLLV3_USB,
> + IMX_PLLV3_USB_VF610,
> + IMX_PLLV3_AV,
> + IMX_PLLV3_ENET,
> + IMX_PLLV3_ENET_IMX7,
> + IMX_PLLV3_SYS_VF610,
> + IMX_PLLV3_DDR_IMX7,
> +};
> +
> +struct clk *clk_register_gate2(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + void __iomem *reg, u8 bit_idx, u8 cgr_val,
> + u8 clk_gate_flags);
> +
> +struct clk *imx_clk_pllv3(enum imx_pllv3_type type, const char *name,
> + const char *parent_name, void __iomem *base,
> + u32 div_mask);
> +
> +static inline struct clk *imx_clk_gate2(const char *name, const char *parent,
> + void __iomem *reg, u8 shift)
> +{
> + return clk_register_gate2(NULL, name, parent, CLK_SET_RATE_PARENT,
> reg,
> + shift, 0x3, 0);
> +}
> +
> +static inline struct clk *imx_clk_fixed_factor(const char *name,
> + const char *parent, unsigned int mult, unsigned int div) {
> + return clk_register_fixed_factor(NULL, name, parent,
> + CLK_SET_RATE_PARENT, mult, div);
> +}
> +
> +static inline struct clk *imx_clk_divider(const char *name, const char
> *parent,
> + void __iomem *reg, u8 shift, u8 width) {
> + return clk_register_divider(NULL, name, parent, CLK_SET_RATE_PARENT,
> + reg, shift, width, 0);
> +}
> +
> +struct clk *imx_clk_pfd(const char *name, const char *parent_name,
> + void __iomem *reg, u8 idx);
> +
> +struct clk *imx_clk_fixup_mux(const char *name, void __iomem *reg,
> + u8 shift, u8 width, const char * const *parents,
> + int num_parents, void (*fixup)(u32 *val));
> +
> +static inline struct clk *imx_clk_mux(const char *name, void __iomem *reg,
> + u8 shift, u8 width, const char * const *parents,
> + int num_parents)
> +{
> + return clk_register_mux(NULL, name, parents, num_parents,
> + CLK_SET_RATE_NO_REPARENT, reg, shift,
> + width, 0);
> +}
> +
> +#endif /* __MACH_IMX_CLK_H */
> diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h new file
> mode 100644 index 0000000000..eac045c5f8
> --- /dev/null
> +++ b/include/linux/clk-provider.h
> @@ -0,0 +1,94 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 DENX Software Engineering
> + * Lukasz Majewski, DENX Software Engineering, lukma at denx.de
> + *
> + * Copyright (c) 2010-2011 Jeremy Kerr <jeremy.kerr at canonical.com>
> + * Copyright (C) 2011-2012 Linaro Ltd <mturquette at linaro.org> */
> +#ifndef __LINUX_CLK_PROVIDER_H #define __LINUX_CLK_PROVIDER_H
> +
> +#define CLK_SET_RATE_PARENT BIT(2) /* propagate rate change up one
> level */
> +#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate
> +change */
> +
> +#define CLK_MUX_INDEX_ONE BIT(0)
> +#define CLK_MUX_INDEX_BIT BIT(1)
> +#define CLK_MUX_HIWORD_MASK BIT(2)
> +#define CLK_MUX_READ_ONLY BIT(3) /* mux can't be changed */
> +#define CLK_MUX_ROUND_CLOSEST BIT(4)
> +
> +struct clk_mux {
> + struct clk clk;
> + void __iomem *reg;
> + u32 *table;
> + u32 mask;
> + u8 shift;
> + u8 flags;
> +
> + /*
> + * Fields from struct clk_init_data - this struct has been
> + * omitted to avoid too deep level of CCF for bootloader
> + */
> + const char * const *parent_names;
> + u8 num_parents;
> +};
> +
> +#define to_clk_mux(_clk) container_of(_clk, struct clk_mux, clk)
> +
> +struct clk_div_table {
> + unsigned int val;
> + unsigned int div;
> +};
> +
> +struct clk_divider {
> + struct clk clk;
> + void __iomem *reg;
> + u8 shift;
> + u8 width;
> + u8 flags;
> + const struct clk_div_table *table;
> +};
> +
> +#define clk_div_mask(width) ((1 << (width)) - 1)
> +#define to_clk_divider(_clk) container_of(_clk, struct clk_divider,
> +clk)
> +
> +#define CLK_DIVIDER_ONE_BASED BIT(0)
> +#define CLK_DIVIDER_POWER_OF_TWO BIT(1)
> +#define CLK_DIVIDER_ALLOW_ZERO BIT(2)
> +#define CLK_DIVIDER_HIWORD_MASK BIT(3)
> +#define CLK_DIVIDER_ROUND_CLOSEST BIT(4)
> +#define CLK_DIVIDER_READ_ONLY BIT(5)
> +#define CLK_DIVIDER_MAX_AT_ZERO BIT(6)
> +
> +struct clk_fixed_factor {
> + struct clk clk;
> + unsigned int mult;
> + unsigned int div;
> +};
> +
> +#define to_clk_fixed_factor(_clk) container_of(_clk, struct clk_fixed_factor,\
> + clk)
> +
> +int clk_register(struct clk *clk, const char *drv_name,
> + ulong drv_data, const char *name,
> + const char *parent_name);
> +
> +struct clk *clk_register_fixed_factor(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + unsigned int mult, unsigned int div);
> +
> +struct clk *clk_register_divider(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + void __iomem *reg, u8 shift, u8 width,
> + u8 clk_divider_flags);
> +
> +struct clk *clk_register_mux(struct device *dev, const char *name,
> + const char * const *parent_names, u8 num_parents,
> + unsigned long flags,
> + void __iomem *reg, u8 shift, u8 width,
> + u8 clk_mux_flags);
> +
> +const char *clk_hw_get_name(const struct clk *hw); ulong
> +clk_generic_get_rate(struct clk *clk); #endif /* __LINUX_CLK_PROVIDER_H
> +*/
I think it might be better to add new Kconfig for the clk files, such as PFD/GATE2
There are mixed usage hw and clk, do we need to provide hw, or just clk?
Set rate is not added, I could consider to add it in my patches.
Also I am thinking to add round_rate, thoughts?
Regards,
Peng.
> --
> 2.11.0
More information about the U-Boot
mailing list