1#![allow(clippy::doc_markdown)]
18
19use rustysnes_savestate::{SaveReader, SaveStateError, SaveWriter};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23enum State {
24 #[default]
25 Mode,
26 Seek,
27 Read,
28 Write,
29}
30
31#[derive(Debug, Clone)]
33pub struct EpsonRtc {
34 secondlo: u8,
36 secondhi: u8,
37 batteryfailure: u8,
38 minutelo: u8,
39 minutehi: u8,
40 resync: u8,
41 hourlo: u8,
42 hourhi: u8,
43 meridian: u8,
44 daylo: u8,
45 dayhi: u8,
46 dayram: u8,
47 monthlo: u8,
48 monthhi: u8,
49 monthram: u8,
50 yearlo: u8,
51 yearhi: u8,
52 weekday: u8,
53 hold: u8,
54 calendar: u8,
55 irqflag: u8,
56 roundseconds: u8,
57 irqmask: u8,
58 irqduty: u8,
59 irqperiod: u8,
60 pause: u8,
61 stop: u8,
62 atime: u8,
63 test: u8,
64
65 chipselect: u8,
67 state: State,
68 offset: u8,
69 ready: bool,
70 mdr: u8,
71}
72
73impl Default for EpsonRtc {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl EpsonRtc {
80 #[must_use]
83 pub const fn new() -> Self {
84 Self {
85 secondlo: 0,
86 secondhi: 0,
87 batteryfailure: 1,
88 minutelo: 0,
89 minutehi: 0,
90 resync: 0,
91 hourlo: 0,
92 hourhi: 0,
93 meridian: 0,
94 daylo: 0,
95 dayhi: 0,
96 dayram: 0,
97 monthlo: 0,
98 monthhi: 0,
99 monthram: 0,
100 yearlo: 0,
101 yearhi: 0,
102 weekday: 0,
103 hold: 0,
104 calendar: 0,
105 irqflag: 0,
106 roundseconds: 0,
107 irqmask: 0,
108 irqduty: 0,
109 irqperiod: 0,
110 pause: 0,
111 stop: 0,
112 atime: 0,
113 test: 0,
114 chipselect: 0,
115 state: State::Mode,
116 offset: 0,
117 ready: false,
118 mdr: 0,
119 }
120 }
121
122 const fn rtc_reset(&mut self) {
123 self.state = State::Mode;
124 self.offset = 0;
125 self.resync = 0;
126 self.pause = 0;
127 self.test = 0;
128 }
129
130 fn rtc_read(&mut self, address: u8) -> u8 {
131 match address & 0xF {
132 0 => self.secondlo,
133 1 => self.secondhi | (self.batteryfailure << 3),
134 2 => self.minutelo,
135 3 => self.minutehi | (self.resync << 3),
136 4 => self.hourlo,
137 5 => self.hourhi | (self.meridian << 2) | (self.resync << 3),
138 6 => self.daylo,
139 7 => self.dayhi | (self.dayram << 2) | (self.resync << 3),
140 8 => self.monthlo,
141 9 => self.monthhi | (self.monthram << 1) | (self.resync << 3),
142 10 => self.yearlo,
143 11 => self.yearhi,
144 12 => self.weekday | (self.resync << 3),
145 13 => {
146 let readflag = u8::from(self.irqflag != 0 && self.irqmask == 0);
147 self.irqflag = 0;
148 self.hold | (self.calendar << 1) | (readflag << 2) | (self.roundseconds << 3)
149 }
150 14 => self.irqmask | (self.irqduty << 1) | (self.irqperiod << 2),
151 _ => self.pause | (self.stop << 1) | (self.atime << 2) | (self.test << 3),
152 }
153 }
154
155 const fn rtc_write(&mut self, address: u8, data: u8) {
156 let data = data & 0xF;
157 match address & 0xF {
158 0 => self.secondlo = data,
159 1 => {
160 self.secondhi = data & 0x7;
161 self.batteryfailure = data >> 3;
162 }
163 2 => self.minutelo = data,
164 3 => self.minutehi = data,
165 4 => self.hourlo = data,
166 5 => {
167 self.hourhi = data;
168 self.meridian = (data >> 2) & 1;
169 if self.atime == 1 {
170 self.meridian = 0;
171 }
172 if self.atime == 0 {
173 self.hourhi &= 1;
174 }
175 }
176 6 => self.daylo = data,
177 7 => {
178 self.dayhi = data & 0x3;
179 self.dayram = data >> 2;
180 }
181 8 => self.monthlo = data,
182 9 => {
183 self.monthhi = data & 0x1;
184 self.monthram = data >> 1;
185 }
186 10 => self.yearlo = data,
187 11 => self.yearhi = data,
188 12 => self.weekday = data,
189 13 => {
190 self.hold = data & 1;
191 self.calendar = (data >> 1) & 1;
192 self.roundseconds = data >> 3;
193 }
194 14 => {
195 self.irqmask = data & 1;
196 self.irqduty = (data >> 1) & 1;
197 self.irqperiod = data >> 2;
198 }
199 _ => {
200 self.pause = data & 1;
201 self.stop = (data >> 1) & 1;
202 self.atime = (data >> 2) & 1;
203 self.test = data >> 3;
204 if self.atime == 1 {
205 self.meridian = 0;
206 }
207 if self.atime == 0 {
208 self.hourhi &= 1;
209 }
210 if self.pause != 0 {
211 self.secondlo = 0;
212 self.secondhi = 0;
213 }
214 }
215 }
216 }
217
218 #[must_use]
220 pub fn read(&mut self, address: u32) -> u8 {
221 match address & 3 {
222 0 => self.chipselect,
223 1 => {
224 if self.chipselect != 1 || !self.ready {
225 return 0;
226 }
227 match self.state {
228 State::Write => self.mdr,
229 State::Read => {
230 let offset = self.offset;
231 self.offset = self.offset.wrapping_add(1);
232 self.rtc_read(offset)
233 }
234 State::Mode | State::Seek => 0,
235 }
236 }
237 2 => u8::from(self.ready) << 7,
238 _ => 0,
239 }
240 }
241
242 pub const fn write(&mut self, address: u32, data: u8) {
244 let data = data & 0xF;
245 match address & 3 {
246 0 => {
247 self.chipselect = data;
248 if self.chipselect != 1 {
249 self.rtc_reset();
250 }
251 self.ready = true;
252 }
253 1 => {
254 if self.chipselect != 1 || !self.ready {
255 return;
256 }
257 match self.state {
258 State::Mode => {
259 if data != 0x03 && data != 0x0c {
260 return;
261 }
262 self.state = State::Seek;
263 self.mdr = data;
264 self.ready = true; }
266 State::Seek => {
267 self.state = if self.mdr == 0x03 {
268 State::Write
269 } else {
270 State::Read
271 };
272 self.offset = data;
273 self.mdr = data;
274 self.ready = true;
275 }
276 State::Write => {
277 let offset = self.offset;
278 self.offset = self.offset.wrapping_add(1);
279 self.rtc_write(offset, data);
280 self.mdr = data;
281 self.ready = true;
282 }
283 State::Read => {}
284 }
285 }
286 _ => {}
287 }
288 }
289
290 pub fn save_state(&self, w: &mut SaveWriter) {
294 w.section(*b"RTC0", |s| {
295 s.write_u8(self.secondlo);
296 s.write_u8(self.secondhi);
297 s.write_u8(self.batteryfailure);
298 s.write_u8(self.minutelo);
299 s.write_u8(self.minutehi);
300 s.write_u8(self.resync);
301 s.write_u8(self.hourlo);
302 s.write_u8(self.hourhi);
303 s.write_u8(self.meridian);
304 s.write_u8(self.daylo);
305 s.write_u8(self.dayhi);
306 s.write_u8(self.dayram);
307 s.write_u8(self.monthlo);
308 s.write_u8(self.monthhi);
309 s.write_u8(self.monthram);
310 s.write_u8(self.yearlo);
311 s.write_u8(self.yearhi);
312 s.write_u8(self.weekday);
313 s.write_u8(self.hold);
314 s.write_u8(self.calendar);
315 s.write_u8(self.irqflag);
316 s.write_u8(self.roundseconds);
317 s.write_u8(self.irqmask);
318 s.write_u8(self.irqduty);
319 s.write_u8(self.irqperiod);
320 s.write_u8(self.pause);
321 s.write_u8(self.stop);
322 s.write_u8(self.atime);
323 s.write_u8(self.test);
324 s.write_u8(self.chipselect);
325 s.write_u8(match self.state {
326 State::Mode => 0,
327 State::Seek => 1,
328 State::Read => 2,
329 State::Write => 3,
330 });
331 s.write_u8(self.offset);
332 s.write_bool(self.ready);
333 s.write_u8(self.mdr);
334 });
335 }
336
337 pub fn load_state(&mut self, r: &mut SaveReader) -> Result<(), SaveStateError> {
349 let mut s = r.expect_section(*b"RTC0")?;
350 self.secondlo = s.read_u8()? & 0x0F;
351 self.secondhi = s.read_u8()? & 0x07;
352 self.batteryfailure = s.read_u8()? & 0x01;
353 self.minutelo = s.read_u8()? & 0x0F;
354 self.minutehi = s.read_u8()? & 0x0F;
355 self.resync = s.read_u8()? & 0x01;
356 self.hourlo = s.read_u8()? & 0x0F;
357 self.hourhi = s.read_u8()? & 0x0F;
358 self.meridian = s.read_u8()? & 0x01;
359 self.daylo = s.read_u8()? & 0x0F;
360 self.dayhi = s.read_u8()? & 0x03;
361 self.dayram = s.read_u8()? & 0x03;
362 self.monthlo = s.read_u8()? & 0x0F;
363 self.monthhi = s.read_u8()? & 0x01;
364 self.monthram = s.read_u8()? & 0x07;
365 self.yearlo = s.read_u8()? & 0x0F;
366 self.yearhi = s.read_u8()? & 0x0F;
367 self.weekday = s.read_u8()? & 0x0F;
368 self.hold = s.read_u8()? & 0x01;
369 self.calendar = s.read_u8()? & 0x01;
370 self.irqflag = s.read_u8()? & 0x01;
371 self.roundseconds = s.read_u8()? & 0x01;
372 self.irqmask = s.read_u8()? & 0x01;
373 self.irqduty = s.read_u8()? & 0x01;
374 self.irqperiod = s.read_u8()? & 0x03;
375 self.pause = s.read_u8()? & 0x01;
376 self.stop = s.read_u8()? & 0x01;
377 self.atime = s.read_u8()? & 0x01;
378 self.test = s.read_u8()? & 0x01;
379 self.chipselect = s.read_u8()? & 0x0F;
380 let state = s.read_u8()?;
381 self.state = match state {
382 0 => State::Mode,
383 1 => State::Seek,
384 2 => State::Read,
385 3 => State::Write,
386 _ => {
387 return Err(SaveStateError::Invalid(alloc::format!(
388 "EpsonRtc state discriminant {state} is not a valid State variant (0-3)"
389 )));
390 }
391 };
392 self.offset = s.read_u8()? & 0x0F;
393 self.ready = s.read_bool()?;
394 self.mdr = s.read_u8()? & 0x0F;
395 if s.remaining() != 0 {
396 return Err(SaveStateError::Invalid(alloc::format!(
397 "RTC0 section has {} trailing byte(s)",
398 s.remaining()
399 )));
400 }
401 Ok(())
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn chipselect_then_write_read_roundtrip() {
411 let mut rtc = EpsonRtc::new();
412 rtc.write(0, 1); assert_eq!(rtc.read(0), 1);
414 rtc.write(1, 0x03); rtc.write(1, 0x00); rtc.write(1, 0x05); rtc.write(0, 0); rtc.write(0, 1); rtc.write(1, 0x0c); rtc.write(1, 0x00); assert_eq!(rtc.read(1), 5);
422 }
423
424 #[test]
425 fn ready_flag_reads_high_bit() {
426 let mut rtc = EpsonRtc::new();
427 rtc.write(0, 1);
428 assert_eq!(rtc.read(2), 0x80);
429 }
430
431 #[test]
432 fn clock_and_handshake_state_round_trips_through_save_state() {
433 let mut rtc = EpsonRtc::new();
434 rtc.write(0, 1);
435 rtc.write(1, 0x03); rtc.write(1, 0x00); rtc.write(1, 0x05); let mut w = SaveWriter::new();
440 rtc.save_state(&mut w);
441 let bytes = w.into_bytes();
442
443 let mut fresh = EpsonRtc::new();
444 let mut r = SaveReader::new(&bytes);
445 fresh.load_state(&mut r).unwrap();
446
447 assert_eq!(fresh.secondlo, 5);
448 assert_eq!(fresh.state, State::Write);
449 assert_eq!(r.remaining(), 0);
450 }
451
452 #[test]
453 fn out_of_range_state_discriminant_is_rejected_not_panicked_on() {
454 let rtc = EpsonRtc::new();
455 let mut w = SaveWriter::new();
456 rtc.save_state(&mut w);
457 let mut bytes = w.into_bytes();
458 let mut r = SaveReader::new(&bytes);
461 let mut s = r.expect_section(*b"RTC0").unwrap();
462 for _ in 0..30 {
463 s.read_u8().unwrap();
464 }
465 let offset = bytes.len() - s.remaining();
466 bytes[offset] = 99;
467
468 let mut fresh = EpsonRtc::new();
469 let mut r2 = SaveReader::new(&bytes);
470 assert!(matches!(
471 fresh.load_state(&mut r2),
472 Err(SaveStateError::Invalid(_))
473 ));
474 }
475}