summaryrefslogtreecommitdiff
path: root/drivers/clk/imx/clk-pllv5.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/imx/clk-pllv5.c')
-rw-r--r--drivers/clk/imx/clk-pllv5.c199
1 files changed, 199 insertions, 0 deletions
diff --git a/drivers/clk/imx/clk-pllv5.c b/drivers/clk/imx/clk-pllv5.c
new file mode 100644
index 000000000000..c45704c7942e
--- /dev/null
+++ b/drivers/clk/imx/clk-pllv5.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+
+#include "clk.h"
+
+#define PLL_EN BIT(0)
+#define BP_PLL_DIV 16
+#define BM_PLL_DIV (0x7 << 16)
+#define PLL_CFG_OFFSET 0x08
+
+struct clk_pllv5 {
+ struct clk_hw hw;
+ void __iomem *base;
+ u32 div_mask;
+ u32 div_shift;
+ u32 cfg_offset;
+};
+
+#define to_clk_pllv5(__hw) container_of(__hw, struct clk_pllv5, hw)
+
+static unsigned long clk_pllv5_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_pllv5 *pll = to_clk_pllv5(hw);
+ u32 val = (readl_relaxed(pll->base + pll->cfg_offset) & pll->div_mask) >> pll->div_shift;
+ u32 div;
+
+ switch (val) {
+ case 1:
+ div = 15;
+ break;
+ case 2:
+ div = 16;
+ break;
+ case 3:
+ div = 20;
+ break;
+ case 4:
+ div = 22;
+ break;
+ case 5:
+ div = 25;
+ break;
+ case 6:
+ div = 30;
+ break;
+ default:
+ div = 20;
+ break;
+ }
+
+ return parent_rate * div;
+}
+
+static long clk_pllv5_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ unsigned long parent_rate = *prate;
+ u32 div;
+
+ div = rate / parent_rate;
+
+ if (div == 15 || div == 16 ||
+ div == 20 || div == 22 ||
+ div == 25 || div == 30)
+ return parent_rate * div;
+ else
+ return parent_rate * 20;
+}
+
+static int clk_pllv5_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_pllv5 *pll = to_clk_pllv5(hw);
+ unsigned long min_rate = parent_rate * 15;
+ unsigned long max_rate = parent_rate * 30;
+ u32 val, div, reg;
+
+ if (rate < min_rate || rate > max_rate)
+ return -EINVAL;
+
+ div = rate / parent_rate;
+
+ switch (div) {
+ case 15:
+ val = 1;
+ break;
+ case 16:
+ val = 2;
+ break;
+ case 20:
+ val = 3;
+ break;
+ case 22:
+ val = 4;
+ break;
+ case 25:
+ val = 5;
+ break;
+ case 30:
+ val = 6;
+ break;
+ default:
+ val = 3;
+ break;
+ }
+
+ reg = readl_relaxed(pll->base + pll->cfg_offset);
+ reg &= ~pll->div_mask;
+ reg |= (val << pll->div_shift);
+ writel_relaxed(val, pll->base + pll->cfg_offset);
+
+ return 0;
+}
+
+static int clk_pllv5_enable(struct clk_hw *hw)
+{
+ u32 val;
+ struct clk_pllv5 *pll = to_clk_pllv5(hw);
+
+ val = readl_relaxed(pll->base);
+ val |= PLL_EN;
+ writel_relaxed(val, pll->base);
+
+ return 0;
+}
+
+static void clk_pllv5_disable(struct clk_hw *hw)
+{
+ u32 val;
+ struct clk_pllv5 *pll = to_clk_pllv5(hw);
+
+ val = readl_relaxed(pll->base);
+ val &= ~PLL_EN;
+ writel_relaxed(val, pll->base);
+}
+
+static int clk_pllv5_is_enabled(struct clk_hw *hw)
+{
+ struct clk_pllv5 *pll = to_clk_pllv5(hw);
+
+ if (readl_relaxed(pll->base) & PLL_EN)
+ return 0;
+
+ return 1;
+}
+
+static const struct clk_ops clk_pllv5_ops = {
+ .recalc_rate = clk_pllv5_recalc_rate,
+ .round_rate = clk_pllv5_round_rate,
+ .set_rate = clk_pllv5_set_rate,
+ .enable = clk_pllv5_enable,
+ .disable = clk_pllv5_disable,
+ .is_enabled = clk_pllv5_is_enabled,
+};
+
+struct clk *imx_clk_pllv5(const char *name, const char *parent_name,
+ void __iomem *base)
+{
+ struct clk_pllv5 *pll;
+ struct clk *clk;
+ struct clk_init_data init;
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->base = base;
+ pll->div_mask = BM_PLL_DIV;
+ pll->div_shift = BP_PLL_DIV;
+ pll->cfg_offset = PLL_CFG_OFFSET;
+
+ init.name = name;
+ init.ops = &clk_pllv5_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+
+ pll->hw.init = &init;
+
+ clk = clk_register(NULL, &pll->hw);
+ if (IS_ERR(clk))
+ kfree(pll);
+
+ return clk;
+}
+