/* * Clock interface for OMAP * * Copyright (C) 2001 RidgeRun, Inc * Written by Gordon McNutt * Updated 2004 for Linux 2.6 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 as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * 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., * 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include /* Input clock in MHz */ static unsigned int source_clock = 12; /* * We use one spinlock for all clock registers for now. We may want to * change this to be clock register specific later on. Before we can do * that, we need to map out the shared clock registers. */ static spinlock_t clock_lock = SPIN_LOCK_UNLOCKED; typedef struct { char *name; __u8 flags; ck_t parent; unsigned long rate_reg; /* Clock rate register */ unsigned long enbl_reg; /* Enable register */ unsigned long idle_reg; /* Idle register */ unsigned long slct_reg; /* Select register */ __s8 rate_shift; /* Clock rate bit shift */ __s8 enbl_shift; /* Clock enable bit shift */ __s8 idle_shift; /* Clock idle bit shift */ __s8 slct_shift; /* Clock select bit shift */ } ck_info_t; #define CK_NAME(ck) ck_info_table[ck].name #define CK_FLAGS(ck) ck_info_table[ck].flags #define CK_PARENT(ck) ck_info_table[ck].parent #define CK_RATE_REG(ck) ck_info_table[ck].rate_reg #define CK_ENABLE_REG(ck) ck_info_table[ck].enbl_reg #define CK_IDLE_REG(ck) ck_info_table[ck].idle_reg #define CK_SELECT_REG(ck) ck_info_table[ck].slct_reg #define CK_RATE_SHIFT(ck) ck_info_table[ck].rate_shift #define CK_ENABLE_SHIFT(ck) ck_info_table[ck].enbl_shift #define CK_IDLE_SHIFT(ck) ck_info_table[ck].idle_shift #define CK_SELECT_SHIFT(ck) ck_info_table[ck].slct_shift #define CK_CAN_CHANGE_RATE(cl) (CK_FLAGS(ck) & CK_RATEF) #define CK_CAN_DISABLE(cl) (CK_FLAGS(ck) & CK_ENABLEF) #define CK_CAN_IDLE(cl) (CK_FLAGS(ck) & CK_IDLEF) #define CK_CAN_SWITCH(cl) (CK_FLAGS(ck) & CK_SELECTF) static ck_info_t ck_info_table[] = { { .name = "clkin", .flags = 0, .parent = OMAP_CLKIN, }, { .name = "ck_gen1", .flags = CK_RATEF | CK_IDLEF, .rate_reg = DPLL_CTL, .idle_reg = ARM_IDLECT1, .idle_shift = IDLDPLL_ARM, .parent = OMAP_CLKIN, }, { .name = "ck_gen2", .flags = 0, .parent = OMAP_CK_GEN1, }, { .name = "ck_gen3", .flags = 0, .parent = OMAP_CK_GEN1, }, { .name = "tc_ck", .flags = CK_RATEF | CK_IDLEF, .parent = OMAP_CK_GEN3, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[TCDIV(9:8)] */ .idle_reg = ARM_IDLECT1, .rate_shift = TCDIV, .idle_shift = IDLIF_ARM }, { .name = "arm_ck", .flags = CK_IDLEF | CK_RATEF, .parent = OMAP_CK_GEN1, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[ARMDIV(5:4)] */ .idle_reg = ARM_IDLECT1, .rate_shift = ARMDIV, .idle_shift = SETARM_IDLE, }, { .name = "mpuper_ck", .flags = CK_RATEF | CK_IDLEF | CK_ENABLEF, .parent = OMAP_CK_GEN1, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[PERDIV(1:0)] */ .enbl_reg = ARM_IDLECT2, .idle_reg = ARM_IDLECT1, .rate_shift = PERDIV, .enbl_shift = EN_PERCK, .idle_shift = IDLPER_ARM }, { .name = "arm_gpio_ck", .flags = CK_ENABLEF, .parent = OMAP_CK_GEN1, .enbl_reg = ARM_IDLECT2, .enbl_shift = EN_GPIOCK }, { .name = "mpuxor_ck", .flags = CK_ENABLEF | CK_IDLEF, .parent = OMAP_CLKIN, .idle_reg = ARM_IDLECT1, .enbl_reg = ARM_IDLECT2, .idle_shift = IDLXORP_ARM, .enbl_shift = EN_XORPCK }, { .name = "mputim_ck", .flags = CK_IDLEF | CK_ENABLEF | CK_SELECTF, .parent = OMAP_CLKIN, .idle_reg = ARM_IDLECT1, .enbl_reg = ARM_IDLECT2, .slct_reg = ARM_CKCTL, .idle_shift = IDLTIM_ARM, .enbl_shift = EN_TIMCK, .slct_shift = ARM_TIMXO }, { .name = "mpuwd_ck", .flags = CK_IDLEF | CK_ENABLEF, .parent = OMAP_CLKIN, .idle_reg = ARM_IDLECT1, .enbl_reg = ARM_IDLECT2, .idle_shift = IDLWDT_ARM, .enbl_shift = EN_WDTCK, }, { .name = "dsp_ck", .flags = CK_RATEF | CK_ENABLEF, .parent = OMAP_CK_GEN2, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[DSPDIV(7:6)] */ .enbl_reg = ARM_CKCTL, .rate_shift = DSPDIV, .enbl_shift = EN_DSPCK, }, { .name = "dspmmu_ck", .flags = CK_RATEF | CK_ENABLEF, .parent = OMAP_CK_GEN2, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[DSPMMUDIV(11:10)] */ .enbl_reg = ARM_CKCTL, .rate_shift = DSPMMUDIV, .enbl_shift = EN_DSPCK, }, { .name = "dma_ck", .flags = CK_RATEF | CK_IDLEF | CK_ENABLEF, .parent = OMAP_CK_GEN3, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[TCDIV(9:8)] */ .idle_reg = ARM_IDLECT1, .enbl_reg = ARM_IDLECT2, .rate_shift = TCDIV, .idle_shift = IDLIF_ARM, .enbl_shift = DMACK_REQ }, { .name = "api_ck", .flags = CK_RATEF | CK_IDLEF | CK_ENABLEF, .parent = OMAP_CK_GEN3, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[TCDIV(9:8)] */ .idle_reg = ARM_IDLECT1, .enbl_reg = ARM_IDLECT2, .rate_shift = TCDIV, .idle_shift = IDLAPI_ARM, .enbl_shift = EN_APICK, }, { .name = "hsab_ck", .flags = CK_RATEF | CK_IDLEF | CK_ENABLEF, .parent = OMAP_CK_GEN3, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[TCDIV(9:8)] */ .idle_reg = ARM_IDLECT1, .enbl_reg = ARM_IDLECT2, .rate_shift = TCDIV, .idle_shift = IDLHSAB_ARM, .enbl_shift = EN_HSABCK, }, { .name = "lbfree_ck", .flags = CK_RATEF | CK_ENABLEF, .parent = OMAP_CK_GEN3, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[TCDIV(9:8)] */ .enbl_reg = ARM_IDLECT2, .rate_shift = TCDIV, .enbl_shift = EN_LBFREECK, }, { .name = "lb_ck", .flags = CK_RATEF | CK_IDLEF | CK_ENABLEF, .parent = OMAP_CK_GEN3, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[TCDIV(9:8)] */ .idle_reg = ARM_IDLECT1, .enbl_reg = ARM_IDLECT2, .rate_shift = TCDIV, .idle_shift = IDLLB_ARM, .enbl_shift = EN_LBCK, }, { .name = "lcd_ck", .flags = CK_RATEF | CK_IDLEF | CK_ENABLEF, .parent = OMAP_CK_GEN3, .rate_reg = ARM_CKCTL, /* ARM_CKCTL[LCDDIV(3:2)] */ .idle_reg = ARM_IDLECT1, .enbl_reg = ARM_IDLECT2, .rate_shift = LCDDIV, .idle_shift = IDLLCD_ARM, .enbl_shift = EN_LCDCK, }, }; /*****************************************************************************/ #define CK_IN_RANGE(ck) (!((ck < OMAP_CK_MIN) || (ck > OMAP_CK_MAX))) int ck_auto_unclock = 1; int ck_debug = 0; #define CK_MAX_PLL_FREQ OMAP_CK_MAX_RATE static __u8 ck_valid_table[CK_MAX_PLL_FREQ / 8 + 1]; static __u8 ck_lookup_table[CK_MAX_PLL_FREQ]; int ck_set_input(ck_t ck, ck_t input) { int ret = 0, shift; unsigned short reg; unsigned long flags; if (!CK_IN_RANGE(ck) || !CK_CAN_SWITCH(ck)) { ret = -EINVAL; goto exit; } reg = omap_readw(CK_SELECT_REG(ck)); shift = CK_SELECT_SHIFT(ck); spin_lock_irqsave(&clock_lock, flags); if (input == OMAP_CLKIN) { reg &= ~(1 << shift); omap_writew(reg, CK_SELECT_REG(ck)); goto exit; } else if (input == CK_PARENT(ck)) { reg |= (1 << shift); omap_writew(reg, CK_SELECT_REG(ck)); goto exit; } ret = -EINVAL; exit: spin_unlock_irqrestore(&clock_lock, flags); return ret; } int ck_get_input(ck_t ck, ck_t * input) { int ret = -EINVAL; unsigned long flags; if (!CK_IN_RANGE(ck)) goto exit; ret = 0; spin_lock_irqsave(&clock_lock, flags); if (CK_CAN_SWITCH(ck)) { int shift; unsigned short reg; reg = omap_readw(CK_SELECT_REG(ck)); shift = CK_SELECT_SHIFT(ck); if (reg & (1 << shift)) { *input = CK_PARENT(ck); goto exit; } } *input = OMAP_CLKIN; exit: spin_unlock_irqrestore(&clock_lock, flags); return ret; } static int __ck_set_pll_rate(ck_t ck, int rate) { unsigned short pll; unsigned long flags; if ((rate < 0) || (rate > CK_MAX_PLL_FREQ)) return -EINVAL; /* Scan downward for the closest matching frequency */ while (rate && !test_bit(rate, (unsigned long *)&ck_valid_table)) rate--; if (!rate) { printk(KERN_ERR "%s: couldn't find a matching rate\n", __FUNCTION__); return -EINVAL; } spin_lock_irqsave(&clock_lock, flags); pll = omap_readw(CK_RATE_REG(ck)); /* Clear the rate bits */ pll &= ~(0x1f << 5); /* Set the rate bits */ pll |= (ck_lookup_table[rate - 1] << 5); omap_writew(pll, CK_RATE_REG(ck)); spin_unlock_irqrestore(&clock_lock, flags); return 0; } static int __ck_set_clkm_rate(ck_t ck, int rate) { int shift, prate, div, ret; unsigned short reg; unsigned long flags; spin_lock_irqsave(&clock_lock, flags); /* * We can only set this clock's value to a fraction of its * parent's value. The interface says I'll round down when necessary. * So first let's get the parent's current rate. */ prate = ck_get_rate(CK_PARENT(ck)); /* * Let's just start with the highest fraction and keep searching * down through available rates until we find one less than or equal * to the desired rate. */ for (div = 0; div < 4; div++) { if (prate <= rate) break; prate = prate / 2; } /* * Oops. Looks like the caller wants a rate lower than we can support. */ if (div == 5) { printk(KERN_ERR "%s: %d is too low\n", __FUNCTION__, rate); ret = -EINVAL; goto exit; } /* * One more detail: if this clock supports more than one parent, then * we're going to automatically switch over to the parent which runs * through the divisor. For omap this is not ambiguous because for all * such clocks one choice is always OMAP_CLKIN (which doesn't run * through the divisor) and the other is whatever I encoded as * CK_PARENT. Note that I wait until we get this far because I don't * want to switch the input until we're sure this is going to work. */ if (CK_CAN_SWITCH(ck)) if ((ret = ck_set_input(ck, CK_PARENT(ck))) < 0) { BUG(); goto exit; } /* * At last, we can set the divisor. Clear the old rate bits and * set the new ones. */ reg = omap_readw(CK_RATE_REG(ck)); shift = CK_RATE_SHIFT(ck); reg &= ~(3 << shift); reg |= (div << shift); omap_writew(reg, CK_RATE_REG(ck)); /* And return the new (actual, after rounding down) rate. */ ret = prate; exit: spin_unlock_irqrestore(&clock_lock, flags); return ret; } int ck_set_rate(ck_t ck, int rate) { int ret = -EINVAL; if (!CK_IN_RANGE(ck) || !CK_CAN_CHANGE_RATE(ck)) goto exit; switch (ck) { default: ret = __ck_set_clkm_rate(ck, rate); break; case OMAP_CK_GEN1: ret = __ck_set_pll_rate(ck, rate); break; }; exit: return ret; } static int __ck_get_pll_rate(ck_t ck) { int m, d; unsigned short pll = omap_readw(CK_RATE_REG(ck)); m = (pll & (0x1f << 7)) >> 7; m = m ? m : 1; d = (pll & (3 << 5)) >> 5; d++; return ((source_clock * m) / d); } static int __ck_get_clkm_rate(ck_t ck) { static int bits2div[] = { 1, 2, 4, 8 }; int in, bits, reg, shift; reg = omap_readw(CK_RATE_REG(ck)); shift = CK_RATE_SHIFT(ck); in = ck_get_rate(CK_PARENT(ck)); bits = (reg & (3 << shift)) >> shift; return (in / bits2div[bits]); } int ck_get_rate(ck_t ck) { int ret = 0; ck_t parent; if (!CK_IN_RANGE(ck)) { ret = -EINVAL; goto exit; } switch (ck) { case OMAP_CK_GEN1: ret = __ck_get_pll_rate(ck); break; case OMAP_CLKIN: ret = source_clock; break; case OMAP_MPUXOR_CK: case OMAP_CK_GEN2: case OMAP_CK_GEN3: case OMAP_ARM_GPIO_CK: ret = ck_get_rate(CK_PARENT(ck)); break; case OMAP_ARM_CK: case OMAP_MPUPER_CK: case OMAP_DSP_CK: case OMAP_DSPMMU_CK: case OMAP_LCD_CK: case OMAP_TC_CK: case OMAP_DMA_CK: case OMAP_API_CK: case OMAP_HSAB_CK: case OMAP_LBFREE_CK: case OMAP_LB_CK: ret = __ck_get_clkm_rate(ck); break; case OMAP_MPUTIM_CK: ck_get_input(ck, &parent); ret = ck_get_rate(parent); break; case OMAP_MPUWD_CK: /* Note that this evaluates to zero if source_clock is 12MHz. */ ret = source_clock / 14; break; default: ret = -EINVAL; break; } exit: return ret; } int ck_enable(ck_t ck) { unsigned short reg; int ret = -EINVAL, shift; unsigned long flags; if (!CK_IN_RANGE(ck)) goto exit; if (ck_debug) printk(KERN_DEBUG "%s: %s\n", __FUNCTION__, CK_NAME(ck)); ret = 0; if (!CK_CAN_DISABLE(ck)) /* Then it must be on... */ goto exit; spin_lock_irqsave(&clock_lock, flags); reg = omap_readw(CK_ENABLE_REG(ck)); shift = CK_ENABLE_SHIFT(ck); reg |= (1 << shift); omap_writew(reg, CK_ENABLE_REG(ck)); spin_unlock_irqrestore(&clock_lock, flags); exit: return ret; } int ck_disable(ck_t ck) { unsigned short reg; int ret = -EINVAL, shift; unsigned long flags; if (!CK_IN_RANGE(ck)) goto exit; if (ck_debug) printk(KERN_DEBUG "%s: %s\n", __FUNCTION__, CK_NAME(ck)); if (!CK_CAN_DISABLE(ck)) goto exit; ret = 0; if (ck == OMAP_CLKIN) return -EINVAL; spin_lock_irqsave(&clock_lock, flags); reg = omap_readw(CK_ENABLE_REG(ck)); shift = CK_ENABLE_SHIFT(ck); reg &= ~(1 << shift); omap_writew(reg, CK_ENABLE_REG(ck)); spin_unlock_irqrestore(&clock_lock, flags); exit: return ret; } int ck_valid_rate(int rate) { return test_bit(rate, (unsigned long *)&ck_valid_table); } static void __ck_make_lookup_table(void) { __u8 m, d; memset(ck_valid_table, 0, sizeof (ck_valid_table)); for (m = 1; m < 32; m++) for (d = 1; d < 5; d++) { int rate = ((source_clock * m) / (d)); if (rate > CK_MAX_PLL_FREQ) continue; if (test_bit(rate, (unsigned long *)&ck_valid_table)) continue; set_bit(rate, (unsigned long *)&ck_valid_table); ck_lookup_table[rate - 1] = (m << 2) | (d - 1); } } int __init init_ck(void) { const struct omap_clock_info *info; int crystal_type = 0; /* Default 12 MHz */ __ck_make_lookup_table(); info = omap_get_per_info(OMAP_TAG_CLOCK, struct omap_clock_info); if (info != NULL) { if (!cpu_is_omap1510()) crystal_type = info->system_clock_type; } /* We want to be in syncronous scalable mode */ omap_writew(0x1000, ARM_SYSST); #if defined(CONFIG_OMAP_ARM_30MHZ) omap_writew(0x1555, ARM_CKCTL); omap_writew(0x2290, DPLL_CTL); #elif defined(CONFIG_OMAP_ARM_60MHZ) omap_writew(0x1005, ARM_CKCTL); omap_writew(0x2290, DPLL_CTL); #elif defined(CONFIG_OMAP_ARM_96MHZ) omap_writew(0x1005, ARM_CKCTL); omap_writew(0x2410, DPLL_CTL); #elif defined(CONFIG_OMAP_ARM_120MHZ) omap_writew(0x110a, ARM_CKCTL); omap_writew(0x2510, DPLL_CTL); #elif defined(CONFIG_OMAP_ARM_168MHZ) omap_writew(0x110f, ARM_CKCTL); omap_writew(0x2710, DPLL_CTL); #elif defined(CONFIG_OMAP_ARM_182MHZ) && defined(CONFIG_ARCH_OMAP730) omap_writew(0x250E, ARM_CKCTL); omap_writew(0x2710, DPLL_CTL); #elif defined(CONFIG_OMAP_ARM_192MHZ) && (defined(CONFIG_ARCH_OMAP1610) || defined(CONFIG_ARCH_OMAP5912)) omap_writew(0x150f, ARM_CKCTL); if (crystal_type == 2) { source_clock = 13; /* MHz */ omap_writew(0x2510, DPLL_CTL); } else omap_writew(0x2810, DPLL_CTL); #elif defined(CONFIG_OMAP_ARM_195MHZ) && defined(CONFIG_ARCH_OMAP730) omap_writew(0x250E, ARM_CKCTL); omap_writew(0x2790, DPLL_CTL); #else #error "OMAP MHZ not set, please run make xconfig" #endif #ifdef CONFIG_MACH_OMAP_PERSEUS2 /* Select slicer output as OMAP input clock */ omap_writew(omap_readw(OMAP730_PCC_UPLD_CTRL) & ~0x1, OMAP730_PCC_UPLD_CTRL); #endif /* Turn off some other junk the bootloader might have turned on */ /* Turn off DSP, ARM_INTHCK, ARM_TIMXO */ omap_writew(omap_readw(ARM_CKCTL) & 0x0fff, ARM_CKCTL); /* Put DSP/MPUI into reset until needed */ omap_writew(0, ARM_RSTCT1); omap_writew(1, ARM_RSTCT2); omap_writew(0x400, ARM_IDLECT1); /* * According to OMAP5910 Erratum SYS_DMA_1, bit DMACK_REQ (bit 8) * of the ARM_IDLECT2 register must be set to zero. The power-on * default value of this bit is one. */ omap_writew(0x0000, ARM_IDLECT2); /* Turn LCD clock off also */ /* * Only enable those clocks we will need, let the drivers * enable other clocks as necessary */ ck_enable(OMAP_MPUPER_CK); ck_enable(OMAP_ARM_GPIO_CK); ck_enable(OMAP_MPUXOR_CK); //ck_set_rate(OMAP_MPUTIM_CK, OMAP_CLKIN); ck_enable(OMAP_MPUTIM_CK); start_mputimer1(0xffffffff); return 0; } EXPORT_SYMBOL(ck_get_rate); EXPORT_SYMBOL(ck_set_rate); EXPORT_SYMBOL(ck_enable); EXPORT_SYMBOL(ck_disable);