/****************************************************************************/
/* Copyright 2010 MBARI.                                                    */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
#include "rtcc.h"
#include "p24Fxxxx.h"
#include "sys_defs.h"

#include <stdio.h>
#include <string.h>

#define HALFSEC         0x0800
#define RTCSYNC         0x1000

#define MAX_RTC_READS   3

static unsigned int decToBcd(int hi_byte, unsigned int reg);
static unsigned int bcdToDec(int hi_byte, unsigned int reg);

void rtcInit()
{
    unsigned char oscon_byte;
    
    /* enable secondary oscillator */
    oscon_byte = OSCCONL | 0x02;
    __builtin_write_OSCCONL(oscon_byte);
    OSCCONbits.OSWEN = 1;    

    /* enable the realtime clock */
    __builtin_write_RTCWEN();
    RCFGCALbits.RTCEN = 1;
    RCFGCALbits.RTCWREN = 0;
}

int rtcSet(DateTime* dt)
{
    int good_write;
    unsigned int min_sec;
    unsigned int wd_hour;
    unsigned int mon_day;
    unsigned int year;

    min_sec = decToBcd(TRUE, dt->minutes);
    min_sec += decToBcd(FALSE, dt->seconds);

    wd_hour = decToBcd(TRUE, dt->day_of_week);
    wd_hour += decToBcd(FALSE, dt->hours);

    mon_day = decToBcd(TRUE, dt->month);
    mon_day += decToBcd(FALSE, dt->day);

    year = decToBcd(FALSE, dt->year);

    /* enable the realtime clock */
    __builtin_write_RTCWEN();

    /* read minute/second and store */
    RCFGCALbits.RTCPTR = 0x00;
    RTCVAL = min_sec;

    /* read weekday/hour and store */
    RCFGCALbits.RTCPTR = 0x01;
    RTCVAL = wd_hour;

    /* read month/day and store */
    RCFGCALbits.RTCPTR = 0x02;
    RTCVAL = mon_day;

    /* read year and store */
    RCFGCALbits.RTCPTR = 0x03;
    RTCVAL = year;

    /* assume a good write */
    good_write = TRUE;
    
    /* write minute/second and compare */
    RCFGCALbits.RTCPTR = 0x00;
    if ( min_sec != RTCVAL )
        good_write = FALSE;

    /* write weekday/hour and compare */
    RCFGCALbits.RTCPTR = 0x01;
    if ( wd_hour != RTCVAL )
        good_write = FALSE;

    /* write month/day and compare */
    RCFGCALbits.RTCPTR = 0x02;
    if ( mon_day != RTCVAL )
        good_write = FALSE;

    /* write year and compare */
    RCFGCALbits.RTCPTR = 0x03;
    if ( year != RTCVAL )
        good_write = FALSE;
    
    /* disable clock register writes */
    RCFGCALbits.RTCWREN = 0;
    
    return good_write;
}

int rtcRead(DateTime* dt)
{
    int i;
    int good_read;
    unsigned int min_sec;
    unsigned int wd_hour;
    unsigned int mon_day;
    unsigned int year;

    /* enable the realtime clock */
    __builtin_write_RTCWEN();

    for (i = 0; i < MAX_RTC_READS; ++i )
    {
        /* read minute/second and store */
        RCFGCALbits.RTCPTR = 0x00;
        min_sec = RTCVAL;

        /* read weekday/hour and store */
        RCFGCALbits.RTCPTR = 0x01;
        wd_hour = RTCVAL;

        /* read month/day and store */
        RCFGCALbits.RTCPTR = 0x02;
        mon_day = RTCVAL;

        /* read year and store */
        RCFGCALbits.RTCPTR = 0x03;
        year = RTCVAL;

        /* read half second bit */
        if ( RCFGCAL & 0x0800 )
            dt->half_second = 1;
        else
            dt->half_second = 0;

        /* assume a good read */
        good_read = TRUE;
        
        /* re-read minute/second and compare */
        RCFGCALbits.RTCPTR = 0x00;
        if ( min_sec != RTCVAL )
            good_read = FALSE;

        /* re-read weekday/hour and compare */
        RCFGCALbits.RTCPTR = 0x01;
        if ( wd_hour != RTCVAL )
            good_read = FALSE;

        /* re-read month/day and compare */
        RCFGCALbits.RTCPTR = 0x02;
        if ( mon_day != RTCVAL )
            good_read = FALSE;

        /* re-read year and compare */
        RCFGCALbits.RTCPTR = 0x03;
        if ( year != RTCVAL )
            good_read = FALSE;

        /* if the registers read the same twice in a row, they're good */
        if ( good_read )
            break;
    }

    /* disable clock register writes */
    RCFGCALbits.RTCWREN = 0;

    /* if it was a good read convert the BCD vals to 
    struct vals, otherwise zero out the struct vals */
    if ( good_read )
    {
        dt->year = bcdToDec(FALSE, year);

        dt->month = bcdToDec(TRUE, mon_day);
        dt->day = bcdToDec(FALSE, mon_day);

        dt->day_of_week = bcdToDec(TRUE, wd_hour);
        dt->hours = bcdToDec(FALSE, wd_hour);

        dt->minutes = bcdToDec(TRUE, min_sec);
        dt->seconds = bcdToDec(FALSE, min_sec);
    }
    else
    {
        dt->year = 0;
        dt->month = 0;
        dt->day = 0;
        dt->day_of_week = 0;
        dt->hours = 0;
        dt->minutes = 0;
        dt->seconds = 0;
        dt->half_second = 0;
    }

    return good_read;
}

int rtcDateToString(DateTime* dt, char* date_str)
{
    char buff[8];

    switch ( dt->day_of_week )
    {
        case 0: strcpy(date_str, "Sunday, "); break;
        case 1: strcpy(date_str, "Monday, "); break;
        case 2: strcpy(date_str, "Tuesday, "); break;
        case 3: strcpy(date_str, "Wednesday, "); break;
        case 4: strcpy(date_str, "Thursday, "); break;
        case 5: strcpy(date_str, "Friday, "); break;
        case 6: strcpy(date_str, "Saturday, "); break;
        default: strcpy(date_str, "[???]day, "); break;
    }
    
    switch ( dt->month )
    {
        case 1: strcat(date_str, "January "); break;
        case 2: strcat(date_str, "February "); break;
        case 3: strcat(date_str, "March "); break;
        case 4: strcat(date_str, "April "); break;
        case 5: strcat(date_str, "May "); break;
        case 6: strcat(date_str, "June "); break;
        case 7: strcat(date_str, "July "); break;
        case 8: strcat(date_str, "August "); break;
        case 9: strcat(date_str, "September "); break;
        case 10: strcat(date_str, "October "); break;
        case 11: strcat(date_str, "November "); break;
        case 12: strcat(date_str, "December "); break;
        default: strcat(date_str, "[???]month "); break;
    }

    sprintf(buff, "%02d, ", dt->day);
    strcat(date_str, buff);

    sprintf(buff, "20%02d", dt->year);
    strcat(date_str, buff);

    return TRUE;
}

int rtcTimeToString(DateTime* dt, char* time_str)
{
    sprintf(time_str, "%02d:%02d:%02d", dt->hours, dt->minutes, dt->seconds);
    return TRUE;
}

static unsigned int decToBcd(int hi_byte, unsigned int reg)
{
    unsigned int bcd_val;
    unsigned char hi_nib, lo_nib;

    /* grab the nibbles */
    hi_nib = reg / 10; 
    lo_nib = reg - (hi_nib * 10);

    /* convert the nibbles to a bcd value */
    bcd_val = hi_nib;
    bcd_val <<= 4;
    bcd_val += lo_nib;

    /* grab the hi_byte or low byte */
    if ( hi_byte )
        bcd_val <<= 8;
    
    return bcd_val;
}

static unsigned int bcdToDec(int hi_byte, unsigned int reg)
{
    unsigned int dec_val, bcd_val;
    unsigned char hi_nib, lo_nib;

    /* grab the hi_byte or low byte */
    if ( hi_byte )
    {
        bcd_val = (0xFF00 & reg);
        bcd_val >>= 8;
    }
    else
    {
        bcd_val = (0x00FF & reg);
    }

    /* grab the nibbles */
    hi_nib = bcd_val & 0xF0; 
    hi_nib >>= 4;

    lo_nib = bcd_val & 0x0F;

    /* convert the nibbles to decimal */
    dec_val = hi_nib;
    dec_val *= 10;
    dec_val += lo_nib;

    return dec_val;
}

