ARM: tegra2: clock: Dynamic rate configuration
Shashank Sharma [Thu, 15 Mar 2012 15:31:02 +0000 (20:31 +0530)]
support dynamic clock rate configuration for pll_d. Till now tegra2
used to look into a pll_d frequency table to match input and output
frequencies, resulting fixed pll_d output frequencies. Whereas
tegra3 had code to configure pll_d for any desired rate using
dynamically generated m,n,p values.

Bug: 931908

Change-Id: I15322e2e4ac0aba58502575cdc83ca4a4542d1e4
Signed-off-by: Shashank Sharma <shashanks@nvidia.com>
Reviewed-on: http://git-master/r/90361
Reviewed-by: Kiran Adduri <kadduri@nvidia.com>
Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>

arch/arm/mach-tegra/tegra2_clocks.c

index e1c5f1e..126a1d5 100644 (file)
@@ -6,7 +6,7 @@
  * Author:
  *     Colin Cross <ccross@google.com>
  *
- * Copyright (C) 2010-2011 NVIDIA Corporation
+ * Copyright (C) 2010-2012 NVIDIA Corporation
  *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
@@ -156,6 +156,7 @@ static void __iomem *reg_pmc_base = IO_ADDRESS(TEGRA_PMC_BASE);
 static void __iomem *misc_gp_hidrev_base = IO_ADDRESS(TEGRA_APB_MISC_BASE);
 
 #define MISC_GP_HIDREV                 0x804
+#define PLLDU_LFCON_SET_DIVN           600
 
 static int tegra2_clk_shared_bus_update(struct clk *bus);
 
@@ -748,6 +749,8 @@ static void tegra2_pll_clk_disable(struct clk *c)
 static int tegra2_pll_clk_set_rate(struct clk *c, unsigned long rate)
 {
        u32 val;
+       u32 p_div = 0;
+       u32 old_base = 0;
        unsigned long input_rate;
        const struct clk_pll_freq_table *sel;
 
@@ -756,52 +759,119 @@ static int tegra2_pll_clk_set_rate(struct clk *c, unsigned long rate)
        input_rate = clk_get_rate(c->parent);
        for (sel = c->u.pll.freq_table; sel->input_rate != 0; sel++) {
                if (sel->input_rate == input_rate && sel->output_rate == rate) {
-                       c->mul = sel->n;
-                       c->div = sel->m * sel->p;
-
-                       val = clk_readl(c->reg + PLL_BASE);
-                       if (c->flags & PLL_FIXED)
-                               val |= PLL_BASE_OVERRIDE;
-                       val &= ~(PLL_BASE_DIVP_MASK | PLL_BASE_DIVN_MASK |
-                                PLL_BASE_DIVM_MASK);
-                       val |= (sel->m << PLL_BASE_DIVM_SHIFT) |
-                               (sel->n << PLL_BASE_DIVN_SHIFT);
-                       BUG_ON(sel->p < 1 || sel->p > 128);
                        if (c->flags & PLLU) {
+                               BUG_ON(sel->p < 1 || sel->p > 2);
                                if (sel->p == 1)
-                                       val |= PLLU_BASE_POST_DIV;
+                                       p_div = PLLU_BASE_POST_DIV;
                        } else {
-                               if (sel->p == 2)
-                                       val |= 1 << PLL_BASE_DIVP_SHIFT;
-                               else if (sel->p == 4)
-                                       val |= 2 << PLL_BASE_DIVP_SHIFT;
-                               else if (sel->p == 8)
-                                       val |= 3 << PLL_BASE_DIVP_SHIFT;
-                               else if (sel->p == 16)
-                                       val |= 4 << PLL_BASE_DIVP_SHIFT;
-                               else if (sel->p == 32)
-                                       val |= 5 << PLL_BASE_DIVP_SHIFT;
-                               else if (sel->p == 64)
-                                       val |= 6 << PLL_BASE_DIVP_SHIFT;
-                               else if (sel->p == 128)
-                                       val |= 7 << PLL_BASE_DIVP_SHIFT;
+                               BUG_ON(sel->p < 1);
+                               for (val = sel->p;
+                                       val > 1; val >>= 1, p_div++)
+                                               ;
+                               p_div <<= PLL_BASE_DIVP_SHIFT;
                        }
-                       clk_writel(val, c->reg + PLL_BASE);
+                       break;
+               }
+       }
 
-                       if (c->flags & PLL_HAS_CPCON) {
-                               val = clk_readl(c->reg + PLL_MISC(c));
-                               val &= ~PLL_MISC_CPCON_MASK;
-                               val |= sel->cpcon << PLL_MISC_CPCON_SHIFT;
-                               clk_writel(val, c->reg + PLL_MISC(c));
+       /*If required rate is not available in pll's frequency table, prepare
+       parameters manually */
+
+       if (sel->input_rate == 0) {
+               unsigned long cfreq;
+               BUG_ON(c->flags & PLLU);
+               struct clk_pll_freq_table cfg;
+               sel = &cfg;
+
+               switch (input_rate) {
+               case 12000000:
+               case 26000000:
+                       cfreq = (rate <= 1000000 * 1000) ? 1000000 : 2000000;
+                       break;
+               case 13000000:
+                       cfreq = (rate <= 1000000 * 1000) ? 1000000 : 2600000;
+                       break;
+               case 16800000:
+               case 19200000:
+                       cfreq = (rate <= 1200000 * 1000) ? 1200000 : 2400000;
+                       break;
+               default:
+                       if (c->parent->flags & DIV_U71_FIXED) {
+                               /* PLLP_OUT1 rate is not in PLLA table */
+                               pr_warn("%s: failed %s ref/out rates %lu/%lu\n",
+                                       __func__, c->name, input_rate, rate);
+                               cfreq = input_rate/(input_rate/1000000);
+                               break;
                        }
+                       pr_err("%s: Unexpected reference rate %lu\n",
+                              __func__, input_rate);
+                       BUG();
+               }
 
-                       if (c->state == ON)
-                               tegra2_pll_clk_enable(c);
+               /* Raise VCO to guarantee 0.5% accuracy */
+               for (cfg.output_rate = rate;
+                       cfg.output_rate < 200 * cfreq;
+                       cfg.output_rate <<= 1, p_div++)
+                               ;
+
+               cfg.p = 0x1 << p_div;
+               cfg.m = input_rate / cfreq;
+               cfg.n = cfg.output_rate / cfreq;
+               cfg.cpcon = 0x08; /* OUT_OF_TABLE_CPCON */
+
+               if ((cfg.m > (PLL_BASE_DIVM_MASK >> PLL_BASE_DIVM_SHIFT)) ||
+                   (cfg.n > (PLL_BASE_DIVN_MASK >> PLL_BASE_DIVN_SHIFT)) ||
+                   (p_div > (PLL_BASE_DIVP_MASK >> PLL_BASE_DIVP_SHIFT)) ||
+                   (cfg.output_rate > c->u.pll.vco_max)) {
+                       pr_err("%s: Failed to set %s out-of-table rate %lu\n",
+                              __func__, c->name, rate);
+                       return -EINVAL;
+               }
+               p_div <<= PLL_BASE_DIVP_SHIFT;
+       }
 
-                       return 0;
+       /*Setup multipliers and divisors, then setup rate*/
+
+       c->mul = sel->n;
+       c->div = sel->m * sel->p;
+
+       old_base = val = clk_readl(c->reg + PLL_BASE);
+       if (c->flags & PLL_FIXED)
+               val |= PLL_BASE_OVERRIDE;
+       val &= ~(PLL_BASE_DIVM_MASK | PLL_BASE_DIVN_MASK |
+                ((c->flags & PLLU) ? PLLU_BASE_POST_DIV : PLL_BASE_DIVP_MASK));
+       val |= (sel->m << PLL_BASE_DIVM_SHIFT) |
+               (sel->n << PLL_BASE_DIVN_SHIFT) | p_div;
+       if (val == old_base)
+               return 0;
+
+       if (c->state == ON) {
+               tegra2_pll_clk_disable(c);
+               val &= ~(PLL_BASE_BYPASS | PLL_BASE_ENABLE);
+       }
+       clk_writel(val, c->reg + PLL_BASE);
+
+       if (c->flags & PLL_HAS_CPCON) {
+               val = clk_readl(c->reg + PLL_MISC(c));
+               val &= ~PLL_MISC_CPCON_MASK;
+               val |= sel->cpcon << PLL_MISC_CPCON_SHIFT;
+               if (c->flags & (PLLU | PLLD)) {
+                       val &= ~PLL_MISC_LFCON_MASK;
+                       if (sel->n >= PLLDU_LFCON_SET_DIVN)
+                               val |= 0x1 << PLL_MISC_LFCON_SHIFT;
+               } else if (c->flags & (PLLX | PLLM)) {
+                       val &= ~(0x1 << PLL_MISC_DCCON_SHIFT);
+                       if (rate >= (c->u.pll.vco_max >> 1))
+                               val |= 0x1 << PLL_MISC_DCCON_SHIFT;
                }
+               clk_writel(val, c->reg + PLL_MISC(c));
        }
-       return -EINVAL;
+
+       if (c->state == ON)
+               tegra2_pll_clk_enable(c);
+
+       return 0;
+
 }
 
 static struct clk_ops tegra_pll_ops = {