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