/*
 * abcrtc.c
 *
 * ABC-bus interface to the real-time clock
 * Very loosely based on the virtual RTC in abc80sim.
 */

#include "fw.h"
#include "io.h"
#include "abcio.h"
#include "systime.h"
#include "console.h"

#define RTC_DEVSEL 54
#define RTC_CALLBACK_MASK ((1 << 8)|(1 << 2)|(1 << 0))

#define RTC_MAGIC 211

/* Command and status register bits */
enum rtc_cmd_status {
    RTC_READY       = 0x01,
    RTC_ID          = 0x02,	/* INP(0) shows magic number */
    RTC_PROGRAM     = 0x04,	/* (Ready to) program RTC */
    RTC_START_WRITE = 0x40,
    RTC_START_READ  = 0x80
};

static uint8_t rdbuf[8];
static uint8_t wrbuf[8];
static bool need_program;	/* Programming is handled at non-IRQ prio */

static void rtc_latch_time(void)
{
    struct tms tms = get_systime();

    rdbuf[0] = 20;		/* Only 20xx years supported */
    rdbuf[1] = tms.tm_year - 20;
    rdbuf[2] = tms.tm_mon;
    rdbuf[3] = tms.tm_mday;
    rdbuf[4] = tms.tm_hour;
    rdbuf[5] = tms.tm_min;
    rdbuf[6] = tms_sec(tms);
    rdbuf[7] = (tms.hold.tm_tick * ((100ULL << 32) >> 15)) >> 32; /* 1/100 s */
}

static void rtc_store_time(void)
{
    struct tms tms;

    /* XXX: should probably do data validation here, even an error bit */

    memset(&tms, 0, sizeof tms);
    tms.tm_year      = wrbuf[1] + 20;
    tms.tm_mon       = wrbuf[2];
    tms.tm_mday      = wrbuf[3];
    tms.tm_hour      = wrbuf[4];
    tms.tm_min       = wrbuf[5];
    tms.tm_2sec      = wrbuf[6] >> 1;
    tms.hold.tm_1sec = wrbuf[6] & 1;
    tms.hold.tm_tick = (wrbuf[7] * ((1ULL << (15+32))/100)) >> 32;

    set_systime(tms);
    do_write_rtc = true;
}

static ABC_CALLBACK(rtc_do_program_ready)
{
    dev->inp_data[1] |= RTC_PROGRAM;
    ABC_INP1_DATA = dev->inp_data[1];
}

static ABC_CALLBACK(rtc_do_id)
{
    /* Called on end of data read */
    dev->inp_data[1] |= RTC_ID;
    ABC_INP1_DATA = dev->inp_data[1];
}

static ABC_CALLBACK(rtc_do_command)
{
    if (data & RTC_PROGRAM) {
	dev->inp_data[1] &= ~RTC_PROGRAM;
	ABC_INP1_DATA = dev->inp_data[1];
	rtc_store_time();
    }
    if (data & RTC_START_WRITE) {
	abc_setup_out_queue(dev, &wrbuf, 8, dev->inp_data[1]|RTC_START_WRITE);
    }
    if (data & RTC_START_READ) {
	dev->inp_data[1] &= ~RTC_ID;
	ABC_INP1_DATA = dev->inp_data[1];
	rtc_latch_time();
	abc_setup_inp_queue(dev, &rdbuf, 8, dev->inp_data[1]|RTC_START_READ);
    }
}

static struct abc_dev rtc_iodev = {
    .callback_mask = RTC_CALLBACK_MASK,
    .status_first_inp_mask = (uint8_t)~RTC_START_READ,
    .status_first_out_mask = (uint8_t)~RTC_START_WRITE,
    .inp_en = 3,
    .inp_data_def = RTC_MAGIC,
    .inp_data[1] = RTC_READY|RTC_ID,
    .callback_out[0] = rtc_do_program_ready,
    .callback_out[2] = rtc_do_command,
    .callback_inp[0] = rtc_do_id
};

void rtc_abc_init(void)
{
    abc_register(&rtc_iodev, RTC_DEVSEL);
}
