diff --git a/examples/basic_keyinput.rs b/examples/basic_keyinput.rs index 6756a18..bc584be 100644 --- a/examples/basic_keyinput.rs +++ b/examples/basic_keyinput.rs @@ -7,10 +7,12 @@ use core::ptr::{addr_of, addr_of_mut}; use gba::{ asm_runtime::USER_IRQ_HANDLER, bios::VBlankIntrWait, + irq::{IrqBits, IE, IME}, + keys::KEYINPUT, mgba::{MgbaLogLevel, MgbaLogger}, - mmio::{BACKDROP_COLOR, DISPCNT, DISPSTAT, IE, IME, KEYINPUT}, - video::{Color, DisplayControl, DisplayStatus}, - IrqBits, + video::{ + Color, DisplayControl, DisplayStatus, BACKDROP_COLOR, DISPCNT, DISPSTAT, + }, }; gba::panic_handler!(mgba_log_err); diff --git a/examples/mode0.rs b/examples/mode0.rs index ec1c91d..937375d 100644 --- a/examples/mode0.rs +++ b/examples/mode0.rs @@ -3,10 +3,12 @@ use gba::{ bios::VBlankIntrWait, - mmio::{BG_PALRAM, DISPCNT, DISPSTAT, IE, IME, VRAM_BG_TILE4}, + irq::{IrqBits, IE, IME}, sample_art::decompress_cga_face_to_vram_4bpp, - video::{Color, DisplayControl, DisplayStatus}, - IrqBits, + video::{ + Color, DisplayControl, DisplayStatus, BG_PALRAM, DISPCNT, DISPSTAT, + VRAM_BG_TILE4, + }, }; gba::panic_handler!(empty_loop); diff --git a/examples/mode3.rs b/examples/mode3.rs index e5dd132..95daa2d 100644 --- a/examples/mode3.rs +++ b/examples/mode3.rs @@ -3,9 +3,11 @@ use gba::{ bios::VBlankIntrWait, - mmio::{DISPCNT, DISPSTAT, IE, IME, KEYINPUT, MODE3_VRAM}, - video::{Color, DisplayControl, DisplayStatus}, - IrqBits, + irq::{IrqBits, IE, IME}, + keys::KEYINPUT, + video::{ + Color, DisplayControl, DisplayStatus, DISPCNT, DISPSTAT, MODE3_VRAM, + }, }; gba::panic_handler!(empty_loop); diff --git a/examples/mode4.rs b/examples/mode4.rs index 8766d56..d1ff8ee 100644 --- a/examples/mode4.rs +++ b/examples/mode4.rs @@ -3,9 +3,12 @@ use gba::{ bios::VBlankIntrWait, - mmio::{BG_PALRAM, DISPCNT, DISPSTAT, IE, IME, KEYINPUT, MODE4_VRAM}, - video::{Color, DisplayControl, DisplayStatus}, - IrqBits, + irq::{IrqBits, IE, IME}, + keys::KEYINPUT, + video::{ + Color, DisplayControl, DisplayStatus, BG_PALRAM, DISPCNT, DISPSTAT, + MODE4_VRAM, + }, }; gba::panic_handler!(empty_loop); diff --git a/examples/mode5.rs b/examples/mode5.rs index 89a5f7a..25987fe 100644 --- a/examples/mode5.rs +++ b/examples/mode5.rs @@ -3,9 +3,11 @@ use gba::{ bios::VBlankIntrWait, - mmio::{DISPCNT, DISPSTAT, IE, IME, KEYINPUT, MODE5_VRAM}, - video::{Color, DisplayControl, DisplayStatus}, - IrqBits, + irq::{IrqBits, IE, IME}, + keys::KEYINPUT, + video::{ + Color, DisplayControl, DisplayStatus, DISPCNT, DISPSTAT, MODE5_VRAM, + }, }; gba::panic_handler!(empty_loop); diff --git a/examples/objects.rs b/examples/objects.rs index abb6459..96dfdfa 100644 --- a/examples/objects.rs +++ b/examples/objects.rs @@ -5,14 +5,15 @@ use gba::{ asm_runtime::USER_IRQ_HANDLER, bios::VBlankIntrWait, gba_cell::GbaCell, - mmio::{ - obj_palbank, BG0CNT, BG_PALRAM, DISPCNT, DISPSTAT, IE, IME, KEYINPUT, - OBJ_ATTR0, OBJ_ATTR_ALL, TEXT_SCREENBLOCKS, VRAM_BG_TILE4, VRAM_OBJ_TILE4, - }, - obj::{ObjAttr, ObjAttr0, ObjDisplayStyle}, + irq::{IrqBits, IE, IME}, + keys::KEYINPUT, sample_art::{decompress_cga_face_to_vram_4bpp, Cga}, - video::{BackgroundControl, Color, DisplayControl, DisplayStatus, TextEntry}, - IrqBits, + video::{ + obj_palbank, BackgroundControl, Color, DisplayControl, DisplayStatus, + ObjAttr, ObjAttr0, ObjDisplayStyle, TextEntry, BG0CNT, BG_PALRAM, DISPCNT, + DISPSTAT, OBJ_ATTR0, OBJ_ATTR_ALL, TEXT_SCREENBLOCKS, VRAM_BG_TILE4, + VRAM_OBJ_TILE4, + }, }; gba::panic_handler!(mgba_log_err); diff --git a/examples/paddle_ball.rs b/examples/paddle_ball.rs index 15e8e66..416f19e 100644 --- a/examples/paddle_ball.rs +++ b/examples/paddle_ball.rs @@ -7,9 +7,9 @@ use gba::{ asm_runtime::USER_IRQ_HANDLER, bios::VBlankIntrWait, gba_cell::GbaCell, - mmio::{DISPCNT, DISPSTAT, IE, IME, KEYINPUT}, - video::{Color, DisplayControl, DisplayStatus, Mode3}, - IrqBits, KeyInput, + irq::{IrqBits, IE, IME}, + keys::{KeyInput, KEYINPUT}, + video::{Color, DisplayControl, DisplayStatus, Mode3, DISPCNT, DISPSTAT}, }; const SCREEN_WIDTH: u16 = 240; diff --git a/examples/timer.rs b/examples/timer.rs index 5de911b..b4bed9a 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -5,10 +5,11 @@ use gba::{ asm_runtime::USER_IRQ_HANDLER, bios::VBlankIntrWait, gba_cell::GbaCell, - mmio::{BACKDROP_COLOR, DISPCNT, DISPSTAT, IE, IME, TIMER0_CONTROL}, - timers::{CpusPerTick, TimerControl}, - video::{Color, DisplayControl, DisplayStatus}, - IrqBits, + irq::{IrqBits, IE, IME}, + timers::{CpusPerTick, TimerControl, TIMER0_CONTROL}, + video::{ + Color, DisplayControl, DisplayStatus, BACKDROP_COLOR, DISPCNT, DISPSTAT, + }, }; static OVERFLOWS: GbaCell = GbaCell::new(0); diff --git a/src/asm_runtime.rs b/src/asm_runtime.rs index 0d319de..3d12d21 100644 --- a/src/asm_runtime.rs +++ b/src/asm_runtime.rs @@ -6,7 +6,8 @@ // cargo feature enabled, and so they should all have the `track_caller` // attribute set whenever the `on_gba` feature is *disabled* -use crate::{gba_cell::GbaCell, IrqBits}; +use crate::gba_cell::GbaCell; + use bracer::*; /// Inserts a `nop` instruction. @@ -215,5 +216,6 @@ core::arch::global_asm! { /// The user-provided interrupt request handler function. #[cfg(feature = "on_gba")] -pub static USER_IRQ_HANDLER: GbaCell> = - GbaCell::new(None); +pub static USER_IRQ_HANDLER: GbaCell< + Option, +> = GbaCell::new(None); diff --git a/src/bios.rs b/src/bios.rs index 0fd9088..89b52eb 100644 --- a/src/bios.rs +++ b/src/bios.rs @@ -9,7 +9,7 @@ //! BIOS functions have a rather high calls overhead compared to a normal //! foreign function. -use crate::IrqBits; +use crate::irq::IrqBits; /// `0x04`: Waits for a specific interrupt type(s) to happen. /// diff --git a/src/dma.rs b/src/dma.rs index 8dcf776..fb42413 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -42,13 +42,13 @@ //! Using the DMA units is equivalent to playing around with raw pointers. It //! must be handled very carefully, or memory corruption can occur. +use core::ffi::c_void; + use bitfrob::{u16_with_bit, u16_with_region}; -use voladdress::{Safe, VolRegion}; +use video::Tile4; +use voladdress::{Safe, Unsafe, VolRegion}; -use crate::{ - mmio::{DMA3_CONTROL, DMA3_DESTINATION, DMA3_SOURCE, DMA3_TRANSFER_COUNT}, - video::Tile4, -}; +use super::*; /// Controls the activity of a DMA unit. #[derive(Debug, Clone, Copy, Default)] @@ -214,3 +214,91 @@ pub fn dma3_copy_tile4(src: &[Tile4], dest: VolRegion) { dma3_copy_u32(src.as_ptr().cast(), dest.as_mut_ptr().cast(), src.len() * 8) }; } + +/// Source address for DMA3. +/// +/// The correct pointer type depends on the transfer mode used. +pub const DMA0_SOURCE: VolAddress<*const c_void, (), Unsafe> = + unsafe { VolAddress::new(0x0400_00B0) }; + +/// Destination address for DMA3. +/// +/// The correct pointer type depends on the transfer mode used. +pub const DMA0_DESTINATION: VolAddress<*mut c_void, (), Unsafe> = + unsafe { VolAddress::new(0x0400_00B4) }; + +/// The number of transfers desired. +/// +/// A value of 0 indicates the maximum number of transfers: `0x4000` +pub const DMA0_TRANSFER_COUNT: VolAddress = + unsafe { VolAddress::new(0x0400_00B8) }; + +/// DMA3 Control Bits. +pub const DMA0_CONTROL: VolAddress = + unsafe { VolAddress::new(0x0400_00BA) }; + +/// Source address for DMA3. +/// +/// The correct pointer type depends on the transfer mode used. +pub const DMA1_SOURCE: VolAddress<*const c_void, (), Unsafe> = + unsafe { VolAddress::new(0x0400_00BC) }; + +/// Destination address for DMA3. +/// +/// The correct pointer type depends on the transfer mode used. +pub const DMA1_DESTINATION: VolAddress<*mut c_void, (), Unsafe> = + unsafe { VolAddress::new(0x0400_00C0) }; + +/// The number of transfers desired. +/// +/// A value of 0 indicates the maximum number of transfers: `0x4000` +pub const DMA1_TRANSFER_COUNT: VolAddress = + unsafe { VolAddress::new(0x0400_00C4) }; + +/// DMA3 Control Bits. +pub const DMA1_CONTROL: VolAddress = + unsafe { VolAddress::new(0x0400_00C6) }; + +/// Source address for DMA3. +/// +/// The correct pointer type depends on the transfer mode used. +pub const DMA2_SOURCE: VolAddress<*const c_void, (), Unsafe> = + unsafe { VolAddress::new(0x0400_00C8) }; + +/// Destination address for DMA3. +/// +/// The correct pointer type depends on the transfer mode used. +pub const DMA2_DESTINATION: VolAddress<*mut c_void, (), Unsafe> = + unsafe { VolAddress::new(0x0400_00CC) }; + +/// The number of transfers desired. +/// +/// A value of 0 indicates the maximum number of transfers: `0x4000` +pub const DMA2_TRANSFER_COUNT: VolAddress = + unsafe { VolAddress::new(0x0400_00D0) }; + +/// DMA3 Control Bits. +pub const DMA2_CONTROL: VolAddress = + unsafe { VolAddress::new(0x0400_00D2) }; + +/// Source address for DMA3. +/// +/// The correct pointer type depends on the transfer mode used. +pub const DMA3_SOURCE: VolAddress<*const c_void, (), Unsafe> = + unsafe { VolAddress::new(0x0400_00D4) }; + +/// Destination address for DMA3. +/// +/// The correct pointer type depends on the transfer mode used. +pub const DMA3_DESTINATION: VolAddress<*mut c_void, (), Unsafe> = + unsafe { VolAddress::new(0x0400_00D8) }; + +/// The number of transfers desired. +/// +/// A value of 0 indicates the maximum number of transfers: `0x1_0000` +pub const DMA3_TRANSFER_COUNT: VolAddress = + unsafe { VolAddress::new(0x0400_00DC) }; + +/// DMA3 Control Bits. +pub const DMA3_CONTROL: VolAddress = + unsafe { VolAddress::new(0x0400_00DE) }; diff --git a/src/gba_cell.rs b/src/gba_cell.rs index ba8f9b4..c5400d0 100644 --- a/src/gba_cell.rs +++ b/src/gba_cell.rs @@ -6,7 +6,7 @@ use core::{ ptr::NonNull, }; -use crate::{video::Color, IrqBits, KeyInput}; +use crate::{irq::IrqBits, keys::KeyInput, video::Color}; /// Marker trait bound for the methods of [`GbaCell`]. /// diff --git a/src/irq.rs b/src/irq.rs new file mode 100644 index 0000000..5c7bef4 --- /dev/null +++ b/src/irq.rs @@ -0,0 +1,214 @@ +//! Hardware interrupt handling + +use super::*; + +/// Interrupt bit flags. +#[derive(Clone, Copy, Default)] +#[repr(transparent)] +pub struct IrqBits(pub u16); +impl IrqBits { + /// The vblank bit. + pub const VBLANK: Self = Self::new().with_vblank(true); + + /// Makes a new, empty value. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// Vertical-blank + #[inline] + #[must_use] + pub const fn vblank(self) -> bool { + u16_get_bit(0, self.0) + } + /// Horizontal-blank + #[inline] + #[must_use] + pub const fn hblank(self) -> bool { + u16_get_bit(1, self.0) + } + /// Vertical-counter match + #[inline] + #[must_use] + pub const fn vcount(self) -> bool { + u16_get_bit(2, self.0) + } + /// Timer 0 overflow + #[inline] + #[must_use] + pub const fn timer0(self) -> bool { + u16_get_bit(3, self.0) + } + /// Timer 1 overflow + #[inline] + #[must_use] + pub const fn timer1(self) -> bool { + u16_get_bit(4, self.0) + } + /// Timer 2 overflow + #[inline] + #[must_use] + pub const fn timer2(self) -> bool { + u16_get_bit(5, self.0) + } + /// Timer 3 overflow + #[inline] + #[must_use] + pub const fn timer3(self) -> bool { + u16_get_bit(6, self.0) + } + /// Serial port communication + #[inline] + #[must_use] + pub const fn serial(self) -> bool { + u16_get_bit(7, self.0) + } + /// DMA 0 complete + #[inline] + #[must_use] + pub const fn dma0(self) -> bool { + u16_get_bit(8, self.0) + } + /// DMA 1 complete + #[inline] + #[must_use] + pub const fn dma1(self) -> bool { + u16_get_bit(9, self.0) + } + /// DMA 2 complete + #[inline] + #[must_use] + pub const fn dma2(self) -> bool { + u16_get_bit(10, self.0) + } + /// DMA 3 complete + #[inline] + #[must_use] + pub const fn dma3(self) -> bool { + u16_get_bit(11, self.0) + } + /// Keypad match + #[inline] + #[must_use] + pub const fn keypad(self) -> bool { + u16_get_bit(12, self.0) + } + /// Game pak + #[inline] + #[must_use] + pub const fn gamepak(self) -> bool { + u16_get_bit(13, self.0) + } + + /// Set the vblank bit. + #[inline] + #[must_use] + pub const fn with_vblank(self, vblank: bool) -> Self { + Self(u16_with_bit(0, self.0, vblank)) + } + /// Set the hblank bit. + #[inline] + #[must_use] + pub const fn with_hblank(self, hblank: bool) -> Self { + Self(u16_with_bit(1, self.0, hblank)) + } + /// Set the vcount bit. + #[inline] + #[must_use] + pub const fn with_vcount(self, vcount: bool) -> Self { + Self(u16_with_bit(2, self.0, vcount)) + } + /// Set the timer0 bit. + #[inline] + #[must_use] + pub const fn with_timer0(self, timer0: bool) -> Self { + Self(u16_with_bit(3, self.0, timer0)) + } + /// Set the timer1 bit. + #[inline] + #[must_use] + pub const fn with_timer1(self, timer1: bool) -> Self { + Self(u16_with_bit(4, self.0, timer1)) + } + /// Set the timer2 bit. + #[inline] + #[must_use] + pub const fn with_timer2(self, timer2: bool) -> Self { + Self(u16_with_bit(5, self.0, timer2)) + } + /// Set the timer3 bit. + #[inline] + #[must_use] + pub const fn with_timer3(self, timer3: bool) -> Self { + Self(u16_with_bit(6, self.0, timer3)) + } + /// Set the serial bit. + #[inline] + #[must_use] + pub const fn with_serial(self, serial: bool) -> Self { + Self(u16_with_bit(7, self.0, serial)) + } + /// Set the dma0 bit. + #[inline] + #[must_use] + pub const fn with_dma0(self, dma0: bool) -> Self { + Self(u16_with_bit(8, self.0, dma0)) + } + /// Set the dma1 bit. + #[inline] + #[must_use] + pub const fn with_dma1(self, dma1: bool) -> Self { + Self(u16_with_bit(9, self.0, dma1)) + } + /// Set the dma2 bit. + #[inline] + #[must_use] + pub const fn with_dma2(self, dma2: bool) -> Self { + Self(u16_with_bit(10, self.0, dma2)) + } + /// Set the dma3 bit. + #[inline] + #[must_use] + pub const fn with_dma3(self, dma3: bool) -> Self { + Self(u16_with_bit(11, self.0, dma3)) + } + /// Set the keypad bit. + #[inline] + #[must_use] + pub const fn with_keypad(self, keypad: bool) -> Self { + Self(u16_with_bit(12, self.0, keypad)) + } + /// Set the gamepak bit. + #[inline] + #[must_use] + pub const fn with_gamepak(self, gamepak: bool) -> Self { + Self(u16_with_bit(13, self.0, gamepak)) + } +} + +/// Interrupts Enabled. +/// +/// When any sub-system is set to "send" interrupts, that interrupt type must +/// *also* be configured here or it won't actually be "received" by the CPU. +pub const IE: PlainAddr = unsafe { VolAddress::new(0x0400_0200) }; + +/// Interrupts Flagged. +/// +/// These are the interrupts that are pending, and haven't been handled. Clear a +/// pending interrupt by writing an [`IrqBits`] value with that bit enabled. The +/// assembly runtime handles this automatically, so you don't normally need to +/// interact with `IF` at all. +pub const IF: PlainAddr = unsafe { VolAddress::new(0x0400_0202) }; + +/// Interrupt Master Enable +/// +/// * When this is set to `true`, hardware interrupts that are flagged will +/// immediately run the interrupt handler. +/// * When this is `false`, any interrupt events that are flagged will be left +/// pending until this is again set to `true`. +/// +/// This defaults to `false`. +/// +/// Technically there's a two CPU cycle delay between this being written and +/// interrupts actually being enabled/disabled. In practice, it doesn't matter. +pub const IME: PlainAddr = unsafe { VolAddress::new(0x0400_0208) }; diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 0000000..8790236 --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,99 @@ +//! Keypad (button) reading. + +use super::*; + +/// Keypad input state. +#[derive(Clone, Copy, Default)] +#[repr(transparent)] +pub struct KeyInput(pub u16); +impl KeyInput { + /// If `a` is pressed (left primary button) + #[inline] + #[must_use] + pub const fn a(self) -> bool { + !bitfrob::u16_get_bit(0, self.0) + } + /// If `b` is pressed (right primary button) + #[inline] + #[must_use] + pub const fn b(self) -> bool { + !bitfrob::u16_get_bit(1, self.0) + } + /// If `select` is pressed (lower/left secondary button) + #[inline] + #[must_use] + pub const fn select(self) -> bool { + !bitfrob::u16_get_bit(2, self.0) + } + /// If `start` is pressed (upper/right secondary button) + #[inline] + #[must_use] + pub const fn start(self) -> bool { + !bitfrob::u16_get_bit(3, self.0) + } + /// If `right` is pressed (d-pad) + #[inline] + #[must_use] + pub const fn right(self) -> bool { + !bitfrob::u16_get_bit(4, self.0) + } + /// If `left` is pressed (d-pad) + #[inline] + #[must_use] + pub const fn left(self) -> bool { + !bitfrob::u16_get_bit(5, self.0) + } + /// If `up` is pressed (d-pad) + #[inline] + #[must_use] + pub const fn up(self) -> bool { + !bitfrob::u16_get_bit(6, self.0) + } + /// If `down` is pressed (d-pad) + #[inline] + #[must_use] + pub const fn down(self) -> bool { + !bitfrob::u16_get_bit(7, self.0) + } + /// If `r` is pressed (right shoulder button) + #[inline] + #[must_use] + pub const fn r(self) -> bool { + !bitfrob::u16_get_bit(8, self.0) + } + /// If `l` is pressed (left shoulder button) + #[inline] + #[must_use] + pub const fn l(self) -> bool { + !bitfrob::u16_get_bit(9, self.0) + } + /// Delta X of the d-pad. right +1, left -1. + #[inline] + #[must_use] + pub const fn dx(self) -> i8 { + if self.right() { + 1 + } else if self.left() { + -1 + } else { + 0 + } + } + /// Delta Y of the d-pad. up +1, down -1. + #[inline] + #[must_use] + pub const fn dy(self) -> i8 { + if self.up() { + 1 + } else if self.down() { + -1 + } else { + 0 + } + } +} + +/// Key Input (read-only). +/// +/// Gives the low-active button state of all system buttons. +pub const KEYINPUT: RoAddr = unsafe { VolAddress::new(0x0400_0130) }; diff --git a/src/lib.rs b/src/lib.rs index a04d5b1..5b88588 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ //! * [Per Project Setup][`per_project_setup`] use bitfrob::{u16_get_bit, u16_with_bit}; +use voladdress::VolAddress; macro_rules! on_gba_or_unimplemented { ($($token_tree:tt)*) => { @@ -55,10 +56,10 @@ pub mod bios; pub mod dma; pub mod gba_cell; pub mod gba_fixed; +pub mod irq; +pub mod keys; pub mod mem; pub mod mgba; -pub mod mmio; -pub mod obj; pub mod panic_handlers; pub mod per_project_setup; pub mod per_system_setup; @@ -67,6 +68,20 @@ pub mod sample_art; pub mod timers; pub mod video; +/// "safe on GBA", which is either Safe or Unsafe according to the `on_gba` +/// cargo feature. +#[cfg(feature = "on_gba")] +type SOGBA = voladdress::Safe; +#[cfg(not(feature = "on_gba"))] +type SOGBA = voladdress::Unsafe; + +/// Responds "normally" to read/write, just holds a setting +type PlainAddr = VolAddress; +/// Read-only addr +type RoAddr = VolAddress; +/// Write-only addr +type WoAddr = VolAddress; + #[cfg(feature = "critical-section")] #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "critical-section")))] pub mod critical_section; @@ -126,278 +141,3 @@ pub type i16fx14 = crate::gba_fixed::Fixed; #[cfg(not(feature = "fixed"))] #[allow(non_camel_case_types)] pub type i32fx8 = crate::gba_fixed::Fixed; - -/// Keypad input state. -#[derive(Clone, Copy, Default)] -#[repr(transparent)] -pub struct KeyInput(pub u16); -impl KeyInput { - /// If `a` is pressed (left primary button) - #[inline] - #[must_use] - pub const fn a(self) -> bool { - !bitfrob::u16_get_bit(0, self.0) - } - /// If `b` is pressed (right primary button) - #[inline] - #[must_use] - pub const fn b(self) -> bool { - !bitfrob::u16_get_bit(1, self.0) - } - /// If `select` is pressed (lower/left secondary button) - #[inline] - #[must_use] - pub const fn select(self) -> bool { - !bitfrob::u16_get_bit(2, self.0) - } - /// If `start` is pressed (upper/right secondary button) - #[inline] - #[must_use] - pub const fn start(self) -> bool { - !bitfrob::u16_get_bit(3, self.0) - } - /// If `right` is pressed (d-pad) - #[inline] - #[must_use] - pub const fn right(self) -> bool { - !bitfrob::u16_get_bit(4, self.0) - } - /// If `left` is pressed (d-pad) - #[inline] - #[must_use] - pub const fn left(self) -> bool { - !bitfrob::u16_get_bit(5, self.0) - } - /// If `up` is pressed (d-pad) - #[inline] - #[must_use] - pub const fn up(self) -> bool { - !bitfrob::u16_get_bit(6, self.0) - } - /// If `down` is pressed (d-pad) - #[inline] - #[must_use] - pub const fn down(self) -> bool { - !bitfrob::u16_get_bit(7, self.0) - } - /// If `r` is pressed (right shoulder button) - #[inline] - #[must_use] - pub const fn r(self) -> bool { - !bitfrob::u16_get_bit(8, self.0) - } - /// If `l` is pressed (left shoulder button) - #[inline] - #[must_use] - pub const fn l(self) -> bool { - !bitfrob::u16_get_bit(9, self.0) - } - /// Delta X of the d-pad. right +1, left -1. - #[inline] - #[must_use] - pub const fn dx(self) -> i8 { - if self.right() { - 1 - } else if self.left() { - -1 - } else { - 0 - } - } - /// Delta Y of the d-pad. up +1, down -1. - #[inline] - #[must_use] - pub const fn dy(self) -> i8 { - if self.up() { - 1 - } else if self.down() { - -1 - } else { - 0 - } - } -} - -/// Interrupt bit flags. -#[derive(Clone, Copy, Default)] -#[repr(transparent)] -pub struct IrqBits(u16); -impl IrqBits { - /// The vblank bit. - pub const VBLANK: Self = Self::new().with_vblank(true); - - /// Makes a new, empty value. - #[inline] - pub const fn new() -> Self { - Self(0) - } - /// Vertical-blank - #[inline] - #[must_use] - pub const fn vblank(self) -> bool { - u16_get_bit(0, self.0) - } - /// Horizontal-blank - #[inline] - #[must_use] - pub const fn hblank(self) -> bool { - u16_get_bit(1, self.0) - } - /// Vertical-counter match - #[inline] - #[must_use] - pub const fn vcount(self) -> bool { - u16_get_bit(2, self.0) - } - /// Timer 0 overflow - #[inline] - #[must_use] - pub const fn timer0(self) -> bool { - u16_get_bit(3, self.0) - } - /// Timer 1 overflow - #[inline] - #[must_use] - pub const fn timer1(self) -> bool { - u16_get_bit(4, self.0) - } - /// Timer 2 overflow - #[inline] - #[must_use] - pub const fn timer2(self) -> bool { - u16_get_bit(5, self.0) - } - /// Timer 3 overflow - #[inline] - #[must_use] - pub const fn timer3(self) -> bool { - u16_get_bit(6, self.0) - } - /// Serial port communication - #[inline] - #[must_use] - pub const fn serial(self) -> bool { - u16_get_bit(7, self.0) - } - /// DMA 0 complete - #[inline] - #[must_use] - pub const fn dma0(self) -> bool { - u16_get_bit(8, self.0) - } - /// DMA 1 complete - #[inline] - #[must_use] - pub const fn dma1(self) -> bool { - u16_get_bit(9, self.0) - } - /// DMA 2 complete - #[inline] - #[must_use] - pub const fn dma2(self) -> bool { - u16_get_bit(10, self.0) - } - /// DMA 3 complete - #[inline] - #[must_use] - pub const fn dma3(self) -> bool { - u16_get_bit(11, self.0) - } - /// Keypad match - #[inline] - #[must_use] - pub const fn keypad(self) -> bool { - u16_get_bit(12, self.0) - } - /// Game pak - #[inline] - #[must_use] - pub const fn gamepak(self) -> bool { - u16_get_bit(13, self.0) - } - - /// Set the vblank bit. - #[inline] - #[must_use] - pub const fn with_vblank(self, vblank: bool) -> Self { - Self(u16_with_bit(0, self.0, vblank)) - } - /// Set the hblank bit. - #[inline] - #[must_use] - pub const fn with_hblank(self, hblank: bool) -> Self { - Self(u16_with_bit(1, self.0, hblank)) - } - /// Set the vcount bit. - #[inline] - #[must_use] - pub const fn with_vcount(self, vcount: bool) -> Self { - Self(u16_with_bit(2, self.0, vcount)) - } - /// Set the timer0 bit. - #[inline] - #[must_use] - pub const fn with_timer0(self, timer0: bool) -> Self { - Self(u16_with_bit(3, self.0, timer0)) - } - /// Set the timer1 bit. - #[inline] - #[must_use] - pub const fn with_timer1(self, timer1: bool) -> Self { - Self(u16_with_bit(4, self.0, timer1)) - } - /// Set the timer2 bit. - #[inline] - #[must_use] - pub const fn with_timer2(self, timer2: bool) -> Self { - Self(u16_with_bit(5, self.0, timer2)) - } - /// Set the timer3 bit. - #[inline] - #[must_use] - pub const fn with_timer3(self, timer3: bool) -> Self { - Self(u16_with_bit(6, self.0, timer3)) - } - /// Set the serial bit. - #[inline] - #[must_use] - pub const fn with_serial(self, serial: bool) -> Self { - Self(u16_with_bit(7, self.0, serial)) - } - /// Set the dma0 bit. - #[inline] - #[must_use] - pub const fn with_dma0(self, dma0: bool) -> Self { - Self(u16_with_bit(8, self.0, dma0)) - } - /// Set the dma1 bit. - #[inline] - #[must_use] - pub const fn with_dma1(self, dma1: bool) -> Self { - Self(u16_with_bit(9, self.0, dma1)) - } - /// Set the dma2 bit. - #[inline] - #[must_use] - pub const fn with_dma2(self, dma2: bool) -> Self { - Self(u16_with_bit(10, self.0, dma2)) - } - /// Set the dma3 bit. - #[inline] - #[must_use] - pub const fn with_dma3(self, dma3: bool) -> Self { - Self(u16_with_bit(11, self.0, dma3)) - } - /// Set the keypad bit. - #[inline] - #[must_use] - pub const fn with_keypad(self, keypad: bool) -> Self { - Self(u16_with_bit(12, self.0, keypad)) - } - /// Set the gamepak bit. - #[inline] - #[must_use] - pub const fn with_gamepak(self, gamepak: bool) -> Self { - Self(u16_with_bit(13, self.0, gamepak)) - } -} diff --git a/src/mgba.rs b/src/mgba.rs index 41bbbe8..2614bf9 100644 --- a/src/mgba.rs +++ b/src/mgba.rs @@ -30,7 +30,9 @@ //! logs at that message level and also implicitly zeroes the message buffer so //! that it's ready for the next message. -use crate::mmio::{MGBA_LOG_BUFFER, MGBA_LOG_ENABLE, MGBA_LOG_SEND}; +use voladdress::VolBlock; + +use super::*; /// This is what you write to [`MGBA_LOG_ENABLE`] to signal to the emulator that /// you want to do logging. @@ -144,3 +146,22 @@ impl core::fmt::Write for MgbaLogger { ); } } + +/// The buffer to put logging messages into. +/// +/// The first `\0` in the buffer is the end of each message. +pub const MGBA_LOG_BUFFER: VolBlock = + unsafe { VolBlock::new(0x04FF_F600) }; + +/// Write to this each time you want to send out the current buffer content. +/// +/// It also resets the buffer content. +pub const MGBA_LOG_SEND: WoAddr = + unsafe { VolAddress::new(0x04FFF700) }; + +/// Allows you to enable/disable mGBA logging. +/// +/// This is enabled by default by the assembly runtime, so you don't normally +/// need to touch this. +pub const MGBA_LOG_ENABLE: PlainAddr = + unsafe { VolAddress::new(0x04FF_F780) }; diff --git a/src/mmio/mod.rs b/src/mmio/mod.rs deleted file mode 100644 index 3166b5f..0000000 --- a/src/mmio/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Definitions for Memory-mapped IO (hardware control). - -use core::ffi::c_void; - -use bitfrob::u8x2; -#[allow(unused_imports)] -use voladdress::VolAddress; -use voladdress::{Unsafe, VolBlock, VolGrid2d, VolGrid2dStrided, VolSeries}; - -use crate::{ - dma::DmaControl, - mgba::MgbaLogLevel, - obj::{ObjAttr, ObjAttr0, ObjAttr1, ObjAttr2}, - video::{ - BackgroundControl, Color, DisplayControl, DisplayStatus, TextEntry, Tile4, - }, - IrqBits, KeyInput, -}; - -/// "safe on GBA", which is either Safe or Unsafe according to the `on_gba` -/// cargo feature. -#[cfg(feature = "on_gba")] -type SOGBA = voladdress::Safe; -#[cfg(not(feature = "on_gba"))] -type SOGBA = voladdress::Unsafe; - -/// Responds "normally" to read/write, just holds a setting -type PlainAddr = VolAddress; -/// Read-only addr -type RoAddr = VolAddress; -/// Write-only addr -type WoAddr = VolAddress; - -mod peripheral_controls; -mod video_memory; - -pub use peripheral_controls::*; -pub use video_memory::*; diff --git a/src/mmio/peripheral_controls.rs b/src/mmio/peripheral_controls.rs deleted file mode 100644 index 4168172..0000000 --- a/src/mmio/peripheral_controls.rs +++ /dev/null @@ -1,217 +0,0 @@ -use crate::timers::TimerControl; - -use super::*; - -/// Display Control setting. -/// -/// This sets what background mode is active, as well as various related -/// details. -/// -/// Unlike most MMIO, this doesn't have an "all 0" state at boot. The -/// `forced_blank` bit it left set by the BIOS's startup routine. -pub const DISPCNT: PlainAddr = - unsafe { VolAddress::new(0x0400_0000) }; - -/// Display Status setting. -/// -/// Gives info on the display state, and controls display-based interrupts. -pub const DISPSTAT: PlainAddr = - unsafe { VolAddress::new(0x0400_0004) }; - -/// The current scanline that the display is working on. -/// -/// Values of 160 to 227 indicate that a vertical blank line is happening. -pub const VCOUNT: RoAddr = unsafe { VolAddress::new(0x0400_0006) }; - -/// Background 0 controls -pub const BG0CNT: PlainAddr = - unsafe { VolAddress::new(0x0400_0008) }; - -/// Background 1 controls -pub const BG1CNT: PlainAddr = - unsafe { VolAddress::new(0x0400_000A) }; - -/// Background 2 controls -pub const BG2CNT: PlainAddr = - unsafe { VolAddress::new(0x0400_000C) }; - -/// Background 3 controls -pub const BG3CNT: PlainAddr = - unsafe { VolAddress::new(0x0400_000E) }; - -/// Source address for DMA3. -/// -/// The correct pointer type depends on the transfer mode used. -pub const DMA0_SOURCE: VolAddress<*const c_void, (), Unsafe> = - unsafe { VolAddress::new(0x0400_00B0) }; - -/// Destination address for DMA3. -/// -/// The correct pointer type depends on the transfer mode used. -pub const DMA0_DESTINATION: VolAddress<*mut c_void, (), Unsafe> = - unsafe { VolAddress::new(0x0400_00B4) }; - -/// The number of transfers desired. -/// -/// A value of 0 indicates the maximum number of transfers: `0x4000` -pub const DMA0_TRANSFER_COUNT: VolAddress = - unsafe { VolAddress::new(0x0400_00B8) }; - -/// DMA3 Control Bits. -pub const DMA0_CONTROL: VolAddress = - unsafe { VolAddress::new(0x0400_00BA) }; - -/// Source address for DMA3. -/// -/// The correct pointer type depends on the transfer mode used. -pub const DMA1_SOURCE: VolAddress<*const c_void, (), Unsafe> = - unsafe { VolAddress::new(0x0400_00BC) }; - -/// Destination address for DMA3. -/// -/// The correct pointer type depends on the transfer mode used. -pub const DMA1_DESTINATION: VolAddress<*mut c_void, (), Unsafe> = - unsafe { VolAddress::new(0x0400_00C0) }; - -/// The number of transfers desired. -/// -/// A value of 0 indicates the maximum number of transfers: `0x4000` -pub const DMA1_TRANSFER_COUNT: VolAddress = - unsafe { VolAddress::new(0x0400_00C4) }; - -/// DMA3 Control Bits. -pub const DMA1_CONTROL: VolAddress = - unsafe { VolAddress::new(0x0400_00C6) }; - -/// Source address for DMA3. -/// -/// The correct pointer type depends on the transfer mode used. -pub const DMA2_SOURCE: VolAddress<*const c_void, (), Unsafe> = - unsafe { VolAddress::new(0x0400_00C8) }; - -/// Destination address for DMA3. -/// -/// The correct pointer type depends on the transfer mode used. -pub const DMA2_DESTINATION: VolAddress<*mut c_void, (), Unsafe> = - unsafe { VolAddress::new(0x0400_00CC) }; - -/// The number of transfers desired. -/// -/// A value of 0 indicates the maximum number of transfers: `0x4000` -pub const DMA2_TRANSFER_COUNT: VolAddress = - unsafe { VolAddress::new(0x0400_00D0) }; - -/// DMA3 Control Bits. -pub const DMA2_CONTROL: VolAddress = - unsafe { VolAddress::new(0x0400_00D2) }; - -/// Source address for DMA3. -/// -/// The correct pointer type depends on the transfer mode used. -pub const DMA3_SOURCE: VolAddress<*const c_void, (), Unsafe> = - unsafe { VolAddress::new(0x0400_00D4) }; - -/// Destination address for DMA3. -/// -/// The correct pointer type depends on the transfer mode used. -pub const DMA3_DESTINATION: VolAddress<*mut c_void, (), Unsafe> = - unsafe { VolAddress::new(0x0400_00D8) }; - -/// The number of transfers desired. -/// -/// A value of 0 indicates the maximum number of transfers: `0x1_0000` -pub const DMA3_TRANSFER_COUNT: VolAddress = - unsafe { VolAddress::new(0x0400_00DC) }; - -/// DMA3 Control Bits. -pub const DMA3_CONTROL: VolAddress = - unsafe { VolAddress::new(0x0400_00DE) }; - -/// Timer0's current counter value. -pub const TIMER0_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_0100) }; -/// Timer1's current counter value. -pub const TIMER1_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_0104) }; -/// Timer2's current counter value. -pub const TIMER2_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_0108) }; -/// Timer3's current counter value. -pub const TIMER3_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_010C) }; - -/// The value for Timer0 to reload on overflow on when the `start` bit is newly -/// set. -pub const TIMER0_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_0100) }; -/// The value for Timer1 to reload on overflow on when the `start` bit is newly -/// set. -pub const TIMER1_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_0104) }; -/// The value for Timer2 to reload on overflow on when the `start` bit is newly -/// set. -pub const TIMER2_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_0108) }; -/// The value for Timer3 to reload on overflow on when the `start` bit is newly -/// set. -pub const TIMER3_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_010C) }; - -/// Control bits for Timer 0. -pub const TIMER0_CONTROL: PlainAddr = - unsafe { VolAddress::new(0x0400_0102) }; - -/// Control bits for Timer 1. -pub const TIMER1_CONTROL: PlainAddr = - unsafe { VolAddress::new(0x0400_0106) }; - -/// Control bits for Timer 2. -pub const TIMER2_CONTROL: PlainAddr = - unsafe { VolAddress::new(0x0400_010A) }; - -/// Control bits for Timer 3. -pub const TIMER3_CONTROL: PlainAddr = - unsafe { VolAddress::new(0x0400_010E) }; - -/// Key Input (read-only). -/// -/// Gives the low-active button state of all system buttons. -pub const KEYINPUT: RoAddr = unsafe { VolAddress::new(0x0400_0130) }; - -/// Interrupts Enabled. -/// -/// When any sub-system is set to "send" interrupts, that interrupt type must -/// *also* be configured here or it won't actually be "received" by the CPU. -pub const IE: PlainAddr = unsafe { VolAddress::new(0x0400_0200) }; - -/// Interrupts Flagged. -/// -/// These are the interrupts that are pending, and haven't been handled. Clear a -/// pending interrupt by writing an [`IrqBits`] value with that bit enabled. The -/// assembly runtime handles this automatically, so you don't normally need to -/// interact with `IF` at all. -pub const IF: PlainAddr = unsafe { VolAddress::new(0x0400_0202) }; - -/// Interrupt Master Enable -/// -/// * When this is set to `true`, hardware interrupts that are flagged will -/// immediately run the interrupt handler. -/// * When this is `false`, any interrupt events that are flagged will be left -/// pending until this is again set to `true`. -/// -/// This defaults to `false`. -/// -/// Technically there's a two CPU cycle delay between this being written and -/// interrupts actually being enabled/disabled. In practice, it doesn't matter. -pub const IME: PlainAddr = unsafe { VolAddress::new(0x0400_0208) }; - -/// The buffer to put logging messages into. -/// -/// The first `\0` in the buffer is the end of each message. -pub const MGBA_LOG_BUFFER: VolBlock = - unsafe { VolBlock::new(0x04FF_F600) }; - -/// Write to this each time you want to send out the current buffer content. -/// -/// It also resets the buffer content. -pub const MGBA_LOG_SEND: WoAddr = - unsafe { VolAddress::new(0x04FFF700) }; - -/// Allows you to enable/disable mGBA logging. -/// -/// This is enabled by default by the assembly runtime, so you don't normally -/// need to touch this. -pub const MGBA_LOG_ENABLE: PlainAddr = - unsafe { VolAddress::new(0x04FF_F780) }; diff --git a/src/mmio/video_memory.rs b/src/mmio/video_memory.rs deleted file mode 100644 index a0d6e8b..0000000 --- a/src/mmio/video_memory.rs +++ /dev/null @@ -1,129 +0,0 @@ -use super::*; - -/// The backdrop color is the color shown when no *other* element is displayed -/// in a given pixel. -pub const BACKDROP_COLOR: PlainAddr = - unsafe { VolAddress::new(0x0500_0000) }; - -/// Palette data for the backgrounds -pub const BG_PALRAM: VolBlock = - unsafe { VolBlock::new(0x0500_0000) }; - -/// Palette data for the objects. -pub const OBJ_PALRAM: VolBlock = - unsafe { VolBlock::new(0x0500_0200) }; - -/// Gets the block for a specific palbank. -/// -/// ## Panics -/// * If the `bank` requested is 16 or greater this will panic. -#[inline] -#[must_use] -#[cfg_attr(feature = "track_caller", track_caller)] -pub const fn obj_palbank(bank: usize) -> VolBlock { - let u = OBJ_PALRAM.index(bank * 16).as_usize(); - unsafe { VolBlock::new(u) } -} - -/// The VRAM byte offset per screenblock index. -/// -/// This is the same for all background types and sizes. -pub const SCREENBLOCK_INDEX_OFFSET: usize = 2 * 1_024; - -/// The VRAM's background tile view, using 4bpp tiles. -pub const VRAM_BG_TILE4: VolBlock = - unsafe { VolBlock::new(0x0600_0000) }; - -/// The VRAM's background tile view, using 8bpp tiles. -pub const VRAM_BG_TILE8: VolBlock = - unsafe { VolBlock::new(0x0600_0000) }; - -/// The text mode screenblocks. -pub const TEXT_SCREENBLOCKS: VolGrid2dStrided< - TextEntry, - SOGBA, - SOGBA, - 32, - 32, - 32, - SCREENBLOCK_INDEX_OFFSET, -> = unsafe { VolGrid2dStrided::new(0x0600_0000) }; - -/// The VRAM's object tile view, using 4bpp tiles. -pub const VRAM_OBJ_TILE4: VolBlock = - unsafe { VolBlock::new(0x0601_0000) }; - -/// The VRAM's object tile view, using 8bpp tiles. -pub const VRAM_OBJ_TILE8: VolBlock = - unsafe { VolBlock::new(0x0601_0000) }; - -/// The VRAM's view in Video Mode 3. -/// -/// Each location is a direct color value. -pub const MODE3_VRAM: VolGrid2d = - unsafe { VolGrid2d::new(0x0600_0000) }; - -/// The VRAM's view in Video Mode 4. -/// -/// Each location is a pair of palette indexes into the background palette. -/// Because the VRAM can't be written with a single byte, we have to work with -/// this in units of [`u8x2`]. It's annoying, I know. -pub const MODE4_VRAM: VolGrid2dStrided< - u8x2, - SOGBA, - SOGBA, - { 240 / 2 }, - 160, - 2, - 0xA000, -> = unsafe { VolGrid2dStrided::new(0x0600_0000) }; - -/// The VRAM's view in Video Mode 5. -/// -/// Each location is a direct color value, but there's a lower image size to -/// allow for two frames. -pub const MODE5_VRAM: VolGrid2dStrided< - Color, - SOGBA, - SOGBA, - 160, - 128, - 2, - 0xA000, -> = unsafe { VolGrid2dStrided::new(0x0600_0000) }; - -/// The combined object attributes. -pub const OBJ_ATTR_ALL: VolSeries< - ObjAttr, - SOGBA, - SOGBA, - 128, - { core::mem::size_of::<[i16; 4]>() }, -> = unsafe { VolSeries::new(0x0700_0000) }; - -/// The object 0th attributes. -pub const OBJ_ATTR0: VolSeries< - ObjAttr0, - SOGBA, - SOGBA, - 128, - { core::mem::size_of::<[i16; 4]>() }, -> = unsafe { VolSeries::new(0x0700_0000) }; - -/// The object 1st attributes. -pub const OBJ_ATTR1: VolSeries< - ObjAttr1, - SOGBA, - SOGBA, - 128, - { core::mem::size_of::<[i16; 4]>() }, -> = unsafe { VolSeries::new(0x0700_0000 + 2) }; - -/// The object 2nd attributes. -pub const OBJ_ATTR2: VolSeries< - ObjAttr2, - SOGBA, - SOGBA, - 128, - { core::mem::size_of::<[i16; 4]>() }, -> = unsafe { VolSeries::new(0x0700_0000 + 4) }; diff --git a/src/obj.rs b/src/obj.rs deleted file mode 100644 index 17c754f..0000000 --- a/src/obj.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! Module for object (OBJ) entry data. -//! -//! The GBA's object drawing allows for hardware drawing that is independent of -//! the background layers. Another common term for objects is "sprites", but -//! within the GBA community they're called objects, so this crate calls them -//! objects too. -//! -//! The GBA has 128 object entries within the Object Attribute Memory (OAM) -//! region. The object entries are also interspersed with the memory for the -//! affine entries, so OAM should not be thought of as being an array of just -//! one or the other types of data. -//! -//! A few of the GBA's controls will affect all objects at once, particularly -//! the Display Control (which can control if the objects are visible at all), -//! but in general each object can be controlled independently. -//! -//! Each object entry consists of a number of bit-packed "attributes". The -//! object's attributes are stored in three 16-bit fields. The [ObjAttr] struct -//! has one field for each 16-bit group of attributes: [ObjAttr0], [ObjAttr1], -//! [ObjAttr2]. -//! -//! When you've got an object's data configured how you want, use either the -//! [`OBJ_ATTR_ALL`][crate::mmio::OBJ_ATTR_ALL] control (to write all fields at -//! once) or the [`OBJ_ATTR0`][crate::mmio::OBJ_ATTR0], -//! [`OBJ_ATTR1`][crate::mmio::OBJ_ATTR1], and/or -//! [`OBJ_ATTR2`][crate::mmio::OBJ_ATTR2] controls (to write just some of the -//! fields). -//! -//! **Note:** When the GBA first boots, the object layer will be off but the -//! object entries in OAM will *not* be set to prevent individual objects from -//! being displayed. Before enabling the object layer you should generally set -//! the [ObjDisplayStyle] of all [ObjAttr0] fields so that any objects you're -//! not using don't appear on the screen. Otherwise, you'll end up with -//! un-configured objects appearing in the upper left corner of the display. - -use bitfrob::{u16_with_bit, u16_with_region, u16_with_value}; - -/// How the object should be displayed. -/// -/// Bit 9 of Attr0 changes meaning depending on Bit 8, so this merges the two -/// bits into a single property. -#[derive(Debug, Clone, Copy, Default)] -#[repr(u16)] -pub enum ObjDisplayStyle { - /// The default, non-affine display - #[default] - Normal = 0 << 8, - /// Affine display - Affine = 1 << 8, - /// The object is *not* displayed at all. - NotDisplayed = 2 << 8, - /// Shows the object using Affine style but double sized. - DoubleSizeAffine = 3 << 8, -} - -/// What special effect the object interacts with -#[derive(Debug, Clone, Copy, Default)] -#[repr(u16)] -pub enum ObjEffectMode { - /// The default, no special effect interaction - #[default] - Normal = 0 << 10, - /// The object counts as a potential 1st target for alpha blending, - /// regardless of the actual blend control settings register configuration. - SemiTransparent = 1 << 10, - /// The object is not displayed. Instead, all non-transparent pixels in this - /// object become part of the "OBJ Window" mask. - Window = 2 << 10, -} - -/// The shape of an object. -/// -/// The object's actual display area also depends on its `size` setting: -/// -/// | Size | Square | Horizontal | Vertical | -/// |:-:|:-:|:-:|:-:| -/// | 0 | 8x8 | 16x8 | 8x16 | -/// | 1 | 16x16 | 32x8 | 8x32 | -/// | 2 | 32x32 | 32x16 | 16x32 | -/// | 3 | 64x64 | 64x32 | 32x64 | -#[derive(Debug, Clone, Copy, Default)] -#[repr(u16)] -#[allow(missing_docs)] -pub enum ObjShape { - #[default] - Square = 0 << 14, - Horizontal = 1 << 14, - Vertical = 2 << 14, -} - -/// Object Attributes, field 0 of the entry. -#[derive(Debug, Clone, Copy, Default)] -#[repr(transparent)] -pub struct ObjAttr0(u16); -impl ObjAttr0 { - /// A new blank attr 0. - #[inline] - pub const fn new() -> Self { - Self(0) - } - /// Sets the `y` position of this object - #[inline] - pub const fn with_y(self, y: u16) -> Self { - Self(u16_with_value(0, 7, self.0, y as u16)) - } - /// The object's display styling. - #[inline] - pub const fn with_style(self, style: ObjDisplayStyle) -> Self { - Self(u16_with_region(8, 9, self.0, style as u16)) - } - /// The special effect mode of the object, if any. - #[inline] - pub const fn with_effect(self, effect: ObjEffectMode) -> Self { - Self(u16_with_region(10, 11, self.0, effect as u16)) - } - /// If the object should use the mosaic effect. - #[inline] - pub const fn with_mosaic(self, mosaic: bool) -> Self { - Self(u16_with_bit(12, self.0, mosaic)) - } - /// If the object draws using 8-bits-per-pixel. - #[inline] - pub const fn with_bpp8(self, bpp8: bool) -> Self { - Self(u16_with_bit(13, self.0, bpp8)) - } - /// The object's shape - #[inline] - pub const fn with_shape(self, shape: ObjShape) -> Self { - Self(u16_with_region(14, 15, self.0, shape as u16)) - } -} - -/// Object Attributes, field 1 of the entry. -#[derive(Debug, Clone, Copy, Default)] -#[repr(transparent)] -pub struct ObjAttr1(u16); -impl ObjAttr1 { - /// A new blank attr 1. - #[inline] - pub const fn new() -> Self { - Self(0) - } - /// Sets the `x` position of this object - #[inline] - pub const fn with_x(self, x: u16) -> Self { - Self(u16_with_value(0, 8, self.0, x as u16)) - } - /// The affine index of the object. - #[inline] - pub const fn with_affine_index(self, index: u16) -> Self { - Self(u16_with_value(9, 13, self.0, index as u16)) - } - /// If the object is horizontally flipped - #[inline] - pub const fn with_hflip(self, hflip: bool) -> Self { - Self(u16_with_bit(12, self.0, hflip)) - } - /// If the object is vertically flipped - #[inline] - pub const fn with_vflip(self, vflip: bool) -> Self { - Self(u16_with_bit(13, self.0, vflip)) - } - /// The object's size - /// - /// The size you set here, combined with the shape of the object, determines - /// the object's actual area. - /// - /// | Size | Square| Horizontal| Vertical| - /// |:-:|:-:|:-:|:-:| - /// | 0 | 8x8 | 16x8 | 8x16 | - /// | 1 | 16x16 | 32x8 | 8x32 | - /// | 2 | 32x32 | 32x16 | 16x32 | - /// | 3 | 64x64 | 64x32 | 32x64 | - #[inline] - pub const fn with_size(self, size: u16) -> Self { - Self(u16_with_value(14, 15, self.0, size as u16)) - } -} - -/// Object Attributes, field 2 of the entry. -#[derive(Debug, Clone, Copy, Default)] -#[repr(transparent)] -pub struct ObjAttr2(u16); -impl ObjAttr2 { - /// A new blank attr 2. - #[inline] - pub const fn new() -> Self { - Self(0) - } - /// The base tile id of the object. - /// - /// All other tiles in the object are automatically selected using the - /// following tiles, according to if - /// [`with_obj_vram_1d`][crate::video::DisplayControl::with_obj_vram_1d] it - /// set or not. - #[inline] - pub const fn with_tile_id(self, id: u16) -> Self { - Self(u16_with_value(0, 9, self.0, id as u16)) - } - /// Sets the object's priority sorting. - /// - /// Lower priority objects are closer to the viewer, and will appear in front - /// other objects that have *higher* priority, and in front of backgrounds of - /// *equal or higher* priority. If two objects have the same priority, the - /// lower index object is shown. - #[inline] - pub const fn with_priority(self, priority: u16) -> Self { - Self(u16_with_value(10, 11, self.0, priority as u16)) - } - /// Sets the palbank value of this object. - #[inline] - pub const fn with_palbank(self, palbank: u16) -> Self { - Self(u16_with_value(12, 15, self.0, palbank as u16)) - } -} - -/// Object Attributes. -/// -/// The fields of this struct are all `pub` so that you can simply alter them as -/// you wish. Some "setter" methods are also provided as a shorthand. -#[derive(Debug, Clone, Copy, Default)] -#[repr(C)] -pub struct ObjAttr(pub ObjAttr0, pub ObjAttr1, pub ObjAttr2); -#[allow(missing_docs)] -impl ObjAttr { - #[inline] - pub const fn new() -> Self { - Self(ObjAttr0::new(), ObjAttr1::new(), ObjAttr2::new()) - } - #[inline] - pub fn set_y(&mut self, y: u16) { - self.0 = self.0.with_y(y); - } - #[inline] - pub fn set_style(&mut self, style: ObjDisplayStyle) { - self.0 = self.0.with_style(style); - } - #[inline] - pub fn set_x(&mut self, x: u16) { - self.1 = self.1.with_x(x); - } - #[inline] - pub fn set_tile_id(&mut self, id: u16) { - self.2 = self.2.with_tile_id(id); - } - #[inline] - pub fn set_palbank(&mut self, palbank: u16) { - self.2 = self.2.with_palbank(palbank); - } -} diff --git a/src/timers.rs b/src/timers.rs index 9662641..d32dd8c 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -1,5 +1,6 @@ //! Timer related data types. +use super::*; use bitfrob::{u8_with_bit, u8_with_region}; /// Control bits for one of the GBA's four timers. @@ -47,3 +48,41 @@ pub enum CpusPerTick { _256 = 2, _1024 = 3, } + +/// Timer0's current counter value. +pub const TIMER0_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_0100) }; +/// Timer1's current counter value. +pub const TIMER1_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_0104) }; +/// Timer2's current counter value. +pub const TIMER2_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_0108) }; +/// Timer3's current counter value. +pub const TIMER3_COUNTER: RoAddr = unsafe { VolAddress::new(0x0400_010C) }; + +/// The value for Timer0 to reload on overflow on when the `start` bit is newly +/// set. +pub const TIMER0_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_0100) }; +/// The value for Timer1 to reload on overflow on when the `start` bit is newly +/// set. +pub const TIMER1_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_0104) }; +/// The value for Timer2 to reload on overflow on when the `start` bit is newly +/// set. +pub const TIMER2_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_0108) }; +/// The value for Timer3 to reload on overflow on when the `start` bit is newly +/// set. +pub const TIMER3_RELOAD: WoAddr = unsafe { VolAddress::new(0x0400_010C) }; + +/// Control bits for Timer 0. +pub const TIMER0_CONTROL: PlainAddr = + unsafe { VolAddress::new(0x0400_0102) }; + +/// Control bits for Timer 1. +pub const TIMER1_CONTROL: PlainAddr = + unsafe { VolAddress::new(0x0400_0106) }; + +/// Control bits for Timer 2. +pub const TIMER2_CONTROL: PlainAddr = + unsafe { VolAddress::new(0x0400_010A) }; + +/// Control bits for Timer 3. +pub const TIMER3_CONTROL: PlainAddr = + unsafe { VolAddress::new(0x0400_010E) }; diff --git a/src/video.rs b/src/video.rs index bd98445..0855bbf 100644 --- a/src/video.rs +++ b/src/video.rs @@ -1,8 +1,11 @@ //! Module for screen-related types and functions. -use bitfrob::{u16_get_bit, u16_with_bit, u16_with_value}; +use bitfrob::{ + u16_get_bit, u16_with_bit, u16_with_region, u16_with_value, u8x2, +}; +use voladdress::{VolBlock, VolGrid2d, VolGrid2dStrided, VolSeries}; -use crate::mmio::MODE3_VRAM; +use super::*; /// A color value. /// @@ -381,7 +384,7 @@ impl Mode3 { in("r7") x, in("r8") x, count = inout(reg) 240 => _, - ptr = inout(reg) crate::mmio::MODE3_VRAM.as_usize() => _, + ptr = inout(reg) MODE3_VRAM.as_usize() => _, options(nostack), ) }); @@ -420,3 +423,382 @@ impl Mode3 { ); } } + +/// The backdrop color is the color shown when no *other* element is displayed +/// in a given pixel. +pub const BACKDROP_COLOR: PlainAddr = + unsafe { VolAddress::new(0x0500_0000) }; + +/// Palette data for the backgrounds +pub const BG_PALRAM: VolBlock = + unsafe { VolBlock::new(0x0500_0000) }; + +/// Palette data for the objects. +pub const OBJ_PALRAM: VolBlock = + unsafe { VolBlock::new(0x0500_0200) }; + +/// Gets the block for a specific palbank. +/// +/// ## Panics +/// * If the `bank` requested is 16 or greater this will panic. +#[inline] +#[must_use] +#[cfg_attr(feature = "track_caller", track_caller)] +pub const fn obj_palbank(bank: usize) -> VolBlock { + let u = OBJ_PALRAM.index(bank * 16).as_usize(); + unsafe { VolBlock::new(u) } +} + +/// The VRAM byte offset per screenblock index. +/// +/// This is the same for all background types and sizes. +pub const SCREENBLOCK_INDEX_OFFSET: usize = 2 * 1_024; + +/// The VRAM's background tile view, using 4bpp tiles. +pub const VRAM_BG_TILE4: VolBlock = + unsafe { VolBlock::new(0x0600_0000) }; + +/// The VRAM's background tile view, using 8bpp tiles. +pub const VRAM_BG_TILE8: VolBlock = + unsafe { VolBlock::new(0x0600_0000) }; + +/// The text mode screenblocks. +pub const TEXT_SCREENBLOCKS: VolGrid2dStrided< + TextEntry, + SOGBA, + SOGBA, + 32, + 32, + 32, + SCREENBLOCK_INDEX_OFFSET, +> = unsafe { VolGrid2dStrided::new(0x0600_0000) }; + +/// The VRAM's object tile view, using 4bpp tiles. +pub const VRAM_OBJ_TILE4: VolBlock = + unsafe { VolBlock::new(0x0601_0000) }; + +/// The VRAM's object tile view, using 8bpp tiles. +pub const VRAM_OBJ_TILE8: VolBlock = + unsafe { VolBlock::new(0x0601_0000) }; + +/// The VRAM's view in Video Mode 3. +/// +/// Each location is a direct color value. +pub const MODE3_VRAM: VolGrid2d = + unsafe { VolGrid2d::new(0x0600_0000) }; + +/// The VRAM's view in Video Mode 4. +/// +/// Each location is a pair of palette indexes into the background palette. +/// Because the VRAM can't be written with a single byte, we have to work with +/// this in units of [`u8x2`]. It's annoying, I know. +pub const MODE4_VRAM: VolGrid2dStrided< + u8x2, + SOGBA, + SOGBA, + { 240 / 2 }, + 160, + 2, + 0xA000, +> = unsafe { VolGrid2dStrided::new(0x0600_0000) }; + +/// The VRAM's view in Video Mode 5. +/// +/// Each location is a direct color value, but there's a lower image size to +/// allow for two frames. +pub const MODE5_VRAM: VolGrid2dStrided< + Color, + SOGBA, + SOGBA, + 160, + 128, + 2, + 0xA000, +> = unsafe { VolGrid2dStrided::new(0x0600_0000) }; + +/// The combined object attributes. +pub const OBJ_ATTR_ALL: VolSeries< + ObjAttr, + SOGBA, + SOGBA, + 128, + { core::mem::size_of::<[i16; 4]>() }, +> = unsafe { VolSeries::new(0x0700_0000) }; + +/// The object 0th attributes. +pub const OBJ_ATTR0: VolSeries< + ObjAttr0, + SOGBA, + SOGBA, + 128, + { core::mem::size_of::<[i16; 4]>() }, +> = unsafe { VolSeries::new(0x0700_0000) }; + +/// The object 1st attributes. +pub const OBJ_ATTR1: VolSeries< + ObjAttr1, + SOGBA, + SOGBA, + 128, + { core::mem::size_of::<[i16; 4]>() }, +> = unsafe { VolSeries::new(0x0700_0000 + 2) }; + +/// The object 2nd attributes. +pub const OBJ_ATTR2: VolSeries< + ObjAttr2, + SOGBA, + SOGBA, + 128, + { core::mem::size_of::<[i16; 4]>() }, +> = unsafe { VolSeries::new(0x0700_0000 + 4) }; + +/// How the object should be displayed. +/// +/// Bit 9 of Attr0 changes meaning depending on Bit 8, so this merges the two +/// bits into a single property. +#[derive(Debug, Clone, Copy, Default)] +#[repr(u16)] +pub enum ObjDisplayStyle { + /// The default, non-affine display + #[default] + Normal = 0 << 8, + /// Affine display + Affine = 1 << 8, + /// The object is *not* displayed at all. + NotDisplayed = 2 << 8, + /// Shows the object using Affine style but double sized. + DoubleSizeAffine = 3 << 8, +} + +/// What special effect the object interacts with +#[derive(Debug, Clone, Copy, Default)] +#[repr(u16)] +pub enum ObjEffectMode { + /// The default, no special effect interaction + #[default] + Normal = 0 << 10, + /// The object counts as a potential 1st target for alpha blending, + /// regardless of the actual blend control settings register configuration. + SemiTransparent = 1 << 10, + /// The object is not displayed. Instead, all non-transparent pixels in this + /// object become part of the "OBJ Window" mask. + Window = 2 << 10, +} + +/// The shape of an object. +/// +/// The object's actual display area also depends on its `size` setting: +/// +/// | Size | Square | Horizontal | Vertical | +/// |:-:|:-:|:-:|:-:| +/// | 0 | 8x8 | 16x8 | 8x16 | +/// | 1 | 16x16 | 32x8 | 8x32 | +/// | 2 | 32x32 | 32x16 | 16x32 | +/// | 3 | 64x64 | 64x32 | 32x64 | +#[derive(Debug, Clone, Copy, Default)] +#[repr(u16)] +#[allow(missing_docs)] +pub enum ObjShape { + #[default] + Square = 0 << 14, + Horizontal = 1 << 14, + Vertical = 2 << 14, +} + +/// Object Attributes, field 0 of the entry. +#[derive(Debug, Clone, Copy, Default)] +#[repr(transparent)] +pub struct ObjAttr0(u16); +impl ObjAttr0 { + /// A new blank attr 0. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// Sets the `y` position of this object + #[inline] + pub const fn with_y(self, y: u16) -> Self { + Self(u16_with_value(0, 7, self.0, y as u16)) + } + /// The object's display styling. + #[inline] + pub const fn with_style(self, style: ObjDisplayStyle) -> Self { + Self(u16_with_region(8, 9, self.0, style as u16)) + } + /// The special effect mode of the object, if any. + #[inline] + pub const fn with_effect(self, effect: ObjEffectMode) -> Self { + Self(u16_with_region(10, 11, self.0, effect as u16)) + } + /// If the object should use the mosaic effect. + #[inline] + pub const fn with_mosaic(self, mosaic: bool) -> Self { + Self(u16_with_bit(12, self.0, mosaic)) + } + /// If the object draws using 8-bits-per-pixel. + #[inline] + pub const fn with_bpp8(self, bpp8: bool) -> Self { + Self(u16_with_bit(13, self.0, bpp8)) + } + /// The object's shape + #[inline] + pub const fn with_shape(self, shape: ObjShape) -> Self { + Self(u16_with_region(14, 15, self.0, shape as u16)) + } +} + +/// Object Attributes, field 1 of the entry. +#[derive(Debug, Clone, Copy, Default)] +#[repr(transparent)] +pub struct ObjAttr1(u16); +impl ObjAttr1 { + /// A new blank attr 1. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// Sets the `x` position of this object + #[inline] + pub const fn with_x(self, x: u16) -> Self { + Self(u16_with_value(0, 8, self.0, x as u16)) + } + /// The affine index of the object. + #[inline] + pub const fn with_affine_index(self, index: u16) -> Self { + Self(u16_with_value(9, 13, self.0, index as u16)) + } + /// If the object is horizontally flipped + #[inline] + pub const fn with_hflip(self, hflip: bool) -> Self { + Self(u16_with_bit(12, self.0, hflip)) + } + /// If the object is vertically flipped + #[inline] + pub const fn with_vflip(self, vflip: bool) -> Self { + Self(u16_with_bit(13, self.0, vflip)) + } + /// The object's size + /// + /// The size you set here, combined with the shape of the object, determines + /// the object's actual area. + /// + /// | Size | Square| Horizontal| Vertical| + /// |:-:|:-:|:-:|:-:| + /// | 0 | 8x8 | 16x8 | 8x16 | + /// | 1 | 16x16 | 32x8 | 8x32 | + /// | 2 | 32x32 | 32x16 | 16x32 | + /// | 3 | 64x64 | 64x32 | 32x64 | + #[inline] + pub const fn with_size(self, size: u16) -> Self { + Self(u16_with_value(14, 15, self.0, size as u16)) + } +} + +/// Object Attributes, field 2 of the entry. +#[derive(Debug, Clone, Copy, Default)] +#[repr(transparent)] +pub struct ObjAttr2(u16); +impl ObjAttr2 { + /// A new blank attr 2. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// The base tile id of the object. + /// + /// All other tiles in the object are automatically selected using the + /// following tiles, according to if + /// [`with_obj_vram_1d`][crate::video::DisplayControl::with_obj_vram_1d] it + /// set or not. + #[inline] + pub const fn with_tile_id(self, id: u16) -> Self { + Self(u16_with_value(0, 9, self.0, id as u16)) + } + /// Sets the object's priority sorting. + /// + /// Lower priority objects are closer to the viewer, and will appear in front + /// other objects that have *higher* priority, and in front of backgrounds of + /// *equal or higher* priority. If two objects have the same priority, the + /// lower index object is shown. + #[inline] + pub const fn with_priority(self, priority: u16) -> Self { + Self(u16_with_value(10, 11, self.0, priority as u16)) + } + /// Sets the palbank value of this object. + #[inline] + pub const fn with_palbank(self, palbank: u16) -> Self { + Self(u16_with_value(12, 15, self.0, palbank as u16)) + } +} + +/// Object Attributes. +/// +/// The fields of this struct are all `pub` so that you can simply alter them as +/// you wish. Some "setter" methods are also provided as a shorthand. +#[derive(Debug, Clone, Copy, Default)] +#[repr(C)] +pub struct ObjAttr(pub ObjAttr0, pub ObjAttr1, pub ObjAttr2); +#[allow(missing_docs)] +impl ObjAttr { + #[inline] + pub const fn new() -> Self { + Self(ObjAttr0::new(), ObjAttr1::new(), ObjAttr2::new()) + } + #[inline] + pub fn set_y(&mut self, y: u16) { + self.0 = self.0.with_y(y); + } + #[inline] + pub fn set_style(&mut self, style: ObjDisplayStyle) { + self.0 = self.0.with_style(style); + } + #[inline] + pub fn set_x(&mut self, x: u16) { + self.1 = self.1.with_x(x); + } + #[inline] + pub fn set_tile_id(&mut self, id: u16) { + self.2 = self.2.with_tile_id(id); + } + #[inline] + pub fn set_palbank(&mut self, palbank: u16) { + self.2 = self.2.with_palbank(palbank); + } +} + +/// Display Control setting. +/// +/// This sets what background mode is active, as well as various related +/// details. +/// +/// Unlike most MMIO, this doesn't have an "all 0" state at boot. The +/// `forced_blank` bit it left set by the BIOS's startup routine. +pub const DISPCNT: PlainAddr = + unsafe { VolAddress::new(0x0400_0000) }; + +/// Display Status setting. +/// +/// Gives info on the display state, and controls display-based interrupts. +pub const DISPSTAT: PlainAddr = + unsafe { VolAddress::new(0x0400_0004) }; + +/// The current scanline that the display is working on. +/// +/// Values of 160 to 227 indicate that a vertical blank line is happening. +pub const VCOUNT: RoAddr = unsafe { VolAddress::new(0x0400_0006) }; + +/// Background 0 controls +pub const BG0CNT: PlainAddr = + unsafe { VolAddress::new(0x0400_0008) }; + +/// Background 1 controls +pub const BG1CNT: PlainAddr = + unsafe { VolAddress::new(0x0400_000A) }; + +/// Background 2 controls +pub const BG2CNT: PlainAddr = + unsafe { VolAddress::new(0x0400_000C) }; + +/// Background 3 controls +pub const BG3CNT: PlainAddr = + unsafe { VolAddress::new(0x0400_000E) };