gb/src/ppu/dma.rs

103 lines
2.4 KiB
Rust

use crate::instruction::Cycle;
#[derive(Debug, Default, Clone)]
pub(crate) struct DirectMemoryAccess {
pub(crate) state: DmaState,
cycle: Cycle,
/// 0xFF46 | DMA - Transfer and Start Address
pub(crate) start: DmaAddress,
}
impl DirectMemoryAccess {
pub(crate) fn clock(&mut self) -> Option<(u16, u16)> {
match self.state {
DmaState::Pending => {
self.cycle += 1;
// Four Cycles pass before we actually start transferring
// files
if self.cycle == 4 {
self.state = DmaState::Transferring;
}
None
}
DmaState::Transferring => {
self.cycle += 1;
let src_addr = self
.start
.addr
.as_mut()
.expect("Source Address present during DMA Transfer");
let addresses = if (self.cycle - 4) % 4 == 0 {
*src_addr += 1;
Some((*src_addr, 0xFE00 | (*src_addr & 0x00FF)))
} else {
None
};
if self.cycle == 644 {
self.reset();
return None;
}
addresses
}
DmaState::Disabled => None,
}
}
pub(crate) fn is_active(&self) -> bool {
self.state == DmaState::Transferring
}
fn reset(&mut self) {
self.cycle = Cycle::new(0);
self.state = DmaState::Disabled;
self.start.addr = None;
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum DmaState {
Disabled,
Pending,
Transferring,
}
impl Default for DmaState {
fn default() -> Self {
Self::Disabled
}
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct DmaAddress {
/// The current *source* address of the DMA Transfer
///
/// NB: Will be None if no DMA Transfer is in progress
addr: Option<u16>,
}
impl DmaAddress {
pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) {
let start = (byte as u16) << 8;
self.addr = Some(start);
*state = DmaState::Pending;
}
}
impl From<DmaAddress> for u8 {
fn from(ctrl: DmaAddress) -> Self {
match ctrl.addr {
Some(addr) => (addr >> 8) as u8,
None => 0xFF, // TODO: What garbage value should be here?
}
}
}