/*
 * ast_systick_tick_management.c
 *
 */ 

/* ASF includes. */
#include "sysclk.h"
#include <asf.h>

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"

#ifdef TICK_MANAGEMENT_BY_AST_SYSTICK

/* Flag set from the tick interrupt to allow the sleep processing to know if
sleep mode was exited because of an AST interrupt or a different interrupt. */
static volatile uint32_t ulTickFlag = pdFALSE;

/* Variable to keep user alarm value. */
static volatile uint32_t ulUserAlarmValue = 0;

static struct ast_calendar xCalendar;

#define MAXIMUM_POSSIBLE_SLEEP_TIME		59
#define MAXIMUM_SECONDS_IN_MINUTE		60
#define MAXIMUM_MINUTES_IN_HOUR			60
#define MAXIMUM_HOURS_IN_DAY			24
#define MAXIMUM_MONTH_IN_YEAR			12

void vPortSetupASTAlarmInterrupt( void )
{
	struct ast_config ast_conf;
	uint32_t alarm0_default_value = 0;

	/* Ensure the AST can bring the CPU out of sleep mode. */
	sleepmgr_lock_mode( SLEEPMGR_RET );

	/* Ensure the 32KHz oscillator is enabled. */
	if( osc_is_ready( OSC_ID_OSC32 ) == pdFALSE )
	{
		osc_enable( OSC_ID_OSC32 );
		osc_wait_ready( OSC_ID_OSC32 );
	}

	/* Enable the AST itself. */
	ast_enable( AST );

	struct ast_calendar calendar;
	/* Calendar initial value. */
	calendar.FIELD.sec = 0;
	calendar.FIELD.min = 0;
	calendar.FIELD.hour = 12;
	calendar.FIELD.day = 20;
	calendar.FIELD.month = 9;
	calendar.FIELD.year = 12;

	ast_conf.mode = AST_CALENDAR_MODE;  /* Calendar mode. */
	ast_conf.osc_type = AST_OSC_32KHZ;
	ast_conf.psel = AST_PSEL_32KHZ_1HZ;
	ast_conf.calendar = calendar;
	ast_set_config( AST, &ast_conf );

	/* The AST alarm interrupt is used as the tick interrupt.  Ensure the alarm
	status starts clear. */
	ast_clear_interrupt_flag( AST, AST_INTERRUPT_ALARM );
	
	ast_write_alarm0_value(AST, alarm0_default_value);

	/* Enable wakeup from alarm 0 in the AST and power manager.  */
	ast_enable_wakeup( AST, AST_WAKEUP_ALARM );
	bpm_enable_wakeup_source( BPM, ( 1 << BPM_BKUPWEN_AST ) );
	//bpm_enable_fast_wakeup(BPM);

	/* Tick interrupt MUST execute at the lowest interrupt priority. */
	NVIC_SetPriority( AST_ALARM_IRQn, configLIBRARY_LOWEST_INTERRUPT_PRIORITY);
	ast_enable_interrupt( AST, AST_INTERRUPT_ALARM );
	NVIC_ClearPendingIRQ( AST_ALARM_IRQn );
	NVIC_EnableIRQ( AST_ALARM_IRQn );
}

void AST_ALARM_Handler(void)
{
	/* The CPU woke because of a tick. */
	ulTickFlag = pdTRUE;
	
	/* Clear alarm interrupt flag. */
	ast_clear_interrupt_flag(AST, AST_INTERRUPT_ALARM);
}

uint32_t get_day_num(struct ast_calendar *pcal)
{
	uint32_t month;
	uint32_t day_in_month;
	month = pcal->FIELD.month;
	switch(month) {
		case 1:
		case 3:
		case 5:
		case 7:
		case 8:
		case 10:
		case 12:
		day_in_month = 31;
		break;
		case 4:
		case 6:
		case 9:
		case 11:
		day_in_month = 30;
		break;
		default:
		day_in_month = 0;
		break;
	}
	if(day_in_month == 0) {
		if((pcal->FIELD.year % 4) == 0){
			day_in_month = 29;
		}
		else {
			day_in_month = 28;
		}
	}
	return day_in_month;
}
/* Set alarm value in date/time calendar format.
   Parameter interval unit: second, maximum interval 59s
 */
void ast_set_calendar_alarm(uint32_t interval)
{
	uint32_t temp;
	uint32_t seconds;
	uint32_t days_in_month;
	struct ast_calendar calendar;
	
	calendar = ast_read_calendar_value(AST);
	seconds = (uint32_t)calendar.FIELD.sec;
	if(seconds + interval >= MAXIMUM_SECONDS_IN_MINUTE) {
		if(calendar.FIELD.min + 1 == MAXIMUM_MINUTES_IN_HOUR) {
			calendar.FIELD.min = 0;
			if(calendar.FIELD.hour + 1 == MAXIMUM_HOURS_IN_DAY){
				calendar.FIELD.hour = 0;
				days_in_month = get_day_num(&calendar);
				if(calendar.FIELD.day == days_in_month ) {
					calendar.FIELD.day = 1;
					if(calendar.FIELD.month == MAXIMUM_MONTH_IN_YEAR){
						calendar.FIELD.month = 1;
						calendar.FIELD.year++;
					}
					else {
						calendar.FIELD.month++;
					}
				}
				else {
					calendar.FIELD.day++;
				}
			}
			else {
				calendar.FIELD.hour++;
			}
		}
		else {
			calendar.FIELD.min++;
		}
		temp = (seconds + interval) % MAXIMUM_SECONDS_IN_MINUTE;
		calendar.field = (calendar.field & (~AST_CALV_SEC_Msk)) | temp;
		ast_write_alarm0_value(AST,calendar.field);
	}
	else {
		ast_write_alarm0_value(AST,calendar.field + interval);
	}
}

static inline uint32_t ast_read_alarm0_value(Ast *ast)
{
	/* Read Alarm0 value. */
	return ast->AST_AR0;
}

void vPortSuppressTicksAndSleep( portTickType xExpectedIdleTime )
	{
	unsigned long ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickIncrements, ulTimeSeconds;
	unsigned long ulCalValue_before, ulCalValue_after;
	portTickType xModifiableIdleTime;

		/* Make sure the SysTick reload value does not overflow the counter. */
/*		
		if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
		{
			xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
		}
*/		
		ulTimeSeconds = xExpectedIdleTime / configTICK_RATE_HZ;
		/* if sleep period less than 1s, do nothing and return back */
		if(ulTimeSeconds == 0)
			return;
		if(ulTimeSeconds > MAXIMUM_POSSIBLE_SLEEP_TIME) {
			ulTimeSeconds = MAXIMUM_POSSIBLE_SLEEP_TIME;
		}
		ulTickFlag = pdFALSE;
		/* Calculate the reload value required to wait xExpectedIdleTime
		tick periods.  -1 is used because this code will execute part way
		through one of the tick periods, and the fraction of a tick period is
		accounted for later. */
/*
		ulReloadValue = ( ulTimerReloadValueForOneTick * ( xExpectedIdleTime - 1UL ) );
		if( ulReloadValue > ulStoppedTimerCompensation )
		{
			ulReloadValue -= ulStoppedTimerCompensation;
		}
*/
		/* Stop the SysTick momentarily.  The time the SysTick is stopped for
		is accounted for as best it can be, but using the tickless mode will
		inevitably result in some tiny drift of the time maintained by the
		kernel with respect to calendar time. */
/*		portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT;*/

		/* Adjust the reload value to take into account that the current time
		slice is already partially complete. */
/*		ulReloadValue += ( portNVIC_SYSTICK_LOAD_REG - ( portNVIC_SYSTICK_LOAD_REG - portNVIC_SYSTICK_CURRENT_VALUE_REG ) );*/

		/* Enter a critical section but don't use the taskENTER_CRITICAL()
		method as that will mask interrupts that should exit sleep mode. */
		__asm volatile( "cpsid i" );

		/* If a context switch is pending or a task is waiting for the scheduler
		to be unsuspended then abandon the low power entry. */
		if( eTaskConfirmSleepModeStatus() == eAbortSleep )
		{
			/* Restart SysTick. */
/*			portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;*/

			/* Re-enable interrupts - see comments above the cpsid instruction()
			above. */
			__asm volatile( "cpsie i" );
		}
		else
		{
			/* Set the new reload value. */
/*			portNVIC_SYSTICK_LOAD_REG = ulReloadValue;*/

			/* Clear the SysTick count flag and set the count value back to
			zero. */
/*			portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;*/
			/* Restart SysTick. */
/*			portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;*/
			
			/* Store the user alarm value */
			ulUserAlarmValue = ast_read_alarm0_value(AST);
			
			/* Set alarm register in time/date format */			
			ast_set_calendar_alarm(ulTimeSeconds);
			
			/* Read current calendar value */
			xCalendar = ast_read_calendar_value(AST);
			
			if(xCalendar.field < ulUserAlarmValue) {
				/* if user alarm occurs before the mcu woken up time, 
				use the user alarm to wake up mcu */
				if(ast_read_alarm0_value(AST) > ulUserAlarmValue) {
					ast_write_alarm0_value(AST, ulUserAlarmValue);
				}
			}
			if(xCalendar.field >= ast_read_alarm0_value(AST)) {
				/* Don't need to restore the user alarm value */
				return;
			}
			if(ulTickFlag == true) {
				/* AST Alarm interrupt has occured, not 
				need to restore the user alarm value */
				return;
			}
			/* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
			set its parameter to 0 to indicate that its implementation contains
			its own wait for interrupt or wait for event instruction, and so wfi
			should not be executed again.  However, the original expected idle
			time variable must remain unmodified, so a copy is taken. */
			xModifiableIdleTime = xExpectedIdleTime;
			configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
			if( xModifiableIdleTime > 0 )
			{
				/* Sleep until something happens. */
				sleepmgr_enter_sleep();
/*				
				__asm volatile( "wfi" );
			    __asm volatile( "dsb" );
				__asm volatile( "isb" );
*/
			}
			configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

			/* Stop SysTick.  Again, the time the SysTick is stopped for is
			accounted for as best it can be, but using the tickless mode will
			inevitably result in some tiny drift of the time maintained by the
			kernel with respect to calendar time. */
/*			portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT;*/
			
			if(ulTickFlag != pdFALSE) {
				ulCompleteTickPeriods = ulTimeSeconds * configTICK_RATE_HZ;
				/* Restore user alarm value */
				ast_write_alarm0_value(AST, ulUserAlarmValue);
			}
			else {
				/* Something other than the AST interrupt ended the sleep.*/
				//only calculate minutes and seconds, on the premise of MAXIMUM_POSSIBLE_SLEEP_TIME.
				ulCalValue_before = xCalendar.FIELD.min * 60 + xCalendar.FIELD.sec;
				xCalendar = ast_read_calendar_value(AST);
				if((xCalendar.FIELD.min == 0) && (ulCalValue_before > MAXIMUM_SECONDS_IN_MINUTE)) { //minute field overflow, hour increased by 1
					ulCalValue_after = MAXIMUM_MINUTES_IN_HOUR * MAXIMUM_SECONDS_IN_MINUTE + xCalendar.FIELD.sec;
				}
				else {
					ulCalValue_after = xCalendar.FIELD.min * 60 + xCalendar.FIELD.sec;
				}
				ulCompleteTickPeriods = (ulCalValue_after - ulCalValue_before) * configTICK_RATE_HZ;
				/* Restore user alarm value */
				ast_write_alarm0_value(AST,ulUserAlarmValue);
			}
			/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
			again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
			value. */
/*			
			portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
			portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
*/
			vTaskStepTick( ulCompleteTickPeriods );
			
			/* Re-enable interrupts - see comments above the cpsid instruction()
			above. */
			__asm volatile( "cpsie i" );
		}
	}
#endif /* TICK_MANAGEMENT_BY_AST_SYSTICK */