/* 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 #include #include #include #include #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; } } } }