/****************************************************************************/
/* 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 <mbariConst.h>			/* MBARI constants		    */
#include <beds.h>			/* BEDS controller definitions	    */
#include <clock.h>			/* BEDS Clock module		    */
#include <io.h>				/* BEDS I/O definitions		    */
#include <spi.h>			/* BEDS SPI definitions		    */
#include <syslog.h>			/* System logger		    */

#include <cfxpico.h>			/* Persistor PicoDOS definitions    */
#include <stdio.h>
#include <string.h>
#include <time.h>


/********************************/
/*	External Data		*/
/********************************/

Extern WhatWokeUs wakeupStatus;
Extern MBool	  initPicoClk;


/********************************/
/*	Global Data		*/
/********************************/

Global volatile time_t	accurateTod;	    /* DS3234 time of day	*/
Global struct tm	accurateTm;	    /* DS3234 time of day as struct tm*/
Global volatile Nat32	accurateClkSec = 0; /* Num ints from DS3234	*/


/********************************/
/*	Module Local Data	*/
/********************************/

MLocal time_t	intTod = 0;
MLocal Int32	systemMsOffset = 0; /* Offset of Persistor clock from*/
				    /* accurate clock, in ms	*/
MLocal Nat16	intTick;
MLocal gtfptr	picoRTCGetTime = NULL;

IEV_C_PROTO(clkIntHandler);


/************************************************************************/
/* Function    : printAccurateTime					*/
/* Purpose     : Print time for the "TIME" and "DATE" commands		*/
/* Input       : None							*/
/* Outputs     : None							*/
/************************************************************************/
MLocal Void printAccurateTime(Void)
{
    time_t	seconds;
    Nat16	ms;
    struct tm	*tmp;
    char	buf[256];

    clkGetTime(&seconds, &ms);
    tmp = localtime(&seconds);
    strftime(buf, sizeof(buf), "%A %B %d, %Y %I:%M:%S", tmp);
    printf("\nAccurate time is: %s.%03hu %s\n", 
	   buf, ms, (tmp->tm_hour >= 12) ? "pm" : "am" );
}


/************************************************************************/
/* Function    : printTimeDiff						*/
/* Purpose     : Print difference between Pico and accurate time, to console*/
/* Input       : None							*/
/* Outputs     : None							*/
/************************************************************************/
MLocal Void printTimeDiff(Void)
{
    Int32 offset = systemMsOffset;

    if (offset >= 0)
	printf("\nPersistor clock is %0ld.%03ld seconds ahead of accurate clock\n",
	       offset/1000, offset%1000);
    else
    {
	offset = -offset;
	printf("\nPersistor clock is %0ld.%03ld seconds behind accurate clock\n",
	       offset/1000, offset%1000);
    }
}


/************************************************************************/
/* Function    : BEDSTimeCmd						*/
/* Purpose     : Replaces "TIME" command-line command			*/
/* Input       : CmdInfoPtr						*/
/* Outputs     : char *							*/
/************************************************************************/
char *BEDSTimeCmd(CmdInfoPtr cip)
{
    char  *rtn;
	
    printAccurateTime();
    printTimeDiff();
    return(NULL);
}


/************************************************************************/
/* Function    : BEDSDateCmd						*/
/* Purpose     : Replaces "DATE" command-line command			*/
/* Input       : CmdInfoPtr						*/
/* Outputs     : char *							*/
/************************************************************************/
char *BEDSDateCmd(CmdInfoPtr cip)
{
    printAccurateTime();
    printTimeDiff();
    return(NULL);
}


/************************************************************************/
/* Function    : BEDSTimeOffsetCmd					*/
/* Purpose     : Implements "TIMEDIFF" command-line command		*/
/* Input       : CmdInfoPtr						*/
/* Outputs     : char *							*/
/************************************************************************/
char *BEDSTimeOffsetCmd(CmdInfoPtr)
{
    printTimeDiff();
    printf("systemMsOffset = %ld\n", systemMsOffset);
    printf("accurateClkSec = %lu\n", accurateClkSec);

    return(NULL);
}


/************************************************************************/
/* Function    : BEDSSetPicoClkFromAccurateClk				*/
/* Purpose     : Implements "SetPClk" command-line command		*/
/* Input       : CmdInfoPtr						*/
/* Outputs     : char *							*/
/************************************************************************/
char *BEDSSetPicoClkFromAccurateClk(CmdInfoPtr)
{
    cprintf("\nSetting PicoDOS RTC from accurate RTC\n");

    RTCSetTime(clkGetAccurateTime(), 0);

    execstr("time");
    return(NULL);
}

/************************************************************************/
/* Function    : BEDSSetAccurateClkFromPicoClk				*/
/* Purpose     : Implements "SetAClk" command-line command		*/
/* Input       : CmdInfoPtr						*/
/* Outputs     : char *							*/
/************************************************************************/
char *BEDSSetAccurateClkFromPicoClk(CmdInfoPtr)
{
    cprintf("\nSetting accurate RTC from PicoDOS RTC\n");
    clkSetAccurateTime(RTCtime(NULL));
    RTCDelayMicroSeconds(1000000);
    clkCalcTimeOffset();

    execstr("time");
    return(NULL);
}


/************************************************************************/
/* 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)
{
    Nat32	seconds;
    Nat16	ticks;
    Int32	ms;

    RTCGetTime(&seconds, &ticks);

//    printf("\nsystemMsOffset = %ld  seconds = %lu\n", systemMsOffset, seconds);

/* Convert ticks to milliseconds, and subtract the difference from accurate clock*/
    ms = (ticks + RTC_TICKS_PER_MS/2)/RTC_TICKS_PER_MS;
    ms -= systemMsOffset;

/* Now normalize to seconds and ms */
    seconds += ms/1000;
    ms %= 1000;

/* C's mod (%) operator can leave a negative remainder!  This is mathematically wrong! */
    while (ms < 0)
    {
	ms += 1000;
	seconds--;
    }

    if (secondp)
	*secondp = seconds;

    if (msp)
	*msp = ms;

    return(seconds);

} /* clkGetTime() */


/************************************************************************/
/* Function    : toBCD							*/
/* Purpose     : Convert binary Byte to BCD				*/
/* Input       : Input byte						*/
/* Outputs     : BCD Result						*/
/* Comments    : Works for input range of 00-99				*/
/*		 Multiples of 100 get stripped, e.g. 167 converts to 0x67*/
/************************************************************************/
MLocal Nat16 toBCD(Nat16 inw)
{
    return((((inw/10)%10) << 4) | (inw%10));
}


/************************************************************************/
/* Function    : fromBCD						*/
/* Purpose     : Convert from BCD to binary				*/
/* Input       : BCD byte						*/
/* Outputs     : Result							*/
/************************************************************************/
MLocal Nat16 fromBCD(Nat16 bcd)
{
    return(10*((bcd >> 4) & 0xf) + (bcd & 0xf));
}


/************************************************************************/
/* Function    : clkSetAccurateTime					*/
/* Purpose     : Set the DS3231 Highly Accurate Clock			*/
/* Input       : Ptrs to seconds, ticks					*/
/* Outputs     : None							*/
/************************************************************************/
Void clkSetAccurateTime(time_t now)
{
    Nat16	clkRegs[7];
    struct tm	*tmp = gmtime(&now);

    clkRegs[0] = toBCD(tmp->tm_sec) & 0x7f;
    clkRegs[1] = toBCD(tmp->tm_min) & 0x7f;
    clkRegs[2] = toBCD(tmp->tm_hour) & 0x3f;
    clkRegs[3] = (tmp->tm_wday & 7) + 1;
    clkRegs[4] = toBCD(tmp->tm_mday) & 0x3f;
    clkRegs[5] = toBCD(tmp->tm_mon + 1) & 0x1f;
    clkRegs[6] = toBCD(tmp->tm_year % 100);
    spiWriteMultRTCRegs(RTC_SEC, 7, clkRegs);

} /* clkSetAccurateTime() */


/************************************************************************/
/* Function    : clkGetAccurateTime					*/
/* Purpose     : Read Time of Day from the DS3234 Accurate RTC		*/
/* Input       : None							*/
/* Outputs     : Time of Day						*/
/* Side Effects: Fills in accurateTod and accurateTm with DS3234 time	*/
/************************************************************************/
time_t clkGetAccurateTime(Void)
{
    Nat16	clkRegs[7];

    spiReadMultRTCRegs(RTC_SEC, 7, clkRegs);
    accurateTm.tm_sec = fromBCD(clkRegs[0]);
    accurateTm.tm_min = fromBCD(clkRegs[1]);
    accurateTm.tm_hour = fromBCD(clkRegs[2]);
    accurateTm.tm_mday = fromBCD(clkRegs[4]);
    accurateTm.tm_mon = fromBCD(clkRegs[5]) - 1;
    accurateTm.tm_year = fromBCD(clkRegs[6]) + 100;
    accurateTm.tm_wday = 0;
    accurateTm.tm_yday = 0;
    accurateTm.tm_isdst = 0;
    accurateTod = mktime(&accurateTm);
    return(accurateTod);

} /* clkGetAccurateTime() */


/************************************************************************/
/* Function    : clkInitializeAccurateTime				*/
/* Purpose     : Initialize and range-check  time of day from the DS3234*/
/* Input       : None							*/
/* Outputs     : TRUE if checks OK, else FALSE				*/
/* Side Effects: Fills in accurateTod and accurateTm with DS3234 time	*/
/************************************************************************/
MLocal MBool clkInitializeAccurateTime(Void)
{
    Nat16	clkRegs[7];

    spiReadMultRTCRegs(RTC_SEC, 7, clkRegs);
    accurateTm.tm_sec = fromBCD(clkRegs[0]);
    accurateTm.tm_min = fromBCD(clkRegs[1]);
    accurateTm.tm_hour = fromBCD(clkRegs[2]);
    accurateTm.tm_mday = fromBCD(clkRegs[4]);
    accurateTm.tm_mon = fromBCD(clkRegs[5]) - 1;
    accurateTm.tm_year = fromBCD(clkRegs[6]) + 100;
    if ((accurateTm.tm_mon > 11) || (accurateTm.tm_mday < 1) ||
	(accurateTm.tm_mday > 31) || (accurateTm.tm_hour > 23) ||
	(accurateTm.tm_min >= 60) || (accurateTm.tm_sec > 60))
    {
	return(FALSE);
    }

    accurateTm.tm_wday = 0;
    accurateTm.tm_yday = 0;
    accurateTm.tm_isdst = 0;
    if ((accurateTod = mktime(&accurateTm)) == (time_t)(-1))
	return(FALSE);

    systemMsOffset = 0;
    return(TRUE);

} /* clkInitializeAccurateTime() */


/************************************************************************/
/* Function    : clkCalcTimeOffset					*/
/* Purpose     : Called from main thread to calc offset between clocks	*/
/* Input       : None							*/
/* Outputs     : None							*/
/* Side Effects: Fills in systemMsOffset				*/
/* The DS3234 clock int fills in intTod and intTick, indicating the	*/
/* Persistor's clock time when the DS3234 ticks a new second.  Then	*/
/* the foreground task must call this function within one second, before*/
/* the DS3234 has time to tick to a new second.  Here, we get the	*/
/* accurate time and subtract it from system time in intTod and intTick.*/
/* The check for non-zero intTod prevents us from using a stale tod.	*/
/************************************************************************/
Void clkCalcTimeOffset(Void)
{
    if (intTod)
	systemMsOffset = (1000 * (intTod - clkGetAccurateTime())) +
	    ((intTick + RTC_TICKS_PER_MS/2)/RTC_TICKS_PER_MS);

    intTod = 0;				/* Make sure we don't do again	*/
					/* until next clk int		*/
} /* clkCalcTimeOffset() */


/************************************************************************/
/* Function    : clkGetTimeOffset					*/
/* Purpose     : Return offset of Persistor clock from accurate clock	*/
/* Input       : None							*/
/* Outputs     : offset of Persistor clock from accurate clock, in ms	*/
/************************************************************************/
Int32 clkGetTimeOffset(Void)
{
    return(systemMsOffset);

} /* clkGetTimeOffset() */


/************************************************************************/
/* Function    : enableClkInt						*/
/* Purpose     : Enable 1-second interrupts from clock chip		*/
/* Input       : None							*/
/* Outputs     : None							*/
/************************************************************************/
Void enableClkInt(Void)
{
    spiWriteRTCReg(RTC_CTRLSTAT, 0x00);    /* Clear any pending ints	*/
    PinBus(IRQ2);			/* Make IRQ2 an interrupt again	*/

} /* enableClkInt() */


/************************************************************************/
/* Function    : disableClkInt						*/
/* Purpose     : Disable 1-second interrupts from clock chip		*/
/* Input       : None							*/
/* Outputs     : None							*/
/************************************************************************/
Void disableClkInt(Void)
{
    PinRead(IRQ2);			/* Make IRQ2 an input		*/
    intTod = 0;				/* disable clkCalcTimeOffset()	*/

} /* disableClkInt() */


/************************************************************************/
/* Function    : clkIntHandler						*/
/* Purpose     : Interrupt Handler for IRQ2 from the RTC clock chip	*/
/* Input       : Implied (IEVStack *ievstack:__a0) parameter		*/
/* Outputs     : None							*/
/* Note        : If foreground is in the middle of setting the time when*/
/*               interrupt fires, we'll get a screwy result for         */
/*               systemMsOffset.  Next 1-sec interrupt will fix it.     */
/************************************************************************/
IEV_C_FUNCT(clkIntHandler)
{
#pragma unused(ievstack)

    PIOSet(CLK_INT_PIN);
    PinIO(IRQ2);		/* Make IRQ2 not an int pin until main	*/
    				/* watch loop can clear the interrupt	*/
    wakeupStatus = WakeIRQ2;
    (*picoRTCGetTime)(&intTod, &intTick);

    accurateClkSec++;		/* Increment second count		*/
    PIOClear(CLK_INT_PIN);

} /* clkIntHandler() */


/************************************************************************/
/* Function    : patchedGetTime						*/
/* Purpose     : RTCGetTime, patched to disable clk ints		*/
/* Input       : Ptrs to hold current seconds and ticks			*/
/* Outputs     : Current seconds					*/
/************************************************************************/
MLocal ulong patchedGetTime(ulong *secs, ushort *ticks)
{
    ulong	curSecs;
    ushort	sr;

    sr = IEVSaveSRThenDisable();	/*No clk ints during RTCGetTime */
    curSecs = (*picoRTCGetTime)(secs, ticks);
    IEVRestoreSavedSR(sr);
    return(curSecs);

} /* patchedGetTime() */


/************************************************************************/
/* Function    : clkInit						*/
/* Purpose     : Initialize clock module, set system time from RTC	*/
/* Input       : None							*/
/* Outputs     : None							*/
/************************************************************************/
Void clkInit()
{
    Nat16	clkRegs[4];
    time_t	now;

    disableClkInt();			/* Set IRQ2 as input for now	*/
    PIOClear(CLK_INT_PIN);

    if (clkInitializeAccurateTime())
    {
	if (initPicoClk)
	    RTCSetTime(accurateTod, 0);
    }
    else
    {
	sysLog("ERROR!!!  Bogus time in accurate RTC!!!  Setting time from PicoDOS clock.");
	clkSetAccurateTime(RTCtime(NULL));
    }

	// Set Alarm Regs to once/second
    clkRegs[0] = clkRegs[1] = clkRegs[2] = clkRegs[3] = 0x80;
    spiWriteMultRTCRegs(RTC_ALM1_SEC, 4, clkRegs);
    spiWriteRTCReg(RTC_CTRL, 5);
    spiWriteRTCReg(RTC_CTRLSTAT, 0x08);

    clkGetAccurateTime();
    RTCGetTime(&intTod, &intTick);
    clkCalcTimeOffset();

    /* Insert the Int vector and RTCGetTime handler	*/
    IEVInsertCFunct(&clkIntHandler, level2InterruptAutovector);
    picoRTCGetTime = BIOSPatchInsert(RTCGetTime, patchedGetTime);

    /* Start up the clock interrupts */
    enableClkInt();

} /* clkInit() */


/************************************************************************/
/* Function    : clk_printtime						*/
/* Purpose     : Print date & time to serial port			*/
/* Input       : time_t							*/
/* Outputs     : None							*/
/************************************************************************/
Void clk_printtime(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);

} /* clk_printtime() */
