clk: ti: Implement FAPLL set_rate for the synthesizer
authorTony Lindgren <tony@atomide.com>
Tue, 17 Mar 2015 01:04:20 +0000 (18:04 -0700)
committerTero Kristo <t-kristo@ti.com>
Tue, 24 Mar 2015 18:26:05 +0000 (20:26 +0200)
We can pretty much get any rate out of the FAPLL because of the fractional
divider. Let's first try just adjusting the post divider, and if that is
not enough, then reprogram both the fractional divider and the post divider.

Let's also add a define for the fixed SYNTH_PHASE_K instead of using 8.

Cc: Brian Hutchinson <b.hutchman@gmail.com>
Cc: Matthijs van Duin <matthijsvanduin@gmail.com>
Cc: Tero Kristo <t-kristo@ti.com>
Signed-off-by: Tony Lindgren <tony@atomide.com>
Signed-off-by: Tero Kristo <t-kristo@ti.com>
drivers/clk/ti/fapll.c

index 97138c1..fc06abe 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/clk-provider.h>
 #include <linux/delay.h>
 #include <linux/err.h>
+#include <linux/math64.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/clk/ti.h>
@@ -47,6 +48,8 @@
 /* Synthesizer frequency register */
 #define SYNTH_LDFREQ           BIT(31)
 
+#define SYNTH_PHASE_K          8
+#define SYNTH_MAX_INT_DIV      0xf
 #define SYNTH_MAX_DIV_M                0xff
 
 struct fapll_data {
@@ -204,7 +207,7 @@ static unsigned long ti_fapll_synth_recalc_rate(struct clk_hw *hw,
        /*
         * Synth frequency integer and fractional divider.
         * Note that the phase output K is 8, so the result needs
-        * to be multiplied by 8.
+        * to be multiplied by SYNTH_PHASE_K.
         */
        if (synth->freq) {
                u32 v, synth_int_div, synth_frac_div, synth_div_freq;
@@ -215,7 +218,7 @@ static unsigned long ti_fapll_synth_recalc_rate(struct clk_hw *hw,
                synth_div_freq = (synth_int_div * 10000000) + synth_frac_div;
                rate *= 10000000;
                do_div(rate, synth_div_freq);
-               rate *= 8;
+               rate *= SYNTH_PHASE_K;
        }
 
        /* Synth post-divider M */
@@ -224,11 +227,138 @@ static unsigned long ti_fapll_synth_recalc_rate(struct clk_hw *hw,
        return DIV_ROUND_UP_ULL(rate, synth_div_m);
 }
 
+static unsigned long ti_fapll_synth_get_frac_rate(struct clk_hw *hw,
+                                                 unsigned long parent_rate)
+{
+       struct fapll_synth *synth = to_synth(hw);
+       unsigned long current_rate, frac_rate;
+       u32 post_div_m;
+
+       current_rate = ti_fapll_synth_recalc_rate(hw, parent_rate);
+       post_div_m = readl_relaxed(synth->div) & SYNTH_MAX_DIV_M;
+       frac_rate = current_rate * post_div_m;
+
+       return frac_rate;
+}
+
+static u32 ti_fapll_synth_set_frac_rate(struct fapll_synth *synth,
+                                       unsigned long rate,
+                                       unsigned long parent_rate)
+{
+       u32 post_div_m, synth_int_div = 0, synth_frac_div = 0, v;
+
+       post_div_m = DIV_ROUND_UP_ULL((u64)parent_rate * SYNTH_PHASE_K, rate);
+       post_div_m = post_div_m / SYNTH_MAX_INT_DIV;
+       if (post_div_m > SYNTH_MAX_DIV_M)
+               return -EINVAL;
+       if (!post_div_m)
+               post_div_m = 1;
+
+       for (; post_div_m < SYNTH_MAX_DIV_M; post_div_m++) {
+               synth_int_div = DIV_ROUND_UP_ULL((u64)parent_rate *
+                                                SYNTH_PHASE_K *
+                                                10000000,
+                                                rate * post_div_m);
+               synth_frac_div = synth_int_div % 10000000;
+               synth_int_div /= 10000000;
+
+               if (synth_int_div <= SYNTH_MAX_INT_DIV)
+                       break;
+       }
+
+       if (synth_int_div > SYNTH_MAX_INT_DIV)
+               return -EINVAL;
+
+       v = readl_relaxed(synth->freq);
+       v &= ~0x1fffffff;
+       v |= (synth_int_div & SYNTH_MAX_INT_DIV) << 24;
+       v |= (synth_frac_div & 0xffffff);
+       v |= SYNTH_LDFREQ;
+       writel_relaxed(v, synth->freq);
+
+       return post_div_m;
+}
+
+static long ti_fapll_synth_round_rate(struct clk_hw *hw, unsigned long rate,
+                                     unsigned long *parent_rate)
+{
+       struct fapll_synth *synth = to_synth(hw);
+       struct fapll_data *fd = synth->fd;
+       unsigned long r;
+
+       if (ti_fapll_clock_is_bypass(fd) || !synth->div || !rate)
+               return -EINVAL;
+
+       /* Only post divider m available with no fractional divider? */
+       if (!synth->freq) {
+               unsigned long frac_rate;
+               u32 synth_post_div_m;
+
+               frac_rate = ti_fapll_synth_get_frac_rate(hw, *parent_rate);
+               synth_post_div_m = DIV_ROUND_UP(frac_rate, rate);
+               r = DIV_ROUND_UP(frac_rate, synth_post_div_m);
+               goto out;
+       }
+
+       r = *parent_rate * SYNTH_PHASE_K;
+       if (rate > r)
+               goto out;
+
+       r = DIV_ROUND_UP_ULL(r, SYNTH_MAX_INT_DIV * SYNTH_MAX_DIV_M);
+       if (rate < r)
+               goto out;
+
+       r = rate;
+out:
+       return r;
+}
+
+static int ti_fapll_synth_set_rate(struct clk_hw *hw, unsigned long rate,
+                                  unsigned long parent_rate)
+{
+       struct fapll_synth *synth = to_synth(hw);
+       struct fapll_data *fd = synth->fd;
+       unsigned long frac_rate, post_rate = 0;
+       u32 post_div_m = 0, v;
+
+       if (ti_fapll_clock_is_bypass(fd) || !synth->div || !rate)
+               return -EINVAL;
+
+       /* Produce the rate with just post divider M? */
+       frac_rate = ti_fapll_synth_get_frac_rate(hw, parent_rate);
+       if (frac_rate < rate) {
+               if (!synth->freq)
+                       return -EINVAL;
+       } else {
+               post_div_m = DIV_ROUND_UP(frac_rate, rate);
+               if (post_div_m && (post_div_m <= SYNTH_MAX_DIV_M))
+                       post_rate = DIV_ROUND_UP(frac_rate, post_div_m);
+               if (!synth->freq && !post_rate)
+                       return -EINVAL;
+       }
+
+       /* Need to recalculate the fractional divider? */
+       if ((post_rate != rate) && synth->freq)
+               post_div_m = ti_fapll_synth_set_frac_rate(synth,
+                                                         rate,
+                                                         parent_rate);
+
+       v = readl_relaxed(synth->div);
+       v &= ~SYNTH_MAX_DIV_M;
+       v |= post_div_m;
+       v |= SYNTH_LDMDIV1;
+       writel_relaxed(v, synth->div);
+
+       return 0;
+}
+
 static struct clk_ops ti_fapll_synt_ops = {
        .enable = ti_fapll_synth_enable,
        .disable = ti_fapll_synth_disable,
        .is_enabled = ti_fapll_synth_is_enabled,
        .recalc_rate = ti_fapll_synth_recalc_rate,
+       .round_rate = ti_fapll_synth_round_rate,
+       .set_rate = ti_fapll_synth_set_rate,
 };
 
 static struct clk * __init ti_fapll_synth_setup(struct fapll_data *fd,