feat(cartridge): implement RTC calendar transfers and clock updates
This commit is contained in:
parent
692be36349
commit
fce222eea2
22 changed files with 1227 additions and 196 deletions
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
use crate::memory::MemoryError;
|
||||
|
||||
mod calendar;
|
||||
mod rtc;
|
||||
pub use calendar::{RtcDateTime, RtcError};
|
||||
use rtc::Rtc;
|
||||
|
||||
pub const GPIO_DATA: u32 = 0x0800_00c4;
|
||||
pub const GPIO_DIRECTION: u32 = 0x0800_00c6;
|
||||
pub const GPIO_CONTROL: u32 = 0x0800_00c8;
|
||||
|
|
@ -11,7 +16,7 @@ pub const GPIO_CONTROL: u32 = 0x0800_00c8;
|
|||
pub enum CartridgeHardware {
|
||||
#[default]
|
||||
None,
|
||||
/// GPIO and RTC command/control only. Calendar and interrupt commands remain diagnostic.
|
||||
/// GPIO and RTC calendar/control. Time advances only through explicit caller updates.
|
||||
Rtc,
|
||||
}
|
||||
|
||||
|
|
@ -27,6 +32,29 @@ impl Cartridge {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rtc_datetime(&self) -> Option<RtcDateTime> {
|
||||
self.gpio.map(|gpio| gpio.rtc.calendar)
|
||||
}
|
||||
|
||||
pub(crate) fn set_rtc_datetime(&mut self, date: RtcDateTime) -> Result<(), RtcError> {
|
||||
self.gpio
|
||||
.as_mut()
|
||||
.ok_or(RtcError::NotAttached)?
|
||||
.rtc
|
||||
.calendar = date;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn advance_rtc_seconds(&mut self, seconds: u64) -> Result<(), RtcError> {
|
||||
self.gpio
|
||||
.as_mut()
|
||||
.ok_or(RtcError::NotAttached)?
|
||||
.rtc
|
||||
.calendar
|
||||
.advance(seconds);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn mapped(&self, address: u32) -> bool {
|
||||
self.gpio.is_some() && (GPIO_DATA..GPIO_CONTROL + 2).contains(&address)
|
||||
}
|
||||
|
|
@ -97,141 +125,3 @@ impl Gpio {
|
|||
(self.latch & self.direction) | (u8::from(self.rtc.output) << 1 & !self.direction)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct Rtc {
|
||||
control: u8,
|
||||
selected: bool,
|
||||
clock: bool,
|
||||
sample: bool,
|
||||
output: bool,
|
||||
phase: Phase,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Phase {
|
||||
Command { byte: u8, bits: u8 },
|
||||
WriteControl { byte: u8, bits: u8 },
|
||||
ReadControl { byte: u8, bits: u8 },
|
||||
Done,
|
||||
}
|
||||
|
||||
impl Default for Rtc {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
control: 0x40, // Deterministic powered 24-hour mode, not a sampled host clock.
|
||||
selected: false,
|
||||
clock: true,
|
||||
sample: true,
|
||||
output: true,
|
||||
phase: Phase::Command { byte: 0, bits: 0 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rtc {
|
||||
fn update(&mut self, pins: u8, direction: u8) -> Result<(), &'static str> {
|
||||
let selected = pins & 4 != 0;
|
||||
let clock = pins & 1 != 0;
|
||||
let data = pins & 2 != 0;
|
||||
if !selected {
|
||||
self.selected = false;
|
||||
self.clock = true;
|
||||
self.output = true;
|
||||
self.phase = Phase::Command { byte: 0, bits: 0 };
|
||||
return Ok(());
|
||||
}
|
||||
if !self.selected {
|
||||
if !clock {
|
||||
return Err("RTC select without high clock");
|
||||
}
|
||||
self.selected = true;
|
||||
self.clock = true;
|
||||
return Ok(());
|
||||
}
|
||||
if !clock {
|
||||
match self.phase {
|
||||
Phase::Command { .. } | Phase::WriteControl { .. } => {
|
||||
if direction & 2 == 0 {
|
||||
return Err("RTC command/data write with SIO input");
|
||||
}
|
||||
self.sample = data;
|
||||
}
|
||||
Phase::ReadControl { byte, bits } if self.clock => {
|
||||
if direction & 2 != 0 {
|
||||
return Err("RTC read with SIO output");
|
||||
}
|
||||
self.output = byte & (1 << bits) != 0;
|
||||
self.phase = if bits == 7 {
|
||||
Phase::Done
|
||||
} else {
|
||||
Phase::ReadControl {
|
||||
byte,
|
||||
bits: bits + 1,
|
||||
}
|
||||
};
|
||||
}
|
||||
Phase::Done if self.clock => return Err("RTC clocks beyond command length"),
|
||||
_ => {}
|
||||
}
|
||||
} else if !self.clock {
|
||||
match self.phase {
|
||||
Phase::Command { byte, bits } | Phase::WriteControl { byte, bits } => {
|
||||
if direction & 2 == 0 || data != self.sample {
|
||||
return Err("RTC SIO change at sampling edge");
|
||||
}
|
||||
let byte = byte | (u8::from(self.sample) << bits);
|
||||
let command = matches!(self.phase, Phase::Command { .. });
|
||||
self.phase = if bits != 7 {
|
||||
if command {
|
||||
Phase::Command {
|
||||
byte,
|
||||
bits: bits + 1,
|
||||
}
|
||||
} else {
|
||||
Phase::WriteControl {
|
||||
byte,
|
||||
bits: bits + 1,
|
||||
}
|
||||
}
|
||||
} else if command {
|
||||
self.command(byte)?
|
||||
} else {
|
||||
// Bits 1/3/5 lack an implemented IRQ/control model.
|
||||
if byte & 0x2a != 0 {
|
||||
return Err("RTC interrupt/unknown control bits");
|
||||
}
|
||||
self.control = byte & 0x40;
|
||||
Phase::Done
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.clock = clock;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn command(&mut self, byte: u8) -> Result<Phase, &'static str> {
|
||||
// The first four wire bits are 0,1,1,0. Translate GBATEK's LSB-first
|
||||
// representation to its MSB-first command numbering; parameters stay LSB first.
|
||||
if byte & 15 != 6 {
|
||||
return Err("RTC command encoding");
|
||||
}
|
||||
let command = byte.reverse_bits();
|
||||
match (command >> 1) & 7 {
|
||||
0 => {
|
||||
self.control = 0;
|
||||
Ok(Phase::Done)
|
||||
}
|
||||
1 if command & 1 != 0 => Ok(Phase::ReadControl {
|
||||
byte: self.control,
|
||||
bits: 0,
|
||||
}),
|
||||
1 => Ok(Phase::WriteControl { byte: 0, bits: 0 }),
|
||||
2 | 3 => Err("RTC calendar access"),
|
||||
6 => Err("RTC force interrupt"),
|
||||
_ => Err("RTC unused command"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue