blob: ecfa37fef8ade52accc9f14a0839d4f6af9afea0 [file] [log] [blame]
#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;
}