diff options
author | Shawn Guo <shawn.guo@freescale.com> | 2010-12-18 21:39:33 +0800 |
---|---|---|
committer | Uwe Kleine-König <u.kleine-koenig@pengutronix.de> | 2010-12-20 17:30:22 +0100 |
commit | 30a7585ff7464ceb1dceb941700654923becb769 (patch) | |
tree | 42c060756bdc2eb464ced6e34982da49b9311580 /arch/arm/mach-mxs/clock-mx23.c | |
parent | bf985969e27b507f734435a99df8bf745a3dbb2b (diff) | |
download | talos-op-linux-30a7585ff7464ceb1dceb941700654923becb769.tar.gz talos-op-linux-30a7585ff7464ceb1dceb941700654923becb769.zip |
ARM: mxs: Add clock support
Add clock for MXS-based SoCs, MX23 and MX28.
Signed-off-by: Shawn Guo <shawn.guo@freescale.com>
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Diffstat (limited to 'arch/arm/mach-mxs/clock-mx23.c')
-rw-r--r-- | arch/arm/mach-mxs/clock-mx23.c | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/arch/arm/mach-mxs/clock-mx23.c b/arch/arm/mach-mxs/clock-mx23.c new file mode 100644 index 000000000000..8f5a19ab558c --- /dev/null +++ b/arch/arm/mach-mxs/clock-mx23.c @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/jiffies.h> + +#include <asm/clkdev.h> +#include <asm/div64.h> + +#include <mach/mx23.h> +#include <mach/common.h> +#include <mach/clock.h> + +#include "regs-clkctrl-mx23.h" + +#define CLKCTRL_BASE_ADDR MX23_IO_ADDRESS(MX23_CLKCTRL_BASE_ADDR) +#define DIGCTRL_BASE_ADDR MX23_IO_ADDRESS(MX23_DIGCTL_BASE_ADDR) + +#define PARENT_RATE_SHIFT 8 + +static int _raw_clk_enable(struct clk *clk) +{ + u32 reg; + + if (clk->enable_reg) { + reg = __raw_readl(clk->enable_reg); + reg &= ~(1 << clk->enable_shift); + __raw_writel(reg, clk->enable_reg); + } + + return 0; +} + +static void _raw_clk_disable(struct clk *clk) +{ + u32 reg; + + if (clk->enable_reg) { + reg = __raw_readl(clk->enable_reg); + reg |= 1 << clk->enable_shift; + __raw_writel(reg, clk->enable_reg); + } +} + +/* + * ref_xtal_clk + */ +static unsigned long ref_xtal_clk_get_rate(struct clk *clk) +{ + return 24000000; +} + +static struct clk ref_xtal_clk = { + .get_rate = ref_xtal_clk_get_rate, +}; + +/* + * pll_clk + */ +static unsigned long pll_clk_get_rate(struct clk *clk) +{ + return 480000000; +} + +static int pll_clk_enable(struct clk *clk) +{ + __raw_writel(BM_CLKCTRL_PLLCTRL0_POWER | + BM_CLKCTRL_PLLCTRL0_EN_USB_CLKS, + CLKCTRL_BASE_ADDR + HW_CLKCTRL_PLLCTRL0_SET); + + /* Only a 10us delay is need. PLLCTRL1 LOCK bitfied is only a timer + * and is incorrect (excessive). Per definition of the PLLCTRL0 + * POWER field, waiting at least 10us. + */ + udelay(10); + + return 0; +} + +static void pll_clk_disable(struct clk *clk) +{ + __raw_writel(BM_CLKCTRL_PLLCTRL0_POWER | + BM_CLKCTRL_PLLCTRL0_EN_USB_CLKS, + CLKCTRL_BASE_ADDR + HW_CLKCTRL_PLLCTRL0_CLR); +} + +static struct clk pll_clk = { + .get_rate = pll_clk_get_rate, + .enable = pll_clk_enable, + .disable = pll_clk_disable, + .parent = &ref_xtal_clk, +}; + +/* + * ref_clk + */ +#define _CLK_GET_RATE_REF(name, sr, ss) \ +static unsigned long name##_get_rate(struct clk *clk) \ +{ \ + unsigned long parent_rate; \ + u32 reg, div; \ + \ + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_##sr); \ + div = (reg >> BP_CLKCTRL_##sr##_##ss##FRAC) & 0x3f; \ + parent_rate = clk_get_rate(clk->parent); \ + \ + return SH_DIV((parent_rate >> PARENT_RATE_SHIFT) * 18, \ + div, PARENT_RATE_SHIFT); \ +} + +_CLK_GET_RATE_REF(ref_cpu_clk, FRAC, CPU) +_CLK_GET_RATE_REF(ref_emi_clk, FRAC, EMI) +_CLK_GET_RATE_REF(ref_pix_clk, FRAC, PIX) +_CLK_GET_RATE_REF(ref_io_clk, FRAC, IO) + +#define _DEFINE_CLOCK_REF(name, er, es) \ + static struct clk name = { \ + .enable_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_##er, \ + .enable_shift = BP_CLKCTRL_##er##_CLKGATE##es, \ + .get_rate = name##_get_rate, \ + .enable = _raw_clk_enable, \ + .disable = _raw_clk_disable, \ + .parent = &pll_clk, \ + } + +_DEFINE_CLOCK_REF(ref_cpu_clk, FRAC, CPU); +_DEFINE_CLOCK_REF(ref_emi_clk, FRAC, EMI); +_DEFINE_CLOCK_REF(ref_pix_clk, FRAC, PIX); +_DEFINE_CLOCK_REF(ref_io_clk, FRAC, IO); + +/* + * General clocks + * + * clk_get_rate + */ +static unsigned long rtc_clk_get_rate(struct clk *clk) +{ + /* ref_xtal_clk is implemented as the only parent */ + return clk_get_rate(clk->parent) / 768; +} + +static unsigned long clk32k_clk_get_rate(struct clk *clk) +{ + return clk->parent->get_rate(clk->parent) / 750; +} + +#define _CLK_GET_RATE(name, rs) \ +static unsigned long name##_get_rate(struct clk *clk) \ +{ \ + u32 reg, div; \ + \ + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_##rs); \ + \ + if (clk->parent == &ref_xtal_clk) \ + div = (reg & BM_CLKCTRL_##rs##_DIV_XTAL) >> \ + BP_CLKCTRL_##rs##_DIV_XTAL; \ + else \ + div = (reg & BM_CLKCTRL_##rs##_DIV_##rs) >> \ + BP_CLKCTRL_##rs##_DIV_##rs; \ + \ + if (!div) \ + return -EINVAL; \ + \ + return clk_get_rate(clk->parent) / div; \ +} + +_CLK_GET_RATE(cpu_clk, CPU) +_CLK_GET_RATE(emi_clk, EMI) + +#define _CLK_GET_RATE1(name, rs) \ +static unsigned long name##_get_rate(struct clk *clk) \ +{ \ + u32 reg, div; \ + \ + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_##rs); \ + div = (reg & BM_CLKCTRL_##rs##_DIV) >> BP_CLKCTRL_##rs##_DIV; \ + \ + if (!div) \ + return -EINVAL; \ + \ + return clk_get_rate(clk->parent) / div; \ +} + +_CLK_GET_RATE1(hbus_clk, HBUS) +_CLK_GET_RATE1(xbus_clk, XBUS) +_CLK_GET_RATE1(ssp_clk, SSP) +_CLK_GET_RATE1(gpmi_clk, GPMI) +_CLK_GET_RATE1(lcdif_clk, PIX) + +#define _CLK_GET_RATE_STUB(name) \ +static unsigned long name##_get_rate(struct clk *clk) \ +{ \ + return clk_get_rate(clk->parent); \ +} + +_CLK_GET_RATE_STUB(uart_clk) +_CLK_GET_RATE_STUB(audio_clk) +_CLK_GET_RATE_STUB(pwm_clk) + +/* + * clk_set_rate + */ +static int cpu_clk_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, bm_busy, div_max, d, f, div, frac; + unsigned long diff, parent_rate, calc_rate; + int i; + + parent_rate = clk_get_rate(clk->parent); + + if (clk->parent == &ref_xtal_clk) { + div_max = BM_CLKCTRL_CPU_DIV_XTAL >> BP_CLKCTRL_CPU_DIV_XTAL; + bm_busy = BM_CLKCTRL_CPU_BUSY_REF_XTAL; + div = DIV_ROUND_UP(parent_rate, rate); + if (div == 0 || div > div_max) + return -EINVAL; + } else { + div_max = BM_CLKCTRL_CPU_DIV_CPU >> BP_CLKCTRL_CPU_DIV_CPU; + bm_busy = BM_CLKCTRL_CPU_BUSY_REF_CPU; + rate >>= PARENT_RATE_SHIFT; + parent_rate >>= PARENT_RATE_SHIFT; + diff = parent_rate; + div = frac = 1; + for (d = 1; d <= div_max; d++) { + f = parent_rate * 18 / d / rate; + if ((parent_rate * 18 / d) % rate) + f++; + if (f < 18 || f > 35) + continue; + + calc_rate = parent_rate * 18 / f / d; + if (calc_rate > rate) + continue; + + if (rate - calc_rate < diff) { + frac = f; + div = d; + diff = rate - calc_rate; + } + + if (diff == 0) + break; + } + + if (diff == parent_rate) + return -EINVAL; + + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_FRAC); + reg &= ~BM_CLKCTRL_FRAC_CPUFRAC; + reg |= frac; + __raw_writel(reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_FRAC); + } + + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_CPU); + reg &= ~BM_CLKCTRL_CPU_DIV_CPU; + reg |= div << BP_CLKCTRL_CPU_DIV_CPU; + __raw_writel(reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_CPU); + + for (i = 10000; i; i--) + if (!(__raw_readl(CLKCTRL_BASE_ADDR + + HW_CLKCTRL_CPU) & bm_busy)) + break; + if (!i) { + pr_err("%s: divider writing timeout\n", __func__); + return -ETIMEDOUT; + } + + return 0; +} + +#define _CLK_SET_RATE(name, dr) \ +static int name##_set_rate(struct clk *clk, unsigned long rate) \ +{ \ + u32 reg, div_max, div; \ + unsigned long parent_rate; \ + int i; \ + \ + parent_rate = clk_get_rate(clk->parent); \ + div_max = BM_CLKCTRL_##dr##_DIV >> BP_CLKCTRL_##dr##_DIV; \ + \ + div = DIV_ROUND_UP(parent_rate, rate); \ + if (div == 0 || div > div_max) \ + return -EINVAL; \ + \ + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_##dr); \ + reg &= ~BM_CLKCTRL_##dr##_DIV; \ + reg |= div << BP_CLKCTRL_##dr##_DIV; \ + if (reg | (1 << clk->enable_shift)) { \ + pr_err("%s: clock is gated\n", __func__); \ + return -EINVAL; \ + } \ + __raw_writel(reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_##dr); \ + \ + for (i = 10000; i; i--) \ + if (!(__raw_readl(CLKCTRL_BASE_ADDR + \ + HW_CLKCTRL_##dr) & BM_CLKCTRL_##dr##_BUSY)) \ + break; \ + if (!i) { \ + pr_err("%s: divider writing timeout\n", __func__); \ + return -ETIMEDOUT; \ + } \ + \ + return 0; \ +} + +_CLK_SET_RATE(xbus_clk, XBUS) +_CLK_SET_RATE(ssp_clk, SSP) +_CLK_SET_RATE(gpmi_clk, GPMI) +_CLK_SET_RATE(lcdif_clk, PIX) + +#define _CLK_SET_RATE_STUB(name) \ +static int name##_set_rate(struct clk *clk, unsigned long rate) \ +{ \ + return -EINVAL; \ +} + +_CLK_SET_RATE_STUB(emi_clk) +_CLK_SET_RATE_STUB(uart_clk) +_CLK_SET_RATE_STUB(audio_clk) +_CLK_SET_RATE_STUB(pwm_clk) +_CLK_SET_RATE_STUB(clk32k_clk) + +/* + * clk_set_parent + */ +#define _CLK_SET_PARENT(name, bit) \ +static int name##_set_parent(struct clk *clk, struct clk *parent) \ +{ \ + if (parent != clk->parent) { \ + __raw_writel(BM_CLKCTRL_CLKSEQ_BYPASS_##bit, \ + HW_CLKCTRL_CLKSEQ_TOG); \ + clk->parent = parent; \ + } \ + \ + return 0; \ +} + +_CLK_SET_PARENT(cpu_clk, CPU) +_CLK_SET_PARENT(emi_clk, EMI) +_CLK_SET_PARENT(ssp_clk, SSP) +_CLK_SET_PARENT(gpmi_clk, GPMI) +_CLK_SET_PARENT(lcdif_clk, PIX) + +#define _CLK_SET_PARENT_STUB(name) \ +static int name##_set_parent(struct clk *clk, struct clk *parent) \ +{ \ + if (parent != clk->parent) \ + return -EINVAL; \ + else \ + return 0; \ +} + +_CLK_SET_PARENT_STUB(uart_clk) +_CLK_SET_PARENT_STUB(audio_clk) +_CLK_SET_PARENT_STUB(pwm_clk) +_CLK_SET_PARENT_STUB(clk32k_clk) + +/* + * clk definition + */ +static struct clk cpu_clk = { + .get_rate = cpu_clk_get_rate, + .set_rate = cpu_clk_set_rate, + .set_parent = cpu_clk_set_parent, + .parent = &ref_cpu_clk, +}; + +static struct clk hbus_clk = { + .get_rate = hbus_clk_get_rate, + .parent = &cpu_clk, +}; + +static struct clk xbus_clk = { + .get_rate = xbus_clk_get_rate, + .set_rate = xbus_clk_set_rate, + .parent = &ref_xtal_clk, +}; + +static struct clk rtc_clk = { + .get_rate = rtc_clk_get_rate, + .parent = &ref_xtal_clk, +}; + +/* usb_clk gate is controlled in DIGCTRL other than CLKCTRL */ +static struct clk usb_clk = { + .enable_reg = DIGCTRL_BASE_ADDR, + .enable_shift = 2, + .enable = _raw_clk_enable, + .disable = _raw_clk_disable, + .parent = &pll_clk, +}; + +#define _DEFINE_CLOCK(name, er, es, p) \ + static struct clk name = { \ + .enable_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_##er, \ + .enable_shift = BP_CLKCTRL_##er##_##es, \ + .get_rate = name##_get_rate, \ + .set_rate = name##_set_rate, \ + .set_parent = name##_set_parent, \ + .enable = _raw_clk_enable, \ + .disable = _raw_clk_disable, \ + .parent = p, \ + } + +_DEFINE_CLOCK(emi_clk, EMI, CLKGATE, &ref_xtal_clk); +_DEFINE_CLOCK(ssp_clk, SSP, CLKGATE, &ref_xtal_clk); +_DEFINE_CLOCK(gpmi_clk, GPMI, CLKGATE, &ref_xtal_clk); +_DEFINE_CLOCK(lcdif_clk, PIX, CLKGATE, &ref_xtal_clk); +_DEFINE_CLOCK(uart_clk, XTAL, UART_CLK_GATE, &ref_xtal_clk); +_DEFINE_CLOCK(audio_clk, XTAL, FILT_CLK24M_GATE, &ref_xtal_clk); +_DEFINE_CLOCK(pwm_clk, XTAL, PWM_CLK24M_GATE, &ref_xtal_clk); +_DEFINE_CLOCK(clk32k_clk, XTAL, TIMROT_CLK32K_GATE, &ref_xtal_clk); + +#define _REGISTER_CLOCK(d, n, c) \ + { \ + .dev_id = d, \ + .con_id = n, \ + .clk = &c, \ + }, + +static struct clk_lookup lookups[] = { + _REGISTER_CLOCK("mxs-duart.0", NULL, uart_clk) + _REGISTER_CLOCK("rtc", NULL, rtc_clk) + _REGISTER_CLOCK(NULL, "hclk", hbus_clk) + _REGISTER_CLOCK(NULL, "xclk", xbus_clk) + _REGISTER_CLOCK(NULL, "usb", usb_clk) + _REGISTER_CLOCK(NULL, "audio", audio_clk) + _REGISTER_CLOCK(NULL, "pwm", pwm_clk) +}; + +static int clk_misc_init(void) +{ + u32 reg; + int i; + + /* Fix up parent per register setting */ + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_CLKSEQ); + cpu_clk.parent = (reg & BM_CLKCTRL_CLKSEQ_BYPASS_CPU) ? + &ref_xtal_clk : &ref_cpu_clk; + emi_clk.parent = (reg & BM_CLKCTRL_CLKSEQ_BYPASS_EMI) ? + &ref_xtal_clk : &ref_emi_clk; + ssp_clk.parent = (reg & BM_CLKCTRL_CLKSEQ_BYPASS_SSP) ? + &ref_xtal_clk : &ref_io_clk; + gpmi_clk.parent = (reg & BM_CLKCTRL_CLKSEQ_BYPASS_GPMI) ? + &ref_xtal_clk : &ref_io_clk; + lcdif_clk.parent = (reg & BM_CLKCTRL_CLKSEQ_BYPASS_PIX) ? + &ref_xtal_clk : &ref_pix_clk; + + /* Use int div over frac when both are available */ + __raw_writel(BM_CLKCTRL_CPU_DIV_XTAL_FRAC_EN, + CLKCTRL_BASE_ADDR + HW_CLKCTRL_CPU_CLR); + __raw_writel(BM_CLKCTRL_CPU_DIV_CPU_FRAC_EN, + CLKCTRL_BASE_ADDR + HW_CLKCTRL_CPU_CLR); + __raw_writel(BM_CLKCTRL_HBUS_DIV_FRAC_EN, + CLKCTRL_BASE_ADDR + HW_CLKCTRL_HBUS_CLR); + + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_XBUS); + reg &= ~BM_CLKCTRL_XBUS_DIV_FRAC_EN; + __raw_writel(reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_XBUS); + + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_SSP); + reg &= ~BM_CLKCTRL_SSP_DIV_FRAC_EN; + __raw_writel(reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_SSP); + + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_GPMI); + reg &= ~BM_CLKCTRL_GPMI_DIV_FRAC_EN; + __raw_writel(reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_GPMI); + + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_PIX); + reg &= ~BM_CLKCTRL_PIX_DIV_FRAC_EN; + __raw_writel(reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_PIX); + + /* + * Set safe hbus clock divider. A divider of 3 ensure that + * the Vddd voltage required for the cpu clock is sufficiently + * high for the hbus clock. + */ + reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_HBUS); + reg &= BM_CLKCTRL_HBUS_DIV; + reg |= 3 << BP_CLKCTRL_HBUS_DIV; + __raw_writel(reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_HBUS); + + for (i = 10000; i; i--) + if (!(__raw_readl(CLKCTRL_BASE_ADDR + + HW_CLKCTRL_HBUS) & BM_CLKCTRL_HBUS_BUSY)) + break; + if (!i) { + pr_err("%s: divider writing timeout\n", __func__); + return -ETIMEDOUT; + } + + /* Gate off cpu clock in WFI for power saving */ + __raw_writel(BM_CLKCTRL_CPU_INTERRUPT_WAIT, + CLKCTRL_BASE_ADDR + HW_CLKCTRL_CPU_SET); + + return 0; +} + +int __init mx23_clocks_init(void) +{ + clk_misc_init(); + + clkdev_add_table(lookups, ARRAY_SIZE(lookups)); + + mxs_timer_init(&clk32k_clk, MX23_INT_TIMER0); + + return 0; +} |