///
/// @file timers.c
/// @authors k. headley
/// @date DD MMM 2009

/// General description of module here

/// @sa doxygen-examples.c for more examples of Doxygen markup


/////////////////////////
// Terms of use
/////////////////////////
/*
 Copyright Information

 Copyright 2002-2019 MBARI
 Monterey Bay Aquarium Research Institute, all rights reserved.

 Terms of Use

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version. You can access the GPLv3 license at
 http://www.gnu.org/licenses/gpl-3.0.html

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details
 (http://www.gnu.org/licenses/gpl-3.0.html)

 MBARI provides the documentation and software code "as is", with no warranty,
 express or implied, as to the software, title, non-infringement of third party
 rights, merchantability, or fitness for any particular purpose, the accuracy of
 the code, or the performance or results which you may obtain from its use. You
 assume the entire risk associated with use of the code, and you agree to be
 responsible for the entire cost of repair or servicing of the program with
 which you are using the code.

 In no event shall MBARI be liable for any damages, whether general, special,
 incidental or consequential damages, arising out of your use of the software,
 including, but not limited to, the loss or corruption of your data or damages
 of any kind resulting from use of the software, any prohibited use, or your
 inability to use the software. You agree to defend, indemnify and hold harmless
 MBARI and its officers, directors, and employees against any claim, loss,
 liability or expense, including attorneys' fees, resulting from loss of or
 damage to property or the injury to or death of any person arising out of the
 use of the software.

 The MBARI software is provided without obligation on the part of the
 Monterey Bay Aquarium Research Institute to assist in its use, correction,
 modification, or enhancement.

 MBARI assumes no responsibility or liability for any third party and/or
 commercial software required for the database or applications. Licensee agrees
 to obtain and maintain valid licenses for any additional third party software
 required.
 */

/////////////////////////
// Headers
/////////////////////////
#include <stdio.h>
#include <math.h>
#include "timers.h"
#include "ioserial.h"
#include "iodigital.h"
#include "pic24.h"

/////////////////////////
// Macros
/////////////////////////


/////////////////////////
// Declarations
/////////////////////////
TickCount_t tkGetTicks();

/////////////////////////
// Imports
/////////////////////////

/////////////////////////
// Module Global Variables
/////////////////////////

/// @var volatile uint16_t g_tkpps_flags
/// @brief timer flags indicate 1 PPS and 2 PPS
volatile uint16_t g_tkpps_flags=0x0000;
/// @var volatile TickCount_t SysTickCount
/// @brief system tick counter
volatile TickCount_t SysTickCount;

uint16_t g_tkidle_flags=0x0000;

/////////////////////////
// Function Definitions
/////////////////////////

// read and clear specified tflag bits
uint16_t tkReadClr(uint16_t mask)
{
    uint16_t retval = TFLAG_BITS(mask);
    TFLAG_CLR_BITS(mask);
    return retval;
}

/// timer2 settings: used for delays and timers
/// interrupt enabled, priority 6, stop while idle,
/// use sys-clock as source, 10ms period(1Tick)
/// timer1 settings: used for idle/sleep mode wakeup
/// interrupt off(turned on before sleep), priority 4
/// continue while idle, external source(RTC), 8sec period.
void tkInit() {
    SysTickCount = 0; //clear sys tick count
    return;
}

/// Set the amount of time(ms) left (must be > 10ms).
/// Use tkTmrStart() after this to make sure it's running
void tkTmrSet(Timer* tmr, UINT32 period_msec) {
    tmr->ticksLeft = period_msec / MS_PER_TICK;
    tmr->lastRead = tkGetTicks();
}

/// return ms left on timer, 0 if expired
UINT32 tkTmrUpdate(Timer* tmr) {

    if(NULL == tmr)return(0);//return zero for bad pointer

    if (tmr->active) {
        UINT32 curTicks = tkGetTicks();
        if (tmr->ticksLeft==0 || (tmr->ticksLeft <= (curTicks - tmr->lastRead))) {
            tmr->ticksLeft = 0;
        } else {
            tmr->ticksLeft -= (curTicks - tmr->lastRead);
        }
        tmr->lastRead = curTicks;
    }

    return (tmr->ticksLeft * MS_PER_TICK);
}

/// reset a timer
void tkTmrReset(Timer* tmr)
{
    tmr->active = FALSE;
    tmr->ticksLeft=0;
    tmr->lastRead=0;
    return;
}

/// stopped timer can be resumed later with tkTmrStart().
UINT32 tkTmrStop(Timer* tmr) {
    tkTmrUpdate(tmr);
    tmr->active = FALSE;
    return (tmr->ticksLeft * MS_PER_TICK);
}

/// starts or resumes a stopped timer
UINT32 tkTmrStart(Timer* tmr) {
    tkTmrUpdate(tmr);
    tmr->active = TRUE;
    return (tmr->ticksLeft * MS_PER_TICK);
}

// delay for specified number of ticks (see MS_PER_TICK)
void tkDelayTicks(TickCount_t ticks) {

    TickCount_t wait_ticks = tkGetTicks() + ticks;

    while (wait_ticks > tkGetTicks())
        Nop(); //  wait for wait_ticks to pass
}

///  Rounds down to multiple of 10ms (min 10ms)
void tkDelayMs(unsigned int ms) {
    ms = (ms / MS_PER_TICK);
    if (ms == 0) ms = 1;

    tkDelayTicks(ms);
}

/// Returns internal tick count,
/// Be careful using this as it rolls over after ~500 days
TickCount_t tkGetTicks() {
    return SysTickCount;
}

/// get the seconds elapsed since reset (rolls over after ~500 days)
unsigned long tkGetUptime() {
    return (tkGetTicks()/TICKS_PER_SEC);
}

/// Put the system in idle mode (sleep disables UARTs) and shuts off RS232
/// drivers. Sleep period limited by PR1 (TMR1 Period Register).
/// The sleep period is given by
///   P24_T1_PR1_MSEC = (1000UL * PR1 * TMR1_CKPSU32)/RTCFREQ)
/// where
///   TMR1_CKPSU32 : pre-scalar 8^(T1CONbits.TCKPS)
///   RTCFREQ      : timer clock frequency (here, the RTC 32kHz clock)
/// currently
///   PR1=A000 (40960)
///   T1CONbits.TCKPS=1
///   RTCFREQ=32768
/// So, PR1_MSEC = 1000 * 40960 * 8 / 32786 = 10000 (10s)
///
/// Duration (msec) is truncated to an integral number of PR1 cycles.
/// Can be interrupted by any ISR.
return_code_t tkDoIdle(UINT32 duration) {

    P24_T1_INT_DI();
    P24_T1_OFF();
    P24_SET_PR1(P24_PR1_DFL);
    byte state = 0;
    // get TMR1 period using PR1 reg
    // be careful modifying this:
    // casts/order matters
    uint32_t pr1_ms = (uint32_t)P24_T1_PR1_MSEC();

    uint64_t cycles=duration/pr1_ms;
    uint64_t dur_tk = cycles*PR1;

    // capture load power states
    if (doGetBit(XCVR_HOST))  state |= 0x1;
    if (doGetBit(XCVR_LICOR)) state |= 0x2;
    if (doGetBit(XCVR_CONS))  state |= 0x8;

    if(MMOD_IS_EN(MOD_TMR)){
        MPRINT(MOD_TMR,"SLEEP durtk, %llu, durms, %lu, cyc, %llu, systk, %lu\r\n", dur_tk,duration, cycles, SysTickCount);
///        MPRINT(MOD_TMR,"%s - SLEEP T1CON, %04X, TMR1_CKPS, %lu,  systk,%lu\r\n",__func__, T1CON,TMR1_CKPSU32, SysTickCount);
        serTxWait(SER_CTTY);
    }

    // turn off loads
    doSetBlink(LO);
    doSetBit(XCVR_HOST, LO); //turn off serial drivers
    doSetBit(XCVR_LICOR, LO);
    doSetBit(XCVR_CONS, LO);

    // clear receive flag
    // UART ISRs will set on RX
    g_rx_flag=0;
    uint64_t loops=0;
    uint16_t tmr1=0;
    uint64_t elapsed_tk=0;
    uint64_t pr1=PR1;
    uint32_t tk_crumbs[3]={0};
    int intr[2]={0};
    uint16_t irng[2]={0xFFFF,0};
    g_tkidle_flags=0;

    while(elapsed_tk < dur_tk){
        // clear TMR1 and clear/set T1 interrupt
        P24_SET_T1(0x0000);
        P24_SET_PR1(P24_PR1_DFL);
        P24_T1_CLR_INT_STATUS_BIT();
        P24_T1_INT_EN();
        P24_T1_ON();

        // go to idle mode
        // block until TMR1 or interrupt
        P24_PM_GOTO_IDLE_MODE();

        // awake, either from console
        // interrupt or TMR1 expired

        // interrupt handler clears T1 interrupt flag
        // and disables TON
        // disable T1 interrupt
        P24_T1_INT_DI();

        // use TMR1 counts to calculate elapsed sleep msec
        tmr1 = TMR1;

        if(g_rx_flag != 0 ){
            // if woken by console
            // add tm1 count to elapsed
            elapsed_tk+=tmr1;
            // set flag: ext interrupt
            g_tkidle_flags|=0x1;
            // quit
            break;
        }else{
            // if tmr1 expired, add full PR1
            // (sometimes tmr1 reads 1 after full cycle...rollover?)
            elapsed_tk+=pr1;
        }
        loops++;

        tk_crumbs[0]=SysTickCount;
        // check for short/long count, indicating possible spurious interrupt
        intr[0]+=( (tmr1<pr1) ? 1 : 0);
        intr[1]+=( (tmr1>pr1) ? 1 : 0);
        // get min/max short/long
        irng[0]=( (tmr1<pr1) && ((pr1-tmr1)<irng[0]) ? (pr1-tmr1) : irng[0]);
        irng[1]=( (tmr1>pr1) && ((tmr1-pr1)>irng[1]) ? (tmr1-pr1) : irng[1]);

        // set flag:extra loops
        if(loops>cycles)g_tkidle_flags|=0x2;
        // short count
        if(tmr1<pr1)g_tkidle_flags|=0x8;
        // long count
        if(tmr1>pr1)g_tkidle_flags|=0x10;
        tk_crumbs[1]+=(SysTickCount-tk_crumbs[0]);

    }
    tk_crumbs[0]=SysTickCount;
    // adjust System Tick Counter
    // for sleep time
    if( (elapsed_tk*(uint64_t)TMR1_CKPSU32) > 0xFFFFFFFF){
         g_tkidle_flags|=0x4;
    }
    uint32_t stk_inc=elapsed_tk;
    stk_inc*=TMR1_CKPSU32;
    stk_inc/=RTCFREQ;
    stk_inc*=TICKS_PER_SEC;
    tk_crumbs[2]+=(SysTickCount-tk_crumbs[0]);

    SysTickCount += stk_inc;//((uint32_t)TICKS_PER_SEC*((etk32 * TMR1_CKPSU32)/RTCFREQ));

    // restore load power states
    if (state & 0x1) doSetBit(XCVR_HOST, HI);
    if (state & 0x2) doSetBit(XCVR_LICOR, HI);
    if (state & 0x8) doSetBit(XCVR_CONS, HI);

    MPRINT(MOD_TMR,"WAKE    etk, %llu, loops, %llu, stkinc, %lu, systk, %lu\r\n", elapsed_tk, loops, stk_inc,SysTickCount);
    MPRINT(MOD_TMR,"WAKE    intr, %d,%d, irng, %u,%u, flags, %04X, crumbs1:2, %lu, %lu\r\n", intr[0],intr[1], irng[0],irng[1], g_tkidle_flags,tk_crumbs[1],tk_crumbs[2]);

    return(ERR_SUCCESS);
}

void __attribute__ ((weak)) TMR2_CallBack(void)
{
    // Add your custom callback code here
//    static int x=0;
//    LATEbits.LATE0=(x++%100==0?0:1);

    // Add your custom callback code here
    ++SysTickCount;
    if(SysTickCount%50==0){
        TFLAG_SET_BITS(TFLAG_2HZ);
    }
    if(SysTickCount%100==0){
        TFLAG_SET_BITS(TFLAG_1HZ);
    }
}

//void TMR2_CallBack(void)
//{
//    LATE ^= LATEbits.LATE0;
//    // Add your custom callback code here
//    ++SysTickCount;
//    if(SysTickCount%50==0){
//        TFLAG_SET_BITS(TFLAG_2HZ);
//    }
//    if(SysTickCount%100==0){
//        TFLAG_SET_BITS(TFLAG_1HZ);
//    }
//}


/// Interrupt Service Routines
/// note: timer counts reset automatically when ISR is triggered
/*
/// Timer1 ISR
void __attribute__((interrupt, no_auto_psv))_T1Interrupt(void) {
  //disable interrupt
    P24_T1_INT_DI();
}


/// Timer2 ISR
void __attribute__((interrupt, no_auto_psv))_T2Interrupt(void) {

    // update system tick counter
     ++SysTickCount;
    // set g_tkpps_flags (1,2 PPS)
    if(SysTickCount%50==0){
        TFLAG_SET_BITS(TFLAG_2HZ);
    }
    if(SysTickCount%100==0){
        TFLAG_SET_BITS(TFLAG_1HZ);
    }
     //clear interrupt flag
     P24_T2_CLR_INT_STATUS_BIT();
}
*/