[PATCH V2 1/6] SPEAr: clk: Add VCO-PLL Synthesizer clock
Turquette, Mike
mturquette at ti.com
Mon Apr 23 15:15:46 EDT 2012
On Thu, Apr 19, 2012 at 9:28 AM, Viresh Kumar <viresh.linux at gmail.com> wrote:
> All SPEAr SoC's contain PLLs. Their Fout is derived based on following equations
>
> - In normal mode
> vco = (2 * M[15:8] * Fin)/N
>
> - In Dithered mode
> vco = (2 * M[15:0] * Fin)/(256 * N)
>
> pll_rate = vco/2^p
>
> vco and pll are very closely bound to each other,
> "vco needs to program: mode, m & n" and "pll needs to program p",
> both share common enable/disable logic and registers.
>
> This patch adds in support for this type of clock.
>
> Signed-off-by: Viresh Kumar <viresh.kumar at st.com>
Hi Viresh,
I took a quick glance through this code from the perspective of common
clk conformance and it looks good.
Reviewed-by: Mike Turquette <mturquette at linaro.org>
Regards,
Mike
> ---
> drivers/clk/Makefile | 3 +
> drivers/clk/spear/Makefile | 5 +
> drivers/clk/spear/clk-vco-pll.c | 346 +++++++++++++++++++++++++++++++++++++++
> drivers/clk/spear/clk.h | 76 +++++++++
> 4 files changed, 430 insertions(+), 0 deletions(-)
> create mode 100644 drivers/clk/spear/Makefile
> create mode 100644 drivers/clk/spear/clk-vco-pll.c
> create mode 100644 drivers/clk/spear/clk.h
>
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 24aa714..0f5e03d 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -2,3 +2,6 @@
> obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o
> obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed-rate.o clk-gate.o \
> clk-mux.o clk-divider.o clk-fixed-factor.o
> +
> +# SoCs specific
> +obj-$(CONFIG_PLAT_SPEAR) += spear/
> diff --git a/drivers/clk/spear/Makefile b/drivers/clk/spear/Makefile
> new file mode 100644
> index 0000000..f59469f
> --- /dev/null
> +++ b/drivers/clk/spear/Makefile
> @@ -0,0 +1,5 @@
> +#
> +# SPEAr Clock specific Makefile
> +#
> +
> +obj-y += clk-vco-pll.o
> diff --git a/drivers/clk/spear/clk-vco-pll.c b/drivers/clk/spear/clk-vco-pll.c
> new file mode 100644
> index 0000000..9efa30d
> --- /dev/null
> +++ b/drivers/clk/spear/clk-vco-pll.c
> @@ -0,0 +1,346 @@
> +/*
> + * Copyright (C) 2012 ST Microelectronics
> + * Viresh Kumar <viresh.kumar at st.com>
> + *
> + * This file is licensed under the terms of the GNU General Public
> + * License version 2. This program is licensed "as is" without any
> + * warranty of any kind, whether express or implied.
> + *
> + * VCO-PLL clock implementation
> + */
> +
> +#define pr_fmt(fmt) "clk-vco-pll: " fmt
> +
> +#include <linux/clk-provider.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/err.h>
> +#include "clk.h"
> +
> +/*
> + * DOC: VCO-PLL clock
> + *
> + * VCO and PLL rate are derived from following equations:
> + *
> + * In normal mode
> + * vco = (2 * M[15:8] * Fin)/N
> + *
> + * In Dithered mode
> + * vco = (2 * M[15:0] * Fin)/(256 * N)
> + *
> + * pll_rate = pll/2^p
> + *
> + * vco and pll are very closely bound to each other, "vco needs to program:
> + * mode, m & n" and "pll needs to program p", both share common enable/disable
> + * logic.
> + *
> + * clk_register_vco_pll() registers instances of both vco & pll.
> + * CLK_SET_RATE_PARENT flag is forced for pll, as it will always pass its
> + * set_rate to vco. A single rate table exists for both the clocks, which
> + * configures m, n and p.
> + */
> +
> +/* PLL_CTR register masks */
> +#define PLL_MODE_NORMAL 0
> +#define PLL_MODE_FRACTION 1
> +#define PLL_MODE_DITH_DSM 2
> +#define PLL_MODE_DITH_SSM 3
> +#define PLL_MODE_MASK 3
> +#define PLL_MODE_SHIFT 3
> +#define PLL_ENABLE 2
> +
> +#define PLL_LOCK_SHIFT 0
> +#define PLL_LOCK_MASK 1
> +
> +/* PLL FRQ register masks */
> +#define PLL_NORM_FDBK_M_MASK 0xFF
> +#define PLL_NORM_FDBK_M_SHIFT 24
> +#define PLL_DITH_FDBK_M_MASK 0xFFFF
> +#define PLL_DITH_FDBK_M_SHIFT 16
> +#define PLL_DIV_P_MASK 0x7
> +#define PLL_DIV_P_SHIFT 8
> +#define PLL_DIV_N_MASK 0xFF
> +#define PLL_DIV_N_SHIFT 0
> +
> +#define to_clk_vco(_hw) container_of(_hw, struct clk_vco, hw)
> +#define to_clk_pll(_hw) container_of(_hw, struct clk_pll, hw)
> +
> +/* Calculates pll clk rate for specific value of mode, m, n and p */
> +static unsigned long pll_calc_rate(struct pll_rate_tbl *rtbl,
> + unsigned long prate, int index, unsigned long *pll_rate)
> +{
> + unsigned long rate = prate;
> + unsigned int mode;
> +
> + mode = rtbl[index].mode ? 256 : 1;
> + rate = (((2 * rate / 10000) * rtbl[index].m) / (mode * rtbl[index].n));
> +
> + if (pll_rate)
> + *pll_rate = (rate / (1 << rtbl[index].p)) * 10000;
> +
> + return rate * 10000;
> +}
> +
> +static long clk_pll_round_rate_index(struct clk_hw *hw, unsigned long drate,
> + unsigned long *prate, int *index)
> +{
> + struct clk_pll *pll = to_clk_pll(hw);
> + unsigned long prev_rate, vco_prev_rate, rate = 0;
> + unsigned long vco_parent_rate =
> + __clk_get_rate(__clk_get_parent(__clk_get_parent(hw->clk)));
> +
> + if (!prate) {
> + pr_err("%s: prate is must for pll clk\n", __func__);
> + return -EINVAL;
> + }
> +
> + for (*index = 0; *index < pll->vco->rtbl_cnt; (*index)++) {
> + prev_rate = rate;
> + vco_prev_rate = *prate;
> + *prate = pll_calc_rate(pll->vco->rtbl, vco_parent_rate, *index,
> + &rate);
> + if (drate < rate) {
> + /* previous clock was best */
> + if (*index) {
> + rate = prev_rate;
> + *prate = vco_prev_rate;
> + (*index)--;
> + }
> + break;
> + }
> + }
> +
> + return rate;
> +}
> +
> +static long clk_pll_round_rate(struct clk_hw *hw, unsigned long drate,
> + unsigned long *prate)
> +{
> + int unused;
> +
> + return clk_pll_round_rate_index(hw, drate, prate, &unused);
> +}
> +
> +static unsigned long clk_pll_recalc_rate(struct clk_hw *hw, unsigned long
> + parent_rate)
> +{
> + struct clk_pll *pll = to_clk_pll(hw);
> + unsigned long flags = 0;
> + unsigned int p;
> +
> + if (pll->vco->lock)
> + spin_lock_irqsave(pll->vco->lock, flags);
> +
> + p = readl_relaxed(pll->vco->cfg_reg);
> +
> + if (pll->vco->lock)
> + spin_unlock_irqrestore(pll->vco->lock, flags);
> +
> + p = (p >> PLL_DIV_P_SHIFT) & PLL_DIV_P_MASK;
> +
> + return parent_rate / (1 << p);
> +}
> +
> +static int clk_pll_set_rate(struct clk_hw *hw, unsigned long drate)
> +{
> + struct clk_pll *pll = to_clk_pll(hw);
> + struct pll_rate_tbl *rtbl = pll->vco->rtbl;
> + unsigned long flags = 0, val;
> + int i;
> +
> + clk_pll_round_rate_index(hw, drate, NULL, &i);
> +
> + if (pll->vco->lock)
> + spin_lock_irqsave(pll->vco->lock, flags);
> +
> + val = readl_relaxed(pll->vco->cfg_reg);
> + val &= ~(PLL_DIV_P_MASK << PLL_DIV_P_SHIFT);
> + val |= (rtbl[i].p & PLL_DIV_P_MASK) << PLL_DIV_P_SHIFT;
> + writel_relaxed(val, pll->vco->cfg_reg);
> +
> + if (pll->vco->lock)
> + spin_unlock_irqrestore(pll->vco->lock, flags);
> +
> + return 0;
> +}
> +
> +static struct clk_ops clk_pll_ops = {
> + .recalc_rate = clk_pll_recalc_rate,
> + .round_rate = clk_pll_round_rate,
> + .set_rate = clk_pll_set_rate,
> +};
> +
> +static inline unsigned long vco_calc_rate(struct clk_hw *hw,
> + unsigned long prate, int index)
> +{
> + struct clk_vco *vco = to_clk_vco(hw);
> +
> + return pll_calc_rate(vco->rtbl, prate, index, NULL);
> +}
> +
> +static long clk_vco_round_rate(struct clk_hw *hw, unsigned long drate,
> + unsigned long *prate)
> +{
> + struct clk_vco *vco = to_clk_vco(hw);
> + int unused;
> +
> + return clk_round_rate_index(hw, drate, vco_calc_rate, vco->rtbl_cnt,
> + &unused);
> +}
> +
> +static unsigned long clk_vco_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct clk_vco *vco = to_clk_vco(hw);
> + unsigned long flags = 0;
> + unsigned int num = 2, den = 0, val, mode = 0;
> +
> + if (vco->lock)
> + spin_lock_irqsave(vco->lock, flags);
> +
> + mode = (readl_relaxed(vco->mode_reg) >> PLL_MODE_SHIFT) & PLL_MODE_MASK;
> +
> + val = readl_relaxed(vco->cfg_reg);
> +
> + if (vco->lock)
> + spin_unlock_irqrestore(vco->lock, flags);
> +
> + den = (val >> PLL_DIV_N_SHIFT) & PLL_DIV_N_MASK;
> +
> + /* calculate numerator & denominator */
> + if (!mode) {
> + /* Normal mode */
> + num *= (val >> PLL_NORM_FDBK_M_SHIFT) & PLL_NORM_FDBK_M_MASK;
> + } else {
> + /* Dithered mode */
> + num *= (val >> PLL_DITH_FDBK_M_SHIFT) & PLL_DITH_FDBK_M_MASK;
> + den *= 256;
> + }
> +
> + if (!den) {
> + WARN(1, "%s: denominator can't be zero\n", __func__);
> + return 0;
> + }
> +
> + return (((parent_rate / 10000) * num) / den) * 10000;
> +}
> +
> +/* Configures new clock rate of vco */
> +static int clk_vco_set_rate(struct clk_hw *hw, unsigned long drate)
> +{
> + struct clk_vco *vco = to_clk_vco(hw);
> + struct pll_rate_tbl *rtbl = vco->rtbl;
> + unsigned long flags = 0, val;
> + int i;
> +
> + clk_round_rate_index(hw, drate, vco_calc_rate, vco->rtbl_cnt, &i);
> +
> + if (vco->lock)
> + spin_lock_irqsave(vco->lock, flags);
> +
> + val = readl_relaxed(vco->mode_reg);
> + val &= ~(PLL_MODE_MASK << PLL_MODE_SHIFT);
> + val |= (rtbl[i].mode & PLL_MODE_MASK) << PLL_MODE_SHIFT;
> + writel_relaxed(val, vco->mode_reg);
> +
> + val = readl_relaxed(vco->cfg_reg);
> + val &= ~(PLL_DIV_N_MASK << PLL_DIV_N_SHIFT);
> + val |= (rtbl[i].n & PLL_DIV_N_MASK) << PLL_DIV_N_SHIFT;
> +
> + val &= ~(PLL_DITH_FDBK_M_MASK << PLL_DITH_FDBK_M_SHIFT);
> + if (rtbl[i].mode)
> + val |= (rtbl[i].m & PLL_DITH_FDBK_M_MASK) <<
> + PLL_DITH_FDBK_M_SHIFT;
> + else
> + val |= (rtbl[i].m & PLL_NORM_FDBK_M_MASK) <<
> + PLL_NORM_FDBK_M_SHIFT;
> +
> + writel_relaxed(val, vco->cfg_reg);
> +
> + if (vco->lock)
> + spin_unlock_irqrestore(vco->lock, flags);
> +
> + return 0;
> +}
> +
> +static struct clk_ops clk_vco_ops = {
> + .recalc_rate = clk_vco_recalc_rate,
> + .round_rate = clk_vco_round_rate,
> + .set_rate = clk_vco_set_rate,
> +};
> +
> +struct clk *clk_register_vco_pll(const char *vco_name, const char *pll_name,
> + const char *vco_gate_name, const char *parent_name,
> + unsigned long flags, void __iomem *mode_reg, void __iomem
> + *cfg_reg, struct pll_rate_tbl *rtbl, u8 rtbl_cnt,
> + spinlock_t *lock, struct clk **pll_clk,
> + struct clk **vco_gate_clk)
> +{
> + struct clk_vco *vco;
> + struct clk_pll *pll;
> + struct clk *vco_clk, *tpll_clk, *tvco_gate_clk;
> + const char **vco_parent_name;
> +
> + if (!vco_name || !pll_name || !parent_name || !mode_reg || !cfg_reg ||
> + !rtbl || !rtbl_cnt) {
> + pr_err("Invalid arguments passed");
> + return ERR_PTR(-EINVAL);
> + }
> +
> + vco = kzalloc(sizeof(*vco), GFP_KERNEL);
> + if (!vco) {
> + pr_err("could not allocate vco clk\n");
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + pll = kzalloc(sizeof(*pll), GFP_KERNEL);
> + if (!pll) {
> + pr_err("could not allocate pll clk\n");
> + goto free_vco;
> + }
> +
> + /* struct clk_vco assignments */
> + vco->mode_reg = mode_reg;
> + vco->cfg_reg = cfg_reg;
> + vco->rtbl = rtbl;
> + vco->rtbl_cnt = rtbl_cnt;
> + vco->lock = lock;
> + pll->vco = vco;
> +
> + if (vco_gate_name) {
> + tvco_gate_clk = clk_register_gate(NULL, vco_gate_name,
> + parent_name, 0, mode_reg, PLL_ENABLE, 0, lock);
> + if (IS_ERR_OR_NULL(tvco_gate_clk))
> + goto free_pll;
> +
> + if (vco_gate_clk)
> + *vco_gate_clk = tvco_gate_clk;
> + vco_parent_name = &vco_gate_name;
> + } else {
> + vco_parent_name = &parent_name;
> + }
> +
> + vco_clk = clk_register(NULL, vco_name, &clk_vco_ops, &vco->hw,
> + vco_parent_name, 1, flags);
> + if (IS_ERR_OR_NULL(vco_clk))
> + goto free_pll;
> +
> + tpll_clk = clk_register(NULL, pll_name, &clk_pll_ops, &pll->hw,
> + &vco_name, 1, CLK_SET_RATE_PARENT);
> + if (IS_ERR_OR_NULL(tpll_clk))
> + goto free_pll;
> +
> + if (pll_clk)
> + *pll_clk = tpll_clk;
> +
> + return vco_clk;
> +
> +free_pll:
> + kfree(pll);
> +free_vco:
> + kfree(vco);
> +
> + pr_err("Failed to register vco pll clock\n");
> +
> + return ERR_PTR(-ENOMEM);
> +}
> diff --git a/drivers/clk/spear/clk.h b/drivers/clk/spear/clk.h
> new file mode 100644
> index 0000000..a66024e
> --- /dev/null
> +++ b/drivers/clk/spear/clk.h
> @@ -0,0 +1,76 @@
> +/*
> + * Clock framework definitions for SPEAr platform
> + *
> + * Copyright (C) 2012 ST Microelectronics
> + * Viresh Kumar <viresh.kumar at st.com>
> + *
> + * This file is licensed under the terms of the GNU General Public
> + * License version 2. This program is licensed "as is" without any
> + * warranty of any kind, whether express or implied.
> + */
> +
> +#ifndef __PLAT_CLOCK_H
> +#define __PLAT_CLOCK_H
> +
> +#include <linux/clk-provider.h>
> +#include <linux/spinlock_types.h>
> +#include <linux/types.h>
> +
> +/* VCO-PLL clk */
> +struct pll_rate_tbl {
> + u8 mode;
> + u16 m;
> + u8 n;
> + u8 p;
> +};
> +
> +struct clk_vco {
> + struct clk_hw hw;
> + void __iomem *mode_reg;
> + void __iomem *cfg_reg;
> + struct pll_rate_tbl *rtbl;
> + u8 rtbl_cnt;
> + spinlock_t *lock;
> +};
> +
> +struct clk_pll {
> + struct clk_hw hw;
> + struct clk_vco *vco;
> + const char *parent[1];
> + spinlock_t *lock;
> +};
> +
> +typedef unsigned long (*clk_calc_rate)(struct clk_hw *hw, unsigned long prate,
> + int index);
> +
> +/* clk register routines */
> +struct clk *clk_register_vco_pll(const char *vco_name, const char *pll_name,
> + const char *vco_gate_name, const char *parent_name,
> + unsigned long flags, void __iomem *mode_reg, void __iomem
> + *cfg_reg, struct pll_rate_tbl *rtbl, u8 rtbl_cnt,
> + spinlock_t *lock, struct clk **pll_clk,
> + struct clk **vco_gate_clk);
> +
> +static inline long clk_round_rate_index(struct clk_hw *hw, unsigned long drate,
> + clk_calc_rate calc_rate, u8 rtbl_cnt, int *index)
> +{
> + unsigned long prev_rate, rate = 0;
> + unsigned long parent_rate = __clk_get_rate(__clk_get_parent(hw->clk));
> +
> + for (*index = 0; *index < rtbl_cnt; (*index)++) {
> + prev_rate = rate;
> + rate = calc_rate(hw, parent_rate, *index);
> + if (drate < rate) {
> + /* previous clock was best */
> + if (*index) {
> + rate = prev_rate;
> + (*index)--;
> + }
> + break;
> + }
> + }
> +
> + return rate;
> +}
> +
> +#endif /* __PLAT_CLOCK_H */
> --
> 1.7.9
>
More information about the linux-arm-kernel
mailing list