/****************************************************************************/
/* Copyright 2003-2016 MBARI                                                */
/****************************************************************************/
/* Summary  : System Ticker and RTC Routines for OASIS5 on PIC32MX470F512L	*/
/* Filename : timer.c                                                       */
/* Author   : Robert Herlien (rah)                                          */
/* Project  : OASIS Mooring Replacement (OASIS5)                            */
/* Revision : 1.0                                                           */
/* Created  : 01/13/2003                                                    */
/*                                                                          */
/* MBARI provides this documentation and code "as is", with no warranty,    */
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium  */
/* Research Institute to assist in its use, correction, modification, or    */
/* enhancement. This information should not be published or distributed to  */
/* third parties without specific written permission from MBARI.            */
/*                                                                          */
/****************************************************************************/
/* Modification History:                                                    */
/* 13jan2003 rah - created                                                  */
/* 20apr2016 rah - ported to OASIS5 from OASIS4 clock.c                     */
/****************************************************************************/
/* This module implements the RTC clock ticker and sleep handler            */ 
/* The tick and RTC interrupt handlers are in FreeRTOS/PIC32/port_asm.S		*/
/* The tick handler calls the system millisecond ticker in FreeRTOS/PIC32/port.c*/
/* It also incrments clkMs, to maintain RTC time to millisecond accuracy	*/
/* The RTC interrupt handler calls _RTCInterrupt in this module				*/
/****************************************************************************/

#include "GenericTypeDefs.h"
#include <xc.h>
#include <cp0defs.h>
#include <timer.h>
#include <rtc.h>
#include <picConfig.h>
#include <serial.h>
#include <dig_io.h>
#include <custom.h>						/* MIN_SLEEP_TIME					*/
#include <utils.h>

#include <FreeRTOS.h>					/* FreeRTOS definitions				*/
#include <time.h>


/********************************/
/*      External Data			*/
/********************************/

Extern Int16		gmtOffset;
Extern TaskHandle_t	oasisTask;


/********************************/
/*      Global Data             */
/********************************/

/* clkMs is global so system ms tick handler can increment it           */
volatile UINT16  clkMs;                 /* Milliseconds of time of day  */


/********************************/
/*      Module Local Data       */
/********************************/

static volatile time_t  clkTod;         /* Current time of day, seconds */


/************************************************************************/
/* Function    : tmrBusyWaitUs                                          */
/* Purpose     : Delay a number of microseconds                         */
/* Input       : Microsecs to delay                                     */
/* Outputs     : None                                                   */
/************************************************************************/
void tmrBusyWaitUs(UINT32 us)
{
    UINT32 stCnt = _CP0_GET_COUNT();

    if (us > 1)
        while ((_CP0_GET_COUNT() - stCnt) < (((us-1)*(SYSCLK/1000000))/2))
            ;                               /* wait for usecs to expire     */

}

/************************************************************************/
/* Function    : clkInit                                                */
/* Purpose     : Initialize system time of day clock                    */
/* Input       : None                                                   */
/* Outputs     : 0 if OK, -1 if RTC time was bogus                      */
/************************************************************************/
int clkInit(void)
{
    struct tm   curTm;
    time_t      tmpTod;
    BYTE	clkRegs[4];

#ifdef PROTO
    INT4R = 0;                          /* INT4 = RPD3 = pin 78        */
#else
    INT4R = 7;                          /* INT4 = RPC13 = pin 73        */
#endif
    INTCONCLR = _INTCON_INT4EP_MASK;    /* Interrupt on negative edge   */
    IPC4bits.INT4IP = 2;                /* RTC interrupt priority       */
    IPC4bits.INT4IS = 0;

	picUnlock();					
	OSCCONSET = _OSCCON_SLPEN_MASK;		/* Set sleep bit                */
	picLock();

	rtcSetAlarm1(0);				    /* Set Alarm Regs to once/second */

    do
    {
        IFS0CLR = _IFS0_INT4IF_MASK;
        clkMs = 0;                      /* Init ms to zero              */
		clkTod = rtcTime(&curTm);       /* Get real time of day         */
    } while (IFS0bits.INT4IF);          /* If got new second, try again */

    IEC0SET = _IEC0_INT4IE_MASK;        /* Enable one-second ints       */

    if ((curTm.tm_mon > 11) || (curTm.tm_mday < 1) ||
        (curTm.tm_mday > 31) || (curTm.tm_hour > 23) ||
        (curTm.tm_min >= 60) || (curTm.tm_sec > 60))
        return(-1);
    
    return(0);
}


/************************************************************************/
/* Function    : clkTime                                                */
/* Purpose     : Return time of day                                     */
/* Input       : None                                                   */
/* Outputs     : Current time of day                                    */
/************************************************************************/
time_t clkTime(void)
{
    return(clkTod);

} /* clkTime() */


/************************************************************************/
/* Function    : clkGetTime                                             */
/* Purpose     : Get time of day with milliseconds                      */
/* Input       : Ptrs to time_t and ms                                  */
/* Outputs     : Current time of day                                    */
/************************************************************************/
time_t clkGetTime(time_t *todp, UINT16 *ms)
{
    if (todp)
        *todp = clkTod;

    if (ms)
        *ms = clkMs;
    
    return(clkTod);

} /* clkTime() */


/************************************************************************/
/* Function    : clkSetTime                                             */
/* Purpose     : Set Date and Time to system and RTC                    */
/* Input       : time_t for new time									*/
/* Outputs     : None                                                   */
/************************************************************************/
int clkSetTime(time_t newTod)
{
    rtcSetTime(newTod);
    clkTod = newTod;
    clkMs = 0;

} /* clk_write() */


/************************************************************************/
/* Function    : clkPrintTime                                           */
/* Purpose     : Print date & time to serial port                       */
/* Input       : Ptr to time to print.  NULL means now					*/
/* Outputs     : None                                                   */
/************************************************************************/
void clkPrintTime(time_t *tod)
{
	struct tm     *tmptr;

	if (tod != NULL)
		tmptr = localtime(tod);
	else
		tmptr = localtime((time_t *)&clkTod);

	xprintf("%04d/%02d/%02d %02d:%02d:%02d", tmptr->tm_year + 1900,
			tmptr->tm_mon+1, tmptr->tm_mday, tmptr-> tm_hour,
			tmptr->tm_min, tmptr->tm_sec);

	if (tod == NULL)
		xprintf(".%03u", clkMs);
	
} /* clkPrintTime() */


/************************************************************************/
/* Function	   : clkLocalHour											*/
/* Purpose	   : Get hour in local time zone							*/
/* Input	   : None													*/
/* Outputs	   : Hour (0 - 23)											*/
/************************************************************************/
Nat16 clkLocalHour(void)
{
    struct tm   curTm;

	rtcTime(&curTm);					/* Get real time of day         */
	return((curTm.tm_hour + gmtOffset + 24) % 24);

} /* clkLocalHour() */


/************************************************************************/
/* Function    : tmrSleep												*/
/* Purpose     : Sleep for given number of seconds                      */
/* Input       : Time to sleep                                          */
/* Outputs     : None                                                   */
/* Comments    : This version used by diagnostic utilities				*/
/************************************************************************/
void tmrSleep(UINT32 secs)
{
	time_t		now;

	if (secs < MIN_SLEEP_TIME)			/* If < min, just return		*/
		return;
	if (secs > MAX_SLEEP_TIME)			/* Limit sleep to max sleep time*/
		secs = MAX_SLEEP_TIME;

	if (serAnyTxActive())					/* If any Tx active, don't sleep*/
		return;

    IEC0CLR = _IEC0_INT4IE_MASK;        /* Turn off RTC int				*/
    digTx0Dis();                        /* Turn off Tx                  */
	digBlinkyOff();						/* Turn off Blinky				*/
	digTest3Off();

	now = rtcTime(NULL);				/* Get current time_t			*/
	if (rtcSetAlarm1(now + secs) == OK) /* Set alarm					*/
	{
		IEC0CLR = _IEC0_INT4IE_MASK;    /* Turn off ms int				*/
		IEC0SET = _IEC0_INT4IE_MASK;	/* Enable RTC int				*/
		setupConsoleWakeFromSleep();	/* Set up hardware for console wakeup*/

		_wait();						/* Wait for int					*/

		IEC0CLR = _IEC0_INT4IE_MASK;	/* Disable RTC ints again		*/
		clkMs = 0;                      /* Init ms to zero              */
		clkTod = rtcTime(NULL);         /* Resync time of day           */
	}

	rtcSetAlarm1(0);					/* Restore 1 Hz RTC ints		*/
	clearConsoleWakeFromSleep();		/* Restore console				*/
    digTx0En();                         /* Turn on Tx                   */

    IEC0SET = (_IEC0_T1IE_MASK | _IEC0_INT4IE_MASK);
								        /* Restore ms and RTC ints		*/
} /* tmrSleep() */


/************************************************************************/
/* Function    : tmrRtosSleep											*/
/* Purpose     : Sleep for given number of seconds                      */
/* Input       : Time to sleep                                          */
/* Outputs     : None                                                   */
/* Comments    : This version used for FreeRTOS Tickless sleep			*/
/************************************************************************/
void tmrRtosSleep(TickType_t slpTicks)
{
	time_t			todSave;
	Nat32			slpSecs;
	TickType_t		msSave;
	eSleepModeStatus sleepSts;

	slpSecs = (slpTicks + TICKS_PER_SEC/2) / TICKS_PER_SEC;
	if (slpSecs < MIN_SLEEP_TIME)			/* If < min, just return	*/
		return;
	if (slpSecs > MAX_SLEEP_TIME)			/* Limit sleep to max sleep time*/
		slpSecs = MAX_SLEEP_TIME;

//	if (serAnyTxActive())					/* If any Tx active, don't sleep*/
//		return;

	INTDisableInterrupts();
	sleepSts = eTaskConfirmSleepModeStatus(); 
	INTEnableInterrupts();

    IEC0CLR = _IEC0_INT4IE_MASK;			/* Turn off RTC int			*/
	todSave = rtcTime(NULL);				/* Get starting seconds		*/

	if (sleepSts != eAbortSleep)
	{
		setRtosRunMode(FALSE);				/* OS scheduler is turned off*/
											/* before calling this func	*/
		if (rtcSetAlarm1(todSave + slpSecs) == OK) /* Set alarm			*/
		{
			IEC0CLR = _IEC0_INT4IE_MASK;    /* Turn off ms int			*/
			IEC0SET = _IEC0_INT4IE_MASK;	/* Enable RTC int			*/
			msSave = (TickType_t)clkMs;		/* Get starting ms			*/
			digTx0Dis();		            /* Turn off Tx				*/
			setupConsoleWakeFromSleep();	/* Set up hardware for serial wakeup*/

			_wait();						/* Wait for int				*/

			IEC0CLR = _IEC0_INT4IE_MASK;	/* Disable RTC ints again	*/
			clkMs = 0;                      /* Init ms to zero			*/
			clkTod = rtcTime(NULL);         /* Resync time of day 		*/
			clearConsoleWakeFromSleep();	/* Restore serial ports		*/

			vTaskStepTick((TICKS_PER_SEC * (clkTod-todSave)) - msSave);
		}

		rtcSetAlarm1(0);					/* Restore 1 Hz RTC ints	*/

		IEC0SET = (_IEC0_T1IE_MASK | _IEC0_INT4IE_MASK);
											/* Turn on ms and RTC ints	*/
	}

	setRtosRunMode(TRUE);					/* Show scheduler running again*/
	xTaskNotifyGive(oasisTask);				/* Wake up Oasis Scheduler	*/	
}


/************************************************************************/
/**********               ISRs                                  *********/
/************************************************************************/

/************************************************************************/
/* Function    : RTCack                                                 */
/* Purpose     : Interrupt Completion routine for RTC                   */
/* Input       : None                                                   */
/************************************************************************/
static void RTCack(void)
{
	rtcTaskPinOn();						/* For debugging				*/
	rtcWriteReg(RTC_CTRLSTAT, 0x00);    /* Clear the int in the RTC     */
#if (configUSE_PREEMPTION == 0)			/* If no preemption, we can get	*/
	clkTod = rtcTime(NULL);				/*  locked out and lose time	*/
#endif
	rtcTaskPinOff();					/* For debugging				*/
}


/************************************************************************/
/* Function    : _RTCInterrupt                                          */
/* Purpose     : External Interrupt 4, servicing the RTC 1/sec interrupt*/
/* Input       : None                                                   */
/* Comment     : Called from assembly language wrapper in port_asm.S    */
/************************************************************************/
void _RTCInterrupt(void)
{
	BaseType_t	xHigherPriorityTaskWoken;

    rtcPinOn();

    clkTod++;                           /* increment time of day        */
    clkMs = 0;                          /* clear ms count               */
	xHigherPriorityTaskWoken = pdFALSE;

	xTimerPendFunctionCallFromISR(RTCack, NULL, 0, &xHigherPriorityTaskWoken);

	portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);

	IFS0CLR = _IFS0_INT4IF_MASK;        /* clear the interrupt flag     */
    rtcPinOff();
}
