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
14static clocksource_t rtc_clocksource = {
15 .name = "rtc",
16 .ticks = 0,
17 .frequency = 1000,
18};
19
20static const u8 RTC_STATUS_REG_B = 0x0B;
21static const u8 CMOS_PORT_ADDRESS = 0x70;
22static const u8 CMOS_PORT_DATA = 0x71;
23
24u8 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
30u8 rtc_is_update_in_progress()
31{
32 return rtc_read_reg(reg: 0x0A) & 0x80;
33}
34
35void 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
98bool 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
107void 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