diff options
Diffstat (limited to 'target/linux/ipq806x/patches-3.18/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch')
-rw-r--r-- | target/linux/ipq806x/patches-3.18/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/target/linux/ipq806x/patches-3.18/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch b/target/linux/ipq806x/patches-3.18/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch new file mode 100644 index 0000000000..701d5e7046 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch @@ -0,0 +1,351 @@ +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 +@@ -6,6 +6,7 @@ clk-qcom-y += clk-pll.o + clk-qcom-y += clk-rcg.o + clk-qcom-y += clk-rcg2.o + clk-qcom-y += clk-branch.o ++clk-qcom-y += clk-hfpll.o + clk-qcom-y += reset.o + + obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.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_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 |