diff options
Diffstat (limited to 'arch/arm/mach-omap2/clock.c')
-rw-r--r-- | arch/arm/mach-omap2/clock.c | 1129 |
1 files changed, 1129 insertions, 0 deletions
diff --git a/arch/arm/mach-omap2/clock.c b/arch/arm/mach-omap2/clock.c new file mode 100644 index 00000000000..85818d9f263 --- /dev/null +++ b/arch/arm/mach-omap2/clock.c @@ -0,0 +1,1129 @@ +/* + * linux/arch/arm/mach-omap2/clock.c + * + * Copyright (C) 2005 Texas Instruments Inc. + * Richard Woodruff <r-woodruff2@ti.com> + * Created for OMAP2. + * + * Cleaned up and modified to use omap shared clock framework by + * Tony Lindgren <tony@atomide.com> + * + * Based on omap1 clock.c, Copyright (C) 2004 - 2005 Nokia corporation + * Written by Tuukka Tikkanen <tuukka.tikkanen@elektrobit.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/delay.h> + +#include <asm/io.h> + +#include <asm/hardware/clock.h> +#include <asm/arch/clock.h> +#include <asm/arch/sram.h> +#include <asm/arch/prcm.h> + +#include "clock.h" + +//#define DOWN_VARIABLE_DPLL 1 /* Experimental */ + +static struct prcm_config *curr_prcm_set; +static struct memory_timings mem_timings; +static u32 curr_perf_level = PRCM_FULL_SPEED; + +/*------------------------------------------------------------------------- + * Omap2 specific clock functions + *-------------------------------------------------------------------------*/ + +/* Recalculate SYST_CLK */ +static void omap2_sys_clk_recalc(struct clk * clk) +{ + u32 div = PRCM_CLKSRC_CTRL; + div &= (1 << 7) | (1 << 6); /* Test if ext clk divided by 1 or 2 */ + div >>= clk->rate_offset; + clk->rate = (clk->parent->rate / div); + propagate_rate(clk); +} + +static u32 omap2_get_dpll_rate(struct clk * tclk) +{ + int dpll_clk, dpll_mult, dpll_div, amult; + + dpll_mult = (CM_CLKSEL1_PLL >> 12) & 0x03ff; /* 10 bits */ + dpll_div = (CM_CLKSEL1_PLL >> 8) & 0x0f; /* 4 bits */ + dpll_clk = (tclk->parent->rate * dpll_mult) / (dpll_div + 1); + amult = CM_CLKSEL2_PLL & 0x3; + dpll_clk *= amult; + + return dpll_clk; +} + +static void omap2_followparent_recalc(struct clk *clk) +{ + followparent_recalc(clk); +} + +static void omap2_propagate_rate(struct clk * clk) +{ + if (!(clk->flags & RATE_FIXED)) + clk->rate = clk->parent->rate; + + propagate_rate(clk); +} + +/* Enable an APLL if off */ +static void omap2_clk_fixed_enable(struct clk *clk) +{ + u32 cval, i=0; + + if (clk->enable_bit == 0xff) /* Parent will do it */ + return; + + cval = CM_CLKEN_PLL; + + if ((cval & (0x3 << clk->enable_bit)) == (0x3 << clk->enable_bit)) + return; + + cval &= ~(0x3 << clk->enable_bit); + cval |= (0x3 << clk->enable_bit); + CM_CLKEN_PLL = cval; + + if (clk == &apll96_ck) + cval = (1 << 8); + else if (clk == &apll54_ck) + cval = (1 << 6); + + while (!CM_IDLEST_CKGEN & cval) { /* Wait for lock */ + ++i; + udelay(1); + if (i == 100000) + break; + } +} + +/* Enables clock without considering parent dependencies or use count + * REVISIT: Maybe change this to use clk->enable like on omap1? + */ +static int omap2_clk_enable(struct clk * clk) +{ + u32 regval32; + + if (clk->flags & ALWAYS_ENABLED) + return 0; + + if (unlikely(clk->enable_reg == 0)) { + printk(KERN_ERR "clock.c: Enable for %s without enable code\n", + clk->name); + return 0; + } + + if (clk->enable_reg == (void __iomem *)&CM_CLKEN_PLL) { + omap2_clk_fixed_enable(clk); + return 0; + } + + regval32 = __raw_readl(clk->enable_reg); + regval32 |= (1 << clk->enable_bit); + __raw_writel(regval32, clk->enable_reg); + + return 0; +} + +/* Stop APLL */ +static void omap2_clk_fixed_disable(struct clk *clk) +{ + u32 cval; + + if(clk->enable_bit == 0xff) /* let parent off do it */ + return; + + cval = CM_CLKEN_PLL; + cval &= ~(0x3 << clk->enable_bit); + CM_CLKEN_PLL = cval; +} + +/* Disables clock without considering parent dependencies or use count */ +static void omap2_clk_disable(struct clk *clk) +{ + u32 regval32; + + if (clk->enable_reg == 0) + return; + + if (clk->enable_reg == (void __iomem *)&CM_CLKEN_PLL) { + omap2_clk_fixed_disable(clk); + return; + } + + regval32 = __raw_readl(clk->enable_reg); + regval32 &= ~(1 << clk->enable_bit); + __raw_writel(regval32, clk->enable_reg); +} + +static int omap2_clk_use(struct clk *clk) +{ + int ret = 0; + + if (clk->usecount++ == 0) { + if (likely((u32)clk->parent)) + ret = omap2_clk_use(clk->parent); + + if (unlikely(ret != 0)) { + clk->usecount--; + return ret; + } + + ret = omap2_clk_enable(clk); + + if (unlikely(ret != 0) && clk->parent) { + omap2_clk_unuse(clk->parent); + clk->usecount--; + } + } + + return ret; +} + +static void omap2_clk_unuse(struct clk *clk) +{ + if (clk->usecount > 0 && !(--clk->usecount)) { + omap2_clk_disable(clk); + if (likely((u32)clk->parent)) + omap2_clk_unuse(clk->parent); + } +} + +/* + * Uses the current prcm set to tell if a rate is valid. + * You can go slower, but not faster within a given rate set. + */ +static u32 omap2_dpll_round_rate(unsigned long target_rate) +{ + u32 high, low; + + if ((CM_CLKSEL2_PLL & 0x3) == 1) { /* DPLL clockout */ + high = curr_prcm_set->dpll_speed * 2; + low = curr_prcm_set->dpll_speed; + } else { /* DPLL clockout x 2 */ + high = curr_prcm_set->dpll_speed; + low = curr_prcm_set->dpll_speed / 2; + } + +#ifdef DOWN_VARIABLE_DPLL + if (target_rate > high) + return high; + else + return target_rate; +#else + if (target_rate > low) + return high; + else + return low; +#endif + +} + +/* + * Used for clocks that are part of CLKSEL_xyz governed clocks. + * REVISIT: Maybe change to use clk->enable() functions like on omap1? + */ +static void omap2_clksel_recalc(struct clk * clk) +{ + u32 fixed = 0, div = 0; + + if (clk == &dpll_ck) { + clk->rate = omap2_get_dpll_rate(clk); + fixed = 1; + div = 0; + } + + if (clk == &iva1_mpu_int_ifck) { + div = 2; + fixed = 1; + } + + if ((clk == &dss1_fck) && ((CM_CLKSEL1_CORE & (0x1f << 8)) == 0)) { + clk->rate = sys_ck.rate; + return; + } + + if (!fixed) { + div = omap2_clksel_get_divisor(clk); + if (div == 0) + return; + } + + if (div != 0) { + if (unlikely(clk->rate == clk->parent->rate / div)) + return; + clk->rate = clk->parent->rate / div; + } + + if (unlikely(clk->flags & RATE_PROPAGATES)) + propagate_rate(clk); +} + +/* + * Finds best divider value in an array based on the source and target + * rates. The divider array must be sorted with smallest divider first. + */ +static inline u32 omap2_divider_from_table(u32 size, u32 *div_array, + u32 src_rate, u32 tgt_rate) +{ + int i, test_rate; + + if (div_array == NULL) + return ~1; + + for (i=0; i < size; i++) { + test_rate = src_rate / *div_array; + if (test_rate <= tgt_rate) + return *div_array; + ++div_array; + } + + return ~0; /* No acceptable divider */ +} + +/* + * Find divisor for the given clock and target rate. + * + * Note that this will not work for clocks which are part of CONFIG_PARTICIPANT, + * they are only settable as part of virtual_prcm set. + */ +static u32 omap2_clksel_round_rate(struct clk *tclk, u32 target_rate, + u32 *new_div) +{ + u32 gfx_div[] = {2, 3, 4}; + u32 sysclkout_div[] = {1, 2, 4, 8, 16}; + u32 dss1_div[] = {1, 2, 3, 4, 5, 6, 8, 9, 12, 16}; + u32 vylnq_div[] = {1, 2, 3, 4, 6, 8, 9, 12, 16, 18}; + u32 best_div = ~0, asize = 0; + u32 *div_array = NULL; + + switch (tclk->flags & SRC_RATE_SEL_MASK) { + case CM_GFX_SEL1: + asize = 3; + div_array = gfx_div; + break; + case CM_PLL_SEL1: + return omap2_dpll_round_rate(target_rate); + case CM_SYSCLKOUT_SEL1: + asize = 5; + div_array = sysclkout_div; + break; + case CM_CORE_SEL1: + if(tclk == &dss1_fck){ + if(tclk->parent == &core_ck){ + asize = 10; + div_array = dss1_div; + } else { + *new_div = 0; /* fixed clk */ + return(tclk->parent->rate); + } + } else if((tclk == &vlynq_fck) && cpu_is_omap2420()){ + if(tclk->parent == &core_ck){ + asize = 10; + div_array = vylnq_div; + } else { + *new_div = 0; /* fixed clk */ + return(tclk->parent->rate); + } + } + break; + } + + best_div = omap2_divider_from_table(asize, div_array, + tclk->parent->rate, target_rate); + if (best_div == ~0){ + *new_div = 1; + return best_div; /* signal error */ + } + + *new_div = best_div; + return (tclk->parent->rate / best_div); +} + +/* Given a clock and a rate apply a clock specific rounding function */ +static long omap2_clk_round_rate(struct clk *clk, unsigned long rate) +{ + u32 new_div = 0; + int valid_rate; + + if (clk->flags & RATE_FIXED) + return clk->rate; + + if (clk->flags & RATE_CKCTL) { + valid_rate = omap2_clksel_round_rate(clk, rate, &new_div); + return valid_rate; + } + + if (clk->round_rate != 0) + return clk->round_rate(clk, rate); + + return clk->rate; +} + +/* + * Check the DLL lock state, and return tue if running in unlock mode. + * This is needed to compenste for the shifted DLL value in unlock mode. + */ +static u32 omap2_dll_force_needed(void) +{ + u32 dll_state = SDRC_DLLA_CTRL; /* dlla and dllb are a set */ + + if ((dll_state & (1 << 2)) == (1 << 2)) + return 1; + else + return 0; +} + +static void omap2_init_memory_params(u32 force_lock_to_unlock_mode) +{ + unsigned long dll_cnt; + u32 fast_dll = 0; + + mem_timings.m_type = !((SDRC_MR_0 & 0x3) == 0x1); /* DDR = 1, SDR = 0 */ + + /* 2422 es2.05 and beyond has a single SIP DDR instead of 2 like others. + * In the case of 2422, its ok to use CS1 instead of CS0. + */ + +#if 0 /* FIXME: Enable after 24xx cpu detection works */ + ctype = get_cpu_type(); + if (cpu_is_omap2422()) + mem_timings.base_cs = 1; + else +#endif + mem_timings.base_cs = 0; + + if (mem_timings.m_type != M_DDR) + return; + + /* With DDR we need to determine the low frequency DLL value */ + if (((mem_timings.fast_dll_ctrl & (1 << 2)) == M_LOCK_CTRL)) + mem_timings.dll_mode = M_UNLOCK; + else + mem_timings.dll_mode = M_LOCK; + + if (mem_timings.base_cs == 0) { + fast_dll = SDRC_DLLA_CTRL; + dll_cnt = SDRC_DLLA_STATUS & 0xff00; + } else { + fast_dll = SDRC_DLLB_CTRL; + dll_cnt = SDRC_DLLB_STATUS & 0xff00; + } + if (force_lock_to_unlock_mode) { + fast_dll &= ~0xff00; + fast_dll |= dll_cnt; /* Current lock mode */ + } + mem_timings.fast_dll_ctrl = fast_dll; + + /* No disruptions, DDR will be offline & C-ABI not followed */ + omap2_sram_ddr_init(&mem_timings.slow_dll_ctrl, + mem_timings.fast_dll_ctrl, + mem_timings.base_cs, + force_lock_to_unlock_mode); + mem_timings.slow_dll_ctrl &= 0xff00; /* Keep lock value */ + + /* Turn status into unlock ctrl */ + mem_timings.slow_dll_ctrl |= + ((mem_timings.fast_dll_ctrl & 0xF) | (1 << 2)); + + /* 90 degree phase for anything below 133Mhz */ + mem_timings.slow_dll_ctrl |= (1 << 1); +} + +static u32 omap2_reprogram_sdrc(u32 level, u32 force) +{ + u32 prev = curr_perf_level, flags; + + if ((curr_perf_level == level) && !force) + return prev; + + if (level == PRCM_HALF_SPEED) { + local_irq_save(flags); + PRCM_VOLTSETUP = 0xffff; + omap2_sram_reprogram_sdrc(PRCM_HALF_SPEED, + mem_timings.slow_dll_ctrl, + mem_timings.m_type); + curr_perf_level = PRCM_HALF_SPEED; + local_irq_restore(flags); + } + if (level == PRCM_FULL_SPEED) { + local_irq_save(flags); + PRCM_VOLTSETUP = 0xffff; + omap2_sram_reprogram_sdrc(PRCM_FULL_SPEED, + mem_timings.fast_dll_ctrl, + mem_timings.m_type); + curr_perf_level = PRCM_FULL_SPEED; + local_irq_restore(flags); + } + + return prev; +} + +static int omap2_reprogram_dpll(struct clk * clk, unsigned long rate) +{ + u32 flags, cur_rate, low, mult, div, valid_rate, done_rate; + u32 bypass = 0; + struct prcm_config tmpset; + int ret = -EINVAL; + + local_irq_save(flags); + cur_rate = omap2_get_dpll_rate(&dpll_ck); + mult = CM_CLKSEL2_PLL & 0x3; + + if ((rate == (cur_rate / 2)) && (mult == 2)) { + omap2_reprogram_sdrc(PRCM_HALF_SPEED, 1); + } else if ((rate == (cur_rate * 2)) && (mult == 1)) { + omap2_reprogram_sdrc(PRCM_FULL_SPEED, 1); + } else if (rate != cur_rate) { + valid_rate = omap2_dpll_round_rate(rate); + if (valid_rate != rate) + goto dpll_exit; + + if ((CM_CLKSEL2_PLL & 0x3) == 1) + low = curr_prcm_set->dpll_speed; + else + low = curr_prcm_set->dpll_speed / 2; + + tmpset.cm_clksel1_pll = CM_CLKSEL1_PLL; + tmpset.cm_clksel1_pll &= ~(0x3FFF << 8); + div = ((curr_prcm_set->xtal_speed / 1000000) - 1); + tmpset.cm_clksel2_pll = CM_CLKSEL2_PLL; + tmpset.cm_clksel2_pll &= ~0x3; + if (rate > low) { + tmpset.cm_clksel2_pll |= 0x2; + mult = ((rate / 2) / 1000000); + done_rate = PRCM_FULL_SPEED; + } else { + tmpset.cm_clksel2_pll |= 0x1; + mult = (rate / 1000000); + done_rate = PRCM_HALF_SPEED; + } + tmpset.cm_clksel1_pll |= ((div << 8) | (mult << 12)); + + /* Worst case */ + tmpset.base_sdrc_rfr = V24XX_SDRC_RFR_CTRL_BYPASS; + + if (rate == curr_prcm_set->xtal_speed) /* If asking for 1-1 */ + bypass = 1; + + omap2_reprogram_sdrc(PRCM_FULL_SPEED, 1); /* For init_mem */ + + /* Force dll lock mode */ + omap2_set_prcm(tmpset.cm_clksel1_pll, tmpset.base_sdrc_rfr, + bypass); + + /* Errata: ret dll entry state */ + omap2_init_memory_params(omap2_dll_force_needed()); + omap2_reprogram_sdrc(done_rate, 0); + } + omap2_clksel_recalc(&dpll_ck); + ret = 0; + +dpll_exit: + local_irq_restore(flags); + return(ret); +} + +/* Just return the MPU speed */ +static void omap2_mpu_recalc(struct clk * clk) +{ + clk->rate = curr_prcm_set->mpu_speed; +} + +/* + * Look for a rate equal or less than the target rate given a configuration set. + * + * What's not entirely clear is "which" field represents the key field. + * Some might argue L3-DDR, others ARM, others IVA. This code is simple and + * just uses the ARM rates. + */ +static long omap2_round_to_table_rate(struct clk * clk, unsigned long rate) +{ + struct prcm_config * ptr; + long highest_rate; + + if (clk != &virt_prcm_set) + return -EINVAL; + + highest_rate = -EINVAL; + + for (ptr = rate_table; ptr->mpu_speed; ptr++) { + if (ptr->xtal_speed != sys_ck.rate) + continue; + + highest_rate = ptr->mpu_speed; + + /* Can check only after xtal frequency check */ + if (ptr->mpu_speed <= rate) + break; + } + return highest_rate; +} + +/* + * omap2_convert_field_to_div() - turn field value into integer divider + */ +static u32 omap2_clksel_to_divisor(u32 div_sel, u32 field_val) +{ + u32 i; + u32 clkout_array[] = {1, 2, 4, 8, 16}; + + if ((div_sel & SRC_RATE_SEL_MASK) == CM_SYSCLKOUT_SEL1) { + for (i = 0; i < 5; i++) { + if (field_val == i) + return clkout_array[i]; + } + return ~0; + } else + return field_val; +} + +/* + * Returns the CLKSEL divider register value + * REVISIT: This should be cleaned up to work nicely with void __iomem * + */ +static u32 omap2_get_clksel(u32 *div_sel, u32 *field_mask, + struct clk *clk) +{ + int ret = ~0; + u32 reg_val, div_off; + u32 div_addr = 0; + u32 mask = ~0; + + div_off = clk->rate_offset; + + switch ((*div_sel & SRC_RATE_SEL_MASK)) { + case CM_MPU_SEL1: + div_addr = (u32)&CM_CLKSEL_MPU; + mask = 0x1f; + break; + case CM_DSP_SEL1: + div_addr = (u32)&CM_CLKSEL_DSP; + if (cpu_is_omap2420()) { + if ((div_off == 0) || (div_off == 8)) + mask = 0x1f; + else if (div_off == 5) + mask = 0x3; + } else if (cpu_is_omap2430()) { + if (div_off == 0) + mask = 0x1f; + else if (div_off == 5) + mask = 0x3; + } + break; + case CM_GFX_SEL1: + div_addr = (u32)&CM_CLKSEL_GFX; + if (div_off == 0) + mask = 0x7; + break; + case CM_MODEM_SEL1: + div_addr = (u32)&CM_CLKSEL_MDM; + if (div_off == 0) + mask = 0xf; + break; + case CM_SYSCLKOUT_SEL1: + div_addr = (u32)&PRCM_CLKOUT_CTRL; + if ((div_off == 3) || (div_off = 11)) + mask= 0x3; + break; + case CM_CORE_SEL1: + div_addr = (u32)&CM_CLKSEL1_CORE; + switch (div_off) { + case 0: /* l3 */ + case 8: /* dss1 */ + case 15: /* vylnc-2420 */ + case 20: /* ssi */ + mask = 0x1f; break; + case 5: /* l4 */ + mask = 0x3; break; + case 13: /* dss2 */ + mask = 0x1; break; + case 25: /* usb */ + mask = 0xf; break; + } + } + + *field_mask = mask; + + if (unlikely(mask == ~0)) + div_addr = 0; + + *div_sel = div_addr; + + if (unlikely(div_addr == 0)) + return ret; + + /* Isolate field */ + reg_val = __raw_readl((void __iomem *)div_addr) & (mask << div_off); + + /* Normalize back to divider value */ + reg_val >>= div_off; + + return reg_val; +} + +/* + * Return divider to be applied to parent clock. + * Return 0 on error. + */ +static u32 omap2_clksel_get_divisor(struct clk *clk) +{ + int ret = 0; + u32 div, div_sel, div_off, field_mask, field_val; + + /* isolate control register */ + div_sel = (SRC_RATE_SEL_MASK & clk->flags); + + div_off = clk->rate_offset; + field_val = omap2_get_clksel(&div_sel, &field_mask, clk); + if (div_sel == 0) + return ret; + + div_sel = (SRC_RATE_SEL_MASK & clk->flags); + div = omap2_clksel_to_divisor(div_sel, field_val); + + return div; +} + +/* Set the clock rate for a clock source */ +static int omap2_clk_set_rate(struct clk *clk, unsigned long rate) + +{ + int ret = -EINVAL; + void __iomem * reg; + u32 div_sel, div_off, field_mask, field_val, reg_val, validrate; + u32 new_div = 0; + + if (!(clk->flags & CONFIG_PARTICIPANT) && (clk->flags & RATE_CKCTL)) { + if (clk == &dpll_ck) + return omap2_reprogram_dpll(clk, rate); + + /* Isolate control register */ + div_sel = (SRC_RATE_SEL_MASK & clk->flags); + div_off = clk->src_offset; + + validrate = omap2_clksel_round_rate(clk, rate, &new_div); + if(validrate != rate) + return(ret); + + field_val = omap2_get_clksel(&div_sel, &field_mask, clk); + if (div_sel == 0) + return ret; + + if(clk->flags & CM_SYSCLKOUT_SEL1){ + switch(new_div){ + case 16: field_val = 4; break; + case 8: field_val = 3; break; + case 4: field_val = 2; break; + case 2: field_val = 1; break; + case 1: field_val = 0; break; + } + } + else + field_val = new_div; + + reg = (void __iomem *)div_sel; + + reg_val = __raw_readl(reg); + reg_val &= ~(field_mask << div_off); + reg_val |= (field_val << div_off); + + __raw_writel(reg_val, reg); + clk->rate = clk->parent->rate / field_val; + + if (clk->flags & DELAYED_APP) + __raw_writel(0x1, (void __iomem *)&PRCM_CLKCFG_CTRL); + ret = 0; + } else if (clk->set_rate != 0) + ret = clk->set_rate(clk, rate); + + if (unlikely(ret == 0 && (clk->flags & RATE_PROPAGATES))) + propagate_rate(clk); + + return ret; +} + +/* Converts encoded control register address into a full address */ +static u32 omap2_get_src_field(u32 *type_to_addr, u32 reg_offset, + struct clk *src_clk, u32 *field_mask) +{ + u32 val = ~0, src_reg_addr = 0, mask = 0; + + /* Find target control register.*/ + switch ((*type_to_addr & SRC_RATE_SEL_MASK)) { + case CM_CORE_SEL1: + src_reg_addr = (u32)&CM_CLKSEL1_CORE; + if (reg_offset == 13) { /* DSS2_fclk */ + mask = 0x1; + if (src_clk == &sys_ck) + val = 0; + if (src_clk == &func_48m_ck) + val = 1; + } else if (reg_offset == 8) { /* DSS1_fclk */ + mask = 0x1f; + if (src_clk == &sys_ck) + val = 0; + else if (src_clk == &core_ck) /* divided clock */ + val = 0x10; /* rate needs fixing */ + } else if ((reg_offset == 15) && cpu_is_omap2420()){ /*vlnyq*/ + mask = 0x1F; + if(src_clk == &func_96m_ck) + val = 0; + else if (src_clk == &core_ck) + val = 0x10; + } + break; + case CM_CORE_SEL2: + src_reg_addr = (u32)&CM_CLKSEL2_CORE; + mask = 0x3; + if (src_clk == &func_32k_ck) + val = 0x0; + if (src_clk == &sys_ck) + val = 0x1; + if (src_clk == &alt_ck) + val = 0x2; + break; + case CM_WKUP_SEL1: + src_reg_addr = (u32)&CM_CLKSEL2_CORE; + mask = 0x3; + if (src_clk == &func_32k_ck) + val = 0x0; + if (src_clk == &sys_ck) + val = 0x1; + if (src_clk == &alt_ck) + val = 0x2; + break; + case CM_PLL_SEL1: + src_reg_addr = (u32)&CM_CLKSEL1_PLL; + mask = 0x1; + if (reg_offset == 0x3) { + if (src_clk == &apll96_ck) + val = 0; + if (src_clk == &alt_ck) + val = 1; + } + else if (reg_offset == 0x5) { + if (src_clk == &apll54_ck) + val = 0; + if (src_clk == &alt_ck) + val = 1; + } + break; + case CM_PLL_SEL2: + src_reg_addr = (u32)&CM_CLKSEL2_PLL; + mask = 0x3; + if (src_clk == &func_32k_ck) + val = 0x0; + if (src_clk == &dpll_ck) + val = 0x2; + break; + case CM_SYSCLKOUT_SEL1: + src_reg_addr = (u32)&PRCM_CLKOUT_CTRL; + mask = 0x3; + if (src_clk == &dpll_ck) + val = 0; + if (src_clk == &sys_ck) + val = 1; + if (src_clk == &func_54m_ck) + val = 2; + if (src_clk == &func_96m_ck) + val = 3; + break; + } + + if (val == ~0) /* Catch errors in offset */ + *type_to_addr = 0; + else + *type_to_addr = src_reg_addr; + *field_mask = mask; + + return val; +} + +static int omap2_clk_set_parent(struct clk *clk, struct clk *new_parent) +{ + void __iomem * reg; + u32 src_sel, src_off, field_val, field_mask, reg_val, rate; + int ret = -EINVAL; + + if (unlikely(clk->flags & CONFIG_PARTICIPANT)) + return ret; + + if (clk->flags & SRC_SEL_MASK) { /* On-chip SEL collection */ + src_sel = (SRC_RATE_SEL_MASK & clk->flags); + src_off = clk->src_offset; + + if (src_sel == 0) + goto set_parent_error; + + field_val = omap2_get_src_field(&src_sel, src_off, new_parent, + &field_mask); + + reg = (void __iomem *)src_sel; + + if (clk->usecount > 0) + omap2_clk_disable(clk); + + /* Set new source value (previous dividers if any in effect) */ + reg_val = __raw_readl(reg) & ~(field_mask << src_off); + reg_val |= (field_val << src_off); + __raw_writel(reg_val, reg); + + if (clk->flags & DELAYED_APP) + __raw_writel(0x1, (void __iomem *)&PRCM_CLKCFG_CTRL); + + if (clk->usecount > 0) + omap2_clk_enable(clk); + + clk->parent = new_parent; + + /* SRC_RATE_SEL_MASK clocks follow their parents rates.*/ + if ((new_parent == &core_ck) && (clk == &dss1_fck)) + clk->rate = new_parent->rate / 0x10; + else + clk->rate = new_parent->rate; + + if (unlikely(clk->flags & RATE_PROPAGATES)) + propagate_rate(clk); + + return 0; + } else { + clk->parent = new_parent; + rate = new_parent->rate; + omap2_clk_set_rate(clk, rate); + ret = 0; + } + + set_parent_error: + return ret; +} + +/* Sets basic clocks based on the specified rate */ +static int omap2_select_table_rate(struct clk * clk, unsigned long rate) +{ + u32 flags, cur_rate, done_rate, bypass = 0; + u8 cpu_mask = 0; + struct prcm_config *prcm; + unsigned long found_speed = 0; + + if (clk != &virt_prcm_set) + return -EINVAL; + + /* FIXME: Change cpu_is_omap2420() to cpu_is_omap242x() */ + if (cpu_is_omap2420()) + cpu_mask = RATE_IN_242X; + else if (cpu_is_omap2430()) + cpu_mask = RATE_IN_243X; + + for (prcm = rate_table; prcm->mpu_speed; prcm++) { + if (!(prcm->flags & cpu_mask)) + continue; + + if (prcm->xtal_speed != sys_ck.rate) + continue; + + if (prcm->mpu_speed <= rate) { + found_speed = prcm->mpu_speed; + break; + } + } + + if (!found_speed) { + printk(KERN_INFO "Could not set MPU rate to %luMHz\n", + rate / 1000000); + return -EINVAL; + } + + curr_prcm_set = prcm; + cur_rate = omap2_get_dpll_rate(&dpll_ck); + + if (prcm->dpll_speed == cur_rate / 2) { + omap2_reprogram_sdrc(PRCM_HALF_SPEED, 1); + } else if (prcm->dpll_speed == cur_rate * 2) { + omap2_reprogram_sdrc(PRCM_FULL_SPEED, 1); + } else if (prcm->dpll_speed != cur_rate) { + local_irq_save(flags); + + if (prcm->dpll_speed == prcm->xtal_speed) + bypass = 1; + + if ((prcm->cm_clksel2_pll & 0x3) == 2) + done_rate = PRCM_FULL_SPEED; + else + done_rate = PRCM_HALF_SPEED; + + /* MPU divider */ + CM_CLKSEL_MPU = prcm->cm_clksel_mpu; + + /* dsp + iva1 div(2420), iva2.1(2430) */ + CM_CLKSEL_DSP = prcm->cm_clksel_dsp; + + CM_CLKSEL_GFX = prcm->cm_clksel_gfx; + + /* Major subsystem dividers */ + CM_CLKSEL1_CORE = prcm->cm_clksel1_core; + if (cpu_is_omap2430()) + CM_CLKSEL_MDM = prcm->cm_clksel_mdm; + + /* x2 to enter init_mem */ + omap2_reprogram_sdrc(PRCM_FULL_SPEED, 1); + + omap2_set_prcm(prcm->cm_clksel1_pll, prcm->base_sdrc_rfr, + bypass); + + omap2_init_memory_params(omap2_dll_force_needed()); + omap2_reprogram_sdrc(done_rate, 0); + + local_irq_restore(flags); + } + omap2_clksel_recalc(&dpll_ck); + + return 0; +} + +/*------------------------------------------------------------------------- + * Omap2 clock reset and init functions + *-------------------------------------------------------------------------*/ + +static struct clk_functions omap2_clk_functions = { + .clk_enable = omap2_clk_enable, + .clk_disable = omap2_clk_disable, + .clk_use = omap2_clk_use, + .clk_unuse = omap2_clk_unuse, + .clk_round_rate = omap2_clk_round_rate, + .clk_set_rate = omap2_clk_set_rate, + .clk_set_parent = omap2_clk_set_parent, +}; + +static void __init omap2_get_crystal_rate(struct clk *osc, struct clk *sys) +{ + u32 div, aplls, sclk = 13000000; + + aplls = CM_CLKSEL1_PLL; + aplls &= ((1 << 23) | (1 << 24) | (1 << 25)); + aplls >>= 23; /* Isolate field, 0,2,3 */ + + if (aplls == 0) + sclk = 19200000; + else if (aplls == 2) + sclk = 13000000; + else if (aplls == 3) + sclk = 12000000; + + div = PRCM_CLKSRC_CTRL; + div &= ((1 << 7) | (1 << 6)); + div >>= sys->rate_offset; + + osc->rate = sclk * div; + sys->rate = sclk; +} + +#ifdef CONFIG_OMAP_RESET_CLOCKS +static void __init omap2_disable_unused_clocks(void) +{ + struct clk *ck; + u32 regval32; + + list_for_each_entry(ck, &clocks, node) { + if (ck->usecount > 0 || (ck->flags & ALWAYS_ENABLED) || + ck->enable_reg == 0) + continue; + + regval32 = __raw_readl(ck->enable_reg); + if ((regval32 & (1 << ck->enable_bit)) == 0) + continue; + + printk(KERN_INFO "Disabling unused clock \"%s\"\n", ck->name); + omap2_clk_disable(ck); + } +} +late_initcall(omap2_disable_unused_clocks); +#endif + +/* + * Switch the MPU rate if specified on cmdline. + * We cannot do this early until cmdline is parsed. + */ +static int __init omap2_clk_arch_init(void) +{ + if (!mpurate) + return -EINVAL; + + if (omap2_select_table_rate(&virt_prcm_set, mpurate)) + printk(KERN_ERR "Could not find matching MPU rate\n"); + + propagate_rate(&osc_ck); /* update main root fast */ + propagate_rate(&func_32k_ck); /* update main root slow */ + + printk(KERN_INFO "Switched to new clocking rate (Crystal/DPLL/MPU): " + "%ld.%01ld/%ld/%ld MHz\n", + (sys_ck.rate / 1000000), (sys_ck.rate / 100000) % 10, + (dpll_ck.rate / 1000000), (mpu_ck.rate / 1000000)) ; + + return 0; +} +arch_initcall(omap2_clk_arch_init); + +int __init omap2_clk_init(void) +{ + struct prcm_config *prcm; + struct clk ** clkp; + u32 clkrate; + + clk_init(&omap2_clk_functions); + omap2_get_crystal_rate(&osc_ck, &sys_ck); + + for (clkp = onchip_clks; clkp < onchip_clks + ARRAY_SIZE(onchip_clks); + clkp++) { + + if ((*clkp)->flags & CLOCK_IN_OMAP242X && cpu_is_omap2420()) { + clk_register(*clkp); + continue; + } + + if ((*clkp)->flags & CLOCK_IN_OMAP243X && cpu_is_omap2430()) { + clk_register(*clkp); + continue; + } + } + + /* Check the MPU rate set by bootloader */ + clkrate = omap2_get_dpll_rate(&dpll_ck); + for (prcm = rate_table; prcm->mpu_speed; prcm++) { + if (prcm->xtal_speed != sys_ck.rate) + continue; + if (prcm->dpll_speed <= clkrate) + break; + } + curr_prcm_set = prcm; + + propagate_rate(&osc_ck); /* update main root fast */ + propagate_rate(&func_32k_ck); /* update main root slow */ + + printk(KERN_INFO "Clocking rate (Crystal/DPLL/MPU): " + "%ld.%01ld/%ld/%ld MHz\n", + (sys_ck.rate / 1000000), (sys_ck.rate / 100000) % 10, + (dpll_ck.rate / 1000000), (mpu_ck.rate / 1000000)) ; + + /* + * Only enable those clocks we will need, let the drivers + * enable other clocks as necessary + */ + clk_use(&sync_32k_ick); + clk_use(&omapctrl_ick); + if (cpu_is_omap2430()) + clk_use(&sdrc_ick); + + return 0; +} |