/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2008 by Michael Sevakis
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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_registers_indexes
{
    RTC_REG_TIME = 0,
    RTC_REG_DAY,
    RTC_REG_TIME2,
    RTC_NUM_REGS,
};

/* was it an alarm that triggered power on ? */
static bool alarm_start = false;

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 },
};

/* 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)
{
    /* only needs to be polled on startup */
    if (mc13783_read(MC13783_INTERRUPT_STATUS1) & MC13783_TODAI)
    {
        alarm_start = true;
        mc13783_write(MC13783_INTERRUPT_STATUS1, MC13783_TODAI);
    }
}

int rtc_read_datetime(struct tm *tm)
{
    uint32_t regs[RTC_NUM_REGS];
    int year, leap, month, day, hour, min;

    /* Read time, day, time - 2nd read of time should be the same or
     * greater */
    do
    {
        if (mc13783_read_regs(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 */
    hour = regs[RTC_REG_TIME] / 3600;
    regs[RTC_REG_TIME] -= hour*3600;
    tm->tm_hour = hour;

    min = regs[RTC_REG_TIME] / 60;
    regs[RTC_REG_TIME] -= min*60;
    tm->tm_min = min;

    tm->tm_sec = regs[RTC_REG_TIME];

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

    /* Weekday */
    tm->tm_wday = (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;
    }

    tm->tm_mday = day + 1; /* 1 to 31 */
    tm->tm_mon = month; /* 0 to 11 */
    tm->tm_year = year % 100 + 100;

    return 7;
}

int rtc_write_datetime(const struct tm *tm)
{
    uint32_t regs[2];
    int year, leap, month, day, i, base_yearday;

    regs[RTC_REG_TIME] = tm->tm_sec +
                         tm->tm_min*60 +
                         tm->tm_hour*3600;

    year = tm->tm_year - 100;

    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 = tm->tm_mon;

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

    regs[RTC_REG_DAY] = day + tm->tm_mday - 1 - base_yearday;

    if (mc13783_write_regs(rtc_registers, regs, 2) == 2)
    {
        return 7;
    }

    return 0;
}

bool rtc_check_alarm_flag(void)
{
    /* We don't need to do anything special if it has already fired */
    return false;
}

bool rtc_enable_alarm(bool enable)
{
    if (enable)
        mc13783_clear(MC13783_INTERRUPT_MASK1, MC13783_TODAM);
    else
        mc13783_set(MC13783_INTERRUPT_MASK1, MC13783_TODAM);

    return false;
}

bool rtc_check_alarm_started(bool release_alarm)
{
    bool rc = alarm_start;

    if (release_alarm)
        alarm_start = false;

    return rc;
}

void rtc_set_alarm(int h, int m)
{
    int day = mc13783_read(MC13783_RTC_DAY);
    int tod = mc13783_read(MC13783_RTC_TIME);

    if (h*3600 + m*60 < tod)
        day++;

    mc13783_write(MC13783_RTC_DAY_ALARM, day);
    mc13783_write(MC13783_RTC_ALARM, h*3600 + m*60);
}

void rtc_get_alarm(int *h, int *m)
{
    int tod = mc13783_read(MC13783_RTC_ALARM);
    *h = tod / 3600;
    *m = tod % 3600 / 60;
}

