From patchwork Thu Jun 13 01:49:01 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 2713221 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 9581A9F1E2 for ; Thu, 13 Jun 2013 02:54:42 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 24EB9201EC for ; Thu, 13 Jun 2013 02:54:41 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 819CA201B9 for ; Thu, 13 Jun 2013 02:54:39 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UmwiI-0001XY-GZ; Thu, 13 Jun 2013 01:52:38 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Umwgz-0005Uj-PF; Thu, 13 Jun 2013 01:51:13 +0000 Received: from smtp.codeaurora.org ([198.145.11.231]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Umwfj-0005LC-O5 for linux-arm-kernel@lists.infradead.org; Thu, 13 Jun 2013 01:50:14 +0000 Received: from smtp.codeaurora.org (localhost [127.0.0.1]) by smtp.codeaurora.org (Postfix) with ESMTP id 250C413F109; Thu, 13 Jun 2013 01:49:15 +0000 (UTC) Received: by smtp.codeaurora.org (Postfix, from userid 486) id 187EB13F114; Thu, 13 Jun 2013 01:49:15 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-4.4 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from sboyd-linux.qualcomm.com (i-global252.qualcomm.com [199.106.103.252]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) (Authenticated sender: sboyd@smtp.codeaurora.org) by smtp.codeaurora.org (Postfix) with ESMTPSA id 1780413F109; Thu, 13 Jun 2013 01:49:14 +0000 (UTC) From: Stephen Boyd To: linux-arm-kernel@lists.infradead.org Subject: [RFC/PATCH 05/13] clk: msm: Add support for phase locked loops (PLLs) Date: Wed, 12 Jun 2013 18:49:01 -0700 Message-Id: <1371088149-22562-6-git-send-email-sboyd@codeaurora.org> X-Mailer: git-send-email 1.8.3.1.378.g9926f66 In-Reply-To: <1371088149-22562-1-git-send-email-sboyd@codeaurora.org> References: <1371088149-22562-1-git-send-email-sboyd@codeaurora.org> X-Virus-Scanned: ClamAV using ClamSMTP X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130612_214956_687130_BEF2F2C3 X-CRM114-Status: GOOD ( 24.24 ) X-Spam-Score: -2.1 (--) Cc: linux-arm-msm@vger.kernel.org, devicetree-discuss@lists.ozlabs.org, Saravana Kannan , Mike Turquette , linux-kernel@vger.kernel.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#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