Skip to main content

ra_hal/
dtc.rs

1//! Data Transfer Controller (`DTC`).
2//!
3//! `DTC` is one of two DMA engines on the `RA4M1`.
4//!
5//! # TODO
6//! - Store channels as type erased structs like `DMAC`?
7//!
8//! # Notes
9//! - There are no fixed channels and `DTC` transfers can can occupy any interrupt (`IEL0..=IEL31`).
10//! - Transfer configuration is stored in SRAM and copied to internal registers by the `DTC` peripheral at the start of each transfer.
11
12use core::{marker::PhantomData, task::Poll};
13
14use bitbybit::{bitenum, bitfield};
15use cortex_m::interrupt::InterruptNumber;
16use embassy_hal_internal::{Peri, PeripheralType, interrupt::InterruptExt as _};
17use embassy_sync::waitqueue::AtomicWaker;
18
19use crate::{
20    NUM_INTERRUPTS,
21    event_link::{IcuInterrupt as _, InterruptEvent},
22    interrupt::{
23        self,
24        typelevel::{Handler as InterruptHandler, Interrupt as InterruptType},
25    },
26    module_stop::ModuleStop as _,
27    pac,
28};
29
30// Access to this is generally rationalized as safe as we're only ever indexing into it
31// via the const associated with the instance.
32static mut DTC_VECTOR_TABLE: VectorTable = VectorTable::new();
33
34cfg_select! {
35    feature = "defmt" => {
36        /// Unit of measure for a DTC transfer.
37        #[allow(private_bounds)]
38        pub trait Word: SealedWord + defmt::Format {}
39    }
40    _ => {
41        /// Unit of measure for a DTC transfer.
42        #[allow(private_bounds)]
43        pub trait Word: SealedWord  {}
44    }
45}
46
47trait SealedWord: Copy {
48    const WORD_SIZE: WordSize;
49}
50
51/// 8-bit (byte) DMA word.
52impl Word for u8 {}
53impl SealedWord for u8 {
54    const WORD_SIZE: WordSize = WordSize::Byte;
55}
56
57/// 16-bit (half-word) DMA word.
58impl Word for u16 {}
59impl SealedWord for u16 {
60    const WORD_SIZE: WordSize = WordSize::HalfWord;
61}
62
63/// 32-bit (word) DMA word.
64impl Word for u32 {}
65impl SealedWord for u32 {
66    const WORD_SIZE: WordSize = WordSize::Word;
67}
68
69/// Interrupt handler for a `DTC` transfer.
70///
71/// The interrupt number determines the priority of the transfer. §17.7.
72pub struct DtcInterruptHandler<C: Instance> {
73    phantom: PhantomData<C>,
74}
75
76/// `DTC` transfer channel.
77///
78/// `DTC` has no channels per se, instead transfers are bound to interrupts which are then bound to specific events.
79pub struct Channel<C: Instance> {
80    entry: DtcEntry,
81    phantom: PhantomData<C>,
82}
83
84/// How to modify a source or target address upon completion of a `DTC` transfer.
85#[bitenum(u2)]
86#[allow(dead_code)]
87#[cfg_attr(feature = "defmt", derive(defmt::Format))]
88enum AddressMode {
89    /// No write-back to the source or target address register is performed.
90    Fixed = 0b00,
91
92    /// The address (source or target) is incremented by the size of the [`Word`] upon completion of a transfer.
93    Increment = 0b10,
94
95    /// The address (source or target) is decremented by the size of the [`Word`] upon completion of a transfer.
96    Decrement = 0b11,
97}
98
99/// Which address is modified upon completion of a transfer.
100#[bitenum(u1, exhaustive = true)]
101#[allow(dead_code)]
102#[cfg_attr(feature = "defmt", derive(defmt::Format))]
103enum RepeatMode {
104    /// The destination address is updated after a transfer.
105    Destination = 0,
106
107    /// The source address is updated after a transfer.
108    Source = 1,
109}
110
111/// When to pass interrupt through to the CPU.
112///
113/// # Notes
114/// The flowchart in Figure 17.4 explains how things ought to work.
115#[bitenum(u1, exhaustive = true)]
116#[allow(dead_code)]
117#[cfg_attr(feature = "defmt", derive(defmt::Format))]
118enum InterruptMode {
119    OnCompletion = 0,
120    PerTransfer = 1,
121}
122
123/// CHNE + CHNS
124#[bitenum(u2)]
125#[allow(dead_code)]
126#[cfg_attr(feature = "defmt", derive(defmt::Format))]
127enum ChainMode {
128    Disabled = 0b00,
129    Continuous = 0b01,
130    Penultimate = 0b11,
131}
132
133/// SZ
134#[bitenum(u2)]
135#[allow(dead_code)]
136#[cfg_attr(feature = "defmt", derive(defmt::Format))]
137enum WordSize {
138    /// 8 bits
139    Byte = 0b00,
140
141    /// 16 bits
142    HalfWord = 0b01,
143
144    /// 32 bits
145    Word = 0b10,
146}
147
148/// MD
149#[bitenum(u2)]
150#[allow(dead_code)]
151#[cfg_attr(feature = "defmt", derive(defmt::Format))]
152enum TransferMode {
153    Normal = 0b00,
154    Repeat = 0b01,
155    Block = 0b10,
156}
157
158#[bitfield(u128, default = 0)]
159#[cfg_attr(feature = "defmt", derive(defmt::Format))]
160pub(crate) struct DtcEntry {
161    #[bits(0..=15)]
162    reserved: u16,
163
164    // MRB
165    #[bits(16..=17)]
166    reserved: u2,
167    /// DM
168    #[bits(18..=19, rw)]
169    dest_address_mode: Option<AddressMode>,
170    /// DTS
171    #[bit(20, rw)]
172    repeat_mode: RepeatMode,
173    /// DISEL
174    #[bit(21, rw)]
175    interrupt_mode: InterruptMode,
176    /// CHNS+CHNE
177    #[bits(22..=23, rw)]
178    chain_mode: Option<ChainMode>,
179
180    // MRA
181    #[bits(24..=25)]
182    reserved: u2,
183    /// SM
184    #[bits(26..=27, rw)]
185    source_address_mode: Option<AddressMode>,
186    /// SZ
187    #[bits(28..=29, rw)]
188    word_size: Option<WordSize>,
189    /// MD
190    #[bits(30..=31, rw)]
191    transfer_mode: Option<TransferMode>,
192
193    // SAR
194    #[bits(32..=63, rw)]
195    source_address: u32,
196
197    // DAR
198    #[bits(64..=95, rw)]
199    dest_address: u32,
200
201    // CRB
202    #[bits(96..=111, rw)]
203    count_b: u16,
204
205    // CRA
206    #[bits(112..=119, rw)]
207    count_a_low: u8,
208    #[bits(120..=127, rw)]
209    count_a_high: u8,
210}
211
212#[repr(C, align(1024))]
213#[cfg_attr(feature = "defmt", derive(defmt::Format))]
214struct VectorTable {
215    vectors: [u32; NUM_INTERRUPTS],
216}
217
218impl Default for VectorTable {
219    fn default() -> Self {
220        Self {
221            vectors: [0; NUM_INTERRUPTS],
222        }
223    }
224}
225
226/// `DTC` instance.
227#[allow(private_bounds)]
228pub trait Instance: SealedInstance {
229    /// Interrupt associated with the `DTC` instance. `DTC_CHANn` = `IELn`.
230    type Int: interrupt::typelevel::Interrupt;
231}
232
233pub(crate) trait SealedInstance: PeripheralType {}
234
235impl VectorTable {
236    const fn new() -> Self {
237        Self {
238            vectors: [0; NUM_INTERRUPTS],
239        }
240    }
241}
242
243/// Represents a DTC transfer, implements `Future` for use in async code.
244pub struct Transfer<'d, C: Instance> {
245    event: InterruptEvent,
246    phantom: PhantomData<&'d C>,
247}
248
249impl<'d, C: Instance> Transfer<'d, C> {
250    fn new(event: InterruptEvent) -> Self {
251        Self {
252            event,
253            phantom: PhantomData,
254        }
255    }
256
257    #[inline(always)]
258    /// Links the associated interrupt to the DTC.
259    /// The transfer itself won't start until the associated interrupt is triggered.
260    pub fn start(&mut self) {
261        trace!("DTC{}: Enable for: {}", C::Int::IRQ.number(), self.event);
262        C::Int::IRQ.dtc_enable(self.event);
263    }
264
265    /// Unlink an interrupt from the DTC.
266    /// This has the practical effect of stopping a repeating transfer but may not actually cancel a transfer currently in progress.
267    #[inline(always)]
268    pub fn stop(&mut self) {
269        C::Int::IRQ.icu_disable();
270    }
271}
272
273pub(crate) fn init() {
274    let dtc = pac::DTC;
275
276    // Turn on DMAC and DTC
277    crate::peripherals::DTC::start_module();
278
279    let vector_base = unsafe { core::ptr::addr_of!(DTC_VECTOR_TABLE.vectors) as u32 };
280
281    // TODO: Consider tracking if  we're in secure mode at run-time or make it a compile knob
282    cfg_select! {
283        trust_zone => dtc.dtcvbr_sec().write_value(vector_base),
284        _ => dtc.dtcvbr().write_value(vector_base),
285    }
286
287    cfg_select! {
288        all(trust_zone_v2, secure) => {
289            dtc.dtccr_sec().modify(|r| r.set_rrs(false));
290            dtc.dtcst().modify(|r| r.set_dtcst(true));
291        },
292        _ => {
293            dtc.dtccr().modify(|r| r.set_rrs(false));
294            dtc.dtcst().modify(|r| r.set_dtcst(true));
295        }
296    }
297}
298
299impl<C: Instance> Channel<C> {
300    fn waker() -> &'static AtomicWaker {
301        static WAKER: AtomicWaker = AtomicWaker::new();
302        &WAKER
303    }
304
305    /// Constructs a new Dtc driver bound to an interrupt.
306    #[allow(private_bounds)]
307    pub fn new<'d>(
308        peri: Peri<'d, C>,
309        irqs: impl interrupt::typelevel::Binding<C::Int, DtcInterruptHandler<C>>,
310    ) -> Self {
311        let _ = peri;
312        let _ = irqs;
313
314        let this = Self {
315            entry: Default::default(),
316            phantom: PhantomData,
317        };
318
319        unsafe { C::Int::IRQ.enable() };
320        C::Int::IRQ.icu_disable();
321
322        this
323    }
324
325    /// Copies the contents of `source` to `dest`.
326    ///
327    /// # Arguments
328    /// * `source` Slice to copy from.
329    /// * `dest` Slice to write to.
330    /// * `increment_on` An [`InterruptEvent`] used to start the transfer.  Can be used with
331    ///   [`SoftwareEvent`](crate::event_link::SoftwareEvent).
332    pub unsafe fn copy_slice<'d, W: Word>(
333        &mut self,
334        source: &'d [W],
335        dest: &'d mut [W],
336        increment_on: InterruptEvent,
337    ) -> Transfer<'d, C> {
338        let len = source.len() as u8;
339        let transfer_entry = DtcEntry::builder()
340            .with_chain_mode(ChainMode::Disabled)
341            .with_source_address_mode(AddressMode::Increment)
342            .with_dest_address_mode(AddressMode::Increment)
343            .with_word_size(W::WORD_SIZE)
344            .with_transfer_mode(TransferMode::Block)
345            .with_repeat_mode(RepeatMode::Destination)
346            .with_interrupt_mode(InterruptMode::OnCompletion)
347            .with_source_address(source.as_ptr() as u32)
348            .with_dest_address(dest.as_ptr() as u32)
349            .with_count_b(1)
350            .with_count_a_low(len)
351            .with_count_a_high(len)
352            .build();
353        self.update_entry(transfer_entry);
354
355        let mut transfer = Transfer::new(increment_on);
356        transfer.start();
357
358        transfer
359    }
360
361    /// Configures a DTC transfer
362    pub unsafe fn write<'d, W: Word>(
363        &mut self,
364        source: &'d [W],
365        dest: *mut W,
366        increment_on: InterruptEvent,
367        repeat: bool,
368    ) -> Transfer<'d, C> {
369        // In normal mode count is a 16-bit counter
370        // In repeat mode count is an 8-bit counter with an 8-bit reload value
371        let (transfer_mode, low_count, high_count) = match repeat {
372            false => (TransferMode::Normal, source.len() as u8, 0),
373            true => (TransferMode::Repeat, source.len() as u8, source.len() as u8),
374        };
375
376        let transfer_entry = DtcEntry::builder()
377            .with_chain_mode(ChainMode::Disabled)
378            .with_source_address_mode(AddressMode::Increment)
379            .with_dest_address_mode(AddressMode::Fixed)
380            .with_word_size(W::WORD_SIZE)
381            .with_transfer_mode(transfer_mode)
382            .with_repeat_mode(RepeatMode::Source)
383            .with_interrupt_mode(InterruptMode::OnCompletion)
384            .with_source_address(source.as_ptr() as u32)
385            .with_dest_address(dest as u32)
386            .with_count_b(0)
387            .with_count_a_low(low_count)
388            .with_count_a_high(high_count)
389            .build();
390        self.update_entry(transfer_entry);
391
392        let mut transfer = Transfer::new(increment_on);
393        transfer.start();
394
395        transfer
396    }
397
398    pub(crate) fn prepare_write<'d, W: Word>(
399        &'d mut self,
400        source: &'d [W],
401        dest: *mut W,
402        last: bool,
403    ) -> DtcEntry {
404        let chain_mode = match last {
405            true => ChainMode::Disabled,
406            false => ChainMode::Penultimate,
407        };
408
409        DtcEntry::builder()
410            .with_chain_mode(chain_mode)
411            .with_source_address_mode(AddressMode::Increment)
412            .with_dest_address_mode(AddressMode::Fixed)
413            .with_word_size(W::WORD_SIZE)
414            .with_transfer_mode(TransferMode::Normal)
415            .with_repeat_mode(RepeatMode::Source)
416            .with_interrupt_mode(InterruptMode::OnCompletion)
417            .with_source_address(source.as_ptr() as u32)
418            .with_dest_address(dest as u32)
419            .with_count_b(0)
420            .with_count_a_low(source.len() as u8)
421            .with_count_a_high(0)
422            .build()
423    }
424
425    /// Configures a DTC transfer
426    pub(crate) unsafe fn write_chain<'d>(
427        &mut self,
428        chain: &'d [DtcEntry],
429        increment_on: InterruptEvent,
430    ) -> Transfer<'d, C> {
431        unsafe { DTC_VECTOR_TABLE.vectors[C::Int::IRQ.number() as usize] = chain.as_ptr() as u32 };
432
433        // Ensure the DTC module re-reads our vector. §17.4.1.
434        let dtc = crate::pac::DTC;
435        dtc.dtccr().modify(|r| r.set_rrs(false));
436        dtc.dtccr().modify(|r| r.set_rrs(true));
437
438        let mut transfer = Transfer::new(increment_on);
439        transfer.start();
440
441        transfer
442    }
443
444    /// Configures a DTC transfer
445    pub unsafe fn read<'d, W: Word>(
446        &mut self,
447        source: *const W,
448        dest: &'d mut [W],
449        increment_on: InterruptEvent,
450        repeat: bool,
451    ) -> Transfer<'d, C> {
452        // In normal mode count is a 16-bit counter
453        // In repeat mode count is an 8-bit counter with an 8-bit reload value
454        let (transfer_mode, low_count, high_count) = match repeat {
455            false => (TransferMode::Normal, dest.len() as u8, 0),
456            true => (TransferMode::Repeat, dest.len() as u8, dest.len() as u8),
457        };
458
459        let transfer_entry = DtcEntry::builder()
460            .with_chain_mode(ChainMode::Disabled)
461            .with_source_address_mode(AddressMode::Fixed)
462            .with_dest_address_mode(AddressMode::Increment)
463            .with_word_size(W::WORD_SIZE)
464            .with_transfer_mode(transfer_mode)
465            .with_repeat_mode(RepeatMode::Destination)
466            .with_interrupt_mode(InterruptMode::OnCompletion)
467            .with_source_address(source as u32)
468            .with_dest_address(dest.as_ptr() as u32)
469            .with_count_b(0)
470            .with_count_a_low(low_count)
471            .with_count_a_high(high_count)
472            .build();
473        self.update_entry(transfer_entry);
474
475        let mut transfer = Transfer::new(increment_on);
476        transfer.start();
477
478        transfer
479    }
480
481    #[inline(always)]
482    fn update_entry(&mut self, entry: DtcEntry) {
483        self.entry = entry;
484        let entry_addr = &self.entry as *const _;
485        unsafe { DTC_VECTOR_TABLE.vectors[C::Int::IRQ.number() as usize] = entry_addr as u32 };
486
487        // Ensure the DTC module re-reads our vector. §17.4.1.
488        let dtc = crate::pac::DTC;
489        dtc.dtccr().modify(|r| r.set_rrs(false));
490        dtc.dtccr().modify(|r| r.set_rrs(true));
491    }
492}
493
494impl<'d, C: Instance> Drop for Transfer<'d, C> {
495    fn drop(&mut self) {
496        trace!(
497            "DTC{}: Dropping transfer status={}",
498            C::Int::IRQ.number(),
499            pac::DTC.dtcsts().read()
500        );
501    }
502}
503
504impl<C: Instance> Drop for Channel<C> {
505    fn drop(&mut self) {
506        trace!("DTC{}: Drop", C::Int::IRQ.number());
507        unsafe { DTC_VECTOR_TABLE.vectors[C::Int::IRQ.number() as usize] = 0 };
508        C::Int::IRQ.dtc_disable();
509    }
510}
511
512impl<C: Instance> InterruptHandler<C::Int> for DtcInterruptHandler<C> {
513    unsafe fn on_interrupt() {
514        C::Int::IRQ.icu_unpend();
515
516        Channel::<C>::waker().wake();
517    }
518}
519
520// Note: MCUs without TrustZone don't have an error register in the DTC peripheral.
521impl<'d, C: Instance> Future for Transfer<'d, C> {
522    type Output = Result<(), ()>;
523
524    fn poll(
525        self: core::pin::Pin<&mut Self>,
526        ctx: &mut core::task::Context<'_>,
527    ) -> Poll<Self::Output> {
528        Channel::<C>::waker().register(ctx.waker());
529
530        #[cfg(dtc_sec)]
531        {
532            let dtc = crate::pac::DTC;
533
534            let err = dtc.dtevr().read();
535            if err.dtesta() && u16::from(err.dtev()) == C::Int::IRQ.number() {
536                error!("DTC Error: {}", err);
537
538                let bus = pac::BUS;
539
540                return Poll::Ready(Err(()));
541            }
542        }
543
544        // §17.3 On completion of a specified round of data transfer, the ICU.IELSRn.DTCE bit is
545        // set to 0 and an interrupt request is sent to the CPU.
546        if !C::Int::IRQ.is_dtc() {
547            C::Int::IRQ.icu_disable();
548            return Poll::Ready(Ok(()));
549        }
550
551        Poll::Pending
552    }
553}
554
555macro_rules! dtc_link {
556    ($index:literal) => {
557        paste::paste! {
558            impl crate::dtc::Instance for crate::peripherals::[< DTC_CHAN $index >] {
559                type Int = interrupt::typelevel::[< IEL $index >];
560            }
561
562            impl crate::dtc::SealedInstance for crate::peripherals::[< DTC_CHAN $index >] {
563            }
564        }
565    };
566}
567pub(crate) use dtc_link;