| From c941bcb6045b8a2a89fe393c4a71452996055bf7 Mon Sep 17 00:00:00 2001 |
| From: Soren Brinkmann <soren.brinkmann@xilinx.com> |
| Date: Mon, 13 May 2013 10:46:36 -0700 |
| Subject: clk: zynq: Factor out PLL driver |
| |
| Refactor the PLL driver so it works with the clock controller driver. |
| |
| Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com> |
| Signed-off-by: Michal Simek <michal.simek@xilinx.com> |
| Acked-by: Mike Turquette <mturquette@linaro.org> |
| (cherry picked from commit 3682af46d55f2c97898b9cc1c8c80afad81f62be) |
| Signed-off-by: Daniel Sangorrin <daniel.sangorrin@toshiba.co.jp> |
| Signed-off-by: Yoshitake Kobayashi <yoshitake.kobayashi@toshiba.co.jp> |
| --- |
| drivers/clk/zynq/pll.c | 235 +++++++++++++++++++++++++++++++++++++++++++++++++ |
| 1 file changed, 235 insertions(+) |
| create mode 100644 drivers/clk/zynq/pll.c |
| |
| diff --git a/drivers/clk/zynq/pll.c b/drivers/clk/zynq/pll.c |
| new file mode 100644 |
| index 000000000000..47e307c25a7b |
| --- /dev/null |
| +++ b/drivers/clk/zynq/pll.c |
| @@ -0,0 +1,235 @@ |
| +/* |
| + * Zynq PLL driver |
| + * |
| + * Copyright (C) 2013 Xilinx |
| + * |
| + * Sören Brinkmann <soren.brinkmann@xilinx.com> |
| + * |
| + * This program is free software: you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License v2 as published by |
| + * the Free Software Foundation. |
| + * |
| + * This program is distributed in the hope that it will be useful, |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| + * GNU General Public License for more details. |
| + * |
| + * You should have received a copy of the GNU General Public License |
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| + * |
| + */ |
| +#include <linux/clk/zynq.h> |
| +#include <linux/clk-provider.h> |
| +#include <linux/slab.h> |
| +#include <linux/io.h> |
| + |
| +/** |
| + * struct zynq_pll |
| + * @hw: Handle between common and hardware-specific interfaces |
| + * @pll_ctrl: PLL control register |
| + * @pll_status: PLL status register |
| + * @lock: Register lock |
| + * @lockbit: Indicates the associated PLL_LOCKED bit in the PLL status |
| + * register. |
| + */ |
| +struct zynq_pll { |
| + struct clk_hw hw; |
| + void __iomem *pll_ctrl; |
| + void __iomem *pll_status; |
| + spinlock_t *lock; |
| + u8 lockbit; |
| +}; |
| +#define to_zynq_pll(_hw) container_of(_hw, struct zynq_pll, hw) |
| + |
| +/* Register bitfield defines */ |
| +#define PLLCTRL_FBDIV_MASK 0x7f000 |
| +#define PLLCTRL_FBDIV_SHIFT 12 |
| +#define PLLCTRL_BPQUAL_MASK (1 << 3) |
| +#define PLLCTRL_PWRDWN_MASK 2 |
| +#define PLLCTRL_PWRDWN_SHIFT 1 |
| +#define PLLCTRL_RESET_MASK 1 |
| +#define PLLCTRL_RESET_SHIFT 0 |
| + |
| +/** |
| + * zynq_pll_round_rate() - Round a clock frequency |
| + * @hw: Handle between common and hardware-specific interfaces |
| + * @rate: Desired clock frequency |
| + * @prate: Clock frequency of parent clock |
| + * Returns frequency closest to @rate the hardware can generate. |
| + */ |
| +static long zynq_pll_round_rate(struct clk_hw *hw, unsigned long rate, |
| + unsigned long *prate) |
| +{ |
| + u32 fbdiv; |
| + |
| + fbdiv = DIV_ROUND_CLOSEST(rate, *prate); |
| + if (fbdiv < 13) |
| + fbdiv = 13; |
| + else if (fbdiv > 66) |
| + fbdiv = 66; |
| + |
| + return *prate * fbdiv; |
| +} |
| + |
| +/** |
| + * zynq_pll_recalc_rate() - Recalculate clock frequency |
| + * @hw: Handle between common and hardware-specific interfaces |
| + * @parent_rate: Clock frequency of parent clock |
| + * Returns current clock frequency. |
| + */ |
| +static unsigned long zynq_pll_recalc_rate(struct clk_hw *hw, |
| + unsigned long parent_rate) |
| +{ |
| + struct zynq_pll *clk = to_zynq_pll(hw); |
| + u32 fbdiv; |
| + |
| + /* |
| + * makes probably sense to redundantly save fbdiv in the struct |
| + * zynq_pll to save the IO access. |
| + */ |
| + fbdiv = (readl(clk->pll_ctrl) & PLLCTRL_FBDIV_MASK) >> |
| + PLLCTRL_FBDIV_SHIFT; |
| + |
| + return parent_rate * fbdiv; |
| +} |
| + |
| +/** |
| + * zynq_pll_is_enabled - Check if a clock is enabled |
| + * @hw: Handle between common and hardware-specific interfaces |
| + * Returns 1 if the clock is enabled, 0 otherwise. |
| + * |
| + * Not sure this is a good idea, but since disabled means bypassed for |
| + * this clock implementation we say we are always enabled. |
| + */ |
| +static int zynq_pll_is_enabled(struct clk_hw *hw) |
| +{ |
| + unsigned long flags = 0; |
| + u32 reg; |
| + struct zynq_pll *clk = to_zynq_pll(hw); |
| + |
| + spin_lock_irqsave(clk->lock, flags); |
| + |
| + reg = readl(clk->pll_ctrl); |
| + |
| + spin_unlock_irqrestore(clk->lock, flags); |
| + |
| + return !(reg & (PLLCTRL_RESET_MASK | PLLCTRL_PWRDWN_MASK)); |
| +} |
| + |
| +/** |
| + * zynq_pll_enable - Enable clock |
| + * @hw: Handle between common and hardware-specific interfaces |
| + * Returns 0 on success |
| + */ |
| +static int zynq_pll_enable(struct clk_hw *hw) |
| +{ |
| + unsigned long flags = 0; |
| + u32 reg; |
| + struct zynq_pll *clk = to_zynq_pll(hw); |
| + |
| + if (zynq_pll_is_enabled(hw)) |
| + return 0; |
| + |
| + pr_info("PLL: enable\n"); |
| + |
| + /* Power up PLL and wait for lock */ |
| + spin_lock_irqsave(clk->lock, flags); |
| + |
| + reg = readl(clk->pll_ctrl); |
| + reg &= ~(PLLCTRL_RESET_MASK | PLLCTRL_PWRDWN_MASK); |
| + writel(reg, clk->pll_ctrl); |
| + while (!(readl(clk->pll_status) & (1 << clk->lockbit))) |
| + ; |
| + |
| + spin_unlock_irqrestore(clk->lock, flags); |
| + |
| + return 0; |
| +} |
| + |
| +/** |
| + * zynq_pll_disable - Disable clock |
| + * @hw: Handle between common and hardware-specific interfaces |
| + * Returns 0 on success |
| + */ |
| +static void zynq_pll_disable(struct clk_hw *hw) |
| +{ |
| + unsigned long flags = 0; |
| + u32 reg; |
| + struct zynq_pll *clk = to_zynq_pll(hw); |
| + |
| + if (!zynq_pll_is_enabled(hw)) |
| + return; |
| + |
| + pr_info("PLL: shutdown\n"); |
| + |
| + /* shut down PLL */ |
| + spin_lock_irqsave(clk->lock, flags); |
| + |
| + reg = readl(clk->pll_ctrl); |
| + reg |= PLLCTRL_RESET_MASK | PLLCTRL_PWRDWN_MASK; |
| + writel(reg, clk->pll_ctrl); |
| + |
| + spin_unlock_irqrestore(clk->lock, flags); |
| +} |
| + |
| +static const struct clk_ops zynq_pll_ops = { |
| + .enable = zynq_pll_enable, |
| + .disable = zynq_pll_disable, |
| + .is_enabled = zynq_pll_is_enabled, |
| + .round_rate = zynq_pll_round_rate, |
| + .recalc_rate = zynq_pll_recalc_rate |
| +}; |
| + |
| +/** |
| + * clk_register_zynq_pll() - Register PLL with the clock framework |
| + * @np Pointer to the DT device node |
| + */ |
| +struct clk *clk_register_zynq_pll(const char *name, const char *parent, |
| + void __iomem *pll_ctrl, void __iomem *pll_status, u8 lock_index, |
| + spinlock_t *lock) |
| +{ |
| + struct zynq_pll *pll; |
| + struct clk *clk; |
| + u32 reg; |
| + const char *parent_arr[1] = {parent}; |
| + unsigned long flags = 0; |
| + struct clk_init_data initd = { |
| + .name = name, |
| + .parent_names = parent_arr, |
| + .ops = &zynq_pll_ops, |
| + .num_parents = 1, |
| + .flags = 0 |
| + }; |
| + |
| + pll = kmalloc(sizeof(*pll), GFP_KERNEL); |
| + if (!pll) { |
| + pr_err("%s: Could not allocate Zynq PLL clk.\n", __func__); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + |
| + /* Populate the struct */ |
| + pll->hw.init = &initd; |
| + pll->pll_ctrl = pll_ctrl; |
| + pll->pll_status = pll_status; |
| + pll->lockbit = lock_index; |
| + pll->lock = lock; |
| + |
| + spin_lock_irqsave(pll->lock, flags); |
| + |
| + reg = readl(pll->pll_ctrl); |
| + reg &= ~PLLCTRL_BPQUAL_MASK; |
| + writel(reg, pll->pll_ctrl); |
| + |
| + spin_unlock_irqrestore(pll->lock, flags); |
| + |
| + clk = clk_register(NULL, &pll->hw); |
| + if (WARN_ON(IS_ERR(clk))) |
| + goto free_pll; |
| + |
| + return clk; |
| + |
| +free_pll: |
| + kfree(pll); |
| + |
| + return clk; |
| +} |
| -- |
| 1.8.5.rc3 |
| |