Logo Search packages:      
Sourcecode: linux version File versions  Download package

clock.c

/*
 * Clock manipulation routines for Freescale STMP37XX/STMP378X
 *
 * Author: Vitaly Wool <vital@embeddedalley.com>
 *
 * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved.
 * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */
#define DEBUG
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/clk.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/clkdev.h>

#include <asm/mach-types.h>
#include <mach/platform.h>
#include <mach/regs-clkctrl.h>

#include "clock.h"

static DEFINE_SPINLOCK(clocks_lock);

static struct clk osc_24M;
static struct clk pll_clk;
static struct clk cpu_clk;
static struct clk hclk;

static int propagate_rate(struct clk *);

static inline int clk_is_busy(struct clk *clk)
{
      return __raw_readl(clk->busy_reg) & (1 << clk->busy_bit);
}

static inline int clk_good(struct clk *clk)
{
      return clk && !IS_ERR(clk) && clk->ops;
}

static int std_clk_enable(struct clk *clk)
{
      if (clk->enable_reg) {
            u32 clk_reg = __raw_readl(clk->enable_reg);
            if (clk->enable_negate)
                  clk_reg &= ~(1 << clk->enable_shift);
            else
                  clk_reg |= (1 << clk->enable_shift);
            __raw_writel(clk_reg, clk->enable_reg);
            if (clk->enable_wait)
                  udelay(clk->enable_wait);
            return 0;
      } else
            return -EINVAL;
}

static int std_clk_disable(struct clk *clk)
{
      if (clk->enable_reg) {
            u32 clk_reg = __raw_readl(clk->enable_reg);
            if (clk->enable_negate)
                  clk_reg |= (1 << clk->enable_shift);
            else
                  clk_reg &= ~(1 << clk->enable_shift);
            __raw_writel(clk_reg, clk->enable_reg);
            return 0;
      } else
            return -EINVAL;
}

static int io_set_rate(struct clk *clk, u32 rate)
{
      u32 reg_frac, clkctrl_frac;
      int i, ret = 0, mask = 0x1f;

      clkctrl_frac = (clk->parent->rate * 18 + rate - 1) / rate;

      if (clkctrl_frac < 18 || clkctrl_frac > 35) {
            ret = -EINVAL;
            goto out;
      }

      reg_frac = __raw_readl(clk->scale_reg);
      reg_frac &= ~(mask << clk->scale_shift);
      __raw_writel(reg_frac | (clkctrl_frac << clk->scale_shift),
                        clk->scale_reg);
      if (clk->busy_reg) {
            for (i = 10000; i; i--)
                  if (!clk_is_busy(clk))
                        break;
            if (!i)
                  ret = -ETIMEDOUT;
            else
                  ret = 0;
      }
out:
      return ret;
}

static long io_get_rate(struct clk *clk)
{
      long rate = clk->parent->rate * 18;
      int mask = 0x1f;

      rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask;
      clk->rate = rate;

      return rate;
}

static long per_get_rate(struct clk *clk)
{
      long rate = clk->parent->rate;
      long div;
      const int mask = 0xff;

      if (clk->enable_reg &&
                  !(__raw_readl(clk->enable_reg) & clk->enable_shift))
            clk->rate = 0;
      else {
            div = (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask;
            if (div)
                  rate /= div;
            clk->rate = rate;
      }

      return clk->rate;
}

static int per_set_rate(struct clk *clk, u32 rate)
{
      int ret = -EINVAL;
      int div = (clk->parent->rate + rate - 1) / rate;
      u32 reg_frac;
      const int mask = 0xff;
      int try = 10;
      int i = -1;

      if (div == 0 || div > mask)
            goto out;

      reg_frac = __raw_readl(clk->scale_reg);
      reg_frac &= ~(mask << clk->scale_shift);

      while (try--) {
            __raw_writel(reg_frac | (div << clk->scale_shift),
                        clk->scale_reg);

            if (clk->busy_reg) {
                  for (i = 10000; i; i--)
                        if (!clk_is_busy(clk))
                              break;
            }
            if (i)
                  break;
      }

      if (!i)
            ret = -ETIMEDOUT;
      else
            ret = 0;

out:
      if (ret != 0)
            printk(KERN_ERR "%s: error %d\n", __func__, ret);
      return ret;
}

static long lcdif_get_rate(struct clk *clk)
{
      long rate = clk->parent->rate;
      long div;
      const int mask = 0xff;

      div = (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask;
      if (div) {
            rate /= div;
            div = (__raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC) &
                  BM_CLKCTRL_FRAC_PIXFRAC) >> BP_CLKCTRL_FRAC_PIXFRAC;
            rate /= div;
      }
      clk->rate = rate;

      return rate;
}

static int lcdif_set_rate(struct clk *clk, u32 rate)
{
      int ret = 0;
      /*
       * On 3700, we can get most timings exact by modifying ref_pix
       * and the divider, but keeping the phase timings at 1 (2
       * phases per cycle).
       *
       * ref_pix can be between 480e6*18/35=246.9MHz and 480e6*18/18=480MHz,
       * which is between 18/(18*480e6)=2.084ns and 35/(18*480e6)=4.050ns.
       *
       * ns_cycle >= 2*18e3/(18*480) = 25/6
       * ns_cycle <= 2*35e3/(18*480) = 875/108
       *
       * Multiply the ns_cycle by 'div' to lengthen it until it fits the
       * bounds. This is the divider we'll use after ref_pix.
       *
       * 6 * ns_cycle >= 25 * div
       * 108 * ns_cycle <= 875 * div
       */
      u32 ns_cycle = 1000000 / rate;
      u32 div, reg_val;
      u32 lowest_result = (u32) -1;
      u32 lowest_div = 0, lowest_fracdiv = 0;

      for (div = 1; div < 256; ++div) {
            u32 fracdiv;
            u32 ps_result;
            int lower_bound = 6 * ns_cycle >= 25 * div;
            int upper_bound = 108 * ns_cycle <= 875 * div;
            if (!lower_bound)
                  break;
            if (!upper_bound)
                  continue;
            /*
             * Found a matching div. Calculate fractional divider needed,
             * rounded up.
             */
            fracdiv = ((clk->parent->rate / 1000 * 18 / 2) *
                        ns_cycle + 1000 * div - 1) /
                        (1000 * div);
            if (fracdiv < 18 || fracdiv > 35) {
                  ret = -EINVAL;
                  goto out;
            }
            /* Calculate the actual cycle time this results in */
            ps_result = 6250 * div * fracdiv / 27;

            /* Use the fastest result that doesn't break ns_cycle */
            if (ps_result <= lowest_result) {
                  lowest_result = ps_result;
                  lowest_div = div;
                  lowest_fracdiv = fracdiv;
            }
      }

      if (div >= 256 || lowest_result == (u32) -1) {
            ret = -EINVAL;
            goto out;
      }
      pr_debug("Programming PFD=%u,DIV=%u ref_pix=%uMHz "
                  "PIXCLK=%uMHz cycle=%u.%03uns\n",
                  lowest_fracdiv, lowest_div,
                  480*18/lowest_fracdiv, 480*18/lowest_fracdiv/lowest_div,
                  lowest_result / 1000, lowest_result % 1000);

      /* Program ref_pix phase fractional divider */
      reg_val = __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC);
      reg_val &= ~BM_CLKCTRL_FRAC_PIXFRAC;
      reg_val |= BF(lowest_fracdiv, CLKCTRL_FRAC_PIXFRAC);
      __raw_writel(reg_val, REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC);

      /* Ungate PFD */
      stmp3xxx_clearl(BM_CLKCTRL_FRAC_CLKGATEPIX,
                  REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC);

      /* Program pix divider */
      reg_val = __raw_readl(clk->scale_reg);
      reg_val &= ~(BM_CLKCTRL_PIX_DIV | BM_CLKCTRL_PIX_CLKGATE);
      reg_val |= BF(lowest_div, CLKCTRL_PIX_DIV);
      __raw_writel(reg_val, clk->scale_reg);

      /* Wait for divider update */
      if (clk->busy_reg) {
            int i;
            for (i = 10000; i; i--)
                  if (!clk_is_busy(clk))
                        break;
            if (!i) {
                  ret = -ETIMEDOUT;
                  goto out;
            }
      }

      /* Switch to ref_pix source */
      reg_val = __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ);
      reg_val &= ~BM_CLKCTRL_CLKSEQ_BYPASS_PIX;
      __raw_writel(reg_val, REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ);

out:
      return ret;
}


static int cpu_set_rate(struct clk *clk, u32 rate)
{
      u32 reg_val;

      if (rate < 24000)
            return -EINVAL;
      else if (rate == 24000) {
            /* switch to the 24M source */
            clk_set_parent(clk, &osc_24M);
      } else {
            int i;
            u32 clkctrl_cpu = 1;
            u32 c = clkctrl_cpu;
            u32 clkctrl_frac = 1;
            u32 val;
            for ( ; c < 0x40; c++) {
                  u32 f = (pll_clk.rate*18/c + rate/2) / rate;
                  int s1, s2;

                  if (f < 18 || f > 35)
                        continue;
                  s1 = pll_clk.rate*18/clkctrl_frac/clkctrl_cpu - rate;
                  s2 = pll_clk.rate*18/c/f - rate;
                  pr_debug("%s: s1 %d, s2 %d\n", __func__, s1, s2);
                  if (abs(s1) > abs(s2)) {
                        clkctrl_cpu = c;
                        clkctrl_frac = f;
                  }
                  if (s2 == 0)
                        break;
            };
            pr_debug("%s: clkctrl_cpu %d, clkctrl_frac %d\n", __func__,
                        clkctrl_cpu, clkctrl_frac);
            if (c == 0x40) {
                  int  d = pll_clk.rate*18/clkctrl_frac/clkctrl_cpu -
                        rate;
                  if (abs(d) > 100 ||
                      clkctrl_frac < 18 || clkctrl_frac > 35)
                        return -EINVAL;
            }

            /* 4.6.2 */
            val = __raw_readl(clk->scale_reg);
            val &= ~(0x3f << clk->scale_shift);
            val |= clkctrl_frac;
            clk_set_parent(clk, &osc_24M);
            udelay(10);
            __raw_writel(val, clk->scale_reg);
            /* ungate */
            __raw_writel(1<<7, clk->scale_reg + 8);
            /* write clkctrl_cpu */
            clk->saved_div = clkctrl_cpu;

            reg_val = __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU);
            reg_val &= ~0x3F;
            reg_val |= clkctrl_cpu;
            __raw_writel(reg_val, REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU);

            for (i = 10000; i; i--)
                  if (!clk_is_busy(clk))
                        break;
            if (!i) {
                  printk(KERN_ERR "couldn't set up CPU divisor\n");
                  return -ETIMEDOUT;
            }
            clk_set_parent(clk, &pll_clk);
            clk->saved_div = 0;
            udelay(10);
      }
      return 0;
}

static long cpu_get_rate(struct clk *clk)
{
      long rate = clk->parent->rate * 18;

      rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & 0x3f;
      rate /= __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU) & 0x3f;
      rate = ((rate + 9) / 10) * 10;
      clk->rate = rate;

      return rate;
}

static long cpu_round_rate(struct clk *clk, u32 rate)
{
      unsigned long r = 0;

      if (rate <= 24000)
            r = 24000;
      else {
            u32 clkctrl_cpu = 1;
            u32 clkctrl_frac;
            do {
                  clkctrl_frac =
                        (pll_clk.rate*18 / clkctrl_cpu + rate/2) / rate;
                  if (clkctrl_frac > 35)
                        continue;
                  if (pll_clk.rate*18 / clkctrl_frac / clkctrl_cpu/10 ==
                      rate / 10)
                        break;
            } while (pll_clk.rate / 2  >= clkctrl_cpu++ * rate);
            if (pll_clk.rate / 2 < (clkctrl_cpu - 1) * rate)
                  clkctrl_cpu--;
            pr_debug("%s: clkctrl_cpu %d, clkctrl_frac %d\n", __func__,
                        clkctrl_cpu, clkctrl_frac);
            if (clkctrl_frac < 18)
                  clkctrl_frac = 18;
            if (clkctrl_frac > 35)
                  clkctrl_frac = 35;

            r = pll_clk.rate * 18;
            r /= clkctrl_frac;
            r /= clkctrl_cpu;
            r = 10 * ((r + 9) / 10);
      }
      return r;
}

static long emi_get_rate(struct clk *clk)
{
      long rate = clk->parent->rate * 18;

      rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & 0x3f;
      rate /= __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_EMI) & 0x3f;
      clk->rate = rate;

      return rate;
}

static int clkseq_set_parent(struct clk *clk, struct clk *parent)
{
      int ret = -EINVAL;
      int shift = 8;

      /* bypass? */
      if (parent == &osc_24M)
            shift = 4;

      if (clk->bypass_reg) {
#ifdef CONFIG_ARCH_STMP378X
            u32 hbus_val, cpu_val;

            if (clk == &cpu_clk && shift == 4) {
                  hbus_val = __raw_readl(REGS_CLKCTRL_BASE +
                              HW_CLKCTRL_HBUS);
                  cpu_val = __raw_readl(REGS_CLKCTRL_BASE +
                              HW_CLKCTRL_CPU);

                  hbus_val &= ~(BM_CLKCTRL_HBUS_DIV_FRAC_EN |
                              BM_CLKCTRL_HBUS_DIV);
                  clk->saved_div = cpu_val & BM_CLKCTRL_CPU_DIV_CPU;
                  cpu_val &= ~BM_CLKCTRL_CPU_DIV_CPU;
                  cpu_val |= 1;

                  if (machine_is_stmp378x()) {
                        __raw_writel(hbus_val,
                              REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS);
                        __raw_writel(cpu_val,
                              REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU);
                        hclk.rate = 0;
                  }
            } else if (clk == &cpu_clk && shift == 8) {
                  hbus_val = __raw_readl(REGS_CLKCTRL_BASE +
                                          HW_CLKCTRL_HBUS);
                  cpu_val = __raw_readl(REGS_CLKCTRL_BASE +
                                          HW_CLKCTRL_CPU);
                  hbus_val &= ~(BM_CLKCTRL_HBUS_DIV_FRAC_EN |
                              BM_CLKCTRL_HBUS_DIV);
                  hbus_val |= 2;
                  cpu_val &= ~BM_CLKCTRL_CPU_DIV_CPU;
                  if (clk->saved_div)
                        cpu_val |= clk->saved_div;
                  else
                        cpu_val |= 2;

                  if (machine_is_stmp378x()) {
                        __raw_writel(hbus_val,
                              REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS);
                        __raw_writel(cpu_val,
                              REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU);
                        hclk.rate = 0;
                  }
            }
#endif
            __raw_writel(1 << clk->bypass_shift, clk->bypass_reg + shift);

            ret = 0;
      }

      return ret;
}

static int hbus_set_rate(struct clk *clk, u32 rate)
{
      u8 div = 0;
      int is_frac = 0;
      u32 clkctrl_hbus;
      struct clk *parent = clk->parent;

      pr_debug("%s: rate %d, parent rate %d\n", __func__, rate,
                  parent->rate);

      if (rate > parent->rate)
            return -EINVAL;

      if (((parent->rate + rate/2) / rate) * rate != parent->rate &&
          parent->rate / rate < 32) {
            pr_debug("%s: switching to fractional mode\n", __func__);
            is_frac = 1;
      }

      if (is_frac)
            div = (32 * rate + parent->rate / 2) / parent->rate;
      else
            div = (parent->rate + rate - 1) / rate;
      pr_debug("%s: div calculated is %d\n", __func__, div);
      if (!div || div > 0x1f)
            return -EINVAL;

      clk_set_parent(&cpu_clk, &osc_24M);
      udelay(10);
      clkctrl_hbus = __raw_readl(clk->scale_reg);
      clkctrl_hbus &= ~0x3f;
      clkctrl_hbus |= div;
      clkctrl_hbus |= (is_frac << 5);

      __raw_writel(clkctrl_hbus, clk->scale_reg);
      if (clk->busy_reg) {
            int i;
            for (i = 10000; i; i--)
                  if (!clk_is_busy(clk))
                        break;
            if (!i) {
                  printk(KERN_ERR "couldn't set up CPU divisor\n");
                  return -ETIMEDOUT;
            }
      }
      clk_set_parent(&cpu_clk, &pll_clk);
      __raw_writel(clkctrl_hbus, clk->scale_reg);
      udelay(10);
      return 0;
}

static long hbus_get_rate(struct clk *clk)
{
      long rate = clk->parent->rate;

      if (__raw_readl(clk->scale_reg) & 0x20) {
            rate *= __raw_readl(clk->scale_reg) & 0x1f;
            rate /= 32;
      } else
            rate /= __raw_readl(clk->scale_reg) & 0x1f;
      clk->rate = rate;

      return rate;
}

static int xbus_set_rate(struct clk *clk, u32 rate)
{
      u16 div = 0;
      u32 clkctrl_xbus;

      pr_debug("%s: rate %d, parent rate %d\n", __func__, rate,
                  clk->parent->rate);

      div = (clk->parent->rate + rate - 1) / rate;
      pr_debug("%s: div calculated is %d\n", __func__, div);
      if (!div || div > 0x3ff)
            return -EINVAL;

      clkctrl_xbus = __raw_readl(clk->scale_reg);
      clkctrl_xbus &= ~0x3ff;
      clkctrl_xbus |= div;
      __raw_writel(clkctrl_xbus, clk->scale_reg);
      if (clk->busy_reg) {
            int i;
            for (i = 10000; i; i--)
                  if (!clk_is_busy(clk))
                        break;
            if (!i) {
                  printk(KERN_ERR "couldn't set up xbus divisor\n");
                  return -ETIMEDOUT;
            }
      }
      return 0;
}

static long xbus_get_rate(struct clk *clk)
{
      long rate = clk->parent->rate;

      rate /= __raw_readl(clk->scale_reg) & 0x3ff;
      clk->rate = rate;

      return rate;
}


/* Clock ops */

static struct clk_ops std_ops = {
      .enable           = std_clk_enable,
      .disable    = std_clk_disable,
      .get_rate   = per_get_rate,
      .set_rate   = per_set_rate,
      .set_parent = clkseq_set_parent,
};

static struct clk_ops min_ops = {
      .enable           = std_clk_enable,
      .disable    = std_clk_disable,
};

static struct clk_ops cpu_ops = {
      .enable           = std_clk_enable,
      .disable    = std_clk_disable,
      .get_rate   = cpu_get_rate,
      .set_rate   = cpu_set_rate,
      .round_rate = cpu_round_rate,
      .set_parent = clkseq_set_parent,
};

static struct clk_ops io_ops = {
      .enable           = std_clk_enable,
      .disable    = std_clk_disable,
      .get_rate   = io_get_rate,
      .set_rate   = io_set_rate,
};

static struct clk_ops hbus_ops = {
      .get_rate   = hbus_get_rate,
      .set_rate   = hbus_set_rate,
};

static struct clk_ops xbus_ops = {
      .get_rate   = xbus_get_rate,
      .set_rate   = xbus_set_rate,
};

static struct clk_ops lcdif_ops = {
      .enable           = std_clk_enable,
      .disable    = std_clk_disable,
      .get_rate   = lcdif_get_rate,
      .set_rate   = lcdif_set_rate,
      .set_parent = clkseq_set_parent,
};

static struct clk_ops emi_ops = {
      .get_rate   = emi_get_rate,
};

/* List of on-chip clocks */

static struct clk osc_24M = {
      .flags            = FIXED_RATE | ENABLED,
      .rate       = 24000,
};

static struct clk pll_clk = {
      .parent           = &osc_24M,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL0,
      .enable_shift     = 16,
      .enable_wait      = 10,
      .flags            = FIXED_RATE | ENABLED,
      .rate       = 480000,
      .ops        = &min_ops,
};

static struct clk cpu_clk = {
      .parent           = &pll_clk,
      .scale_reg  = REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC,
      .scale_shift      = 0,
      .bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
      .bypass_shift     = 7,
      .busy_reg   = REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU,
      .busy_bit   = 28,
      .flags            = RATE_PROPAGATES | ENABLED,
      .ops        = &cpu_ops,
};

static struct clk io_clk = {
      .parent           = &pll_clk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC,
      .enable_shift     = 31,
      .enable_negate    = 1,
      .scale_reg  = REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC,
      .scale_shift      = 24,
      .flags            = RATE_PROPAGATES | ENABLED,
      .ops        = &io_ops,
};

static struct clk hclk = {
      .parent           = &cpu_clk,
      .scale_reg  = REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS,
      .bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
      .bypass_shift     = 7,
      .busy_reg   = REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS,
      .busy_bit   = 29,
      .flags            = RATE_PROPAGATES | ENABLED,
      .ops        = &hbus_ops,
};

static struct clk xclk = {
      .parent           = &osc_24M,
      .scale_reg  = REGS_CLKCTRL_BASE + HW_CLKCTRL_XBUS,
      .busy_reg   = REGS_CLKCTRL_BASE + HW_CLKCTRL_XBUS,
      .busy_bit   = 31,
      .flags            = RATE_PROPAGATES | ENABLED,
      .ops        = &xbus_ops,
};

static struct clk uart_clk = {
      .parent           = &xclk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
      .enable_shift     = 31,
      .enable_negate    = 1,
      .flags            = ENABLED,
      .ops        = &min_ops,
};

static struct clk audio_clk = {
      .parent           = &xclk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
      .enable_shift     = 30,
      .enable_negate    = 1,
      .ops        = &min_ops,
};

static struct clk pwm_clk = {
      .parent           = &xclk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
      .enable_shift     = 29,
      .enable_negate    = 1,
      .ops        = &min_ops,
};

static struct clk dri_clk = {
      .parent           = &xclk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
      .enable_shift     = 28,
      .enable_negate    = 1,
      .ops        = &min_ops,
};

static struct clk digctl_clk = {
      .parent           = &xclk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
      .enable_shift     = 27,
      .enable_negate    = 1,
      .ops        = &min_ops,
};

static struct clk timer_clk = {
      .parent           = &xclk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
      .enable_shift     = 26,
      .enable_negate    = 1,
      .flags            = ENABLED,
      .ops        = &min_ops,
};

static struct clk lcdif_clk = {
      .parent           = &pll_clk,
      .scale_reg  = REGS_CLKCTRL_BASE + HW_CLKCTRL_PIX,
      .busy_reg   = REGS_CLKCTRL_BASE + HW_CLKCTRL_PIX,
      .busy_bit   = 29,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_PIX,
      .enable_shift     = 31,
      .enable_negate    = 1,
      .bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
      .bypass_shift     = 1,
      .flags            = NEEDS_SET_PARENT,
      .ops        = &lcdif_ops,
};

static struct clk ssp_clk = {
      .parent           = &io_clk,
      .scale_reg  = REGS_CLKCTRL_BASE + HW_CLKCTRL_SSP,
      .busy_reg   = REGS_CLKCTRL_BASE + HW_CLKCTRL_SSP,
      .busy_bit   = 29,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SSP,
      .enable_shift     = 31,
      .bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
      .bypass_shift     = 5,
      .enable_negate    = 1,
      .flags            = NEEDS_SET_PARENT,
      .ops        = &std_ops,
};

static struct clk gpmi_clk = {
      .parent           = &io_clk,
      .scale_reg  = REGS_CLKCTRL_BASE + HW_CLKCTRL_GPMI,
      .busy_reg   = REGS_CLKCTRL_BASE + HW_CLKCTRL_GPMI,
      .busy_bit   = 29,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_GPMI,
      .enable_shift     = 31,
      .enable_negate    = 1,
      .bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
      .bypass_shift     = 4,
      .flags            = NEEDS_SET_PARENT,
      .ops        = &std_ops,
};

static struct clk spdif_clk = {
      .parent           = &pll_clk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SPDIF,
      .enable_shift     = 31,
      .enable_negate    = 1,
      .ops        = &min_ops,
};

static struct clk emi_clk = {
      .parent           = &pll_clk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_EMI,
      .enable_shift     = 31,
      .enable_negate    = 1,
      .scale_reg  = REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC,
      .scale_shift      = 8,
      .busy_reg   = REGS_CLKCTRL_BASE + HW_CLKCTRL_EMI,
      .busy_bit   = 28,
      .bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
      .bypass_shift     = 6,
      .flags            = ENABLED,
      .ops        = &emi_ops,
};

static struct clk ir_clk = {
      .parent           = &io_clk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_IR,
      .enable_shift     = 31,
      .enable_negate    = 1,
      .bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
      .bypass_shift     = 3,
      .ops        = &min_ops,
};

static struct clk saif_clk = {
      .parent           = &pll_clk,
      .scale_reg  = REGS_CLKCTRL_BASE + HW_CLKCTRL_SAIF,
      .busy_reg   = REGS_CLKCTRL_BASE + HW_CLKCTRL_SAIF,
      .busy_bit   = 29,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SAIF,
      .enable_shift     = 31,
      .enable_negate    = 1,
      .bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
      .bypass_shift     = 0,
      .ops        = &std_ops,
};

static struct clk usb_clk = {
      .parent           = &pll_clk,
      .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL0,
      .enable_shift     = 18,
      .enable_negate    = 1,
      .ops        = &min_ops,
};

/* list of all the clocks */
static struct clk_lookup onchip_clks[] = {
      {
            .con_id = "osc_24M",
            .clk = &osc_24M,
      }, {
            .con_id = "pll",
            .clk = &pll_clk,
      }, {
            .con_id = "cpu",
            .clk = &cpu_clk,
      }, {
            .con_id = "hclk",
            .clk = &hclk,
      }, {
            .con_id = "xclk",
            .clk = &xclk,
      }, {
            .con_id = "io",
            .clk = &io_clk,
      }, {
            .con_id = "uart",
            .clk = &uart_clk,
      }, {
            .con_id = "audio",
            .clk = &audio_clk,
      }, {
            .con_id = "pwm",
            .clk = &pwm_clk,
      }, {
            .con_id = "dri",
            .clk = &dri_clk,
      }, {
            .con_id = "digctl",
            .clk = &digctl_clk,
      }, {
            .con_id = "timer",
            .clk = &timer_clk,
      }, {
            .con_id = "lcdif",
            .clk = &lcdif_clk,
      }, {
            .con_id = "ssp",
            .clk = &ssp_clk,
      }, {
            .con_id = "gpmi",
            .clk = &gpmi_clk,
      }, {
            .con_id = "spdif",
            .clk = &spdif_clk,
      }, {
            .con_id = "emi",
            .clk = &emi_clk,
      }, {
            .con_id = "ir",
            .clk = &ir_clk,
      }, {
            .con_id = "saif",
            .clk = &saif_clk,
      }, {
            .con_id = "usb",
            .clk = &usb_clk,
      },
};

static int __init propagate_rate(struct clk *clk)
{
      struct clk_lookup *cl;

      for (cl = onchip_clks; cl < onchip_clks + ARRAY_SIZE(onchip_clks);
           cl++) {
            if (unlikely(!clk_good(cl->clk)))
                  continue;
            if (cl->clk->parent == clk && cl->clk->ops->get_rate) {
                  cl->clk->ops->get_rate(cl->clk);
                  if (cl->clk->flags & RATE_PROPAGATES)
                        propagate_rate(cl->clk);
            }
      }

      return 0;
}

/* Exported API */
unsigned long clk_get_rate(struct clk *clk)
{
      if (unlikely(!clk_good(clk)))
            return 0;

      if (clk->rate != 0)
            return clk->rate;

      if (clk->ops->get_rate != NULL)
            return clk->ops->get_rate(clk);

      return clk_get_rate(clk->parent);
}
EXPORT_SYMBOL(clk_get_rate);

long clk_round_rate(struct clk *clk, unsigned long rate)
{
      if (unlikely(!clk_good(clk)))
            return 0;

      if (clk->ops->round_rate)
            return clk->ops->round_rate(clk, rate);

      return 0;
}
EXPORT_SYMBOL(clk_round_rate);

static inline int close_enough(long rate1, long rate2)
{
      return rate1 && !((rate2 - rate1) * 1000 / rate1);
}

int clk_set_rate(struct clk *clk, unsigned long rate)
{
      int ret = -EINVAL;

      if (unlikely(!clk_good(clk)))
            goto out;

      if (clk->flags & FIXED_RATE || !clk->ops->set_rate)
            goto out;

      else if (!close_enough(clk->rate, rate)) {
            ret = clk->ops->set_rate(clk, rate);
            if (ret < 0)
                  goto out;
            clk->rate = rate;
            if (clk->flags & RATE_PROPAGATES)
                  propagate_rate(clk);
      } else
            ret = 0;

out:
      return ret;
}
EXPORT_SYMBOL(clk_set_rate);

int clk_enable(struct clk *clk)
{
      unsigned long clocks_flags;

      if (unlikely(!clk_good(clk)))
            return -EINVAL;

      if (clk->parent)
            clk_enable(clk->parent);

      spin_lock_irqsave(&clocks_lock, clocks_flags);

      clk->usage++;
      if (clk->ops && clk->ops->enable)
            clk->ops->enable(clk);

      spin_unlock_irqrestore(&clocks_lock, clocks_flags);
      return 0;
}
EXPORT_SYMBOL(clk_enable);

static void local_clk_disable(struct clk *clk)
{
      if (unlikely(!clk_good(clk)))
            return;

      if (clk->usage == 0 && clk->ops->disable)
            clk->ops->disable(clk);

      if (clk->parent)
            local_clk_disable(clk->parent);
}

void clk_disable(struct clk *clk)
{
      unsigned long clocks_flags;

      if (unlikely(!clk_good(clk)))
            return;

      spin_lock_irqsave(&clocks_lock, clocks_flags);

      if ((--clk->usage) == 0 && clk->ops->disable)
            clk->ops->disable(clk);

      spin_unlock_irqrestore(&clocks_lock, clocks_flags);
      if (clk->parent)
            clk_disable(clk->parent);
}
EXPORT_SYMBOL(clk_disable);

/* Some additional API */
int clk_set_parent(struct clk *clk, struct clk *parent)
{
      int ret = -ENODEV;
      unsigned long clocks_flags;

      if (unlikely(!clk_good(clk)))
            goto out;

      if (!clk->ops->set_parent)
            goto out;

      spin_lock_irqsave(&clocks_lock, clocks_flags);

      ret = clk->ops->set_parent(clk, parent);
      if (!ret) {
            /* disable if usage count is 0 */
            local_clk_disable(parent);

            parent->usage += clk->usage;
            clk->parent->usage -= clk->usage;

            /* disable if new usage count is 0 */
            local_clk_disable(clk->parent);

            clk->parent = parent;
      }
      spin_unlock_irqrestore(&clocks_lock, clocks_flags);

out:
      return ret;
}
EXPORT_SYMBOL(clk_set_parent);

struct clk *clk_get_parent(struct clk *clk)
{
      if (unlikely(!clk_good(clk)))
            return NULL;
      return clk->parent;
}
EXPORT_SYMBOL(clk_get_parent);

static int __init clk_init(void)
{
      struct clk_lookup *cl;
      struct clk_ops *ops;

      spin_lock_init(&clocks_lock);

      for (cl = onchip_clks; cl < onchip_clks + ARRAY_SIZE(onchip_clks);
           cl++) {
            if (cl->clk->flags & ENABLED)
                  clk_enable(cl->clk);
            else
                  local_clk_disable(cl->clk);

            ops = cl->clk->ops;

            if ((cl->clk->flags & NEEDS_INITIALIZATION) &&
                        ops && ops->set_rate)
                  ops->set_rate(cl->clk, cl->clk->rate);

            if (cl->clk->flags & FIXED_RATE) {
                  if (cl->clk->flags & RATE_PROPAGATES)
                        propagate_rate(cl->clk);
            } else {
                  if (ops && ops->get_rate)
                        ops->get_rate(cl->clk);
            }

            if (cl->clk->flags & NEEDS_SET_PARENT) {
                  if (ops && ops->set_parent)
                        ops->set_parent(cl->clk, cl->clk->parent);
            }
      }
      clkdev_add_table(onchip_clks, ARRAY_SIZE(onchip_clks));
      return 0;
}

arch_initcall(clk_init);

Generated by  Doxygen 1.6.0   Back to index