123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- Content-Type: text/plain; charset="utf-8"
- MIME-Version: 1.0
- Content-Transfer-Encoding: 7bit
- Subject: [v3,05/13] clk: qcom: Add support for High-Frequency PLLs (HFPLLs)
- From: Stephen Boyd <sboyd@codeaurora.org>
- X-Patchwork-Id: 6063261
- Message-Id: <1426920332-9340-6-git-send-email-sboyd@codeaurora.org>
- To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
- Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
- linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
- Viresh Kumar <viresh.kumar@linaro.org>
- Date: Fri, 20 Mar 2015 23:45:24 -0700
- HFPLLs are the main frequency source for Krait CPU clocks. Add
- support for changing the rate of these PLLs.
- Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
- ---
- I'd really like to get rid of __clk_hfpll_init_once() if possible...
- drivers/clk/qcom/Makefile | 1 +
- drivers/clk/qcom/clk-hfpll.c | 253 +++++++++++++++++++++++++++++++++++++++++++
- drivers/clk/qcom/clk-hfpll.h | 54 +++++++++
- 3 files changed, 308 insertions(+)
- create mode 100644 drivers/clk/qcom/clk-hfpll.c
- create mode 100644 drivers/clk/qcom/clk-hfpll.h
- --- a/drivers/clk/qcom/Makefile
- +++ b/drivers/clk/qcom/Makefile
- @@ -8,6 +8,7 @@ clk-qcom-y += clk-rcg2.o
- clk-qcom-y += clk-branch.o
- clk-qcom-y += clk-regmap-divider.o
- clk-qcom-y += clk-regmap-mux.o
- +clk-qcom-y += clk-hfpll.o
- clk-qcom-y += reset.o
- clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o
-
- --- /dev/null
- +++ b/drivers/clk/qcom/clk-hfpll.c
- @@ -0,0 +1,253 @@
- +/*
- + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 and
- + * only version 2 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.
- + */
- +#include <linux/kernel.h>
- +#include <linux/export.h>
- +#include <linux/regmap.h>
- +#include <linux/delay.h>
- +#include <linux/err.h>
- +#include <linux/clk-provider.h>
- +#include <linux/spinlock.h>
- +
- +#include "clk-regmap.h"
- +#include "clk-hfpll.h"
- +
- +#define PLL_OUTCTRL BIT(0)
- +#define PLL_BYPASSNL BIT(1)
- +#define PLL_RESET_N BIT(2)
- +
- +/* Initialize a HFPLL at a given rate and enable it. */
- +static void __clk_hfpll_init_once(struct clk_hw *hw)
- +{
- + struct clk_hfpll *h = to_clk_hfpll(hw);
- + struct hfpll_data const *hd = h->d;
- + struct regmap *regmap = h->clkr.regmap;
- +
- + if (likely(h->init_done))
- + return;
- +
- + /* Configure PLL parameters for integer mode. */
- + if (hd->config_val)
- + regmap_write(regmap, hd->config_reg, hd->config_val);
- + regmap_write(regmap, hd->m_reg, 0);
- + regmap_write(regmap, hd->n_reg, 1);
- +
- + if (hd->user_reg) {
- + u32 regval = hd->user_val;
- + unsigned long rate;
- +
- + rate = clk_hw_get_rate(hw->clk);
- +
- + /* Pick the right VCO. */
- + if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
- + regval |= hd->user_vco_mask;
- + regmap_write(regmap, hd->user_reg, regval);
- + }
- +
- + if (hd->droop_reg)
- + regmap_write(regmap, hd->droop_reg, hd->droop_val);
- +
- + h->init_done = true;
- +}
- +
- +static void __clk_hfpll_enable(struct clk_hw *hw)
- +{
- + struct clk_hfpll *h = to_clk_hfpll(hw);
- + struct hfpll_data const *hd = h->d;
- + struct regmap *regmap = h->clkr.regmap;
- + u32 val;
- +
- + __clk_hfpll_init_once(hw);
- +
- + /* Disable PLL bypass mode. */
- + regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL);
- +
- + /*
- + * 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. */
- + regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N);
- +
- + /* Wait for PLL to lock. */
- + if (hd->status_reg) {
- + do {
- + regmap_read(regmap, hd->status_reg, &val);
- + } while (!(val & BIT(hd->lock_bit)));
- + } else {
- + udelay(60);
- + }
- +
- + /* Enable PLL output. */
- + regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL);
- +}
- +
- +/* Enable an already-configured HFPLL. */
- +static int clk_hfpll_enable(struct clk_hw *hw)
- +{
- + unsigned long flags;
- + struct clk_hfpll *h = to_clk_hfpll(hw);
- + struct hfpll_data const *hd = h->d;
- + struct regmap *regmap = h->clkr.regmap;
- + u32 mode;
- +
- + spin_lock_irqsave(&h->lock, flags);
- + regmap_read(regmap, hd->mode_reg, &mode);
- + if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)))
- + __clk_hfpll_enable(hw);
- + spin_unlock_irqrestore(&h->lock, flags);
- +
- + return 0;
- +}
- +
- +static void __clk_hfpll_disable(struct clk_hfpll *h)
- +{
- + struct hfpll_data const *hd = h->d;
- + struct regmap *regmap = h->clkr.regmap;
- +
- + /*
- + * Disable the PLL output, disable test mode, enable the bypass mode,
- + * and assert the reset.
- + */
- + regmap_update_bits(regmap, hd->mode_reg,
- + PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0);
- +}
- +
- +static void clk_hfpll_disable(struct clk_hw *hw)
- +{
- + struct clk_hfpll *h = to_clk_hfpll(hw);
- + unsigned long flags;
- +
- + spin_lock_irqsave(&h->lock, flags);
- + __clk_hfpll_disable(h);
- + spin_unlock_irqrestore(&h->lock, flags);
- +}
- +
- +static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate,
- + unsigned long *parent_rate)
- +{
- + struct clk_hfpll *h = to_clk_hfpll(hw);
- + struct hfpll_data const *hd = h->d;
- + unsigned long rrate;
- +
- + rate = clamp(rate, hd->min_rate, hd->max_rate);
- +
- + rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate;
- + if (rrate > hd->max_rate)
- + rrate -= *parent_rate;
- +
- + return rrate;
- +}
- +
- +/*
- + * For optimization reasons, assumes no downstream clocks are actively using
- + * it.
- + */
- +static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate,
- + unsigned long parent_rate)
- +{
- + struct clk_hfpll *h = to_clk_hfpll(hw);
- + struct hfpll_data const *hd = h->d;
- + struct regmap *regmap = h->clkr.regmap;
- + unsigned long flags;
- + u32 l_val, val;
- + bool enabled;
- +
- + l_val = rate / parent_rate;
- +
- + spin_lock_irqsave(&h->lock, flags);
- +
- + enabled = __clk_is_enabled(hw->clk);
- + if (enabled)
- + __clk_hfpll_disable(h);
- +
- + /* Pick the right VCO. */
- + if (hd->user_reg && hd->user_vco_mask) {
- + regmap_read(regmap, hd->user_reg, &val);
- + if (rate <= hd->low_vco_max_rate)
- + val &= ~hd->user_vco_mask;
- + else
- + val |= hd->user_vco_mask;
- + regmap_write(regmap, hd->user_reg, val);
- + }
- +
- + regmap_write(regmap, hd->l_reg, l_val);
- +
- + if (enabled)
- + __clk_hfpll_enable(hw);
- +
- + spin_unlock_irqrestore(&h->lock, flags);
- +
- + return 0;
- +}
- +
- +static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw,
- + unsigned long parent_rate)
- +{
- + struct clk_hfpll *h = to_clk_hfpll(hw);
- + struct hfpll_data const *hd = h->d;
- + struct regmap *regmap = h->clkr.regmap;
- + u32 l_val;
- +
- + regmap_read(regmap, hd->l_reg, &l_val);
- +
- + return l_val * parent_rate;
- +}
- +
- +static void clk_hfpll_init(struct clk_hw *hw)
- +{
- + struct clk_hfpll *h = to_clk_hfpll(hw);
- + struct hfpll_data const *hd = h->d;
- + struct regmap *regmap = h->clkr.regmap;
- + u32 mode, status;
- +
- + regmap_read(regmap, hd->mode_reg, &mode);
- + if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) {
- + __clk_hfpll_init_once(hw);
- + return;
- + }
- +
- + if (hd->status_reg) {
- + regmap_read(regmap, hd->status_reg, &status);
- + if (!(status & BIT(hd->lock_bit))) {
- + WARN(1, "HFPLL %s is ON, but not locked!\n",
- + __clk_get_name(hw->clk));
- + clk_hfpll_disable(hw);
- + __clk_hfpll_init_once(hw);
- + }
- + }
- +}
- +
- +static int hfpll_is_enabled(struct clk_hw *hw)
- +{
- + struct clk_hfpll *h = to_clk_hfpll(hw);
- + struct hfpll_data const *hd = h->d;
- + struct regmap *regmap = h->clkr.regmap;
- + u32 mode;
- +
- + regmap_read(regmap, hd->mode_reg, &mode);
- + mode &= 0x7;
- + return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL);
- +}
- +
- +const struct clk_ops clk_ops_hfpll = {
- + .enable = clk_hfpll_enable,
- + .disable = clk_hfpll_disable,
- + .is_enabled = hfpll_is_enabled,
- + .round_rate = clk_hfpll_round_rate,
- + .set_rate = clk_hfpll_set_rate,
- + .recalc_rate = clk_hfpll_recalc_rate,
- + .init = clk_hfpll_init,
- +};
- +EXPORT_SYMBOL_GPL(clk_ops_hfpll);
- --- /dev/null
- +++ b/drivers/clk/qcom/clk-hfpll.h
- @@ -0,0 +1,54 @@
- +/*
- + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License version 2 and
- + * only version 2 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.
- + */
- +#ifndef __QCOM_CLK_HFPLL_H__
- +#define __QCOM_CLK_HFPLL_H__
- +
- +#include <linux/clk-provider.h>
- +#include <linux/spinlock.h>
- +#include "clk-regmap.h"
- +
- +struct hfpll_data {
- + u32 mode_reg;
- + u32 l_reg;
- + u32 m_reg;
- + u32 n_reg;
- + u32 user_reg;
- + u32 droop_reg;
- + u32 config_reg;
- + u32 status_reg;
- + u8 lock_bit;
- +
- + u32 droop_val;
- + u32 config_val;
- + u32 user_val;
- + u32 user_vco_mask;
- + unsigned long low_vco_max_rate;
- +
- + unsigned long min_rate;
- + unsigned long max_rate;
- +};
- +
- +struct clk_hfpll {
- + struct hfpll_data const *d;
- + int init_done;
- +
- + struct clk_regmap clkr;
- + spinlock_t lock;
- +};
- +
- +#define to_clk_hfpll(_hw) \
- + container_of(to_clk_regmap(_hw), struct clk_hfpll, clkr)
- +
- +extern const struct clk_ops clk_ops_hfpll;
- +
- +#endif
|