diff options
author | Siva Durga Prasad Paladugu | 2016-11-15 16:15:41 +0530 |
---|---|---|
committer | Michal Simek | 2017-01-10 10:18:12 +0100 |
commit | 128ec1fe6f5ee3a753326215732e6030e2c7c4d6 (patch) | |
tree | 7bdd13c6201c2590f60bdebe03cba69aeb9ab684 /drivers/clk | |
parent | 5ce987feb35202bc21b36afdce4eb4bf95cf7a66 (diff) |
clk: zynqmp: Add clock driver support for zynqmp
Add basic clock driver support for zynqmp which
sets the required clock for GEM controller
Signed-off-by: Siva Durga Prasad Paladugu <sivadur@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/Kconfig | 7 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/clk_zynqmp.c | 241 |
3 files changed, 249 insertions, 0 deletions
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index c05ce2a9efa..335ef9e1d7c 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -28,6 +28,13 @@ config CLK_BOSTON help Enable this to support the clocks +config CLK_ZYNQMP + bool "Enable clock driver support for ZynqMP" + depends on ARCH_ZYNQMP + help + This clock driver adds support for clock realted settings for + ZynqMP platform. + source "drivers/clk/tegra/Kconfig" source "drivers/clk/uniphier/Kconfig" source "drivers/clk/exynos/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 40a5e8cae86..f55348e8f15 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_SANDBOX) += clk_sandbox.o obj-$(CONFIG_SANDBOX) += clk_sandbox_test.o obj-$(CONFIG_MACH_PIC32) += clk_pic32.o +obj-$(CONFIG_CLK_ZYNQMP) += clk_zynqmp.o obj-y += tegra/ obj-$(CONFIG_CLK_UNIPHIER) += uniphier/ diff --git a/drivers/clk/clk_zynqmp.c b/drivers/clk/clk_zynqmp.c new file mode 100644 index 00000000000..694274d9910 --- /dev/null +++ b/drivers/clk/clk_zynqmp.c @@ -0,0 +1,241 @@ +/* + * ZynqMP clock driver + * + * Copyright (C) 2016 Xilinx, Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <linux/bitops.h> +#include <clk-uclass.h> +#include <dm/device.h> +#include <clk.h> + +#define ZYNQMP_GEM0_REF_CTRL 0xFF5E0050 +#define ZYNQMP_IOPLL_CTRL 0xFF5E0020 +#define ZYNQMP_RPLL_CTRL 0xFF5E0030 +#define ZYNQMP_DPLL_CTRL 0xFD1A002C +#define ZYNQMP_SIP_SVC_MMIO_WRITE 0xC2000013 +#define ZYNQMP_SIP_SVC_MMIO_WRITE 0xC2000013 +#define ZYNQMP_SIP_SVC_MMIO_WRITE 0xC2000013 +#define ZYNQMP_SIP_SVC_MMIO_READ 0xC2000014 +#define ZYNQMP_DIV_MAX_VAL 0x3F +#define ZYNQMP_DIV1_SHFT 8 +#define ZYNQMP_DIV1_SHFT 8 +#define ZYNQMP_DIV2_SHFT 16 +#define ZYNQMP_DIV_MASK 0x3F +#define ZYNQMP_PLL_CTRL_FBDIV_MASK 0x7F +#define ZYNQMP_PLL_CTRL_FBDIV_SHFT 8 +#define ZYNQMP_GEM_REF_CTRL_SRC_MASK 0x7 +#define ZYNQMP_GEM0_CLK_ID 45 +#define ZYNQMP_GEM1_CLK_ID 46 +#define ZYNQMP_GEM2_CLK_ID 47 +#define ZYNQMP_GEM3_CLK_ID 48 + +static unsigned long pss_ref_clk; + +static int zynqmp_calculate_divisors(unsigned long req_rate, + unsigned long parent_rate, + u32 *div1, u32 *div2) +{ + u32 req_div = 1; + u32 i; + + /* + * calculate two divisors to get + * required rate and each divisor + * should be less than 63 + */ + req_div = DIV_ROUND_UP(parent_rate, req_rate); + + for (i = 1; i <= req_div; i++) { + if ((req_div % i) == 0) { + *div1 = req_div / i; + *div2 = i; + if ((*div1 < ZYNQMP_DIV_MAX_VAL) && + (*div2 < ZYNQMP_DIV_MAX_VAL)) + return 0; + } + } + + return -1; +} + +static int zynqmp_get_periph_id(unsigned long id) +{ + int periph_id; + + switch (id) { + case ZYNQMP_GEM0_CLK_ID: + periph_id = 0; + break; + case ZYNQMP_GEM1_CLK_ID: + periph_id = 1; + break; + case ZYNQMP_GEM2_CLK_ID: + periph_id = 2; + break; + case ZYNQMP_GEM3_CLK_ID: + periph_id = 3; + break; + default: + printf("%s, Invalid clock id:%ld\n", __func__, id); + return -EINVAL; + } + + return periph_id; +} + +static int zynqmp_set_clk(unsigned long id, u32 div1, u32 div2) +{ + struct pt_regs regs; + ulong reg; + u32 mask, value; + + id = zynqmp_get_periph_id(id); + if (id < 0) + return -EINVAL; + + reg = (ulong)((u32 *)ZYNQMP_GEM0_REF_CTRL + id); + mask = (ZYNQMP_DIV_MASK << ZYNQMP_DIV1_SHFT) | + (ZYNQMP_DIV_MASK << ZYNQMP_DIV2_SHFT); + value = (div1 << ZYNQMP_DIV1_SHFT) | (div2 << ZYNQMP_DIV2_SHFT); + + debug("%s: reg:0x%lx, mask:0x%x, value:0x%x\n", __func__, reg, mask, + value); + + regs.regs[0] = ZYNQMP_SIP_SVC_MMIO_WRITE; + regs.regs[1] = ((u64)mask << 32) | reg; + regs.regs[2] = value; + regs.regs[3] = 0; + + smc_call(®s); + + return regs.regs[0]; +} + +static unsigned long zynqmp_clk_get_rate(struct clk *clk) +{ + struct pt_regs regs; + ulong reg; + unsigned long value; + int id; + + id = zynqmp_get_periph_id(clk->id); + if (id < 0) + return -EINVAL; + + reg = (ulong)((u32 *)ZYNQMP_GEM0_REF_CTRL + id); + + regs.regs[0] = ZYNQMP_SIP_SVC_MMIO_READ; + regs.regs[1] = reg; + regs.regs[2] = 0; + regs.regs[3] = 0; + + smc_call(®s); + + value = upper_32_bits(regs.regs[0]); + + value &= ZYNQMP_GEM_REF_CTRL_SRC_MASK; + + switch (value) { + case 0: + regs.regs[1] = ZYNQMP_IOPLL_CTRL; + break; + case 2: + regs.regs[1] = ZYNQMP_RPLL_CTRL; + break; + case 3: + regs.regs[1] = ZYNQMP_DPLL_CTRL; + break; + default: + return -EINVAL; + } + + regs.regs[0] = ZYNQMP_SIP_SVC_MMIO_READ; + regs.regs[2] = 0; + regs.regs[3] = 0; + + smc_call(®s); + + value = upper_32_bits(regs.regs[0]) & + (ZYNQMP_PLL_CTRL_FBDIV_MASK << + ZYNQMP_PLL_CTRL_FBDIV_SHFT); + value >>= ZYNQMP_PLL_CTRL_FBDIV_SHFT; + value *= pss_ref_clk; + + return value; +} + +static ulong zynqmp_clk_set_rate(struct clk *clk, unsigned long clk_rate) +{ + int ret; + u32 div1 = 0; + u32 div2 = 0; + unsigned long input_clk; + + input_clk = zynqmp_clk_get_rate(clk); + if (IS_ERR_VALUE(input_clk)) { + dev_err(dev, "failed to get input_clk\n"); + return -EINVAL; + } + + debug("%s: i/p CLK %ld, clk_rate:0x%ld\n", __func__, input_clk, + clk_rate); + + ret = zynqmp_calculate_divisors(clk_rate, input_clk, &div1, &div2); + if (ret) { + dev_err(dev, "failed to proper divisors\n"); + return -EINVAL; + } + + debug("%s: Div1:%d, Div2:%d\n", __func__, div1, div2); + + ret = zynqmp_set_clk(clk->id, div1, div2); + if (ret) { + dev_err(dev, "failed to set gem clk\n"); + return -EINVAL; + } + + return 0; +} + +static int zynqmp_clk_probe(struct udevice *dev) +{ + struct clk clk; + int ret; + + debug("%s\n", __func__); + ret = clk_get_by_name(dev, "pss_ref_clk", &clk); + if (ret < 0) { + dev_err(dev, "failed to get pss_ref_clk\n"); + return ret; + } + + pss_ref_clk = clk_get_rate(&clk); + if (IS_ERR_VALUE(pss_ref_clk)) { + dev_err(dev, "failed to get rate pss_ref_clk\n"); + return -EINVAL; + } + + return 0; +} + +static struct clk_ops zynqmp_clk_ops = { + .set_rate = zynqmp_clk_set_rate, + .get_rate = zynqmp_clk_get_rate, +}; + +static const struct udevice_id zynqmp_clk_ids[] = { + { .compatible = "xlnx,zynqmp-clkc" }, + { } +}; + +U_BOOT_DRIVER(zynqmp_clk) = { + .name = "zynqmp-clk", + .id = UCLASS_CLK, + .of_match = zynqmp_clk_ids, + .probe = zynqmp_clk_probe, + .ops = &zynqmp_clk_ops, +}; |