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}