Files
86Box-fork/src/rtc.c
OBattler 0236637c5a Proper RTC emulation based on work by Mahod.
Updated README.md.
Updated AMD makefile.
2016-07-11 01:27:23 +02:00

261 lines
8.8 KiB
C

/* Emulation of:
Dallas Semiconductor DS12C887 Real Time Clock
http://datasheets.maximintegrated.com/en/ds/DS12885-DS12C887A.pdf
http://dev-docs.atariforge.org/files/MC146818A_RTC_1984.pdf
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "nvr.h"
#include "rtc.h"
int enable_sync;
typedef struct
{
int sec;
int min;
int hour;
int mday;
int mon;
int year;
}
internal_clock_t;
internal_clock_t internal_clock;
/* When the RTC was last updated */
time_t rtc_set_time = 0;
/* Table for days in each month */
int rtc_days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/* Called to determine whether the year is leap or not */
static int rtc_is_leap(int org_year)
{
if (org_year % 400 == 0) return 1;
if (org_year % 100 == 0) return 0;
if (org_year % 4 == 0) return 1;
return 0;
}
/* Called to determine the days in the current month */
static int rtc_get_days(int org_month, int org_year)
{
if (org_month != 2)
{
return rtc_days_in_month[org_month];
}
else
{
return rtc_is_leap(org_year) ? 29 : 28;
}
}
/* Called when the internal clock gets updated */
static void rtc_recalc()
{
if (internal_clock.sec == 60)
{
internal_clock.sec = 0;
internal_clock.min++;
}
if (internal_clock.min == 60)
{
internal_clock.min = 0;
internal_clock.hour++;
}
if (internal_clock.hour == 24)
{
internal_clock.hour = 0;
internal_clock.mday++;
}
if (internal_clock.mday == (rtc_get_days(internal_clock.mon, internal_clock.year) + 1))
{
internal_clock.mday = 1;
internal_clock.mon++;
}
if (internal_clock.mon == 13)
{
internal_clock.mon = 1;
internal_clock.year++;
}
nvr_dosave = 1;
}
/* Called when ticking the second */
void rtc_tick()
{
internal_clock.sec++;
rtc_recalc();
}
/* Called when modifying the NVR registers */
void time_update(char *nvrram, int reg)
{
int temp;
switch(reg)
{
case RTCSECONDS:
internal_clock.sec = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCSECONDS] : DCB(nvrram[RTCSECONDS]);
break;
case RTCMINUTES:
internal_clock.min = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCMINUTES] : DCB(nvrram[RTCMINUTES]);
break;
case RTCHOURS:
temp = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCHOURS] : DCB(nvrram[RTCHOURS]);
if (nvrram[RTCREGB] & RTC2412)
{
internal_clock.hour = temp;
}
else
{
internal_clock.hour = ((temp & ~RTCAMPM) % 12) + ((temp & RTCAMPM) ? 12 : 0);
}
break;
case RTCDOM:
internal_clock.mday = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCDOM] : DCB(nvrram[RTCDOM]);
break;
case RTCMONTH:
internal_clock.mon = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCMONTH] : DCB(nvrram[RTCMONTH]);
break;
case RTCYEAR:
internal_clock.year = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCYEAR] : DCB(nvrram[RTCYEAR]);
internal_clock.year += (nvrram[RTCREGB] & RTCDM) ? 1900 : (DCB(nvrram[RTCCENTURY]) * 100);
break;
case RTCCENTURY:
if (nvrram[RTCREGB] & RTCDM) return;
internal_clock.year %= 100;
internal_clock.year += (DCB(nvrram[RTCCENTURY]) * 100);
break;
case 0xFF: /* Load the entire internal clock state from the NVR. */
internal_clock.sec = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCSECONDS] : DCB(nvrram[RTCSECONDS]);
internal_clock.min = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCMINUTES] : DCB(nvrram[RTCMINUTES]);
temp = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCHOURS] : DCB(nvrram[RTCHOURS]);
if (nvrram[RTCREGB] & RTC2412)
{
internal_clock.hour = temp;
}
else
{
internal_clock.hour = ((temp & ~RTCAMPM) % 12) + ((temp & RTCAMPM) ? 12 : 0);
}
internal_clock.mday = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCDOM] : DCB(nvrram[RTCDOM]);
internal_clock.mon = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCMONTH] : DCB(nvrram[RTCMONTH]);
internal_clock.year = (nvrram[RTCREGB] & RTCDM) ? nvrram[RTCYEAR] : DCB(nvrram[RTCYEAR]);
internal_clock.year += (nvrram[RTCREGB] & RTCDM) ? 1900 : (DCB(nvrram[RTCCENTURY]) * 100);
break;
}
}
/* Called to obtain the current day of the week based on the internal clock */
static int time_week_day()
{
int day_of_month = internal_clock.mday;
int month2 = internal_clock.mon;
int year2 = internal_clock.year % 100;
int century = ((internal_clock.year - year2) / 100) % 4;
int sum = day_of_month + month2 + year2 + century;
/* (Sum mod 7) gives 0 for Saturday, we need it for Sunday, so +6 for Saturday to get 6 and Sunday 0 */
int raw_wd = ((sum + 6) % 7);
return raw_wd;
}
/* Called to get time into the internal clock */
static void time_internal(struct tm **time_var)
{
if (*time_var == NULL) *time_var = (struct tm *) malloc(sizeof(struct tm));
(*time_var)->tm_sec = internal_clock.sec;
(*time_var)->tm_min = internal_clock.min;
(*time_var)->tm_hour = internal_clock.hour;
(*time_var)->tm_wday = time_week_day();
(*time_var)->tm_mday = internal_clock.mday;
(*time_var)->tm_mon = internal_clock.mon - 1;
(*time_var)->tm_year = internal_clock.year - 1900;
}
/* Periodic RTC update function
See also: nvr_onesec() in nvr.c
*/
void time_get(char *nvrram)
{
time_t cur_time;
struct tm* cur_time_tm;
int dow, mon, year;
if (enable_sync)
{
cur_time = time(NULL);
/* Mingw doesn't support localtime_r */
#if __MINGW32__
cur_time_tm = localtime(&cur_time);
#else
#if __MINGW64__
cur_time_tm = localtime(&cur_time);
#else
localtime_r(&cur_time, &cur_time_tm);
#endif
#endif
}
else
{
time_internal(&cur_time_tm);
}
if (nvrram[RTCREGB] & RTCDM)
{
nvrram[RTCSECONDS] = cur_time_tm->tm_sec;
nvrram[RTCMINUTES] = cur_time_tm->tm_min;
nvrram[RTCDOW] = cur_time_tm->tm_wday + 1;
nvrram[RTCDOM] = cur_time_tm->tm_mday;
nvrram[RTCMONTH] = cur_time_tm->tm_mon + 1;
nvrram[RTCYEAR] = cur_time_tm->tm_year % 100;
if (nvrram[RTCREGB] & RTC2412)
{
nvrram[RTCHOURS] = cur_time_tm->tm_hour;
}
else
{
nvrram[RTCHOURS] = (cur_time_tm->tm_hour % 12) ? (cur_time_tm->tm_hour % 12) : 12;
if (cur_time_tm->tm_hour > 11)
{
nvrram[RTCHOURS] |= RTCAMPM;
}
}
}
else
{
nvrram[RTCSECONDS] = BCD(cur_time_tm->tm_sec);
nvrram[RTCMINUTES] = BCD(cur_time_tm->tm_min);
nvrram[RTCDOW] = BCD(cur_time_tm->tm_wday + 1);
nvrram[RTCDOM] = BCD(cur_time_tm->tm_mday);
nvrram[RTCMONTH] = BCD(cur_time_tm->tm_mon + 1);
nvrram[RTCYEAR] = BCD(cur_time_tm->tm_year % 100);
if (nvrram[RTCREGB] & RTC2412)
{
nvrram[RTCHOURS] = BCD(cur_time_tm->tm_hour);
}
else
{
nvrram[RTCHOURS] = (cur_time_tm->tm_hour % 12) ? BCD(cur_time_tm->tm_hour % 12) : BCD(12);
if (cur_time_tm->tm_hour > 11)
{
nvrram[RTCHOURS] |= RTCAMPM;
}
}
}
}