/***
 * Driver for the rtc4513 realtime clock
 * Copyright (c) 2003, Inventronik
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Inventronik nor the names of its contributors may be
 * used to endorse or promote products derived from this software without specific
 * prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: rtc4513.c,v 1.1 2005/01/27 18:14:09 jensi Exp $ *
 */
#include "../rtc4513/rtc4513.h"

static void send_rtc_nibble(byte data)
{
    int i;

    for(i=0; i<4; i++) {
        output_bit(RTCDATA, (short)((data>>i)&0x01));
        delay_us(3);
        output_high(RTCCLK);
        delay_us(3);
        output_low(RTCCLK);
        delay_us(3);
    }
}

static byte rcv_rtc_nibble(void)
{
    byte data=0;    
    int i;

    for(i=0; i<4; i++) {
        output_high(RTCCLK);
        delay_us(3);
        data += input(RTCDATA)<<i;
        output_low(RTCCLK);
        delay_us(3);
    }
    return data;
}

static void send_rtc_byte(byte data)
{
    send_rtc_nibble(data);
    send_rtc_nibble(data>>4);
}

static void rtc_write(byte addr, byte *data, int count)
{
    int i;

    output_high(RTCENA);
    send_rtc_nibble(0x03);
    send_rtc_nibble(addr);

    for(i=0; i<count; i++) {
        send_rtc_byte(data[i]);
    }
    output_low(RTCENA);
}

static void rtc_read(byte addr, byte *data, int count)
{
    int i;
    output_high(RTCENA);
    send_rtc_nibble(0x0c);
    send_rtc_nibble(addr);

    for(i=0; i<count; i++) {
        data[i] = rcv_rtc_nibble();
        data[i] |= (rcv_rtc_nibble()<<4);
    }
    output_low(RTCENA);
}

static void rtc_start(short start)
{
    if(start) {
        output_high(RTCENA);
        send_rtc_nibble(0x03);
        send_rtc_nibble(CFREG);
        send_rtc_nibble(0x04);
        output_low(RTCENA);
        delay_us(3);
        output_high(RTCENA);
        send_rtc_nibble(0x03);
        send_rtc_nibble(CDREG);
        send_rtc_nibble(0x02);
        output_low(RTCENA);
    } else {
        output_high(RTCENA);
        send_rtc_nibble(0x03);
        send_rtc_nibble(CFREG);
        send_rtc_nibble(0x07);
        output_low(RTCENA);
    }
}


static byte byte2bcd(byte data)
{
    return ((data/10)<<4) + (data%10);
}

static byte bcd2byte(byte bcd)
{
    return (bcd>>4)*10 + (bcd&0x0f);
}


static void printbin(byte val) 
{
    byte mask=128;
    while(mask) {
        if(mask & val) 
            putc('1');
        else 
            putc('0');
        mask /= 2;
    }
}

/***
 * print all registers
 */
void printall(void)
{
    byte data[8], i;

    rtc_read(SEC1REG, data, 8);
    printf("\r\n");
    for(i=0; i<8; i++) {
        printf("%02d: ", i);
        printbin(data[i]);
        printf("\r\n");
    }
}

/***
 * set time
 */
void settime(byte hrs, byte min, byte sec)
{
    byte data[3];

    data[0] = byte2bcd(sec);
    data[1] = byte2bcd(min);
    data[2] = byte2bcd(hrs);
    rtc_start(0);
    rtc_write(SEC1REG, data, 3);
    rtc_start(1);
}

/***
 * set date
 */
void setdate(byte day, byte month, byte year)
{
    byte data[3];

    data[0] = byte2bcd(day);
    data[1] = byte2bcd(month);
    data[2] = byte2bcd(year);
    rtc_start(0);
    rtc_write(DAY1REG, data, 3);
    rtc_start(1);
}

/***
 * set dow (day-of-the-week)
 * (1-Mo ... 6-Su)
 */
void setdow(byte dow)
{
    rtc_start(0);
    output_high(RTCENA);
    send_rtc_nibble(0x03);
    send_rtc_nibble(DOWREG);
    send_rtc_nibble(dow);
    output_low(RTCENA);
    rtc_start(1);
}

/***
 * returns part of the current time
 *
 * char type - 's'-sec, 'm'-min, 'h'-hrs
 * short new - aquires the current time if new is set
 */
byte gettime(char type, short new)
{
    static byte data[3];

    if(new) {
        do { // read again if fr (carry read flag)
            rtc_read(SEC1REG, data, 3);
        } while(data[1]&0x80 || data[2]&0x80);
//printf("s: 0x%x, 0x%x\r\n", data[0], bcd2byte(data[0]&0x7f));
    }

    switch(type) {
        case 's': return bcd2byte(data[0]&0x7f); // unmask highest bit
        case 'm': return bcd2byte(data[1]);
        case 'h': return bcd2byte(data[2]&0x3f);
    }
    return 0;
}

/***
 * returns part of the current date
 *
 * char type - 'd'-day, 'm'-mon, 'y'-yrs
 * short new - aquires the current time if new is set
 */
byte getdate(char type, short new)
{
    static byte data[3];

    if(new) {
        do { // read again if fr (carry read flag)
            rtc_read(DAY1REG, data, 3);
        } while(data[0]&0x80 || data[1]&0x80);
    }
    switch(type) {
        case 'd': return bcd2byte(data[0]&0x3f);
        case 'm': return bcd2byte(data[1]&0x1f);
        case 'y': return bcd2byte(data[2]);
    }
    return 0;
}

/***
 * compares time
 * returns 0 if it is equal, -1 if earlier or 1 if later
 *
 * char type - 's'-sec, 'm'-min, 'h'-hrs
 *
 * normaly done by comparing the calculated times in secs,
 * but the result could be bigger than unsigned long (16bit)
 */
signed int cmptime(byte hrs, byte min, byte sec)
{
    signed int h, m, s;

    h = hrs-gettime('h', 1);
    m = min-gettime('m', 0);
    s = sec-gettime('s', 0);

    if(h>0)
        return -1;
    if(h<0) 
        return 1;
    if(!h && m<0)
        return 1;
    if(!h && !m && s<0)
        return 1;
    if(!h && !m && !s)
        return 0;
    return -1;
}

/***
 * initalizes the rtc
 */
void rtc_init(void)
{
    output_low(RTCCLK);
    output_low(RTCDATA);
    delay_us(100);
    rtc_start(1);
}

