Merge tag 'iwlwifi-for-john-2014-10-23' of git://git.kernel.org/pub/scm/linux/kernel...
[cascardo/linux.git] / drivers / clk / qcom / clk-pll.c
index 9db03d3..b823bc3 100644 (file)
@@ -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, unsigned long parent_rate)
                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);