/* --COPYRIGHT--,BSD
 * Copyright (c) 2019, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * --/COPYRIGHT--*/
//*****************************************************************************
//  HAL Timing functions for smoke detector AFE demo.
//      This file implements drivers to control:
//          ULP Timer: Periodic interrupt working at lowest power consumption.
//                      Uses RTC with VLO source.
//                      Can be calibrated for higher accuracy using GP Timer.
//                      Expected to be ON all the time.
//          GP Timer : General purpose timer used to create us and ms blocking
//                      delays, and calibrate ULP Timer.
//                      us delay and calibration use ACLK (REFO).
//                      ms delay uses SMCLK (DCO).
//                      Independent from ULP and LP since it needs to run
//                      concurrently. Not expected to be ON all the time.
//          LP Timer : Periodic timer independent from ULP Timer and GP Timer.
//                      Used to create multiple periodic events at a predefined
//                      time base (i.e. 100ms).
//                      Uses ACLK (REFO) which is more accurate but requires
//                      more power, so it shouldn't be ON all the time.
//
// Texas Instruments, Inc.
// Jan 2020
// Luis R.
// ******************************************************************************
#include <msp430.h>
#include <stdint.h>
#include <stdbool.h>
#include "DualRaySmokeAFE_HAL.h"
#include "DualRaySmokeAFE_HAL_Config_Private.h"

/**** Local Variables *********************************************************/
// State of ULP Timer Calibration
static enum tULPTimerCalibration {
    ULPTimer_Calibration_Disabled = 0,
    ULPTimer_Calibration_Start,
    ULPTimer_Calibration_Cap1,
    ULPTimer_Calibration_Done,
    ULPTimer_Calibration_Error,
}eULPTimerCalibration;

// State of General Purpose Timer
static enum tGPTimer
{
   GPTimer_Disabled = 0,
   GPTimer_CaptureMode,
   GPTimer_CompareOnce,
}eGPTimer;

// Timer capture values for ULP Timer calibration
static uint16_t ULPTimerCap1,ULPTimerCap2;
// Actual calculated frequency of VLO
static uint16_t ULPTimerActualSrcFreq;
// ULP Timer Interval in ms
static uint16_t ULPTimerIntervalms;
// ULP Timer ISR Callback
static tULPTimerCallback ULPTimer_ISRCallback;
// LP Periodic Timer Array of Callback functions
static tLPPerTimerCallback LPPerTimer_CallbackFunct[DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_CALLBACK_MAX] =
                                                                    {NULL};
// LP Periodic Timer Callback Enable flag
static bool LPPerTimer_CallbackEnable[DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_CALLBACK_MAX];
// LP Periodic Timer Enable flag
static bool LPPerTimer_Enable;


/**** Functions **************************************************************/
void DualRaySmokeAFE_HAL_Timing_Init(void)
{
    uint16_t i;

    // Default state of calibration state machine
    eULPTimerCalibration = ULPTimer_Calibration_Disabled;
    // Initialize expected RTC frequency
    ULPTimerActualSrcFreq = DUALRAYSMOKEAFE_HAL_TIMING_ULPTIMER_SOURCECLK_HZ;
    // Initialize RTC interval, disabled by default
    ULPTimerIntervalms = 0;
    // RTC ISR Callback is disabled by default
    ULPTimer_ISRCallback = NULL;

    // GP Timer disabled by default
    eGPTimer = GPTimer_Disabled;

    // Initialize Low-Power Periodic Timer (not running by default)
    // Clear all callback functions
    for (i=0; i < DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_CALLBACK_MAX; i++)
    {
        LPPerTimer_CallbackEnable[i] = false;
    }
    LPPerTimer_Enable = false;
}


void DualRaySmokeAFE_HAL_Timing_ULPTimer_setIntervalms(uint16_t interval_ms)
{
    uint32_t rtc_count;

    if (interval_ms == 0)
    {
        RTCCTL = RTCSS__DISABLED;
    }
    else
    {
        // Validate interval
        if (interval_ms < DUALRAYSMOKEAFE_HAL_TIMING_ULPTIMER_MIN_INTERVAL_MS)
        {
            interval_ms = DUALRAYSMOKEAFE_HAL_TIMING_ULPTIMER_MIN_INTERVAL_MS;
        }
        else if (interval_ms > DUALRAYSMOKEAFE_HAL_TIMING_ULPTIMER_MAX_INTERVAL_MS)
        {
            interval_ms = DUALRAYSMOKEAFE_HAL_TIMING_ULPTIMER_MIN_INTERVAL_MS;
        }

        ULPTimerIntervalms = interval_ms;

        // Calculate the number of RTC counts using the calibrated value of RTC
        rtc_count = ((uint32_t) ULPTimerIntervalms * (uint32_t) ULPTimerActualSrcFreq) /
                     (uint32_t) 1000;
        // setup RTC Modulo register
        RTCMOD = (uint16_t)(rtc_count) - 1;
#if (HAL_TIMING_RTC_FREQ_HZ == (HAL_TIMING_RTC_SOURCECLK_HZ/10))
        // source = VLO, divided by 10
        RTCCTL = RTCSS__VLOCLK | RTCSR | RTCPS__10 | RTCIE;
#else
#error "RTC setting not currently supported. Modify DualRaySmokeAFE_HAL_Timing.c"
#endif
    }
}

void DualRaySmokeAFE_HAL_Timing_ULPTimer_registerCallback(
                                       tULPTimerCallback Callback)
{
    ULPTimer_ISRCallback = Callback;
}

void DualRaySmokeAFE_HAL_Timing_ULPTimer_calibrate(void)
{
    uint16_t rtc_counter;

    RTCCTL = RTCSS__DISABLED;

    eGPTimer = GPTimer_CaptureMode;
    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_CAPTUREINIT();   // Initialize GP timer in capture mode
    eULPTimerCalibration = ULPTimer_Calibration_Start;    // Start calibration

    // Output RTC pulse every RTC_CAL VLO*Divider clock cycles
    RTCMOD = DUALRAYSMOKEAFE_HAL_TIMING_ULPTIMER_CAL_CYCLES -1;
#if (HAL_TIMING_RTC_FREQ_HZ == HAL_TIMING_RTC_SOURCECLK_HZ/10)
    // RTC clock divider=10. software reset => VLOCLK_freq/10 for RTC counter
    RTCCTL = RTCSS__VLOCLK | RTCSR | RTCPS__10;
#else
#error "RTC setting not currently supported. Modify DualRaySmokeAFE_HAL_Timing.c"
#endif

    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_CAPTURESTART(); // Start Timer to take captures

    __disable_interrupt();
    while((eULPTimerCalibration != ULPTimer_Calibration_Done) && (eULPTimerCalibration != ULPTimer_Calibration_Error))
    {
        __bis_SR_register(LPM3_bits + GIE);     // Enter LPM3 with general interrupt enabled, calibrate VLO with REFO in LPM3 mode
        __no_operation();                       // Wait for LPM3 wake-up by RTC overflow
        __disable_interrupt();
    }
    __enable_interrupt();

    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_STOP();
    eGPTimer = GPTimer_Disabled;
    RTCCTL = RTCSS__DISABLED;               // Stop RTC

    if (eULPTimerCalibration == ULPTimer_Calibration_Done)
    {
        // Get VLO calibration counter value,
        // rtc_counter value stands for REFO clock cycles for period of RTC_CAL VLO*Divider clock cycles
        // REFO clock is 32768Hz. Then VLO*Divider clock cycle = count / (RTC_CAL*32768) (s)
        rtc_counter = ULPTimerCap2 - ULPTimerCap1;
        // how many counts VLO/10 cycles for INTERVAL_TIME(s)
        ULPTimerActualSrcFreq = ((uint32_t) DUALRAYSMOKEAFE_HAL_TIMING_ULPTIMER_CAL_CYCLES *
                         (uint32_t) LSBUS_FREQ_HZ) /
                         (uint32_t) rtc_counter;
    }
    else
    {
        // Error during calibration, set default frequency
        ULPTimerActualSrcFreq = DUALRAYSMOKEAFE_HAL_TIMING_ULPTIMER_SOURCECLK_HZ;
    }

    // Update RTC configuration
    DualRaySmokeAFE_HAL_Timing_ULPTimer_setIntervalms(ULPTimerIntervalms);

}


void DualRaySmokeAFE_HAL_Timing_GPTimer_BlockingLPDelayms(uint16_t delay_ms)
{
    // Calculate delay in ACLK cycles
    uint16_t delay_cycles = ((uint32_t)delay_ms*(uint32_t)LSBUS_FREQ_HZ)/
                             (uint32_t)1000;

    eGPTimer = GPTimer_CompareOnce;

    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_COMPAREINIT(delay_cycles);  // Initialize timer in capture mode
    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_LPCOMPARESTART();             // Start timer
    __disable_interrupt();
    while (eGPTimer == GPTimer_CompareOnce)
    {
        // Enter LPM3 with general interrupt enabled
        __bis_SR_register(LPM3_bits + GIE);
        // Wait for LPM3 wake-up by Timer Interrupt
        __no_operation();
        __disable_interrupt();
    }
    __enable_interrupt();
    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_STOP();
    eGPTimer = GPTimer_Disabled;
}

void DualRaySmokeAFE_HAL_Timing_GPTimer_BlockingHPDelayus(uint16_t delay_us)
{
    // Calculate delay in SMCLK cycles
    uint16_t delay_cycles = (uint32_t)delay_us*(uint32_t)HSBUS_FREQ_MHZ;

    eGPTimer = GPTimer_CompareOnce;

    // Initialize timer in capture mode
    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_COMPAREINIT(delay_cycles);
    // Start timer
    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_HPCOMPARESTART();
    __disable_interrupt();
    while (eGPTimer == GPTimer_CompareOnce)
    {
        // Enter LPM0 with general interrupt enabled
        __bis_SR_register(LPM0_bits + GIE);
        // Wait for LPM wake-up by Timer Interrupt
        __no_operation();
        __disable_interrupt();
    }
    __enable_interrupt();
    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_STOP();
    eGPTimer = GPTimer_Disabled;
}


void DualRaySmokeAFE_HAL_Timing_LPPerTimer_setIntervalms(uint16_t interval_ms)
{
    uint32_t temp;

    // calculate period using ACLK
    temp = ((uint32_t) interval_ms) * ((uint32_t) LSBUS_FREQ_HZ);
    temp = temp / 1000;

    // Initialize Low Power Periodic Timer
    DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_INIT((uint16_t) temp);

}

bool DualRaySmokeAFE_HAL_Timing_LPPerTimer_getStatus(void)
{
    return LPPerTimer_Enable;
}

int16_t DualRaySmokeAFE_HAL_Timing_LPPerTimer_registerCallback(tLPPerTimerCallback Callback)
{
    uint16_t gie_state;
    int16_t i;
    int16_t id;

    gie_state = __get_interrupt_state(); // backup ISR state
    __disable_interrupt();      // disable interrupt to avoid conflict with ISR

    for (i=0; i < DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_CALLBACK_MAX; i++)
    {
        if (LPPerTimer_CallbackFunct[i] == NULL)
        {
            // Empty slot found, return OK
            id = i;
            LPPerTimer_CallbackFunct[i] = Callback;
            __set_interrupt_state(gie_state); // restore interrupts
            return id;
        }
    }

    // No empty slot found, return error
    __set_interrupt_state(gie_state); // restore interrupts
    return -1;
}

int16_t DualRaySmokeAFE_HAL_Timing_LPPerTimer_enableCallback(int16_t id)
{
    uint16_t gie_state;
    int16_t ret = -1;   // Return error by default

    gie_state = __get_interrupt_state(); // backup ISR state
    __disable_interrupt();      // disable interrupt to avoid conflict with ISR

    if ( (id < DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_CALLBACK_MAX) &&
         (LPPerTimer_CallbackFunct[id] != NULL) )
    {
        // Check if the timer should start for the first time
        if (LPPerTimer_Enable == false)
        {
            DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_RESTART();
            LPPerTimer_Enable = true;
        }
        // enable the callback function
        LPPerTimer_CallbackEnable[id] = true;
        ret = id;   // return OK
    }

    __set_interrupt_state(gie_state); // restore interrupts
    return ret;
}

int16_t DualRaySmokeAFE_HAL_Timing_LPPerTimer_disableCallback(int16_t id)
{
    uint16_t gie_state;
    int16_t ret = -1;   // Return error by default

    gie_state = __get_interrupt_state(); // backup ISR state
    __disable_interrupt();      // disable interrupt to avoid conflict with ISR

    if ( (id < DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_CALLBACK_MAX) &&
         (LPPerTimer_CallbackFunct[id] != NULL) )
    {
        LPPerTimer_CallbackEnable[id] = false;
        ret = id;  // return OK
    }
    __set_interrupt_state(gie_state); // restore interrupts
    return ret;
}


/**** Local Functions *********************************************************/

//! \brief Interrupt Service routine for General Purpose Timer.
//!         Handles the capture mode to calibrate RTC, and Compare mode
//!         to implement a low-power delay.
//!
//! \return none
#pragma vector = DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_VECTOR
__interrupt void GPTimer_ISR(void)
{
    if (eGPTimer == GPTimer_CaptureMode)
    {
        if (eULPTimerCalibration == ULPTimer_Calibration_Start)
        {
            ULPTimerCap1 = DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_CAPTUREGETCCR();    // First capture counter
            eULPTimerCalibration = ULPTimer_Calibration_Cap1;
            return;
        }
        else if (eULPTimerCalibration == ULPTimer_Calibration_Cap1)
        {
            ULPTimerCap2 = DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_CAPTUREGETCCR();    // Second capture counter
            eULPTimerCalibration = ULPTimer_Calibration_Done;
            DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_STOP();         // Stop timer
            __bic_SR_register_on_exit(LPM4_bits);   // Exit LPM
            return;
        }
    }
    else if (eGPTimer == GPTimer_CompareOnce)
    {
        eGPTimer = GPTimer_Disabled;
        DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_STOP();         // Stop timer
       __bic_SR_register_on_exit(LPM4_bits);   // Exit LPM
       return;
    }


    // Unexpected state
    eULPTimerCalibration = ULPTimer_Calibration_Error;
    DUALRAYSMOKEAFE_HAL_TIMING_GPTIMER_STOP();         // Stop timer
   __bic_SR_register_on_exit(LPM3_bits);   // Exit LPM3

}

//! \brief ISR for RTC used as Ultra Low Power Periodic Timer.
//!         This ISR calls a callback function which is defined in Application
//!         to perform periodic tasks.
//!
//! \return none
#pragma vector=DUALRAYSMOKEAFE_HAL_TIMING_ULPTIMER_VECTOR
__interrupt void ULPPerTimer_ISR(void)
{
    switch(__even_in_range(RTCIV,RTCIV_RTCIF))
    {
    case  RTCIV_NONE:   break;                  // No interrupt
    case  RTCIV_RTCIF:                          // RTC Overflow
        if (ULPTimer_ISRCallback != NULL)
        {
            if (ULPTimer_ISRCallback() == true)
            {
                __bic_SR_register_on_exit(LPM4_bits);   // Exit LPM
            }
        }
        break;
    default: break;
    }
}


//! \brief Interrupt Service routine for Low Power Periodic Timer.
//!     Calls the registered callback functions.
//!     If no function is enabled, the timer will be disabled.
//!
//! \return none
#pragma vector = DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_VECTOR
__interrupt void LPPeriodicTimer_ISR(void)
{
    bool enabled = false;
    bool wake_up = false;
    uint16_t i;

    if (LPPerTimer_Enable == true)
    {
        for (i=0; i < DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_CALLBACK_MAX; i++)
        {
            if ( (LPPerTimer_CallbackFunct[i] != NULL) &&
                 (LPPerTimer_CallbackEnable[i] == true) )
            {
                enabled |= true;   // set flag to indicate that a function is enabled
                // Call the callback function.
                // The function will return true if it should wake-up MCU.
                wake_up |= LPPerTimer_CallbackFunct[i]();
            }
        }
        // If no function is enabled, disable the timer
        if (enabled == 0)
        {
            LPPerTimer_Enable = false;
            DUALRAYSMOKEAFE_HAL_TIMING_LPPERTIMER_STOP();
        }
        // Wake-up MCU if any of the functions requested it
        if (wake_up == true)
        {
            __bic_SR_register_on_exit(LPM4_bits);   // Exit LPM
        }
    }
}
