| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
|---|---|
| 2 | |
| 3 | #define CURRENT_YEAR 2024 |
| 4 | |
| 5 | #include "mos/x86/devices/rtc.hpp" |
| 6 | |
| 7 | #include "mos/assert.hpp" |
| 8 | #include "mos/device/clocksource.hpp" |
| 9 | #include "mos/x86/devices/port.hpp" |
| 10 | #include "mos/x86/x86_interrupt.hpp" |
| 11 | |
| 12 | #include <mos/types.hpp> |
| 13 | |
| 14 | static clocksource_t rtc_clocksource = { |
| 15 | .name = "rtc", |
| 16 | .ticks = 0, |
| 17 | .frequency = 1000, |
| 18 | }; |
| 19 | |
| 20 | static const u8 RTC_STATUS_REG_B = 0x0B; |
| 21 | static const u8 CMOS_PORT_ADDRESS = 0x70; |
| 22 | static const u8 CMOS_PORT_DATA = 0x71; |
| 23 | |
| 24 | u8 rtc_read_reg(x86_port_t reg) |
| 25 | { |
| 26 | port_outb(port: CMOS_PORT_ADDRESS, value: reg); |
| 27 | return port_inb(port: CMOS_PORT_DATA); |
| 28 | } |
| 29 | |
| 30 | u8 rtc_is_update_in_progress() |
| 31 | { |
| 32 | return rtc_read_reg(reg: 0x0A) & 0x80; |
| 33 | } |
| 34 | |
| 35 | void rtc_read_time(timeval_t *time) |
| 36 | { |
| 37 | u8 last_second; |
| 38 | u8 last_minute; |
| 39 | u8 last_hour; |
| 40 | u8 last_day; |
| 41 | u8 last_month; |
| 42 | u8 last_year; |
| 43 | |
| 44 | while (rtc_is_update_in_progress()) |
| 45 | ; // Make sure an update isn't in progress |
| 46 | time->second = rtc_read_reg(reg: 0x00); |
| 47 | time->minute = rtc_read_reg(reg: 0x02); |
| 48 | time->hour = rtc_read_reg(reg: 0x04); |
| 49 | time->day = rtc_read_reg(reg: 0x07); |
| 50 | time->month = rtc_read_reg(reg: 0x08); |
| 51 | time->year = rtc_read_reg(reg: 0x09); |
| 52 | |
| 53 | do |
| 54 | { |
| 55 | last_second = time->second; |
| 56 | last_minute = time->minute; |
| 57 | last_hour = time->hour; |
| 58 | last_day = time->day; |
| 59 | last_month = time->month; |
| 60 | last_year = time->year; |
| 61 | |
| 62 | while (rtc_is_update_in_progress()) |
| 63 | ; // Make sure an update isn't in progress |
| 64 | time->second = rtc_read_reg(reg: 0x00); |
| 65 | time->minute = rtc_read_reg(reg: 0x02); |
| 66 | time->hour = rtc_read_reg(reg: 0x04); |
| 67 | time->day = rtc_read_reg(reg: 0x07); |
| 68 | time->month = rtc_read_reg(reg: 0x08); |
| 69 | time->year = rtc_read_reg(reg: 0x09); |
| 70 | } while ((last_second != time->second) || (last_minute != time->minute) || (last_hour != time->hour) || (last_day != time->day) || (last_month != time->month) || |
| 71 | (last_year != time->year)); |
| 72 | |
| 73 | #define bcd_to_binary(val) (((val) & 0x0F) + ((val) / 16) * 10) |
| 74 | |
| 75 | const u8 registerB = rtc_read_reg(reg: 0x0B); |
| 76 | if (!(registerB & 0x04)) |
| 77 | { |
| 78 | // BCD to binary values |
| 79 | time->second = bcd_to_binary(time->second); |
| 80 | time->minute = bcd_to_binary(time->minute); |
| 81 | time->hour = ((time->hour & 0x0F) + (((time->hour & 0x70) / 16) * 10)) | (time->hour & 0x80); // |
| 82 | time->day = bcd_to_binary(time->day); |
| 83 | time->month = bcd_to_binary(time->month); |
| 84 | time->year = bcd_to_binary(time->year); |
| 85 | } |
| 86 | |
| 87 | if (!(registerB & 0x02) && (time->hour & 0x80)) |
| 88 | { |
| 89 | // 12 hour clock to 24 hour |
| 90 | time->hour = ((time->hour & 0x7F) + 12) % 24; |
| 91 | } |
| 92 | |
| 93 | time->year += (CURRENT_YEAR / 100) * 100; |
| 94 | if (time->year < CURRENT_YEAR) |
| 95 | time->year += 100; |
| 96 | } |
| 97 | |
| 98 | bool rtc_irq_handler(u32 irq, void *data) |
| 99 | { |
| 100 | MOS_UNUSED(data); |
| 101 | MOS_ASSERT(irq == IRQ_CMOS_RTC); |
| 102 | rtc_read_reg(reg: 0x0C); // select register C and ack the interrupt |
| 103 | clocksource_tick(clocksource: &rtc_clocksource); |
| 104 | return true; |
| 105 | } |
| 106 | |
| 107 | void rtc_init() |
| 108 | { |
| 109 | port_outb(port: CMOS_PORT_ADDRESS, value: 0x80 | RTC_STATUS_REG_B); // select register B, and disable NMI |
| 110 | const u8 val = port_inb(port: CMOS_PORT_DATA); // read the current value of register B |
| 111 | port_outb(port: CMOS_PORT_ADDRESS, value: 0x80 | RTC_STATUS_REG_B); // set the index again (a read will reset the index to register D) |
| 112 | port_outb(port: CMOS_PORT_DATA, value: val | 0x40); // write the previous value ORed with 0x40. This turns on bit 6 of register B |
| 113 | |
| 114 | rtc_read_reg(reg: 0x0C); // select register C and ack the interrupt |
| 115 | clocksource_register(clocksource: &rtc_clocksource); |
| 116 | } |
| 117 |