/*
 * Copyright 2004 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
 */

/*!
 * @file mxc_pm_scma11.c
 *
 * @brief This file provides all the kernel level and user level API
 * definitions for the CRM_AP, DPLL and DSM modules.
 *
 * @ingroup LPMD
 */

/*
 * Include Files
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/kdev_t.h>
#include <linux/major.h>
#include <linux/mm.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/circ_buf.h>
#include <linux/delay.h>
#include <asm/arch/platform.h>
#include <asm/arch/mxc_pm.h>
#include <asm/arch/clock.h>
#include <asm/arch/spba.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/mach/time.h>
#include <asm/arch/gpio.h>

#include "mxc_pm_scma11.h"
#include "../atlas/module/atlas_power.h"

#define HI_INTR IO_ADDRESS(AVIC_BASE_ADDR + 0x48)
#define LO_INTR IO_ADDRESS(AVIC_BASE_ADDR + 0x4c)

#define HI_INTR_EN IO_ADDRESS(AVIC_BASE_ADDR + 0x10)
#define LO_INTR_EN IO_ADDRESS(AVIC_BASE_ADDR + 0x14)

static unsigned int pll_dp_opreg[NUM_PLL] = {PLL0_DP_OP, PLL1_DP_OP, PLL2_DP_OP};
static unsigned int pll_dp_ctlreg[NUM_PLL] = {PLL0_DP_CTL, PLL1_DP_CTL, PLL2_DP_CTL};
static unsigned int pll_dp_mfnreg[NUM_PLL] = {PLL0_DP_MFN, PLL1_DP_MFN, PLL2_DP_MFN};
static unsigned int pll_dp_mfdreg[NUM_PLL] = {PLL0_DP_MFD, PLL1_DP_MFD, PLL2_DP_MFD};
static unsigned int pll_dp_hfs_opreg[NUM_PLL] = {PLL0_DP_HFSOP, PLL1_DP_HFSOP, PLL2_DP_HFSOP};
static unsigned int pll_dp_hfs_mfnreg[NUM_PLL] = {PLL0_DP_HFSMFN, PLL1_DP_HFSMFN, PLL2_DP_HFSMFN};
static unsigned int pll_dp_hfs_mfdreg[NUM_PLL] = {PLL0_DP_HFSMFD, PLL1_DP_HFSMFD, PLL2_DP_HFSMFD};
static unsigned int gate_stop_wait[NUM_GATE_CTRL] = {ACG0_STOP_WAIT, ACG1_STOP_WAIT, ACG3_STOP_WAIT,
                                                     ACG4_STOP_WAIT, ACG5_STOP_WAIT, ACG6_STOP_WAIT};
static unsigned int gate_stop[NUM_GATE_CTRL] = {ACG0_STOP, ACG1_STOP, ACG3_STOP,
                                                ACG4_STOP, ACG5_STOP, ACG6_STOP};
static unsigned int gate_run[NUM_GATE_CTRL] = {ACG0_RUN, ACG1_RUN, ACG3_RUN,
                                                ACG4_RUN, ACG5_RUN, ACG6_RUN};

static int mfn_status = 0;

/*!
 * Calculating possible divider value for AHB
 *
 * @param   apfreq     The ARM frequency desired or changed as a result
 *                     of PLL or Integer scaling
 * @param   ahbfreq    The desired AHB frequency
 * @param   ipfreq     The desired IP frequency
 * @param   crm_issue  Software workaround for the hardware CRM issue
 *
 * @return             Returns 0 on success and -AHB_IPG_EXCEED_LIMIT error if
 *                     AHB or IPG frequency exceeds the limit of 133 or 66.5MHz
 */
static int mxc_pm_ahb_ipdiv(long apfreq, long ahbfreq, long ipfreq, int crm_issue)
{
        unsigned long ahbfactor = 0, ipfactor, acdr;

        if (ahbfreq > AHB_FREQ || ipfreq > IPG_FREQ) {
                return -AHB_IPG_EXCEED_LIMIT;
        }
        if ((crm_issue == 1) && (NO_SDRAMC_RATIO_ISSUE == 0)) {
                apfreq = apfreq / 2;
        }
        ahbfactor = apfreq / ahbfreq;
        ipfactor = apfreq / ipfreq;
        if (ahbfactor == 0) {
                ahbfactor++;
        }
        if (ipfactor == 0) {
                ipfactor++;
        }
        if (((apfreq / ahbfactor) / 1000000) > (ahbfreq / 1000000)) {
                ahbfactor++;
        }
        if (((apfreq / ipfactor) / 1000000) > (ipfreq / 1000000)) {
                ipfactor++;
        }
        if (ahbfreq == AHB_FREQ) {
                /* Workaround to support the sequence 532-266-532-399 */
                /*
                 * update ip divider when updating ahb divider in this case
                 * where ip div and ahb div is 1:2 ratio
                 */
                ipfactor = 2 * ahbfactor;
                writel((((readl(CRM_AP_ACDR) & 0x00000F00) | (ahbfactor << 4)) | (ipfactor)), CRM_AP_ACDR);
        } else {
                writel((((readl(CRM_AP_ACDR) & 0x00000F00) | (ahbfactor << 4)) | (ipfactor)), CRM_AP_ACDR);
        }
        acdr = readl(CRM_AP_ACDR);
        return 0;
}

/*!
 * To find out the desired voltage transition
 *
 * @param   armfreq    The desired ARM frequency
 * @param   ahbfreq    The desired AHB frequency
 * @param   ipfreq     The desired IP frequency
 * @param   crm_issue  Software workaround for the hardware CRM issue
 *
 * @return             Returns value indicating if the change is to
 *                     increase or decrease voltage or only frequency
 */
static int if_hightolow(long armfreq, long ahbfreq, long ipfreq, int crm_issue)
{
        int curr_volt = HI_VOLT, hi_to_lo = 0, ahb;
        unsigned long runfreq = mxc_get_clocks(CPU_CLK);

        if (((readl(CRM_AP_ADCR)) & ADCR_VSTAT) != 0) {
                curr_volt = LO_VOLT;
        }
        if (armfreq < BREAKPT_FREQ) {
                hi_to_lo = 1;
        }
        if (crm_issue) {
                runfreq = 2 * runfreq;
        }
        if (curr_volt == LO_VOLT && hi_to_lo == 0) {
                /*
                 * Frequency has to be increased.
                 * So calculate AHB and IP dividers
                 * before changing ARM freq
                 */
                if ((CRM_ISSUE == 1) && (NO_SDRAMC_RATIO_ISSUE == 0)) {
                        crm_issue = 1;
                } else {
                        crm_issue = 0;
                }
                ahb = mxc_pm_ahb_ipdiv(armfreq, ahbfreq, ipfreq, crm_issue);
                return CHG_VF_HI;
        }
        if (curr_volt == HI_VOLT && hi_to_lo == 1) {
                /*
                 * Frequency has to be decreased.
                 * So AHB and IP dividers are calculated
                 * after changing ARM freq
                 */
                return CHG_VF_LO;
        }
        if ((armfreq / 1000000) > (((runfreq /1000000) + 1))) {
                /*
                 * Frequency has to be increased.
                 * So calculate AHB and IP dividers
                 * before changing ARM freq
                 */
                if ((readl(CRM_AP_ADCR) & (ADCR_DFS_DIV_EN)) == 0) {
                        if ((CRM_ISSUE == 1) && (NO_SDRAMC_RATIO_ISSUE == 0)) {
                                if (curr_volt == HI_VOLT) {
                                        crm_issue = 1;
                                } else {
                                        crm_issue = 0;
                                }
                        } else {
                                crm_issue = 0;
                        }
                        ahb = mxc_pm_ahb_ipdiv(armfreq, ahbfreq, ipfreq, crm_issue);
                }
                return CHG_FREQ_HI;
        }
        /*
         * Frequency has to be decreased.
         * So AHB and IP dividers are calculated
         * after changing ARM freq
         */
        return CHG_FREQ_LO;
}

/*!
 * Calculating possible LFDF value for Integer Scaling
 *
 * @param   value      The divider value to be set for the DFS block.
 *                     Possible values are 0,2,4,8.
 *                     This occurs during Integer scaling
 *
 */
static void mxc_pm_setlfdf(int value)
{
        unsigned long adcr_val;

        adcr_val = readl(CRM_AP_ADCR);
        switch(value) {
        case LFDF_0:
                adcr_val = (adcr_val & (~ADCR_LFDF_LSB)) & (~ADCR_LFDF_MSB);
                break;
        case LFDF_2:
                adcr_val = (adcr_val | (ADCR_LFDF_LSB)) & (~ADCR_LFDF_MSB);
                break;
        case LFDF_4:
                adcr_val = (adcr_val & (~ADCR_LFDF_LSB)) | (ADCR_LFDF_MSB);
                break;
        case LFDF_8:
                adcr_val |= (ADCR_LFDF_LSB) | (ADCR_LFDF_MSB);
                break;
        }
        writel(adcr_val, CRM_AP_ADCR);
}

/*!
 * To find the divider which supports the next lowest ARM frequency
 * supported using Integer Scaling
 *
 * @param   armfreq    The desired ARM frequency
 *
 * @return             Returns ARM divider value
 */
static int mxc_pm_findnext(unsigned long armfreq)
{
        unsigned long pllfreq, runfreq;
        int divider = 0;

        runfreq = mxc_get_clocks(CPU_CLK);
        pllfreq = mxc_pll_clock(MCUPLL);
        if (CONFIG_ALT_PLL) {
                /* This implies AP core may be running out of either PLL0 or PLL1 */
                if ((readl(CRM_AP_ASCSR) & (PLL1_SELECT)) != 0) {
                        pllfreq = mxc_pll_clock(DSPPLL);
                }
        }
        divider = pllfreq / armfreq;
        if ((((pllfreq / 1000000) % (armfreq / 1000000)) > 0) &&
            (((pllfreq / armfreq) - 1) <= (pllfreq / runfreq)) &&
            (((armfreq / 1000000) % (runfreq / 1000000)) > 1)) {
                return ++divider;
        }
        return divider;
}

/*!
 * To calculate MFI, MFN, MFD values. Using this the output frequency
 * whose value is calculated using,
 * 2 * REF_FREQ * (MF / PDF), where
 * REF_FREQ is 26 Mhz
 * MF = MFI + (MFN + MFD)
 * PDF is assumed to be 1
 *
 * @param   armfreq    The desired ARM frequency
 * @param   ahbfreq    The desired AHB frequency
 * @param   ipfreq     The desired IP frequency
 * @param   pll_num    PLL that has to be changed
 * @param   reg_type   Register type to indicate low-frequency
 *                     or high frequency setting.
 *                     0 - Low Frequency
 *                     1 - High Frequency (Shadow registers)
 *
 * @return             Returns 0 on success or
 *                     Returns -1 on error
 */
static int mxc_pm_calc_mf(long armfreq, long ahbfreq, long ipfreq, int pll_num, int reg_type)
{
        signed long dp_opval, mf, mfi, mfn, mfd, ref_freq, pll, freq_out, runfreq;
        int ret_val = -1, pdf;

        /*
         * Each PLL is  identified using base address and
         * PLL0 is assumed to be selected for AP domain
         * MF is  calculated here. PDF is set to 1 which is divide by 2
         * by default by hardware
         */
        ref_freq = REF_FREQ;
        pdf = (readl(pll_dp_opreg[pll_num])) & 0xF;
        if (pdf != 0) {
                writel((pdf & 0x000000F0), pll_dp_opreg[pll_num]);
                writel((pdf & 0x000000F0), pll_dp_hfs_opreg[pll_num]);
        }
        pdf = (readl(pll_dp_opreg[pll_num])) & 0xF;
        mf = (armfreq * (pdf + 1)) / (2 * ref_freq);
        if (mfn_status == 1) {
                mfi = ((readl(pll_dp_opreg[pll_num])) >> 4) & 0xF;
                mfd = (ref_freq) / 1000000;
                if (reg_type == 0) {
                        writel(mfd, pll_dp_mfdreg[pll_num]);
                        mfd = readl(pll_dp_mfdreg[pll_num]);
                } else {
                        writel(mfd, pll_dp_hfs_mfdreg[pll_num]);
                        mfd = readl(pll_dp_hfs_mfdreg[pll_num]);
                }
                /* To handle frequencies greater that 440Mhz */
                /* To avoid negative mfn values while calculation */
                mfn = ((armfreq - (2 * ref_freq * mfi)) * (mfd + 1)) / (2 * ref_freq);
                if ((armfreq > (2 * ref_freq * mfi)) && mfn < 0) {
                        mfd = (ref_freq / 2) / 1000000;
                        if (reg_type == 0) {
                                writel(mfd, pll_dp_mfdreg[pll_num]);
                                mfd = readl(pll_dp_mfdreg[pll_num]);
                        } else {
                                writel(mfd, pll_dp_hfs_mfdreg[pll_num]);
                                mfd = readl(pll_dp_hfs_mfdreg[pll_num]);
                        }
                        /* calculating mfn again */
                        mfn = ((armfreq - (2 * ref_freq * mfi)) * (mfd + 1)) / (2 * ref_freq);
                }
                mfn = (mfn <= 0x4000000) ? mfn : (mfn - 0x10000000);
                if (reg_type == 0) {
                        writel(mfn, pll_dp_mfnreg[pll_num]);
                        writel(mfd, pll_dp_mfdreg[pll_num]);
                } else {
                        writel(mfn, pll_dp_hfs_mfnreg[pll_num]);
                        writel(mfd, pll_dp_hfs_mfdreg[pll_num]);
                }
                readl(pll_dp_hfs_mfnreg[pll_num]);
                readl(pll_dp_mfnreg[pll_num]);
                freq_out = (2 * ref_freq * mfi + (((2 * ref_freq) / (mfd + 1)) * mfn)) /
                           (pdf + 1);
                return 0;
        } else {
                mfi = (int)mf;
                mfn = (armfreq - (2 * mf * ref_freq)) / 1000000;
        }
        mfd = (2 * REF_FREQ) / 1000000;
        mfi = (mfi <= 5) ? 5: mfi;
        mfn = (mfn <= 0x4000000) ? mfn: (mfn - 0x10000000);
        /*
         * Calculate freq_out to see whether required freq is
         * less than the calculated freq. If calc freq is greater then
         * the actual output freq value should be minimum of current
         * PLL freq or total possible min freq
         */
        freq_out = (2 * ref_freq * mfi + ((2 * ref_freq / (mfd + 1)) * mfn)) /
                   (pdf + 1);
        runfreq = mxc_get_clocks(CPU_CLK);
        if(((readl(CRM_AP_ADCR) & (ADCR_ALT_PLL)) != 0) && mfn_status == 0) {
                pll = mxc_pll_clock(DSPPLL);
        } else {
                pll = mxc_pll_clock(MCUPLL);
        }
        if (reg_type == 0) {
                /* Low Frequency setting */
                 if (freq_out > armfreq) {
                        if (pll <= freq_out) {
                                /* retain mfd and mfn values to old ones */
                                mfn = readl(pll_dp_mfnreg[pll_num]) & 0x07FFFFFF;
                        } else if (mfi == 0x5) {
                                /*
                                 * Reset mfn and mfd to 0, so we get minimun
                                 * possible frequency obtainable using
                                 * PLL scaling which is 260MHz
                                 */
                                mfn = mfd = 0;
                        }
                }
                dp_opval = (mfi << 4) | (0x0000000F & readl(pll_dp_opreg[pll_num]));
                writel(dp_opval, pll_dp_opreg[pll_num]);
                writel(mfn, pll_dp_mfnreg[pll_num]);
                writel(mfd, pll_dp_mfdreg[pll_num]);
                ret_val = 0;
        } else {
                /* High Frequency setting */
                if (freq_out > armfreq) {
                        if (pll <= freq_out) {
                                /* retain mfd and mfn values to old ones */
                                mfn = readl(pll_dp_hfs_mfnreg[pll_num]) & 0x07FFFFFF;
                        } else if (mfi == 0x5) {
                                /*
                                 * Reset mfn and mfd to 0, so we get minimum
                                 * possible frequency obtainable using
                                 * PLL scaling which is 260MHz
                                 */
                                mfn = mfd = 0;
                        }
                }
                dp_opval = (mfi << 4) | (0x0000000F & readl(pll_dp_hfs_opreg[pll_num]));
                writel(dp_opval, pll_dp_hfs_opreg[pll_num]);
                writel(mfn, pll_dp_hfs_mfnreg[pll_num]);
                writel(mfd, pll_dp_hfs_mfdreg[pll_num]);
                ret_val = 0;
        }
        while((readl(pll_dp_ctlreg[pll_num]) & 0x00000001) != 0x00000001);
        return ret_val;
}

/*!
 * Implementing steps required to set Integer Scaling
 *
 * @param   armfreq    The desired ARM frequency. AHB and IP
 *                     frequency are changed depending on ARM
 *                     frequency and the divider values.
 * @param   ahbfreq    The desired AHB frequency
 * @param   ipfreq     The desired IP frequency
 *
 * @return             Returns 0 on success or
 *                     Returns -PLL_LESS_ARM_ERR if PLL frequency is
 *                     less than the desired ARM frequency
 *                     Returns -INTEGER_DVFS_NOT_ALLOW if the desired
 *                     core frequency is greater than 133MHz in case of SCM Pass1
 *                     Returns -INT_DFS_LOW_NOT_ALLOW if the desired
 *                     core frequency transition is in low-voltage
 */
int mxc_pm_intscale(long armfreq, long ahbfreq, long ipfreq)
{
        unsigned long adcr_val, dsm_crm_ctrl, pllfreq, runfreq;
        unsigned long div_val, readval;
        int change_req, ret_val = -1, apdiv, i, pll_num = 0, corefreq;

        /* steps to be done to set Integer scaling */
        /*
         * PLL should be relocked at high voltage frequency before doing
         * Integer DVFS
         */
        if ((readl(CRM_AP_ASCSR) & 0x00000018) != 0) {
                pllfreq = mxc_pll_clock(DSPPLL);
                pll_num = 1;
        } else {
                pllfreq = mxc_pll_clock(MCUPLL);
        }
        if ((armfreq / 1000000) > ((pllfreq / 1000000) + 1)) {
                return -PLL_LESS_ARM_ERR;
        }
        dsm_crm_ctrl = readl(DSM_CRM_CONTROL) & (~CRM_CTRL_DVFS_BYP);
        writel(dsm_crm_ctrl, DSM_CRM_CONTROL);
        change_req = if_hightolow(armfreq, ahbfreq, ipfreq, 0);
        if (CRM_ISSUE_INT) {
                if ((armfreq > MAX_INT_FREQ) || (pllfreq > 532000000)) {
                        return -INTEGER_DVFS_NOT_ALLOW;
                }
                corefreq = (mxc_get_clocks(CPU_CLK) / 1000000) + 1;
                if (((corefreq == 399) || (corefreq == 400)) && (armfreq == MAX_INT_FREQ)
                     && (ahbfreq == AHB_FREQ)) {
                        writel(((readl(CRM_AP_ACDR) & 0xFFFFF000) | 0x00000112), CRM_AP_ACDR);
                        return 0;
                }
                if (corefreq != MAX_INT_FREQ) {
                        if ((readl(pll_dp_ctlreg[pll_num]) & DP_HFSM) == 0) {
                                /* PLL relock */
                                mxc_pm_calc_mf(266000000, ahbfreq, ipfreq, pll_num, 0);
                        } else {
                                mxc_pm_calc_mf(266000000, ahbfreq, ipfreq, pll_num, 1);
                        }
                }
                if (armfreq == MAX_INT_FREQ) {
                        /* setting ratio 2:2:4 */
                        writel(((readl(CRM_AP_ACDR) & 0xFFFFF000) | 0x00000024 ), CRM_AP_ACDR);
                        /*
                         * No voltage change required since 133Mhz is supported at
                         * low-voltage
                         */
                        change_req = CHG_VF_HI;
                } else {
                        change_req = CHG_VF_LO;
                }
        }
        dsm_crm_ctrl = readl(DSM_CRM_CONTROL);
        if (change_req == CHG_VF_HI) {
                adcr_val = ((readl(CRM_AP_ADCR) & (~ADCR_ALT_PLL)) & (~ADCR_DIV_BYP)) | (ADCR_DFS_DIV_EN);
                writel(adcr_val, CRM_AP_ADCR);
                /* ap_delay is written */
                writel(((readl(CRM_AP_ADCR) & 0x0000ffff) | 0x00030000), CRM_AP_ADCR);
                dsm_crm_ctrl &= (~CRM_CTRL_DVFS_VCTRL);
                writel(dsm_crm_ctrl, DSM_CRM_CONTROL);
                while(readl(CRM_AP_ADCR) & 0x10);
                /*
                 * If pllfreq = 600Mhz and current runfreq = 100Mhz and
                 * user wants to do Integer DVFS for 500Mhz, Integer DVFS using
                 * AP_DIV has to be used
                 */
                if ((armfreq / 1000000) == ((pllfreq / 1000000) + 1)) {
                        ret_val = 0;
                } else if (CRM_ISSUE_INT) {
                        /*
                         * Voltage is increased and LFDF bit is set to 0 and
                         * voltage is again decreased since 133Mhz is supported
                         * at low voltage. LFDF bits could be changed only
                         * at high voltage. If high voltage is retained then
                         * subsequent PLL scaling methods gets affected with
                         * respect to HFS and LFS register bits
                         */
                        mxc_pm_setlfdf(LFDF_0);
                        dsm_crm_ctrl = readl(DSM_CRM_CONTROL) | CRM_CTRL_DVFS_VCTRL;
                        writel(dsm_crm_ctrl, DSM_CRM_CONTROL);
                        return 0;
                }
        } else if (change_req == CHG_VF_LO) {
                /*
                 * Transition from hi to lo voltage but LFDF and DFS_DIV_EN are
                 * changed at high voltage i.e, before asserting lvs signal
                 */
                adcr_val = ((readl(CRM_AP_ADCR) & (~ADCR_ALT_PLL)) & (~ADCR_DIV_BYP)) | (ADCR_DFS_DIV_EN);
                writel(adcr_val, CRM_AP_ADCR);
                if (CRM_ISSUE_INT) {
                        mxc_pm_setlfdf(LFDF_2);
                        if ((readl(CRM_AP_ADCR) & ADCR_TSTAT) == 0) {
                                dsm_crm_ctrl |= (CRM_CTRL_DVFS_VCTRL);
                                writel(dsm_crm_ctrl, DSM_CRM_CONTROL);
                        }
                        return 0;
                }
                runfreq = mxc_get_clocks(CPU_CLK);
                apdiv = mxc_main_clock_divider(CPU_CLK);
                /*
                 * If SDRAMC ratio issue and CRM issue exists, then AP_DIV may be 2.
                 * So, when doing Integer DVFS, lfdf divider should be properly chosen to
                 * include/exclude this divide by 2 as ARM divider will not be changed
                 * after DFS divider is updated but AHB and IPG are updated automatically
                 * by the hardware to maintain the 133 and 66.5MHz frequency
                 */
                if ((apdiv == 2) && (NO_SDRAMC_RATIO_ISSUE == 0) && (CRM_ISSUE == 1)) {
                        /* As ratio is always maintained at 2:2:4 at high freq */
                        pllfreq = pllfreq / 2;
                }
                /*
                 * Making use of DFS dividers if requested frequency is
                 * multiple of PLL frequency. mxc_pll_clock() returns frequencies such as
                 * 531777777 for 532000000. So, adding 1 to it for correct comparison.
                 * Also, DFS is bypassed at high voltage. So the following code will
                 * affect only low voltage. But, LFDF should be set at high voltage.
                 */
                if ((armfreq / 1000000) == (((pllfreq /1000000) + 1))) {
                        mxc_pm_setlfdf(LFDF_0);
                        ret_val = 0;
                } else if ((armfreq / 1000000) == (((pllfreq /1000000) + 1) / 2)) {
                        mxc_pm_setlfdf(LFDF_2);
                        ret_val = 0;
                } else if ((armfreq / 1000000) == (((pllfreq /1000000) + 1) / 4)) {
                        mxc_pm_setlfdf(LFDF_4);
                        ret_val = 0;
                } else if ((armfreq / 1000000) == (((pllfreq /1000000) + 1) / 8)) {
                        mxc_pm_setlfdf(LFDF_8);
                        ret_val = 0;
                } else {
                        /* Default LFDF value */
                        mxc_pm_setlfdf(LFDF_2);
                }
                if ((readl(CRM_AP_ADCR) & ADCR_TSTAT) == 0) {
                        dsm_crm_ctrl |= (CRM_CTRL_DVFS_VCTRL);
                        writel(dsm_crm_ctrl, DSM_CRM_CONTROL);
                }
        } else {
                if (((readl(CRM_AP_ADCR)) & ADCR_VSTAT) != 0) {
                        /*
                         * Frequency change within the same-lo voltage is not approved.
                         * Inorder to do Integer DVFS, move to the high voltage range and
                         * then set LFDF and move to the low voltage range
                         */
                        return -INT_DFS_LOW_NOT_ALLOW;
                }
        }
        if (ret_val == 0) {
                /* LFDF is used */
                apdiv = mxc_main_clock_divider(CPU_CLK);
                if (apdiv != 1) {
                        /*
                         * Set it back to 1 since previously Integer DVFS
                         * using AP_DIV was done before
                         */
                        if ((apdiv >= 2) && (CRM_ISSUE == 1) && (NO_SDRAMC_RATIO_ISSUE == 0)) {
                                /* Retaining divide by 2 because of the issue */
                                writel((readl(CRM_AP_ACDR) & 0x000000FF), CRM_AP_ACDR);
                        } else {
                                /* For all AP_DIV other than 1 */
                                ret_val = mxc_pm_ahb_ipdiv((mxc_get_clocks(CPU_CLK) * mxc_get_lfdf_value() * apdiv), ahbfreq, ipfreq, 0);
                                for (i=0; i<100; i++);
                                writel(((readl(CRM_AP_ACDR) & 0x000000FF) | 0x00000800), CRM_AP_ACDR);
                        }
                }
                readval = readl(CRM_AP_ACDR);
                readval = readl(CRM_AP_ADCR);
                return ret_val;
        }
        /* For any other intermediate frequencies lower than pll freq */
        /*
         * In this case, change integer DVFS is done by changing AP_DIV and
         * having LFDF = 1
         */
        runfreq = mxc_get_clocks(CPU_CLK);
        if ((readl(CRM_AP_ASCSR) & 0x00000018) != 0) {
                pllfreq = mxc_pll_clock(DSPPLL);
        } else {
                pllfreq = mxc_pll_clock(MCUPLL);
        }
        div_val = mxc_pm_findnext(armfreq);
        if ((div_val == 0x1) && (runfreq == pllfreq)) {
                /*
                 * Only when current freq equal to PLL freq, the
                 * divider value is 1. if not, we retain the existing
                 * frequency which is lower than what is required
                 */
                div_val = 0x8;
        } else if ((div_val <= 0x6) && (div_val > 0x1)) {
                div_val = div_val - 0x2;
        } else if (div_val >= 0x8) {
                div_val = div_val - 0x3;
        } else if (div_val >= 0x10) {
                div_val = div_val - 0x4;
        } else if (div_val >= 0x12) {
                div_val = div_val - 0x5;
        } else {
                /* retain the existing divider */
                return 0;
        }
        div_val = div_val << 8;
        mxc_pm_setlfdf(LFDF_0);
        writel(((readl(CRM_AP_ACDR) & 0x000000FF) | div_val), CRM_AP_ACDR);
        ret_val = mxc_pm_ahb_ipdiv((mxc_get_clocks(CPU_CLK) * mxc_get_lfdf_value()), ahbfreq, ipfreq, 0);
        return ret_val;
}

/*!
 * Steps required in addition if the option selected is alternate PLL
 *
 * @param   armfreq    The desired ARM frequency
 * @param   pll_num    PLL that has to be changed
 * @param   change_req Voltage/Frequency transition either increase or decrease
 *
 */
static void mxc_pm_altpll(long armfreq, int pll_num, int change_req)
{
        unsigned int adcr_val = readl(CRM_AP_ADCR);
        int ap_sel;

        if (change_req == CHG_VF_HI) {
                /*
                 * When changing to high voltage, wait until
                 * TSTAT is expired
                 */
                while ((adcr_val & ADCR_TSTAT) == 0) {
                        adcr_val = readl(CRM_AP_ADCR);
                        /* loop until timer expires */
                }
        }
        if (pll_num != 0) {
                /* switching PLL from PLL0 to PLL1 */
                ap_sel = readl(CRM_AP_ASCSR) & 0xFFFFFFE7;
                ap_sel |= 0x00000008;
        } else {
                /* Switching back to PLL0 */
                ap_sel = readl(CRM_AP_ASCSR);
                ap_sel &= (0xFFFFFFE7);
        }
        writel(ap_sel, CRM_AP_ASCSR);
}

/*!
 * AP divider can be changed due to CRM workaround or Integer DVFS
 * using AP_DIV. So, they have to be reset to 1 to get core freq = pllfreq
 *
 * @param   change_req Voltage/Frequency transition either increase or decrease
 *
 */
static void mxc_pm_apdiv_adjust(int change_req)
{
        int i, current_vstat = 0;

        /*
         * AP divider can be changed due to
         * 1 - CRM Workaround
         * 2 - Integer DVFS using AP_DIV
         * This has to be reset to 1 if we have to get core freq = pll freq
         */
        if (((readl(CRM_AP_ADCR)) & ADCR_VSTAT) != 0) {
                current_vstat = 1;
        }
        if (CRM_ISSUE) {
                if (NO_SDRAMC_RATIO_ISSUE) {
                        /*
                         * At higher freq & voltage, change AP_DIV to 1 only if there
                         * is no SDRAMC issue. AHB and IP dividers are already changed.
                         */
                         for (i=0; i<100; i++);
                         writel((readl(CRM_AP_ACDR) | 0x00000800), CRM_AP_ACDR);
                } else if (((change_req == CHG_FREQ_HI) || (change_req == CHG_VF_LO) ||
                           (change_req == CHG_FREQ_LO)) && (current_vstat == 1)) {
                        /*
                         * At high/lo freq at low voltage and not at hi voltage
                         * since at high voltage there is a risk that
                         * we may exceed 1:4:8 ratio
                         */
                        /*
                         * SDRAMC issue is not at low voltage as we are
                         * maintaining ratio less than 1:4:8 as the breakpt
                         * frequency is 400Mhz
                         */
                        for (i=0; i<100; i++);
                        writel((readl(CRM_AP_ACDR) | 0x00000800), CRM_AP_ACDR);
                } else if (mxc_main_clock_divider(CPU_CLK) > 2) {
                        /*
                         * This implies there is SDRAM issue
                         * So, reset divider back to 2 if they are
                         * previously changed to more than 2 by Integer DVFS
                         * using AP_DIV
                         */
                        for (i=0; i<100; i++);
                        writel((readl(CRM_AP_ACDR) & 0xFFFFF0FF), CRM_AP_ACDR);
                }
        } else {
                /* AP_DIV due to Integer DVFS */
                for (i=0; i<100; i++);
                writel((readl(CRM_AP_ACDR) | 0x00000800), CRM_AP_ACDR);
        }
}

/*!
 * AHB and IP divider are changed due to PLL scaling (either due to CRM workaround)
 * or software set up
 *
 * @param   armfreq    The desired ARM frequency
 * @param   ahbfreq    The desired AHB frequency
 * @param   ipfreq     The desired IP frequency
 * @param   change_req Voltage/Frequency transition either increase or decrease
 *
 */
static void mxc_pm_ahb_ipdiv_adjust(long armfreq, long ahbfreq, long ipfreq, int change_req)
{
        int current_vstat = 0, ahb;

        if (((readl(CRM_AP_ADCR)) & ADCR_VSTAT) != 0) {
                current_vstat = 1;
        }
        /* Calculating AHB and IP dividers */
        if (change_req == CHG_FREQ_LO || change_req == CHG_VF_LO) {
                if ((CRM_ISSUE == 1) && (NO_SDRAMC_RATIO_ISSUE == 0)) {
                        /*
                         * For frequency change within high voltage range, SDRAMC
                         * ratio issue comes into picture. So, dividers have to be
                         * recalculated
                         */
                        if ((change_req == CHG_FREQ_LO) && (current_vstat == 0)) {
                                ahb = mxc_pm_ahb_ipdiv(armfreq, ahbfreq, ipfreq, 1);
                        } else {
                                ahb = mxc_pm_ahb_ipdiv(armfreq, ahbfreq, ipfreq, 0);
                        }
                } else {
                        ahb = mxc_pm_ahb_ipdiv(armfreq, ahbfreq, ipfreq, 0);
                }
        }
}


/*!
 * To scale the PLL according to the voltage change and/or frequency change
 *
 * @param   armfreq    The desired ARM frequency
 * @param   ahbfreq    The desired AHB frequency
 * @param   ipfreq     The desired IP frequency
 * @param   pll_num    The PLL number that has to be scaled.
 *                     Possible values are 0 through 2
 *                     0 - PLL0 (ARM); 1 - PLL1 (DSP); 2 - PLL2 (common domain)
 *
 * @return             Returns 0 on success or
 *                     Returns -1 on error
 */
static int mxc_pm_scale(long armfreq, long ahbfreq, long ipfreq, int pll_num)
{
        unsigned int dsm_crm_ctrl;
        int change_req, ret_val = -1, crm_issue = 0;

        /*
         * Three cases have to be handled
         * 1 - High Voltage to Low Voltage
         * 2 - Low Voltage to High Voltage
         * 3 - Change Frequency only within the
         * same voltage range
         */
        if (CRM_ISSUE) {
                crm_issue = 1;
        }
        change_req = if_hightolow(armfreq, ahbfreq, ipfreq, crm_issue);
        dsm_crm_ctrl = readl(DSM_CRM_CONTROL);
        if (change_req == CHG_VF_HI) {
                /* Case 1: normal to overdrive */
                /* Setting ap_delay */
                writel(((readl(CRM_AP_ADCR) & 0x0000ffff) | 0x00030000), CRM_AP_ADCR);
                dsm_crm_ctrl &= ~(CRM_CTRL_DVFS_VCTRL);
                writel(dsm_crm_ctrl, DSM_CRM_CONTROL);
                while(readl(CRM_AP_ADCR) & 0x10);
                if ((readl(pll_dp_ctlreg[pll_num]) & DP_HFSM) == 0) {
                        /* Change in low frequency setting */
                        ret_val = mxc_pm_calc_mf(armfreq, ahbfreq, ipfreq, pll_num, 0);
                } else {
                        ret_val = mxc_pm_calc_mf(armfreq, ahbfreq, ipfreq, pll_num, 1);
                }
        } else if (change_req == CHG_VF_LO) {
                /* Case 2: overdrive to normal */
                /* comment out TSTAT bit compare and try again */
                if ((readl(CRM_AP_ADCR) & ADCR_TSTAT) == 0) {
                        dsm_crm_ctrl |= (CRM_CTRL_DVFS_VCTRL);
                        writel(dsm_crm_ctrl, DSM_CRM_CONTROL);
                }
                if ((readl(pll_dp_ctlreg[pll_num]) & DP_HFSM) == 0) {
                        /* Change in low frequency setting */
                        ret_val = mxc_pm_calc_mf(armfreq, ahbfreq, ipfreq, pll_num, 0);
                } else {
                        ret_val = mxc_pm_calc_mf(armfreq, ahbfreq, ipfreq, pll_num, 1);
                }
        } else {
                /* Case 3: Change frequency only */
                if ((readl(pll_dp_ctlreg[pll_num]) & DP_HFSM) == 0) {
                        /* Change in low frequency setting */
                        ret_val = mxc_pm_calc_mf(armfreq, ahbfreq, ipfreq, pll_num, 0);
                } else {
                        ret_val = mxc_pm_calc_mf(armfreq, ahbfreq, ipfreq, pll_num, 1);
                }
        }
        if(((readl(CRM_AP_ADCR) & (ADCR_ALT_PLL)) != 0) && mfn_status == 0) {
                if (pll_num != 0) {
                        mxc_pm_altpll(armfreq, pll_num, change_req);
                }
        }
        /* Calculating AHB and IP dividers */
        if (change_req == CHG_FREQ_LO || change_req == CHG_VF_LO) {
                mxc_pm_ahb_ipdiv_adjust(armfreq, ahbfreq, ipfreq, change_req);
        }
        mxc_pm_apdiv_adjust(change_req);
        return ret_val;
}

/*!
 * Implementing steps required to set PLL Scaling using patref, noclock, alt_pll
 *
 * @param   armfreq    The desired ARM frequency. AHB and IP
 *                     frequency are changed depending on ARM
 *                     frequency and the divider values.
 * @param   ahbfreq    The desired AHB frequency
 * @param   ipfreq     The desired IP frequency
 *
 * @return             Returns 0 on success or
 *                     Returns -1 on error
 */
int mxc_pm_pllscale(long armfreq, long ahbfreq, long ipfreq)
{
        unsigned int adcr_val, dvfs_bypass, i;
        unsigned long runfreq;
        int pll_num = 0, ret_val = -1, change_req, crm_issue = 0;

        runfreq = mxc_get_clocks(CPU_CLK);
        /*
         * Workaround for CRM issue as well as S/W setup to avoid AHB
         * exceeding 133Mhz
         */
        if (CRM_ISSUE) {
                if (mxc_main_clock_divider(CPU_CLK) < 2) {
                        writel((readl(CRM_AP_ACDR) & 0x000000FF ), CRM_AP_ACDR);
                }
                for (i=0; i<100; i++);
                crm_issue = 1;
        }
        /* Clearing DVFS_BYP bit */
        dvfs_bypass = readl(DSM_CRM_CONTROL) & (~(CRM_CTRL_DVFS_BYP));
        writel(dvfs_bypass, DSM_CRM_CONTROL);
        /* Clearing DFS_DIV_EN bit */
        adcr_val = (readl(CRM_AP_ADCR)) | ADCR_DIV_BYP;
        writel(adcr_val, CRM_AP_ADCR);
        if (CONFIG_PLL_PATREF) {
                /* Clear CLK_ON to indicate it is PLL pat ref */
                adcr_val = readl(CRM_AP_ADCR) | (ADCR_CLK_ON);
                /* Clear ALT_PLL to indicate PLL relock */
                adcr_val &= ~(ADCR_ALT_PLL);
        } else if (CONFIG_PLL_NOCLOCK) {
                /* Clear CLK_ON to indicate it is PLL no lock */
                adcr_val = (readl(CRM_AP_ADCR)) & (~(ADCR_CLK_ON));
                /* Clear ALT_PLL to indicate PLL relock */
                adcr_val &= ~(ADCR_ALT_PLL);
        } else if (CONFIG_ALT_PLL) {
                /* Set ALT_PLL to indicate alternate PLL */
                adcr_val |= (ADCR_ALT_PLL);
                writel(adcr_val, CRM_AP_ADCR);
                /* Choose alternate PLL */
                pll_num = 1;
                /*
                 * Enable PLL1. PLL2 is not used because max frequency
                 * obtained is 200Mhz according to the spec on PLL2.
                 */
                for (i=0; i<100; i++);
                writel(0x00000023, pll_dp_ctlreg[pll_num]);
                if ((armfreq / 1000000) == (mxc_pll_clock(MCUPLL) / 1000000)) {
                        writel((readl(CRM_AP_ADCR) & (~ADCR_DFS_DIV_EN)), CRM_AP_ADCR);
                        pll_num = 0;
                        /* Switch to PLL0 which is already locked at 399Mhz */
                        change_req = if_hightolow(armfreq, ahbfreq, ipfreq, crm_issue);
                        if (change_req == CHG_VF_LO) {
                                /* overdrive to normal */
                                if ((readl(CRM_AP_ADCR) & ADCR_TSTAT) == 0) {
                                        writel((readl(DSM_CRM_CONTROL) | CRM_CTRL_DVFS_VCTRL), DSM_CRM_CONTROL);
                                }
                        }
                        mxc_pm_altpll(armfreq, pll_num, change_req);
                        if (change_req == CHG_VF_LO || change_req == CHG_FREQ_LO) {
                                mxc_pm_ahb_ipdiv_adjust(armfreq, ahbfreq, ipfreq, change_req);
                        }
                        mxc_pm_apdiv_adjust(change_req);
                        return 0;
                }
        } else if (CONFIG_PLL_MFN) {
                /* Set ALT_PLL to indicate alternate PLL */
                adcr_val |= (ADCR_ALT_PLL);
                mfn_status = 1;
        }
        writel(adcr_val, CRM_AP_ADCR);
        writel((readl(CRM_AP_ADCR) & (~ADCR_DFS_DIV_EN)), CRM_AP_ADCR);
        ret_val = mxc_pm_scale(armfreq, ahbfreq, ipfreq, pll_num);
        return ret_val;
}

/*!
 * To find the suitable scaling techniques if user is unknown of the choice
 * between Integer scaling and PLL scaling
 *
 * @param   armfreq    The desired ARM frequency
 * @param   ahbfreq    The desired AHB frequency
 * @param   ipfreq     The desired IP frequency
 *
 * @return             Returns 0 on success
 */
int mxc_pm_dvfs(unsigned long armfreq, long ahbfreq, long ipfreq)
{
        unsigned long runfreq, pllfreq;
        int retval = -1;

        /*
         * Using PLL scaling minimum freq of 260MHz can be obtained.
         * So, we use integer scaling for 260MHz and even if the divider factor
         * may not be integer multiple
         */
        if (armfreq < 260000000) {
                return mxc_pm_intscale(armfreq, ahbfreq, ipfreq);
        }
        runfreq = (mxc_get_clocks(CPU_CLK) / 1000000) + 1;
        if ((readl(CRM_AP_ASCSR) & 0x00000018) != 0) {
                pllfreq = (mxc_pll_clock(DSPPLL) / 1000000) + 1;
        } else {
                pllfreq = (mxc_pll_clock(MCUPLL) / 1000000) + 1;
        }
        if ((armfreq / 1000000) > pllfreq) {
                retval = mxc_pm_pllscale(armfreq, ahbfreq, ipfreq);
        } else if ((pllfreq % (armfreq / 1000000)) == 0) {
                if ((CRM_ISSUE_INT) && (armfreq >= 266000000)) {
                        retval = mxc_pm_pllscale(armfreq, ahbfreq, ipfreq);
                } else {
                        retval = mxc_pm_intscale(armfreq, ahbfreq, ipfreq);
                }
        } else {
                retval = mxc_pm_pllscale(armfreq, ahbfreq, ipfreq);
        }
        return retval;
}

/*!
 * Interrupt service routine registered to handle the DSM interrupt.
 *
 * @param   irq     the interrupt number
 * @param   dev_id  driver private data
 * @param   regs    holds a snapshot of the processor's context before the
 *                  processor entered the interrupt code
 *
 * @return          The function returns \b IRQ_RETVAL(1) if interrupt was
 *                  handled, returns \b IRQ_RETVAL(0) if the interrupt was
 *                  not handled.
 *                  \b IRQ_RETVAL is defined in \b include/linux/interrupt.h.
 */
static irqreturn_t mxc_pm_inthand(int irq, void *dev_id, struct pt_regs *regs)
{
        int handled = 0;
        unsigned int sreg1, status = 0;

        sreg1 = readl(DSM_INT_STATUS);
        if (sreg1 & INT_STATUS_RWK) {
                /* When wake up is caused by external interrupt */
                writel((readl(DSM_MASK_REG) | 0xFFFFFC00), DSM_MASK_REG);
                writel((readl(DSM_INT_STATUS) | INT_STATUS_RWK), DSM_INT_STATUS);
                handled = 1;
        } else if (sreg1 & INT_STATUS_WKTIME_INT) {
                /* When wake up is caused by wakeup timer */
                status |= (INT_STATUS_WKTIME_INT);
                writel(status, DSM_INT_STATUS);
                handled = 1;
        } else {
                /* for all other interrupts */
                status |= 0x0000017F;
                writel(status, DSM_INT_STATUS);
                handled = 1;
        }
        return IRQ_RETVAL(handled);
}

/*!
 * Implementing steps required to transition to low-power modes
 *
 * @param   mode    The desired low-power mode. Possible values are,
 *                  WAIT_MODE, STOP_MODE or DSM_MODE
 *
 */
void mxc_pm_lowpower(int mode)
{
        unsigned int crm_ctrl, ctrl_slp, retval;

        local_irq_disable();
        /* Turning on keypad clock to run during STOP mode */
        writel((readl(CRM_AP_AMLPMRA) | 0x00000800), CRM_AP_AMLPMRA);
        switch(mode) {
        case WAIT_MODE:
                /* Writing '01' to CRM_lpmd bits */
                crm_ctrl = (readl(DSM_CRM_CONTROL) & ~(CRM_CTRL_LPMD1)) | (CRM_CTRL_LPMD0);
                writel(crm_ctrl, DSM_CRM_CONTROL);
                break;
        case STOP_MODE:
                /* Writing '00' to CRM_lpmd bits */
                /* Bypass all acknowledgements */
                writel((readl(CRM_AP_AMCR) | 0x007F0000), CRM_AP_AMCR);
                /* Enable KPP Interrupt */
                writel((readl(LO_INTR_EN) | 0x01000000), LO_INTR_EN);
                /* Disable Timer interrupt source */
                writel((readl(LO_INTR_EN) & 0xCFFFFFFF), LO_INTR_EN);
                crm_ctrl = (readl(DSM_CRM_CONTROL) & ~(CRM_CTRL_LPMD1)) & (~CRM_CTRL_LPMD0);
                writel(crm_ctrl, DSM_CRM_CONTROL);
                break;
        case DSM_MODE:
                /* Registering DSM interrupt handler */
                retval = request_irq(INT_DSM_AP, mxc_pm_inthand, 0,
                                     "mxc_pm_dsm", NULL);
                /* Bypass all acknowledgements and enable WellBias*/
                writel((readl(CRM_AP_AMCR) | 0x007F0001), CRM_AP_AMCR);
                /* Enable KPP Interrupt */
                writel((readl(LO_INTR_EN) | 0x01000000), LO_INTR_EN);
                /* Disable Timer interrupt source */
                writel((readl(LO_INTR_EN) & 0xCFFFFFFF), LO_INTR_EN);
                /* Enable DSM */
                writel((readl(DSM_CONTROL0) | CONTROL0_MSTR_EN), DSM_CONTROL0);
                /* Enable SLEEP and WAKE UP DISABLE */
                ctrl_slp = ((readl(DSM_CONTROL1) | CONTROL1_SLEEP) | CONTROL1_WAKEUP_DISABLE);
                writel(ctrl_slp, DSM_CONTROL1);
                if (CONFIG_RESET_COUNT32) {
                        /* Set RST_COUNT32_EN bit */
                        writel((readl(DSM_CONTROL1) | CONTROL1_RST_CNT32_EN), DSM_CONTROL1);
                }
                /* Set STBY_COMMIT_EN bit */
                writel((readl(DSM_CONTROL0) | CONTROL0_STBY_COMMIT_EN), DSM_CONTROL0);
                /* Set RESTART bit */
                writel((readl(DSM_CONTROL0) | CONTROL0_RESTART), DSM_CONTROL0);
                if ((readl(DSM_CONTROL1) & CONTROL1_RST_CNT32_EN)) {
                        /*
                         * If RST_CNT32_EN bit is set, all the registers
                         * can be cleared to 0
                         */
                        writel(0x00000200, DSM_SLEEP_TIME);
                        writel(0, DSM_WAKE_TIME);
                        writel(0, DSM_WARM_TIME);
                        writel(0, DSM_LOCK_TIME);
                        writel(0, DSM_RESTART_TIME);
                } else {
                        /*
                         * The max value for COUNT32 register before it is reset is
                         * 65 seconds before it is reset. So, I have used some arbitrary
                         * values such that the total time for DSM which is 0x00055fff
                         * here is within this value. These values are bound to change
                         * after testing.
                         */
                        writel(0x00000200, DSM_SLEEP_TIME);
                        writel(0x001fffff, DSM_WAKE_TIME);
                        writel(0x00000500, DSM_WARM_TIME);
                        writel(0x00000700, DSM_LOCK_TIME);
                        writel(0x001fffff, DSM_RESTART_TIME);
                }
                writel((readl(DSM_CTREN) | DSM_CTREN_CNT32), DSM_CTREN);
                /* Writing '00' to CRM_lpmd bits */
                crm_ctrl = (readl(DSM_CRM_CONTROL) & (~CRM_CTRL_LPMD1)) & (~CRM_CTRL_LPMD0);
                writel(crm_ctrl, DSM_CRM_CONTROL);
                break;
        }
        /* Executing CP15 (Wait-for-Interrupt) Instruction */
        cpu_do_idle();
        /* Enable timer interrupt */
        writel((readl(LO_INTR_EN) | 0x30000000), LO_INTR_EN);
        local_irq_enable();
}

/*!
 * Implementing Level 1 CRM Gate Control. Level 2 gate control
 * is provided at module level using LPMD registers
 *
 * @param   group   The desired clock gate control register bits.
 *                  Possible values are 0 through 6
 * @param   opt     The desired option requesting clock to run during stop
 *                  and wait modes or just during the stop mode. Possible
 *                  values are GATE_STOP_WAIT and GATE_STOP.
 *
 */
void mxc_pm_clockgate(int group, int opt)
{
        if (opt == GATE_STOP_WAIT) {
                writel(((readl(CRM_AP_ACGCR) & (~gate_run[group])) | (gate_stop_wait[group])), CRM_AP_ACGCR);
        } else if (opt == GATE_STOP) {
                writel(((readl(CRM_AP_ACGCR) & (~gate_run[group])) | (gate_stop[group])), CRM_AP_ACGCR);
        }
}

/*!
 * Configuring CKOH pins to measure changed frequency
 *
 * @param   opt     The desired clock needed to measure. Possible
 *                  values are, CKOH_AP_SEL, CKOH_AHB_SEL or CKOH_IP_SEL
 *
 */
void mxc_pm_ckohsel(unsigned int opt)
{
        switch(opt) {
        case 1:
                /* To select AP clock */
                writel(0x00001600, CRM_AP_ACR);
                break;
        case 2:
                /* To select AHB clock */
                writel(0x00002600, CRM_AP_ACR);
                break;
        case 3:
                /* To select IP clock */
                writel(0x00003600, CRM_AP_ACR);
                break;
        }
}

/*!
 * Configuring Atlas Regulators to supply 1.6V always
 */
void mxc_pm_atlas_high(void)
{
        struct t_switcher_conf sconfa;

        /* Configure Atlas regulator */
        /* get the current configuration */
        atlas_power_get_switcher(SW_SW1A, &sconfa);
        /* change from current configuration */
        sconfa.sw_setting = 0x1C;
        sconfa.sw_setting_dvs = 0x1C;
        sconfa.sw_setting_standby = 0x4;
        sconfa.sw_dvs_speed = 0x3;
        atlas_power_conf_switcher(SW_SW1A, &sconfa);
}

/*!
 * Configuring Atlas Regulators to supply 1.2V always
 */
void mxc_pm_atlas_low(void)
{
        struct t_switcher_conf sconfa;

        /* Configure Atlas regulator */
        /* get the current configuration */
        atlas_power_get_switcher(SW_SW1A, &sconfa);
        /* change from current configuration */
        sconfa.sw_setting = 0xC;
        sconfa.sw_setting_dvs = 0xC;
        sconfa.sw_setting_standby = 0x4;
        sconfa.sw_dvs_speed = 0x3;
        atlas_power_conf_switcher(SW_SW1A, &sconfa);
}

/*!
 * Configuring Atlas Regulators to supply 1.6V at Hi voltage and
 * 1.2V at Lo voltage
 */
void mxc_pm_atlas_hilo(void)
{
        struct t_switcher_conf sconfa;

        /* Configure Atlas regulator */
        /* get the current configuration */
        atlas_power_get_switcher(SW_SW1A, &sconfa);
        /* change from current configuration */
        sconfa.sw_setting = 0x1C;
        sconfa.sw_setting_dvs = 0xC;
        sconfa.sw_setting_standby = 0x4;
        sconfa.sw_dvs_speed = 0x3;
        atlas_power_conf_switcher(SW_SW1A, &sconfa);
}

/*!
 * This function is used to load the module.
 *
 * @return   Returns an Integer on success
 */
int __init mxc_pm_init_module(void)
{
        unsigned long adcr_val, cscr_val;

        /* Resetting LFDF */
        mxc_pm_setlfdf(LFDF_0);
        /* Disable DFS_DIV_EN bit */
        adcr_val = readl(CRM_AP_ADCR) & (~ADCR_DFS_DIV_EN);
        writel(adcr_val, CRM_AP_ADCR);
        /* To select AP clock domain */
        cscr_val = readl(CRM_COM_CSCR);
        cscr_val |= (CSCR_CKOHSEL);
        writel(cscr_val, CRM_COM_CSCR);
        /*
         * To set CKOH divider (divide by 10) and select CKOH pin to measure ap_clk
         * by default
         */
        writel(0x00001600, CRM_AP_ACR);
        /* Set AREN bit for PLL Auto restart enable for PLL scaling */
        writel((DP_AREN | readl(PLL0_DP_CONFIG)), PLL0_DP_CONFIG);
        /* Init settings for PLL */
        writel((readl(PLL0_DP_CTL) | 0x00000032), PLL0_DP_CTL);
        /*
         * Possible int divisors are 1 through 16.
         * These are theoretical values. Most settings
         * require a ratio of 1:4:8 for ARM:AHB:IP. Also,
         * by default these divisors are set to 1:4:8 because,
         * in case of integer scaling, the ratio is changed to
         * 1:2:4 for low freq setting and in case of PLL scaling this
         * remains as such
         */
        printk("Low-Level PM Driver module loaded\n");
        return 0;
}

/*!
 * This function is used to unload the module
 */
void __exit mxc_pm_cleanup_module(void)
{
        printk("Low-Level PM Driver module Unloaded\n");
}

module_init(mxc_pm_init_module);
module_exit(mxc_pm_cleanup_module);

EXPORT_SYMBOL(mxc_pm_dvfs);
EXPORT_SYMBOL(mxc_pm_intscale);
EXPORT_SYMBOL(mxc_pm_pllscale);
EXPORT_SYMBOL(mxc_pm_ckohsel);
EXPORT_SYMBOL(mxc_pm_clockgate);
EXPORT_SYMBOL(mxc_pm_lowpower);
EXPORT_SYMBOL(mxc_pm_atlas_high);
EXPORT_SYMBOL(mxc_pm_atlas_low);
EXPORT_SYMBOL(mxc_pm_atlas_hilo);

MODULE_AUTHOR("Freescale Semiconductor");
MODULE_DESCRIPTION("SCMA11 Low-level PM Driver");
MODULE_LICENSE("GPL");
