diff options
Diffstat (limited to 'drivers/clk/zynqmp/clk-pll-zynqmp.c')
-rw-r--r-- | drivers/clk/zynqmp/clk-pll-zynqmp.c | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/drivers/clk/zynqmp/clk-pll-zynqmp.c b/drivers/clk/zynqmp/clk-pll-zynqmp.c new file mode 100644 index 0000000000..e4b759b73c --- /dev/null +++ b/drivers/clk/zynqmp/clk-pll-zynqmp.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Zynq UltraScale+ MPSoC PLL Clock + * + * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@pengutronix.de> + * + * Based on the Linux driver in drivers/clk/zynqmp/ + * + * Copyright (C) 2016-2018 Xilinx + */ + +#include <common.h> +#include <linux/clk.h> +#include <mach/firmware-zynqmp.h> + +#include "clk-zynqmp.h" + +struct zynqmp_pll { + struct clk clk; + unsigned int clk_id; + const char *parent; + const struct zynqmp_eemi_ops *ops; +}; + +#define to_zynqmp_pll(clk) \ + container_of(clk, struct zynqmp_pll, clk) + +#define PLL_FBDIV_MIN 25 +#define PLL_FBDIV_MAX 125 + +#define PS_PLL_VCO_MIN 1500000000 +#define PS_PLL_VCO_MAX 3000000000UL + +enum pll_mode { + PLL_MODE_INT, + PLL_MODE_FRAC, +}; + +#define FRAC_DIV (1 << 16) + +static inline enum pll_mode zynqmp_pll_get_mode(struct zynqmp_pll *pll) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + + pll->ops->ioctl(0, IOCTL_GET_PLL_FRAC_MODE, pll->clk_id, 0, + ret_payload); + + return ret_payload[1]; +} + +static inline void zynqmp_pll_set_mode(struct zynqmp_pll *pll, enum pll_mode mode) +{ + pll->ops->ioctl(0, IOCTL_SET_PLL_FRAC_MODE, pll->clk_id, mode, NULL); +} + +static long zynqmp_pll_round_rate(struct clk *clk, unsigned long rate, + unsigned long *prate) +{ + struct zynqmp_pll *pll = to_zynqmp_pll(clk); + u32 fbdiv; + long rate_div; + + rate_div = (rate * FRAC_DIV) / *prate; + if (rate_div % FRAC_DIV) + zynqmp_pll_set_mode(pll, PLL_MODE_FRAC); + else + zynqmp_pll_set_mode(pll, PLL_MODE_INT); + + if (zynqmp_pll_get_mode(pll) == PLL_MODE_FRAC) { + if (rate > PS_PLL_VCO_MAX) { + fbdiv = rate / PS_PLL_VCO_MAX; + rate = rate / (fbdiv + 1); + } + if (rate < PS_PLL_VCO_MIN) { + fbdiv = DIV_ROUND_UP(PS_PLL_VCO_MIN, rate); + rate = rate * fbdiv; + } + } else { + fbdiv = DIV_ROUND_CLOSEST(rate, *prate); + fbdiv = clamp_t(u32, fbdiv, PLL_FBDIV_MIN, PLL_FBDIV_MAX); + rate = *prate * fbdiv; + } + + return rate; +} + +static unsigned long zynqmp_pll_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct zynqmp_pll *pll = to_zynqmp_pll(clk); + u32 clk_id = pll->clk_id; + u32 fbdiv, data; + unsigned long rate, frac; + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + ret = pll->ops->clock_getdivider(clk_id, &fbdiv); + + rate = parent_rate * fbdiv; + + if (zynqmp_pll_get_mode(pll) == PLL_MODE_FRAC) { + pll->ops->ioctl(0, IOCTL_GET_PLL_FRAC_DATA, clk_id, 0, + ret_payload); + data = ret_payload[1]; + frac = (parent_rate * data) / FRAC_DIV; + rate = rate + frac; + } + + return rate; +} + +static int zynqmp_pll_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct zynqmp_pll *pll = to_zynqmp_pll(clk); + u32 clk_id = pll->clk_id; + u32 fbdiv; + long rate_div, frac, m, f; + + if (zynqmp_pll_get_mode(pll) == PLL_MODE_FRAC) { + rate_div = (rate * FRAC_DIV) / parent_rate; + m = rate_div / FRAC_DIV; + f = rate_div % FRAC_DIV; + m = clamp_t(u32, m, (PLL_FBDIV_MIN), (PLL_FBDIV_MAX)); + rate = parent_rate * m; + frac = (parent_rate * f) / FRAC_DIV; + + pll->ops->clock_setdivider(clk_id, m); + pll->ops->ioctl(0, IOCTL_SET_PLL_FRAC_DATA, clk_id, f, NULL); + + return rate + frac; + } else { + fbdiv = DIV_ROUND_CLOSEST(rate, parent_rate); + fbdiv = clamp_t(u32, fbdiv, PLL_FBDIV_MIN, PLL_FBDIV_MAX); + pll->ops->clock_setdivider(clk_id, fbdiv); + + return parent_rate * fbdiv; + } +} + +static int zynqmp_pll_is_enabled(struct clk *clk) +{ + struct zynqmp_pll *pll = to_zynqmp_pll(clk); + u32 is_enabled; + int ret; + + ret = pll->ops->clock_getstate(pll->clk_id, &is_enabled); + if (ret) + return -EIO; + + return !!(is_enabled); +} + +static int zynqmp_pll_enable(struct clk *clk) +{ + struct zynqmp_pll *pll = to_zynqmp_pll(clk); + + if (zynqmp_pll_is_enabled(clk)) + return 0; + + return pll->ops->clock_enable(pll->clk_id); +} + +static void zynqmp_pll_disable(struct clk *clk) +{ + struct zynqmp_pll *pll = to_zynqmp_pll(clk); + + if (!zynqmp_pll_is_enabled(clk)) + return; + + pll->ops->clock_disable(pll->clk_id); +} + +static const struct clk_ops zynqmp_pll_ops = { + .enable = zynqmp_pll_enable, + .disable = zynqmp_pll_disable, + .is_enabled = zynqmp_pll_is_enabled, + .round_rate = zynqmp_pll_round_rate, + .recalc_rate = zynqmp_pll_recalc_rate, + .set_rate = zynqmp_pll_set_rate, +}; + +struct clk *zynqmp_clk_register_pll(const char *name, + unsigned int clk_id, + const char **parents, + unsigned int num_parents, + const struct clock_topology *nodes) +{ + struct zynqmp_pll *pll; + int ret; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + pll->clk_id = clk_id; + pll->ops = zynqmp_pm_get_eemi_ops(); + pll->parent = strdup(parents[0]); + + pll->clk.name = strdup(name); + pll->clk.ops = &zynqmp_pll_ops; + pll->clk.flags = nodes->flag | CLK_SET_RATE_PARENT; + pll->clk.parent_names = &pll->parent; + pll->clk.num_parents = 1; + + ret = clk_register(&pll->clk); + if (ret) { + kfree(pll); + return ERR_PTR(ret); + } + + return &pll->clk; +} |