atsamd-hal 0.23.3

HAL and Peripheral access API for ATSAMD11, ATSAMD21, ATSAMD51, ATSAME51, ATSAME53 and ATSAME54 microcontrollers
Documentation
//! # Abstractions to setup and use the DMA controller
//!
//! # Initializing
//!
//! The DMAC should be initialized using the
//! [`DmaController::init`] method. It will consume the
//! DMAC object generated by the PAC. By default, all four priority levels
//! will be enabled, but can be selectively enabled/disabled through the
//! [`DmaController::enable_levels`] ansd [`DmaController::disable_levels`]
//! methods.
//!
//! # Splitting Channels
//!
//! Using the [`DmaController::split`] method will return
//! a struct containing handles to individual channels.
//!
//! # Releasing the DMAC
//!
//! Using the [`free`](DmaController::free) method will
//! deinitialize the DMAC and return the underlying PAC object.
#![allow(unused_braces)]

use atsamd_hal_macros::{hal_cfg, hal_macro_helper};
use core::marker::PhantomData;

use modular_bitfield::prelude::*;
use seq_macro::seq;

#[hal_cfg(any("dmac-d11", "dmac-d21"))]
pub use crate::pac::dmac::chctrlb::{
    Lvlselect as PriorityLevel, Trigactselect as TriggerAction, Trigsrcselect as TriggerSource,
};

#[hal_cfg("dmac-d5x")]
pub use crate::pac::dmac::channel::{
    chctrla::{
        Burstlenselect as BurstLength, Thresholdselect as FifoThreshold,
        Trigactselect as TriggerAction, Trigsrcselect as TriggerSource,
    },
    chprilvl::Prilvlselect as PriorityLevel,
};

use super::{
    channel::{Channel, Uninitialized},
    sram,
};
use crate::{
    pac::{Dmac, Pm},
    typelevel::NoneT,
};

/// Trait representing a DMA channel ID
pub trait ChId {
    const U8: u8;
    const USIZE: usize;
}

/// Mask representing which priority levels should be enabled/disabled
#[bitfield]
#[repr(u16)]
pub struct PriorityLevelMask {
    #[skip]
    _reserved: B8,
    /// Level 0
    pub level0: bool,
    /// Level 1
    pub level1: bool,
    /// Level 2
    pub level2: bool,
    /// Level 3
    pub level3: bool,
    #[skip]
    _reserved: B4,
}

impl Default for PriorityLevelMask {
    fn default() -> Self {
        Self::new()
    }
}

/// Mask representing which priority levels should be configured as round-robin
#[bitfield]
#[repr(u32)]
pub struct RoundRobinMask {
    #[skip]
    _reserved: B7,
    /// Level 0
    pub level0: bool,
    #[skip]
    _reserved: B7,
    /// Level 1
    pub level1: bool,
    #[skip]
    _reserved: B7,
    /// Level 2
    pub level2: bool,
    #[skip]
    _reserved: B7,
    /// Level 3
    pub level3: bool,
}

impl Default for RoundRobinMask {
    fn default() -> Self {
        Self::new()
    }
}

macro_rules! define_channels_struct {
    ($num_channels:literal) => {
        seq!(N in 0..$num_channels {
            #(
                /// Type alias for a channel number
                pub enum Ch~N {}

                impl ChId for Ch~N {
                    const U8: u8 = N;
                    const USIZE: usize = N;
                }
            )*

            /// Struct generating individual handles to each DMA channel
            pub struct Channels(
                #(
                    pub Channel<Ch~N, Uninitialized>,
                )*
            );
        });
    };
}

with_num_channels!(define_channels_struct);

#[cfg(feature = "async")]
macro_rules! define_channels_struct_future {
    ($num_channels:literal) => {
        seq!(N in 0..$num_channels {
            /// Struct generating individual handles to each DMA channel for `async` operation
            pub struct FutureChannels(
                #(
                    pub Channel<Ch~N, super::channel::UninitializedFuture>,
                )*
            );
        });
    };
}

#[cfg(feature = "async")]
with_num_channels!(define_channels_struct_future);

/// Initialized DMA Controller
pub struct DmaController<I = NoneT> {
    dmac: Dmac,
    _irqs: PhantomData<I>,
}

impl DmaController {
    /// Initialize the DMAC and return a DmaController object useable by
    /// [`Transfer`](super::transfer::Transfer)'s. By default, all
    /// priority levels are enabled unless subsequently disabled using the
    /// `level_x_enabled` methods.
    #[inline]
    #[hal_macro_helper]
    pub fn init(mut dmac: Dmac, _pm: &mut Pm) -> Self {
        // ----- Initialize clocking ----- //
        #[hal_cfg(any("dmac-d11", "dmac-d21"))]
        {
            // Enable clocking
            _pm.ahbmask().modify(|_, w| w.dmac_().set_bit());
            _pm.apbbmask().modify(|_, w| w.dmac_().set_bit());
        }

        Self::swreset(&mut dmac);

        // SAFETY:
        //
        // This is safe because we write a whole u32 to 32-bit registers,
        // and the descriptor array addesses will never change since they are static.
        // We just need to ensure the writeback and descriptor_section addresses
        // are valid.
        unsafe {
            dmac.baseaddr()
                .write(|w| w.baseaddr().bits(sram::descriptor_section_addr() as u32));
            dmac.wrbaddr()
                .write(|w| w.wrbaddr().bits(sram::writeback_addr() as u32));
        }

        // ----- Select priority levels ----- //
        dmac.ctrl().modify(|_, w| {
            w.lvlen3().set_bit();
            w.lvlen2().set_bit();
            w.lvlen1().set_bit();
            w.lvlen0().set_bit()
        });

        // Enable DMA controller
        dmac.ctrl().modify(|_, w| w.dmaenable().set_bit());

        Self {
            dmac,
            _irqs: PhantomData,
        }
    }

    /// Release the DMAC and return the register block.
    ///
    /// **Note**: The [`Channels`] struct is consumed by this method. This means
    /// that any [`Channel`] obtained by [`split`](DmaController::split) must be
    /// moved back into the [`Channels`] struct before being able to pass it
    /// into [`free`](DmaController::free).
    #[inline]
    #[hal_macro_helper]
    pub fn free(mut self, _channels: Channels, _pm: &mut Pm) -> Dmac {
        self.dmac.ctrl().modify(|_, w| w.dmaenable().clear_bit());

        Self::swreset(&mut self.dmac);

        #[hal_cfg(any("dmac-d11", "dmac-d21"))]
        {
            // Disable the DMAC clocking
            _pm.apbbmask().modify(|_, w| w.dmac_().clear_bit());
            _pm.ahbmask().modify(|_, w| w.dmac_().clear_bit());
        }

        // Release the DMAC
        self.dmac
    }
}

impl<T> DmaController<T> {
    /// Enable multiple priority levels simultaneously
    #[inline]
    pub fn enable_levels(&mut self, mask: PriorityLevelMask) {
        // SAFETY This is safe because the use of bitfields ensures that only the
        // LVLENx bits are written to. The fact that we are given a mask means we need
        // to do the bit-level setting ourselves.
        let mask: u16 = mask.into();
        unsafe {
            self.dmac.ctrl().modify(|r, w| w.bits(r.bits() | mask));
        }
    }

    /// Disable multiple priority levels simultaneously
    #[inline]
    pub fn disable_levels(&mut self, mask: PriorityLevelMask) {
        // SAFETY This is safe because the use of bitfields ensures that only the
        // LVLENx bits are written to. The fact that we are given a mask means we need
        // to do the bit-level clearing ourselves.
        let mask: u16 = mask.into();
        unsafe {
            self.dmac.ctrl().modify(|r, w| w.bits(r.bits() & !mask));
        }
    }

    /// Enable round-robin arbitration for multiple priority levels
    /// simultaneously
    #[inline]
    pub fn round_robin_arbitration(&mut self, mask: RoundRobinMask) {
        // SAFETY This is safe because the use of bitfields ensures that only the
        // RRLVLENx bits are written to. The fact that we are given a mask means we need
        // to do the bit-level setting ourselves.
        let mask: u32 = mask.into();
        unsafe {
            self.dmac.prictrl0().modify(|r, w| w.bits(r.bits() | mask));
        }
    }

    /// Disable round-robin arbitration (ie, enable static priorities) for
    /// multiple priority levels simultaneously
    #[inline]
    pub fn static_arbitration(&mut self, mask: RoundRobinMask) {
        // SAFETY This is safe because the use of bitfields ensures that only the
        // RRLVLENx bits are written to. The fact that we are given a mask means we need
        // to do the bit-level clearing ourselves.
        let mask: u32 = mask.into();
        unsafe {
            self.dmac.prictrl0().modify(|r, w| w.bits(r.bits() & !mask));
        }
    }

    /// Use the [`DmaController`] in async mode. You are required to provide the
    /// struct created by the
    /// [`bind_interrupts`](crate::bind_interrupts) macro to prove
    /// that the interrupt sources have been correctly configured. This function
    /// will automatically enable the relevant NVIC interrupt sources. However,
    /// you are required to configure the desired interrupt priorities prior to
    /// calling this method. Consult [`crate::async_hal::interrupts`]
    /// module-level documentation for more information.
    /// [`bind_interrupts`](crate::bind_interrupts).
    #[cfg(feature = "async")]
    #[inline]
    pub fn into_future<I>(self, _interrupts: I) -> DmaController<I>
    where
        I: crate::async_hal::interrupts::Binding<
                crate::async_hal::interrupts::DMAC,
                super::async_api::InterruptHandler,
            >,
    {
        use crate::async_hal::interrupts::{DMAC, InterruptSource};

        DMAC::unpend();
        unsafe { DMAC::enable() };

        DmaController {
            dmac: self.dmac,
            _irqs: PhantomData,
        }
    }

    /// Issue a software reset to the DMAC and wait for reset to complete
    #[inline]
    fn swreset(dmac: &mut Dmac) {
        dmac.ctrl().modify(|_, w| w.swrst().set_bit());
        while dmac.ctrl().read().swrst().bit_is_set() {}
    }
}

#[cfg(feature = "async")]
impl<I> DmaController<I>
where
    I: crate::async_hal::interrupts::Binding<
            crate::async_hal::interrupts::DMAC,
            super::async_api::InterruptHandler,
        >,
{
    /// Release the DMAC and return the register block.
    ///
    /// **Note**: The [`Channels`] struct is consumed by this method. This means
    /// that any [`Channel`] obtained by [`split`](DmaController::split) must be
    /// moved back into the [`Channels`] struct before being able to pass it
    /// into [`free`](DmaController::free).
    #[inline]
    #[hal_macro_helper]
    pub fn free(mut self, _channels: FutureChannels, _pm: &mut Pm) -> Dmac {
        self.dmac.ctrl().modify(|_, w| w.dmaenable().clear_bit());

        Self::swreset(&mut self.dmac);

        #[hal_cfg(any("dmac-d11", "dmac-d21"))]
        {
            // Disable the DMAC clocking
            _pm.apbbmask().modify(|_, w| w.dmac_().clear_bit());
            _pm.ahbmask().modify(|_, w| w.dmac_().clear_bit());
        }

        // Release the DMAC
        self.dmac
    }
}

macro_rules! define_split {
    ($num_channels:literal) => {
        seq!(N in 0..$num_channels {
            /// Split the DMAC into individual channels
            #[inline]
            pub fn split(&mut self) -> Channels {
                Channels(
                    #(
                        crate::dmac::channel::new_chan(core::marker::PhantomData),
                    )*
                )
            }
        });
    };
}

impl DmaController {
    with_num_channels!(define_split);
}

#[cfg(feature = "async")]
macro_rules! define_split_future {
    ($num_channels:literal) => {
        seq!(N in 0..$num_channels {
            /// Split the DMAC into individual channels
            #[inline]
            pub fn split(&mut self) -> FutureChannels {
                FutureChannels(
                    #(
                        crate::dmac::channel::new_chan_future(core::marker::PhantomData),
                    )*
                )
            }
        });
    };
}

#[cfg(feature = "async")]
impl<I> DmaController<I>
where
    I: crate::async_hal::interrupts::Binding<
            crate::async_hal::interrupts::DMAC,
            super::async_api::InterruptHandler,
        >,
{
    with_num_channels!(define_split_future);
}