/****************************************************************************/
/* Copyright 2003-2016 MBARI                                                */
/****************************************************************************/
/* Summary  : Support for DS3232 I2C RTC chip for OASIS5 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                                                  */
/* $Log: rtc.c,v $
/* Revision 1.1  2016/05/09 17:18:26  bobh
/* First rev of dbgTest ported to FreeRTOS
/*
/* Revision 1.1  2016/03/23 23:45:43  bobh
/* Initial revision of dbgTest.MX470
/*
 */
/****************************************************************************/

#include <xc.h>
#include <mbariTypes.h>
#include "rtc.h"
#include "i2c.h"
#include "timer.h"

#include <stdio.h>
#include <time.h>


/************************************************************************/
/* Function    : rtcInit                                                */
/* Purpose     : Initialize RTC module                                  */
/* Input       : None                                                   */
/* Outputs     : None                                                   */
/* Side Effects: Initializes I2C Bus 1, as RTC is only unit on I2C1     */
/************************************************************************/
void rtcInit(void)
{
    I2C1ADD = 0x68;             /* I2C address for DS3232 RTC chip      */
    i2cInit();
}


/************************************************************************/
/* Function    : earlyAbort						*/
/* Purpose     : Error return						*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/************************************************************************/
static Errno earlyAbort(Errno errCode)
{
    i2cStop();
    return(errCode);

} /* earlyAbort() */


/************************************************************************/
/* Function    : rtcReadReg                                             */
/* Purpose     : Read One RTC Register                                  */
/* Input       : Register address, ptr to result                        */
/* Outputs     : OK or Error number                                     */
/************************************************************************/
Errno	rtcReadReg(Nat16 addr, Byte *rdat)
{
    Errno       rtn;

    if ((rtn = i2cStart()) != I2C_SUCCESS)
        return(rtn);

    if ((rtn = i2cSendByte(RTC_ADDR_WR)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    if ((rtn = i2cSendByte(addr)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    if ((rtn = i2cRestart()) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    if ((rtn = i2cSendByte(RTC_ADDR_RD)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    i2cClrRcv();

    if ((rtn = i2cRcvByte(rdat)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    rtn = i2cSendNack();

    i2cStop();
    return(rtn);
}


/************************************************************************/
/* Function    : rtcWriteReg                                            */
/* Purpose     : Write One RTC Register                                 */
/* Input       : Register address, value to write                       */
/* Outputs     : OK or Error number                                     */
/************************************************************************/
Errno	rtcWriteReg(Nat16 addr, Byte wdat)
{
    Errno       rtn;

    if ((rtn = i2cStart()) != I2C_SUCCESS)
        return(rtn);

    if ((rtn = i2cSendByte(RTC_ADDR_WR)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    if ((rtn = i2cSendByte(addr)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    if ((rtn = i2cSendByte(wdat)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    i2cStop();
    return(I2C_SUCCESS);
}


/************************************************************************/
/* Function    : rtcReadMultRegs                                        */
/* Purpose     : Read Multiple RTC Registers                            */
/* Input       : Register address, num registers to read,  ptr to result*/
/* Outputs     : OK or Error number                                     */
/************************************************************************/
Errno	rtcReadMultRegs(Nat16 addr, Nat16 numRegs, Byte *rdat)
{
    int         i;
    Errno       rtn;

    if ((rtn = i2cStart()) != I2C_SUCCESS)
        return(rtn);

    if ((rtn = i2cSendByte(RTC_ADDR_WR)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    if ((rtn = i2cSendByte(addr)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    if ((rtn = i2cRestart()) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    if ((rtn = i2cSendByte(RTC_ADDR_RD)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    i2cClrRcv();

    for (i = 0; i < numRegs-1; i++)
        if ((rtn = i2cRcvByte(rdat+i)) != I2C_SUCCESS)
            return(earlyAbort(rtn));
        else if ((rtn = i2cSendAck()) != I2C_SUCCESS)
            return(earlyAbort(rtn));

    if ((rtn = i2cRcvByte(rdat+i)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    rtn = i2cSendNack();

    i2cStop();
    return(rtn);
}


/************************************************************************/
/* Function    : rtcWriteMultRegs                                       */
/* Purpose     : Write Multiple RTC Registers                           */
/* Input       : Register address, ptr to result                        */
/* Input       : Register address, num registers, ptr to reg data       */
/* Outputs     : OK or Error number                                     */
/************************************************************************/
Errno	rtcWriteMultRegs(Nat16 addr, Nat16 numRegs, Byte *wdat)
{
    int         i;
    Errno       rtn;

    if ((rtn = i2cStart()) != I2C_SUCCESS)
        return(rtn);
    
    if ((rtn = i2cSendByte(RTC_ADDR_WR)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    if ((rtn = i2cSendByte(addr)) != I2C_SUCCESS)
        return(earlyAbort(rtn));

    for (i = 0; i < numRegs; i++)
        if ((rtn = i2cSendByte(wdat[i])) != I2C_SUCCESS)
            return(earlyAbort(rtn));

    i2cStop();
    return(I2C_SUCCESS);
}


/************************************************************************/
/* 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));
    rtcWriteMultRegs(RTC_SEC, 7, clkRegs);

	return(OK);

} /* 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)
{
    Byte        clkRegs[4];
    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;
	}

    rtcWriteReg(RTC_CTRL, 4);
    rtcWriteMultRegs(RTC_ALM1_SEC, 4, clkRegs);
    rtcWriteReg(RTC_CTRL, 5);
    rtcWriteReg(RTC_CTRLSTAT, 0);

	return(OK);

} /* 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;

    rtcReadMultRegs(RTC_SEC, 7, clkRegs);
    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);

} /* rtcTime() */
