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