/***********************************************************************
 * $Id: system_LPC43xx.c 8389 2011-10-19 13:53:14Z nxp28536 $
 *
 * Project: LPC43xx Common
 *
 * Description:
 *     CMSIS Cortex-M4 Device Peripheral Access Layer Source File
 *     for the NXP LPC43xx Device Series 
 *
 ***********************************************************************
 * Software that is described herein is for illustrative purposes only
 * which provides customers with programming information regarding the
 * products. This software is supplied "AS IS" without any warranties.
 * NXP Semiconductors assumes no responsibility or liability for the
 * use of the software, conveys no license or title under any patent,
 * copyright, or mask work right to the product. NXP Semiconductors
 * reserves the right to make changes in the software without
 * notification. NXP Semiconductors also make no representation or
 * warranty that such application will be suitable for the specified
 * use without further testing or modification.
 **********************************************************************/

#include <stdint.h>
#if defined CORE_M4
#include "LPC43xx.h"   
#endif

#ifdef CORE_M0
#include "LPC43xx_M0.h"                
#endif

#include "scu.h" 
#include "type.h"
#include "config.h"


/*--------------------- Clock Configuration ----------------------------------*/
//#define OTP
#define FLASH_SETUP         0
#define FLASHCFG_Val        0x0000303A

/*----------------------------------------------------------------------------
  Check the register settings
 *----------------------------------------------------------------------------*/
#define CHECK_RANGE(val, min, max)                ((val < min) || (val > max))
#define CHECK_RSVD(val, mask)                     (val & mask)

/* Clock Configuration -------------------------------------------------------*/
#if (CHECK_RSVD((SCS_Val),       ~0x00000030))
   #error "SCS: Invalid values of reserved bits!"
#endif

#if (CHECK_RANGE((CLKSRCSEL_Val), 0, 2))
   #error "CLKSRCSEL: Value out of range!"
#endif

#if (CHECK_RSVD((PLL0CFG_Val),   ~0x00FF7FFF))
   #error "PLL0CFG: Invalid values of reserved bits!"
#endif

#if (CHECK_RSVD((PLL1CFG_Val),   ~0x0000007F))
   #error "PLL1CFG: Invalid values of reserved bits!"
#endif

#if ((CCLKCFG_Val != 0) && (((CCLKCFG_Val - 1) % 2)))
   #error "CCLKCFG: CCLKSEL field does not contain only odd values or 0!"
#endif

#if (CHECK_RSVD((USBCLKCFG_Val), ~0x0000000F))
   #error "USBCLKCFG: Invalid values of reserved bits!"
#endif

#if (CHECK_RSVD((PCLKSEL0_Val),   0x000C0C00))
   #error "PCLKSEL0: Invalid values of reserved bits!"
#endif

#if (CHECK_RSVD((PCLKSEL1_Val),   0x03000300))
   #error "PCLKSEL1: Invalid values of reserved bits!"
#endif

#if (CHECK_RSVD((PCONP_Val),      0x10100821))
   #error "PCONP: Invalid values of reserved bits!"
#endif

#if (CHECK_RSVD((CLKOUTCFG_Val), ~0x000001FF))
   #error "CLKOUTCFG: Invalid values of reserved bits!"
#endif

/* Flash Accelerator Configuration -------------------------------------------*/
#if (CHECK_RSVD((FLASHCFG_Val), ~0x0000F07F))
   #error "FLASHCFG: Invalid values of reserved bits!"
#endif


/*----------------------------------------------------------------------------
  DEFINES
 *----------------------------------------------------------------------------*/
uint32_t XtalFrequency = 0; 				
uint32_t PL160M_0Frequency = 0; 
uint32_t PL160M_1Frequency = 0; 
uint32_t PL160M_2Frequency = 0; 
uint32_t PL550Frequency = 0; 
uint32_t PL550FracFrequency = 0; //New in Falcon
uint32_t IDIVAFrequency = 0;
uint32_t IDIVBFrequency = 0;
uint32_t IDIVCFrequency = 0;
uint32_t IDIVDFrequency = 0;
uint32_t IDIVEFrequency = 0;
uint32_t USB1Frequency = 0;
uint32_t M4Frequency = 0;
uint32_t SPIFIFrequency = 0;
uint32_t SPIFrequency = 0;
uint32_t EnetRxFrequency = 0;
uint32_t EnetTxFrequency = 0;
uint32_t EXTFrequency = 0;
uint32_t VPB1Frequency = 0;
uint32_t VPB3Frequency = 0;
uint32_t LCDFrequency = 0;
uint32_t SCIFrequency = 0;
uint32_t VADCFrequency = 0;	
uint32_t SDIOFrequency = 0;
uint32_t SSP0Frequency = 0;
uint32_t SSP1Frequency = 0;
uint32_t UART0Frequency = 0;
uint32_t UART1Frequency = 0;
uint32_t UART2Frequency = 0;
uint32_t UART3Frequency = 0;
uint32_t OUTFrequency = 0;
uint32_t AOTESTFrequency = 0;
uint32_t ISOFrequency = 0;
uint32_t BSRFrequency = 0;
uint32_t CLK_TESTFrequency = 0;
uint32_t APLLFrequency = 0;   
uint32_t SPARE0Frequency = 0; 
uint32_t SPARE1Frequency = 0; 

/**
 * Initialize the system
 *
 * @param  none
 * @return none
 *
 * @brief  Setup the microcontroller system.
 *         
 */
void SystemInit(void)
{
	// M4 runs on IRC by default
	M4Frequency = IRC_OSC; 
	XtalFrequency = XTAL_FREQ;
	EXTFrequency = EXT_FREQ;
}

/**
 * Set Clock
 *
 * @param  target PLL, source clock, division
 * @return none
 *
 * @brief  Setup a clock 
 */
void SetClock(CLKBASE_Type target_clk, CLKSRC_Type src_clk, CLKDIV_Type div)
{
	volatile uint32_t target_clk_adr;
	volatile uint8_t auto_block=TRUE;
	uint32_t src_freq;

	EnableSourceClk(src_clk);

	switch(div)
	{
		case(DIV1):						// Divide by 1 == no division
			break;
		case(DIV2):	
			LPC_CGU->IDIVA_CTRL = (src_clk<<24) | (1<<2) | AUTO_BLOCK;	
			IDIVAFrequency = GetClockFrequency(src_clk)/2;
			src_clk = SRC_IDIV_0; 		// Set new src_clk for target_clk
			break;
		case(DIV4):	
			LPC_CGU->IDIVB_CTRL = (src_clk<<24) | (3<<2) |AUTO_BLOCK;		
			IDIVBFrequency = GetClockFrequency(src_clk)/4;
			src_clk = SRC_IDIV_1; 		// Set new src_clk for target_clk
			break;
		case(DIV8):	
			LPC_CGU->IDIVC_CTRL = (src_clk<<24) | (7<<2) |AUTO_BLOCK;		
			IDIVCFrequency = GetClockFrequency(src_clk)/8;
			src_clk = SRC_IDIV_2; 		// Set new src_clk for target_clk
			break;
		case(DIV16):	
			LPC_CGU->IDIVD_CTRL = (src_clk<<24) | (15<<2) |AUTO_BLOCK;		
			IDIVDFrequency = GetClockFrequency(src_clk)/16;
			src_clk = SRC_IDIV_3; 		// Set new src_clk for target_clk
			break;
		case(DIV256):
			LPC_CGU->IDIVE_CTRL = (src_clk<<24) | (255<<2) |AUTO_BLOCK;	// MAX 128? IDIV bit 2:9 = 7 bits = 127 max
			IDIVEFrequency = GetClockFrequency(src_clk)/256;
			src_clk = SRC_IDIV_4; 		// Set new src_clk for target_clk
			break;
		default:
			break;
	}

	src_freq = GetClockFrequency(src_clk);

	switch(target_clk)
	{
		case(BASE_OUT_CLK):
		{
			LPC_SCU->SFSCLK_0 = 1; 					// function 1; CGU clk out, diable pull down, disable pull-up
			auto_block = FALSE;
			break;
		}
		case(XTAL):
		{
			XtalFrequency = (uint32_t) src_clk;	  	// convert target clock directly to frequency
			break;
		}
		case(ENET_RX):
		{
			EnetRxFrequency = (uint32_t) src_clk;	// convert target clock directly to frequency
			break;
		}
		case(ENET_TX):
		{
			EnetTxFrequency = (uint32_t) src_clk;	// convert target clock directly to frequency
			break;
		}
		case(BASE_USB1_CLK):
		{
			USB1Frequency = src_freq;
			break;
		}
		case(BASE_M4_CLK):
		{
			M4Frequency = src_freq;
			break;
		}
		case(BASE_SPIFI_CLK):
		{
			SPIFIFrequency = src_freq;
			break;
		}
		case(BASE_SPI_CLK):
		{
			SPIFrequency = src_freq;
			break;
		}
		case(BASE_PHY_RX_CLK):
		{
			EnetRxFrequency = src_freq;
			break;
		}
		case(BASE_PHY_TX_CLK):
		{
			EnetTxFrequency = src_freq;
			break;
		}
		case(BASE_VPB1_CLK):
		{
			VPB1Frequency = src_freq;
			break;
		}
		case(BASE_VPB3_CLK):
		{
			VPB3Frequency = src_freq;
			break;
		}
		case(BASE_LCD_CLK):
		{
			LCDFrequency = src_freq;
			break;
		}
		case (BASE_VADC_CLK) :
		{
			VADCFrequency = src_freq;
			break;
		}
		case(BASE_SDIO_CLK):
		{
			SDIOFrequency = src_freq;
			break;
		}
		case(BASE_SSP0_CLK):
		{
			SSP0Frequency = src_freq;
			break;
		}
		case(BASE_SSP1_CLK):
		{
			SSP1Frequency = src_freq;
			break;
		}
		case(BASE_UART0_CLK):
		{
			UART0Frequency = src_freq;
			break;
		}
		case(BASE_UART1_CLK):
		{
			UART1Frequency = src_freq;
			break;
		}
		case(BASE_UART2_CLK):
		{
			UART2Frequency = src_freq;
			break;
		}
		case(BASE_UART3_CLK):
		{
			UART3Frequency = src_freq;
			break;
		}
		case(BASE_AOTEST_CLK):
		{
			AOTESTFrequency = src_freq;
			break;
		}
		case(BASE_ISO_TCK):
		{
			ISOFrequency = src_freq;
			break;
		}
		case(BASE_BSR_TCK):
		{
			BSRFrequency = src_freq;
			break;
		}
		case(BASE_CLK_TEST):
		{
			CLK_TESTFrequency = src_freq;
			break;
		}
		case(BASE_APLL_CLK): //New in Falcon
		{
			APLLFrequency = src_freq;
			break;
		}
		case(BASE_SPARE0_CLK): //New in Falcon
		{
			SPARE0Frequency = src_freq;
			break;
		}
		case(BASE_SPARE1_CLK): //New in Falcon
		{
			SPARE1Frequency = src_freq;
			break;
		}
		default:
			break;
	}

	if(target_clk<200)
	{
		target_clk_adr = (uint32_t) &LPC_CGU->IDIVA_CTRL + (target_clk-2)*4;	
		*(uint32_t *)target_clk_adr = (src_clk<<24) | (auto_block<<11);	
	}
}

/**
 * Get Clock Frequency
 *
 * @param  source clock
 * @return frequency
 *
 * @brief  returns the current frequency of a base clock
 */
uint32_t GetClockFrequency(CLKSRC_Type src_clk)
{
   	switch(src_clk)
	{
		case(SRC_OSC32K):
			return RTC_CLK;
		case(SRC_IRC):
			return IRC_OSC;
		case(SRC_ENET_RX_CLK):
			return EnetRxFrequency;
		case(SRC_ENET_TX_CLK):
			return EnetTxFrequency;
		case(SRC_EXT_TCK):
			return EXTFrequency;
		case(SRC_XTAL):
			return XtalFrequency;
		case(SRC_PL550M_0):
			return PL550Frequency;
		case(SRC_PL550M_FRAC): //New in Falcon
			return PL550FracFrequency;
		case(SRC_PL160M_0):
			return PL160M_0Frequency;
		case(SRC_PL160M_1):
			return PL160M_1Frequency;
		case(SRC_PL160M_2):
			return PL160M_2Frequency;
		case(SRC_IDIV_0):
			return IDIVAFrequency;
		case(SRC_IDIV_1):
			return IDIVBFrequency;
		case(SRC_IDIV_2):
			return IDIVCFrequency;
		case(SRC_IDIV_3):
			return IDIVDFrequency;
		case(SRC_IDIV_4):
			return IDIVEFrequency;
		default:
		return 0;
	}
}

/**
 * Set PL160M
 *
 * @param  source clock, desired frequency 
 * @return none
 *
 * @brief  	Setup the PL160M PLL 
 *		   	If frequency equals 0 then disable PLL
 *			Integer mode only (fbsel=1, direct=0)				
 *			Fclkout = M * Fclkin/N 
 *			Fcc0 = 2 * P * Fclkout = 2 * P * M * Fclkin/N 
 *			msel+1 = feedback-divider value M 	(1 to 2^15)	
 *			nsel+1 = pre-divider value N		(1 to 2^8)	
 *			psel+1 = post-divider value P(x2)	(1 to 2^5)	
 */
void SetPL160M(CLKSRC_Type src_clk, uint32_t mult) 
{
	uint32_t msel=0, nsel=0, psel=0, pval=1;	

//	EnableSourceClk(src_clk);

	if(mult==0)
	{
		LPC_CGU->PLL1_CTRL |= PD_ENABLE; 	// Power down PLL
		DisableSourceClk(src_clk);
	}
	else
	{
		EnableSourceClk(src_clk);
			 
		switch(src_clk)
		{
			case(SRC_OSC32K):
				PL160M_0Frequency = mult * RTC_CLK;
				break;
			case(SRC_IRC):
				PL160M_0Frequency = mult * IRC_OSC;
				break;
			case(SRC_ENET_RX_CLK):
				PL160M_0Frequency = mult * EnetRxFrequency;
				break;
			case(SRC_ENET_TX_CLK):
				PL160M_0Frequency = mult * EnetTxFrequency;
				break;
			case(SRC_EXT_TCK):
				PL160M_0Frequency = mult * EXTFrequency;
				break;
			case(SRC_XTAL):
				PL160M_0Frequency = mult * XtalFrequency;
				break;
			default:
				PL160M_0Frequency = mult * IRC_OSC;
				break;
		}
	
		// CCO must be in range of 156 - 320 MHz
		// Increase P if FCCO is too low. 
		msel = mult-1;
		//psel is encoded such that 0=1, 1=2, 2=4, 3=8
		while(2*(pval)*PL160M_0Frequency < 156000000) {
			psel++;	
			pval*=2;
		}
//		if(2*(pval)*PL160M_0Frequency > 320000000) {
//			THIS IS OUT OF RANGE!!!
//			HOW DO WE ASSERT IN SAMPLE CODE?
//			__breakpoint(0);
//		}
		LPC_CGU->PLL1_CTRL = (src_clk<<24) | (msel<<16) | (nsel<<12) | (psel<<8) | FBSEL;
		while((LPC_CGU->PLL1_STAT&1) == 0x0); 		// Wait for PLL lock
	}
}

/**
 * Set PLL USB (PL550M)
 *
 * @param  enable
 * @return none
 *
 * @brief  	Setup the USB PLL to 480 MHz 
 *		   	If enable equals 0 then disable PLL
 *		   	Only clock sources IRC and XTAL are valid
 *			Mode1a only: Normal operating mode without post- and pre-divider				
 *			Fclkout = 2 * M * Fclkin
 *			msel+1 = feedback-divider value M 	(1 to 2^15)	
 */
void SetPLLUSB(CLKSRC_Type src_clk, uint8_t enable)
{
	if(!enable)
	{
		LPC_CGU->PLL0USB_CTRL |= PD_ENABLE; 	// Power down PLL
	}
	else
	{
		// Setup PLL550 to generate 480MHz from 12 MHz crystal
		LPC_CGU->PLL0USB_CTRL |= PD_ENABLE; 	// Power down PLL
							//	P			N
		LPC_CGU->PLL0USB_NP_DIV = (98<<0) | (514<<12);
							//	SELP	SELI	SELR	MDEC	 
		LPC_CGU->PLL0USB_MDIV = (0xB<<17)|(0x10<<22)|(0<<28)|(0x7FFA<<0); 					
		LPC_CGU->PLL0USB_CTRL =(SRC_XTAL<<24) | (0x3<<2) | CLKEN;  
		
		// Set the USB0 clock source to PLL550 (480MHz)
		LPC_CGU->BASE_USB0_CLK = (0<<0) | (1<<11) | (SRC_PL550M_0<<24);	
			
		while((LPC_CGU->PLL0USB_STAT&1) == 0x0);	// Wait for PLL lock 
	}

	PL550Frequency = 480000000UL;
}

/**
 * Enable source clock pheripheral
 *
 * @param  clock source
 * @return none
 *
 * @brief  	Enable clock specific peripherals
 */
void EnableSourceClk(CLKSRC_Type src_clk)
{
	uint32_t i=0;
	const uint32_t PlainEnable = (0x2 << 3);	/* no pull up, no pull down (plain) */


	if(src_clk == SRC_OSC32K)
	{
		LPC_CREG->CREG0 &= ~((1<<3)|(1<<2));		// Active mode of 32 KHz osc and release reset
		LPC_CREG->CREG0 |= (1<<1)|(1<<0);			// Enable 32 kHz & 1 kHz on osc32k
	}
	if(src_clk == SRC_ENET_RX_CLK)scu_pinmux(0xC ,0 , PlainEnable, FUNC3); 	// enet_rx_clk on PC_0 func 3
	if(src_clk == SRC_ENET_TX_CLK)scu_pinmux(0x1 ,19, PlainEnable, FUNC0); 	// enet_tx_clk on P1_19 func 0
	if(src_clk == SRC_XTAL && (LPC_CGU->XTAL_OSC_CTRL&0x1))
	{
		LPC_CGU->XTAL_OSC_CTRL &= ~(1<<0);								// Enable Xo50M
		for(i=0;i<0xFFFF;i++);
	}
}

/**
 * Disable source clock pheripheral
 *
 * @param  clock source
 * @return none
 *
 * @brief  	Disable clock specific peripherals
 */
void DisableSourceClk(CLKSRC_Type src_clk)
{
	uint32_t i=0;
	const uint32_t PlainEnable = (0x2 << 3);	/* no pull up, no pull down (plain) */

	if(src_clk == SRC_OSC32K)
	{
		LPC_CREG->CREG0 &= ~((1<<1)|(1<<0));	// Disable 32 kHz & 1 kHz on osc32k
		LPC_CREG->CREG0 |= ((1<<3)|(1<<2));		// osc32k in power down and in reset mode
	}
	if(src_clk == SRC_ENET_RX_CLK)scu_pinmux(0xC ,0 , PlainEnable, FUNC0); 	// nc on PC_0 func 0
	if(src_clk == SRC_ENET_TX_CLK)scu_pinmux(0x1 ,19, PlainEnable, FUNC2); 	// nc on P1_19 func 2
	if(src_clk == SRC_XTAL)
	{
		LPC_CGU->XTAL_OSC_CTRL = (1<<0);		// Disable Xo50M
		for(i=0;i<0xFFFF;i++);
	}
}
