summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaniya Das <tdas@codeaurora.org>2016-10-07 13:00:03 +0530
committerTaniya Das <tdas@codeaurora.org>2016-11-16 09:53:20 +0530
commit72609b4929859621ee4741112fe2746897444083 (patch)
tree1523d2c1e22638d1d86d36b0c49c05ad9a654a0e
parent1e1700765225e4aa31d076f78c9937bd93509083 (diff)
clk: qcom: Add support to be able to slew PLL
Few alpha PLLs would require to be able to slew in the same VCO mode, which means the PLL would be able to update the new frequency L value without turning it off. But to be support this feature the PLL needs to calibrated at the mid of the VCO range and then enabled. Change-Id: I24820bd6254002f8a8db9604d230dcbce59b1beb Signed-off-by: Taniya Das <tdas@codeaurora.org>
-rw-r--r--drivers/clk/qcom/clk-alpha-pll.c179
-rw-r--r--drivers/clk/qcom/clk-alpha-pll.h1
2 files changed, 180 insertions, 0 deletions
diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c
index 085b9acfb9d5..a208b4b01e1c 100644
--- a/drivers/clk/qcom/clk-alpha-pll.c
+++ b/drivers/clk/qcom/clk-alpha-pll.c
@@ -117,6 +117,11 @@ static int wait_for_pll_latch_ack(struct clk_alpha_pll *pll, u32 mask)
return wait_for_pll(pll, mask, 0, "latch_ack");
}
+static int wait_for_pll_update(struct clk_alpha_pll *pll, u32 mask)
+{
+ return wait_for_pll(pll, mask, 1, "update");
+}
+
/* alpha pll with hwfsm support */
#define PLL_OFFLINE_REQ BIT(7)
@@ -633,3 +638,177 @@ const struct clk_ops clk_alpha_pll_postdiv_ops = {
.set_rate = clk_alpha_pll_postdiv_set_rate,
};
EXPORT_SYMBOL_GPL(clk_alpha_pll_postdiv_ops);
+
+static int clk_alpha_pll_slew_update(struct clk_alpha_pll *pll)
+{
+ int ret = 0;
+ u32 val;
+
+ regmap_update_bits(pll->clkr.regmap, pll->offset + PLL_MODE,
+ PLL_UPDATE, PLL_UPDATE);
+ regmap_read(pll->clkr.regmap, pll->offset + PLL_MODE, &val);
+
+ ret = wait_for_pll_update(pll, PLL_UPDATE);
+ if (ret)
+ return ret;
+ /*
+ * HPG mandates a wait of at least 570ns before polling the LOCK
+ * detect bit. Have a delay of 1us just to be safe.
+ */
+ mb();
+ udelay(1);
+
+ ret = wait_for_pll_enable(pll, PLL_LOCK_DET);
+
+ return ret;
+}
+
+static int clk_alpha_pll_slew_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
+ unsigned long freq_hz;
+ const struct pll_vco *curr_vco, *vco;
+ u32 l;
+ u64 a;
+
+ freq_hz = alpha_pll_round_rate(rate, parent_rate, &l, &a);
+ if (freq_hz != rate) {
+ pr_err("alpha_pll: Call clk_set_rate with rounded rates!\n");
+ return -EINVAL;
+ }
+
+ curr_vco = alpha_pll_find_vco(pll, clk_hw_get_rate(hw));
+ if (!curr_vco) {
+ pr_err("alpha pll: not in a valid vco range\n");
+ return -EINVAL;
+ }
+
+ vco = alpha_pll_find_vco(pll, freq_hz);
+ if (!vco) {
+ pr_err("alpha pll: not in a valid vco range\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Dynamic pll update will not support switching frequencies across
+ * vco ranges. In those cases fall back to normal alpha set rate.
+ */
+ if (curr_vco->val != vco->val)
+ return clk_alpha_pll_set_rate(hw, rate, parent_rate);
+
+ a = a << (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH);
+
+ regmap_write(pll->clkr.regmap, pll->offset + PLL_L_VAL, l);
+ regmap_write(pll->clkr.regmap, pll->offset + PLL_ALPHA_VAL, a);
+ regmap_write(pll->clkr.regmap, pll->offset + PLL_ALPHA_VAL_U, a >> 32);
+
+ /* Ensure that the write above goes through before proceeding. */
+ mb();
+
+ if (clk_hw_is_enabled(hw))
+ clk_alpha_pll_slew_update(pll);
+
+ return 0;
+}
+
+/*
+ * Slewing plls should be bought up at frequency which is in the middle of the
+ * desired VCO range. So after bringing up the pll at calibration freq, set it
+ * back to desired frequency(that was set by previous clk_set_rate).
+ */
+static int clk_alpha_pll_calibrate(struct clk_hw *hw)
+{
+ unsigned long calibration_freq, freq_hz;
+ struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
+ const struct pll_vco *vco;
+ u64 a;
+ u32 l;
+ int rc;
+
+ vco = alpha_pll_find_vco(pll, clk_hw_get_rate(hw));
+ if (!vco) {
+ pr_err("alpha pll: not in a valid vco range\n");
+ return -EINVAL;
+ }
+
+ /*
+ * As during slewing plls vco_sel won't be allowed to change, vco table
+ * should have only one entry table, i.e. index = 0, find the
+ * calibration frequency.
+ */
+ calibration_freq = (pll->vco_table[0].min_freq +
+ pll->vco_table[0].max_freq)/2;
+
+ freq_hz = alpha_pll_round_rate(calibration_freq,
+ clk_hw_get_rate(clk_hw_get_parent(hw)), &l, &a);
+ if (freq_hz != calibration_freq) {
+ pr_err("alpha_pll: call clk_set_rate with rounded rates!\n");
+ return -EINVAL;
+ }
+
+ /* Setup PLL for calibration frequency */
+ a <<= (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH);
+
+ regmap_write(pll->clkr.regmap, pll->offset + PLL_L_VAL, l);
+ regmap_write(pll->clkr.regmap, pll->offset + PLL_ALPHA_VAL, a);
+ regmap_write(pll->clkr.regmap, pll->offset + PLL_ALPHA_VAL_U, a >> 32);
+
+ regmap_update_bits(pll->clkr.regmap, pll->offset + PLL_USER_CTL,
+ PLL_VCO_MASK << PLL_VCO_SHIFT,
+ vco->val << PLL_VCO_SHIFT);
+
+ regmap_update_bits(pll->clkr.regmap, pll->offset + PLL_USER_CTL,
+ PLL_ALPHA_EN, PLL_ALPHA_EN);
+
+ /* Bringup the pll at calibration frequency */
+ rc = clk_alpha_pll_enable(hw);
+ if (rc) {
+ pr_err("alpha pll calibration failed\n");
+ return rc;
+ }
+
+ /*
+ * PLL is already running at calibration frequency.
+ * So slew pll to the previously set frequency.
+ */
+ freq_hz = alpha_pll_round_rate(clk_hw_get_rate(hw),
+ clk_hw_get_rate(clk_hw_get_parent(hw)), &l, &a);
+
+ pr_debug("pll %s: setting back to required rate %lu, freq_hz %ld\n",
+ hw->init->name, clk_hw_get_rate(hw), freq_hz);
+
+ /* Setup the PLL for the new frequency */
+ a <<= (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH);
+
+ regmap_write(pll->clkr.regmap, pll->offset + PLL_L_VAL, l);
+ regmap_write(pll->clkr.regmap, pll->offset + PLL_ALPHA_VAL, a);
+ regmap_write(pll->clkr.regmap, pll->offset + PLL_ALPHA_VAL_U, a >> 32);
+
+ regmap_update_bits(pll->clkr.regmap, pll->offset + PLL_USER_CTL,
+ PLL_ALPHA_EN, PLL_ALPHA_EN);
+
+ return clk_alpha_pll_slew_update(pll);
+}
+
+static int clk_alpha_pll_slew_enable(struct clk_hw *hw)
+{
+ int rc;
+
+ rc = clk_alpha_pll_calibrate(hw);
+ if (rc)
+ return rc;
+
+ rc = clk_alpha_pll_enable(hw);
+
+ return rc;
+}
+
+const struct clk_ops clk_alpha_pll_slew_ops = {
+ .enable = clk_alpha_pll_slew_enable,
+ .disable = clk_alpha_pll_disable,
+ .recalc_rate = clk_alpha_pll_recalc_rate,
+ .round_rate = clk_alpha_pll_round_rate,
+ .set_rate = clk_alpha_pll_slew_set_rate,
+};
+EXPORT_SYMBOL_GPL(clk_alpha_pll_slew_ops);
diff --git a/drivers/clk/qcom/clk-alpha-pll.h b/drivers/clk/qcom/clk-alpha-pll.h
index 9b1d3ee61cac..dd92bc340f8a 100644
--- a/drivers/clk/qcom/clk-alpha-pll.h
+++ b/drivers/clk/qcom/clk-alpha-pll.h
@@ -80,6 +80,7 @@ struct clk_alpha_pll_postdiv {
extern const struct clk_ops clk_alpha_pll_ops;
extern const struct clk_ops clk_alpha_pll_hwfsm_ops;
extern const struct clk_ops clk_alpha_pll_postdiv_ops;
+extern const struct clk_ops clk_alpha_pll_slew_ops;
void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap,
const struct pll_config *config);