Improve DMA

According to GBATEK:
The SAD, DAD, and CNT_L registers are holding the initial start
addresses, and initial length. The hardware does NOT change the content
of these registers during or after the transfer.

The actual transfer takes place by using internal pointer/counter
registers. The initial values are copied into internal regs under the
following circumstances:

Upon DMA Enable (Bit 15) changing from 0 to 1: Reloads SAD, DAD, CNT_L.

Upon Repeat: Reloads CNT_L, and optionally DAD (Increment+Reload).


Former-commit-id: 1e606dc88603a4600a79a341ef17fe8ccb482871
This commit is contained in:
Michel Heily 2019-11-16 18:16:13 +02:00
parent 4f2fbc2af2
commit ccfff31123

View file

@ -21,12 +21,22 @@ pub struct DmaChannel {
pub wc: u32, pub wc: u32,
pub ctrl: DmaChannelCtrl, pub ctrl: DmaChannelCtrl,
// These are "latched" when the dma is enabled.
internal: DmaInternalRegs,
running: bool, running: bool,
cycles: usize, cycles: usize,
start_cycles: usize, start_cycles: usize,
irq: Interrupt, irq: Interrupt,
} }
#[derive(Debug, Default)]
struct DmaInternalRegs {
src_addr: u32,
dst_addr: u32,
count: u32,
}
impl DmaChannel { impl DmaChannel {
pub fn new(id: usize) -> DmaChannel { pub fn new(id: usize) -> DmaChannel {
if id > 3 { if id > 3 {
@ -42,6 +52,7 @@ impl DmaChannel {
ctrl: DmaChannelCtrl(0), ctrl: DmaChannelCtrl(0),
cycles: 0, cycles: 0,
start_cycles: 0, start_cycles: 0,
internal: Default::default(),
} }
} }
@ -78,12 +89,13 @@ impl DmaChannel {
pub fn write_dma_ctrl(&mut self, value: u16) -> bool { pub fn write_dma_ctrl(&mut self, value: u16) -> bool {
let ctrl = DmaChannelCtrl(value); let ctrl = DmaChannelCtrl(value);
let mut start_immediately = false; let mut start_immediately = false;
if ctrl.is_enabled() { if ctrl.is_enabled() && !self.ctrl.is_enabled() {
self.start_cycles = self.cycles; self.start_cycles = self.cycles;
self.running = true; self.running = true;
if ctrl.timing() == 0 { start_immediately = ctrl.timing() == 0;
start_immediately = true; self.internal.src_addr = self.src;
} self.internal.dst_addr = self.dst;
self.internal.count = self.wc;
} }
self.ctrl = ctrl; self.ctrl = ctrl;
return start_immediately; return start_immediately;
@ -91,25 +103,30 @@ impl DmaChannel {
fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) { fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
let word_size = if self.ctrl.is_32bit() { 4 } else { 2 }; let word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
let dst_rld = self.dst; let count = match self.internal.count {
for word in 0..self.wc { 0 => match self.id {
3 => 0x1_0000,
_ => 0x0_4000,
},
_ => self.internal.count,
};
for _ in 0..count {
if word_size == 4 { if word_size == 4 {
let w = sb.read_32(self.src); let w = sb.read_32(self.internal.src_addr);
sb.write_32(self.dst, w) sb.write_32(self.internal.dst_addr, w)
} else { } else {
let hw = sb.read_16(self.src); let hw = sb.read_16(self.internal.src_addr);
// println!("src {:x} dst {:x}", self.src, self.dst); sb.write_16(self.internal.dst_addr, hw)
sb.write_16(self.dst, hw)
} }
match self.ctrl.src_adj() { match self.ctrl.src_adj() {
/* Increment */ 0 => self.src += word_size, /* Increment */ 0 => self.internal.src_addr += word_size,
/* Decrement */ 1 => self.src -= word_size, /* Decrement */ 1 => self.internal.src_addr -= word_size,
/* Fixed */ 2 => {} /* Fixed */ 2 => {}
_ => panic!("forbidden DMA source address adjustment"), _ => panic!("forbidden DMA source address adjustment"),
} }
match self.ctrl.dst_adj() { match self.ctrl.dst_adj() {
/* Increment[+Reload] */ 0 | 3 => self.dst += word_size, /* Increment[+Reload] */ 0 | 3 => self.internal.dst_addr += word_size,
/* Decrement */ 1 => self.dst -= word_size, /* Decrement */ 1 => self.internal.dst_addr -= word_size,
/* Fixed */ 2 => {} /* Fixed */ 2 => {}
_ => panic!("forbidden DMA dest address adjustment"), _ => panic!("forbidden DMA dest address adjustment"),
} }
@ -121,7 +138,7 @@ impl DmaChannel {
self.start_cycles = self.cycles; self.start_cycles = self.cycles;
/* reload */ /* reload */
if 3 == self.ctrl.dst_adj() { if 3 == self.ctrl.dst_adj() {
self.dst = dst_rld; self.internal.dst_addr = self.dst;
} }
} else { } else {
self.running = false; self.running = false;