| #include <linux/clk.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/io.h> |
| #include <linux/clkdev.h> |
| |
| #include <mach/clock.h> |
| #include <mach/hardware.h> |
| #include <mach/common.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; |
| |
| 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]); |
| |
| clkdev_add_table(lookups, ARRAY_SIZE(lookups)); |
| |
| gpt_base = MXC91231_IO_ADDRESS(MXC91231_GPT1_BASE_ADDR); |
| mxc_timer_init(&gpt_clk, gpt_base, MXC91231_INT_GPT); |
| |
| return 0; |
| } |