From fd06a2cc719296f65a280cb1533b125f63cfcb34 Mon Sep 17 00:00:00 2001
From: Stephen Boyd <sboyd@codeaurora.org>
Date: Mon, 28 Apr 2014 15:58:11 -0700
Subject: [PATCH 127/182] clk: qcom: Add support for setting rates on PLLs

Some PLLs may require changing their rate at runtime. Add support
for these PLLs.

Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
---
 drivers/clk/qcom/clk-pll.c |   68 +++++++++++++++++++++++++++++++++++++++++++-
 drivers/clk/qcom/clk-pll.h |   20 +++++++++++++
 2 files changed, 87 insertions(+), 1 deletion(-)

--- a/drivers/clk/qcom/clk-pll.c
+++ b/drivers/clk/qcom/clk-pll.c
@@ -97,7 +97,7 @@ 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;
+	u32 l, m, n, config;
 	unsigned long rate;
 	u64 tmp;
 
@@ -116,13 +116,79 @@ clk_pll_recalc_rate(struct clk_hw *hw, u
 		do_div(tmp, n);
 		rate += tmp;
 	}
+	if (pll->post_div_width) {
+		regmap_read(pll->clkr.regmap, pll->config_reg, &config);
+		config >>= pll->post_div_shift;
+		config &= BIT(pll->post_div_width) - 1;
+		rate /= config + 1;
+	}
+
 	return rate;
 }
 
+static const
+struct pll_freq_tbl *find_freq(const struct pll_freq_tbl *f, unsigned long rate)
+{
+	if (!f)
+		return NULL;
+
+	for (; f->freq; f++)
+		if (rate <= f->freq)
+			return f;
+
+	return NULL;
+}
+
+static long
+clk_pll_determine_rate(struct clk_hw *hw, unsigned long rate,
+		       unsigned long *p_rate, struct clk **p)
+{
+	struct clk_pll *pll = to_clk_pll(hw);
+	const struct pll_freq_tbl *f;
+
+	f = find_freq(pll->freq_tbl, rate);
+	if (!f)
+		return clk_pll_recalc_rate(hw, *p_rate);
+
+	return f->freq;
+}
+
+static int
+clk_pll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long p_rate)
+{
+	struct clk_pll *pll = to_clk_pll(hw);
+	const struct pll_freq_tbl *f;
+	bool enabled;
+	u32 mode;
+	u32 enable_mask = PLL_OUTCTRL | PLL_BYPASSNL | PLL_RESET_N;
+
+	f = find_freq(pll->freq_tbl, rate);
+	if (!f)
+		return -EINVAL;
+
+	regmap_read(pll->clkr.regmap, pll->mode_reg, &mode);
+	enabled = (mode & enable_mask) == enable_mask;
+
+	if (enabled)
+		clk_pll_disable(hw);
+
+	regmap_update_bits(pll->clkr.regmap, pll->l_reg, 0x3ff, f->l);
+	regmap_update_bits(pll->clkr.regmap, pll->m_reg, 0x7ffff, f->m);
+	regmap_update_bits(pll->clkr.regmap, pll->n_reg, 0x7ffff, f->n);
+	regmap_write(pll->clkr.regmap, pll->config_reg, f->ibits);
+
+	if (enabled)
+		clk_pll_enable(hw);
+
+	return 0;
+}
+
 const struct clk_ops clk_pll_ops = {
 	.enable = clk_pll_enable,
 	.disable = clk_pll_disable,
 	.recalc_rate = clk_pll_recalc_rate,
+	.determine_rate = clk_pll_determine_rate,
+	.set_rate = clk_pll_set_rate,
 };
 EXPORT_SYMBOL_GPL(clk_pll_ops);
 
--- a/drivers/clk/qcom/clk-pll.h
+++ b/drivers/clk/qcom/clk-pll.h
@@ -18,6 +18,21 @@
 #include "clk-regmap.h"
 
 /**
+ * struct pll_freq_tbl - PLL frequency table
+ * @l: L value
+ * @m: M value
+ * @n: N value
+ * @ibits: internal values
+ */
+struct pll_freq_tbl {
+	unsigned long freq;
+	u16 l;
+	u16 m;
+	u16 n;
+	u32 ibits;
+};
+
+/**
  * struct clk_pll - phase locked loop (PLL)
  * @l_reg: L register
  * @m_reg: M register
@@ -26,6 +41,7 @@
  * @mode_reg: mode register
  * @status_reg: status register
  * @status_bit: ANDed with @status_reg to determine if PLL is enabled
+ * @freq_tbl: PLL frequency table
  * @hw: handle between common and hardware-specific interfaces
  */
 struct clk_pll {
@@ -36,6 +52,10 @@ struct clk_pll {
 	u32	mode_reg;
 	u32	status_reg;
 	u8	status_bit;
+	u8	post_div_width;
+	u8	post_div_shift;
+
+	const struct pll_freq_tbl *freq_tbl;
 
 	struct clk_regmap clkr;
 };
