/**
* \file  sysTimer.c
*
* \brief System timer implementation
*
* Copyright (c) 2018 - 2019 Microchip Technology Inc. and its subsidiaries. 
*
* \asf_license_start
*
* \page License
*
* Subject to your compliance with these terms, you may use Microchip
* software and any derivatives exclusively with Microchip products. 
* It is your responsibility to comply with third party license terms applicable 
* to your use of third party software (including open source software) that 
* may accompany Microchip software.
*
* THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS".  NO WARRANTIES, 
* WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, 
* INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, 
* AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE 
* LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL 
* LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE 
* SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE 
* POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.  TO THE FULLEST EXTENT 
* ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY 
* RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, 
* THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
*
* \asf_license_stop
*
*/

/*- Includes ---------------------------------------------------------------*/
#include "compiler.h"
#include "common_hw_timer.h"
#include "sysTimer.h"


/*****************************************************************************
*****************************************************************************/
static void placeTimer(SYS_Timer_t *timer);
static void SYS_HwExpiry_Cb(void);
static void SYS_HwOverflow_Cb(void);

/*- Variables --------------------------------------------------------------*/
static SYS_Timer_t *timers;
volatile uint32_t SysTimerIrqCount;

volatile uint8_t timerExtension1,timerExtension2;

/*- Implementations --------------------------------------------------------*/

/*************************************************************************//**
*****************************************************************************/
void SYS_TimerInit(void)
{
	SysTimerIrqCount = 0;
    timerExtension1 = 0;
    timerExtension2 = 0;
    set_common_tc_overflow_callback(SYS_HwOverflow_Cb);
	set_common_tc_expiry_callback(SYS_HwExpiry_Cb);
	common_tc_init();
	common_tc_delay(SYS_TIMER_INTERVAL * MS);
	timers = NULL;
}

/*************************************************************************//**
*****************************************************************************/
void SYS_TimerStart(SYS_Timer_t *timer)
{
	if (!SYS_TimerStarted(timer)) {
		placeTimer(timer);
	}
}

/*************************************************************************//**
*****************************************************************************/
void SYS_TimerStop(SYS_Timer_t *timer)
{
	SYS_Timer_t *prev = NULL;

	for (SYS_Timer_t *t = timers; t; t = t->next) {
		if (t == timer) {
			if (prev) {
				prev->next = t->next;
			} else {
				timers = t->next;
			}

			if (t->next) {
				t->next->timeout += timer->timeout;
			}

			break;
		}

		prev = t;
	}
}

/*************************************************************************//**
*****************************************************************************/
bool SYS_TimerStarted(SYS_Timer_t *timer)
{
	for (SYS_Timer_t *t = timers; t; t = t->next) {
		if (t == timer) {
			return true;
		}
	}
	return false;
}

/*************************************************************************//**
*****************************************************************************/
void SYS_TimerTaskHandler(void)
{
	uint32_t elapsed;
	uint32_t cnt;
	irqflags_t flags;

	if (0 == SysTimerIrqCount) {
		return;
	}

	/* Enter a critical section */
	flags = cpu_irq_save();
	cnt = SysTimerIrqCount;
	SysTimerIrqCount = 0;
	/* Leave the critical section */
	cpu_irq_restore(flags);

	elapsed = cnt * SYS_TIMER_INTERVAL;

	while (timers && (timers->timeout <= elapsed)) {
		SYS_Timer_t *timer = timers;

		elapsed -= timers->timeout;
		timers = timers->next;
		if (SYS_TIMER_PERIODIC_MODE == timer->mode) {
			placeTimer(timer);
		}

		if (timer->handler) {
			timer->handler(timer);
		}
	}

	if (timers) {
		timers->timeout -= elapsed;
	}
}

/*************************************************************************//**
*****************************************************************************/
static void placeTimer(SYS_Timer_t *timer)
{
	if (timers) {
		SYS_Timer_t *prev = NULL;
		uint32_t timeout = timer->interval;

		for (SYS_Timer_t *t = timers; t; t = t->next) {
			if (timeout < t->timeout) {
				t->timeout -= timeout;
				break;
			} else {
				timeout -= t->timeout;
			}

			prev = t;
		}

		timer->timeout = timeout;

		if (prev) {
			timer->next = prev->next;
			prev->next = timer;
		} else {
			timer->next = timers;
			timers = timer;
		}
	} else {
		timer->next = NULL;
		timer->timeout = timer->interval;
		timers = timer;
	}
}

/*********************************************************************
* Function:         void MiWi_TickGet()
*
* PreCondition:     none
*
* Input:		    none
*
* Output:		    MIWI_TICK - the current symbol time
*
* Side Effects:	    The timer interrupt is disabled
*                   for several instruction cycles while the 
*                   timer value is grabbed.  This is to prevent a
*                   rollover from incrementing the timer extenders
*                   during the read of their values
*
* Overview:		    This function returns the current time
*
* Note:			    This function should never be called 
*                   when interrupts are disabled if interrupts are 
*                   disabled when this is called then the timer 
*                   might rollover and the byte extension would not 
*                   get updated.
********************************************************************/
uint32_t MiWi_TickGet(void)
{
    MIWI_TICK currentTime;
	uint8_t current_timerExtension1 = timerExtension1;

	currentTime.word.w0 = common_tc_read_count();

	/* NOP to allow the interrupt to process before copying timerExtension1 */
	nop();

	/* If interrupt occurred during interrupt disable  read count again*/
	if (current_timerExtension1 != timerExtension1)
	{
		currentTime.word.w0 = common_tc_read_count();
	}
    /* copy the byte extension */
    currentTime.byte.b2 = timerExtension1;
    currentTime.byte.b3 = timerExtension2;

    return currentTime.Val;
}

/*********************************************************************
* Function:         MIWI_TICK MiWi_TickGetDiff()
*
* PreCondition:     none
*
* Input:		    current_tick  - Recent tick read from MiWi_TickGet
*                   previous_tick - Tick read prior to current_tick for 
*                                   calculation.
*
* Output:		    uint32_t - Difference in current_tick to previous_tick
*
* Side Effects:	    none
*
* Overview:		    This function returns difference between current_tick
*                   and previous_tick
*
* Note:			    none
********************************************************************/
uint32_t MiWi_TickGetDiff(MIWI_TICK current_tick, MIWI_TICK previous_tick)
{
	uint32_t ret_val;
	if (current_tick.Val > previous_tick.Val)
	{
		/* If current > previous, no overflow in running timer*/
		ret_val = current_tick.Val - previous_tick.Val;
	}
	else
	{
		/* Handling Overflow as current tick < previous tick */
		ret_val = (0xFFFFFFFF - previous_tick.Val) + current_tick.Val;
	}
	return ret_val;
}


/*****************************************************************************
*****************************************************************************/
static void SYS_HwExpiry_Cb(void)
{
	SysTimerIrqCount++;
	common_tc_delay(SYS_TIMER_INTERVAL * MS);
}

static void SYS_HwOverflow_Cb(void)
{
	timerExtension1++;
	if(timerExtension1 == 0)
	{
		timerExtension2++;
	}
}

void SYS_TimerAdjust_SleptTime(uint32_t sleeptime)
{
    irqflags_t flags;

    /* Enter a critical section */
    flags = cpu_irq_save();
    SysTimerIrqCount = (sleeptime / SYS_TIMER_INTERVAL);
    /* Leave the critical section */
    cpu_irq_restore(flags);

    /* Stop and Start Timers */
    common_tc_overflow_stop();
    common_tc_compare_stop();
    set_common_tc_overflow_callback(SYS_HwOverflow_Cb);
    set_common_tc_expiry_callback(SYS_HwExpiry_Cb);
    common_tc_init();
    common_tc_delay(SYS_TIMER_INTERVAL * MS);
}