/****************************************************************************/
/* Copyright 2003-2018 MBARI                                                */
/****************************************************************************/
/* Summary  : Support for DS3234 SPI RTC chip for BEDS2 on PIC32MX470F512L	*/
/* Filename : rtc.c                                                         */
/* Author   : Robert Herlien (rah)                                          */
/* Project  : OASIS Mooring Replacement (OASIS5)                            */
/* Revision: 1.0                                                            */
/* Created  : 02/25/2016                                                    */
/*                                                                          */
/* 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:                                                    */
/* 25feb2016 rah - created                                                  */
/* 20feb2018 rah - Ported to BEDS2											*/
/* $Log$
 */
/****************************************************************************/

#include <xc.h>
#include <beds.h>
#include <rtc.h>
#include <spi.h>
#include <clock.h>

#include <stdio.h>
#include <time.h>
#include <string.h>						/* For memset					*/


/************************************************************************/
/* Function	   : enableRTCInt											*/
/* Purpose	   : Enable 1-second RTC Interrupt							*/
/* Input	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
void enableRTCInt(void)
{
	Byte ack = 0x00;

	rtcWriteRegs(RTC_CTRLSTAT, 1, &ack);
    IEC0SET = _IEC0_INT4IE_MASK;			/* Turn on RTC int			*/

} /* enableRTCInt() */


/************************************************************************/
/* Function    : rtcReadRegs											*/
/* Purpose     : Read One or More RTC Registers							*/
/* Input       : Register address, num registers to read,  ptr to result*/
/* Outputs     : OK or Error number                                     */
/************************************************************************/
Errno	rtcReadRegs(Nat32 addr, int numRegs, Byte *rdat)
{
	Byte	addrReg, dummy;
	Byte	wrRegs[16];

	if (numRegs > 16)
		return(ERROR);

	clkIntsOff();							/* Turn off tick and RTC ints*/
	spiSelectRTC();							/* Turn on SPI1 and select RTC*/
	memset(wrRegs, 0, sizeof(wrRegs));

	addrReg = (Byte)(addr & 0x7f);			/* Set addr, clr r/w bit	*/
	spi1WriteRead(&addrReg, &dummy, 1);		/* Write addr				*/
	spi1WriteRead(wrRegs, rdat, numRegs);	/* Read result				*/

	spi1Unselect();							/* De-assert /CS on RTC		*/
	clkIntsOn();							/* Tick & RTC ints back on	*/

    return(OK);
}


/************************************************************************/
/* Function    : rtcWriteRegs											*/
/* Purpose     : Write One or More RTC Registers						*/
/* Input       : Register address, num regs, ptr to data				*/
/* Outputs     : OK or Error number                                     */
/************************************************************************/
Errno	rtcWriteRegs(Nat32 addr, int numRegs, Byte *wdat)
{
	Byte	addrReg;
	Byte	rdRegs[16];

	if (numRegs > 16)
		return(ERROR);

	clkIntsOff();							/* Turn off tick and RTC ints*/
	spiSelectRTC();							/* Turn on SPI1 and select RTC*/

	addrReg = (Byte)((addr & 0x7f) | 0x80);	/* Set addr and r/w bit		*/
	spi1WriteRead(&addrReg, rdRegs, 1);		/* Write addr				*/
	spi1WriteRead(wdat, rdRegs, numRegs);	/* Write registers			*/


	spi1Unselect();							/* De-assert /CS on RTC		*/
	clkIntsOn();							/* Tick & RTC ints back on	*/

    return(OK);
}


/************************************************************************/
/* 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*/
/************************************************************************/
Nat16 toBCD(Nat16 inw)
{
    return((((inw/10)%10) << 4) | (inw%10));
}


/************************************************************************/
/* Function    : fromBCD                                                */
/* Purpose     : Convert from BCD to binary                             */
/* Input       : BCD byte                                               */
/* Outputs     : Result                                                 */
/************************************************************************/
Nat16 fromBCD(Nat16 bcd)
{
    return(10*((bcd >> 4) & 0xf) + (bcd & 0xf));
}


/************************************************************************/
/* Function    : rtcSetTime                                             */
/* Purpose     : Set the DS3232 Highly Accurate Clock                   */
/* Input       : time_t for new time                                    */
/* Outputs     : OK or ERROR											*/
/************************************************************************/
Errno rtcSetTime(time_t newTime)
{
    Byte        clkRegs[7];
    struct tm   *tmp = gmtime(&newTime);

    if ((tmp->tm_mon > 11) || (tmp->tm_mday < 1) ||
        (tmp->tm_mday > 31) || (tmp->tm_hour > 23) ||
        (tmp->tm_min >= 60) || (tmp->tm_sec > 60))
        return(ERROR);
    
    clkRegs[0] = (Byte)(toBCD(tmp->tm_sec) & 0x7f);
    clkRegs[1] = (Byte)(toBCD(tmp->tm_min) & 0x7f);
    clkRegs[2] = (Byte)(toBCD(tmp->tm_hour) & 0x3f);
    clkRegs[3] = (Byte)((tmp->tm_wday & 7) + 1);
    clkRegs[4] = (Byte)(toBCD(tmp->tm_mday) & 0x3f);
    clkRegs[5] = (Byte)(toBCD(tmp->tm_mon + 1) & 0x1f);
    clkRegs[6] = (Byte)(toBCD(tmp->tm_year % 100));

    return(rtcWriteRegs(RTC_SEC, 7, clkRegs));

} /* rtcSetTime() */


/************************************************************************/
/* Function    : rtcSetAlarm1											*/
/* Purpose     : Set Alarm1 in the DS3232 Highly Accurate Clock			*/
/* Input       : time_t for alarm time, 0 to set alarm once/sec			*/
/* Outputs     : OK or ERROR											*/
/************************************************************************/
Errno rtcSetAlarm1(time_t almTime)
{
	Errno		rtn;
    Byte        clkRegs[4], ctrlRegs[2];
    struct tm   *tmp;

	if (almTime)
	{
		tmp = gmtime(&almTime);
		if ((tmp->tm_mon > 11) || (tmp->tm_mday < 1) ||
			(tmp->tm_mday > 31) || (tmp->tm_hour > 23) ||
			(tmp->tm_min >= 60) || (tmp->tm_sec > 60))
			return(ERROR);

		clkRegs[0] = (Byte)(toBCD(tmp->tm_sec) & 0x7f);
		clkRegs[1] = (Byte)(toBCD(tmp->tm_min) & 0x7f);
		clkRegs[2] = (Byte)(toBCD(tmp->tm_hour) & 0x3f);
		clkRegs[3] = 0x80;
    }
	else
	{
		clkRegs[0] = clkRegs[1] = clkRegs[2] = clkRegs[3] = 0x80;
	}

	ctrlRegs[0] = 4;
    if ((rtn = rtcWriteRegs(RTC_CTRL, 1, ctrlRegs)) != OK)
		return(rtn);

	clkDelayUs(50);
    if ((rtn = rtcWriteRegs(RTC_ALM1_SEC, 4, clkRegs)) != OK)
		return(rtn);
	
	clkDelayUs(50);
	ctrlRegs[0] = 5;
	ctrlRegs[1] = 0;
    return(rtcWriteRegs(RTC_CTRL, 2, ctrlRegs));

} /* rtcSetAlarm1() */


/************************************************************************/
/* Function    : rtcTime                                                */
/* Purpose     : Read Time of Day from the DS3232 Accurate RTC          */
/* Input       : Pointer to struct tm                                   */
/* Outputs     : RTC Time of Day                                        */
/* Comment     : Fills in *tmPtr if non-NULL                            */
/************************************************************************/
time_t  rtcTime(struct tm *tmPtr)
{
    Byte        clkRegs[7];
    struct tm	accurateTm;
    time_t      accurateTod;

    if (tmPtr == NULL)
        tmPtr = &accurateTm;

	memset(clkRegs, 0, sizeof(clkRegs));

    if (rtcReadRegs(RTC_SEC, 7, clkRegs) == OK)
	{
		tmPtr->tm_sec = fromBCD(clkRegs[0]);
		tmPtr->tm_min = fromBCD(clkRegs[1]);
		tmPtr->tm_hour = fromBCD(clkRegs[2]);
		tmPtr->tm_mday = fromBCD(clkRegs[4]);
		tmPtr->tm_mon = fromBCD(clkRegs[5]) - 1;
		tmPtr->tm_year = fromBCD(clkRegs[6]) + 100;
		tmPtr->tm_wday = 0;
		tmPtr->tm_yday = 0;
		tmPtr->tm_isdst = 0;
		accurateTod = mktime(tmPtr);
		return(accurateTod);
	}

	return(ERROR);

} /* rtcTime() */
