Skip to main content

esp_hal/lcd_cam/
mod.rs

1//! # LCD and Camera
2//!
3//! ## Overview
4//! This peripheral consists of an LCD module and a Camera module, which can be
5//! used simultaneously. For more information on these modules, please refer to
6//! the documentation in their respective modules.
7
8use core::marker::PhantomData;
9
10use enumset::{EnumSet, EnumSetType};
11use esp_sync::RawMutex;
12
13use crate::{
14    Async,
15    Blocking,
16    asynch::AtomicWaker,
17    clock::dividers::FractionalDivider,
18    handler,
19    interrupt::InterruptHandler,
20    lcd_cam::{cam::Cam, lcd::Lcd},
21    peripherals::{Interrupt, LCD_CAM},
22    system::{Cpu, GenericPeripheralGuard},
23};
24
25pub mod cam;
26pub mod lcd;
27pub(crate) mod ll;
28
29/// DMA TX channel trait for LCD (I8080, DPI) peripherals.
30///
31/// Implemented for every TX-capable channel type that can serve the LCD module.
32#[diagnostic::on_unimplemented(
33    message = "The DMA channel cannot be used as a TX channel for LCD",
34    label = "This DMA channel"
35)]
36pub trait LcdDmaTxChannel<'d>: Into<ErasedTxChannel<'d>> + crate::private::Sealed {}
37
38/// DMA RX channel trait for the Camera peripheral.
39///
40/// Implemented for every RX-capable channel type that can serve the Camera module.
41#[diagnostic::on_unimplemented(
42    message = "The DMA channel cannot be used as an RX channel for Camera",
43    label = "This DMA channel"
44)]
45pub trait CamDmaRxChannel<'d>: Into<ErasedRxChannel<'d>> + crate::private::Sealed {}
46
47with_lcd_cam_dma_engine! {
48    ($engine:tt, $any_channel:tt) => {
49        type ErasedTxChannel<'d> = <crate::dma::$any_channel<'d> as crate::dma::DmaChannel>::Tx;
50        type ErasedRxChannel<'d> = <crate::dma::$any_channel<'d> as crate::dma::DmaChannel>::Rx;
51
52        crate::macros::impl_dma_channel_trait! {
53            $engine,
54            peri = LCD_CAM,
55            ($peri:path, $ch:path) => {
56                impl<'d> LcdDmaTxChannel<'d> for $ch {}
57                impl<'d> CamDmaRxChannel<'d> for $ch {}
58            }
59        }
60
61        // All channels split into the erased TX/RX channels, so we
62        // must implement the traits only once, outside of the macro.
63        impl<'d> LcdDmaTxChannel<'d> for ErasedTxChannel<'d> {}
64        impl<'d> CamDmaRxChannel<'d> for ErasedRxChannel<'d> {}
65    };
66}
67
68/// Represents a combined LCD and Camera interface.
69pub struct LcdCam<'d, Dm: crate::DriverMode> {
70    /// The LCD interface.
71    pub lcd: Lcd<'d, Dm>,
72    /// The Camera interface.
73    pub cam: Cam<'d>,
74}
75
76impl<'d> LcdCam<'d, Blocking> {
77    /// Creates a new `LcdCam` instance.
78    pub fn new(lcd_cam: LCD_CAM<'d>) -> Self {
79        let lcd_guard = GenericPeripheralGuard::new();
80        let cam_guard = GenericPeripheralGuard::new();
81
82        Self {
83            lcd: Lcd {
84                inner: lcd::Inner {
85                    lcd_cam: unsafe { lcd_cam.clone_unchecked() },
86                    _guard: lcd_guard,
87                    clock_requested: false,
88                },
89                _mode: PhantomData,
90            },
91            cam: Cam {
92                lcd_cam,
93                _guard: cam_guard,
94                clock_requested: false,
95            },
96        }
97    }
98
99    /// Reconfigures the peripheral for asynchronous operation.
100    pub fn into_async(mut self) -> LcdCam<'d, Async> {
101        self.set_interrupt_handler(interrupt_handler);
102        LcdCam {
103            lcd: self.lcd.into_async(),
104            cam: self.cam,
105        }
106    }
107
108    /// Registers an interrupt handler for the LCD_CAM peripheral.
109    ///
110    /// Replaces any previously registered interrupt handlers.
111    #[instability::unstable]
112    pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
113        for core in crate::system::Cpu::other() {
114            crate::interrupt::disable(core, Interrupt::LCD_CAM);
115        }
116        crate::interrupt::bind_handler(Interrupt::LCD_CAM, handler);
117    }
118}
119
120impl crate::private::Sealed for LcdCam<'_, Blocking> {}
121// TODO: This interrupt is shared with the Camera module, we should handle this
122// in a similar way to the gpio::IO
123#[instability::unstable]
124impl crate::interrupt::InterruptConfigurable for LcdCam<'_, Blocking> {
125    fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
126        self.set_interrupt_handler(handler);
127    }
128}
129
130impl<'d> LcdCam<'d, Async> {
131    /// Reconfigures the peripheral for blocking operation.
132    pub fn into_blocking(self) -> LcdCam<'d, Blocking> {
133        crate::interrupt::disable(Cpu::current(), Interrupt::LCD_CAM);
134        LcdCam {
135            lcd: self.lcd.into_blocking(),
136            cam: self.cam,
137        }
138    }
139}
140
141/// LCD_CAM bit order
142#[derive(Debug, Clone, Copy, PartialEq, Default)]
143#[cfg_attr(feature = "defmt", derive(defmt::Format))]
144pub enum BitOrder {
145    /// Do not change bit order.
146    #[default]
147    Native   = 0,
148    /// Inverts bit order.
149    Inverted = 1,
150}
151
152/// LCD_CAM byte order
153#[derive(Debug, Clone, Copy, PartialEq, Default)]
154#[cfg_attr(feature = "defmt", derive(defmt::Format))]
155pub enum ByteOrder {
156    /// Do not change byte order.
157    #[default]
158    Native   = 0,
159    /// Inverts byte order.
160    Inverted = 1,
161}
162
163pub(crate) static LCD_DONE_WAKER: AtomicWaker = AtomicWaker::new();
164pub(crate) static LCD_VSYNC_WAKER: AtomicWaker = AtomicWaker::new();
165static INT_ENA_LOCK: RawMutex = RawMutex::new();
166
167#[handler]
168fn interrupt_handler() {
169    // TODO: this is a shared interrupt with Camera and here we ignore that!
170    let status = LCD_CAM::regs().lc_dma_int_st().read();
171    if status.lcd_trans_done_int_st().bit_is_set() {
172        Instance::unlisten_lcd_done();
173        LCD_DONE_WAKER.wake();
174    }
175    if status.lcd_vsync_int_st().bit_is_set() {
176        Instance::unlisten(LcdCamInterrupt::LcdVsync.into());
177        LCD_VSYNC_WAKER.wake();
178    }
179}
180
181pub(crate) struct Instance;
182
183/// The interrupt sources of the LCD_CAM DMA interrupt registers.
184///
185/// This type is `pub(crate)` today. If the Camera driver ever needs its own
186/// listen API, it can be promoted to a public `LcdCamInterrupt` without
187/// renaming.
188#[derive(Debug, EnumSetType)]
189pub(crate) enum LcdCamInterrupt {
190    /// The LCD has started outputting a new frame.
191    LcdVsync,
192
193    /// A DMA transfer to the LCD has finished.
194    LcdTransDone,
195
196    /// The camera has received a VSYNC pulse.
197    CamVsync,
198
199    /// The camera has received an HSYNC pulse.
200    CamHs,
201}
202
203// LCD and Camera share the interrupt registers. Serialize INT_ENA updates
204// across cores and interrupt contexts.
205impl Instance {
206    fn enable_listen(sources: EnumSet<LcdCamInterrupt>, en: bool) {
207        INT_ENA_LOCK.lock(|| {
208            LCD_CAM::regs().lc_dma_int_ena().modify(|_, w| {
209                for source in sources {
210                    match source {
211                        LcdCamInterrupt::LcdVsync => {
212                            w.lcd_vsync_int_ena().bit(en);
213                        }
214                        LcdCamInterrupt::LcdTransDone => {
215                            w.lcd_trans_done_int_ena().bit(en);
216                        }
217                        LcdCamInterrupt::CamVsync => {
218                            w.cam_vsync_int_ena().bit(en);
219                        }
220                        LcdCamInterrupt::CamHs => {
221                            w.cam_hs_int_ena().bit(en);
222                        }
223                    }
224                }
225                w
226            });
227        });
228    }
229
230    pub(crate) fn listen(sources: EnumSet<LcdCamInterrupt>) {
231        Self::enable_listen(sources, true);
232    }
233
234    pub(crate) fn unlisten(sources: EnumSet<LcdCamInterrupt>) {
235        Self::enable_listen(sources, false);
236    }
237
238    pub(crate) fn interrupts() -> EnumSet<LcdCamInterrupt> {
239        let raw = LCD_CAM::regs().lc_dma_int_raw().read();
240        let mut sources = EnumSet::new();
241        if raw.lcd_vsync_int_raw().bit() {
242            sources.insert(LcdCamInterrupt::LcdVsync);
243        }
244        if raw.lcd_trans_done_int_raw().bit() {
245            sources.insert(LcdCamInterrupt::LcdTransDone);
246        }
247        if raw.cam_vsync_int_raw().bit() {
248            sources.insert(LcdCamInterrupt::CamVsync);
249        }
250        if raw.cam_hs_int_raw().bit() {
251            sources.insert(LcdCamInterrupt::CamHs);
252        }
253        sources
254    }
255
256    pub(crate) fn clear_interrupts(sources: EnumSet<LcdCamInterrupt>) {
257        LCD_CAM::regs().lc_dma_int_clr().write(|w| {
258            for source in sources {
259                match source {
260                    LcdCamInterrupt::LcdVsync => {
261                        w.lcd_vsync_int_clr().set_bit();
262                    }
263                    LcdCamInterrupt::LcdTransDone => {
264                        w.lcd_trans_done_int_clr().set_bit();
265                    }
266                    LcdCamInterrupt::CamVsync => {
267                        w.cam_vsync_int_clr().set_bit();
268                    }
269                    LcdCamInterrupt::CamHs => {
270                        w.cam_hs_int_clr().set_bit();
271                    }
272                }
273            }
274            w
275        });
276    }
277
278    pub(crate) fn listen_lcd_done() {
279        Self::listen(LcdCamInterrupt::LcdTransDone.into());
280    }
281
282    pub(crate) fn unlisten_lcd_done() {
283        Self::unlisten(LcdCamInterrupt::LcdTransDone.into());
284    }
285
286    pub(crate) fn is_lcd_done_set() -> bool {
287        Self::interrupts().contains(LcdCamInterrupt::LcdTransDone)
288    }
289}
290pub(crate) struct ClockDivider {
291    /// Integral clock divider value, 2 to 256.
292    pub div_num: u32,
293
294    /// Fractional clock divider numerator value, 0 to 63.
295    pub div_b: u32,
296
297    /// Fractional clock divider denominator value, 1 to 63.
298    pub div_a: u32,
299}
300
301impl ClockDivider {
302    fn new(divider: FractionalDivider) -> Self {
303        Self {
304            div_num: divider.integer,
305            div_b: divider.numerator,
306            // An integral divider has no denominator, but the clock tree only accepts
307            // denominators of 1 or more.
308            div_a: divider.denominator.max(1),
309        }
310    }
311}
312
313/// Clock configuration errors.
314#[derive(Debug, Clone, Copy, PartialEq)]
315#[cfg_attr(feature = "defmt", derive(defmt::Format))]
316pub enum ClockError {
317    /// Desired frequency was too low for the dividers to divide to.
318    FrequencyTooLow,
319}
320
321pub(crate) fn calculate_clkm(
322    desired_frequency: u32,
323    source_frequencies: &[u32],
324) -> Result<(usize, ClockDivider), ClockError> {
325    let mut result_error = 0;
326    let mut result = None;
327
328    for (i, &source_frequency) in source_frequencies.iter().enumerate() {
329        let Some(divider) = calculate_closest_divider(source_frequency, desired_frequency) else {
330            continue;
331        };
332
333        // A divider may land either side of the desired frequency, so pick the source that gets
334        // closest to it.
335        let error = divider
336            .output_frequency(source_frequency)
337            .abs_diff(desired_frequency);
338        if result.is_none() || error < result_error {
339            result = Some((i, divider));
340            result_error = error;
341        }
342    }
343
344    let (index, divider) = result.ok_or(ClockError::FrequencyTooLow)?;
345
346    Ok((index, ClockDivider::new(divider)))
347}
348
349fn calculate_closest_divider(
350    source_frequency: u32,
351    desired_frequency: u32,
352) -> Option<FractionalDivider> {
353    // For current chips, LCD and CAM have the same divider range.
354    let (min_divider, max_divider) = property!("clock_tree.lcd_cam.lcd_clock.div_num");
355    let (_, max_denominator) = property!("clock_tree.lcd_cam.lcd_clock.div_a");
356
357    if source_frequency / desired_frequency < min_divider {
358        // Source clock isn't fast enough to reach the desired frequency.
359        // Return max output.
360        return Some(FractionalDivider {
361            integer: min_divider,
362            numerator: 0,
363            denominator: 0,
364        });
365    }
366
367    let divider = FractionalDivider::new(source_frequency, desired_frequency, max_denominator);
368
369    // Source is too fast to divide down to the desired frequency.
370    (divider.integer <= max_divider).then_some(divider)
371}