aboutsummaryrefslogtreecommitdiff
path: root/drivers/edma/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/edma/src/lib.rs')
-rw-r--r--drivers/edma/src/lib.rs990
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)
+ }
+ }
+}