> 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 <lu...@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, lu...@denx.de > + * > + * Copyright (C) 2011 Sascha Hauer, Pengutronix > +<s.ha...@pengutronix.de> > + * Copyright (C) 2011 Richard Zhao, Linaro <richard.z...@linaro.org> > + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd > +<mturque...@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, lu...@denx.de > + * > + * Copyright (C) 2011 Sascha Hauer, Pengutronix > +<s.ha...@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, lu...@denx.de > + * > + * Copyright (C) 2011 Sascha Hauer, Pengutronix > +<s.ha...@pengutronix.de> > + * Copyright (C) 2011 Richard Zhao, Linaro <richard.z...@linaro.org> > + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd > +<mturque...@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, lu...@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, lu...@denx.de > + * > + * Copyright (C) 2010-2011 Canonical Ltd <jeremy.k...@canonical.com> > + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd > +<mturque...@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, lu...@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, lu...@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, lu...@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, lu...@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, lu...@denx.de > + * > + * Copyright (c) 2010-2011 Jeremy Kerr <jeremy.k...@canonical.com> > + * Copyright (C) 2011-2012 Linaro Ltd <mturque...@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 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de https://lists.denx.de/listinfo/u-boot