/*
 * Title    :   PCF8563 library
 * Authors:     Ondrej Lutera
 * Software:  AVR-GCC
 * Target:    any AVR device
 * Copyright: GPL
 *
 *  30.08.2011
 */

#include "pcf8563.h"
#include "string.h"
#include <avr/interrupt.h>


err_t   errors;

recalib_t recalib;
recalib_t ee_recalib EEMEM;

pcf_t   pcf_time;
pcf_t   ee_pcf_time EEMEM;

void initPCF(void){
    pcf_t curr_time;

    i2cInit();

    eeprom_read_block((void *)&recalib,&ee_recalib,sizeof(recalib_t));

    if(recalib.isSet != 'Y'){

        recalib.isSet = 'Y';
        recalib.sec = PCF_DEF_RECALIB_SEC;
        recalib.month = PCF_DEF_RECALIB_PERID_MONTH;

        getFromPCF(&curr_time);

        recalib.last_hour = curr_time.hour;
        recalib.last_day = curr_time.day;
        recalib.last_month = curr_time.month;
        recalib.last_year = curr_time.year;

        eeprom_update_block((void *)&recalib,&ee_recalib,sizeof(recalib_t));

    }

    eeprom_read_block((void *)(&pcf_time),&ee_pcf_time,sizeof(pcf_t));

    eeprom_read_block((void *)&recalib,&ee_recalib,sizeof(recalib_t));

    if(pcf_time.isSet == 'Y'){

        getFromPCF(&curr_time);

        if(curr_time.year < pcf_time.year) {
            errors.time = 'Y';
            return ;
        }

        if(curr_time.year == pcf_time.year){

                if(curr_time.month < pcf_time.month){

                    errors.time = 'Y';
                    return ;

                }

                if(curr_time.month == pcf_time.month ){


                        if(curr_time.day < pcf_time.day){

                            errors.time = 'Y';
                            return ;

                        }

                        if(curr_time.day == pcf_time.day){

                            if(curr_time.hour < pcf_time.hour ){

                                errors.time = 'Y';
                                return ;

                            }
                            if(curr_time.hour == pcf_time.hour ){

                                if(curr_time.min < pcf_time.min ){

                                    errors.time = 'Y';
                                    return ;


                                }

                                if(curr_time.min == pcf_time.min && curr_time.sec < pcf_time.sec){

                                    errors.time = 'Y';
                                    return ;
                                }



                            }


                        }


                }


        }


    }

}

inline void timePCFPoll(void){


    getFromPCF((pcf_t *)&pcf_time);


}

void getFromPCF(pcf_t * str){

    uint8_t bcd[7];

    i2cReceive(PCF8563, 0x02, 7, bcd);

    if(bcd[0]&PCF_TIME_VL_BIT) {
        errors.bat = 'Y';
    }else errors.bat = 'N';
    
    /* viz datasheet pcf8563 */

    str->sec = (((bcd[0]&0b01111111)>>4)&0x0f)*10+(bcd[0]&0x0f);
    str->min = (((bcd[1]&0b01111111)>>4)&0x0f)*10+(bcd[1]&0x0f);
    str->hour = (((bcd[2]&0b00111111)>>4)&0x0f)*10+(bcd[2]&0x0f);
    str->day = ((bcd[3]>>4)&3)*10+((bcd[3]&0x0f)>9?9:(bcd[3]&0x0f));
    str->month = (((bcd[5]&0b00011111)>>4)&0x0f)*10+(bcd[5]&0x0f);
    str->year = (str->year/100)*100 + (((bcd[6]>>4) &0x0f) *10+ (bcd[6]&0x0f)  );

    if(bcd[5]&(1<<7)) {
        str->year += 100;
        bcd[5]&=0x7F;
        i2cSend(PCF8563,0x07,1,(bcd+5));
    }


}

void setToPCF(pcf_t * str){

    uint8_t bcd[7];
    uint8_t year;

    bcd[0] =( (str->sec%10)| (((str->sec/10))<<4) ) &127 ;
    bcd[1] =( (str->min%10)| (((str->min/10))<<4) ) &127 ;
    bcd[2] =( (str->hour%10)| (((str->hour/10))<<4) ) &63;
    bcd[3] =( (str->day%10)| (((str->day/10))<<4) ) &63;

    i2cReceive(PCF8563, 0x06, 1, bcd+4);

    bcd[5] = ( (str->month%10)| (((str->month/10))<<4) ) &31;

    year = str->year%100;
    bcd[6] = (year%10)| ((((year)/10))<<4) ;

    i2cSend(PCF8563,0x02,7,bcd);

}

void setFrekOutPCF(uint8_t reg){


    i2cSend(PCF8563,PCF_CLK_ADDR,1,&reg);


}

void setRecalibPCF(int8_t sec,uint8_t perid_month){

    if(perid_month==0 || sec ==0) return;

    pcf_t curr_time;

    cli();
    getFromPCF(&curr_time);
    sei();

    recalib.isSet = 'Y';
    recalib.sec = sec ;
    recalib.month = perid_month;

    recalib.last_hour = curr_time.hour;
    recalib.last_day = curr_time.day ;
    recalib.last_month = curr_time.month;
    recalib.last_year = curr_time.year;

    eeprom_update_block((void*)&recalib,&ee_recalib,sizeof(recalib_t));

}

uint8_t old_sec=0;
void timeRecalibPoll(void){

    int8_t months=0;

    pcf_t newTime;

    if(recalib.isSet == 'Y' && pcf_time.sec!=old_sec){

        old_sec = pcf_time.sec;

        months = (pcf_time.year-recalib.last_year)*12;
        months += ( (int8_t)pcf_time.month - (int8_t)recalib.last_month);

        if(months>1 || (months==1 && pcf_time.day >= recalib.last_day && pcf_time.hour >= recalib.last_hour)){

            cli();
            memcpy(&newTime,(pcf_t*)&pcf_time,sizeof(pcf_t));
            sei();

            if(recalib.sec<0 && (newTime.sec + recalib.sec)>=0 ){

                newTime.sec += recalib.sec;

                recalib.last_month++ ;

                if(recalib.last_month>12) {
                    recalib.last_month = 1;
                    recalib.last_year ++;

                }

                cli();
                setToPCF((pcf_t *)&newTime);
                sei();

            }

            if(recalib.sec>0 && (newTime.sec + recalib.sec)<=59 ){

                newTime.sec += recalib.sec;

                recalib.last_month++ ;

                if(recalib.last_month>12) {
                    recalib.last_month = 1;
                    recalib.last_year ++;

                }

                cli();
                setToPCF((pcf_t *)&newTime);
                sei();

            }


        }


    }


}

void testRec(recalib_t * rec){

if(rec) memcpy(rec,(void *)&recalib,sizeof(recalib_t));

}

void getTimePCF(pcf_t * str){

if(str) {
cli();
memcpy(str,(pcf_t*)&pcf_time,sizeof(pcf_t));
sei();
}

}

void getErrors(err_t *err){

if(err) memcpy(err,(err_t*)&errors,sizeof(err_t));

}

void setTimePCF(pcf_t * str){

    if(str){
    cli();
    setToPCF(str);
    sei();

    memcpy(&pcf_time,str,sizeof(pcf_t));
    eeprom_update_block(&pcf_time,&ee_pcf_time,sizeof(pcf_t));

    setRecalibPCF(recalib.sec,recalib.month);
    }

}

void getRecalibPCF(int8_t * sec,uint8_t *perid_month){

*sec = recalib.sec;
*perid_month = recalib.month;


}
