diff options
Diffstat (limited to 'drivers/clk/imx')
-rw-r--r-- | drivers/clk/imx/Kconfig | 16 | ||||
-rw-r--r-- | drivers/clk/imx/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/imx/clk-gate2.c | 103 | ||||
-rw-r--r-- | drivers/clk/imx/clk-imx6q.c | 179 | ||||
-rw-r--r-- | drivers/clk/imx/clk-pfd.c | 90 | ||||
-rw-r--r-- | drivers/clk/imx/clk-pllv3.c | 82 | ||||
-rw-r--r-- | drivers/clk/imx/clk.h | 69 |
7 files changed, 541 insertions, 0 deletions
diff --git a/drivers/clk/imx/Kconfig b/drivers/clk/imx/Kconfig index a6fb58d6cf..3e6a980c8c 100644 --- a/drivers/clk/imx/Kconfig +++ b/drivers/clk/imx/Kconfig @@ -1,3 +1,19 @@ +config SPL_CLK_IMX6Q + bool "SPL clock support for i.MX6Q" + depends on ARCH_MX6 && SPL + select SPL_CLK + select SPL_CLK_CCF + help + This enables SPL DM/DTS support for clock driver in i.MX6Q platforms. + +config CLK_IMX6Q + bool "Clock support for i.MX6Q" + depends on ARCH_MX6 + select CLK + select 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 eb379c188a..105a58ca90 100644 --- a/drivers/clk/imx/Makefile +++ b/drivers/clk/imx/Makefile @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_$(SPL_TPL_)CLK_CCF) += clk-gate2.o clk-pllv3.o clk-pfd.o +obj-$(CONFIG_$(SPL_TPL_)CLK_IMX6Q) += clk-imx6q.o obj-$(CONFIG_CLK_IMX8) += clk-imx8.o ifdef CONFIG_CLK_IMX8 diff --git a/drivers/clk/imx/clk-gate2.c b/drivers/clk/imx/clk-gate2.c new file mode 100644 index 0000000000..571be32088 --- /dev/null +++ b/drivers/clk/imx/clk-gate2.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 DENX Software Engineering + * Lukasz Majewski, DENX Software Engineering, lukma@denx.de + * + * Copyright (C) 2010-2011 Canonical Ltd <jeremy.kerr@canonical.com> + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@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 = to_clk_gate2(dev_get_clk_ptr(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 = to_clk_gate2(dev_get_clk_ptr(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); + + gate->reg = reg; + gate->bit_idx = bit_idx; + gate->cgr_val = cgr_val; + gate->flags = clk_gate2_flags; + + clk = &gate->clk; + + ret = clk_register(clk, UBOOT_DM_CLK_IMX_GATE2, 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@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..188b2b3b90 --- /dev/null +++ b/drivers/clk/imx/clk-pfd.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 DENX Software Engineering + * Lukasz Majewski, DENX Software Engineering, lukma@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: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#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 = + to_clk_pfd(dev_get_clk_ptr(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, 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..fbb7b24d5e --- /dev/null +++ b/drivers/clk/imx/clk-pllv3.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 DENX Software Engineering + * Lukasz Majewski, DENX Software Engineering, lukma@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 = to_clk_pllv3(dev_get_clk_ptr(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, 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..e6d51830e8 --- /dev/null +++ b/drivers/clk/imx/clk.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 DENX Software Engineering + * Lukasz Majewski, DENX Software Engineering, lukma@denx.de + */ +#ifndef __MACH_IMX_CLK_H +#define __MACH_IMX_CLK_H + +#include <linux/clk-provider.h> + +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 */ |