Skip to main content

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}