davinci: clock: add support for setting sysclk rate
authorSekhar Nori <nsekhar@ti.com>
Tue, 20 Jul 2010 11:16:49 +0000 (16:46 +0530)
committerKevin Hilman <khilman@deeprootsystems.com>
Fri, 24 Sep 2010 14:40:24 +0000 (07:40 -0700)
Setting sysclk rate will be useful in cases where the
sysclk is not at a fixed ratio to the PLL output but
can asynchronously be changed.

This support forms the basis of attempt to keep the AEMIF
clock constant on OMAP-L138 even as PLL0 output changes
as ARM clock is changed to save power.

This patch has been tested on OMAP-L138.

Signed-off-by: Sekhar Nori <nsekhar@ti.com>
Signed-off-by: Kevin Hilman <khilman@deeprootsystems.com>
arch/arm/mach-davinci/clock.c
arch/arm/mach-davinci/clock.h

index 5b0cb62a058fb8fffc494934d5f0bbb0159a569a..01ba080433db556d0364cadb791303d2433b69a3 100644 (file)
@@ -287,6 +287,79 @@ static unsigned long clk_sysclk_recalc(struct clk *clk)
        return rate;
 }
 
+int davinci_set_sysclk_rate(struct clk *clk, unsigned long rate)
+{
+       unsigned v;
+       struct pll_data *pll;
+       unsigned long input;
+       unsigned ratio = 0;
+
+       /* If this is the PLL base clock, wrong function to call */
+       if (clk->pll_data)
+               return -EINVAL;
+
+       /* There must be a parent... */
+       if (WARN_ON(!clk->parent))
+               return -EINVAL;
+
+       /* ... the parent must be a PLL... */
+       if (WARN_ON(!clk->parent->pll_data))
+               return -EINVAL;
+
+       /* ... and this clock must have a divider. */
+       if (WARN_ON(!clk->div_reg))
+               return -EINVAL;
+
+       pll = clk->parent->pll_data;
+
+       input = clk->parent->rate;
+
+       /* If pre-PLL, source clock is before the multiplier and divider(s) */
+       if (clk->flags & PRE_PLL)
+               input = pll->input_rate;
+
+       if (input > rate) {
+               /*
+                * Can afford to provide an output little higher than requested
+                * only if maximum rate supported by hardware on this sysclk
+                * is known.
+                */
+               if (clk->maxrate) {
+                       ratio = DIV_ROUND_CLOSEST(input, rate);
+                       if (input / ratio > clk->maxrate)
+                               ratio = 0;
+               }
+
+               if (ratio == 0)
+                       ratio = DIV_ROUND_UP(input, rate);
+
+               ratio--;
+       }
+
+       if (ratio > PLLDIV_RATIO_MASK)
+               return -EINVAL;
+
+       do {
+               v = __raw_readl(pll->base + PLLSTAT);
+       } while (v & PLLSTAT_GOSTAT);
+
+       v = __raw_readl(pll->base + clk->div_reg);
+       v &= ~PLLDIV_RATIO_MASK;
+       v |= ratio | PLLDIV_EN;
+       __raw_writel(v, pll->base + clk->div_reg);
+
+       v = __raw_readl(pll->base + PLLCMD);
+       v |= PLLCMD_GOSET;
+       __raw_writel(v, pll->base + PLLCMD);
+
+       do {
+               v = __raw_readl(pll->base + PLLSTAT);
+       } while (v & PLLSTAT_GOSTAT);
+
+       return 0;
+}
+EXPORT_SYMBOL(davinci_set_sysclk_rate);
+
 static unsigned long clk_leafclk_recalc(struct clk *clk)
 {
        if (WARN_ON(!clk->parent))
index 01e36483ac3d0b4e5e32b768f556982ec20dd713..11099980b58bdfc2342ba67a8ccd373f07fb4fdf 100644 (file)
@@ -70,6 +70,9 @@
 #include <linux/list.h>
 #include <asm/clkdev.h>
 
+#define PLLSTAT_GOSTAT BIT(0)
+#define PLLCMD_GOSET   BIT(0)
+
 struct pll_data {
        u32 phys_base;
        void __iomem *base;
@@ -86,6 +89,7 @@ struct clk {
        struct module           *owner;
        const char              *name;
        unsigned long           rate;
+       unsigned long           maxrate;        /* H/W supported max rate */
        u8                      usecount;
        u8                      lpsc;
        u8                      gpsc;
@@ -118,6 +122,7 @@ struct clk {
 int davinci_clk_init(struct clk_lookup *clocks);
 int davinci_set_pllrate(struct pll_data *pll, unsigned int prediv,
                                unsigned int mult, unsigned int postdiv);
+int davinci_set_sysclk_rate(struct clk *clk, unsigned long rate);
 
 extern struct platform_device davinci_wdt_device;
 extern void davinci_watchdog_reset(struct platform_device *);