Add support for MSM's PLLs (phase locked loops). This is
sufficient enough to be able to determine the rate the PLL is
running at.

Cc: devicetree-discuss@lists.ozlabs.org
Signed-off-by: Stephen Boyd <sb...@codeaurora.org>
---
 Documentation/devicetree/bindings/clock/msm.txt |  40 ++++
 drivers/clk/Kconfig                             |   2 +
 drivers/clk/Makefile                            |   1 +
 drivers/clk/msm/Kconfig                         |   4 +
 drivers/clk/msm/Makefile                        |   3 +
 drivers/clk/msm/clk-pll.c                       | 233 ++++++++++++++++++++++++
 drivers/clk/msm/clk-pll.h                       |  43 +++++
 7 files changed, 326 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/msm.txt
 create mode 100644 drivers/clk/msm/Kconfig
 create mode 100644 drivers/clk/msm/Makefile
 create mode 100644 drivers/clk/msm/clk-pll.c
 create mode 100644 drivers/clk/msm/clk-pll.h

diff --git a/Documentation/devicetree/bindings/clock/msm.txt 
b/Documentation/devicetree/bindings/clock/msm.txt
new file mode 100644
index 0000000..2192621
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/msm.txt
@@ -0,0 +1,40 @@
+Bindings for Qualcomm's clock controllers
+
+These bindings use the common clock binding
+Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+PLL Binding
+-----------
+
+Required properties:
+- compatible : shall be "qcom,pll".
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : from common clock binding; shall be set to the reference clock
+
+Example:
+       pll8: pll8 {
+               #clock-cells = <0>;
+               compatible = "qcom,pll";
+               clocks = <&pxo>;
+       };
+
+Voteable PLL Binding
+--------------------
+
+Voteable PLLs are PLLs surrounded by a voting wrapper that aggregates
+votes from multiple masters in the system and enables or disables the
+PLL according to the current vote.
+
+Required properties:
+- compatible: shall be "qcom,pll-vote"
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : from common clock binding; shall be set to the pll that is wrapped
+  in voting logic
+
+Example:
+       vpll8: vpll8 {
+               #clock-cells = <0>;
+               compatible = "qcom,pll-vote";
+               clocks = <&pll8>;
+       };
+
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 0357ac4..acdb826 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -81,6 +81,8 @@ config COMMON_CLK_AXI_CLKGEN
          Support for the Analog Devices axi-clkgen pcore clock generator for 
Xilinx
          FPGAs. It is commonly used in Analog Devices' reference designs.
 
+source "drivers/clk/msm/Kconfig"
+
 endmenu
 
 source "drivers/clk/mvebu/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 137d3e7..c9e768b 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_PLAT_SPEAR)      += spear/
 obj-$(CONFIG_ARCH_U300)                += clk-u300.o
 obj-$(CONFIG_COMMON_CLK_VERSATILE) += versatile/
 obj-$(CONFIG_ARCH_PRIMA2)      += clk-prima2.o
+obj-$(CONFIG_COMMON_CLK_MSM)   += msm/
 obj-$(CONFIG_PLAT_ORION)       += mvebu/
 ifeq ($(CONFIG_COMMON_CLK), y)
 obj-$(CONFIG_ARCH_MMP)         += mmp/
diff --git a/drivers/clk/msm/Kconfig b/drivers/clk/msm/Kconfig
new file mode 100644
index 0000000..bf7e3d2
--- /dev/null
+++ b/drivers/clk/msm/Kconfig
@@ -0,0 +1,4 @@
+menuconfig COMMON_CLK_MSM
+       tristate "Support for Qualcomm's MSM designs"
+       depends on OF
+
diff --git a/drivers/clk/msm/Makefile b/drivers/clk/msm/Makefile
new file mode 100644
index 0000000..16b750f
--- /dev/null
+++ b/drivers/clk/msm/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_COMMON_CLK_MSM) += clk-msm.o
+
+clk-msm-$(CONFIG_COMMON_CLK_MSM) += clk-pll.o
diff --git a/drivers/clk/msm/clk-pll.c b/drivers/clk/msm/clk-pll.c
new file mode 100644
index 0000000..03c2c41
--- /dev/null
+++ b/drivers/clk/msm/clk-pll.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#include <asm/div64.h>
+
+#include "clk-pll.h"
+
+/**
+ * struct clk_pll - phase locked loop (PLL)
+ * @l_reg: L register
+ * @m_reg: M register
+ * @n_reg: N register
+ * @config_reg: config register
+ * @mode_reg: mode register
+ * @status_reg: status register
+ * @status_bit: ANDed with @status_reg to determine if PLL is enabled
+ * @hw: handle between common and hardware-specific interfaces
+ */
+struct clk_pll {
+       void __iomem *l_reg;
+       void __iomem *m_reg;
+       void __iomem *n_reg;
+       void __iomem *config_reg;
+       void __iomem *mode_reg;
+       void __iomem *status_reg;
+       u8 status_bit;
+
+       struct clk_hw   hw;
+};
+
+#define to_clk_pll(_hw) container_of(_hw, struct clk_pll, hw)
+
+#define PLL_OUTCTRL    BIT(0)
+#define PLL_BYPASSNL   BIT(1)
+#define PLL_RESET_N    BIT(2)
+
+static int clk_pll_enable(struct clk_hw *hw)
+{
+       struct clk_pll *pll = to_clk_pll(hw);
+       u32 mode;
+
+       mode = readl_relaxed(pll->mode_reg);
+       /* Disable PLL bypass mode. */
+       mode |= PLL_BYPASSNL;
+       writel(mode, pll->mode_reg);
+
+       /*
+        * H/W requires a 5us delay between disabling the bypass and
+        * de-asserting the reset. Delay 10us just to be safe.
+        */
+       udelay(10);
+
+       /* De-assert active-low PLL reset. */
+       mode |= PLL_RESET_N;
+       writel(mode, pll->mode_reg);
+
+       /* Wait until PLL is locked. */
+       udelay(50);
+
+       /* Enable PLL output. */
+       mode |= PLL_OUTCTRL;
+       writel(mode, pll->mode_reg);
+
+       return 0;
+}
+
+static void clk_pll_disable(struct clk_hw *hw)
+{
+       struct clk_pll *pll = to_clk_pll(hw);
+       u32 mode;
+
+       mode = readl_relaxed(pll->mode_reg);
+       mode &= ~(PLL_OUTCTRL | PLL_RESET_N | PLL_BYPASSNL);
+       writel_relaxed(mode, pll->mode_reg);
+}
+
+static unsigned long
+clk_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+       struct clk_pll *pll = to_clk_pll(hw);
+       u32 l, m, n;
+       unsigned long rate;
+       u64 tmp;
+
+       l = readl_relaxed(pll->l_reg) & 0x3ff;
+       m = readl_relaxed(pll->m_reg) & 0x7ffff;
+       n = readl_relaxed(pll->n_reg) & 0x7ffff;
+
+       rate = parent_rate * l;
+       if (n) {
+               tmp = parent_rate;
+               tmp *= m;
+               do_div(tmp, n);
+               rate += tmp;
+       }
+       return rate;
+}
+
+static const struct clk_ops clk_pll_ops = {
+       .enable = clk_pll_enable,
+       .disable = clk_pll_disable,
+       .recalc_rate = clk_pll_recalc_rate,
+};
+
+struct clk *pll_clk_register(struct device *dev, struct pll_desc *desc,
+               struct clk_init_data *init)
+{
+       struct clk_pll *p;
+
+       p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
+       if (!p)
+               return ERR_PTR(-ENOMEM);
+
+       p->l_reg = desc->base + desc->l_reg;
+       p->m_reg = desc->base + desc->m_reg;
+       p->n_reg = desc->base + desc->n_reg;
+       p->config_reg = desc->base + desc->config_reg;
+       p->mode_reg = desc->base + desc->mode_reg;
+       p->status_reg = desc->base + desc->status_reg;
+       p->status_bit = desc->status_bit;
+
+       init->ops = &clk_pll_ops;
+       p->hw.init = init;
+
+       return devm_clk_register(dev, &p->hw);
+}
+
+/**
+ * struct clk_pll_vote - phase locked loop (PLL) with hardware voting wrapper
+ * @vote_reg: Voting register
+ * @vote_bit: ORed into @vote_reg to enable PLL
+ * @hw: handle between common and hardware-specific interfaces
+ */
+struct clk_pll_vote {
+       void __iomem    *vote_reg;
+       u8              vote_bit;
+       struct clk_hw   hw;
+};
+
+#define to_clk_pll_vote(_hw) container_of(_hw, struct clk_pll_vote, hw)
+
+static DEFINE_SPINLOCK(pll_vote_lock);
+
+static int wait_for_pll(struct clk_pll *pll)
+{
+       int count;
+       const char *name = __clk_get_name(pll->hw.clk);
+
+       /* Wait for pll to enable. */
+       for (count = 200; count > 0; count--) {
+               if (readl_relaxed(pll->status_reg) & BIT(pll->status_bit))
+                       return 0;
+               udelay(1);
+       }
+
+       WARN("%s didn't enable after voting for it!\n", name);
+       return -ETIMEDOUT;
+}
+
+static int clk_pll_vote_enable(struct clk_hw *hw)
+{
+       u32 val;
+       unsigned long flags;
+       struct clk_pll_vote *pll = to_clk_pll_vote(hw);
+       struct clk_pll *p = to_clk_pll(__clk_get_hw(__clk_get_parent(hw->clk)));
+
+       spin_lock_irqsave(&pll_vote_lock, flags);
+
+       val = readl_relaxed(pll->vote_reg);
+       val |= BIT(pll->vote_bit);
+       writel(val, pll->vote_reg);
+
+       spin_unlock_irqrestore(&pll_vote_lock, flags);
+
+       return wait_for_pll(p);
+}
+
+static void clk_pll_vote_disable(struct clk_hw *hw)
+{
+       u32 val;
+       unsigned long flags;
+       struct clk_pll_vote *pll = to_clk_pll_vote(hw);
+
+       spin_lock_irqsave(&pll_vote_lock, flags);
+
+       val = readl_relaxed(pll->vote_reg);
+       val &= ~BIT(pll->vote_bit);
+       writel_relaxed(val, pll->vote_reg);
+
+       spin_unlock_irqrestore(&pll_vote_lock, flags);
+}
+
+static const struct clk_ops clk_pll_vote_ops = {
+       .enable = clk_pll_vote_enable,
+       .disable = clk_pll_vote_disable,
+};
+
+struct clk *pll_vote_clk_register(struct device *dev,
+               struct pll_vote_desc *desc, struct clk_init_data *init)
+{
+       struct clk_pll_vote *p;
+
+       p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
+       if (!p)
+               return ERR_PTR(-ENOMEM);
+
+       p->vote_reg = desc->base + desc->vote_reg;
+       p->vote_bit = desc->vote_bit;
+
+       init->ops = &clk_pll_vote_ops;
+       p->hw.init = init;
+
+       return devm_clk_register(dev, &p->hw);
+}
diff --git a/drivers/clk/msm/clk-pll.h b/drivers/clk/msm/clk-pll.h
new file mode 100644
index 0000000..4e63a5e
--- /dev/null
+++ b/drivers/clk/msm/clk-pll.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#ifndef __MSM_CLK_PLL_H__
+#define __MSM_CLK_PLL_H__
+
+struct device;
+struct clk;
+struct clk_init_data;
+
+struct pll_desc {
+       void __iomem *base;
+       u16 l_reg;
+       u16 m_reg;
+       u16 n_reg;
+       u16 config_reg;
+       u16 mode_reg;
+       u16 status_reg;
+       u8 status_bit;
+};
+
+struct pll_vote_desc {
+       void __iomem *base;
+       u16 vote_reg;
+       u8 vote_bit;
+};
+
+extern struct clk *pll_clk_register(struct device *dev, struct pll_desc *desc,
+               struct clk_init_data *init);
+extern struct clk *pll_vote_clk_register(struct device *dev,
+               struct pll_vote_desc *desc, struct clk_init_data *init);
+
+#endif
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation

_______________________________________________
devicetree-discuss mailing list
devicetree-discuss@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/devicetree-discuss

Reply via email to