150 lines
4.2 KiB
D
150 lines
4.2 KiB
D
/**
|
|
* 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;
|
|
}
|
|
}
|