atsamd_hal/dmac/channel/
reg.rs

1//! # Channel registers
2//!
3//! This module adds a [`RegisterBlock`] struct, which acts as a proxy for the
4//! registers a single DMAC [`Channel`](super::Channel) can read/write. Its
5//! purpose is to remediate the inadequacies of the PAC. In particular, for
6//! SAMD11/SAMD21, the CHID register must be written with the correct channel ID
7//! before accessing the channel specific registers. There is a provided
8//! `with_chid` method that takes a closure with the register read/write proxies
9//! to ensure any read/write to these registers are done in an interrupt-safe
10//! way. For SAMD51+, `with_chid` returns the register block which contains the
11//! registers owned by a specific channel.
12
13use atsamd_hal_macros::{hal_cfg, hal_macro_helper};
14
15use super::super::dma_controller::ChId;
16use core::marker::PhantomData;
17use paste::paste;
18
19use crate::pac::{
20    self, Dmac, Peripherals,
21    dmac::{Busych, Intstatus, Pendch, Swtrigctrl},
22    dmac::{
23        busych::BusychSpec, intstatus::IntstatusSpec, pendch::PendchSpec,
24        swtrigctrl::SwtrigctrlSpec,
25    },
26};
27
28#[hal_cfg(any("dmac-d11", "dmac-d21"))]
29use pac::dmac as channel_regs;
30
31#[hal_cfg("dmac-d5x")]
32use pac::dmac::channel as channel_regs;
33
34use channel_regs::{
35    Chctrla, Chctrlb, Chintenclr, Chintenset, Chintflag, Chstatus, chctrla::ChctrlaSpec,
36    chctrlb::ChctrlbSpec, chintenclr::ChintenclrSpec, chintenset::ChintensetSpec,
37    chintflag::ChintflagSpec, chstatus::ChstatusSpec,
38};
39
40#[hal_cfg("dmac-d5x")]
41use pac::dmac::channel::{Chprilvl, chprilvl::ChprilvlSpec};
42
43//==============================================================================
44// RegisterBlock
45//==============================================================================
46/// Read/write proxy for DMAC registers accessible to individual channels.
47pub(super) trait Register<Id: ChId> {
48    /// Get a shared reference to the underlying PAC object
49    fn dmac(&self) -> &Dmac;
50
51    /// Set channel ID and run the closure. A closure is needed to ensure
52    /// the registers are accessed in an interrupt-safe way, as the SAMD21
53    /// DMAC is a little funky - It requires setting the channel number in
54    /// the CHID register, then access the channel control registers.
55    /// If an interrupt were to change the CHID register and not reset it
56    /// to the expected value, we would be faced with undefined behaviour.
57    #[hal_cfg(any("dmac-d11", "dmac-d21"))]
58    #[inline]
59    fn with_chid<F: FnOnce(&Dmac) -> R, R>(&mut self, fun: F) -> R {
60        // SAFETY: This method is ONLY safe if the individual channels are GUARANTEED
61        // not to mess with either:
62        // - The global DMAC configuration
63        // - The configuration of other channels.
64        //
65        // In practice, this means that the channel-specific registers should only be
66        // accessed through the `with_chid` method.
67
68        let dmac = self.dmac();
69
70        let mut old_id = 0;
71
72        dmac.chid().modify(|r, w| {
73            // Get the CHID contents before changing channel
74            old_id = r.id().bits();
75            // Change channels
76            unsafe { w.id().bits(Id::U8) }
77        });
78
79        // Run the provided closure on the channel we own
80        let ret = fun(dmac);
81        // Restore the old CHID value. This way, if we're running `with_chid` from an
82        // ISR, the CHID value will still be what the preempted context expects
83        // when the method returns.
84        unsafe { dmac.chid().write(|w| w.id().bits(old_id)) };
85
86        ret
87    }
88
89    /// Set channel ID and run the closure. A closure is needed to ensure
90    /// the registers are accessed in an interrupt-safe way, as the SAMD21
91    /// DMAC is a little funky. For the SAMD51/SAMEx, we simply take a reference
92    /// to the correct channel number and run the closure on that.
93    #[hal_cfg("dmac-d5x")]
94    #[inline]
95    fn with_chid<F: FnOnce(&pac::dmac::Channel) -> R, R>(&mut self, fun: F) -> R {
96        // SAFETY: This method is ONLY safe if the individual channels are GUARANTEED
97        // not to mess with either:
98        // - The global DMAC configuration
99        // - The configuration of other channels.
100        //
101        // In practice, this means that the channel-specific registers should only be
102        // accessed through the `with_chid` method.
103        let ch = &self.dmac().channel(Id::USIZE);
104        fun(ch)
105    }
106}
107
108macro_rules! reg_proxy {
109    (@new $reg:ident) => {
110        paste! {
111            /// Register proxy tied to a specific channel
112            pub(super) struct [< $reg:camel Proxy >]<Id: ChId, REG> {
113                #[allow(unused)]
114                dmac: Dmac,
115                _id: PhantomData<Id>,
116                _reg: PhantomData<REG>,
117            }
118
119            impl<Id: ChId> [< $reg:camel Proxy >]<Id, [< $reg:camel >]> {
120                /// Create a new register proxy
121                #[inline]
122                pub fn new() -> Self {
123                    Self {
124                        // SAFETY: This is safe as long as the register
125                        // only reads/writes registers through
126                        // the `with_chid` method.
127                        dmac: unsafe { Peripherals::steal().dmac },
128                        _id: PhantomData,
129                        _reg: PhantomData,
130                    }
131                }
132            }
133        }
134    };
135
136    // Internal rule for a Read-enabled register
137    (@read_reg $reg:ident) => {
138        paste! {
139            impl<Id: ChId> Register<Id> for [< $reg:camel Proxy >]<Id, [< $reg:camel >]> {
140                fn dmac(&self) -> &Dmac {
141                    &self.dmac
142                }
143            }
144
145            impl<Id> [< $reg:camel Proxy >]<Id, [< $reg:camel >]> where Id: ChId, [< $reg:camel Spec>]: pac::generic::Readable {
146                #[inline]
147                #[allow(dead_code)]
148                pub fn read(&mut self) -> channel_regs::[< $reg:lower >]::R {
149                    self.with_chid(|d| d.[< $reg:lower >]().read())
150                }
151            }
152        }
153    };
154
155    // Read-only register
156    ($reg:ident, register, r) => {
157        paste! {
158
159            reg_proxy!(@new $reg);
160            reg_proxy!(@read_reg $reg);
161        }
162    };
163
164    // Read-write register
165    ($reg:ident, register, rw) => {
166        paste! {
167            reg_proxy!(@new $reg);
168            reg_proxy!(@read_reg $reg);
169
170            impl<Id> [< $reg:camel Proxy >]<Id, [< $reg:camel >]> where Id: ChId, [< $reg:camel Spec >]: pac::generic::Writable {
171                #[inline]
172                #[allow(dead_code)]
173                pub fn write<F>(&mut self, func: F)
174                where
175                    for<'w> F: FnOnce(&'w mut channel_regs::[< $reg:lower >]::W) -> &'w mut channel_regs::[< $reg:lower >]::W,
176                {
177                    self.with_chid(|d| d.[< $reg:lower >]().write(|w| func(w)));
178                }
179            }
180
181            impl<Id>[< $reg:camel Proxy >]<Id, [< $reg:camel >]> where
182                Id: ChId,
183                [< $reg:camel Spec >]: pac::generic::Writable + pac::generic::Readable
184            {
185                #[inline]
186                #[allow(dead_code)]
187                pub fn modify<F>(&mut self, func: F)
188                where
189                    for<'w> F: FnOnce(
190                        &channel_regs::[< $reg:lower >]::R,
191                        &'w mut channel_regs::[< $reg:lower >]::W
192                    ) -> &'w mut channel_regs::[< $reg:lower >]::W,
193                {
194                    self.with_chid(|d| d.[< $reg:lower >]().modify(|r, w| func(r, w)));
195                }
196            }
197        }
198    };
199
200    // Internal rule for read-enabled bit
201    (@read_bit $reg:ident) => {
202        paste! {
203            impl<Id: ChId> Register<Id> for [< $reg:camel Proxy >]<Id, [< $reg:camel >]> {
204                fn dmac(&self) -> &Dmac {
205                    &self.dmac
206                }
207            }
208
209            impl<Id> [< $reg:camel Proxy >]<Id, [< $reg:camel >]> where Id: ChId, [< $reg:camel Spec >]: pac::generic::Readable {
210                #[inline]
211                #[allow(dead_code)]
212                pub fn read_bit(&self) -> bool {
213                    self.dmac.[< $reg:lower >]().read().bits() & (1 << Id::U8) != 0
214                }
215            }
216        }
217    };
218
219    // Read-only bit
220    ($reg:ident, bit, r) => {
221        paste! {
222            reg_proxy!(@new $reg);
223            reg_proxy!(@read_bit $reg);
224        }
225    };
226
227    // Read-write bit
228    ($reg:ident, bit, rw) => {
229        paste! {
230            reg_proxy!(@new $reg);
231            reg_proxy!(@read_bit $reg);
232
233            impl<Id> [< $reg:camel Proxy >]<Id, [< $reg:camel >]> where
234                Id: ChId,
235                [< $reg:camel Spec >]: pac::generic::Readable + pac::generic::Writable
236            {
237                #[inline]
238                #[allow(dead_code)]
239                pub fn set_bit(&mut self) {
240                    // SAFETY: This is safe because we are only writing
241                    // to the bit controlled by the channel.
242                    unsafe {
243                        self.dmac.[< $reg:lower >]().modify(|r, w| w.bits(r.bits() | (1 << Id::U8)));
244                    }
245                }
246
247                #[inline]
248                #[allow(dead_code)]
249                pub fn clear_bit(&mut self) {
250                    // SAFETY: This is safe because we are only writing
251                    // to the bit controlled by the channel.
252                    unsafe {
253                        self.dmac.[< $reg:lower >]().modify(|r, w| w.bits(r.bits() & !(1 << Id::U8)));
254                    }
255                }
256            }
257        }
258    };
259}
260
261reg_proxy!(chctrla, register, rw);
262reg_proxy!(chctrlb, register, rw);
263reg_proxy!(chintenclr, register, rw);
264reg_proxy!(chintenset, register, rw);
265reg_proxy!(chintflag, register, rw);
266reg_proxy!(chstatus, register, r);
267#[hal_cfg("dmac-d5x")]
268reg_proxy!(chprilvl, register, rw);
269
270reg_proxy!(intstatus, bit, r);
271reg_proxy!(busych, bit, r);
272reg_proxy!(pendch, bit, r);
273reg_proxy!(swtrigctrl, bit, rw);
274
275/// Acts as a proxy to the PAC DMAC object. Only registers and bits
276/// within registers that should be readable/writable by specific
277/// [`Channel`]s are exposed.
278///
279/// This struct implements [`Drop`]. Dropping this struct will stop
280/// any ongoing transfers for the channel which it represents.
281#[allow(dead_code)]
282#[hal_macro_helper]
283pub(super) struct RegisterBlock<Id: ChId> {
284    pub chctrla: ChctrlaProxy<Id, Chctrla>,
285    pub chctrlb: ChctrlbProxy<Id, Chctrlb>,
286    pub chintenclr: ChintenclrProxy<Id, Chintenclr>,
287    pub chintenset: ChintensetProxy<Id, Chintenset>,
288    pub chintflag: ChintflagProxy<Id, Chintflag>,
289    pub chstatus: ChstatusProxy<Id, Chstatus>,
290    pub intstatus: IntstatusProxy<Id, Intstatus>,
291    pub busych: BusychProxy<Id, Busych>,
292    pub pendch: PendchProxy<Id, Pendch>,
293    pub swtrigctrl: SwtrigctrlProxy<Id, Swtrigctrl>,
294    #[hal_cfg("dmac-d5x")]
295    pub chprilvl: ChprilvlProxy<Id, Chprilvl>,
296}
297
298impl<Id: ChId> RegisterBlock<Id> {
299    #[hal_macro_helper]
300    pub(super) fn new(_id: PhantomData<Id>) -> Self {
301        Self {
302            chctrla: ChctrlaProxy::new(),
303            chctrlb: ChctrlbProxy::new(),
304            chintenclr: ChintenclrProxy::new(),
305            chintenset: ChintensetProxy::new(),
306            chintflag: ChintflagProxy::new(),
307            chstatus: ChstatusProxy::new(),
308            intstatus: IntstatusProxy::new(),
309            busych: BusychProxy::new(),
310            pendch: PendchProxy::new(),
311            swtrigctrl: SwtrigctrlProxy::new(),
312            #[hal_cfg("dmac-d5x")]
313            chprilvl: ChprilvlProxy::new(),
314        }
315    }
316}
317
318impl<Id: ChId> Drop for RegisterBlock<Id> {
319    fn drop(&mut self) {
320        // Stop any potentially ongoing transfers
321        self.chctrla.modify(|_, w| w.enable().clear_bit());
322
323        while self.chctrla.read().enable().bit_is_set() {
324            core::hint::spin_loop();
325        }
326
327        // Prevent the compiler from re-ordering read/write
328        // operations beyond this fence.
329        // (see https://docs.rust-embedded.org/embedonomicon/dma.html#compiler-misoptimizations)
330        core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); // ▼
331    }
332}