/*
 * Read/write DS3231M RTC to/from the sysclock.
 *
 * This currently doesn't try to maintain any kind of subsecond
 * accuracy.
 */

#include <string.h>
#include "fw.h"
#include "console.h"
#include "io.h"
#include "systime.h"

static inline uint32_t i2c_wait(void)
{
    waitfor(I2C_IRQ);
}

static void i2c_send(uint8_t byte, uint8_t ctl)
{
    i2c_wait();
    I2C_WDATA = (byte << 8) | I2C_NAK | ctl;
}

static bool i2c_acked(void)
{
    i2c_wait();
    return !(I2C_RDATA & I2C_NAK);
}

static uint8_t i2c_recv(uint8_t ctl)
{
    uint32_t rdata;

    i2c_wait();
    I2C_WDATA = (~0xff) | ctl;

    i2c_wait();
    return I2C_RDATA_DATA;
}

#define RTC_REGS 19		/* Total RTC registers */
#define RTC_TIME_REGS 7		/* RTC registers with time info */
#define RTC_ADDR 0x68
#define RTC_WCMD ((RTC_ADDR << 1)+0)
#define RTC_RCMD ((RTC_ADDR << 1)+1)

static unsigned int unbcd(uint8_t v)
{
    return (v & 0x0f) + (v >> 4) * 10;
}

static uint8_t tobcd(unsigned int v)
{
    uint8_t q;

    v %= 100;
    return ((v/10) << 4) + (v%10);
}

static int i2c_start_rtc(void)
{
    int sda_retry_count = 16;

    i2c_set_speed(400);

    /* SDA held low? */
    while (!(I2C_RDATA & I2C_SDA)) {
	i2c_send(0xff, I2C_DUMMY);

	if (!sda_retry_count--) {
	    con_printf("RTC: I2C SDA stuck low\n");
	    return -1;
	}
    }

    i2c_send(RTC_WCMD, 0);

    if (!i2c_acked()) {
	con_printf("No RTC detected at I2C address 0x%02x\n", RTC_ADDR);
	i2c_send(0xff, I2C_P);
	return -1;
    }

    return 0;
}

void read_rtc(void)
{
    uint8_t rtc_regs[RTC_TIME_REGS];
    int i;
    struct tms tms;

    if (i2c_start_rtc())
	return;

    i2c_send(0, I2C_SR);	/* Starting register */
    i2c_send(RTC_RCMD, 0);

    for (i = 0; i < RTC_TIME_REGS-1; i++)
	rtc_regs[i] = i2c_recv(0);
    rtc_regs[i] = i2c_recv(I2C_NAK | I2C_P);

    /* Convert to struct tms and set systime */
    memset(&tms, 0, sizeof tms);

    unsigned int sec = unbcd(rtc_regs[0]);
    tms.tm_2sec = sec >> 1;
    tms.hold.tm_1sec = sec & 1;
    tms.hold.tm_tick = 0x4000;	/* Without more info, assume mid-second */

    tms.tm_min = unbcd(rtc_regs[1]);

    unsigned int hour;
    if (rtc_regs[2] & 0x40) {
	/* AM/PM mode - this shouldn't happen */
	hour = unbcd(rtc_regs[2] & 0x1f);
	if (hour > 11)
	    hour -= 12;
	if (rtc_regs[2] & 0x20)
	    hour += 12;
    } else {
	/* 24-hour mode */
	hour = unbcd(rtc_regs[2]);
    }
    tms.tm_hour = hour;

    tms.tm_mday = unbcd(rtc_regs[4]);
    tms.tm_mon  = unbcd(rtc_regs[5] & 0x1f);
    tms.tm_year = unbcd(rtc_regs[6]) + (rtc_regs[5] & 0x80 ? 100 : 0) + 20;

    set_systime(tms);

#ifdef TEST
    con_printf("RTC register content:\n");
    for (i = 0; i < RTC_REGS; i++)
	con_printf(" %02x", rtc_regs[i]);
#endif

    con_printf("RTC time: %04u-%02u-%02u %02u:%02u:%02u\n",
	       tms.tm_year + 1980, tms.tm_mon, tms.tm_mday,
	       tms.tm_hour, tms.tm_min, tms_sec(tms));
}

bool do_write_rtc;

void write_rtc(void)
{
    uint8_t rtc_regs[RTC_TIME_REGS];
    int i;
    struct tms tms;
    int sda_retry_count = 16;

    do_write_rtc = false;
    tms = get_systime();

    rtc_regs[0] = tobcd(tms_sec(tms));
    rtc_regs[1] = tobcd(tms.tm_min);
    rtc_regs[2] = tobcd(tms.tm_hour);
    rtc_regs[3] = 0;		/* Day of week, unused */
    rtc_regs[4] = tobcd(tms.tm_mday);
    rtc_regs[5] = tobcd(tms.tm_mon);
    rtc_regs[6] = tobcd(tms.tm_year - 20);

    if (i2c_start_rtc())
	return;

    i2c_send(0, 0);		/* Starting register */

    for (i = 0; i < RTC_TIME_REGS-1; i++)
	i2c_send(rtc_regs[i], 0);

    i2c_send(rtc_regs[i], I2C_P);
}
