Skip to main content

sam4l/
dma.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Implementation of the PDCA DMA peripheral.
6
7use crate::pm;
8use core::cell::Cell;
9use core::cmp;
10use core::sync::atomic;
11use enum_primitive::cast::FromPrimitive;
12use kernel::debug;
13use kernel::utilities::StaticRef;
14use kernel::utilities::cells::{OptionalCell, TakeCell};
15use kernel::utilities::registers::interfaces::{Readable, Writeable};
16use kernel::utilities::registers::{
17    ReadOnly, ReadWrite, WriteOnly, register_bitfields, register_structs,
18};
19
20register_structs! {
21    /// Memory registers for a DMA channel. Section 16.6.1 of the datasheet.
22    #[allow(dead_code)]
23    DMARegisters {
24        (0x000 => mar: ReadWrite<u32, MemoryAddress::Register>),
25        (0x004 => psr: ReadWrite<u8>),
26        (0x005 => _psr_padding: [u8; 3]),
27        (0x008 => tcr: ReadWrite<u32, TransferCounter::Register>),
28        (0x00C => marr: ReadWrite<u32, MemoryAddressReload::Register>),
29        (0x010 => tcrr: ReadWrite<u32, TransferCounter::Register>),
30        (0x014 => cr: WriteOnly<u32, Control::Register>),
31        (0x018 => mr: ReadWrite<u32, Mode::Register>),
32        (0x01C => sr: ReadOnly<u32, Status::Register>),
33        (0x020 => ier: WriteOnly<u32, Interrupt::Register>),
34        (0x024 => idr: WriteOnly<u32, Interrupt::Register>),
35        (0x028 => imr: ReadOnly<u32, Interrupt::Register>),
36        (0x02C => isr: ReadOnly<u32, Interrupt::Register>),
37        (0x030 => @END),
38    }
39}
40
41register_bitfields![u32,
42    MemoryAddress [
43        MADDR OFFSET(0) NUMBITS(32) []
44    ],
45
46    MemoryAddressReload [
47        MARV OFFSET(0) NUMBITS(32) []
48    ],
49
50    TransferCounter [
51        /// Transfer Counter Value
52        TCV OFFSET(0) NUMBITS(16) []
53    ],
54
55    Control [
56        /// Transfer Error Clear
57        ECLR 8,
58        /// Transfer Disable
59        TDIS 1,
60        /// Transfer Enable
61        TEN 0
62    ],
63
64    Mode [
65        /// Ring Buffer
66        RING OFFSET(3) NUMBITS(1) [
67            Disable = 0,
68            Enable = 1
69        ],
70        /// Event Trigger
71        ETRIG OFFSET(2) NUMBITS(1) [
72            StartOnRequest = 0,
73            StartOnEvent = 1
74        ],
75        /// Size of Transfer
76        SIZE OFFSET(0) NUMBITS(2) [
77            Byte = 0,
78            Halfword = 1,
79            Word = 2
80        ]
81    ],
82
83    Status [
84        /// Transfer Enabled
85        TEN 0
86    ],
87
88    Interrupt [
89        /// Transfer Error
90        TERR 2,
91        /// Transfer Complete
92        TRC 1,
93        /// Reload Counter Zero
94        RCZ 0
95    ]
96];
97
98/// The PDCA's base addresses in memory (Section 7.1 of manual).
99const DMA_BASE_ADDR: usize = 0x400A2000;
100
101/// The number of bytes between each memory mapped DMA Channel (Section 16.6.1).
102const DMA_CHANNEL_SIZE: usize = 0x40;
103
104/// Shared counter that Keeps track of how many DMA channels are currently
105/// active.
106static NUM_ENABLED: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
107
108/// The DMA channel number.
109///
110/// Each channel transfers data between memory and a
111/// particular peripheral function (e.g., SPI read or SPI write, but not both
112/// simultaneously). There are 16 available channels (Section 16.7).
113#[derive(Copy, Clone)]
114pub enum DMAChannelNum {
115    // Relies on the fact that assigns values 0-15 to each constructor in order
116    DMAChannel00 = 0,
117    DMAChannel01 = 1,
118    DMAChannel02 = 2,
119    DMAChannel03 = 3,
120    DMAChannel04 = 4,
121    DMAChannel05 = 5,
122    DMAChannel06 = 6,
123    DMAChannel07 = 7,
124    DMAChannel08 = 8,
125    DMAChannel09 = 9,
126    DMAChannel10 = 10,
127    DMAChannel11 = 11,
128    DMAChannel12 = 12,
129    DMAChannel13 = 13,
130    DMAChannel14 = 14,
131    DMAChannel15 = 15,
132}
133
134enum_primitive::enum_from_primitive! {
135/// The peripheral function a channel is assigned to (Section 16.7). `*_RX`
136/// means transfer data from peripheral to memory, `*_TX` means transfer data
137/// from memory to peripheral.
138#[allow(non_camel_case_types)]
139#[derive(Copy, Clone, PartialEq)]
140#[repr(u8)]
141pub enum DMAPeripheral {
142    USART0_RX = 0,
143    USART1_RX = 1,
144    USART2_RX = 2,
145    USART3_RX = 3,
146    SPI_RX = 4,
147    TWIM0_RX = 5,
148    TWIM1_RX = 6,
149    TWIM2_RX = 7,
150    TWIM3_RX = 8,
151    TWIS0_RX = 9,
152    TWIS1_RX = 10,
153    ADCIFE_RX = 11,
154    CATB_RX = 12,
155    IISC_CH0_RX = 14,
156    IISC_CH1_RX = 15,
157    PARC_RX = 16,
158    AESA_RX = 17,
159    USART0_TX = 18,
160    USART1_TX = 19,
161    USART2_TX = 20,
162    USART3_TX = 21,
163    SPI_TX = 22,
164    TWIM0_TX = 23,
165    TWIM1_TX = 24,
166    TWIM2_TX = 25,
167    TWIM3_TX = 26,
168    TWIS0_TX = 27,
169    TWIS1_TX = 28,
170    ADCIFE_TX = 29,
171    CATB_TX = 30,
172    ABDACB_SDR0_TX = 31,
173    ABDACB_SDR1_TX = 32,
174    IISC_CH0_TX = 33,
175    IISC_CH1_TX = 34,
176    DACC_TX = 35,
177    AESA_TX = 36,
178    LCDCA_ACMDR_TX = 37,
179    LCDCA_ABMDR_TX = 38,
180}
181}
182
183#[derive(Copy, Clone, Debug, PartialEq)]
184#[repr(u8)]
185pub enum DMAWidth {
186    ///  DMA is acting on bytes
187    Width8Bit = 0,
188    /// DMA is acting on halfwords
189    Width16Bit = 1,
190    /// DMA is acting on words
191    Width32Bit = 2,
192}
193
194pub struct DMAChannel {
195    registers: StaticRef<DMARegisters>,
196    client: OptionalCell<&'static dyn DMAClient>,
197    width: Cell<DMAWidth>,
198    enabled: Cell<bool>,
199    buffer: TakeCell<'static, [u8]>,
200}
201
202pub trait DMAClient {
203    fn transfer_done(&self, pid: DMAPeripheral);
204}
205
206impl DMAChannel {
207    pub fn new(channel: DMAChannelNum) -> DMAChannel {
208        DMAChannel {
209            registers: unsafe {
210                StaticRef::new(
211                    (DMA_BASE_ADDR + (channel as usize) * DMA_CHANNEL_SIZE) as *const DMARegisters,
212                )
213            },
214            client: OptionalCell::empty(),
215            width: Cell::new(DMAWidth::Width8Bit),
216            enabled: Cell::new(false),
217            buffer: TakeCell::empty(),
218        }
219    }
220
221    pub fn initialize(&self, client: &'static dyn DMAClient, width: DMAWidth) {
222        self.client.set(client);
223        self.width.set(width);
224    }
225
226    pub fn enable(&self) {
227        pm::enable_clock(pm::Clock::HSB(pm::HSBClock::PDCA));
228        pm::enable_clock(pm::Clock::PBB(pm::PBBClock::PDCA));
229
230        if !self.enabled.get() {
231            NUM_ENABLED.fetch_add(1, atomic::Ordering::Relaxed);
232
233            // Disable all interrupts
234            self.registers
235                .idr
236                .write(Interrupt::TERR::SET + Interrupt::TRC::SET + Interrupt::RCZ::SET);
237
238            self.enabled.set(true);
239        }
240    }
241
242    pub fn disable(&self) {
243        if self.enabled.get() {
244            let num_enabled = NUM_ENABLED.fetch_sub(1, atomic::Ordering::Relaxed);
245            if num_enabled == 1 {
246                pm::disable_clock(pm::Clock::HSB(pm::HSBClock::PDCA));
247                pm::disable_clock(pm::Clock::PBB(pm::PBBClock::PDCA));
248            }
249            self.registers.cr.write(Control::TDIS::SET);
250            self.enabled.set(false);
251        }
252    }
253
254    pub fn is_enabled(&self) -> bool {
255        self.enabled.get()
256    }
257
258    pub fn handle_interrupt(&self) {
259        self.registers
260            .idr
261            .write(Interrupt::TERR::SET + Interrupt::TRC::SET + Interrupt::RCZ::SET);
262        let channel = self.registers.psr.get();
263
264        match DMAPeripheral::from_u8(channel) {
265            Some(channel) => {
266                self.client.map(|client| {
267                    client.transfer_done(channel);
268                });
269            }
270            None => {
271                debug!(
272                    "sam4l dma: psr read back invalid DMAPeripheral value {}",
273                    channel
274                );
275            }
276        }
277    }
278
279    pub fn start_transfer(&self) {
280        self.registers.cr.write(Control::TEN::SET);
281    }
282
283    pub fn prepare_transfer(&self, pid: DMAPeripheral, buf: &'static mut [u8], mut len: usize) {
284        // TODO(alevy): take care of zero length case
285
286        let maxlen = buf.len()
287            / match self.width.get() {
288                DMAWidth::Width8Bit => 1,
289                DMAWidth::Width16Bit => 2,
290                DMAWidth::Width32Bit => 4,
291            };
292        len = cmp::min(len, maxlen);
293        self.registers
294            .mr
295            .write(Mode::SIZE.val(self.width.get() as u32));
296
297        self.registers.psr.set(pid as u8);
298        self.registers
299            .marr
300            .write(MemoryAddressReload::MARV.val(core::ptr::from_ref::<u8>(&buf[0]) as u32));
301        self.registers
302            .tcrr
303            .write(TransferCounter::TCV.val(len as u32));
304
305        self.registers.ier.write(Interrupt::TRC::SET);
306
307        // Store the buffer reference in the TakeCell so it can be returned to
308        // the caller in `handle_interrupt`
309        self.buffer.replace(buf);
310    }
311
312    pub fn do_transfer(&self, pid: DMAPeripheral, buf: &'static mut [u8], len: usize) {
313        self.prepare_transfer(pid, buf, len);
314        self.start_transfer();
315    }
316
317    /// Aborts any current transactions and returns the buffer used in the
318    /// transaction.
319    pub fn abort_transfer(&self) -> Option<&'static mut [u8]> {
320        self.registers
321            .idr
322            .write(Interrupt::TERR::SET + Interrupt::TRC::SET + Interrupt::RCZ::SET);
323
324        // Reset counter
325        self.registers.tcr.write(TransferCounter::TCV.val(0));
326
327        self.buffer.take()
328    }
329
330    pub fn transfer_counter(&self) -> usize {
331        self.registers.tcr.read(TransferCounter::TCV) as usize
332    }
333}