/*
 * DANG_BEGIN_MODULE
 *
 * Description: Real Time Clock (RTC) chip emulation for DOSEMU.
 *
 * Maintainers: Scott Buchholz
 * 
 * This code provides for rudimentary RTC emulation for DOSEMU.  It
 * allows you to change the date and time within DOSEMU, although this
 * won't affect the times your disk writes register one whit.  Can't
 * have it all...
 *
 * There is a delta value maintained which contains the number of seconds
 * offset the DOSEMU clock is from "real time" (e.g. Linux time)
 *
 * NOTE:  the offset code for setting days/months/years is probably awful.
 *        alarms are not currently supported (what do they do, anyway?)
 *        high speed interrupts are not supported (surprise!)
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

#include "dosemu.h"
#include "base/iodev.h"
#include "base/int.h"
#include "base/port.h"
#include "base/timers.h"
#include "base/bios.h"

struct rtc_alarm_struct {
  Bit8u sec, min, hour;
};

static struct rtc_alarm_struct   rtc_alrm;
static time_t                    rtc_ofs;
static Bit8u                     rtc_regA, rtc_regB, rtc_regC, rtc_regD;

void set_ticks(Bit32u new)
{
  WRITE_DWORD(bios_timer, new + rtc_ofs * PIT_TICK_RATE);
  WRITE_BYTE(bios_timer_overflow, 0);
}

static inline time_t rtc_time(time_t *res)
{
  time_t temp;
  /*
   * This routine is quite broken.  It assumes that there are 30 days
   * in a month and 365 days in a year.  That said, if you set your DOSEMU
   * time to some value nowhere near your wall time, you're asking for
   * trouble anyway.
   * Incidentally, if you think it's worth fixing this (it's not), then
   * remember that you have to figure out the current location in time and
   * then go forward from "now".
   * Additionally, if the DAYLIGHT SAVINGS is enabled, do we add or
   * subtract one?
   */

  time(&temp);
  temp += rtc_ofs;
  if ((rtc_regB & 0x01) == 1)    /* daylight savings? */
    temp += 3600;
  if (res)
    *res = temp;
  return temp;
}

static inline Bit8u BCD(Bit8u value)
{
  if (value > 99)
    return(0x99);
  else
    return(((value/10) << 4) | (value%10));
}

static inline Bit8u rtc_get_alrm(Bit32u reg)
{
  int value = 0;

  switch (reg) {
    case RTC_SECALRM:
      value = rtc_alrm.sec;
      break;
    case RTC_MINALRM:
      value = rtc_alrm.min;
      break;
    case RTC_HOURALRM:
      value = rtc_alrm.hour;
      break;
  }
  return value;
}

static inline Bit8u rtc_get_date(Bit32u reg)
{
  struct tm *tm;
  time_t cur_time;

  cur_time = rtc_time(NULL);
  tm       = localtime(&cur_time);

  switch (reg) {
    case RTC_SEC:
      return tm->tm_sec;
    case RTC_MIN:
      return tm->tm_min;
    case RTC_HOUR:
      if (((rtc_regB & 0x02) == 0) && (tm->tm_hour > 12)) /* check 24hr mode */
	return tm->tm_hour - 12;
      else
	return tm->tm_hour;
    case RTC_DOW:
      return tm->tm_wday;
    case RTC_DOM:
      return tm->tm_mday;
    case RTC_MON:
      return tm->tm_mon;
    case RTC_YEAR:
      return tm->tm_year;
  }
  return 0;
}

static inline void rtc_set_alrm(Bit32u reg, Bit8u value)
{
  switch (reg) {
    case RTC_SECALRM:
      rtc_alrm.sec = value;
      break;
    case RTC_MINALRM:
      rtc_alrm.min = value;
      break;
    case RTC_HOURALRM:
      rtc_alrm.hour = value;
      break;
  }
}

static void rtc_set_date(Bit32u reg, Bit8u value)
{
  struct tm *tm;
  time_t cur_time;

  cur_time = rtc_time(NULL);
  tm       = localtime(&cur_time);

  switch (reg) {
    case RTC_SEC:
      rtc_ofs += value - tm->tm_sec;
      break;
    case RTC_MIN:
      rtc_ofs += (value - tm->tm_min) * SECS_PER_MIN;
      break;
    case RTC_HOUR:
      rtc_ofs += (value - tm->tm_hour) * SECS_PER_HOUR;
      break;
    case RTC_DOW:
      rtc_ofs += (value - tm->tm_wday) * SECS_PER_DAY;
      break;
    case RTC_DOM:
      rtc_ofs += (value - tm->tm_mday) * SECS_PER_DAY;
      break;
    case RTC_MON:
      rtc_ofs += (value - tm->tm_mon) * SECS_PER_MONTH;
      break;
    case RTC_YEAR:
      rtc_ofs += (value - tm->tm_year) * SECS_PER_YEAR;
      break;
  }
}

Bit8u rtc_read(Bit32u reg)
{
  int value = -1;

  switch (reg) {
    case RTC_SEC:
    case RTC_MIN:
    case RTC_HOUR:
    case RTC_DOW:
    case RTC_DOM:
    case RTC_MON:
    case RTC_YEAR:
      value = rtc_get_date(reg);
      h_printf("RTC: time/date read (reg %lu, 0x%2.2x)\n", reg, value);
      break;
    case RTC_SECALRM:
    case RTC_MINALRM:
    case RTC_HOURALRM:
      value = rtc_get_alrm(reg);
      h_printf("RTC: alarm read (reg %lu, 0x%2.2x)\n", reg, value);
      break;
    case RTC_REGA:
      value = rtc_regA;
      h_printf("RTC: regA read (0x%2.2x)\n", rtc_regA);
      break;
    case RTC_REGB:
      value = rtc_regB;
      h_printf("RTC: regB read (0x%2.2x)\n", rtc_regB);
      break;
    case RTC_REGC:
      value = rtc_regC;
      h_printf("RTC: regC read (0x%2.2x)\n", rtc_regC);
      break;
    case RTC_REGD:
      value = rtc_regD;
      h_printf("RTC: regD read (0x%2.2x)\n", rtc_regD);
      break;                    /* read-only */
  }
  return value;
}

void rtc_write(Bit32u reg, Bit8u value)
{
  switch (reg) {
    case RTC_SEC:
    case RTC_MIN:
    case RTC_HOUR:
    case RTC_DOW:
    case RTC_DOM:
    case RTC_MON:
    case RTC_YEAR:
      rtc_set_date(reg, value);
      h_printf("RTC: time/date set (reg %lu, 0x%2.2x)\n", reg, value);
      break;
    case RTC_SECALRM:
    case RTC_MINALRM:
    case RTC_HOURALRM:
      rtc_set_alrm(reg, value);
      h_printf("RTC: alarm set (reg %lu, 0x%2.2x)\n", reg, value);
      break;
    case RTC_REGA:
      rtc_regA = 0x7f & value;  /* someone's resetting the divider freq. */
      h_printf("RTC: regA = 0x%2.2x (0x%2.2x requested)\n", rtc_regA, value);
      break;
    case RTC_REGB:
      rtc_regB = 0x03 & value;  /* we're (sadly) ignoring most bytes for now */
      h_printf("RTC: regB = 0x%2.2x (0x%2.2x requested)\n", rtc_regB, value);
      break;
    case RTC_REGC:
    case RTC_REGD:
      h_printf("RTC: some nutcase is writing the read-only registers\n");
      break;                    /* read-only */
  }
}

void rtc_init(void)        /* called at startup */
{
}

void rtc_reset(void)       /* called upon reboot */
{
  struct tm *tm;
  time_t     cur_time;
  Bit32u     ticks;

  rtc_alrm.sec = rtc_alrm.min = rtc_alrm.hour = 0;
  rtc_ofs  = 0;
  rtc_regA = 0x26;
  rtc_regB = 0x02;
  rtc_regC = 0x00;
  rtc_regD = 0x80;

  cur_time = time(NULL);
  tm       = localtime(&cur_time);
  ticks    = tm->tm_sec + (60*tm->tm_min) + (3600*tm->tm_hour);
  ticks   *= PIT_TICK_RATE;
  ticks   /= 65536;

  set_ticks(ticks);
}
