/** 
 * @file  hal_board.c
 * 
 * Copyright 2008 Texas Instruments, Inc.
******************************************************************************/
#include "hal_board.h"

#include "msp430.h"

#include "hal_compat.h"
// #include "hal_adc.h"
// #include "hal_usb.h"

static void halBoardSetVCoreUp(unsigned char level);
static void halBoardSetVCoreDown(unsigned char level);
static void halBoardGetSystemClockSettings(unsigned char systemClockSpeed, 
                                           unsigned char *setDcoRange,
                                           unsigned char *setVCore,
                                           unsigned int  *setMultiplier);

/************************************************************************
 * @brief  Increments the VCore setting.
 * 
 * @param  level The target VCore setting
 * 
 * @return none
 *************************************************************************/
static void halBoardSetVCoreUp (unsigned char level)
{
  // Open PMM module registers for write access 
  PMMCTL0_H = 0xA5;                         
  
  // Set SVS/M high side to new level
  SVSMHCTL = (SVSMHCTL & ~(SVSHRVL0*3 + SVSMHRRL0)) | \
             (SVSHE + SVSHRVL0 * level + SVMHE + SVSMHRRL0 * level); 
  
  // Set SVM new Level    
  SVSMLCTL = SVSLE + SVMLE + SVSMLRRL0 * level;       
  // Set SVS/M low side to new level
  SVSMLCTL = (SVSMLCTL & ~(SVSMLRRL_3)) | (SVMLE + SVSMLRRL0 * level);
   
  while ((PMMIFG & SVSMLDLYIFG) == 0);      // Wait till SVM is settled (Delay)
  PMMCTL0_L = PMMCOREV0 * level;            // Set VCore to x
  PMMIFG &= ~(SVMLVLRIFG + SVMLIFG);        // Clear already set flags
  
  if ((PMMIFG & SVMLIFG))
    while ((PMMIFG & SVMLVLRIFG) == 0);     // Wait till level is reached
  
  // Set SVS/M Low side to new level
  SVSMLCTL = (SVSMLCTL & ~(SVSLRVL0*3 + SVSMLRRL_3)) | \
             (SVSLE + SVSLRVL0 * level + SVMLE + SVSMLRRL0 * level);
  
  // Lock PMM module registers from write access
  PMMCTL0_H = 0x00;                         
}

/************************************************************************
 * @brief  Decrements the VCore setting.
 * 
 * @param  level The target VCore.  
 * 
 * @return none
 *************************************************************************/
static void halBoardSetVCoreDown(unsigned char level)
{
  // Open PMM module registers for write access
  PMMCTL0_H = 0xA5;                         
  
  // Set SVS/M low side to new level
  SVSMLCTL = (SVSMLCTL & ~(SVSLRVL0*3 + SVSMLRRL_3)) | \
             (SVSLRVL0 * level + SVMLE + SVSMLRRL0 * level); 
  
  while ((PMMIFG & SVSMLDLYIFG) == 0);      // Wait till SVM is settled (Delay)
  PMMCTL0_L = (level * PMMCOREV0);          // Set VCore to new level 
  // Lock PMM module registers for write access
  
  PMMCTL0_H = 0x00;                         
}

/************************************************************************
 * @brief  Get function for the DCORSEL, VCORE, and DCO multiplier settings 
 *         that map to a given clock speed. 
 * 
 * @param  systemClockSpeed Target DCO frequency - SYSCLK_xxMHZ.
 * 
 * @param  setDcoRange      Pointer to the DCO range select bits.
 * 
 * @param  setVCore         Pointer to the VCore level bits. 
 * 
 * @param  setMultiplier    Pointer to the DCO multiplier bits. 
 *
 * @return none
 ************************************************************************/
static void halBoardGetSystemClockSettings(unsigned char systemClockSpeed, 
                                    unsigned char *setDcoRange,
                                    unsigned char *setVCore,
                                    unsigned int  *setMultiplier)
{
  switch (systemClockSpeed)
  {
  case SYSCLK_1MHZ: 
    *setDcoRange = DCORSEL_1MHZ;
    *setVCore = VCORE_1MHZ;
    *setMultiplier = DCO_MULT_1MHZ;
    break;
  case SYSCLK_4MHZ: 
    *setDcoRange = DCORSEL_4MHZ;
    *setVCore = VCORE_4MHZ;
    *setMultiplier = DCO_MULT_4MHZ;
    break;
  case SYSCLK_8MHZ: 
    *setDcoRange = DCORSEL_8MHZ;
    *setVCore = VCORE_8MHZ;
    *setMultiplier = DCO_MULT_8MHZ;
    break;
  case SYSCLK_12MHZ: 
    *setDcoRange = DCORSEL_12MHZ;
    *setVCore = VCORE_12MHZ;
    *setMultiplier = DCO_MULT_12MHZ;
    break;
  case SYSCLK_16MHZ: 
    *setDcoRange = DCORSEL_16MHZ;
    *setVCore = VCORE_16MHZ;
    *setMultiplier = DCO_MULT_16MHZ;
    break;     
  }	
}

/************************************************************************
 * @brief  Set function for the PMM core voltage (PMMCOREV) setting
 * 
 * @param  level Target VCore setting 
 * 
 * @return none
 *************************************************************************/
void halBoardSetVCore(unsigned char level)
{
  unsigned int currentVCore;

  currentVCore = PMMCTL0 & PMMCOREV_3;      // Get actual VCore  
  // Change VCore step by step 
  while (level != currentVCore) 
  {   
    if (level > currentVCore) 
      halBoardSetVCoreUp(++currentVCore);
    else
      halBoardSetVCoreDown(--currentVCore);
  }
}

/************************************************************************
 * @brief  Initialization routine for XT1. 
 * 
 * Sets the necessary internal capacitor values and loops until all 
 * ocillator fault flags remain cleared. 
 * 
 * @param  none
 * 
 * @return none
 *************************************************************************/
void halBoardStartXT1(void)
{
  /* LFXT can take up to 1000ms to start.
   * Go to the loop below 4 times for a total of 2 sec timout.
   * If a timeout happens due to no XTAL present or a faulty XTAL
   * the clock system will fall back to REFOCLK (~32kHz) */
  P5SEL |= BIT4 + BIT5;
  /* Set XTAL2 pins to output to reduce power consumption */
  P5DIR |= BIT2 + BIT3;
  /* Turn XT1 ON */
  UCSCTL6 &= ~(XT1OFF);
  /* Set XTAL CAPS to 12 pF */
  UCSCTL6 |= XCAP_3;

  do {
    /* Clear Oscillator fault flags */
    UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG);
    /* Clear the Oscillator fault interrupt flag */
    SFRIFG1 &= ~OFIFG;
    /* Test the fault flag */
    __delay_cycles(56250); 
  } while (SFRIFG1 & OFIFG);
  /* Reduse drive strength to reduce power consumption */
  UCSCTL6 &= ~(XT1DRIVE_3);
}

/************************************************************************
 * @brief  Set function for MCLK frequency.
 * 
 * @param  systemClockSpeed Intended frequency of operation - SYSCLK_xxMHZ.
 * 
 * @return none
 *************************************************************************/
void halBoardSetSystemClock(unsigned char systemClockSpeed)
{
  unsigned char setDcoRange = 0;
  unsigned char setVCore = 0;
  unsigned int  setMultiplier = 0;

  halBoardGetSystemClockSettings( systemClockSpeed, &setDcoRange,  \
                                  &setVCore, &setMultiplier);
  	
  if (setVCore > (PMMCTL0 & PMMCOREV_3))	// Only change VCore if necessary
    halBoardSetVCore( setVCore );   
    
  UCSCTL0 = 0x00;                           // Set lowest possible DCOx, MODx
  UCSCTL1 = setDcoRange;                    // Select suitable range
  
  UCSCTL2 = setMultiplier + FLLD_1;         // Set DCO Multiplier
  UCSCTL4 = SELA__XT1CLK | SELS__DCOCLKDIV  |  SELM__DCOCLKDIV ;
  
  // Worst-case settling time for the DCO when the DCO range bits have been 
  // changed is n x 32 x 32 x f_FLL_reference. See UCS chapter in 5xx UG 
  // for optimization.
  // 32 x 32 x / f_FLL_reference (32,768 Hz) = .03125 = t_DCO_settle
  // t_DCO_settle / (1 / 18 MHz) = 562500 = counts_DCO_settle
  
  // __delay_cycles(562500);  
  int i;
  for (i=0;i<10;i++){
    __delay_cycles(56250);  
  }
}

/************************************************************************
 * @brief  Initializes all GPIO configurations. 
 *         TI example did set all ports to OUTPUT, we don't.
 * @param  none
 * 
 * @return none
 *************************************************************************/
void halBoardInit(void)
{  
  //Tie unused ports
  PAOUT  = 0;
  PADIR  = 0;  
  PASEL  = 0;
  PBOUT  = 0;  
  PBDIR  = 0;
  PBSEL  = 0;
  PCOUT  = 0;    
  PCDIR  = 0;
  PCSEL  = 0;  
  PDOUT  = 0;  
  PDDIR  = 0;
  PDSEL  = 0;  
  PJOUT  = 0;    
  PJDIR  = 0;
     
  // AUDIO_PORT_OUT = AUDIO_OUT_PWR_PIN ;
  // USB_PORT_DIR &= ~USB_PIN_RXD;             // USB RX Pin, Input with 
  //                                           // ...pulled down Resistor
  // USB_PORT_OUT &= ~USB_PIN_RXD;
  // USB_PORT_REN |= USB_PIN_RXD;
}
