| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Zynq PLL driver |
| * |
| * Copyright (C) 2013 Xilinx |
| * |
| * Sören Brinkmann <soren.brinkmann@xilinx.com> |
| */ |
| #include <linux/clk/zynq.h> |
| #include <linux/clk-provider.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| |
| /** |
| * struct zynq_pll - pll clock |
| * @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 |
| |
| #define PLL_FBDIV_MIN 13 |
| #define PLL_FBDIV_MAX 66 |
| |
| /** |
| * 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 |
| * Return: 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 < PLL_FBDIV_MIN) |
| fbdiv = PLL_FBDIV_MIN; |
| else if (fbdiv > PLL_FBDIV_MAX) |
| fbdiv = PLL_FBDIV_MAX; |
| |
| 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 |
| * Return: 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 |
| * Return: 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 |
| * Return: 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 |
| * @name: PLL name |
| * @parent: Parent clock name |
| * @pll_ctrl: Pointer to PLL control register |
| * @pll_status: Pointer to PLL status register |
| * @lock_index: Bit index to this PLL's lock status bit in @pll_status |
| * @lock: Register lock |
| * Return: handle to the registered clock. |
| */ |
| 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) |
| 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; |
| } |