|
@@ -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;
|
|
|
+}
|