/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2008 by Michael Sevakis
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "config.h"
#include "system.h"
#include "rtc.h"
#include "mc13783.h"

/* NOTE: Defined the base to be original firmware compatible if needed -
 * ie. the day and year as it would interpret a DAY register value of zero. */

/* Days passed since midnight 01 Jan, 1601 to midnight on the base date. */
#ifdef TOSHIBA_GIGABEAT_S
    /* Gigabeat S seems to be 1 day behind the ususual - this will
     * make the RTC match file dates created by retailos. */
    #define RTC_BASE_DAY_COUNT  138425
    #define RTC_BASE_MONTH      12
    #define RTC_BASE_DAY        31
    #define RTC_BASE_YEAR       1979
#elif 1
    #define RTC_BASE_DAY_COUNT  138426
    #define RTC_BASE_MONTH      1
    #define RTC_BASE_DAY        1
    #define RTC_BASE_YEAR       1980
#else
    #define RTC_BASE_DAY_COUNT  134774
    #define RTC_BASE_MONTH      1
    #define RTC_BASE_DAY        1
    #define RTC_BASE_YEAR       1970
#endif

enum rtc_buffer_field_indexes
{
    RTC_I_SECONDS = 0,
    RTC_I_MINUTES,
    RTC_I_HOURS,
    RTC_I_WEEKDAY,
    RTC_I_DAY,
    RTC_I_MONTH,
    RTC_I_YEAR,
    RTC_NUM_FIELDS,
};

enum rtc_registers_indexes
{
    RTC_REG_TIME = 0,
    RTC_REG_DAY,
    RTC_REG_TIME2,
    RTC_NUM_REGS,
};

static const unsigned char rtc_registers[RTC_NUM_REGS] =
{
    [RTC_REG_TIME]  = MC13783_RTC_TIME,
    [RTC_REG_DAY]   = MC13783_RTC_DAY,
    [RTC_REG_TIME2] = MC13783_RTC_TIME,
};

static const unsigned char month_table[2][12] =
{
    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
    { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
};

static inline void to_bcd(unsigned char *bcd, const unsigned char *buf,
                          int len)
{
    while (len-- > 0)
    {
        unsigned char d = *buf++;
        *bcd++ = ((d / 10) << 4) | (d % 10);
    }
}

static inline void from_bcd(unsigned char *buf, const unsigned char *bcd,
                            int len)
{
    while (len-- > 0)
    {
        unsigned char d = *bcd++;
        *buf++ = ((d >> 4) & 0x0f) * 10 + (d & 0xf);
    }
}

/* Get number of leaps since the reference date of 1601/01/01 */
static int get_leap_count(int d)
{
    int lm = (d + 1) / 146097;
    int lc = (d + 1 - lm) / 36524;
    int ly = (d + 1 - lm + lc) / 1461;
    return ly - lc + lm;
}

static int is_leap_year(int y)
{
    return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0) ? 1 : 0;
}

/** Public APIs **/
void rtc_init(void)
{
    /* Nothing to do */
}

int rtc_read_datetime(unsigned char* buf)
{
    uint32_t regs[RTC_NUM_REGS];
    int year, leap, month, day;

    /* Read time, day, time - 2nd read of time should be the same or
     * greater */
    do
    {
        if (mc13783_read_regset(rtc_registers, regs,
                                RTC_NUM_REGS) < RTC_NUM_REGS)
        {
            /* Couldn't read registers */
            return 0;
        }
    }
    /* If TOD counter turned over - reread */
    while (regs[RTC_REG_TIME2] < regs[RTC_REG_TIME]);

    /* TOD: = 0 to 86399 */
    buf[RTC_I_HOURS] = regs[RTC_REG_TIME] / 3600;
    regs[RTC_REG_TIME] -= buf[RTC_I_HOURS]*3600;

    buf[RTC_I_MINUTES] = regs[RTC_REG_TIME] / 60;
    regs[RTC_REG_TIME] -= buf[RTC_I_MINUTES]*60;

    buf[RTC_I_SECONDS] = regs[RTC_REG_TIME];

    /* DAY: 0 to 32767 */
    day = regs[RTC_REG_DAY] + RTC_BASE_DAY_COUNT;

    /* Weekday */
    buf[RTC_I_WEEKDAY] = (day + 1) % 7; /* 1601/01/01 = Monday */

    /* Get number of leaps for today */
    leap = get_leap_count(day);
    year = (day - leap) / 365;

    /* Get number of leaps for yesterday */
    leap = get_leap_count(day - 1);

    /* Get day number for year 0-364|365 */
    day = day - leap - year * 365;

    year += 1601;

    /* Get the current month */
    leap = is_leap_year(year);

    for (month = 0; month < 12; month++)
    {
        int days = month_table[leap][month];

        if (day < days)
            break;

        day -= days;
    }

    buf[RTC_I_DAY] = day + 1; /* 1 to 31 */
    buf[RTC_I_MONTH] = month + 1; /* 1 to 12 */
    buf[RTC_I_YEAR] = year % 100;

    to_bcd(buf, buf, RTC_NUM_FIELDS);

    return 7;
}

int rtc_write_datetime(unsigned char* buf)
{
    uint32_t regs[2];
    unsigned char fld[RTC_NUM_FIELDS];
    int year, leap, month, day, i, base_yearday;

    from_bcd(fld, buf, RTC_NUM_FIELDS);

    regs[RTC_REG_TIME] = fld[RTC_I_SECONDS] +
                         fld[RTC_I_MINUTES]*60 +
                         fld[RTC_I_HOURS]*3600;

    year = fld[RTC_I_YEAR];

    if (year < RTC_BASE_YEAR - 1900)
        year += 2000;
    else
        year += 1900;

    /* Get number of leaps for day before base */
    leap = get_leap_count(RTC_BASE_DAY_COUNT - 1);

    /* Get day number for base year 0-364|365 */
    base_yearday = RTC_BASE_DAY_COUNT - leap -
                    (RTC_BASE_YEAR - 1601) * 365;

    /* Get the number of days elapsed from reference */
    for (i = RTC_BASE_YEAR, day = 0; i < year; i++)
    {
        day += is_leap_year(i) ? 366 : 365;
    }

    /* Find the number of days passed this year up to the 1st of the
     * month. */
    leap = is_leap_year(year);
    month = fld[RTC_I_MONTH] - 1;

    for (i = 0; i < month; i++)
    {
        day += month_table[leap][i];
    }

    regs[RTC_REG_DAY] = day + fld[RTC_I_DAY] - 1 - base_yearday;

    if (mc13783_write_regset(rtc_registers, regs, 2) == 2)
    {
        return RTC_NUM_FIELDS;
    }

    return 0;
}
