/** * RTC (Real-Time Clock) functionality. */ module hulk.rtc; import hulk.cpu; import hulk.klog; struct Rtc { private enum ubyte PORT_SELECT = 0x70u; private enum ubyte PORT_DATA = 0x71u; private enum ubyte DISABLE_NMI = 0x80u; private enum ubyte REG_RTC_SECONDS = 0x0u; private enum ubyte REG_RTC_MINUTES = 0x2u; private enum ubyte REG_RTC_HOURS = 0x4u; private enum ubyte REG_RTC_DAY = 0x7u; private enum ubyte REG_RTC_MONTH = 0x8u; private enum ubyte REG_RTC_YEAR = 0x9u; private enum ubyte REG_SR_A = 0xAu; private enum ubyte REG_SR_B = 0xBu; private enum ubyte REG_SR_C = 0xCu; private enum ubyte SR_A_RTC_UPDATE_IN_PROGRESS = 0x80u; private enum ubyte SR_B_24HOUR = 0x02u; private enum ubyte SR_B_BINARY = 0x04u; private enum ubyte SR_B_ENABLE_IRQ = 0x40u; private static __gshared bool rtc_24hour_mode; private static __gshared bool rtc_binary_mode; static struct time { public ubyte year; public ubyte month; public ubyte day; public ubyte hour; public ubyte minute; public ubyte second; } public static void initialize() { /* Enable IRQ 8 to receive RTC interrupts. */ ubyte sr_b = read_register(REG_SR_B, true); write_register(REG_SR_B, sr_b | SR_B_ENABLE_IRQ, true); /* Record RTC mode flags from status register B. */ rtc_24hour_mode = (sr_b & SR_B_24HOUR) != 0u; rtc_binary_mode = (sr_b & SR_B_BINARY) != 0u; /* Read the current system time. */ time t = read_rtc_time(); Klog.writefln("System time is 20%02u-%02u-%02u %02u:%02u:%02u", t.year, t.month, t.day, t.hour, t.minute, t.second); /* Send EOI to enable more RTC interrupts and re-enable NMIs. */ eoi(); } private static ubyte read_register(ubyte register, bool disable_nmi = false) { out8(PORT_SELECT, register | (disable_nmi ? DISABLE_NMI : 0u)); return in8(PORT_DATA); } private static void write_register(ubyte register, ubyte value, bool disable_nmi = false) { out8(PORT_SELECT, register | (disable_nmi ? DISABLE_NMI : 0u)); out8(PORT_DATA, value); } private static void eoi() { /* Read from status register C to clear the interrupt. */ read_register(REG_SR_C); } public static void isr() { static __gshared ulong count; static __gshared ulong seconds; count++; if ((count % 1024) == 0u) { seconds++; } eoi(); } private static time read_rtc_time() { time[2] times; size_t reads; do { times[reads & 1] = read_rtc_time_unsafe(); reads++; } while ((reads < 2u) || (times[0] != times[1])); return times[0]; } private static time read_rtc_time_unsafe() { time t; while ((read_register(REG_SR_A) & SR_A_RTC_UPDATE_IN_PROGRESS) != 0u) { } t.year = read_rtc_time_register(REG_RTC_YEAR); t.month = read_rtc_time_register(REG_RTC_MONTH); t.day = read_rtc_time_register(REG_RTC_DAY); t.minute = read_rtc_time_register(REG_RTC_MINUTES); t.second = read_rtc_time_register(REG_RTC_SECONDS); /* Hour field is treated differently because it might have 'PM' flag * in highest bit if we're in 24-hour mode. */ ubyte hour = read_register(REG_RTC_HOURS); ubyte pm = hour >> 7u; hour &= 0x7Fu; if (!rtc_binary_mode) { /* Hour is in BCD format; convert to binary. */ hour = bcd_to_binary(hour); } if (!rtc_24hour_mode) { /* Hour is in 12-hour format; convert to 24-hour format. */ hour = cast(ubyte)((hour % 12u) + pm * 12u); } t.hour = hour; return t; } private static ubyte read_rtc_time_register(ubyte register) { ubyte value = read_register(register); if (!rtc_binary_mode) { value = bcd_to_binary(value); } return value; } private static ubyte bcd_to_binary(ubyte bcd) { return (bcd & 0xFu) + ((bcd & 0xF0u) >> 4u) * 10u; } }