core: More RTC work
Former-commit-id: be2955b08d65c547d206d637ae499dbad892a87a Former-commit-id: 19f0e4e6f9c598c35fb8172d93a1519f921eb27f
This commit is contained in:
parent
304ac31c02
commit
8e1ba1117c
|
@ -24,6 +24,10 @@ args:
|
|||
- flash64k
|
||||
- eeprom
|
||||
- autodetect
|
||||
- rtc:
|
||||
long: rtc
|
||||
help: Force cartridge to have RTC
|
||||
required: false
|
||||
- skip_bios:
|
||||
long: skip-bios
|
||||
help: Skip running bios and start from the ROM instead
|
||||
|
|
|
@ -245,12 +245,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
let mut rom_name = Path::new(&rom_path).file_name().unwrap().to_str().unwrap();
|
||||
|
||||
let gamepak = GamepakBuilder::new()
|
||||
let mut builder = GamepakBuilder::new()
|
||||
.save_type(BackupType::try_from(
|
||||
matches.value_of("save_type").unwrap(),
|
||||
)?)
|
||||
.file(Path::new(&rom_path))
|
||||
.build()?;
|
||||
.file(Path::new(&rom_path));
|
||||
|
||||
if matches.occurrences_of("rtc") != 0 {
|
||||
builder = builder.with_rtc();
|
||||
}
|
||||
|
||||
let gamepak = builder.build()?;
|
||||
|
||||
let mut gba = GameBoyAdvance::new(
|
||||
bios_bin.into_boxed_slice(),
|
||||
|
|
|
@ -5,13 +5,14 @@ authors = ["Michel Heily <michelheily@gmail.com>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde = { version = "1.0.104", features = ["derive", "rc"] }
|
||||
bincode = "1.2.1"
|
||||
byteorder = "1"
|
||||
num = "0.2.1"
|
||||
num-traits = "0.2"
|
||||
enum-primitive-derive = "^0.1"
|
||||
bit = "^0.1"
|
||||
chrono = "0.4"
|
||||
colored = "1.9"
|
||||
ansi_term = "0.12.1"
|
||||
hexdump = "0.1.0"
|
||||
|
@ -36,6 +37,7 @@ gdbstub = { version = "0.1.2", optional = true, features = ["std"] }
|
|||
ringbuf = "0.2.1"
|
||||
goblin = { version = "0.2", optional = true }
|
||||
fuzzy-matcher = { version = "0.3.4", optional = true }
|
||||
bit_reverse = "0.1.8"
|
||||
|
||||
[target.'cfg(target_arch="wasm32")'.dependencies]
|
||||
instant = { version = "0.1.2", features = ["wasm-bindgen"] }
|
||||
|
|
|
@ -14,12 +14,21 @@ use super::Cartridge;
|
|||
|
||||
use super::loader::{load_from_bytes, load_from_file, LoadRom};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GpioDeviceType {
|
||||
Rtc,
|
||||
SolarSensor,
|
||||
Gyro,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GamepakBuilder {
|
||||
path: Option<PathBuf>,
|
||||
bytes: Option<Box<[u8]>>,
|
||||
save_path: Option<PathBuf>,
|
||||
save_type: BackupType,
|
||||
gpio_device: GpioDeviceType,
|
||||
create_backup_file: bool,
|
||||
}
|
||||
|
||||
|
@ -30,6 +39,7 @@ impl GamepakBuilder {
|
|||
path: None,
|
||||
save_path: None,
|
||||
bytes: None,
|
||||
gpio_device: GpioDeviceType::None,
|
||||
create_backup_file: true,
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +94,11 @@ impl GamepakBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_rtc(mut self) -> Self {
|
||||
self.gpio_device = GpioDeviceType::Rtc;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(mut self) -> GBAResult<Cartridge> {
|
||||
let (bytes, symbols) = if let Some(bytes) = self.bytes {
|
||||
match load_from_bytes(bytes.to_vec())? {
|
||||
|
@ -127,7 +142,14 @@ impl GamepakBuilder {
|
|||
|
||||
let backup = create_backup(self.save_type, self.save_path);
|
||||
|
||||
let gpio = Gpio::new();
|
||||
let gpio = match self.gpio_device {
|
||||
GpioDeviceType::None => Gpio::new_none(),
|
||||
GpioDeviceType::Rtc => {
|
||||
info!("Emulating RTC!");
|
||||
Gpio::new_rtc()
|
||||
}
|
||||
_ => unimplemented!("Gpio device {:?} not implemented", self.gpio_device),
|
||||
};
|
||||
|
||||
let size = bytes.len();
|
||||
Ok(Cartridge {
|
||||
|
|
|
@ -1,90 +1,138 @@
|
|||
use super::rtc::Rtc;
|
||||
use super::{GPIO_PORT_CONTROL, GPIO_PORT_DATA, GPIO_PORT_DIRECTION};
|
||||
|
||||
use bit::BitIndex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||
enum GpioDirection {
|
||||
In = 0,
|
||||
Out = 1,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||
enum GpioPortControl {
|
||||
WriteOnly = 0,
|
||||
ReadWrite = 1,
|
||||
}
|
||||
|
||||
trait GpioDevice: Sized {
|
||||
fn write(&mut self);
|
||||
fn read(&mut self);
|
||||
}
|
||||
|
||||
enum GpioDeviceType {
|
||||
Rtc,
|
||||
SolarSensor,
|
||||
Gyro,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Gpio {
|
||||
rtc: Option<RefCell<Rtc>>,
|
||||
direction: [GpioDirection; 4],
|
||||
control: GpioPortControl,
|
||||
}
|
||||
|
||||
impl Gpio {
|
||||
pub fn new() -> Self {
|
||||
Gpio {
|
||||
rtc: None,
|
||||
direction: [GpioDirection::In; 4],
|
||||
control: GpioPortControl::WriteOnly,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_readable(&self) -> bool {
|
||||
self.control != GpioPortControl::WriteOnly
|
||||
}
|
||||
|
||||
pub fn read(&self, addr: u32) -> u16 {
|
||||
match addr {
|
||||
GPIO_PORT_DATA => unimplemented!(),
|
||||
GPIO_PORT_DIRECTION => {
|
||||
let mut direction = 0u16;
|
||||
for i in 0..4 {
|
||||
direction.set_bit(i, self.direction[i] == GpioDirection::Out);
|
||||
}
|
||||
direction
|
||||
}
|
||||
GPIO_PORT_CONTROL => self.control as u16,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, addr: u32) -> u16 {
|
||||
match addr {
|
||||
GPIO_PORT_DATA => unimplemented!(),
|
||||
GPIO_PORT_DIRECTION => {
|
||||
let mut direction = 0u16;
|
||||
for i in 0..4 {
|
||||
direction.set_bit(i, self.direction[i] == GpioDirection::Out);
|
||||
}
|
||||
direction
|
||||
}
|
||||
GPIO_PORT_CONTROL => self.control as u16,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_gpio() {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
use super::rtc::Rtc;
|
||||
use super::{GPIO_PORT_CONTROL, GPIO_PORT_DATA, GPIO_PORT_DIRECTION};
|
||||
|
||||
use bit::BitIndex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Struct holding the logical state of a serial port
|
||||
#[repr(transparent)]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
pub struct GpioPort(u16);
|
||||
|
||||
impl GpioPort {
|
||||
pub fn get(&self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn set(&mut self, value: u16) {
|
||||
self.0 = value & 1;
|
||||
}
|
||||
|
||||
pub fn high(&self) -> bool {
|
||||
self.0 != 0
|
||||
}
|
||||
|
||||
pub fn low(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
}
|
||||
|
||||
pub const GPIO_LOW: GpioPort = GpioPort(0);
|
||||
pub const GPIO_HIGH: GpioPort = GpioPort(1);
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||
pub enum GpioDirection {
|
||||
/// GPIO to GBA
|
||||
In = 0,
|
||||
/// GBA to GPIO
|
||||
Out = 1,
|
||||
}
|
||||
|
||||
pub type GpioState = [GpioDirection; 4];
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||
enum GpioPortControl {
|
||||
WriteOnly = 0,
|
||||
ReadWrite = 1,
|
||||
}
|
||||
|
||||
pub trait GpioDevice: Sized {
|
||||
fn write(&mut self, gpio_state: &GpioState, data: u16);
|
||||
fn read(&self, gpio_state: &GpioState) -> u16;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Gpio {
|
||||
rtc: Option<Rtc>,
|
||||
direction: GpioState,
|
||||
control: GpioPortControl,
|
||||
}
|
||||
|
||||
impl Gpio {
|
||||
pub fn new_none() -> Self {
|
||||
Gpio {
|
||||
rtc: None,
|
||||
direction: [GpioDirection::Out; 4],
|
||||
control: GpioPortControl::WriteOnly,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_rtc() -> Self {
|
||||
Gpio {
|
||||
rtc: Some(Rtc::new()),
|
||||
direction: [GpioDirection::Out; 4],
|
||||
control: GpioPortControl::WriteOnly,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_readable(&self) -> bool {
|
||||
self.control != GpioPortControl::WriteOnly
|
||||
}
|
||||
|
||||
pub fn read(&self, addr: u32) -> u16 {
|
||||
match addr {
|
||||
GPIO_PORT_DATA => {
|
||||
if let Some(rtc) = &self.rtc {
|
||||
rtc.read(&self.direction)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
GPIO_PORT_DIRECTION => {
|
||||
let mut direction = 0u16;
|
||||
for i in 0..4 {
|
||||
direction.set_bit(i, self.direction[i] == GpioDirection::Out);
|
||||
}
|
||||
direction
|
||||
}
|
||||
GPIO_PORT_CONTROL => self.control as u16,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, addr: u32, value: u16) {
|
||||
match addr {
|
||||
GPIO_PORT_DATA => {
|
||||
if let Some(rtc) = &mut self.rtc {
|
||||
rtc.write(&self.direction, value);
|
||||
}
|
||||
}
|
||||
GPIO_PORT_DIRECTION => {
|
||||
for i in 0..4 {
|
||||
if value.bit(i) {
|
||||
self.direction[i] = GpioDirection::Out;
|
||||
} else {
|
||||
self.direction[i] = GpioDirection::In;
|
||||
}
|
||||
}
|
||||
}
|
||||
GPIO_PORT_CONTROL => {
|
||||
info!("GPIO port control: {:?}", self.control);
|
||||
self.control = if value != 0 {
|
||||
GpioPortControl::ReadWrite
|
||||
} else {
|
||||
GpioPortControl::WriteOnly
|
||||
};
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_gpio() {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ mod builder;
|
|||
mod loader;
|
||||
pub use builder::GamepakBuilder;
|
||||
|
||||
pub const GPIO_PORT_DATA: u32 = 0x0800_00C4;
|
||||
pub const GPIO_PORT_DIRECTION: u32 = 0x0800_00C6;
|
||||
pub const GPIO_PORT_CONTROL: u32 = 0x0800_00C8;
|
||||
pub const GPIO_PORT_DATA: u32 = 0xC4;
|
||||
pub const GPIO_PORT_DIRECTION: u32 = 0xC6;
|
||||
pub const GPIO_PORT_CONTROL: u32 = 0xC8;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum BackupMedia {
|
||||
|
@ -56,7 +56,7 @@ use super::sysbus::consts::*;
|
|||
pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
|
||||
|
||||
fn is_gpio_access(addr: u32) -> bool {
|
||||
match addr {
|
||||
match addr & 0x1ff_ffff {
|
||||
GPIO_PORT_DATA | GPIO_PORT_DIRECTION | GPIO_PORT_CONTROL => true,
|
||||
_ => false,
|
||||
}
|
||||
|
@ -82,8 +82,11 @@ impl Bus for Cartridge {
|
|||
}
|
||||
|
||||
fn read_16(&self, addr: u32) -> u16 {
|
||||
if is_gpio_access(addr) && self.gpio.is_readable() {
|
||||
return self.gpio.read(addr);
|
||||
if is_gpio_access(addr) {
|
||||
if !(self.gpio.is_readable()) {
|
||||
info!("trying to read GPIO when reads are not allowed");
|
||||
}
|
||||
return self.gpio.read(addr & 0x1ff_ffff);
|
||||
}
|
||||
|
||||
if addr & 0xff000000 == GAMEPAK_WS2_HI
|
||||
|
@ -109,7 +112,7 @@ impl Bus for Cartridge {
|
|||
|
||||
fn write_16(&mut self, addr: u32, value: u16) {
|
||||
if is_gpio_access(addr) {
|
||||
self.gpio.write(addr);
|
||||
self.gpio.write(addr & 0x1ff_ffff, value);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,613 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
enum Port {
|
||||
#[doc("Serial Clock")]
|
||||
Sck = 0,
|
||||
#[doc("Serial IO")]
|
||||
Sio = 1,
|
||||
#[doc("Chip Select")]
|
||||
Cs = 2,
|
||||
}
|
||||
|
||||
/// Model of the S3511 8pin RTC
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Rtc {}
|
||||
|
||||
impl Rtc {
|
||||
pub fn new() -> Self {
|
||||
Rtc {}
|
||||
}
|
||||
|
||||
pub fn read_port(port: usize) -> u8 {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn write_port(port: usize) {}
|
||||
}
|
||||
use bit::BitIndex;
|
||||
use bit_reverse::LookupReverse;
|
||||
use chrono::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use num::FromPrimitive;
|
||||
|
||||
use std::cmp;
|
||||
|
||||
use super::gpio::{GpioDevice, GpioDirection, GpioPort, GpioState, GPIO_LOW};
|
||||
|
||||
fn num2bcd(mut num: u8) -> u8 {
|
||||
num = cmp::min(num, 99);
|
||||
|
||||
let mut bcd = 0;
|
||||
let mut digit = 1;
|
||||
|
||||
while num > 0 {
|
||||
let x = num % 10;
|
||||
bcd += x * digit;
|
||||
digit = digit << 4;
|
||||
num /= 10;
|
||||
}
|
||||
bcd
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
||||
enum Port {
|
||||
#[doc("Serial Clock")]
|
||||
Sck = 0,
|
||||
#[doc("Serial IO")]
|
||||
Sio = 1,
|
||||
#[doc("Chip Select")]
|
||||
Cs = 2,
|
||||
}
|
||||
|
||||
impl Port {
|
||||
#[inline]
|
||||
pub fn index(self) -> usize {
|
||||
self as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// RTC Registers codes in the GBA
|
||||
#[derive(Primitive, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
||||
enum RegisterKind {
|
||||
ForceReset = 0,
|
||||
DateTime = 2,
|
||||
ForceIrq = 3,
|
||||
Status = 4,
|
||||
Time = 6,
|
||||
Free = 7,
|
||||
}
|
||||
|
||||
impl RegisterKind {
|
||||
fn param_count(&self) -> u8 {
|
||||
use RegisterKind::*;
|
||||
match self {
|
||||
ForceReset => 0,
|
||||
DateTime => 7,
|
||||
ForceIrq => 0,
|
||||
Status => 1,
|
||||
Time => 3,
|
||||
Free => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
||||
enum State {
|
||||
Idle,
|
||||
WaitForChipSelect,
|
||||
GetCommandByte,
|
||||
RxFromMaster {
|
||||
reg: RegisterKind,
|
||||
byte_count: u8,
|
||||
byte_index: u8,
|
||||
},
|
||||
TxToMaster {
|
||||
byte_count: u8,
|
||||
byte_index: u8,
|
||||
},
|
||||
}
|
||||
|
||||
/// A Simple LSB-first 8-bit queue
|
||||
#[derive(Serialize, Deserialize, Clone, DebugStub)]
|
||||
struct SerialBuffer {
|
||||
byte: u8,
|
||||
counter: usize,
|
||||
}
|
||||
|
||||
impl SerialBuffer {
|
||||
fn new() -> Self {
|
||||
SerialBuffer {
|
||||
byte: 0,
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push_bit(&mut self, bit: bool) {
|
||||
if self.counter == 8 {
|
||||
return;
|
||||
}
|
||||
self.byte.set_bit(self.counter, bit);
|
||||
self.counter += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pop_bit(&mut self) -> bool {
|
||||
if self.counter > 0 {
|
||||
let result = self.byte.bit(0);
|
||||
self.byte = self.byte.wrapping_shr(1);
|
||||
self.counter -= 1;
|
||||
result
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.byte = 0;
|
||||
self.counter = 0;
|
||||
}
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.counter
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.count() == 0
|
||||
}
|
||||
|
||||
fn is_full(&self) -> bool {
|
||||
self.count() == 8
|
||||
}
|
||||
|
||||
fn value(&self) -> Option<u8> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mask = if self.is_full() {
|
||||
0b11111111
|
||||
} else {
|
||||
(1 << self.counter) - 1
|
||||
};
|
||||
Some(self.byte & u8::from(mask))
|
||||
}
|
||||
}
|
||||
|
||||
fn load_byte(&mut self, value: u8) {
|
||||
self.byte = value;
|
||||
self.counter = 8;
|
||||
}
|
||||
|
||||
fn take_byte(&mut self) -> Option<u8> {
|
||||
let result = self.value();
|
||||
self.reset();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Model of the S3511 8pin RTC
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Rtc {
|
||||
state: State,
|
||||
sck: GpioPort,
|
||||
sio: GpioPort,
|
||||
cs: GpioPort,
|
||||
status: registers::StatusRegister,
|
||||
serial_buffer: SerialBuffer,
|
||||
internal_buffer: [u8; 8],
|
||||
}
|
||||
|
||||
impl Rtc {
|
||||
pub fn new() -> Self {
|
||||
Rtc {
|
||||
state: State::Idle,
|
||||
sck: GPIO_LOW,
|
||||
sio: GPIO_LOW,
|
||||
cs: GPIO_LOW,
|
||||
status: registers::StatusRegister {
|
||||
mode_24h: true,
|
||||
..Default::default()
|
||||
},
|
||||
serial_buffer: SerialBuffer::new(),
|
||||
internal_buffer: [0; 8],
|
||||
}
|
||||
}
|
||||
|
||||
fn serial_read(&mut self) {
|
||||
self.serial_buffer.push_bit(self.sio.high());
|
||||
}
|
||||
|
||||
fn force_reset(&mut self) {
|
||||
self.serial_buffer.reset();
|
||||
self.state = State::Idle;
|
||||
self.status.write(0);
|
||||
// TODO according to the datasheet, the date time registers should reset to 0-0-0-0..
|
||||
}
|
||||
|
||||
/// Loads a register contents into an internal buffer
|
||||
fn load_register(&mut self, r: RegisterKind) {
|
||||
match r {
|
||||
RegisterKind::Status => self.internal_buffer[0] = self.status.read(),
|
||||
RegisterKind::DateTime => {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
let year = local.year();
|
||||
assert!(year >= 2000 && year <= 2099); // Wonder if I will leave to see this assert fail
|
||||
|
||||
let hour = if self.status.mode_24h {
|
||||
local.hour()
|
||||
} else {
|
||||
let (_, hour12) = local.hour12();
|
||||
hour12 - 1
|
||||
};
|
||||
|
||||
self.internal_buffer[0] = num2bcd((year % 100) as u8);
|
||||
self.internal_buffer[1] = num2bcd(local.month() as u8);
|
||||
self.internal_buffer[2] = num2bcd(local.day() as u8);
|
||||
self.internal_buffer[3] = num2bcd(local.weekday().number_from_monday() as u8);
|
||||
self.internal_buffer[4] = num2bcd(hour as u8);
|
||||
self.internal_buffer[5] = num2bcd(local.minute() as u8);
|
||||
self.internal_buffer[6] = num2bcd(local.second() as u8);
|
||||
|
||||
println!(
|
||||
"DateTime result: {:x?} dt={:?}",
|
||||
self.internal_buffer, local
|
||||
);
|
||||
}
|
||||
RegisterKind::Time => {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
let hour = if self.status.mode_24h {
|
||||
local.hour()
|
||||
} else {
|
||||
let (_, hour12) = local.hour12();
|
||||
hour12 - 1
|
||||
};
|
||||
self.internal_buffer[0] = num2bcd(hour as u8);
|
||||
self.internal_buffer[1] = num2bcd(local.minute() as u8);
|
||||
self.internal_buffer[2] = num2bcd(local.second() as u8);
|
||||
}
|
||||
_ => warn!("RTC: read {:?} not implemented", r),
|
||||
}
|
||||
}
|
||||
|
||||
fn store_register(&mut self, r: RegisterKind) {
|
||||
use RegisterKind::*;
|
||||
info!("write register {:?} {:?}", r, self.internal_buffer);
|
||||
match r {
|
||||
Status => self.status.write(self.internal_buffer[0]),
|
||||
ForceReset => self.force_reset(),
|
||||
_ => warn!("RTC: write {:?} not implemented", r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GpioDevice for Rtc {
|
||||
fn write(&mut self, gpio_state: &GpioState, data: u16) {
|
||||
assert_eq!(gpio_state[Port::Sck.index()], GpioDirection::Out);
|
||||
assert_eq!(gpio_state[Port::Cs.index()], GpioDirection::Out);
|
||||
|
||||
let old_sck = self.sck;
|
||||
let old_cs = self.cs;
|
||||
|
||||
self.sck.set(data.bit(Port::Sck.index()) as u16);
|
||||
self.cs.set(data.bit(Port::Cs.index()) as u16);
|
||||
|
||||
let sck_rising_edge = old_sck.low() && self.sck.high();
|
||||
let sck_falling_edge = old_sck.high() && self.sck.low();
|
||||
|
||||
if sck_falling_edge && gpio_state[Port::Sio.index()] == GpioDirection::Out {
|
||||
self.sio.set(data.bit(Port::Sio.index()) as u16);
|
||||
}
|
||||
|
||||
if self.cs.high() && old_cs.low() {
|
||||
trace!("RTC CS went from low to high!");
|
||||
}
|
||||
|
||||
use State::*;
|
||||
match self.state {
|
||||
Idle => {
|
||||
if self.sck.high() && self.cs.low() {
|
||||
self.state = WaitForChipSelect;
|
||||
}
|
||||
}
|
||||
WaitForChipSelect => {
|
||||
if self.sck.high() && self.cs.high() {
|
||||
self.state = GetCommandByte;
|
||||
self.serial_buffer.reset();
|
||||
}
|
||||
}
|
||||
GetCommandByte => {
|
||||
if !sck_falling_edge {
|
||||
return;
|
||||
}
|
||||
|
||||
// receive bit
|
||||
self.serial_read();
|
||||
if !self.serial_buffer.is_full() {
|
||||
return;
|
||||
}
|
||||
|
||||
// finished collecting all the bits
|
||||
let mut command = self.serial_buffer.value().unwrap();
|
||||
self.serial_buffer.reset();
|
||||
|
||||
let lsb_first = command.bit_range(0..4) == 0b0110;
|
||||
if !lsb_first && command.bit_range(4..8) != 0b0110 {
|
||||
panic!("RTC bad command format");
|
||||
}
|
||||
|
||||
if !lsb_first {
|
||||
command = command.swap_bits();
|
||||
}
|
||||
|
||||
let reg = RegisterKind::from_u8(command.bit_range(4..7)).expect("RTC bad register");
|
||||
let byte_count = reg.param_count();
|
||||
|
||||
let is_read_operation = command.bit(7);
|
||||
|
||||
info!(
|
||||
"got command: {} {:?}",
|
||||
if is_read_operation { "READ" } else { "WRITE" },
|
||||
reg
|
||||
);
|
||||
|
||||
if byte_count != 0 {
|
||||
if is_read_operation {
|
||||
self.load_register(reg);
|
||||
self.state = TxToMaster {
|
||||
byte_count,
|
||||
byte_index: 0,
|
||||
};
|
||||
} else {
|
||||
self.state = RxFromMaster {
|
||||
reg,
|
||||
byte_count,
|
||||
byte_index: 0,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
assert!(!is_read_operation);
|
||||
self.store_register(reg);
|
||||
self.state = Idle;
|
||||
}
|
||||
|
||||
self.serial_buffer.reset();
|
||||
}
|
||||
TxToMaster {
|
||||
byte_count,
|
||||
byte_index,
|
||||
} => {
|
||||
if !sck_falling_edge {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_byte_index = if self.serial_buffer.is_empty() {
|
||||
byte_index + 1
|
||||
} else {
|
||||
byte_index
|
||||
};
|
||||
|
||||
if byte_count == new_byte_index {
|
||||
self.state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
if byte_index != new_byte_index {
|
||||
self.serial_buffer
|
||||
.load_byte(self.internal_buffer[byte_index as usize]);
|
||||
}
|
||||
|
||||
assert_eq!(gpio_state[Port::Sio.index()], GpioDirection::In);
|
||||
self.sio.set(self.serial_buffer.pop_bit() as u16);
|
||||
|
||||
self.state = TxToMaster {
|
||||
byte_count,
|
||||
byte_index: new_byte_index,
|
||||
};
|
||||
}
|
||||
RxFromMaster {
|
||||
reg,
|
||||
byte_count,
|
||||
byte_index,
|
||||
} => {
|
||||
if !sck_falling_edge {
|
||||
return;
|
||||
}
|
||||
|
||||
assert_eq!(gpio_state[Port::Sio.index()], GpioDirection::Out);
|
||||
|
||||
self.serial_read();
|
||||
if !self.serial_buffer.is_full() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.internal_buffer[byte_index as usize] = self.serial_buffer.take_byte().unwrap();
|
||||
|
||||
let byte_index = byte_index + 1;
|
||||
|
||||
if byte_index == byte_count {
|
||||
self.store_register(reg);
|
||||
self.state = Idle;
|
||||
} else {
|
||||
self.state = RxFromMaster {
|
||||
reg,
|
||||
byte_count,
|
||||
byte_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&self, _gpio_state: &GpioState) -> u16 {
|
||||
let mut result = 0;
|
||||
result.set_bit(Port::Sck.index(), self.sck.high());
|
||||
result.set_bit(Port::Sio.index(), self.sio.high());
|
||||
result.set_bit(Port::Cs.index(), self.cs.high());
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
mod registers {
|
||||
use super::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Default)]
|
||||
pub(super) struct StatusRegister {
|
||||
pub(super) per_minute_irq: bool,
|
||||
pub(super) mode_24h: bool,
|
||||
pub(super) power_off: bool,
|
||||
}
|
||||
|
||||
impl StatusRegister {
|
||||
pub(super) fn read(&self) -> u8 {
|
||||
let mut result = 0;
|
||||
result.set_bit(3, self.per_minute_irq);
|
||||
result.set_bit(6, self.mode_24h);
|
||||
result.set_bit(7, self.power_off);
|
||||
result
|
||||
}
|
||||
|
||||
pub(super) fn write(&mut self, value: u8) {
|
||||
self.per_minute_irq = value.bit(3);
|
||||
self.mode_24h = value.bit(6);
|
||||
self.power_off = value.bit(7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn transmit(rtc: &mut Rtc, gpio_state: &GpioState, bit: u8) {
|
||||
rtc.write(&gpio_state, 0b0100_u16 | (u16::from(bit) << 1));
|
||||
rtc.write(&gpio_state, 0b0101_u16);
|
||||
}
|
||||
|
||||
fn receive_bytes(rtc: &mut Rtc, gpio_state: &GpioState, bytes: &mut [u8]) {
|
||||
for byte in bytes.iter_mut() {
|
||||
for i in 0..8 {
|
||||
rtc.write(&gpio_state, 0b0100_u16);
|
||||
let data = rtc.read(&gpio_state);
|
||||
rtc.write(&gpio_state, 0b0101_u16);
|
||||
byte.set_bit(i, data.bit(Port::Sio.index()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transmit_bits(rtc: &mut Rtc, gpio_state: &GpioState, bits: &[u8]) {
|
||||
for bit in bits.iter() {
|
||||
transmit(rtc, gpio_state, *bit);
|
||||
}
|
||||
}
|
||||
|
||||
fn start_serial_transfer(rtc: &mut Rtc, gpio_state: &GpioState) {
|
||||
assert_eq!(rtc.state, State::Idle);
|
||||
|
||||
// set CS low,
|
||||
rtc.write(&gpio_state, 0b0001);
|
||||
assert_eq!(rtc.state, State::WaitForChipSelect);
|
||||
|
||||
// set CS high, SCK rising edge
|
||||
rtc.write(&gpio_state, 0b0101);
|
||||
assert_eq!(rtc.state, State::GetCommandByte);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serial_buffer() {
|
||||
let mut serial_buffer = SerialBuffer::new();
|
||||
assert_eq!(serial_buffer.value(), None);
|
||||
serial_buffer.push_bit(true);
|
||||
assert_eq!(serial_buffer.value(), Some(1));
|
||||
let _ = serial_buffer.pop_bit();
|
||||
assert_eq!(serial_buffer.value(), None);
|
||||
|
||||
serial_buffer.push_bit(false);
|
||||
serial_buffer.push_bit(true);
|
||||
serial_buffer.push_bit(true);
|
||||
serial_buffer.push_bit(false);
|
||||
assert_eq!(serial_buffer.count(), 4);
|
||||
|
||||
assert_eq!(serial_buffer.value(), Some(6));
|
||||
let _ = serial_buffer.pop_bit();
|
||||
assert_eq!(serial_buffer.value(), Some(3)); // pops are LSB first
|
||||
let _ = serial_buffer.pop_bit();
|
||||
assert_eq!(serial_buffer.count(), 2);
|
||||
assert_eq!(serial_buffer.value(), Some(1));
|
||||
}
|
||||
|
||||
macro_rules! setup_rtc {
|
||||
($rtc:ident, $gpio_state:ident) => {
|
||||
let mut $rtc = Rtc::new(Rc::new(Cell::new(false)));
|
||||
#[allow(unused_mut)]
|
||||
let mut $gpio_state = [
|
||||
GpioDirection::Out, /* SCK */
|
||||
GpioDirection::Out, /* SIO */
|
||||
GpioDirection::Out, /* CS */
|
||||
GpioDirection::In, /* dont-care */
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rtc_status() {
|
||||
setup_rtc!(rtc, gpio_state);
|
||||
|
||||
rtc.status.mode_24h = false;
|
||||
start_serial_transfer(&mut rtc, &mut gpio_state);
|
||||
|
||||
// write Status register command
|
||||
transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 0, 1, 0]);
|
||||
assert_eq!(
|
||||
rtc.state,
|
||||
State::RxFromMaster {
|
||||
reg: RegisterKind::Status,
|
||||
byte_count: 1,
|
||||
byte_index: 0,
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(rtc.status.mode_24h, false);
|
||||
|
||||
let mut serial_buffer = SerialBuffer::new();
|
||||
serial_buffer.load_byte(
|
||||
registers::StatusRegister {
|
||||
mode_24h: true,
|
||||
..Default::default()
|
||||
}
|
||||
.read(),
|
||||
);
|
||||
|
||||
while !serial_buffer.is_empty() {
|
||||
transmit(&mut rtc, &gpio_state, serial_buffer.pop_bit() as u8);
|
||||
}
|
||||
|
||||
assert!(rtc.serial_buffer.is_empty());
|
||||
assert_eq!(rtc.status.mode_24h, true);
|
||||
|
||||
start_serial_transfer(&mut rtc, &mut gpio_state);
|
||||
|
||||
// read Status register command
|
||||
transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 0, 1, 1]);
|
||||
assert_eq!(
|
||||
rtc.state,
|
||||
State::TxToMaster {
|
||||
byte_count: 1,
|
||||
byte_index: 0,
|
||||
}
|
||||
);
|
||||
|
||||
// adjust SIO pin
|
||||
gpio_state[Port::Sio.index()] = GpioDirection::In;
|
||||
|
||||
let mut bytes = [0];
|
||||
receive_bytes(&mut rtc, &gpio_state, &mut bytes);
|
||||
|
||||
let mut status = registers::StatusRegister::default();
|
||||
status.write(bytes[0]);
|
||||
|
||||
assert_eq!(status.mode_24h, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_time() {
|
||||
setup_rtc!(rtc, gpio_state);
|
||||
|
||||
start_serial_transfer(&mut rtc, &mut gpio_state);
|
||||
transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 1, 0, 1]);
|
||||
assert_eq!(
|
||||
rtc.state,
|
||||
State::TxToMaster {
|
||||
byte_count: 7,
|
||||
byte_index: 0
|
||||
}
|
||||
);
|
||||
|
||||
gpio_state[Port::Sio.index()] = GpioDirection::In;
|
||||
let mut bytes = [0; 7];
|
||||
receive_bytes(&mut rtc, &gpio_state, &mut bytes);
|
||||
assert_eq!(rtc.state, State::Idle);
|
||||
|
||||
println!("{:x?}", bytes);
|
||||
let local: DateTime<Local> = Local::now();
|
||||
assert_eq!(bytes[0], num2bcd((local.year() % 100) as u8));
|
||||
assert_eq!(bytes[1], num2bcd(local.month() as u8));
|
||||
assert_eq!(bytes[2], num2bcd(local.day() as u8));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ impl GameBoyAdvance {
|
|||
let sound_controller = Box::new(SoundController::new(
|
||||
audio_device.borrow().get_sample_rate() as f32,
|
||||
));
|
||||
|
||||
let io = IoDevices::new(gpu, sound_controller);
|
||||
let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak));
|
||||
|
||||
|
|
|
@ -282,7 +282,7 @@ macro_rules! memory_map {
|
|||
$sb.io.$write_fn(addr, $value)
|
||||
}
|
||||
PALRAM_ADDR | VRAM_ADDR | OAM_ADDR => $sb.io.gpu.$write_fn($addr, $value),
|
||||
GAMEPAK_WS0_LO | GAMEPAK_WS0_HI => {}
|
||||
GAMEPAK_WS0_LO => $sb.cartridge.$write_fn($addr, $value),
|
||||
GAMEPAK_WS2_HI => $sb.cartridge.$write_fn($addr, $value),
|
||||
SRAM_LO | SRAM_HI => $sb.cartridge.$write_fn($addr, $value),
|
||||
_ => {
|
||||
|
|
Reference in a new issue