1use core::marker::PhantomData;
2
3#[cfg(feature = "rp2040")]
4use rp2040_hal as hal;
5
6#[cfg(feature = "rp235x")]
7use rp235x_hal as hal;
8
9use hal::dma::{SingleChannel, Word, double_buffer};
10use hal::gpio::{Pin, PinId, PullDown};
11use hal::pio::{
12 Buffers, PIO, PIOBuilder, PIOExt, PinDir, Running, Rx, ShiftDirection, StateMachine,
13 StateMachineIndex, Tx, UninitStateMachine,
14};
15
16use pio::Program;
17use pio_proc::pio_file;
18use rp_cvideo_core::buffer::Buffer;
19use rp_cvideo_core::buffer_preparation::LabelAddresses;
20use rp_cvideo_core::config::CVideoConfig;
21use rp_cvideo_core::display_buffer::{DisplayBuffer, NoFields};
22
23type DmaTxStateQueued<'t, NF> = double_buffer::ReadNext<Buffer<'t, NF>>;
24type DmaTxStateUnQueued = ();
25
26type CVideoDmaTransfer<'t, DmaCh1, DmaCh2, NF, P, SM, DmaTxState> =
27 double_buffer::Transfer<DmaCh1, DmaCh2, Buffer<'t, NF>, Tx<(P, SM), Word>, DmaTxState>;
28
29#[allow(missing_docs)]
30pub struct CVideo<
31 'buffer,
32 P: PIOExt,
33 SM: StateMachineIndex,
34 DmaCh1: SingleChannel,
35 DmaCh2: SingleChannel,
36 DmaTxState,
37 NF: NoFields,
38 PinSync: PinId,
39 PinVideo: PinId,
40> {
41 sm: StateMachine<(P, SM), Running>,
42 dma_transfer: CVideoDmaTransfer<'buffer, DmaCh1, DmaCh2, NF, P, SM, DmaTxState>,
43
44 pin_sync: Pin<PinSync, P::PinFunction, PullDown>,
46 pin_video: Pin<PinVideo, P::PinFunction, PullDown>,
47 pio_sm_rx: Rx<(P, SM)>,
48
49 _nf: PhantomData<NF>,
50}
51
52pub struct CVideoPeripherals<P, SM, DmaCh1, DmaCh2, PinSync, PinVideo>
54where
55 P: PIOExt,
56 SM: StateMachineIndex,
57 DmaCh1: SingleChannel,
58 DmaCh2: SingleChannel,
59 PinSync: PinId,
60 PinVideo: PinId,
61{
62 #[allow(missing_docs)]
63 pub pin_sync: Pin<PinSync, P::PinFunction, PullDown>,
64
65 #[allow(missing_docs)]
66 pub pin_video: Pin<PinVideo, P::PinFunction, PullDown>,
67
68 #[allow(missing_docs)]
69 pub sm: UninitStateMachine<(P, SM)>,
70
71 #[allow(missing_docs)]
72 pub dma_ch1: DmaCh1,
73
74 #[allow(missing_docs)]
75 pub dma_ch2: DmaCh2,
76}
77
78pub type CVideoInitResult<'buffer, P, SM, DmaCh1, DmaCh2, NF, PinSync, PinVideo> = Result<
80 (
82 CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateUnQueued, NF, PinSync, PinVideo>,
83 DisplayBuffer<'buffer, NF>,
84 PIO<P>,
85 ),
86 (
88 CVideoPeripherals<P, SM, DmaCh1, DmaCh2, PinSync, PinVideo>,
89 PIO<P>,
90 &'static str,
91 ),
92>;
93
94impl<'buffer, P, SM, DmaCh1, DmaCh2, NF, PinSync, PinVideo>
95 CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateUnQueued, NF, PinSync, PinVideo>
96where
97 P: PIOExt,
98 SM: StateMachineIndex,
99 DmaCh1: SingleChannel,
100 DmaCh2: SingleChannel,
101 NF: NoFields,
102 PinSync: PinId,
103 PinVideo: PinId,
104{
105 pub fn init<'conf>(
116 config: CVideoConfig<'conf, NF>,
117 mut pio: PIO<P>,
118 data_buf: &'buffer mut [u32],
119 hw: CVideoPeripherals<P, SM, DmaCh1, DmaCh2, PinSync, PinVideo>,
120 ) -> CVideoInitResult<'buffer, P, SM, DmaCh1, DmaCh2, NF, PinSync, PinVideo> {
121 if !config.check() {
122 return Err((hw, pio, "config.check() returned false"));
123 }
124
125 let pin_sync_id = hw.pin_sync.id().num;
126 let pin_video_id = hw.pin_video.id().num;
127
128 let (program, program_labels) = load_patched_program(config.ticks_per_pixel());
129
130 let maybe_installed = pio.install(&program);
131 if maybe_installed.is_err() {
132 return Err((hw, pio, "Could not install PIO program"));
133 }
134 let installed = maybe_installed.unwrap();
135
136 let installed_offset = installed.offset() as u32;
137 let (mut sm, pio_sm_rx, pio_sm_tx) = PIOBuilder::from_installed_program(installed)
138 .out_pins(pin_video_id, 1)
139 .set_pins(pin_video_id, 1)
140 .side_set_pin_base(pin_sync_id)
141 .autopull(true)
142 .pull_threshold(32)
143 .out_shift_direction(ShiftDirection::Right)
144 .buffers(Buffers::OnlyTx)
145 .clock_divisor_fixed_point(config.pio_prescaler(), 0)
146 .build(hw.sm);
147
148 let maybe_prep_buf =
149 config.prepare_buffer(data_buf, program_labels.with_offset(installed_offset));
150 if let Err(reason) = maybe_prep_buf {
151 let (sm, installed) = sm.uninit(pio_sm_rx, pio_sm_tx);
152 pio.uninstall(installed);
153 return Err((
154 CVideoPeripherals {
155 pin_sync: hw.pin_sync,
156 pin_video: hw.pin_video,
157 sm,
158 dma_ch1: hw.dma_ch1,
159 dma_ch2: hw.dma_ch2,
160 },
161 pio,
162 reason,
163 ));
164 }
165 let (buffer_a, buffer_b) = maybe_prep_buf.unwrap();
166
167 assert!(matches!(buffer_b, Buffer::DisplayBuffer(_))); let dma_transfer =
170 double_buffer::Config::new((hw.dma_ch1, hw.dma_ch2), buffer_a, pio_sm_tx).start();
171
172 sm.set_pindirs([
173 (pin_sync_id, PinDir::Output),
174 (pin_video_id, PinDir::Output),
175 ]);
176
177 Ok((
182 Self {
183 sm: sm.start(),
184 dma_transfer,
185 pin_sync: hw.pin_sync,
186 pin_video: hw.pin_video,
187 pio_sm_rx,
188 _nf: Default::default(),
189 },
190 buffer_b.unwrap_display_buffer(),
191 pio,
192 ))
193 }
194
195 pub fn commit_draw_buffer(
197 self,
198 display_buffer: DisplayBuffer<'buffer, NF>,
199 ) -> CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateQueued<'buffer, NF>, NF, PinSync, PinVideo>
200 {
201 CVideo::<P, SM, DmaCh1, DmaCh2, DmaTxStateQueued<NF>, NF, PinSync, PinVideo> {
202 sm: self.sm,
203 dma_transfer: self
204 .dma_transfer
205 .read_next(Buffer::DisplayBuffer(display_buffer)),
206 pin_sync: self.pin_sync,
207 pin_video: self.pin_video,
208 pio_sm_rx: self.pio_sm_rx,
209 _nf: Default::default(),
210 }
211 }
212
213 #[allow(clippy::type_complexity)]
218 pub fn uninit(
219 self,
220 mut pio: PIO<P>,
221 ) -> (
222 CVideoPeripherals<P, SM, DmaCh1, DmaCh2, PinSync, PinVideo>,
223 PIO<P>,
224 ) {
225 let (dma_ch1, dma_ch2, _, pio_sm_tx) = self.dma_transfer.wait();
226 let (sm, installed) = self.sm.uninit(self.pio_sm_rx, pio_sm_tx);
227 pio.uninstall(installed);
228
229 (
230 CVideoPeripherals {
231 pin_sync: self.pin_sync,
232 pin_video: self.pin_video,
233 sm,
234 dma_ch1,
235 dma_ch2,
236 },
237 pio,
238 )
239 }
240}
241
242impl<'buffer, P, SM, DmaCh1, DmaCh2, NF, PinSync, PinVideo>
243 CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateQueued<'buffer, NF>, NF, PinSync, PinVideo>
244where
245 P: PIOExt,
246 SM: StateMachineIndex,
247 DmaCh1: SingleChannel,
248 DmaCh2: SingleChannel,
249 NF: NoFields,
250 PinSync: PinId,
251 PinVideo: PinId,
252{
253 #[allow(clippy::type_complexity)]
256 pub fn wait(
257 self,
258 ) -> (
259 CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateUnQueued, NF, PinSync, PinVideo>,
260 DisplayBuffer<'buffer, NF>,
261 ) {
262 let (mut buffer, mut next_transfer) = self.dma_transfer.wait();
263
264 if matches!(buffer, Buffer::SyncStuff(_)) {
265 (buffer, next_transfer) = next_transfer.read_next(buffer).wait();
266 }
267
268 (
269 CVideo::<P, SM, DmaCh1, DmaCh2, DmaTxStateUnQueued, NF, PinSync, PinVideo> {
270 sm: self.sm,
271 dma_transfer: next_transfer,
272 _nf: Default::default(),
273 pin_sync: self.pin_sync,
274 pin_video: self.pin_video,
275 pio_sm_rx: self.pio_sm_rx,
276 },
277 buffer.unwrap_display_buffer(),
278 )
279 }
280}
281
282fn load_patched_program(ticks_per_pixel: u32) -> (Program<32>, LabelAddresses) {
284 assert!(ticks_per_pixel >= 2);
285 let mut pio_program = pio_file!("src/cvideo.pio", select_program("cvideo"));
286 let display_out_instruction_ptr = pio_program.public_defines.display_out_instruction as usize;
287
288 let mut display_out_instruction = pio::Instruction::decode(
289 pio_program.program.code[display_out_instruction_ptr],
290 pio_program.program.side_set,
291 )
292 .unwrap();
293 display_out_instruction.delay = (ticks_per_pixel - 2) as u8;
294 pio_program.program.code[display_out_instruction_ptr] =
295 display_out_instruction.encode(pio_program.program.side_set);
296
297 let label_addresses = LabelAddresses {
298 sync: pio_program.public_defines.sync.try_into().unwrap(),
299 blank: pio_program.public_defines.blank.try_into().unwrap(),
300 display: pio_program.public_defines.display.try_into().unwrap(),
301 };
302
303 (pio_program.program, label_addresses)
304}