1use core::fmt;
2
3use crate::{cpu, testing};
4
5pub const CURRENT_CENTURY: u16 = 2000 / 100;
6
7pub const RTC_ADDRESS: u16 = 0x70;
8pub const RTC_DATA: u16 = 0x71;
9
10pub const RTC_SECONDS: u8 = 0x00;
11pub const RTC_MINUTES: u8 = 0x02;
12pub const RTC_HOURS: u8 = 0x04;
13pub const RTC_DAY_OF_MONTH: u8 = 0x07;
14pub const RTC_MONTH: u8 = 0x08;
15pub const RTC_YEAR: u8 = 0x09;
16
17pub const RTC_STATUS_A: u8 = 0x0A;
18pub const RTC_STATUS_B: u8 = 0x0B;
19
20pub const SECONDS_PER_MINUTE: u64 = 60;
21pub const SECONDS_PER_HOUR: u64 = 60 * SECONDS_PER_MINUTE;
22pub const SECONDS_PER_DAY: u64 = 24 * SECONDS_PER_HOUR;
23pub const DAYS_PER_MONTH_ARRAY: [u64; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
24
25const UNIX_EPOCH_IN_SECONDS: u64 = 62135596800;
27
28#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
29pub struct RtcTime {
30 pub seconds: u8,
31 pub minutes: u8,
32 pub hours: u8,
33 pub day_of_month: u8,
34 pub month: u8,
35 pub year: u16,
36}
37
38impl RtcTime {
39 pub fn seconds_since_unix_epoch(&self) -> Option<u64> {
40 if self.year < 1970 {
42 return None;
43 }
44
45 let is_year_leap = self.month > 2
46 && self.year.is_multiple_of(4)
47 && (!self.year.is_multiple_of(100) || self.year.is_multiple_of(400));
48
49 let last_year = (self.year - 1) as u64;
50 let days_in_last_years =
51 (last_year * 365) + (last_year / 4) - (last_year / 100) + (last_year / 400);
52 let this_year_days = DAYS_PER_MONTH_ARRAY[self.month as usize - 1]
53 + self.day_of_month as u64
54 - !is_year_leap as u64;
55
56 let total_days = days_in_last_years + this_year_days;
57
58 let timestamp_since_unix = total_days * SECONDS_PER_DAY
59 + self.hours as u64 * SECONDS_PER_HOUR
60 + self.minutes as u64 * SECONDS_PER_MINUTE
61 + self.seconds as u64;
62
63 Some(timestamp_since_unix - UNIX_EPOCH_IN_SECONDS)
64 }
65}
66
67pub struct Rtc {
68 century_reg: Option<u8>,
69}
70
71impl fmt::Display for RtcTime {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(
74 f,
75 "{year:04}-{month:02}-{day_of_month:02} {hours:02}:{minutes:02}:{seconds:02}",
76 year = self.year,
77 month = self.month,
78 day_of_month = self.day_of_month,
79 hours = self.hours,
80 minutes = self.minutes,
81 seconds = self.seconds,
82 )
83 }
84}
85
86impl Rtc {
87 pub const fn new(century_reg: Option<u8>) -> Self {
88 let century_reg = if let Some(century_reg) = century_reg {
89 if century_reg == 0 {
90 None
91 } else {
92 Some(century_reg)
93 }
94 } else {
95 None
96 };
97 Self { century_reg }
98 }
99
100 fn read_register(&self, reg: u8) -> u8 {
101 unsafe {
102 cpu::io_out(RTC_ADDRESS, reg);
103 cpu::io_in(RTC_DATA)
104 }
105 }
106
107 fn is_updating(&self) -> bool {
108 self.read_register(RTC_STATUS_A) & 0x80 != 0
109 }
110
111 fn is_bcd(&self) -> bool {
112 self.read_register(RTC_STATUS_B) & 0x04 == 0
113 }
114
115 fn get_time_sync(&self) -> (RtcTime, u8) {
116 let mut t = RtcTime::default();
118 let mut century = 0;
119
120 loop {
121 while self.is_updating() {}
122 let mut century_new = century;
123 let t_new = RtcTime {
124 seconds: self.read_register(RTC_SECONDS),
125 minutes: self.read_register(RTC_MINUTES),
126 hours: self.read_register(RTC_HOURS),
127 day_of_month: self.read_register(RTC_DAY_OF_MONTH),
128 month: self.read_register(RTC_MONTH),
129 year: self.read_register(RTC_YEAR) as u16,
130 };
131
132 if let Some(century_reg) = self.century_reg {
133 century_new = self.read_register(century_reg);
134 }
135
136 if t_new == t && century_new == century {
138 break;
139 }
140 t = t_new;
141 century = century_new;
142 }
143 (t, century)
144 }
145
146 pub fn get_time(&self) -> RtcTime {
147 let (mut t, mut century) = self.get_time_sync();
148
149 if self.is_bcd() {
150 t.seconds = (t.seconds & 0x0F) + ((t.seconds / 16) * 10);
151 t.minutes = (t.minutes & 0x0F) + ((t.minutes / 16) * 10);
152 t.hours = ((t.hours & 0x0F) + (((t.hours & 0x70) / 16) * 10)) | (t.hours & 0x80);
153 t.day_of_month = (t.day_of_month & 0x0F) + ((t.day_of_month / 16) * 10);
154 t.month = (t.month & 0x0F) + ((t.month / 16) * 10);
155 t.year = (t.year & 0x0F) + ((t.year / 16) * 10);
156 if self.century_reg.is_some() {
157 century = (century & 0x0F) + ((century / 16) * 10);
158 }
159 }
160 if t.hours & 0x80 != 0 {
161 t.hours = ((t.hours & 0x7F) + 12) % 24;
162 }
163 let century = if self.century_reg.is_some() {
164 century as u16
165 } else {
166 CURRENT_CENTURY
167 };
168 t.year += century * 100;
169
170 t
171 }
172}
173
174#[macro_rules_attribute::apply(testing::test)]
175fn test_seconds_since_unix_epoch() {
176 type RtcTestData = (u16, u8, u8, u8, u8, u8);
178 const TESTS: [(RtcTestData, u64); 5] = [
179 ((2024, 1, 1, 12, 3, 45), 1704110625),
180 ((1987, 11, 28, 0, 0, 0), 565056000),
181 ((5135, 3, 4, 9, 33, 45), 99883100025),
182 ((2811, 3, 4, 9, 33, 45), 26544792825),
183 ((2404, 2, 29, 9, 33, 45), 13700828025),
184 ];
185
186 for ((year, month, day, hours, minutes, seconds), expected) in TESTS {
187 let t = RtcTime {
188 seconds,
189 minutes,
190 hours,
191 day_of_month: day,
192 month,
193 year,
194 };
195 assert_eq!(t.seconds_since_unix_epoch(), Some(expected));
196 }
197}