diff options
Diffstat (limited to 'drivers/edma/src/lib.rs')
| -rw-r--r-- | drivers/edma/src/lib.rs | 990 |
1 files changed, 990 insertions, 0 deletions
diff --git a/drivers/edma/src/lib.rs b/drivers/edma/src/lib.rs new file mode 100644 index 0000000..7a23953 --- /dev/null +++ b/drivers/edma/src/lib.rs @@ -0,0 +1,990 @@ +//! DMA blocks for i.MX RT MCUs. +//! +//! eDMA works for all i.MX RT 1000 and 1100 series MCUs. +//! The eDMA3 and eDMA4 is specifically targeted for the +//! 1180 MCUs. + +#![no_std] + +pub mod dma; +pub mod dmamux; +pub mod tcd; + +pub mod element { + /// An ID for an element size. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[non_exhaustive] + #[repr(u8)] + pub enum ElementSize { + /// `u8` transfer. + U8 = 0, + /// `u16` transfer. + U16 = 1, + /// `u32` transfer. + U32 = 2, + /// `u64` transfer. + U64 = 3, + } + + /// Describes a transferrable DMA element; basically, an unsigned + /// integer of any size. + pub trait Element: Copy + private::Sealed { + /// An identifier describing the data transfer size + /// + /// Part of the TCD API; see documentation on TCD\[SSIZE\] + /// and TCD\[DSIZE\] for more information. + const DATA_TRANSFER_ID: ElementSize; + } + + impl Element for u8 { + const DATA_TRANSFER_ID: ElementSize = ElementSize::U8; + } + + impl Element for u16 { + const DATA_TRANSFER_ID: ElementSize = ElementSize::U16; + } + + impl Element for u32 { + const DATA_TRANSFER_ID: ElementSize = ElementSize::U32; + } + + impl Element for u64 { + const DATA_TRANSFER_ID: ElementSize = ElementSize::U64; + } + + mod private { + pub trait Sealed {} + + impl Sealed for u8 {} + impl Sealed for u16 {} + impl Sealed for u32 {} + impl Sealed for u64 {} + } +} + +pub mod edma { + use core::num::NonZeroU8; + + use crate::{dma::edma as dma, dmamux, element::ElementSize, tcd::edma as tcd}; + use ral_registers::Instance; + + /// A DMA channel for an eDMA controller. + /// + /// The channel is three pointers wide. + pub struct Channel { + index: usize, + dma: Instance<dma::RegisterBlock>, + mux: Instance<dmamux::RegisterBlock>, + } + + impl Channel { + /// Create a new DMA channel. + /// + /// # Safety + /// + /// The channel formed by this call cannot alias another channel with the + /// same index. The channel index must be valid for the hardware. + pub const unsafe fn new( + dma: Instance<dma::RegisterBlock>, + mux: Instance<dmamux::RegisterBlock>, + index: usize, + ) -> Self { + Self { index, dma, mux } + } + + /// Returns the channel index. + pub fn index(&self) -> usize { + self.index + } + + /// Enable the DMA channel. + /// + /// # Safety + /// + /// If the channel is incorrectly configured, it may access + /// invalid memory. + pub unsafe fn enable(&mut self) { + ral_registers::write_reg!(dma, self.dma, SERQ, self.index as u8); + } + + /// Disable the DMA channel. + pub fn disable(&mut self) { + ral_registers::write_reg!(dma, self.dma, CERQ, self.index as u8); + } + + /// Returns `true` if the DMA channel is enabled. + pub fn is_enabled(&self) -> bool { + (1 << self.index) & ral_registers::read_reg!(dma, self.dma, ERQ) != 0 + } + + fn tcd(&self) -> Instance<tcd::RegisterBlock> { + // Safety: caller claims the index is valid for + // the DMA controller. + unsafe { Instance::new_unchecked(&raw mut (*self.dma.as_ptr()).TCD[self.index]) } + } + + /// Returns `true` if the hardware is signaling requests towards + /// this DMA channel. + pub fn is_hardware_signaling(&self) -> bool { + (1 << self.index) & ral_registers::read_reg!(dma, self.dma, HRS) != 0 + } + + /// Returns `true` if an interrupt has activated for this + /// DMA channel. + pub fn is_interrupt(&self) -> bool { + (1 << self.index) & ral_registers::read_reg!(dma, self.dma, INT) != 0 + } + + /// Clear the interrupt trigger. + pub fn clear_interrupt(&self) { + ral_registers::write_reg!(dma, self.dma, CINT, self.index as u8); + } + + /// Returns `true` if this DMA channel has finished its transfer. + pub fn is_done(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), CSR, DONE == 1) + } + + /// Clear the "done" signal from software. + pub fn clear_done(&self) { + ral_registers::write_reg!(dma, self.dma, CDNE, self.index as u8); + } + + /// Returns `true` if this channel has an error. + pub fn has_error(&self) -> bool { + (1 << self.index) & ral_registers::read_reg!(dma, self.dma, ERR) != 0 + } + + /// Clear this channel's error. + pub fn clear_error(&self) { + ral_registers::write_reg!(dma, self.dma, CERR, self.index as u8); + } + + /// Returns `true` if this channel is actively transferring. + pub fn is_active(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), CSR, ACTIVE == 1) + } + + /// Reset the control descriptor for the channel. + /// + /// Call this at least once, after acquiring the channel, to + /// put it into a known-good state. + pub fn reset(&mut self) { + unsafe { self.tcd().as_ptr().write_bytes(0, 1) }; + } + + /// Set the source address for a DMA transfer + /// + /// `saddr` should be a memory location that can provide the DMA controller + /// with data. + /// + /// # Safety + /// + /// If the DMA channel is already enabled, the DMA engine may start reading this + /// memory location. You must ensure that reads to `saddr` do not perform + /// inappropriate side effects. You must ensure `saddr` is valid for the + /// lifetime of the transfer. + pub unsafe fn set_source_address(&self, saddr: *const ()) { + ral_registers::write_reg!(tcd, self.tcd(), SADDR, saddr as u32); + } + + /// Set the source offset *in bytes* + /// + /// `offset` could be negative, which would decrement the address. + /// + /// # Safety + /// + /// This method could allow a DMA engine to read beyond a buffer or + /// address. You must ensure that the source is valid for these offsets. + pub unsafe fn set_source_offset(&self, offset: i16) { + ral_registers::write_reg!(tcd, self.tcd(), SOFF, offset); + } + + /// Set the destination address for a DMA transfer + /// + /// `daddr` should be a memory location that can store data from the + /// DMA controller. + /// + /// # Safety + /// + /// If the DMA channel is already enabled, the DMA engine may start + /// writing to this address. You must ensure that writes to `daddr` + /// are safe, and that the memory is valid for the lifetime of the + /// transfer. + pub unsafe fn set_destination_address(&self, daddr: *const ()) { + ral_registers::write_reg!(tcd, self.tcd(), DADDR, daddr as u32); + } + + /// Set the destination offset *in bytes* + /// + /// `offset` could be negative, which would decrement the address. + /// + /// # Safety + /// + /// This method could allow a DMA engine to write beyond the range of + /// a buffer. You must ensure that the destination is valid for these + /// offsets. + pub unsafe fn set_destination_offset(&self, offset: i16) { + ral_registers::write_reg!(tcd, self.tcd(), DOFF, offset); + } + + /// Set the transfer attributes. + /// + /// The attributes describes the ways the source is read and + /// the destination is written. The modulo mask allows the + /// buffer to be treated as a circular buffer. + /// + /// # Safety + /// + /// Incorrect sizes and modulos may cause invalid memory accesses. + pub unsafe fn set_attributes( + &self, + source_size: ElementSize, + source_modulo: u8, + destination_size: ElementSize, + destination_modulo: u8, + ) { + ral_registers::write_reg!(tcd, self.tcd(), ATTR, + SSIZE: source_size as u16, + SMOD: source_modulo as u16, + DSIZE: destination_size as u16, + DMOD: destination_modulo as u16, + ); + } + + /// Set the source last address adjustment *in bytes* + /// + /// # Safety + /// + /// This could allow the DMA engine to reference an invalid source buffer. + /// You must ensure that the adjustment performed by the DMA engine is + /// valid, assuming that another DMA transfer immediately runs after the + /// current transfer completes. + pub unsafe fn set_source_last_address_adjustment(&self, adjustment: i32) { + ral_registers::write_reg!(tcd, self.tcd(), SLAST, adjustment); + } + + /// Set the destination last addrss adjustment *in bytes* + /// + /// # Safety + /// + /// This could allow the DMA engine to reference an invalid destination address. + /// You must ensure that the adjustment performed by the DMA engine is + /// valid, assuming that another DMA transfer immediately runs after the + /// current transfer completes. + pub unsafe fn set_destination_last_address_adjustment(&self, adjustment: i32) { + ral_registers::write_reg!(tcd, self.tcd(), DLAST_SGA, adjustment); + } + + /// Set the number of *bytes* to transfer per minor loop + /// + /// Describes how many bytes we should transfer for each DMA service request. + /// Note that `nbytes` of `0` is interpreted as a 4GB transfer. + /// + /// # Safety + /// + /// This might allow the DMA engine to read beyond the source, or write beyond + /// the destination. Caller must ensure that the number of bytes per minor loop + /// is valid for the given transfer. + pub unsafe fn set_minor_loop_bytes(&self, nbytes: u32) { + ral_registers::write_reg!(tcd, self.tcd(), NBYTES, nbytes); + } + + /// Tells the DMA channel how many transfer iterations to perform + /// + /// A 'transfer iteration' is a read from a source, and a write to a destination, with + /// read and write sizes described by a minor loop. Each iteration requires a DMA + /// service request, either from hardware or from software. The maximum number of iterations + /// is 2^15. + /// + /// # Safety + /// + /// This may allow the DMA engine to read beyond the source, or write beyond + /// the destination. Caller must ensure that the number of iterations is valid + /// for the transfer. + pub unsafe fn set_transfer_iterations(&mut self, iterations: u16) { + let iterations = iterations & 0x7FFF; + // Note that this is clearing the ELINK bit. We don't have support + // for channel-to-channel linking right now. Clearing ELINK is intentional + // to use the whole 15 bits for iterations. + ral_registers::write_reg!(tcd, self.tcd(), CITER, iterations); + ral_registers::write_reg!(tcd, self.tcd(), BITER, iterations); + } + + /// Returns the beginning transfer iterations setting for the channel. + /// + /// This reflects the last call to `set_transfer_iterations`. + pub fn beginning_transfer_iterations(&self) -> u16 { + ral_registers::read_reg!(tcd, self.tcd(), BITER) + } + + /// Enable or disable 'disable on completion' + /// + /// 'Disable on completion' lets the DMA channel automatically clear the request signal + /// when it completes a transfer. + pub fn set_disable_on_completion(&mut self, dreq: bool) { + ral_registers::modify_reg!(tcd, self.tcd(), CSR, DREQ: dreq as u16); + } + + /// Enable or disable interrupt generation when the transfer completes + /// + /// You're responsible for registering your interrupt handler. + pub fn set_interrupt_on_completion(&mut self, intr: bool) { + ral_registers::modify_reg!(tcd, self.tcd(), CSR, INTMAJOR: intr as u16); + } + + /// Start a DMA transfer + /// + /// `start()` should be used to request service from the DMA controller. It's + /// necessary for in-memory DMA transfers. Do not use it for hardware-initiated + /// DMA transfers. DMA transfers that involve hardware will rely on the hardware + /// to request DMA service. + /// + /// Flag is automatically cleared by hardware after it's asserted. + pub fn start(&mut self) { + ral_registers::modify_reg!(tcd, self.tcd(), CSR, START: 1); + } + + /// Set the source signal for channel activation. + /// + /// The source is typically an upstream peripheral. + /// Use `None` to clear the source. + /// + /// This call reads back the written source. If they're equal + /// the return is `true`. The values may not be equal if the + /// source is selected in another DMA channel. + pub fn set_mux_source_signal(&self, signal: Option<NonZeroU8>) -> bool { + let signal = signal.map_or(0, NonZeroU8::get) as u32; + ral_registers::modify_reg!(dmamux, self.mux, CHCFG[self.index], SOURCE: signal); + signal == ral_registers::read_reg!(dmamux, self.mux, CHCFG[self.index], SOURCE) + } + + /// Enable or disable the DMA MUX source signaling / triggering. + /// + /// Make sure to set up a source signal before enabling. + pub fn set_mux_source_enable(&self, enable: bool) { + ral_registers::modify_reg!(dmamux, self.mux, CHCFG[self.index], ENBL: enable as u32); + } + + /// Set periodic triggering for this DMA channel. + /// + /// Only the first four DMA channels can periodically trigger + /// on a PIT timer. Note that this isn't enabled. + /// + /// # Panics + /// + /// Panics if this isn't one of the first four DMA channels. + pub fn set_mux_periodic(&self, periodic: bool) { + assert!(self.index < 4); + ral_registers::modify_reg!(dmamux, self.mux, CHCFG[self.index], TRIG: periodic as u32); + } + } +} + +pub mod edma3 { + use core::num::NonZeroU8; + + use crate::{dma::edma3 as dma, element::ElementSize, tcd::edma34 as tcd}; + use ral_registers::Instance; + + /// A DMA channel for an eDMA controller. + /// + /// The channel is two pointers wide. + pub struct Channel { + index: usize, + dma: Instance<dma::RegisterBlock>, + } + + impl Channel { + /// Create a new DMA channel. + /// + /// # Safety + /// + /// The channel formed by this call cannot alias another channel with the + /// same index. The channel index must be valid for the hardware. + pub const unsafe fn new(dma: Instance<dma::RegisterBlock>, index: usize) -> Self { + Self { index, dma } + } + + /// Enable the DMA channel. + /// + /// # Safety + /// + /// If the channel is incorrectly configured, it may access + /// invalid memory. + pub unsafe fn enable(&mut self) { + ral_registers::modify_reg!(tcd, self.tcd(), CSR, ERQ: 1, DONE: 0); + } + + /// Disable the DMA channel. + pub fn disable(&mut self) { + ral_registers::modify_reg!(tcd, self.tcd(), CSR, ERQ: 0, DONE: 0); + } + + /// Returns `true` if the DMA channel is enabled. + pub fn is_enabled(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), CSR, ERQ != 0) + } + + fn tcd(&self) -> Instance<tcd::RegisterBlock> { + // Safety: caller claims the index is valid for + // the DMA controller. + unsafe { Instance::new_unchecked(&raw mut (*self.dma.as_ptr()).TCD[self.index]) } + } + + /// Returns `true` if the hardware is signaling requests towards + /// this DMA channel. + pub fn is_hardware_signaling(&self) -> bool { + (1 << self.index) & ral_registers::read_reg!(dma, self.dma, HRS) != 0 + } + + /// Returns `true` if an interrupt has activated for this + /// DMA channel. + pub fn is_interrupt(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), INT) != 0 + } + + /// Clear the interrupt trigger. + pub fn clear_interrupt(&self) { + ral_registers::write_reg!(tcd, self.tcd(), INT, 1); + } + + /// Returns `true` if this DMA channel has finished its transfer. + pub fn is_done(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), CSR, DONE == 1) + } + + /// Clear the "done" signal from software. + pub fn clear_done(&self) { + // Safety: the CSR register is the first register of the + // TCD. The peripheral tolerates 8-bit access. We can poke + // the DONE bit with a single write without risking a race + // in the lower bits of the register. + // + // I should rewrite the register block to lay out the data + // like I want... + unsafe { + let csr: *mut u32 = &raw mut (*self.tcd().as_ptr()).CSR; + csr.cast::<u8>().add(3).write_volatile(1 << 6) + }; + } + + /// Returns `true` if this channel has an error. + pub fn has_error(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), ES) != 0 + } + + /// Clear this channel's error. + pub fn clear_error(&self) { + ral_registers::write_reg!(tcd, self.tcd(), ES, ERR: 1); + } + + /// Returns `true` if this channel is actively transferring. + pub fn is_active(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), CSR, ACTIVE == 1) + } + + /// Reset the control descriptor for the channel. + /// + /// Call this at least once, after acquiring the channel, to + /// put it into a known-good state. + pub fn reset(&mut self) { + unsafe { + let tcd = &raw mut (*self.tcd().as_ptr()).TCD; + tcd.write_bytes(0, 1); + }; + } + + /// Set the source address for a DMA transfer + /// + /// `saddr` should be a memory location that can provide the DMA controller + /// with data. + /// + /// # Safety + /// + /// If the DMA channel is already enabled, the DMA engine may start reading this + /// memory location. You must ensure that reads to `saddr` do not perform + /// inappropriate side effects. You must ensure `saddr` is valid for the + /// lifetime of the transfer. + pub unsafe fn set_source_address(&self, saddr: *const ()) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.SADDR, saddr as u32); + } + + /// Set the source offset *in bytes* + /// + /// `offset` could be negative, which would decrement the address. + /// + /// # Safety + /// + /// This method could allow a DMA engine to read beyond a buffer or + /// address. You must ensure that the source is valid for these offsets. + pub unsafe fn set_source_offset(&self, offset: i16) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.SOFF, offset); + } + + /// Set the destination address for a DMA transfer + /// + /// `daddr` should be a memory location that can store data from the + /// DMA controller. + /// + /// # Safety + /// + /// If the DMA channel is already enabled, the DMA engine may start + /// writing to this address. You must ensure that writes to `daddr` + /// are safe, and that the memory is valid for the lifetime of the + /// transfer. + pub unsafe fn set_destination_address(&self, daddr: *const ()) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.DADDR, daddr as u32); + } + + /// Set the destination offset *in bytes* + /// + /// `offset` could be negative, which would decrement the address. + /// + /// # Safety + /// + /// This method could allow a DMA engine to write beyond the range of + /// a buffer. You must ensure that the destination is valid for these + /// offsets. + pub unsafe fn set_destination_offset(&self, offset: i16) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.DOFF, offset); + } + + /// Set the transfer attributes. + /// + /// The attributes describes the ways the source is read and + /// the destination is written. The modulo mask allows the + /// buffer to be treated as a circular buffer. + /// + /// # Safety + /// + /// Incorrect sizes and modulos may cause invalid memory accesses. + pub unsafe fn set_attributes( + &self, + source_size: ElementSize, + source_modulo: u8, + destination_size: ElementSize, + destination_modulo: u8, + ) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.ATTR, + SSIZE: source_size as u16, + SMOD: source_modulo as u16, + DSIZE: destination_size as u16, + DMOD: destination_modulo as u16, + ); + } + + /// Set the source last address adjustment *in bytes* + /// + /// # Safety + /// + /// This could allow the DMA engine to reference an invalid source buffer. + /// You must ensure that the adjustment performed by the DMA engine is + /// valid, assuming that another DMA transfer immediately runs after the + /// current transfer completes. + pub unsafe fn set_source_last_address_adjustment(&self, adjustment: i32) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.SLAST, adjustment); + } + + /// Set the destination last addrss adjustment *in bytes* + /// + /// # Safety + /// + /// This could allow the DMA engine to reference an invalid destination address. + /// You must ensure that the adjustment performed by the DMA engine is + /// valid, assuming that another DMA transfer immediately runs after the + /// current transfer completes. + pub unsafe fn set_destination_last_address_adjustment(&self, adjustment: i32) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.DLAST_SGA, adjustment); + } + + /// Set the number of *bytes* to transfer per minor loop + /// + /// Describes how many bytes we should transfer for each DMA service request. + /// Note that `nbytes` of `0` is interpreted as a 4GB transfer. + /// + /// # Safety + /// + /// This might allow the DMA engine to read beyond the source, or write beyond + /// the destination. Caller must ensure that the number of bytes per minor loop + /// is valid for the given transfer. + pub unsafe fn set_minor_loop_bytes(&self, nbytes: u32) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.NBYTES, nbytes); + } + + /// Tells the DMA channel how many transfer iterations to perform + /// + /// A 'transfer iteration' is a read from a source, and a write to a destination, with + /// read and write sizes described by a minor loop. Each iteration requires a DMA + /// service request, either from hardware or from software. The maximum number of iterations + /// is 2^15. + /// + /// # Safety + /// + /// This may allow the DMA engine to read beyond the source, or write beyond + /// the destination. Caller must ensure that the number of iterations is valid + /// for the transfer. + pub unsafe fn set_transfer_iterations(&mut self, iterations: u16) { + let iterations = iterations & 0x7FFF; + // Note that this is clearing the ELINK bit. We don't have support + // for channel-to-channel linking right now. Clearing ELINK is intentional + // to use the whole 15 bits for iterations. + ral_registers::write_reg!(tcd, self.tcd(), TCD.CITER, iterations); + ral_registers::write_reg!(tcd, self.tcd(), TCD.BITER, iterations); + } + + /// Returns the beginning transfer iterations setting for the channel. + /// + /// This reflects the last call to `set_transfer_iterations`. + pub fn beginning_transfer_iterations(&self) -> u16 { + ral_registers::read_reg!(tcd, self.tcd(), TCD.BITER) + } + + /// Enable or disable 'disable on completion' + /// + /// 'Disable on completion' lets the DMA channel automatically clear the request signal + /// when it completes a transfer. + pub fn set_disable_on_completion(&mut self, dreq: bool) { + ral_registers::modify_reg!(tcd, self.tcd(), TCD.CSR, DREQ: dreq as u16); + } + + /// Enable or disable interrupt generation when the transfer completes + /// + /// You're responsible for registering your interrupt handler. + pub fn set_interrupt_on_completion(&mut self, intr: bool) { + ral_registers::modify_reg!(tcd, self.tcd(), TCD.CSR, INTMAJOR: intr as u16); + } + + /// Start a DMA transfer + /// + /// `start()` should be used to request service from the DMA controller. It's + /// necessary for in-memory DMA transfers. Do not use it for hardware-initiated + /// DMA transfers. DMA transfers that involve hardware will rely on the hardware + /// to request DMA service. + /// + /// Flag is automatically cleared by hardware after it's asserted. + pub fn start(&mut self) { + ral_registers::modify_reg!(tcd, self.tcd(), TCD.CSR, START: 1); + } + + /// Set the source signal for channel activation. + /// + /// The source is typically an upstream peripheral. + /// Use `None` to disable the source. + /// + /// This call reads back the written source. If they're equal + /// the return is `true`. The values may not be equal if the + /// source is selected in another DMA channel. + pub fn set_source_signal(&self, signal: Option<NonZeroU8>) -> bool { + let signal = signal.map_or(0, NonZeroU8::get) as u32; + ral_registers::write_reg!(tcd, self.tcd(), MUX, SRC: signal); + signal == ral_registers::read_reg!(tcd, self.tcd(), MUX, SRC) + } + } +} + +pub mod edma4 { + use core::num::NonZeroU8; + + use crate::{dma::edma4 as dma, element::ElementSize, tcd::edma34 as tcd}; + use ral_registers::Instance; + + /// A DMA channel for an eDMA controller. + /// + /// The channel is two pointers wide. + pub struct Channel { + index: usize, + dma: Instance<dma::RegisterBlock>, + } + + impl Channel { + /// Create a new DMA channel. + /// + /// # Safety + /// + /// The channel formed by this call cannot alias another channel with the + /// same index. The channel index must be valid for the hardware. + pub const unsafe fn new(dma: Instance<dma::RegisterBlock>, index: usize) -> Self { + Self { index, dma } + } + + /// Enable the DMA channel. + /// + /// # Safety + /// + /// If the channel is incorrectly configured, it may access + /// invalid memory. + pub unsafe fn enable(&mut self) { + ral_registers::modify_reg!(tcd, self.tcd(), CSR, ERQ: 1, DONE: 0); + } + + /// Disable the DMA channel. + pub fn disable(&mut self) { + ral_registers::modify_reg!(tcd, self.tcd(), CSR, ERQ: 0, DONE: 0); + } + + /// Returns `true` if the DMA channel is enabled. + pub fn is_enabled(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), CSR, ERQ != 0) + } + + fn tcd(&self) -> Instance<tcd::RegisterBlock> { + // Safety: caller claims the index is valid for + // the DMA controller. + unsafe { Instance::new_unchecked(&raw mut (*self.dma.as_ptr()).TCD[self.index]) } + } + + /// Returns `true` if the hardware is signaling requests towards + /// this DMA channel. + pub fn is_hardware_signaling(&self) -> bool { + if self.index < 32 { + (1 << self.index) & ral_registers::read_reg!(dma, self.dma, HRS_LOW) != 0 + } else { + (1 << (self.index - 32)) & ral_registers::read_reg!(dma, self.dma, HRS_HIGH) != 0 + } + } + + /// Returns `true` if an interrupt has activated for this + /// DMA channel. + pub fn is_interrupt(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), INT) != 0 + } + + /// Clear the interrupt trigger. + pub fn clear_interrupt(&self) { + ral_registers::write_reg!(tcd, self.tcd(), INT, 1); + } + + /// Returns `true` if this DMA channel has finished its transfer. + pub fn is_done(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), CSR, DONE == 1) + } + + /// Clear the "done" signal from software. + pub fn clear_done(&self) { + // Safety: the CSR register is the first register of the + // TCD. The peripheral tolerates 8-bit access. We can poke + // the DONE bit with a single write without risking a race + // in the lower bits of the register. + // + // I should rewrite the register block to lay out the data + // like I want... + unsafe { + let csr: *mut u32 = &raw mut (*self.tcd().as_ptr()).CSR; + csr.cast::<u8>().add(3).write_volatile(1 << 6) + }; + } + + /// Returns `true` if this channel has an error. + pub fn has_error(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), ES) != 0 + } + + /// Clear this channel's error. + pub fn clear_error(&self) { + ral_registers::write_reg!(tcd, self.tcd(), ES, ERR: 1); + } + + /// Returns `true` if this channel is actively transferring. + pub fn is_active(&self) -> bool { + ral_registers::read_reg!(tcd, self.tcd(), CSR, ACTIVE == 1) + } + + /// Reset the control descriptor for the channel. + /// + /// Call this at least once, after acquiring the channel, to + /// put it into a known-good state. + pub fn reset(&mut self) { + unsafe { + let tcd = &raw mut (*self.tcd().as_ptr()).TCD; + tcd.write_bytes(0, 1); + }; + } + + /// Set the source address for a DMA transfer + /// + /// `saddr` should be a memory location that can provide the DMA controller + /// with data. + /// + /// # Safety + /// + /// If the DMA channel is already enabled, the DMA engine may start reading this + /// memory location. You must ensure that reads to `saddr` do not perform + /// inappropriate side effects. You must ensure `saddr` is valid for the + /// lifetime of the transfer. + pub unsafe fn set_source_address(&self, saddr: *const ()) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.SADDR, saddr as u32); + } + + /// Set the source offset *in bytes* + /// + /// `offset` could be negative, which would decrement the address. + /// + /// # Safety + /// + /// This method could allow a DMA engine to read beyond a buffer or + /// address. You must ensure that the source is valid for these offsets. + pub unsafe fn set_source_offset(&self, offset: i16) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.SOFF, offset); + } + + /// Set the destination address for a DMA transfer + /// + /// `daddr` should be a memory location that can store data from the + /// DMA controller. + /// + /// # Safety + /// + /// If the DMA channel is already enabled, the DMA engine may start + /// writing to this address. You must ensure that writes to `daddr` + /// are safe, and that the memory is valid for the lifetime of the + /// transfer. + pub unsafe fn set_destination_address(&self, daddr: *const ()) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.DADDR, daddr as u32); + } + + /// Set the destination offset *in bytes* + /// + /// `offset` could be negative, which would decrement the address. + /// + /// # Safety + /// + /// This method could allow a DMA engine to write beyond the range of + /// a buffer. You must ensure that the destination is valid for these + /// offsets. + pub unsafe fn set_destination_offset(&self, offset: i16) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.DOFF, offset); + } + + /// Set the transfer attributes. + /// + /// The attributes describes the ways the source is read and + /// the destination is written. The modulo mask allows the + /// buffer to be treated as a circular buffer. + /// + /// # Safety + /// + /// Incorrect sizes and modulos may cause invalid memory accesses. + pub unsafe fn set_attributes( + &self, + source_size: ElementSize, + source_modulo: u8, + destination_size: ElementSize, + destination_modulo: u8, + ) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.ATTR, + SSIZE: source_size as u16, + SMOD: source_modulo as u16, + DSIZE: destination_size as u16, + DMOD: destination_modulo as u16, + ); + } + + /// Set the source last address adjustment *in bytes* + /// + /// # Safety + /// + /// This could allow the DMA engine to reference an invalid source buffer. + /// You must ensure that the adjustment performed by the DMA engine is + /// valid, assuming that another DMA transfer immediately runs after the + /// current transfer completes. + pub unsafe fn set_source_last_address_adjustment(&self, adjustment: i32) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.SLAST, adjustment); + } + + /// Set the destination last addrss adjustment *in bytes* + /// + /// # Safety + /// + /// This could allow the DMA engine to reference an invalid destination address. + /// You must ensure that the adjustment performed by the DMA engine is + /// valid, assuming that another DMA transfer immediately runs after the + /// current transfer completes. + pub unsafe fn set_destination_last_address_adjustment(&self, adjustment: i32) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.DLAST_SGA, adjustment); + } + + /// Set the number of *bytes* to transfer per minor loop + /// + /// Describes how many bytes we should transfer for each DMA service request. + /// Note that `nbytes` of `0` is interpreted as a 4GB transfer. + /// + /// # Safety + /// + /// This might allow the DMA engine to read beyond the source, or write beyond + /// the destination. Caller must ensure that the number of bytes per minor loop + /// is valid for the given transfer. + pub unsafe fn set_minor_loop_bytes(&self, nbytes: u32) { + ral_registers::write_reg!(tcd, self.tcd(), TCD.NBYTES, nbytes); + } + + /// Tells the DMA channel how many transfer iterations to perform + /// + /// A 'transfer iteration' is a read from a source, and a write to a destination, with + /// read and write sizes described by a minor loop. Each iteration requires a DMA + /// service request, either from hardware or from software. The maximum number of iterations + /// is 2^15. + /// + /// # Safety + /// + /// This may allow the DMA engine to read beyond the source, or write beyond + /// the destination. Caller must ensure that the number of iterations is valid + /// for the transfer. + pub unsafe fn set_transfer_iterations(&mut self, iterations: u16) { + let iterations = iterations & 0x7FFF; + // Note that this is clearing the ELINK bit. We don't have support + // for channel-to-channel linking right now. Clearing ELINK is intentional + // to use the whole 15 bits for iterations. + ral_registers::write_reg!(tcd, self.tcd(), TCD.CITER, iterations); + ral_registers::write_reg!(tcd, self.tcd(), TCD.BITER, iterations); + } + + /// Returns the beginning transfer iterations setting for the channel. + /// + /// This reflects the last call to `set_transfer_iterations`. + pub fn beginning_transfer_iterations(&self) -> u16 { + ral_registers::read_reg!(tcd, self.tcd(), TCD.BITER) + } + + /// Enable or disable 'disable on completion' + /// + /// 'Disable on completion' lets the DMA channel automatically clear the request signal + /// when it completes a transfer. + pub fn set_disable_on_completion(&mut self, dreq: bool) { + ral_registers::modify_reg!(tcd, self.tcd(), TCD.CSR, DREQ: dreq as u16); + } + + /// Enable or disable interrupt generation when the transfer completes + /// + /// You're responsible for registering your interrupt handler. + pub fn set_interrupt_on_completion(&mut self, intr: bool) { + ral_registers::modify_reg!(tcd, self.tcd(), TCD.CSR, INTMAJOR: intr as u16); + } + + /// Start a DMA transfer + /// + /// `start()` should be used to request service from the DMA controller. It's + /// necessary for in-memory DMA transfers. Do not use it for hardware-initiated + /// DMA transfers. DMA transfers that involve hardware will rely on the hardware + /// to request DMA service. + /// + /// Flag is automatically cleared by hardware after it's asserted. + pub fn start(&mut self) { + ral_registers::modify_reg!(tcd, self.tcd(), TCD.CSR, START: 1); + } + + /// Set the source signal for channel activation. + /// + /// The source is typically an upstream peripheral. + /// Use `None` to disable the source. + /// + /// This call reads back the written source. If they're equal + /// the return is `true`. The values may not be equal if the + /// source is selected in another DMA channel. + pub fn set_source_signal(&self, signal: Option<NonZeroU8>) -> bool { + let signal = signal.map_or(0, NonZeroU8::get) as u32; + ral_registers::write_reg!(tcd, self.tcd(), MUX, SRC: signal); + signal == ral_registers::read_reg!(tcd, self.tcd(), MUX, SRC) + } + } +} |
