//-----------------------------------------------------------------------------
//  refclock.c
//    Allows dynamic retrieval / updating of reference clocks.
//--
//  Copyright (c) Logic Product Development.  All rights reserved.
//  THIS SOURCE CODE, AND ITS USE AND DISTRIBUTION, IS SUBJECT TO THE TERMS
//  AND CONDITIONS OF THE APPLICABLE LICENSE AGREEMENT
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//
//  Copyright (C) 2004-2006, Freescale Semiconductor, Inc. All Rights Reserved.
//  THIS SOURCE CODE, AND ITS USE AND DISTRIBUTION, IS SUBJECT TO THE TERMS
//  AND CONDITIONS OF THE APPLICABLE LICENSE AGREEMENT
//
//-----------------------------------------------------------------------------
#include <windows.h>
#include <ceddk.h>
#include "bsp.h"
#include <imx31_refclock.h>
#include <nkintr.h>
#include "regs.h"

//-----------------------------------------------------------------------------
// Board clocks 
//-----------------------------------------------------------------------------
#define CKIH_INPUT_FREQ                 26000000U
#define CKIL_INPUT_FREQ                 32000U

// register value definitions for MPCTL and PDR0 for default and overclock modes
#define MPCTL_399MHZ                    0x00331C23
#define MPCTL_532MHZ                    0x00192806

#define PDR0_399MHZ                     0xFF871450
#define PDR0_532MHZ                     0xFF871C58
#define PDR0_266MHZ                     0xFF871C59

#define PDR0_MCU_PODF                   0x00000007

#define CLOCK_DEFAULT                   0
#define CLOCK_OVERCLOCK                 1
#define CLOCK_LOWCLOCK                  2

#define CCM_XXCTL_MFN_LSH               CCM_MPCTL_MFN_LSH
#define CCM_XXCTL_MFI_LSH               CCM_MPCTL_MFI_LSH
#define CCM_XXCTL_MFD_LSH               CCM_MPCTL_MFD_LSH
#define CCM_XXCTL_PD_LSH                CCM_MPCTL_PDF_LSH
#define CCM_XXCTL_BRM_LSH               CCM_MPCTL_BRMO_LSH
#define CCM_XXCTL_MFN_WID               CCM_MPCTL_MFN_WID
#define CCM_XXCTL_MFI_WID               CCM_MPCTL_MFI_WID
#define CCM_XXCTL_MFD_WID               CCM_MPCTL_MFD_WID
#define CCM_XXCTL_PD_WID                CCM_MPCTL_PDF_WID
#define CCM_XXCTL_BRM_WID               CCM_MPCTL_BRMO_WID

//-----------------------------------------------------------------------------
// Local Variables
static PCSP_CCM_REGS g_pCCM;
static UINT32 g_clockFreq[DDK_CLOCK_SIGNAL_ENUM_END];


//-----------------------------------------------------------------------------
// Local Helper Functions
static void RefClockUpdate(void);
static UINT32 CalculatePLL(UINT32 ref, UINT32 ctl);
static void InitializePLLs(void);

//-----------------------------------------------------------------------------
// External functions
extern BOOL OALPmicWriteMasked(UINT32 addr, UINT32 mask, UINT32 data);

//-----------------------------------------------------------------------------
// IMX31RefClockInit
//-----------------------------------------------------------------------------
BOOL IMX31RefClockInit(void)
{
   g_pCCM = (PCSP_CCM_REGS)OALPAtoUA(CSP_BASE_REG_PA_CCM);
   if (g_pCCM == NULL) {
       return FALSE;
   }
   
   InitializePLLs();
   RefClockUpdate();
   return TRUE;
}

//-----------------------------------------------------------------------------
// IMX31RefClockNotifyChanged
//-----------------------------------------------------------------------------
void IMX31RefClockNotifyChanged(void)
{
   RefClockUpdate();
   return;
}

//-----------------------------------------------------------------------------
// IMX31RefClockGet
//-----------------------------------------------------------------------------
BOOL IMX31RefClockGet(DDK_CLOCK_SIGNAL ddkClkSig, UINT32* pClkFreq)
{
   RefClockUpdate();
   
   OALMSG(OAL_FUNC, (L"Clock Frequencies:\r\n"));
   OALMSG(OAL_FUNC, (L"    MCU PLL    = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_MCUPLL]));
   OALMSG(OAL_FUNC, (L"    USB PLL    = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_USBPLL]));
   OALMSG(OAL_FUNC, (L"    SER PLL    = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_SERPLL]));
   OALMSG(OAL_FUNC, (L"    ARM CLOCK  = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_ARM]));
   OALMSG(OAL_FUNC, (L"    IPU CLOCK  = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_IPU]));
   OALMSG(OAL_FUNC, (L"    AHB CLOCK  = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_AHB]));
   OALMSG(OAL_FUNC, (L"    IPG CLOCK  = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_IPG]));
   OALMSG(OAL_FUNC, (L"    NFC CLOCK  = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_NFC]));
   OALMSG(OAL_FUNC, (L"    GACC CLOCK = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_GACC]));
   OALMSG(OAL_FUNC, (L"    PER CLOCK  = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_PER]));
   OALMSG(OAL_FUNC, (L"    SSI1 CLOCK = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_SSI1]));
   OALMSG(OAL_FUNC, (L"    SSI2 CLOCK = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_SSI2]));
   OALMSG(OAL_FUNC, (L"    FIRI CLOCK = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_FIRI]));
   OALMSG(OAL_FUNC, (L"    CSI CLOCK  = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_CSI]));
   OALMSG(OAL_FUNC, (L"    USB CLOCK  = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_USB]));
   OALMSG(OAL_FUNC, (L"    SIM CLOCK  = %9d Hz\r\n", g_clockFreq[DDK_CLOCK_SIGNAL_SIM]));
   
   if ((DDK_CLOCK_SIGNAL_ENUM_FIRST <= ddkClkSig) &&
       (DDK_CLOCK_SIGNAL_ENUM_END > ddkClkSig) &&
       (NULL != pClkFreq)) {
      *pClkFreq = g_clockFreq[ddkClkSig];
      return TRUE;
   }
   
   return FALSE;
}


void RefClockSetGatingMode(DDK_CLOCK_GATE_INDEX index, DDK_CLOCK_GATE_MODE mode)
{
   BOOL fEnable;
   // CGR is a shared register so we must disable interrupts temporarily for
   // safe access
   fEnable = INTERRUPTS_ENABLE(FALSE);
   
   // Update the clock gating mode
   INSREG32(&g_pCCM->CGR[CCM_CGR_INDEX(index)], CCM_CGR_MASK(index), CCM_CGR_VAL(index, mode));
   
   INTERRUPTS_ENABLE(fEnable);
}


BOOL IMX31RefClockSetOverclock(DWORD dwClockMode)
{
   DWORD mpctl = INREG32(&g_pCCM->MPCTL);
   BOOL fRetValue=TRUE;
   
   /* there are currently three modes implemented:
    *   0 - default speed               (399MHz)
    *   1 - overclock speed             (532MHz)
    *   2 - overclock speed cut in half (266MHz)
    */
   
   switch (dwClockMode) {
      case CLOCK_DEFAULT:
         /* Check the MPCTL and PDR0 registers to see if we are already 
          * running in default mode. If not, change the core voltage to
          * 1.4V and change the frequency to 399MHz.
          */
         if (MPCTL_532MHZ == mpctl) {
            // in case we come from OVERCLOCK_HALF
            OUTREG32(&g_pCCM->PDR0, PDR0_532MHZ);
            
            // change PLL frequency from 532MHz to 399MHz
            OUTREG32(&g_pCCM->MPCTL, MPCTL_399MHZ);
            
            // change dividers from 2 to 3
            OUTREG32(&g_pCCM->PDR0, PDR0_399MHZ);
            
            // change voltage to 1.4V
            OALPmicWriteMasked(
                 MC13783_SW0_ADDR
                ,PMIC_SWITCHERS0_SW1A_MASK
                ,PMIC_SWITCHERS0_SW1A_1_4V );
         }
         break;
      case CLOCK_OVERCLOCK:
         /* Check the MPCTL and PDR0 registers to see if we are already 
          * running in overclock mode. If not, change the core voltage to
          * 1.6V and change the frequency to 532MHz.
          */
         if (MPCTL_399MHZ == mpctl) {
            // change voltage to 1.6V
            OALPmicWriteMasked(
                 MC13783_SW0_ADDR
                ,PMIC_SWITCHERS0_SW1A_MASK
                ,PMIC_SWITCHERS0_SW1A_1_6V );
            
            // change dividers from 3 to 2
            OUTREG32(&g_pCCM->PDR0, PDR0_532MHZ);
            
            // change PLL frequency from 399MHz to 532MHz
            OUTREG32(&g_pCCM->MPCTL, MPCTL_532MHZ);
         }
         else {
            // in case we come from OVERCLOCK_HALF
            OUTREG32(&g_pCCM->PDR0, PDR0_532MHZ);
         }
         break;
         
      case CLOCK_LOWCLOCK:
         /* Check the MPCTL and PDR0 registers to see if we are already 
          * running in overclock mode. If not, change the core voltage to
          * 1.6V and change the frequency to 532MHz.
          */
         if (MPCTL_399MHZ == mpctl) {
            // change voltage to 1.6V
            OALPmicWriteMasked(
                 MC13783_SW0_ADDR
                ,PMIC_SWITCHERS0_SW1A_MASK
                ,PMIC_SWITCHERS0_SW1A_1_6V );
            
            // change dividers from 3 to 2
            OUTREG32(&g_pCCM->PDR0, PDR0_532MHZ);
            
            // change PLL frequency from 399MHz to 532MHz
            OUTREG32(&g_pCCM->MPCTL, MPCTL_532MHZ);
         }
         
         // update MCU_PODF divider to divide by 2
         OUTREG32(&g_pCCM->PDR0, PDR0_266MHZ);
         break;
      default:
         fRetValue = FALSE;
         break;
   }
   
   return fRetValue;
}


DWORD IMX31RefClockGetOverclock(void)
{
   DWORD ret = CLOCK_DEFAULT;
   
   BOOL bOverclock = (MPCTL_532MHZ == INREG32(&g_pCCM->MPCTL));
   BOOL bHalfclock = (PDR0_MCU_PODF & INREG32(&g_pCCM->PDR0)) ? TRUE : FALSE;
   
   if (bOverclock) {
      ret = CLOCK_OVERCLOCK;
      
      if (bHalfclock) {
         ret = CLOCK_LOWCLOCK;
      }
   }
   
   return ret;
}


static void RefClockUpdate(void)
{
   UINT32 mcupll, usbpll, serpll;
   UINT32 pdr, ccmr, div, ahbFreq;
   
   mcupll = g_clockFreq[DDK_CLOCK_SIGNAL_MCUPLL];
   usbpll = g_clockFreq[DDK_CLOCK_SIGNAL_USBPLL];
   serpll = g_clockFreq[DDK_CLOCK_SIGNAL_SERPLL];
   
   // Get post dividers for MCU clock domain
   pdr = INREG32(&g_pCCM->PDR0);
   
   // Calculate MCU clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR0_MCU_PODF);
   g_clockFreq[DDK_CLOCK_SIGNAL_ARM] = mcupll / (div + 1);
   
   // Calculate HSP (IPU) clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR0_HSP_PODF);
   g_clockFreq[DDK_CLOCK_SIGNAL_IPU] = mcupll / (div + 1);
   
   // Calculate AHB (MAX) clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR0_MAX_PODF);
   ahbFreq = mcupll / (div + 1);
   g_clockFreq[DDK_CLOCK_SIGNAL_AHB] = ahbFreq;
   
   // Calculate IPG clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR0_IPG_PODF);
   g_clockFreq[DDK_CLOCK_SIGNAL_IPG] = ahbFreq / (div + 1);
   
   // Calculate NFC clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR0_NFC_PODF);
   g_clockFreq[DDK_CLOCK_SIGNAL_NFC] = ahbFreq / (div + 1);
   
   // Calculate GACC clock frequency
   g_clockFreq[DDK_CLOCK_SIGNAL_GACC] = ahbFreq >> 1;
   
   // Calculate PER clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR0_PER_PODF);
   
   // PER clock source can be IPG_CLK or USBPLL and is selected by PERCS
   if (EXTREG32BF(&g_pCCM->CCMR, CCM_CCMR_PERCS) == CCM_CCMR_PERCS_USB_CLK) {
      g_clockFreq[DDK_CLOCK_SIGNAL_PER] = usbpll / (div + 1);
   }
   else {
      g_clockFreq[DDK_CLOCK_SIGNAL_PER] = g_clockFreq[DDK_CLOCK_SIGNAL_IPG];
   }
   
   // Get clock source selections for peripheral clocks
   ccmr = INREG32(&g_pCCM->CCMR);
   
   // Calculate SIM clock frequency (same as PERCLK)
   g_clockFreq[DDK_CLOCK_SIGNAL_SIM] = g_clockFreq[DDK_CLOCK_SIGNAL_PER];
   
   // Calculate CSI clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR0_CSI_PODF) + 1;
   switch (CSP_BITFEXT(ccmr, CCM_CCMR_CSCS)) {
      case CCM_CCMR_CSCS_USB_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_CSI] = usbpll / div;
         break;
         
      case CCM_CCMR_CSCS_SERIAL_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_CSI] = serpll / div;
         break;
   }
   
   // Get post dividers for peripheral baud clocks
   pdr = INREG32(&g_pCCM->PDR1);
   
   // Calculate SSI1 clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR1_SSI1_PRE_PODF) + 1;
   div *= (CSP_BITFEXT(pdr, CCM_PDR1_SSI1_PODF) + 1);
   switch (CSP_BITFEXT(ccmr, CCM_CCMR_SSI1S)) {
      case CCM_CCMR_SSI1S_MCU_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_SSI1] = mcupll / div;
         break;
         
      case CCM_CCMR_SSI1S_USB_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_SSI1] = usbpll / div;
         break;
         
      case CCM_CCMR_SSI1S_SERIAL_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_SSI1] = serpll / div;
         break;
   }
   
   // Calculate SSI2 clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR1_SSI2_PRE_PODF) + 1;
   div *= (CSP_BITFEXT(pdr, CCM_PDR1_SSI2_PODF) + 1);
   switch (CSP_BITFEXT(ccmr, CCM_CCMR_SSI2S)) {
      case CCM_CCMR_SSI2S_MCU_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_SSI2] = mcupll / div;
         break;
         
      case CCM_CCMR_SSI2S_USB_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_SSI2] = usbpll / div;
         break;
         
      case CCM_CCMR_SSI2S_SERIAL_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_SSI2] = serpll / div;
         break;
   }
   
   // Calculate FIRI clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR1_FIRI_PRE_PODF) + 1;
   div *= (CSP_BITFEXT(pdr, CCM_PDR1_FIRI_PODF) + 1);
   switch (CSP_BITFEXT(ccmr, CCM_CCMR_FIRS)) {
      case CCM_CCMR_FIRS_MCU_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_FIRI] = mcupll / div;
         break;
         
      case CCM_CCMR_FIRS_USB_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_FIRI] = usbpll / div;
         break;
         
      case CCM_CCMR_FIRS_SERIAL_CLK:
         g_clockFreq[DDK_CLOCK_SIGNAL_FIRI] = serpll / div;
         break;
   }
   
   // Calculate USB clock frequency
   div = CSP_BITFEXT(pdr, CCM_PDR1_USB_PRDF) + 1;
   div *= (CSP_BITFEXT(pdr, CCM_PDR1_USB_PODF) + 1);
   g_clockFreq[DDK_CLOCK_SIGNAL_USB] = usbpll / div;
   
   return;
}

static UINT32 CalculatePLL(UINT32 ref, UINT32 ctl)
{
   UINT32 pllFreq;
   UINT32 brm, pd, mfd, mfi, mfn;
   
   // PLL = 2 * RefClk * (MFI + MFN/(MFD+1)) / (PD+1)
   // PLL = 2 * (RefClk*MFI + RefClk*MFN/(MFD+1)) / (PD+1)
   brm = CSP_BITFEXT(ctl, CCM_XXCTL_BRM);
   pd  = CSP_BITFEXT(ctl, CCM_XXCTL_PD) + 1;
   mfd = CSP_BITFEXT(ctl, CCM_XXCTL_MFD) + 1;
   mfi = CSP_BITFEXT(ctl, CCM_XXCTL_MFI);
   mfn = CSP_BITFEXT(ctl, CCM_XXCTL_MFN);
   
   ref /= 10; // Keeps (ref * mfn) from overflowing
   if (0 == (pd & 0x0001)) {
      pd /= 2;
      pllFreq = ((ref * mfi) + ((ref * mfn) / mfd)) / pd;
   }
   else {
      pllFreq = (2 * ((ref * mfi) + ((ref * mfn) / mfd))) / pd;
   }
   pllFreq *= 10; // Compenstate for previous division
   
   return pllFreq;
}

static void InitializePLLs(void)
{
   UINT32 ccmr, ref, ctl, pllFreq;
   
   ccmr = INREG32(&g_pCCM->CCMR);
   
   // Determine Reference Clock (CKIH or CKIL)
   switch (CSP_BITFEXT(ccmr, CCM_CCMR_PRCS)) {
      case CCM_CCMR_PRCS_CKIH_REF:
         ref = CKIH_INPUT_FREQ;
         break;
         
      case CCM_CCMR_PRCS_FPM_REF:
         ref = CKIL_INPUT_FREQ;
         ref *= CSP_BITFEXT(ccmr, CCM_CCMR_FPMF);
         break;
   }
   
   // MPCTL - calculate Main PLL
   ctl = INREG32(&g_pCCM->MPCTL);
   pllFreq = CalculatePLL(ref, ctl);
   g_clockFreq[DDK_CLOCK_SIGNAL_MCUPLL] = pllFreq;
   
   // UPCTL - calculate Usb PLL
   ctl = INREG32(&g_pCCM->UPCTL);
   pllFreq = CalculatePLL(ref, ctl);
   g_clockFreq[DDK_CLOCK_SIGNAL_USBPLL] = pllFreq;
   
   // SPCTL - calculate Serial PLL
   ctl = INREG32(&g_pCCM->SRPCTL);
   pllFreq = CalculatePLL(ref, ctl);
   g_clockFreq[DDK_CLOCK_SIGNAL_SERPLL] = pllFreq;
   
   return;
}

//-----------------------------------------------------------------------------
// End Of File
//-----------------------------------------------------------------------------
