/* * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ /*! * @defgroup DPM_MX27 Power Management * @ingroup MSL_MX27 */ /*! * @file mach-mx27/mxc_pm.c * * @brief This file contains the implementation of the Low-level power * management driver. It modifies the registers of the PLL and clock module * of the i.MX27. * * @ingroup DPM_MX27 */ /* * Include Files */ #include #include #include #include #include #include #include #include #include "crm_regs.h" /* Local defines */ #define MAX_ARM_FREQ 400000000 #define MAX_AHB_FREQ 133000000 #define MAX_IPG_FREQ 66500000 #define FREQ_COMP_TOLERANCE 100 /* tolerance percentage times 100 */ #define MX27_LLPM_DEBUG 0 /* * Global variables */ #if 0 /*! * These variables hold the various clock values when the module is loaded. * This is needed because these clocks are derived from MPLL and when MPLL * output changes, these clocks need to be adjusted. */ static u32 perclk1, perclk2, perclk3, perclk4, nfcclk, cpuclk; /*! * Compare two frequences using allowable tolerance * * The MX27 PLL can generate many frequencies. This function * compares the generated frequency to the requested frequency * and determines it they are within and acceptable tolerance. * * @param freq1 desired frequency * @param freq2 generated frequency * * @return Returns 0 is frequencies are within talerance * and non-zero is they are not. */ static s32 freq_equal(u32 freq1, u32 freq2) { if (freq1 > freq2) { return (freq1 - freq2) <= (freq1 / FREQ_COMP_TOLERANCE); } return (freq2 - freq1) <= (freq1 / FREQ_COMP_TOLERANCE); } /*! * Select the PLL frequency based on the desired ARM frequency. * * The MPLL will be configured to output three frequencies, 400/333/266 MHz. * * @param armfreq Desired ARM frequency * * @return Returns one of the selected PLL frequency (400/333/266 MHz). * Returns -1 on error. * */ static s32 select_freq_pll(u32 armfreq) { u32 div; div = 266000000 / armfreq; if ((div == 0) || (!freq_equal(armfreq, 266000000 / div))) { div = 400000000 / armfreq; if ((div == 0) || (!freq_equal(armfreq, 400000000 / div))) { return -1; } return 400000000; } return 266000000; } /*! * Check whether the desired ARM and AHB frequencies are valid. * * @param armfreq Desired ARM frequency * @param ahbfreq Desired AHB frequency * * @return Returns 0 on success * Return -1 on error */ static s32 mx27_pm_check_parameters(u32 armfreq, u32 ahbfreq) { u32 ahbdiv; /* No idea about minimum frequencies.. just a guess! */ if ((armfreq < 1000000) || (ahbfreq < 1000000)) { printk("arm or ahb frequencies are less\n"); return -1; } if ((armfreq > MAX_ARM_FREQ) || (ahbfreq > MAX_AHB_FREQ)) { printk("arm or ahb freq. are too much\n"); return -1; } /* AHB divider value is restricted to less than 8 */ ahbdiv = armfreq / ahbfreq; if ((ahbdiv == 0) || (ahbdiv > 8)) { printk("Invalid ahb frequency\n"); return -1; } return 0; } /*! * Integer clock scaling * * Change the main ARM clock frequencies without changing the MPLL. * The integer dividers (PRESC and BCLKDIV) are changed to obtain the * desired frequency. Since NFC clock is derived from ARM frequency, * NFCDIV is also adjusted. * * @param arm_freq Desired ARM frequency * @param ahb_freq Desired AHB frequency * @param pll_freq Current PLL frequency * * @return Returns 0 */ static s32 mx27_pm_intscale(u32 arm_freq, u32 ahb_freq, s32 pll_freq) { u32 pre_div, bclk_div, nfc_div; /* Calculate ARM divider */ pre_div = pll_freq / arm_freq; if (pre_div == 0) pre_div = 1; /* Calculate AHB divider */ bclk_div = arm_freq / ahb_freq; if (bclk_div == 0) bclk_div = 1; if ((arm_freq / bclk_div) > ahb_freq) bclk_div++; /* NFC clock is dependent on ARM clock */ nfc_div = arm_freq / nfcclk; if ((arm_freq / nfc_div) > nfcclk) nfc_div++; /* Adjust NFC divider */ mxc_set_clocks_div(NFC_CLK, nfc_div); #if MX27_LLPM_DEBUG printk("DIVIDERS: PreDiv = %d BCLKDIV = %d \n", pre_div, bclk_div); printk("Integer scaling\n"); printk("PLL = %d : ARM = %d: AHB = %d\n", pll_freq, arm_freq, ahb_freq); #endif /* * This part is tricky. What to adjust first (PRESC or BCLKDIV)? * After trial and error, if current ARM frequency is greater than * desired ARM frequency, then adjust PRESC first, else if current * ARM frequency is less than desired ARM frequency, then adjust * BCLKDIV first. */ if (cpuclk > arm_freq) { mxc_set_clocks_div(CPU_CLK, pre_div); mxc_set_clocks_div(AHB_CLK, bclk_div); } else { mxc_set_clocks_div(AHB_CLK, bclk_div); mxc_set_clocks_div(CPU_CLK, pre_div); } cpuclk = arm_freq; mdelay(50); return 0; } /*! * Set dividers for various peripheral clocks. * * PERCLK1, PERCLK2, PERCLK3 and PERCLK4 are adjusted based on the MPLL * output frequency. * * @param pll_freq Desired MPLL output frequency */ static void mx27_set_dividers(u32 pll_freq) { s32 perdiv1, perdiv2, perdiv3, perdiv4; perdiv1 = pll_freq / perclk1; if ((pll_freq / perdiv1) > perclk1) perdiv1++; perdiv2 = pll_freq / perclk2; if ((pll_freq / perdiv2) > perclk2) perdiv2++; perdiv3 = pll_freq / perclk3; if ((pll_freq / perdiv3) > perclk3) perdiv3++; perdiv4 = pll_freq / perclk4; if ((pll_freq / perdiv4) > perclk4) perdiv4++; mxc_set_clocks_div(PERCLK1, perdiv1); mxc_set_clocks_div(PERCLK2, perdiv2); mxc_set_clocks_div(PERCLK3, perdiv3); mxc_set_clocks_div(PERCLK4, perdiv4); } /*! * Change MPLL output frequency and adjust derived clocks to produce the * desired frequencies. * * @param arm_freq Desired ARM frequency * @param ahb_freq Desired AHB frequency * @param org_pll Current PLL frequency * * @return Returns 0 on success * Returns -1 on error */ static s32 mx27_pm_pllscale(u32 arm_freq, u32 ahb_freq, s32 org_pll) { u32 mfi, mfn, mfd, pd = 1, cscr; s32 pll_freq; /* Obtain the PLL frequency for the desired ARM frequency */ pll_freq = select_freq_pll(arm_freq); if (pll_freq == -1) { return -1; } /* The MPCTL0 register values are programmed based on the oscillator */ cscr = __raw_readl(IO_ADDRESS(CCM_BASE_ADDR) + CCM_CSCR); if ((cscr & CCM_CSCR_OSC26M) == 0) { /* MPCTL0 register values are programmed for 400/266 MHz */ switch (pll_freq) { case 400000000: mfi = 7; mfn = 9; mfd = 12; pd = 0; break; case 266000000: mfi = 10; mfn = 6; mfd = 25; break; default: return -1; } } else { /* MPCTL0 register values are programmed for 400/266 MHz */ switch (pll_freq) { case 400000000: mfi = 12; mfn = 2; mfd = 3; break; case 266000000: mfi = 8; mfn = 10; mfd = 31; break; default: return -1; } } #if MX27_LLPM_DEBUG printk("PLL scaling\n"); printk("PLL = %d : ARM = %d: AHB = %d\n", pll_freq, arm_freq, ahb_freq); #endif /* Adjust the peripheral clock dividers for new PLL frequency */ mx27_set_dividers(pll_freq); if (pll_freq > org_pll) { /* Set the dividers first */ mx27_pm_intscale(arm_freq, ahb_freq, pll_freq); /* Set the PLL */ mxc_pll_set(MCUPLL, mfi, pd, mfd, mfn); mdelay(50); } else { /* Set the PLL first */ mxc_pll_set(MCUPLL, mfi, pd, mfd, mfn); mdelay(50); /* Set the dividers later */ mx27_pm_intscale(arm_freq, ahb_freq, pll_freq); } return 0; } #endif /*! * Implement steps required to transition to low-power modes. * * @param mode The desired low-power mode. Possible values are, * DOZE_MODE * WAIT_MODE * STOP_MODE * DSM_MODE */ void mxc_pm_lowpower(s32 mode) { u32 cscr; local_irq_disable(); /* WAIT and DOZE execute WFI only */ switch (mode) { case STOP_MODE: case DSM_MODE: /* Clear MPEN and SPEN to disable MPLL/SPLL */ cscr = __raw_readl(CCM_CSCR); cscr &= 0xFFFFFFFC; __raw_writel(cscr, CCM_CSCR); break; } /* Executes WFI */ arch_idle(); local_irq_enable(); } #if 0 /*! * Called to change the core frequency. This function internally decides * whether to do integer scaling or pll scaling. * * @param arm_freq Desired ARM frequency * @param ahb_freq Desired AHB frequency * @param ipg_freq Desired IP frequency, constant AHB / 2 always. * * @return Returns 0 on success * Returns -1 on error */ int mxc_pm_dvfs(unsigned long arm_freq, long ahb_freq, long ipg_freq) { u32 divider; s32 pll_freq, ret; unsigned long flags; if (mx27_pm_check_parameters(arm_freq, ahb_freq) != 0) { return -1; } local_irq_save(flags); /* Get the current PLL frequency */ pll_freq = mxc_pll_clock(MCUPLL); #if MX27_LLPM_DEBUG printk("MCU PLL frequency is %d\n", pll_freq); #endif /* Decide whether to do integer scaling or pll scaling */ if (arm_freq > pll_freq) { /* Do PLL scaling */ ret = mx27_pm_pllscale(arm_freq, ahb_freq, pll_freq); } else { /* We need integer divider values */ divider = pll_freq / arm_freq; if (!freq_equal(arm_freq, pll_freq / divider)) { /* Do PLL scaling */ ret = mx27_pm_pllscale(arm_freq, ahb_freq, pll_freq); } else { /* Do integer scaling */ ret = mx27_pm_intscale(arm_freq, ahb_freq, pll_freq); } } local_irq_restore(flags); return ret; } #endif /* * This API is not supported on i.MX27 */ int mxc_pm_intscale(long armfreq, long ahbfreq, long ipfreq) { return -MXC_PM_API_NOT_SUPPORTED; } /* * This API is not supported on i.MX27 */ int mxc_pm_pllscale(long armfreq, long ahbfreq, long ipfreq) { return -MXC_PM_API_NOT_SUPPORTED; } /*! * This function is used to load the module. * * @return Returns an Integer on success */ static int __init mxc_pm_init_module(void) { printk(KERN_INFO "MX27: Power management module initialized\n"); return 0; } /*! * This function is used to unload the module */ static void __exit mxc_pm_cleanup_module(void) { printk(KERN_INFO "MX27: Power management module exit\n"); } module_init(mxc_pm_init_module); module_exit(mxc_pm_cleanup_module); EXPORT_SYMBOL(mxc_pm_lowpower); //EXPORT_SYMBOL(mxc_pm_dvfs); EXPORT_SYMBOL(mxc_pm_pllscale); EXPORT_SYMBOL(mxc_pm_intscale); MODULE_AUTHOR("Freescale Semiconductor"); MODULE_DESCRIPTION("i.MX27 low level PM driver"); MODULE_LICENSE("GPL");