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

clock.c

#include <linux/clk.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>

#include <mach/clock.h>
#include <mach/hardware.h>
#include <mach/common.h>

#include <asm/clkdev.h>
#include <asm/bug.h>
#include <asm/div64.h>

#include "crm_regs.h"

#define CRM_SMALL_DIVIDER(base, name) \
      crm_small_divider(base, \
                    base ## _ ## name ## _OFFSET, \
                    base ## _ ## name ## _MASK)
#define CRM_1DIVIDER(base, name) \
      crm_divider(base, \
                base ## _ ## name ## _OFFSET, \
                base ## _ ## name ## _MASK, 1)
#define CRM_16DIVIDER(base, name) \
      crm_divider(base, \
                base ## _ ## name ## _OFFSET, \
                base ## _ ## name ## _MASK, 16)

static u32 crm_small_divider(void __iomem *reg, u8 offset, u32 mask)
{
      static const u32 crm_small_dividers[] = {
            2, 3, 4, 5, 6, 8, 10, 12
      };
      u8 idx;

      idx = (__raw_readl(reg) & mask) >> offset;
      if (idx > 7)
            return 1;

      return crm_small_dividers[idx];
}

static u32 crm_divider(void __iomem *reg, u8 offset, u32 mask, u32 z)
{
      u32 div;
      div = (__raw_readl(reg) & mask) >> offset;
      return div ? div : z;
}

static int _clk_1bit_enable(struct clk *clk)
{
      u32 reg;

      reg = __raw_readl(clk->enable_reg);
      reg |= 1 << clk->enable_shift;
      __raw_writel(reg, clk->enable_reg);

      return 0;
}

static void _clk_1bit_disable(struct clk *clk)
{
      u32 reg;

      reg = __raw_readl(clk->enable_reg);
      reg &= ~(1 << clk->enable_shift);
      __raw_writel(reg, clk->enable_reg);
}

static int _clk_3bit_enable(struct clk *clk)
{
      u32 reg;

      reg = __raw_readl(clk->enable_reg);
      reg |= 0x7 << clk->enable_shift;
      __raw_writel(reg, clk->enable_reg);

      return 0;
}

static void _clk_3bit_disable(struct clk *clk)
{
      u32 reg;

      reg = __raw_readl(clk->enable_reg);
      reg &= ~(0x7 << clk->enable_shift);
      __raw_writel(reg, clk->enable_reg);
}

static unsigned long ckih_rate;

static unsigned long clk_ckih_get_rate(struct clk *clk)
{
      return ckih_rate;
}

static struct clk ckih_clk = {
      .get_rate = clk_ckih_get_rate,
};

static unsigned long clk_ckih_x2_get_rate(struct clk *clk)
{
      return 2 * clk_get_rate(clk->parent);
}

static struct clk ckih_x2_clk = {
      .parent = &ckih_clk,
      .get_rate = clk_ckih_x2_get_rate,
};

static unsigned long clk_ckil_get_rate(struct clk *clk)
{
      return CKIL_CLK_FREQ;
}

static struct clk ckil_clk = {
      .get_rate = clk_ckil_get_rate,
};

/* plls stuff */
static struct clk mcu_pll_clk;
static struct clk dsp_pll_clk;
static struct clk usb_pll_clk;

static struct clk *pll_clk(u8 sel)
{
      switch (sel) {
      case 0:
            return &mcu_pll_clk;
      case 1:
            return &dsp_pll_clk;
      case 2:
            return &usb_pll_clk;
      }
      BUG();
}

static void __iomem *pll_base(struct clk *clk)
{
      if (clk == &mcu_pll_clk)
            return MXC_PLL0_BASE;
      else if (clk == &dsp_pll_clk)
            return MXC_PLL1_BASE;
      else if (clk == &usb_pll_clk)
            return MXC_PLL2_BASE;
      BUG();
}

static unsigned long clk_pll_get_rate(struct clk *clk)
{
      const void __iomem *pllbase;
      unsigned long dp_op, dp_mfd, dp_mfn, pll_hfsm, ref_clk, mfi;
      long mfn, mfn_abs, mfd, pdf;
      s64 temp;
      pllbase = pll_base(clk);

      pll_hfsm = __raw_readl(pllbase + MXC_PLL_DP_CTL) & MXC_PLL_DP_CTL_HFSM;
      if (pll_hfsm == 0) {
            dp_op = __raw_readl(pllbase + MXC_PLL_DP_OP);
            dp_mfd = __raw_readl(pllbase + MXC_PLL_DP_MFD);
            dp_mfn = __raw_readl(pllbase + MXC_PLL_DP_MFN);
      } else {
            dp_op = __raw_readl(pllbase + MXC_PLL_DP_HFS_OP);
            dp_mfd = __raw_readl(pllbase + MXC_PLL_DP_HFS_MFD);
            dp_mfn = __raw_readl(pllbase + MXC_PLL_DP_HFS_MFN);
      }

      pdf = dp_op & MXC_PLL_DP_OP_PDF_MASK;
      mfi = (dp_op >> MXC_PLL_DP_OP_MFI_OFFSET) & MXC_PLL_DP_OP_PDF_MASK;
      mfi = (mfi <= 5) ? 5 : mfi;
      mfd = dp_mfd & MXC_PLL_DP_MFD_MASK;
      mfn = dp_mfn & MXC_PLL_DP_MFN_MASK;
      mfn = (mfn <= 0x4000000) ? mfn : (mfn - 0x10000000);

      if (mfn < 0)
            mfn_abs = -mfn;
      else
            mfn_abs = mfn;

/* XXX: actually this asumes that ckih is fed to pll, but spec says
 * that ckih_x2 is also possible. need to check this out.
 */
      ref_clk = clk_get_rate(&ckih_clk);

      ref_clk *= 2;
      ref_clk /= pdf + 1;

      temp = (u64) ref_clk * mfn_abs;
      do_div(temp, mfd);
      if (mfn < 0)
            temp = -temp;
      temp += ref_clk * mfi;

      return temp;
}

static int clk_pll_enable(struct clk *clk)
{
      void __iomem *ctl;
      u32 reg;

      ctl = pll_base(clk);
      reg = __raw_readl(ctl);
      reg |= (MXC_PLL_DP_CTL_RST | MXC_PLL_DP_CTL_UPEN);
      __raw_writel(reg, ctl);
      do {
            reg = __raw_readl(ctl);
      } while ((reg & MXC_PLL_DP_CTL_LRF) != MXC_PLL_DP_CTL_LRF);
      return 0;
}

static void clk_pll_disable(struct clk *clk)
{
      void __iomem *ctl;
      u32 reg;

      ctl = pll_base(clk);
      reg = __raw_readl(ctl);
      reg &= ~(MXC_PLL_DP_CTL_RST | MXC_PLL_DP_CTL_UPEN);
      __raw_writel(reg, ctl);
}

static struct clk mcu_pll_clk = {
      .parent = &ckih_clk,
      .get_rate = clk_pll_get_rate,
      .enable = clk_pll_enable,
      .disable = clk_pll_disable,
};

static struct clk dsp_pll_clk = {
      .parent = &ckih_clk,
      .get_rate = clk_pll_get_rate,
      .enable = clk_pll_enable,
      .disable = clk_pll_disable,
};

static struct clk usb_pll_clk = {
      .parent = &ckih_clk,
      .get_rate = clk_pll_get_rate,
      .enable = clk_pll_enable,
      .disable = clk_pll_disable,
};
/* plls stuff end */

/* ap_ref_clk stuff */
static struct clk ap_ref_clk;

static unsigned long clk_ap_ref_get_rate(struct clk *clk)
{
      u32 ascsr, acsr;
      u8 ap_pat_ref_div_2, ap_isel, acs, ads;

      ascsr = __raw_readl(MXC_CRMAP_ASCSR);
      acsr = __raw_readl(MXC_CRMAP_ACSR);

      /* 0 for ckih, 1 for ckih*2 */
      ap_isel = ascsr & MXC_CRMAP_ASCSR_APISEL;
      /* reg divider */
      ap_pat_ref_div_2 = (ascsr >> MXC_CRMAP_ASCSR_AP_PATDIV2_OFFSET) & 0x1;
      /* undocumented, 1 for disabling divider */
      ads = (acsr >> MXC_CRMAP_ACSR_ADS_OFFSET) & 0x1;
      /* 0 for pat_ref, 1 for divider out */
      acs = acsr & MXC_CRMAP_ACSR_ACS;

      if (acs & !ads)
            /* use divided clock */
            return clk_get_rate(clk->parent) / (ap_pat_ref_div_2 ? 2 : 1);

      return clk_get_rate(clk->parent) * (ap_isel ? 2 : 1);
}

static struct clk ap_ref_clk = {
      .parent = &ckih_clk,
      .get_rate = clk_ap_ref_get_rate,
};
/* ap_ref_clk stuff end */

/* ap_pre_dfs_clk stuff */
static struct clk ap_pre_dfs_clk;

static unsigned long clk_ap_pre_dfs_get_rate(struct clk *clk)
{
      u32 acsr, ascsr;

      acsr = __raw_readl(MXC_CRMAP_ACSR);
      ascsr = __raw_readl(MXC_CRMAP_ASCSR);

      if (acsr & MXC_CRMAP_ACSR_ACS) {
            u8 sel;
            sel = (ascsr & MXC_CRMAP_ASCSR_APSEL_MASK) >>
                  MXC_CRMAP_ASCSR_APSEL_OFFSET;
            return clk_get_rate(pll_clk(sel)) /
                  CRM_SMALL_DIVIDER(MXC_CRMAP_ACDR, ARMDIV);
      }
      return clk_get_rate(&ap_ref_clk);
}

static struct clk ap_pre_dfs_clk = {
      .get_rate = clk_ap_pre_dfs_get_rate,
};
/* ap_pre_dfs_clk stuff end */

/* usb_clk stuff */
static struct clk usb_clk;

static struct clk *clk_usb_parent(struct clk *clk)
{
      u32 acsr, ascsr;

      acsr = __raw_readl(MXC_CRMAP_ACSR);
      ascsr = __raw_readl(MXC_CRMAP_ASCSR);

      if (acsr & MXC_CRMAP_ACSR_ACS) {
            u8 sel;
            sel = (ascsr & MXC_CRMAP_ASCSR_USBSEL_MASK) >>
                  MXC_CRMAP_ASCSR_USBSEL_OFFSET;
            return pll_clk(sel);
      }
      return &ap_ref_clk;
}

static unsigned long clk_usb_get_rate(struct clk *clk)
{
      return clk_get_rate(clk->parent) /
            CRM_SMALL_DIVIDER(MXC_CRMAP_ACDER2, USBDIV);
}

static struct clk usb_clk = {
      .enable_reg = MXC_CRMAP_ACDER2,
      .enable_shift = MXC_CRMAP_ACDER2_USBEN_OFFSET,
      .get_rate = clk_usb_get_rate,
      .enable = _clk_1bit_enable,
      .disable = _clk_1bit_disable,
};
/* usb_clk stuff end */

static unsigned long clk_ipg_get_rate(struct clk *clk)
{
      return clk_get_rate(clk->parent) / CRM_16DIVIDER(MXC_CRMAP_ACDR, IPDIV);
}

static unsigned long clk_ahb_get_rate(struct clk *clk)
{
      return clk_get_rate(clk->parent) /
            CRM_16DIVIDER(MXC_CRMAP_ACDR, AHBDIV);
}

static struct clk ipg_clk = {
      .parent = &ap_pre_dfs_clk,
      .get_rate = clk_ipg_get_rate,
};

static struct clk ahb_clk = {
      .parent = &ap_pre_dfs_clk,
      .get_rate = clk_ahb_get_rate,
};

/* perclk_clk stuff */
static struct clk perclk_clk;

static unsigned long clk_perclk_get_rate(struct clk *clk)
{
      u32 acder2;

      acder2 = __raw_readl(MXC_CRMAP_ACDER2);
      if (acder2 & MXC_CRMAP_ACDER2_BAUD_ISEL_MASK)
            return 2 * clk_get_rate(clk->parent);

      return clk_get_rate(clk->parent);
}

static struct clk perclk_clk = {
      .parent = &ckih_clk,
      .get_rate = clk_perclk_get_rate,
};
/* perclk_clk stuff end */

/* uart_clk stuff */
static struct clk uart_clk[];

static unsigned long clk_uart_get_rate(struct clk *clk)
{
      u32 div;

      switch (clk->id) {
      case 0:
      case 1:
            div = CRM_SMALL_DIVIDER(MXC_CRMAP_ACDER2, BAUDDIV);
            break;
      case 2:
            div = CRM_SMALL_DIVIDER(MXC_CRMAP_APRA, UART3DIV);
            break;
      default:
            BUG();
      }
      return clk_get_rate(clk->parent) / div;
}

static struct clk uart_clk[] = {
      {
            .id = 0,
            .parent = &perclk_clk,
            .enable_reg = MXC_CRMAP_APRA,
            .enable_shift = MXC_CRMAP_APRA_UART1EN_OFFSET,
            .get_rate = clk_uart_get_rate,
            .enable = _clk_1bit_enable,
            .disable = _clk_1bit_disable,
      }, {
            .id = 1,
            .parent = &perclk_clk,
            .enable_reg = MXC_CRMAP_APRA,
            .enable_shift = MXC_CRMAP_APRA_UART2EN_OFFSET,
            .get_rate = clk_uart_get_rate,
            .enable = _clk_1bit_enable,
            .disable = _clk_1bit_disable,
      }, {
            .id = 2,
            .parent = &perclk_clk,
            .enable_reg = MXC_CRMAP_APRA,
            .enable_shift = MXC_CRMAP_APRA_UART3EN_OFFSET,
            .get_rate = clk_uart_get_rate,
            .enable = _clk_1bit_enable,
            .disable = _clk_1bit_disable,
      },
};
/* uart_clk stuff end */

/* sdhc_clk stuff */
static struct clk nfc_clk;

static unsigned long clk_nfc_get_rate(struct clk *clk)
{
      return clk_get_rate(clk->parent) /
            CRM_1DIVIDER(MXC_CRMAP_ACDER2, NFCDIV);
}

static struct clk nfc_clk = {
      .parent = &ahb_clk,
      .enable_reg = MXC_CRMAP_ACDER2,
      .enable_shift = MXC_CRMAP_ACDER2_NFCEN_OFFSET,
      .get_rate = clk_nfc_get_rate,
      .enable = _clk_1bit_enable,
      .disable = _clk_1bit_disable,
};
/* sdhc_clk stuff end */

/* sdhc_clk stuff */
static struct clk sdhc_clk[];

static struct clk *clk_sdhc_parent(struct clk *clk)
{
      u32 aprb;
      u8 sel;
      u32 mask;
      int offset;

      aprb = __raw_readl(MXC_CRMAP_APRB);

      switch (clk->id) {
      case 0:
            mask = MXC_CRMAP_APRB_SDHC1_ISEL_MASK;
            offset = MXC_CRMAP_APRB_SDHC1_ISEL_OFFSET;
            break;
      case 1:
            mask = MXC_CRMAP_APRB_SDHC2_ISEL_MASK;
            offset = MXC_CRMAP_APRB_SDHC2_ISEL_OFFSET;
            break;
      default:
            BUG();
      }
      sel = (aprb & mask) >> offset;

      switch (sel) {
      case 0:
            return &ckih_clk;
      case 1:
            return &ckih_x2_clk;
      }
      return &usb_clk;
}

static unsigned long clk_sdhc_get_rate(struct clk *clk)
{
      u32 div;

      switch (clk->id) {
      case 0:
            div = CRM_SMALL_DIVIDER(MXC_CRMAP_APRB, SDHC1_DIV);
            break;
      case 1:
            div = CRM_SMALL_DIVIDER(MXC_CRMAP_APRB, SDHC2_DIV);
            break;
      default:
            BUG();
      }

      return clk_get_rate(clk->parent) / div;
}

static int clk_sdhc_enable(struct clk *clk)
{
      u32 amlpmre1, aprb;

      amlpmre1 = __raw_readl(MXC_CRMAP_AMLPMRE1);
      aprb = __raw_readl(MXC_CRMAP_APRB);
      switch (clk->id) {
      case 0:
            amlpmre1 |= (0x7 << MXC_CRMAP_AMLPMRE1_MLPME4_OFFSET);
            aprb |= (0x1 << MXC_CRMAP_APRB_SDHC1EN_OFFSET);
            break;
      case 1:
            amlpmre1 |= (0x7 << MXC_CRMAP_AMLPMRE1_MLPME5_OFFSET);
            aprb |= (0x1 << MXC_CRMAP_APRB_SDHC2EN_OFFSET);
            break;
      }
      __raw_writel(amlpmre1, MXC_CRMAP_AMLPMRE1);
      __raw_writel(aprb, MXC_CRMAP_APRB);
      return 0;
}

static void clk_sdhc_disable(struct clk *clk)
{
      u32 amlpmre1, aprb;

      amlpmre1 = __raw_readl(MXC_CRMAP_AMLPMRE1);
      aprb = __raw_readl(MXC_CRMAP_APRB);
      switch (clk->id) {
      case 0:
            amlpmre1 &= ~(0x7 << MXC_CRMAP_AMLPMRE1_MLPME4_OFFSET);
            aprb &= ~(0x1 << MXC_CRMAP_APRB_SDHC1EN_OFFSET);
            break;
      case 1:
            amlpmre1 &= ~(0x7 << MXC_CRMAP_AMLPMRE1_MLPME5_OFFSET);
            aprb &= ~(0x1 << MXC_CRMAP_APRB_SDHC2EN_OFFSET);
            break;
      }
      __raw_writel(amlpmre1, MXC_CRMAP_AMLPMRE1);
      __raw_writel(aprb, MXC_CRMAP_APRB);
}

static struct clk sdhc_clk[] = {
      {
            .id = 0,
            .get_rate = clk_sdhc_get_rate,
            .enable = clk_sdhc_enable,
            .disable = clk_sdhc_disable,
      }, {
            .id = 1,
            .get_rate = clk_sdhc_get_rate,
            .enable = clk_sdhc_enable,
            .disable = clk_sdhc_disable,
      },
};
/* sdhc_clk stuff end */

/* wdog_clk stuff */
static struct clk wdog_clk[] = {
      {
            .id = 0,
            .parent = &ipg_clk,
            .enable_reg = MXC_CRMAP_AMLPMRD,
            .enable_shift = MXC_CRMAP_AMLPMRD_MLPMD7_OFFSET,
            .enable = _clk_3bit_enable,
            .disable = _clk_3bit_disable,
      }, {
            .id = 1,
            .parent = &ipg_clk,
            .enable_reg = MXC_CRMAP_AMLPMRD,
            .enable_shift = MXC_CRMAP_AMLPMRD_MLPMD3_OFFSET,
            .enable = _clk_3bit_enable,
            .disable = _clk_3bit_disable,
      },
};
/* wdog_clk stuff end */

/* gpt_clk stuff */
static struct clk gpt_clk = {
      .parent = &ipg_clk,
      .enable_reg = MXC_CRMAP_AMLPMRC,
      .enable_shift = MXC_CRMAP_AMLPMRC_MLPMC4_OFFSET,
      .enable = _clk_3bit_enable,
      .disable = _clk_3bit_disable,
};
/* gpt_clk stuff end */

/* cspi_clk stuff */
static struct clk cspi_clk[] = {
      {
            .id = 0,
            .parent = &ipg_clk,
            .enable_reg = MXC_CRMAP_AMLPMRE2,
            .enable_shift = MXC_CRMAP_AMLPMRE2_MLPME0_OFFSET,
            .enable = _clk_3bit_enable,
            .disable = _clk_3bit_disable,
      }, {
            .id = 1,
            .parent = &ipg_clk,
            .enable_reg = MXC_CRMAP_AMLPMRE1,
            .enable_shift = MXC_CRMAP_AMLPMRE1_MLPME6_OFFSET,
            .enable = _clk_3bit_enable,
            .disable = _clk_3bit_disable,
      },
};
/* cspi_clk stuff end */

#define _REGISTER_CLOCK(d, n, c) \
      { \
            .dev_id = d, \
            .con_id = n, \
            .clk = &c, \
      },

static struct clk_lookup lookups[] = {
      _REGISTER_CLOCK("imx-uart.0", NULL, uart_clk[0])
      _REGISTER_CLOCK("imx-uart.1", NULL, uart_clk[1])
      _REGISTER_CLOCK("imx-uart.2", NULL, uart_clk[2])
      _REGISTER_CLOCK("mxc-mmc.0", NULL, sdhc_clk[0])
      _REGISTER_CLOCK("mxc-mmc.1", NULL, sdhc_clk[1])
      _REGISTER_CLOCK("mxc-wdt.0", NULL, wdog_clk[0])
      _REGISTER_CLOCK("spi_imx.0", NULL, cspi_clk[0])
      _REGISTER_CLOCK("spi_imx.1", NULL, cspi_clk[1])
};

int __init mxc91231_clocks_init(unsigned long fref)
{
      void __iomem *gpt_base;
      int i;

      ckih_rate = fref;

      usb_clk.parent = clk_usb_parent(&usb_clk);
      sdhc_clk[0].parent = clk_sdhc_parent(&sdhc_clk[0]);
      sdhc_clk[1].parent = clk_sdhc_parent(&sdhc_clk[1]);

      for (i = 0; i < ARRAY_SIZE(lookups); i++)
            clkdev_add(&lookups[i]);

      gpt_base = MXC91231_IO_ADDRESS(MXC91231_GPT1_BASE_ADDR);
      mxc_timer_init(&gpt_clk, gpt_base, MXC91231_INT_GPT);

      return 0;
}

Generated by  Doxygen 1.6.0   Back to index