/* * linux/arch/arm/mach-omap1/clock.c * * Copyright (C) 2004 - 2005, 2009-2010 Nokia Corporation * Written by Tuukka Tikkanen * * Modified to use omap shared clock framework by * Tony Lindgren * * 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 #include #include #include #include #include #include #include #include #include #include "soc.h" #include "iomap.h" #include "clock.h" #include "opp.h" #include "sram.h" __u32 arm_idlect1_mask; struct clk *api_ck_p, *ck_dpll1_p, *ck_ref_p; static LIST_HEAD(clocks); static DEFINE_MUTEX(clocks_mutex); static DEFINE_SPINLOCK(clockfw_lock); /* * Omap1 specific clock functions */ unsigned long omap1_uart_recalc(struct clk *clk) { unsigned int val = __raw_readl(clk->enable_reg); return val & clk->enable_bit ? 48000000 : 12000000; } unsigned long omap1_sossi_recalc(struct clk *clk) { u32 div = omap_readl(MOD_CONF_CTRL_1); div = (div >> 17) & 0x7; div++; return clk->parent->rate / div; } static void omap1_clk_allow_idle(struct clk *clk) { struct arm_idlect1_clk * iclk = (struct arm_idlect1_clk *)clk; if (!(clk->flags & CLOCK_IDLE_CONTROL)) return; if (iclk->no_idle_count > 0 && !(--iclk->no_idle_count)) arm_idlect1_mask |= 1 << iclk->idlect_shift; } static void omap1_clk_deny_idle(struct clk *clk) { struct arm_idlect1_clk * iclk = (struct arm_idlect1_clk *)clk; if (!(clk->flags & CLOCK_IDLE_CONTROL)) return; if (iclk->no_idle_count++ == 0) arm_idlect1_mask &= ~(1 << iclk->idlect_shift); } static __u16 verify_ckctl_value(__u16 newval) { /* This function checks for following limitations set * by the hardware (all conditions must be true): * DSPMMU_CK == DSP_CK or DSPMMU_CK == DSP_CK/2 * ARM_CK >= TC_CK * DSP_CK >= TC_CK * DSPMMU_CK >= TC_CK * * In addition following rules are enforced: * LCD_CK <= TC_CK * ARMPER_CK <= TC_CK * * However, maximum frequencies are not checked for! */ __u8 per_exp; __u8 lcd_exp; __u8 arm_exp; __u8 dsp_exp; __u8 tc_exp; __u8 dspmmu_exp; per_exp = (newval >> CKCTL_PERDIV_OFFSET) & 3; lcd_exp = (newval >> CKCTL_LCDDIV_OFFSET) & 3; arm_exp = (newval >> CKCTL_ARMDIV_OFFSET) & 3; dsp_exp = (newval >> CKCTL_DSPDIV_OFFSET) & 3; tc_exp = (newval >> CKCTL_TCDIV_OFFSET) & 3; dspmmu_exp = (newval >> CKCTL_DSPMMUDIV_OFFSET) & 3; if (dspmmu_exp < dsp_exp) dspmmu_exp = dsp_exp; if (dspmmu_exp > dsp_exp+1) dspmmu_exp = dsp_exp+1; if (tc_exp < arm_exp) tc_exp = arm_exp; if (tc_exp < dspmmu_exp) tc_exp = dspmmu_exp; if (tc_exp > lcd_exp) lcd_exp = tc_exp; if (tc_exp > per_exp) per_exp = tc_exp; newval &= 0xf000; newval |= per_exp << CKCTL_PERDIV_OFFSET; newval |= lcd_exp << CKCTL_LCDDIV_OFFSET; newval |= arm_exp << CKCTL_ARMDIV_OFFSET; newval |= dsp_exp << CKCTL_DSPDIV_OFFSET; newval |= tc_exp << CKCTL_TCDIV_OFFSET; newval |= dspmmu_exp << CKCTL_DSPMMUDIV_OFFSET; return newval; } static int calc_dsor_exp(struct clk *clk, unsigned long rate) { /* Note: If target frequency is too low, this function will return 4, * which is invalid value. Caller must check for this value and act * accordingly. * * Note: This function does not check for following limitations set * by the hardware (all conditions must be true): * DSPMMU_CK == DSP_CK or DSPMMU_CK == DSP_CK/2 * ARM_CK >= TC_CK * DSP_CK >= TC_CK * DSPMMU_CK >= TC_CK */ unsigned long realrate; struct clk * parent; unsigned dsor_exp; parent = clk->parent; if (unlikely(parent == NULL)) return -EIO; realrate = parent->rate; for (dsor_exp=0; dsor_exp<4; dsor_exp++) { if (realrate <= rate) break; realrate /= 2; } return dsor_exp; } unsigned long omap1_ckctl_recalc(struct clk *clk) { /* Calculate divisor encoded as 2-bit exponent */ int dsor = 1 << (3 & (omap_readw(ARM_CKCTL) >> clk->rate_offset)); return clk->parent->rate / dsor; } unsigned long omap1_ckctl_recalc_dsp_domain(struct clk *clk) { int dsor; /* Calculate divisor encoded as 2-bit exponent * * The clock control bits are in DSP domain, * so api_ck is needed for access. * Note that DSP_CKCTL virt addr = phys addr, so * we must use __raw_readw() instead of omap_readw(). */ omap1_clk_enable(api_ck_p); dsor = 1 << (3 & (__raw_readw(DSP_CKCTL) >> clk->rate_offset)); omap1_clk_disable(api_ck_p); return clk->parent->rate / dsor; } /* MPU virtual clock functions */ int omap1_select_table_rate(struct clk *clk, unsigned long rate) { /* Find the highest supported frequency <= rate and switch to it */ struct mpu_rate * ptr; unsigned long ref_rate; ref_rate = ck_ref_p->rate; for (ptr = omap1_rate_table; ptr->rate; ptr++) { if (!(ptr->flags & cpu_mask)) continue; if (ptr->xtal != ref_rate) continue; /* Can check only after xtal frequency check */ if (ptr->rate <= rate) break; } if (!ptr->rate) return -EINVAL; /* * In most cases we should not need to reprogram DPLL. * Reprogramming the DPLL is tricky, it must be done from SRAM. */ omap_sram_reprogram_clock(ptr->dpllctl_val, ptr->ckctl_val); /* XXX Do we need to recalculate the tree below DPLL1 at this point? */ ck_dpll1_p->rate = ptr->pll_rate; return 0; } int omap1_clk_set_rate_dsp_domain(struct clk *clk, unsigned long rate) { int dsor_exp; u16 regval; dsor_exp = calc_dsor_exp(clk, rate); if (dsor_exp > 3) dsor_exp = -EINVAL; if (dsor_exp < 0) return dsor_exp; regval = __raw_readw(DSP_CKCTL); regval &= ~(3 << clk->rate_offset); regval |= dsor_exp << clk->rate_offset; __raw_writew(regval, DSP_CKCTL); clk->rate = clk->parent->rate / (1 << dsor_exp); return 0; } long omap1_clk_round_rate_ckctl_arm(struct clk *clk, unsigned long rate) { int dsor_exp = calc_dsor_exp(clk, rate); if (dsor_exp < 0) return dsor_exp; if (dsor_exp > 3) dsor_exp = 3; return clk->parent->rate / (1 << dsor_exp); } int omap1_clk_set_rate_ckctl_arm(struct clk *clk, unsigned long rate) { int dsor_exp; u16 regval; dsor_exp = calc_dsor_exp(clk, rate); if (dsor_exp > 3) dsor_exp = -EINVAL; if (dsor_exp < 0) return dsor_exp; regval = omap_readw(ARM_CKCTL); regval &= ~(3 << clk->rate_offset); regval |= dsor_exp << clk->rate_offset; regval = verify_ckctl_value(regval); omap_writew(regval, ARM_CKCTL); clk->rate = clk->parent->rate / (1 << dsor_exp); return 0; } long omap1_round_to_table_rate(struct clk *clk, unsigned long rate) { /* Find the highest supported frequency <= rate */ struct mpu_rate * ptr; long highest_rate; unsigned long ref_rate; ref_rate = ck_ref_p->rate; highest_rate = -EINVAL; for (ptr = omap1_rate_table; ptr->rate; ptr++) { if (!(ptr->flags & cpu_mask)) continue; if (ptr->xtal != ref_rate) continue; highest_rate = ptr->rate; /* Can check only after xtal frequency check */ if (ptr->rate <= rate) break; } return highest_rate; } static unsigned calc_ext_dsor(unsigned long rate) { unsigned dsor;