From 6d942b07d069653fedc9de7f4225e6755fb47a64 Mon Sep 17 00:00:00 2001 From: Alex Janka Date: Sat, 16 Nov 2024 10:10:06 +1100 Subject: [PATCH] add link port uart --- agb/Cargo.toml | 15 ++- agb/src/lib.rs | 4 + agb/src/serial_link/mod.rs | 230 +++++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 agb/src/serial_link/mod.rs diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 6984418c..702c252b 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -22,12 +22,21 @@ agb_image_converter = { version = "0.21.1", path = "../agb-image-converter" } agb_sound_converter = { version = "0.21.1", path = "../agb-sound-converter" } agb_macros = { version = "0.21.1", path = "../agb-macros" } agb_fixnum = { version = "0.21.1", path = "../agb-fixnum" } -agb_hashmap = { version = "0.21.1", path = "../agb-hashmap", features = ["allocator_api"] } +agb_hashmap = { version = "0.21.1", path = "../agb-hashmap", features = [ + "allocator_api", +] } bilge = "0.2" qrcodegen-no-heap = { version = "1.8", optional = true } -portable-atomic = { version = "1.6.0", default-features = false, features = ["unsafe-assume-single-core", "fallback"] } -once_cell = { version = "1.20.1", default-features = false, features = ["critical-section"] } +portable-atomic = { version = "1.6.0", default-features = false, features = [ + "unsafe-assume-single-core", + "fallback", +] } +once_cell = { version = "1.20.1", default-features = false, features = [ + "critical-section", +] } critical-section = { version = "1.1.2", features = ["restore-state-u16"] } +embedded-hal = "0.2.7" +nb = "1.1" [package.metadata.docs.rs] default-target = "thumbv4t-none-eabi" diff --git a/agb/src/lib.rs b/agb/src/lib.rs index b657221f..4e347e70 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -10,6 +10,8 @@ any(test, feature = "testing"), reexport_test_harness_main = "test_main" )] +#![allow(incomplete_features)] +#![feature(adt_const_params)] #![feature(allocator_api)] #![warn(clippy::all)] #![allow(clippy::needless_pass_by_ref_mut)] @@ -181,6 +183,8 @@ mod panics_render; /// Simple random number generator pub mod rng; pub mod save; +/// Link port support +pub mod serial_link; mod single; /// Implements sound output. pub mod sound; diff --git a/agb/src/serial_link/mod.rs b/agb/src/serial_link/mod.rs new file mode 100644 index 00000000..869470af --- /dev/null +++ b/agb/src/serial_link/mod.rs @@ -0,0 +1,230 @@ +use core::ops::{Deref, DerefMut}; + +use embedded_hal::serial::{Read, Write}; + +use crate::memory_mapped::MemoryMapped; + +const SIODATA8: MemoryMapped = unsafe { MemoryMapped::new(0x0400_012A) }; +const SIOCNT: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0128) }; +const RCNT: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0134) }; + +#[derive(Debug)] +pub enum LinkPortError { + GbaErrorBit, +} + +pub struct LinkPortUart; + +impl LinkPortUart { + pub fn init(rate: BaudRate, with_interrupts: bool, clear_to_send: bool) -> Self { + RCNT.set(0x0); + SIOCNT.set(0x0); + let reg: u16 = SioControlReg::default_uart() + .with_baud(rate) + .with_interrupts(with_interrupts) + .with_cts(clear_to_send) + .into(); + SIOCNT.set(reg); + Self + } +} + +impl Read for LinkPortUart { + type Error = LinkPortError; + + fn read(&mut self) -> Result> { + match SioControlReg::from(SIOCNT.get()) { + v if *v.error => Err(nb::Error::Other(LinkPortError::GbaErrorBit)), + v if *v.recv_empty => Err(nb::Error::WouldBlock), + _ => Ok((SIODATA8.get() & 0xFF) as u8), + } + } +} + +impl Write for LinkPortUart { + type Error = LinkPortError; + + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + match self.flush() { + Ok(_) => { + SIODATA8.set(word as u16); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + match SioControlReg::from(SIOCNT.get()) { + v if *v.error => Err(nb::Error::Other(LinkPortError::GbaErrorBit)), + v if *v.send_full => Err(nb::Error::WouldBlock), + _ => Ok(()), + } + } +} + +pub enum BaudRate { + B9600 = 0b00, + B38400 = 0b01, + B57600 = 0b10, + B115200 = 0b11, +} + +impl From for BaudRate { + fn from(value: u16) -> Self { + match value { + 0b00 => Self::B9600, + 0b01 => Self::B38400, + 0b10 => Self::B57600, + 0b11 => Self::B115200, + _ => panic!("passed invalid value"), + } + } +} + +pub enum SioMode { + Normal8bit = 0b00, + Multiplayer = 0b01, + Normal32bit = 0b10, + Uart = 0b11, +} + +impl From for SioMode { + fn from(value: u16) -> Self { + match value { + 0b00 => Self::Normal8bit, + 0b01 => Self::Multiplayer, + 0b10 => Self::Normal32bit, + 0b11 => Self::Uart, + _ => panic!("passed invalid value"), + } + } +} + +struct SioControlReg { + baud_rate: BaudRate, // 0-1 + flow_control: BoolField, // 2 + parity_odd: BoolField, // 3 + send_full: BoolField, // 4 + recv_empty: BoolField, // 5 + error: BoolField, // 6 + data_8bit: BoolField, // 7 + fifo_enabled: BoolField, // 8 + parity_enabled: BoolField, // 9 + tx_enabled: BoolField, // 10 + rx_enabled: BoolField, // 11 + mode: SioMode, // 12-13 + irq_enable: BoolField, // 14 +} + +impl SioControlReg { + fn default_uart() -> Self { + Self { + baud_rate: BaudRate::B9600, + flow_control: BoolField(false), + parity_odd: BoolField(false), + send_full: BoolField(false), + recv_empty: BoolField(false), + error: BoolField(false), + data_8bit: BoolField(true), + // fifo_enabled: BoolField(true), + fifo_enabled: BoolField(true), + parity_enabled: BoolField(false), + tx_enabled: BoolField(true), + rx_enabled: BoolField(true), + mode: SioMode::Uart, + irq_enable: BoolField(false), + } + } + + fn with_baud(mut self, rate: BaudRate) -> Self { + self.baud_rate = rate; + self + } + + fn with_interrupts(mut self, interrupts: bool) -> Self { + *self.irq_enable = interrupts; + self + } + + fn with_cts(mut self, clear_to_send: bool) -> Self { + *self.flow_control = clear_to_send; + self + } +} + +impl From for u16 { + fn from(value: SioControlReg) -> Self { + value.baud_rate as u16 + | u16::from(value.flow_control) << 2 + | u16::from(value.parity_odd) << 3 + | u16::from(value.send_full) << 4 + | u16::from(value.recv_empty) << 5 + | u16::from(value.error) << 6 + | u16::from(value.data_8bit) << 7 // bit start + | u16::from(value.fifo_enabled) << 8 + | u16::from(value.parity_enabled) << 9 + | u16::from(value.tx_enabled) << 10 + | u16::from(value.rx_enabled) << 11 + | (value.mode as u16) << 12 + | u16::from(value.irq_enable) << 14 + } +} + +impl From for SioControlReg { + fn from(value: u16) -> Self { + Self { + baud_rate: BaudRate::from(value & 0b11), + flow_control: (value & (1 << 2)).into(), + parity_odd: (value & (1 << 3)).into(), + send_full: (value & (1 << 4)).into(), + recv_empty: (value & (1 << 5)).into(), + error: (value & (1 << 6)).into(), + data_8bit: (value & (1 << 7)).into(), + fifo_enabled: (value & (1 << 8)).into(), + parity_enabled: (value & (1 << 9)).into(), + tx_enabled: (value & (1 << 10)).into(), + rx_enabled: (value & (1 << 11)).into(), + mode: ((value & (0b11 << 12)) >> 12).into(), + irq_enable: (value & (1 << 14)).into(), + } + } +} + +pub struct BoolField(bool); + +impl Deref for BoolField { + type Target = bool; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for BoolField { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for u16 { + fn from(value: BoolField) -> Self { + if *value { + 1 + } else { + 0 + } + } +} + +impl From for BoolField { + fn from(value: u16) -> Self { + Self(value != 0) + } +} + +impl From for BoolField { + fn from(value: bool) -> Self { + Self(value) + } +}