/****************************************************************************/
/* Copyright 2003-2012 MBARI												*/
/****************************************************************************/
/* Summary	: Real-time Clock support for BEDS System						*/
/* Filename : clock.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: Benthic Event Detection System (BEDS)							*/
/* Revision : 1.0															*/
/* Created	: 10/18/2011 from Oasis4 clock.c								*/
/*																			*/
/* 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.			*/
/*																			*/
/****************************************************************************/
/* This module is rather complicated due to the fact that there are two		*/
/* clocks in the system: the Persistor RTC maintained by the CF2's MSP430,	*/
/* and the DS3234 "Extremely Accurate Clock".  Like the man with two watches,*/
/* if we're not careful, we won't know what time it is.						*/
/* In this module, we believe the DS3234, and attempt to maintain the		*/
/* concept of system time based on that clock.	 These routines use the		*/
/* DS3234 for accuracy, but the Persistor RTC for precision, since the		*/
/* DS3234 only has a precision of one second.  The difference between these */
/* two clock sources is maintained in variable systemMsOffset.				*/
/****************************************************************************/
/* Modification History:													*/
/* 14oct2011 rah - created from Oasis4 clock.c								*/
/* 10oct2002 rah - created from OASIS clock.c								*/
/* $Log$
 */
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <xc.h>							/* PIC32MX register definitions		*/
#include <beds.h>						/* BEDS controller definitions		*/
#include <clock.h>						/* BEDS Clock module				*/
#include <dig_io.h>						/* BEDS digital I/O definitions		*/
#include <spi.h>						/* BEDS SPI definitions				*/
#include <syslog.h>						/* System logger					*/
#include <rtc.h>
#include <picConfig.h>
#include <watch.h>						/* For getRunMode()					*/
#include <debug.h>						/* For debug pin definitions		*/

#include <cp0defs.h>
#include <stdio.h>
#include <string.h>
#include <time.h>


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

MLocal volatile time_t	clkTod = 0x5a4a0000; /* Current time of day,	*/
											/* initialized to 1/1/2018	*/
MLocal volatile Nat16	clkMs = 0;			/* ms component of time of day*/
MLocal volatile Nat32	clkTick = 0;		/* System ticker			*/
MLocal struct tm		clkTm;				/* DS3234 time of day as struct tm*/
MLocal volatile MBool	needRTCAck;
MLocal volatile MBool	rtcIsBroken = FALSE;
MLocal volatile MBool	trackingRTC = FALSE;
MLocal MBool			sleeping = FALSE;
MLocal time_t			lastRTC = 0;		/* Last time read RTC		*/
MLocal time_t			systemStartTime;
MLocal Nat32			wakeupVec = 0;


/************************************************************************/
/* Function	   : clkPrintTime											*/
/* Purpose	   : Print date & time to serial port						*/
/* Input	   : time_t													*/
/* Outputs	   : None													*/
/************************************************************************/
void clkPrintTime(time_t ptime)
{
  struct tm		*tmptr;

  tmptr = gmtime(&ptime);

  printf("%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);

} /* clkPrintTime() */


/************************************************************************/
/* Function	   : clkTime												*/
/* Purpose	   : Return current tod as time_t							*/
/* Input	   : None													*/
/* Outputs	   : time_t													*/
/************************************************************************/
time_t clkTime(void)
{
	return(clkTod);
}


/************************************************************************/
/* Function    : time													*/
/* Purpose     : Override XC32 library's time() function				*/
/* Input       : time_t ptr												*/
/* Outputs     : Current time of day                                    */
/************************************************************************/
time_t time(time_t *tp)
{
	if (tp)
		*tp = clkTod;

	return(clkTod);
}


/************************************************************************/
/* Function	   : clkGetTime												*/
/* Purpose	   : Get the current time with high accuracy and precision	*/
/* Input	   : Ptrs to seconds, milliseconds							*/
/* Outputs	   : time_t													*/
/************************************************************************/
time_t	clkGetTime(time_t *secondp, Nat16 *msp)
{
	if (secondp)
		*secondp = clkTod;
	
	if (msp)
		*msp = clkMs;
	
	return(clkTod);

} /* clkGetTime() */


/************************************************************************/
/* 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;
	trackingRTC = FALSE;			/* Force re-read of RTC				*/
	lastRTC = clkTod;

} /* clkSetTime() */


/************************************************************************/
/* Function	   : clkGetTick												*/
/* Purpose	   : Get the current tick count								*/
/* Input	   : None													*/
/* Outputs	   : Tick count												*/
/************************************************************************/
Nat32	clkGetTick(void)
{
	return(clkTick);

} /* clkGetTick() */


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

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

}

/************************************************************************/
/* Function    : clkDelayMs												*/
/* Purpose     : Delay a number of milliseconds                         */
/* Input       : Millisecs to delay                                     */
/* Outputs     : None                                                   */
/************************************************************************/
void clkDelayMs(Nat32 ms)
{
	Nat32	stTick;

	if (ms < 2*MS_PER_TICK)
		clkDelayUs(1000*ms);
	else
		for (stTick = clkTick+1; clkTick < (stTick + (ms/MS_PER_TICK)); )
			 ;
}


/************************************************************************/
/* Function	   : uptimeCmd												*/
/* Purpose	   : User "uptime" command									*/
/* Inputs	   : None													*/
/* Outputs	   : OK														*/
/************************************************************************/
CmdRtn uptimeCmd(int argc, char **argv)
{
	time_t	uptime;

	uptime = clkTime() - systemStartTime;

	printf("up %u days, %02u:%02u\r\n",
		   uptime/86400L, (uptime%86400L)/3600, (uptime%3600)/60);

	return(OK);
}


/************************************************************************/
/* Function	   : clkInit												*/
/* Purpose	   : Initialize clock module, set system time from RTC		*/
/* Input	   : None													*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
int clkInit(void)
{
    struct tm   curTm;
    time_t      tmpTod;
    BYTE	clkRegs[4];

   /* Set up timer 1          */
    T1CON = 0x0010;                     /* Prescale divide by 8         */
    PR1 = (PBCLK/(8*TICKS_PER_SEC))-1;  /* set timer period             */
    TMR1 = 0x0000;                      /* clear timer count            */
    
    IPC1bits.T1IP = 1;                  /* System timer int priority    */
    IPC1bits.T1IS = 0;

    /* setup timer interrupts */
    IFS0CLR = _IFS0_T1IF_MASK;
    IEC0SET = _IEC0_T1IE_MASK;

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

    INT4R = 7;                          /* INT4 = RPC13 = pin 73        */

    INTCONCLR = _INTCON_INT4EP_MASK;    /* Interrupt on negative edge   */
    IPC4bits.INT4IP = 2;                /* RTC interrupt priority       */
    IPC4bits.INT4IS = 0;

	needRTCAck = FALSE;

	rtcIsBroken = 
		(rtcSetAlarm1(0) != OK);		/* Set Alarm Regs to once/second */

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

	lastRTC = systemStartTime = clkTod;
	rtcSetAlarm1(0);					/* Set alarm regs to once/sec	*/
    IEC0SET = _IEC0_INT4IE_MASK;		/* Enable one-second ints       */

    T1CONbits.TON = 1;                  /* start tick timer          */

    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    : clkSleep												*/
/* Purpose     : Put CPU into sleep mode for n seconds					*/
/* Input       : Number of seconds to sleep								*/
/* Output      : None                                                   */
/************************************************************************/
void clkSleep(Nat32 secs)
{
#if 0
	time_t		slpStart;

	if (OSCCONbits.SLPEN == 0)
	{
		picUnlock();
		OSCCONSET = _OSCCON_SLPEN_MASK;		/* Set sleep bit			*/
		picLock();
	}

	wakeupVec = 0;
	sleeping = TRUE;
    IEC0CLR = _IEC0_T1IE_MASK;			/* Turn off ms int				*/
	U1MODESET = _U1MODE_WAKE_MASK;		/* Set UARTS to wake from sleep	*/
	U2MODESET = _U2MODE_WAKE_MASK;

	for (slpStart = clkTod; 
		 ((clkTod < (slpStart + secs)) && (wakeupVec == 0)); )
	{
		_wait();						/* Sleep until done or UART char */
		if (needRTCAck)					/* If woke due to RTC			*/
			RTCack();					/* Ack it						*/
	}

	U1MODECLR = _U1MODE_WAKE_MASK;		/* Clear UART wakeups			*/
	U2MODECLR = _U2MODE_WAKE_MASK;
	sleeping = FALSE;					/* Show we're no longer sleeping*/
	wakeupVec = 0;						/* Clear wakeup vec				*/

    IEC0SET = _IEC0_T1IE_MASK;			/* Re-enable ms int				*/
#endif
}


/************************************************************************/
/* Function    : clkIdle												*/
/* Purpose     : Put CPU into idle mode for n ticks						*/
/* Input       : Number of ticks to sleep								*/
/* Output      : None                                                   */
/************************************************************************/
void clkIdle(Nat32 ticks)
{
	Nat32		idleStart;

	if (OSCCONbits.SLPEN)
	{
		picUnlock();
		OSCCONCLR = _OSCCON_SLPEN_MASK;	/* Clear sleep bit				*/
		picLock();
	}

	wakeupVec = 0;
	sleeping = TRUE;

	for (idleStart = clkTick; 
		 ((clkTick < (idleStart + ticks)) && (wakeupVec == 0)); )
	{
		_wait();						/* Sleep until done or UART char */
		if (needRTCAck)					/* If woke due to RTC			*/
			RTCack();					/* Ack it						*/
	}

	sleeping = FALSE;					/* Show we're no longer sleeping*/
	wakeupVec = 0;						/* Clear wakeup vec				*/
}


/************************************************************************/
/* Function    : clkSleeping											*/
/* Purpose     : Return sleep status of CPU								*/
/* Input       : None                                                   */
/* Output      : TRUE if CPU was sleeping when interrupt occurred		*/
/* Comment     : Only useful from interrupt that may wake us from sleep	*/
/************************************************************************/
MBool clkSleeping(void)
{
	return(sleeping);
}


/************************************************************************/
/* Function    : clkSetWakeupVec										*/
/* Purpose     : Set a bit in wakeup vector								*/
/* Input       : Vector with one bit set								*/
/* Output      : None													*/
/************************************************************************/
void clkSetWakeupVec(Nat32 bitVec)
{
	if (sleeping)
		wakeupVec |= bitVec;
	
}

/************************************************************************/
/* Function    : RTCack                                                 */
/* Purpose     : Acknowledge RTC int									*/
/* Input       : None                                                   */
/* Output      : None                                                   */
/************************************************************************/
void RTCack(void)
{
	time_t	tod;
	Byte ack = 0x00;

	rtcAckPinOn();						/* For debugging				*/
    IEC0CLR = _IEC0_INT4IE_MASK;		/* Turn off RTC int				*/
										/* Clear the int in the RTC     */
	rtcIsBroken = (rtcWriteRegs(RTC_CTRLSTAT, 1, &ack) != OK);
	needRTCAck = FALSE;

	if (!rtcIsBroken &&
		(!trackingRTC || ((clkTod - lastRTC) > 30)))
	{									/* Re-read RTC every 10 minutes	*/
		rtcUpdatePinOn();				/*  so we periodically re-read time*/
		tod = rtcTime(NULL);
		if (tod == ERROR)
			rtcIsBroken = TRUE;
		else
		{
			clkTod = lastRTC = tod;
			trackingRTC = TRUE;
		}
		rtcUpdatePinOff();
	}

    IEC0SET = _IEC0_INT4IE_MASK;		/* RTC int back on				*/
	rtcAckPinOff();						/* For debugging				*/
}


/************************************************************************/
/**********          ISRs and related funcs                     *********/
/************************************************************************/

/************************************************************************/
/* Function    : tickIntHandler                                         */
/* Purpose     : Interrupt handler for Timer1 (clock ticker)			*/
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void __attribute__( (interrupt(IPL1AUTO), vector(_TIMER_1_VECTOR)) ) _tickIntHandler(void)
{
	tickPinOn();
	clkMs += MS_PER_TICK;
	clkTick++;

	if (clkMs > 1020)
		trackingRTC = FALSE;

	if (!trackingRTC && (clkMs >= 1000))
	{
		clkTod++;
		clkMs -= 1000;
	}

	IFS0CLR = _IFS0_T1IF_MASK;        /* clear the interrupt flag     */
	tickPinOff();
}


/************************************************************************/
/* Function    : rtcIntHandler                                        	*/
/* Purpose     : Interrupt handler for External Int 4 (RTC)				*/
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void __attribute__( (interrupt(IPL2AUTO), vector(_EXTERNAL_4_VECTOR)) ) _rtcIntHandler(void)
{
    rtcPinOn();
	clkMs = 0;
	clkTod++;
	needRTCAck = TRUE;
	IFS0CLR = _IFS0_INT4IF_MASK;        /* clear the interrupt flag     */
    rtcPinOff();
}
