Skip to main content

esp_hal/mipi_dsi/
vdma.rs

1//! Minimal VDMA abstraction for MIPI-DSI video streaming.
2
3use crate::{peripherals::VDMA, private::Sealed, reg_access::VolatileCell};
4
5/// Implemented by VDMA channel singletons (`VDMA_CH0`–`VDMA_CH3`).
6///
7/// Provides the hardware channel index without exposing the full DMA trait
8/// surface, since VDMA uses DW-GDMA linked-list mode which is incompatible
9/// with the standard GDMA channel traits.
10pub trait VdmaDmaChannel: Sealed {
11    /// Zero-based hardware channel index (0–3).
12    fn channel_id(&self) -> u8;
13}
14
15for_each_dma_channel! {
16    ("VDMA", $ch:ident, $num:literal, compatible = [$($compatible:ident),*]) => {
17        impl VdmaDmaChannel for crate::peripherals::$ch<'_> {
18            #[inline]
19            fn channel_id(&self) -> u8 { $num }
20        }
21    };
22}
23
24/// Fixed write destination for all DSI DMA transfers (DSI bridge pixel FIFO).
25pub(super) const DSI_BRG_MEM_BASE: u32 = 0x5010_5000;
26
27const PORT_MEMORY: u32 = 1;
28const PORT_DSI: u32 = 0;
29
30/// CTL0 value for mem → DSI transfers (constant across all frame buffers):
31///   sms=1 (MEMORY), dms=0 (DSI), sinc=0 (increment), dinc=1 (fixed),
32///   src/dst transfer width = 64-bit (3),
33///   src_msize = 512 items (8), dst_msize = 256 items (7).
34const CTRL_LO: u32 = PORT_MEMORY        // bit  0: sms
35    | (PORT_DSI  << 2)                  // bit  2: dms
36    // | (0      << 4)                  // bit  4: sinc = INCREMENT
37    | (1         << 6)                  // bit  6: dinc = FIXED
38    | (3         << 8)                  // bits 10:8  src_tr_width  (64-bit)
39    | (3         << 11)                 // bits 13:11 dst_tr_width  (64-bit)
40    | (8         << 14)                 // bits 17:14 src_msize     (512)
41    | (7         << 18); // bits 21:18 dst_msize     (256)
42
43/// CTL1 base: arlen_en=1, arlen=16, awlen_en=1, awlen=16.
44const CTRL_HI_BASE: u32 = (1 << 6) | (16 << 7) | (1 << 15) | (16 << 16);
45
46const LLI_VALID: u32 = 1 << 31;
47
48/// One VDMA link-list item (LLI).
49#[repr(C)]
50pub(super) struct VdmaLinkItem {
51    sar_lo: VolatileCell<u32>,    // 0x00 – source address (low 32 bits)
52    sar_hi: VolatileCell<u32>,    // 0x04 – source address (high 32 bits, always 0)
53    dar_lo: VolatileCell<u32>,    // 0x08 – destination address (DSI_BRG_MEM_BASE)
54    dar_hi: VolatileCell<u32>,    // 0x0C
55    block_ts: VolatileCell<u32>,  // 0x10 – transfer size in 64-bit units, minus 1
56    _res1: VolatileCell<u32>,     // 0x14
57    llp_lo: VolatileCell<u32>,    // 0x18 – next LLI pointer low | LMS
58    llp_hi: VolatileCell<u32>,    // 0x1C
59    ctrl_lo: VolatileCell<u32>,   // 0x20 – CTL0
60    ctrl_hi: VolatileCell<u32>,   // 0x24 – CTL1
61    sstat: VolatileCell<u32>,     // 0x28
62    dstat: VolatileCell<u32>,     // 0x2C
63    status_lo: VolatileCell<u32>, // 0x30
64    status_hi: VolatileCell<u32>, // 0x34
65    _res2: VolatileCell<u32>,     // 0x38
66    _res3: VolatileCell<u32>,     // 0x3C
67}
68
69impl VdmaLinkItem {
70    pub(super) const fn zeroed() -> Self {
71        Self {
72            sar_lo: VolatileCell::new(0),
73            sar_hi: VolatileCell::new(0),
74            dar_lo: VolatileCell::new(0),
75            dar_hi: VolatileCell::new(0),
76            block_ts: VolatileCell::new(0),
77            _res1: VolatileCell::new(0),
78            llp_lo: VolatileCell::new(0),
79            llp_hi: VolatileCell::new(0),
80            ctrl_lo: VolatileCell::new(0),
81            ctrl_hi: VolatileCell::new(0),
82            sstat: VolatileCell::new(0),
83            dstat: VolatileCell::new(0),
84            status_lo: VolatileCell::new(0),
85            status_hi: VolatileCell::new(0),
86            _res2: VolatileCell::new(0),
87            _res3: VolatileCell::new(0),
88        }
89    }
90
91    /// Populate this LLI for a circular frame-buffer transfer.
92    ///
93    /// `next` must point to the next LLI in the chain.
94    /// `LLI_LAST` is deliberately **not** set; the DMA always follows the LLP
95    /// pointer to continue the linked-list ring.
96    pub(super) fn configure(&self, src_addr: u32, fb_size: usize, next: *const VdmaLinkItem) {
97        debug_assert!(fb_size.is_multiple_of(8), "fb_size must be a multiple of 8");
98        let block_ts = (fb_size / 8) as u32 - 1;
99        let ctrl_hi = CTRL_HI_BASE | LLI_VALID; // no LLI_LAST → loop forever
100        self.sar_lo.set(src_addr);
101        self.sar_hi.set(0);
102        self.dar_lo.set(DSI_BRG_MEM_BASE);
103        self.dar_hi.set(0);
104        self.block_ts.set(block_ts);
105        self._res1.set(0);
106        // llp_lo: bits[31:6] = next_addr >> 6, bit[0] = lms (PORT_MEMORY).
107        // next is 64-byte aligned so bottom 6 bits are zero.
108        self.llp_lo.set(next as u32 | PORT_MEMORY);
109        self.llp_hi.set(0);
110        self.ctrl_lo.set(CTRL_LO);
111        self.ctrl_hi.set(ctrl_hi);
112        self.sstat.set(0);
113        self.dstat.set(0);
114        self.status_lo.set(0);
115        self.status_hi.set(0);
116        self._res2.set(0);
117        self._res3.set(0);
118    }
119
120    /// Update the source address in this LLI.
121    ///
122    /// Safe to call while the DMA is running: the controller latches `sar_lo`
123    /// at the **start** of each block, so an in-flight block is unaffected and
124    /// the new address takes effect for the next block.
125    pub(super) fn set_source(&self, src_addr: u32) {
126        self.sar_lo.set(src_addr);
127    }
128
129    /// Re-arm this LLI so the DMA can use it again.
130    ///
131    /// The DW-GDMA clears `LLI_VALID` in the LLI memory after consuming a
132    /// block.  Writing the complete `ctrl_hi` value (including `LLI_VALID`)
133    /// lets the DMA resume when it next fetches this LLI.  Call this on the
134    /// **just-consumed** LLI from the ping-pong pair while the DMA is busy
135    /// with the other one.
136    pub(super) fn rearm(&self) {
137        self.ctrl_hi.set(CTRL_HI_BASE | LLI_VALID);
138    }
139}
140
141/// Handle for a single VDMA channel dedicated to the DSI bridge.
142pub(super) struct VdmaChannel {
143    channel_id: u8,
144}
145
146impl VdmaChannel {
147    /// Initialise the VDMA controller and configure channel `channel_id`
148    /// (0-indexed, 0–3) for mem→DSI linked-list transfers.
149    ///
150    /// The caller must hold the `Vdma` peripheral guard before calling this.
151    pub(super) fn new(channel_id: u8) -> Self {
152        VDMA::regs().reset0().write(|w| w.dmac_rst().set_bit());
153        while VDMA::regs().reset0().read().dmac_rst().bit_is_set() {}
154        VDMA::regs().cfg0().modify(|_, w| {
155            w.dmac_en().set_bit();
156            w.int_en().set_bit()
157        });
158
159        let ch = VDMA::regs().ch(channel_id as usize);
160
161        // linked-list multi-block for both source and destination
162        ch.cfg0().write(|w| unsafe {
163            w.ch1_src_multblk_type().bits(3);
164            w.ch1_dst_multblk_type().bits(3)
165        });
166
167        // M→P, DMA as flow controller (tt_fc = 1 = DW_GDMA_LL_FLOW_M2P_DMAC)
168        // HW handshake on both ends; dst handshake peripheral = DSI (0)
169        // channel priority = 1; outstanding: src = 5 (4+1), dst = 2 (1+1)
170        ch.cfg1().write(|w| unsafe {
171            w.ch1_tt_fc().bits(1);
172            w.ch1_hs_sel_src().clear_bit();
173            w.ch1_hs_sel_dst().clear_bit();
174            w.ch1_dst_per().bits(0);
175            w.ch1_ch_prior().bits(1);
176            w.ch1_src_osr_lmt().bits(4);
177            w.ch1_dst_osr_lmt().bits(1)
178        });
179
180        Self { channel_id }
181    }
182
183    /// Point the channel's LLP at `item` and enable the channel.
184    pub(super) fn start(&mut self, item: &VdmaLinkItem) {
185        let addr = item as *const VdmaLinkItem as u32;
186        debug_assert_eq!(addr & 0x3F, 0, "LLI must be 64-byte aligned");
187        let dma = VDMA::regs();
188        let ch = dma.ch(self.channel_id as usize);
189
190        // LLP0: lms = MEMORY, loc0 = addr >> 6
191        ch.llp0()
192            .write(|w| unsafe { w.ch1_lms().bit(PORT_MEMORY != 0).ch1_loc0().bits(addr >> 6) });
193        unsafe { ch.llp1().write_with_zero(|w| w) };
194
195        self.ch_enable(true);
196    }
197
198    fn ch_enable(&self, en: bool) {
199        let shift = self.channel_id;
200        let val: u32 = if en {
201            0x0101 << shift // ch_en bit + ch_en_we bit
202        } else {
203            0x0100 << shift // ch_en_we only (clears ch_en)
204        };
205        unsafe { VDMA::regs().chen0().write(|w| w.bits(val)) };
206    }
207}