gb_pak/rtc.rs
1//! The MBC3 clock: seconds to days, running on the cartridge battery.
2//!
3//! The clock keeps time while the console is off, so a game can tell how long the
4//! player was away.
5//!
6//! # Latching
7//!
8//! The counters inside the controller are not readable. What the window shows is
9//! a snapshot of them, and [`latch`] takes a fresh one. Reading straight through
10//! without one would mix an old second with a new minute, since the five
11//! registers arrive one at a time and the clock does not wait.
12//!
13//! ```ignore
14//! let (h, m) = gb_pak::rtc::latch(|c| (c.hours(), c.minutes()));
15//! ```
16//!
17//! # Trusting the time
18//!
19//! A cartridge whose clock has never been set may hold noise.
20
21use core::ptr::{read_volatile, write_volatile};
22
23use crate::{WINDOW, reg};
24
25const SECONDS: u8 = 0x08;
26const MINUTES: u8 = 0x09;
27const HOURS: u8 = 0x0A;
28const DAYS_LOW: u8 = 0x0B;
29const FLAGS: u8 = 0x0C;
30
31const FLAG_DAY_HIGH: u8 = 0x01;
32const FLAG_HALTED: u8 = 0x40;
33const FLAG_OVERFLOW: u8 = 0x80;
34
35#[inline]
36fn get(register: u8) -> u8 {
37 reg::select_raw(register);
38 unsafe { read_volatile(WINDOW as *const u8) }
39}
40
41#[inline]
42fn put(register: u8, value: u8) {
43 reg::select_raw(register);
44 unsafe { write_volatile(WINDOW as *mut u8, value) };
45}
46
47/// What the clock counts: time since it was set, not a calendar date.
48///
49/// `days` runs to 511 and then wraps, setting [`Latch::overflowed`]. A game that
50/// wants a date keeps its own epoch and adds this to it.
51#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
52pub struct Time {
53 pub days: u16,
54 pub hours: u8,
55 pub minutes: u8,
56 pub seconds: u8,
57}
58
59/// One snapshot of the clock.
60///
61/// Reading a field is a register select and a load. They all come from the same
62/// snapshot, so an hour and a minute cannot be a tick apart.
63pub struct Latch(());
64
65/// Snapshot the clock, switch the RAM on, and read it inside `f`.
66///
67/// The window holds one thing at a time, so `f` must not reach the save memory
68/// or latch again: the rest of `f` would look at whatever that left selected.
69pub fn latch<R>(f: impl FnOnce(&Latch) -> R) -> R {
70 reg::enable();
71 reg::latch();
72 let r = f(&Latch(()));
73 reg::disable();
74 r
75}
76
77/// Snapshot the clock and read all of it.
78#[inline]
79pub fn time() -> Time {
80 latch(|c| c.time())
81}
82
83impl Latch {
84 /// Seconds, 0 to 59 on a clock that has been set.
85 #[inline]
86 pub fn seconds(&self) -> u8 {
87 get(SECONDS)
88 }
89
90 /// Minutes, 0 to 59 on a clock that has been set.
91 #[inline]
92 pub fn minutes(&self) -> u8 {
93 get(MINUTES)
94 }
95
96 /// Hours, 0 to 23 on a clock that has been set.
97 #[inline]
98 pub fn hours(&self) -> u8 {
99 get(HOURS)
100 }
101
102 /// Days, 0 to 511 before the counter wraps.
103 #[inline]
104 pub fn days(&self) -> u16 {
105 get(DAYS_LOW) as u16 | ((get(FLAGS) & FLAG_DAY_HIGH) as u16) << 8
106 }
107
108 /// All four fields at once.
109 #[inline]
110 pub fn time(&self) -> Time {
111 Time {
112 days: self.days(),
113 hours: self.hours(),
114 minutes: self.minutes(),
115 seconds: self.seconds(),
116 }
117 }
118
119 /// The day counter has wrapped at least once. Stays set until
120 /// [`clear_overflow`].
121 #[inline]
122 pub fn overflowed(&self) -> bool {
123 get(FLAGS) & FLAG_OVERFLOW != 0
124 }
125
126 /// The clock is stopped.
127 #[inline]
128 pub fn halted(&self) -> bool {
129 get(FLAGS) & FLAG_HALTED != 0
130 }
131}
132
133/// Set the clock. Starts it if it was stopped, and clears [`Latch::overflowed`].
134///
135/// Out-of-range fields are written as given.
136pub fn set(time: Time) {
137 reg::enable();
138
139 // The clock has to be stopped across the writes, or it ticks between them and
140 // lands on a time that is part old and part new.
141 put(FLAGS, FLAG_HALTED);
142 put(SECONDS, time.seconds);
143 put(MINUTES, time.minutes);
144 put(HOURS, time.hours);
145 put(DAYS_LOW, time.days as u8);
146 put(FLAGS, ((time.days >> 8) as u8) & FLAG_DAY_HIGH);
147
148 reg::disable();
149}
150
151/// Stop the clock, or start it again.
152///
153/// A stopped clock keeps its reading and does not count.
154pub fn set_halted(halted: bool) {
155 reg::enable();
156 reg::latch();
157 let flags = get(FLAGS);
158 put(FLAGS, if halted { flags | FLAG_HALTED } else { flags & !FLAG_HALTED });
159 reg::disable();
160}
161
162/// Acknowledge a day-counter wrap.
163pub fn clear_overflow() {
164 reg::enable();
165 reg::latch();
166 let flags = get(FLAGS);
167 put(FLAGS, flags & !FLAG_OVERFLOW);
168 reg::disable();
169}