use core::hint::spin_loop;
use core::ptr::{read_volatile, write_volatile};
use crate::cache;
const DMA_BASE: usize = crate::soc::PERIPHERAL_BASE as usize + 0x7000;
const CHANNEL_STRIDE: usize = 0x100;
const DMA_ENABLE: *mut u32 = (DMA_BASE + 0xff0) as *mut u32;
const CS: usize = 0x00;
const CONBLK_AD: usize = 0x04;
const DEBUG: usize = 0x20;
const CS_ACTIVE: u32 = 1 << 0;
const CS_END: u32 = 1 << 1;
const CS_INT: u32 = 1 << 2;
const CS_ERROR: u32 = 1 << 8;
const CS_RESET: u32 = 1 << 31;
const DEBUG_ERRORS: u32 = 0b111;
const TI_WAIT_RESP: u32 = 1 << 3;
const TI_DEST_INC: u32 = 1 << 4;
const TI_DEST_DREQ: u32 = 1 << 6;
const TI_SRC_INC: u32 = 1 << 8;
const TI_SRC_DREQ: u32 = 1 << 10;
const TI_PERMAP_SHIFT: u32 = 16;
const TI_NO_WIDE_BURSTS: u32 = 1 << 26;
const MAX_CHANNEL: u8 = 14;
const FIRST_LITE_CHANNEL: u8 = 7;
const MAX_LEN_FULL: usize = (1 << 30) - 1;
const MAX_LEN_LITE: usize = (1 << 16) - 1;
#[repr(C, align(32))]
struct ControlBlock {
ti: u32,
source_ad: u32,
dest_ad: u32,
txfr_len: u32,
stride: u32,
nextconblk: u32,
_reserved: [u32; 2],
}
impl ControlBlock {
const EMPTY: ControlBlock = ControlBlock {
ti: 0,
source_ad: 0,
dest_ad: 0,
txfr_len: 0,
stride: 0,
nextconblk: 0,
_reserved: [0; 2],
};
}
fn peripheral_write_cb(
src_bus: u32,
len_bytes: u32,
dreq: u8,
dest_bus: u32,
next_bus: u32,
) -> ControlBlock {
ControlBlock {
ti: TI_SRC_INC
| TI_DEST_DREQ
| TI_WAIT_RESP
| TI_NO_WIDE_BURSTS
| ((dreq as u32) << TI_PERMAP_SHIFT),
source_ad: src_bus,
dest_ad: dest_bus,
txfr_len: len_bytes,
stride: 0,
nextconblk: next_bus,
_reserved: [0; 2],
}
}
fn peripheral_read_cb(src_bus: u32, len_bytes: u32, dreq: u8, dest_bus: u32) -> ControlBlock {
ControlBlock {
ti: TI_DEST_INC
| TI_SRC_DREQ
| TI_WAIT_RESP
| TI_NO_WIDE_BURSTS
| ((dreq as u32) << TI_PERMAP_SHIFT),
source_ad: src_bus,
dest_ad: dest_bus,
txfr_len: len_bytes,
stride: 0,
nextconblk: 0,
_reserved: [0; 2],
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
LengthMismatch,
TooLong,
Transfer,
}
pub struct Dma {
taken: u16,
}
impl Dma {
pub fn new() -> Self {
Self { taken: 0 }
}
pub fn channel(&mut self, id: u8) -> Option<Channel> {
if id > MAX_CHANNEL || self.taken & (1 << id) != 0 {
return None;
}
self.taken |= 1 << id;
unsafe {
let enable = read_volatile(DMA_ENABLE);
write_volatile(DMA_ENABLE, enable | (1 << id));
}
Some(Channel {
id,
cb: [ControlBlock::EMPTY; 2],
})
}
}
impl Default for Dma {
fn default() -> Self {
Self::new()
}
}
pub struct Channel {
id: u8,
cb: [ControlBlock; 2],
}
impl Channel {
pub fn memcpy(&mut self, dest: &mut [u8], src: &[u8]) -> Result<(), Error> {
if src.len() != dest.len() {
return Err(Error::LengthMismatch);
}
let len = src.len();
if len == 0 {
return Ok(());
}
if len > self.max_len() {
return Err(Error::TooLong);
}
let src_phys = src.as_ptr() as usize as u32;
let dest_phys = dest.as_mut_ptr() as usize as u32;
cache::clean_range(src_phys, len);
cache::clean_range(dest_phys, len);
self.cb[0] = ControlBlock {
ti: TI_SRC_INC | TI_DEST_INC | TI_WAIT_RESP,
source_ad: to_bus(src_phys),
dest_ad: to_bus(dest_phys),
txfr_len: len as u32,
stride: 0,
nextconblk: 0,
_reserved: [0; 2],
};
let cb_phys = (&self.cb[0] as *const ControlBlock) as usize as u32;
cache::clean_range(cb_phys, core::mem::size_of::<ControlBlock>());
self.start(to_bus(cb_phys));
self.wait_blocking()?;
cache::invalidate_range(dest_phys, len);
Ok(())
}
pub fn copy_from_peripheral(
&mut self,
dest: &mut [u8],
dreq: u8,
src_bus: u32,
) -> Result<(), Error> {
let len = dest.len();
if len == 0 {
return Ok(());
}
if len > self.max_len() {
return Err(Error::TooLong);
}
let dest_phys = dest.as_mut_ptr() as usize as u32;
cache::clean_range(dest_phys, len);
self.cb[0] = peripheral_read_cb(src_bus, len as u32, dreq, to_bus(dest_phys));
let cb_phys = (&self.cb[0] as *const ControlBlock) as usize as u32;
cache::clean_range(cb_phys, core::mem::size_of::<ControlBlock>());
self.start(to_bus(cb_phys));
self.wait_blocking()?;
cache::invalidate_range(dest_phys, len);
Ok(())
}
pub fn copy_to_peripheral(&mut self, src: &[u8], dreq: u8, dest_bus: u32) -> Result<(), Error> {
let len = src.len();
if len == 0 {
return Ok(());
}
if len > self.max_len() {
return Err(Error::TooLong);
}
let src_phys = src.as_ptr() as usize as u32;
cache::clean_range(src_phys, len);
self.cb[0] = peripheral_write_cb(to_bus(src_phys), len as u32, dreq, dest_bus, 0);
let cb_phys = (&self.cb[0] as *const ControlBlock) as usize as u32;
cache::clean_range(cb_phys, core::mem::size_of::<ControlBlock>());
self.start(to_bus(cb_phys));
self.wait_blocking()
}
pub fn start_from_peripheral<'a>(
&'a mut self,
dest: &'a mut [u8],
dreq: u8,
src_bus: u32,
) -> Result<Transfer<'a>, Error> {
let len = dest.len();
if len == 0 {
return Ok(Transfer {
channel: self,
invalidate: None,
});
}
if len > self.max_len() {
return Err(Error::TooLong);
}
let dest_phys = dest.as_mut_ptr() as usize as u32;
cache::clean_range(dest_phys, len);
self.cb[0] = peripheral_read_cb(src_bus, len as u32, dreq, to_bus(dest_phys));
let cb_phys = (&self.cb[0] as *const ControlBlock) as usize as u32;
cache::clean_range(cb_phys, core::mem::size_of::<ControlBlock>());
self.start(to_bus(cb_phys));
Ok(Transfer {
channel: self,
invalidate: Some((dest_phys, len)),
})
}
pub fn start_to_peripheral<'a>(
&'a mut self,
src: &'a [u8],
dreq: u8,
dest_bus: u32,
) -> Result<Transfer<'a>, Error> {
let len = src.len();
if len == 0 {
return Ok(Transfer {
channel: self,
invalidate: None,
});
}
if len > self.max_len() {
return Err(Error::TooLong);
}
let src_phys = src.as_ptr() as usize as u32;
cache::clean_range(src_phys, len);
self.cb[0] = peripheral_write_cb(to_bus(src_phys), len as u32, dreq, dest_bus, 0);
let cb_phys = (&self.cb[0] as *const ControlBlock) as usize as u32;
cache::clean_range(cb_phys, core::mem::size_of::<ControlBlock>());
self.start(to_bus(cb_phys));
Ok(Transfer {
channel: self,
invalidate: None,
})
}
fn wait_blocking(&self) -> Result<(), Error> {
unsafe {
let cs = self.reg(CS);
loop {
let status = read_volatile(cs);
if status & CS_ACTIVE == 0 {
if status & CS_ERROR != 0 || read_volatile(self.reg(DEBUG)) & DEBUG_ERRORS != 0
{
return Err(Error::Transfer);
}
return Ok(());
}
spin_loop();
}
}
}
pub fn write_peripheral<'a>(
&'a mut self,
src: &'a [u32],
dreq: u8,
dest_bus: u32,
cyclic: bool,
) -> Result<Transfer<'a>, Error> {
let len = core::mem::size_of_val(src);
if len == 0 {
return Ok(Transfer {
channel: self,
invalidate: None,
});
}
if len > self.max_len() {
return Err(Error::TooLong);
}
let src_phys = src.as_ptr() as usize as u32;
cache::clean_range(src_phys, len);
let cb_phys = (&self.cb[0] as *const ControlBlock) as usize as u32;
let cb_bus = to_bus(cb_phys);
self.cb[0] = peripheral_write_cb(
to_bus(src_phys),
len as u32,
dreq,
dest_bus,
if cyclic { cb_bus } else { 0 },
);
cache::clean_range(cb_phys, core::mem::size_of::<ControlBlock>());
self.start(cb_bus);
Ok(Transfer {
channel: self,
invalidate: None,
})
}
pub fn stream_peripheral<'a>(
&'a mut self,
buffers: [&'a mut [u32]; 2],
dreq: u8,
dest_bus: u32,
) -> Result<Stream<'a>, Error> {
let [len0, len1] = [
core::mem::size_of_val(&*buffers[0]),
core::mem::size_of_val(&*buffers[1]),
];
if len0 == 0 || len1 == 0 {
return Err(Error::LengthMismatch);
}
if len0 > self.max_len() || len1 > self.max_len() {
return Err(Error::TooLong);
}
let src_bus = [
to_bus(buffers[0].as_ptr() as usize as u32),
to_bus(buffers[1].as_ptr() as usize as u32),
];
cache::clean_range(buffers[0].as_ptr() as usize as u32, len0);
cache::clean_range(buffers[1].as_ptr() as usize as u32, len1);
let cb_bus = [
to_bus((&self.cb[0] as *const ControlBlock) as usize as u32),
to_bus((&self.cb[1] as *const ControlBlock) as usize as u32),
];
self.cb[0] = peripheral_write_cb(src_bus[0], len0 as u32, dreq, dest_bus, cb_bus[1]);
self.cb[1] = peripheral_write_cb(src_bus[1], len1 as u32, dreq, dest_bus, cb_bus[0]);
cache::clean_range(
(&self.cb as *const [ControlBlock; 2]) as usize as u32,
core::mem::size_of::<[ControlBlock; 2]>(),
);
self.start(cb_bus[0]);
Ok(Stream {
channel: self,
buffers,
cb_bus,
next: 0,
})
}
fn start(&self, cb_bus: u32) {
unsafe {
let cs = self.reg(CS);
write_volatile(cs, CS_RESET);
while read_volatile(cs) & CS_RESET != 0 {
spin_loop();
}
write_volatile(cs, CS_END | CS_INT);
write_volatile(self.reg(DEBUG), DEBUG_ERRORS);
write_volatile(self.reg(CONBLK_AD), cb_bus);
write_volatile(cs, CS_ACTIVE);
}
}
fn halt(&self) {
unsafe {
let cs = self.reg(CS);
write_volatile(cs, CS_RESET);
while read_volatile(cs) & CS_RESET != 0 {
spin_loop();
}
}
}
fn max_len(&self) -> usize {
if self.id >= FIRST_LITE_CHANNEL {
MAX_LEN_LITE
} else {
MAX_LEN_FULL
}
}
fn reg(&self, offset: usize) -> *mut u32 {
(DMA_BASE + self.id as usize * CHANNEL_STRIDE + offset) as *mut u32
}
}
pub struct Transfer<'a> {
channel: &'a mut Channel,
invalidate: Option<(u32, usize)>,
}
impl Transfer<'_> {
pub fn is_complete(&self) -> bool {
unsafe { read_volatile(self.channel.reg(CS)) & CS_ACTIVE == 0 }
}
pub fn is_error(&self) -> bool {
unsafe {
read_volatile(self.channel.reg(CS)) & CS_ERROR != 0
|| read_volatile(self.channel.reg(DEBUG)) & DEBUG_ERRORS != 0
}
}
pub fn stop(self) {}
}
impl Drop for Transfer<'_> {
fn drop(&mut self) {
self.channel.halt();
if let Some((phys, len)) = self.invalidate {
cache::invalidate_range(phys, len);
}
}
}
pub struct Stream<'a> {
channel: &'a mut Channel,
buffers: [&'a mut [u32]; 2],
cb_bus: [u32; 2],
next: usize,
}
impl Stream<'_> {
fn playing(&self) -> usize {
let current = unsafe { read_volatile(self.channel.reg(CONBLK_AD)) };
if current == self.cb_bus[1] {
1
} else {
0
}
}
pub fn feed(&mut self, fill: impl FnOnce(&mut [u32])) {
let idx = self.next;
while self.playing() == idx {
spin_loop();
}
fill(self.buffers[idx]);
let addr = self.buffers[idx].as_ptr() as usize as u32;
let len = core::mem::size_of_val(&*self.buffers[idx]);
cache::clean_range(addr, len);
self.next = 1 - idx;
}
pub fn is_error(&self) -> bool {
unsafe {
read_volatile(self.channel.reg(CS)) & CS_ERROR != 0
|| read_volatile(self.channel.reg(DEBUG)) & DEBUG_ERRORS != 0
}
}
pub fn stop(self) {}
}
impl Drop for Stream<'_> {
fn drop(&mut self) {
self.channel.halt();
}
}
fn to_bus(physical_address: u32) -> u32 {
(physical_address & 0x3fff_ffff) | 0xc000_0000
}