// ******************************************************************
// File: ClockDriver.h
// Description: Library to manage the clocks.
//              functions: Init_Ocs, Test_Ports, Set_DCO_4096_(delta)
//  Carlos Silva Lütty
//  U.T.F.S.M.
//  March 2007
//
// Version 1.0
//
// ******************************************************************
// CLOCKDRIVER Lib
// ******************************************************************

#ifndef CLOCKDRIVER__H
#define CLOCKDRIVER__H

#include  <msp430x14x.h>

// macro interrupt defines
#define   CLK_IE          {IE1 |= OFIE;}
#define   CLK_ID          {IE1 &= ~OFIE;}
#define   CLK_CF          {IFG1 &= ~OFIFG;}

// macro XT2 on-off defines
#define   XT2_ON          {BCSCTL1 &= ~XT2OFF;}
#define   XT2_OFF         {BCSCTL1 |= XT2OFF;}

// macro SMCLK source defines
#define   SMCLK_DCO       {BCSCTL2 &= ~SELS;}
#define   SMCLK_XT1       {XT2_OFF; BCSCTL2 |= SELS;}
#define   SMCLK_XT2       {XT2_ON; BCSCTL2 |= SELS;}

// macro MCLK source defines
#define   MCLK_DCO        {BCSCTL2 |= SELM_1;}
#define   MCLK_XT1        {BCSCTL2 |= SELM_3;}
#define   MCLK_XT2        {Init_Ocs();}

// macro ACLK frecuency defines
#define   ACLK_32768      {BCSCTL1 &= ~(DIVA0 + DIVA1);}
#define   ACLK_16384      {BCSCTL1 |= DIVA_1;}
#define   ACLK_8192       {BCSCTL1 |= DIVA_2;}
#define   ACLK_4096       {BCSCTL1 |= DIVA_3;}

// macro SMCLK frecuency defines
#define   SMCLK_8M        {SMCLK_XT2; BCSCTL2 |= DIVS_0;}
#define   SMCLK_4M        {SMCLK_XT2; BCSCTL2 |= DIVS_1;}
#define   SMCLK_2M        {SMCLK_XT2; BCSCTL2 |= DIVS_2;}
#define   SMCLK_1M        {SMCLK_XT2; BCSCTL2 |= DIVS_3;}
#define   SMCLK_32768     {SMCLK_XT1; BCSCTL2 |= DIVS_0;}
#define   SMCLK_16384     {SMCLK_XT1; BCSCTL2 |= DIVS_1;}
#define   SMCLK_8192      {SMCLK_XT1; BCSCTL2 |= DIVS_2;}
#define   SMCLK_4096      {SMCLK_XT1; BCSCTL2 |= DIVS_3;}

// macro MCLK frecuency defines
#define   MCLK_8M         {MCLK_XT2; BCSCTL2 |= DIVM_0;}
#define   MCLK_4M         {MCLK_XT2; BCSCTL2 |= DIVM_1;}
#define   MCLK_2M         {MCLK_XT2; BCSCTL2 |= DIVM_2;}
#define   MCLK_1M         {MCLK_XT2; BCSCTL2 |= DIVM_3;}
#define   MCLK_32768      {MCLK_XT1; BCSCTL2 |= DIVM_0;}
#define   MCLK_16384      {MCLK_XT1; BCSCTL2 |= DIVM_1;}
#define   MCLK_8192       {MCLK_XT1; BCSCTL2 |= DIVM_2;}
#define   MCLK_4096       {MCLK_XT1; BCSCTL2 |= DIVM_3;}

// macro DCO max-min frecuency defines
#define   MAX_DCO         {DCOCTL |= DCO2 + DCO1 + DCO0; BCSCTL1 |= RSEL2 + RSEL1 + RSEL0;}
#define   MIN_DCO         {DCOCTL &= ~(DCO2 + DCO1 + DCO0); BCSCTL1 &= ~(RSEL2 + RSEL1 + RSEL0);}

/*************************************************************************
 * Test_Ports              make the 3 clock signals visible in the P5.
 *                         Use this if you want to use an osciloscope to
 *                         ensure you have the desire frecuency.
 *
 * MSP430 resource         P5.4-6 as clock signals
 *
 * arguments               none
 * return                  none
 *************************************************************************/
void Test_Ports(void) {
  P5DIR |= BIT4 + BIT5 + BIT6;                    // P5.6,5,4 as output
  P5SEL |= BIT4 + BIT5 + BIT6;                    // P5.6,5,5 as clock signal
}

/*************************************************************************
 * Init_Ocs                Turn on the XT2 and wait until the amplitude
 *                         and the frecuency is stable. Use the XT2 as
 *                         source of MCLK and SMCLK closk signals.
 *
 * MSP430 resource         XT2, IFG1, IE1, BCSCTL2, WDTCTL.
 *
 * arguments               none
 * return                  none
 *************************************************************************/
void Init_Ocs(void) {
  XT2_ON;                                         // XT2 on (8MHz)
  do                                              // Wait until XT2 is stable in frecuency
    IFG1 &= ~OFIFG;
  while (IFG1 & OFIFG);
  IE1 &= ~WDTIE;                                  // Disable WDT interrupts
  IFG1 &= ~WDTIFG;                                // Clear the flag of WDT interrupts
  BCSCTL2 |= SELM_2 + SELS;                       // MCLK <- XT2 | SMCLK <- XT2
  WDTCTL = WDTPW | WDTTMSEL | WDTCNTCL | WDTIS1;  // use WDT as Timer, generate an interrupt every 512 cycles of SMCLK
  while (!(IFG1 & WDTIFG));                       // Count 1024 cycles of XT2 until amplitude is stable
  CLK_CF;                                         // Clear the flag of the Oscilator Fault
}

/*************************************************************************
 * Set_DCO_4096_(delta)    set DCO frecuency as a multiple of 4096.
 *
 * MSP430 resource         DCO, ACLK, TA.
 *
 * arguments               int DELTA          DELTA * 4096 = DCO frecuency
 * return                  none
 *************************************************************************/
void Set_DCO_4096_(int DELTA) {                   // valid DELTA [20,1170]
  unsigned int Compare, Oldcapture = 0;
  
  if (DELTA < 20) {                               // if DELTA is too small
    MIN_DCO;                                      // set min DCO frecuency
    return;
  }
  else if (DELTA > 1170) {                        // if DELTA is too big
    MAX_DCO;                                      // set max DCO frecuency
    return;
  }
  
  ACLK_4096;                                      // ACLK = LFXT1CLK/8
  CCTL2 = CM_1 + CCIS_1 + CAP;                    // CAP, ACLK
  TACTL = TASSEL_2 + MC_2 + TACLR;                // SMCLK, cont-mode, clear
  
  while (1) {
    while (!(CCIFG & CCTL2));                     // wait for capture
    CCTL2 &= ~CCIFG;                              // clear flag when capture take place
    Compare = CCR2;                               // get the captured SMCLK
    Compare = Compare - Oldcapture;               // SMCLK diference
    Oldcapture = CCR2;                            // save the last capture SMCLK
    
    if (DELTA == Compare) break;                  // success condition
    else if (DELTA < Compare) {                   // DCO is too fast
      DCOCTL--;                                   // go slower
      if (DCOCTL == 0xFF) {
        if (!(BCSCTL1 == (XT2OFF + DIVA_3)))
          BCSCTL1--;                              // DCO under the limit, set a lower RSEL
      }
    }
    else {
      DCOCTL++;
      if (DCOCTL == 0x00) {
        if (!(BCSCTL1 == (XT2OFF + DIVA_3 + 0x07)))
          BCSCTL1++;                              // DCO upper the limit, set a higher RSEL
      }
    }
  }
  CCTL2 = 0;                                      // stop CCR2
  TACTL = 0;                                      // stop Timer_A
  ACLK_32768;                                     // ACLK = LFXT1CLK/1
}

#endif /* _CLOCKDRIVER__H */
