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
|
- flash64k
|
||||||
- eeprom
|
- eeprom
|
||||||
- autodetect
|
- autodetect
|
||||||
|
- rtc:
|
||||||
|
long: rtc
|
||||||
|
help: Force cartridge to have RTC
|
||||||
|
required: false
|
||||||
- skip_bios:
|
- skip_bios:
|
||||||
long: skip-bios
|
long: skip-bios
|
||||||
help: Skip running bios and start from the ROM instead
|
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 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(
|
.save_type(BackupType::try_from(
|
||||||
matches.value_of("save_type").unwrap(),
|
matches.value_of("save_type").unwrap(),
|
||||||
)?)
|
)?)
|
||||||
.file(Path::new(&rom_path))
|
.file(Path::new(&rom_path));
|
||||||
.build()?;
|
|
||||||
|
if matches.occurrences_of("rtc") != 0 {
|
||||||
|
builder = builder.with_rtc();
|
||||||
|
}
|
||||||
|
|
||||||
|
let gamepak = builder.build()?;
|
||||||
|
|
||||||
let mut gba = GameBoyAdvance::new(
|
let mut gba = GameBoyAdvance::new(
|
||||||
bios_bin.into_boxed_slice(),
|
bios_bin.into_boxed_slice(),
|
||||||
|
|
|
@ -5,13 +5,14 @@ authors = ["Michel Heily <michelheily@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0.104", features = ["derive"] }
|
serde = { version = "1.0.104", features = ["derive", "rc"] }
|
||||||
bincode = "1.2.1"
|
bincode = "1.2.1"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
num = "0.2.1"
|
num = "0.2.1"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
enum-primitive-derive = "^0.1"
|
enum-primitive-derive = "^0.1"
|
||||||
bit = "^0.1"
|
bit = "^0.1"
|
||||||
|
chrono = "0.4"
|
||||||
colored = "1.9"
|
colored = "1.9"
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
hexdump = "0.1.0"
|
hexdump = "0.1.0"
|
||||||
|
@ -36,6 +37,7 @@ gdbstub = { version = "0.1.2", optional = true, features = ["std"] }
|
||||||
ringbuf = "0.2.1"
|
ringbuf = "0.2.1"
|
||||||
goblin = { version = "0.2", optional = true }
|
goblin = { version = "0.2", optional = true }
|
||||||
fuzzy-matcher = { version = "0.3.4", optional = true }
|
fuzzy-matcher = { version = "0.3.4", optional = true }
|
||||||
|
bit_reverse = "0.1.8"
|
||||||
|
|
||||||
[target.'cfg(target_arch="wasm32")'.dependencies]
|
[target.'cfg(target_arch="wasm32")'.dependencies]
|
||||||
instant = { version = "0.1.2", features = ["wasm-bindgen"] }
|
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};
|
use super::loader::{load_from_bytes, load_from_file, LoadRom};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum GpioDeviceType {
|
||||||
|
Rtc,
|
||||||
|
SolarSensor,
|
||||||
|
Gyro,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GamepakBuilder {
|
pub struct GamepakBuilder {
|
||||||
path: Option<PathBuf>,
|
path: Option<PathBuf>,
|
||||||
bytes: Option<Box<[u8]>>,
|
bytes: Option<Box<[u8]>>,
|
||||||
save_path: Option<PathBuf>,
|
save_path: Option<PathBuf>,
|
||||||
save_type: BackupType,
|
save_type: BackupType,
|
||||||
|
gpio_device: GpioDeviceType,
|
||||||
create_backup_file: bool,
|
create_backup_file: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +39,7 @@ impl GamepakBuilder {
|
||||||
path: None,
|
path: None,
|
||||||
save_path: None,
|
save_path: None,
|
||||||
bytes: None,
|
bytes: None,
|
||||||
|
gpio_device: GpioDeviceType::None,
|
||||||
create_backup_file: true,
|
create_backup_file: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,6 +94,11 @@ impl GamepakBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_rtc(mut self) -> Self {
|
||||||
|
self.gpio_device = GpioDeviceType::Rtc;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(mut self) -> GBAResult<Cartridge> {
|
pub fn build(mut self) -> GBAResult<Cartridge> {
|
||||||
let (bytes, symbols) = if let Some(bytes) = self.bytes {
|
let (bytes, symbols) = if let Some(bytes) = self.bytes {
|
||||||
match load_from_bytes(bytes.to_vec())? {
|
match load_from_bytes(bytes.to_vec())? {
|
||||||
|
@ -127,7 +142,14 @@ impl GamepakBuilder {
|
||||||
|
|
||||||
let backup = create_backup(self.save_type, self.save_path);
|
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();
|
let size = bytes.len();
|
||||||
Ok(Cartridge {
|
Ok(Cartridge {
|
||||||
|
|
|
@ -4,44 +4,73 @@ use super::{GPIO_PORT_CONTROL, GPIO_PORT_DATA, GPIO_PORT_DIRECTION};
|
||||||
use bit::BitIndex;
|
use bit::BitIndex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::cell::RefCell;
|
/// 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)]
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||||
enum GpioDirection {
|
pub enum GpioDirection {
|
||||||
|
/// GPIO to GBA
|
||||||
In = 0,
|
In = 0,
|
||||||
|
/// GBA to GPIO
|
||||||
Out = 1,
|
Out = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type GpioState = [GpioDirection; 4];
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||||
enum GpioPortControl {
|
enum GpioPortControl {
|
||||||
WriteOnly = 0,
|
WriteOnly = 0,
|
||||||
ReadWrite = 1,
|
ReadWrite = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait GpioDevice: Sized {
|
pub trait GpioDevice: Sized {
|
||||||
fn write(&mut self);
|
fn write(&mut self, gpio_state: &GpioState, data: u16);
|
||||||
fn read(&mut self);
|
fn read(&self, gpio_state: &GpioState) -> u16;
|
||||||
}
|
|
||||||
|
|
||||||
enum GpioDeviceType {
|
|
||||||
Rtc,
|
|
||||||
SolarSensor,
|
|
||||||
Gyro,
|
|
||||||
None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Gpio {
|
pub struct Gpio {
|
||||||
rtc: Option<RefCell<Rtc>>,
|
rtc: Option<Rtc>,
|
||||||
direction: [GpioDirection; 4],
|
direction: GpioState,
|
||||||
control: GpioPortControl,
|
control: GpioPortControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gpio {
|
impl Gpio {
|
||||||
pub fn new() -> Self {
|
pub fn new_none() -> Self {
|
||||||
Gpio {
|
Gpio {
|
||||||
rtc: None,
|
rtc: None,
|
||||||
direction: [GpioDirection::In; 4],
|
direction: [GpioDirection::Out; 4],
|
||||||
|
control: GpioPortControl::WriteOnly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_rtc() -> Self {
|
||||||
|
Gpio {
|
||||||
|
rtc: Some(Rtc::new()),
|
||||||
|
direction: [GpioDirection::Out; 4],
|
||||||
control: GpioPortControl::WriteOnly,
|
control: GpioPortControl::WriteOnly,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +81,13 @@ impl Gpio {
|
||||||
|
|
||||||
pub fn read(&self, addr: u32) -> u16 {
|
pub fn read(&self, addr: u32) -> u16 {
|
||||||
match addr {
|
match addr {
|
||||||
GPIO_PORT_DATA => unimplemented!(),
|
GPIO_PORT_DATA => {
|
||||||
|
if let Some(rtc) = &self.rtc {
|
||||||
|
rtc.read(&self.direction)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
GPIO_PORT_DIRECTION => {
|
GPIO_PORT_DIRECTION => {
|
||||||
let mut direction = 0u16;
|
let mut direction = 0u16;
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
|
@ -65,17 +100,30 @@ impl Gpio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&mut self, addr: u32) -> u16 {
|
pub fn write(&mut self, addr: u32, value: u16) {
|
||||||
match addr {
|
match addr {
|
||||||
GPIO_PORT_DATA => unimplemented!(),
|
GPIO_PORT_DATA => {
|
||||||
|
if let Some(rtc) = &mut self.rtc {
|
||||||
|
rtc.write(&self.direction, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
GPIO_PORT_DIRECTION => {
|
GPIO_PORT_DIRECTION => {
|
||||||
let mut direction = 0u16;
|
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
direction.set_bit(i, self.direction[i] == GpioDirection::Out);
|
if value.bit(i) {
|
||||||
|
self.direction[i] = GpioDirection::Out;
|
||||||
|
} else {
|
||||||
|
self.direction[i] = GpioDirection::In;
|
||||||
}
|
}
|
||||||
direction
|
|
||||||
}
|
}
|
||||||
GPIO_PORT_CONTROL => self.control as u16,
|
}
|
||||||
|
GPIO_PORT_CONTROL => {
|
||||||
|
info!("GPIO port control: {:?}", self.control);
|
||||||
|
self.control = if value != 0 {
|
||||||
|
GpioPortControl::ReadWrite
|
||||||
|
} else {
|
||||||
|
GpioPortControl::WriteOnly
|
||||||
|
};
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,9 @@ mod builder;
|
||||||
mod loader;
|
mod loader;
|
||||||
pub use builder::GamepakBuilder;
|
pub use builder::GamepakBuilder;
|
||||||
|
|
||||||
pub const GPIO_PORT_DATA: u32 = 0x0800_00C4;
|
pub const GPIO_PORT_DATA: u32 = 0xC4;
|
||||||
pub const GPIO_PORT_DIRECTION: u32 = 0x0800_00C6;
|
pub const GPIO_PORT_DIRECTION: u32 = 0xC6;
|
||||||
pub const GPIO_PORT_CONTROL: u32 = 0x0800_00C8;
|
pub const GPIO_PORT_CONTROL: u32 = 0xC8;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub enum BackupMedia {
|
pub enum BackupMedia {
|
||||||
|
@ -56,7 +56,7 @@ use super::sysbus::consts::*;
|
||||||
pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
|
pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
|
||||||
|
|
||||||
fn is_gpio_access(addr: u32) -> bool {
|
fn is_gpio_access(addr: u32) -> bool {
|
||||||
match addr {
|
match addr & 0x1ff_ffff {
|
||||||
GPIO_PORT_DATA | GPIO_PORT_DIRECTION | GPIO_PORT_CONTROL => true,
|
GPIO_PORT_DATA | GPIO_PORT_DIRECTION | GPIO_PORT_CONTROL => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,11 @@ impl Bus for Cartridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_16(&self, addr: u32) -> u16 {
|
fn read_16(&self, addr: u32) -> u16 {
|
||||||
if is_gpio_access(addr) && self.gpio.is_readable() {
|
if is_gpio_access(addr) {
|
||||||
return self.gpio.read(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
|
if addr & 0xff000000 == GAMEPAK_WS2_HI
|
||||||
|
@ -109,7 +112,7 @@ impl Bus for Cartridge {
|
||||||
|
|
||||||
fn write_16(&mut self, addr: u32, value: u16) {
|
fn write_16(&mut self, addr: u32, value: u16) {
|
||||||
if is_gpio_access(addr) {
|
if is_gpio_access(addr) {
|
||||||
self.gpio.write(addr);
|
self.gpio.write(addr & 0x1ff_ffff, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,30 @@
|
||||||
|
use bit::BitIndex;
|
||||||
|
use bit_reverse::LookupReverse;
|
||||||
|
use chrono::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
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 {
|
enum Port {
|
||||||
#[doc("Serial Clock")]
|
#[doc("Serial Clock")]
|
||||||
Sck = 0,
|
Sck = 0,
|
||||||
|
@ -10,18 +34,580 @@ enum Port {
|
||||||
Cs = 2,
|
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
|
/// Model of the S3511 8pin RTC
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Rtc {}
|
pub struct Rtc {
|
||||||
|
state: State,
|
||||||
|
sck: GpioPort,
|
||||||
|
sio: GpioPort,
|
||||||
|
cs: GpioPort,
|
||||||
|
status: registers::StatusRegister,
|
||||||
|
serial_buffer: SerialBuffer,
|
||||||
|
internal_buffer: [u8; 8],
|
||||||
|
}
|
||||||
|
|
||||||
impl Rtc {
|
impl Rtc {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Rtc {}
|
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],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_port(port: usize) -> u8 {
|
fn serial_read(&mut self) {
|
||||||
0
|
self.serial_buffer.push_bit(self.sio.high());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_port(port: usize) {}
|
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(
|
let sound_controller = Box::new(SoundController::new(
|
||||||
audio_device.borrow().get_sample_rate() as f32,
|
audio_device.borrow().get_sample_rate() as f32,
|
||||||
));
|
));
|
||||||
|
|
||||||
let io = IoDevices::new(gpu, sound_controller);
|
let io = IoDevices::new(gpu, sound_controller);
|
||||||
let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak));
|
let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak));
|
||||||
|
|
||||||
|
|
|
@ -282,7 +282,7 @@ macro_rules! memory_map {
|
||||||
$sb.io.$write_fn(addr, $value)
|
$sb.io.$write_fn(addr, $value)
|
||||||
}
|
}
|
||||||
PALRAM_ADDR | VRAM_ADDR | OAM_ADDR => $sb.io.gpu.$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),
|
GAMEPAK_WS2_HI => $sb.cartridge.$write_fn($addr, $value),
|
||||||
SRAM_LO | SRAM_HI => $sb.cartridge.$write_fn($addr, $value),
|
SRAM_LO | SRAM_HI => $sb.cartridge.$write_fn($addr, $value),
|
||||||
_ => {
|
_ => {
|
||||||
|
|
Reference in a new issue