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