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 crate::{
11    Async,
12    Blocking,
13    asynch::AtomicWaker,
14    clock::dividers::FractionalDivider,
15    handler,
16    interrupt::InterruptHandler,
17    lcd_cam::{cam::Cam, lcd::Lcd},
18    peripherals::{Interrupt, LCD_CAM},
19    system::{Cpu, GenericPeripheralGuard},
20};
21
22pub mod cam;
23pub mod lcd;
24
25/// DMA TX channel trait for LCD (I8080, DPI) peripherals.
26///
27/// Implemented for every TX-capable channel type that can serve the LCD module.
28#[diagnostic::on_unimplemented(
29    message = "The DMA channel cannot be used as a TX channel for LCD",
30    label = "This DMA channel"
31)]
32pub trait LcdDmaTxChannel<'d>: Into<ErasedTxChannel<'d>> + crate::private::Sealed {}
33
34/// DMA RX channel trait for the Camera peripheral.
35///
36/// Implemented for every RX-capable channel type that can serve the Camera module.
37#[diagnostic::on_unimplemented(
38    message = "The DMA channel cannot be used as an RX channel for Camera",
39    label = "This DMA channel"
40)]
41pub trait CamDmaRxChannel<'d>: Into<ErasedRxChannel<'d>> + crate::private::Sealed {}
42
43with_lcd_cam_dma_engine! {
44    ($engine:tt, $any_channel:tt) => {
45        type ErasedTxChannel<'d> = <crate::dma::$any_channel<'d> as crate::dma::DmaChannel>::Tx;
46        type ErasedRxChannel<'d> = <crate::dma::$any_channel<'d> as crate::dma::DmaChannel>::Rx;
47
48        crate::macros::impl_dma_channel_trait! {
49            $engine,
50            peri = LCD_CAM,
51            ($peri:path, $ch:path) => {
52                impl<'d> LcdDmaTxChannel<'d> for $ch {}
53                impl<'d> CamDmaRxChannel<'d> for $ch {}
54            }
55        }
56
57        // All channels split into the erased TX/RX channels, so we
58        // must implement the traits only once, outside of the macro.
59        impl<'d> LcdDmaTxChannel<'d> for ErasedTxChannel<'d> {}
60        impl<'d> CamDmaRxChannel<'d> for ErasedRxChannel<'d> {}
61    };
62}
63
64/// Represents a combined LCD and Camera interface.
65pub struct LcdCam<'d, Dm: crate::DriverMode> {
66    /// The LCD interface.
67    pub lcd: Lcd<'d, Dm>,
68    /// The Camera interface.
69    pub cam: Cam<'d>,
70}
71
72impl<'d> LcdCam<'d, Blocking> {
73    /// Creates a new `LcdCam` instance.
74    pub fn new(lcd_cam: LCD_CAM<'d>) -> Self {
75        let lcd_guard = GenericPeripheralGuard::new();
76        let cam_guard = GenericPeripheralGuard::new();
77
78        Self {
79            lcd: Lcd {
80                inner: lcd::Inner {
81                    lcd_cam: unsafe { lcd_cam.clone_unchecked() },
82                    _guard: lcd_guard,
83                    clock_requested: false,
84                },
85                _mode: PhantomData,
86            },
87            cam: Cam {
88                lcd_cam,
89                _guard: cam_guard,
90                clock_requested: false,
91            },
92        }
93    }
94
95    /// Reconfigures the peripheral for asynchronous operation.
96    pub fn into_async(mut self) -> LcdCam<'d, Async> {
97        self.set_interrupt_handler(interrupt_handler);
98        LcdCam {
99            lcd: self.lcd.into_async(),
100            cam: self.cam,
101        }
102    }
103
104    /// Registers an interrupt handler for the LCD_CAM peripheral.
105    ///
106    /// Replaces any previously registered interrupt handlers.
107    #[instability::unstable]
108    pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
109        for core in crate::system::Cpu::other() {
110            crate::interrupt::disable(core, Interrupt::LCD_CAM);
111        }
112        crate::interrupt::bind_handler(Interrupt::LCD_CAM, handler);
113    }
114}
115
116impl crate::private::Sealed for LcdCam<'_, Blocking> {}
117// TODO: This interrupt is shared with the Camera module, we should handle this
118// in a similar way to the gpio::IO
119#[instability::unstable]
120impl crate::interrupt::InterruptConfigurable for LcdCam<'_, Blocking> {
121    fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
122        self.set_interrupt_handler(handler);
123    }
124}
125
126impl<'d> LcdCam<'d, Async> {
127    /// Reconfigures the peripheral for blocking operation.
128    pub fn into_blocking(self) -> LcdCam<'d, Blocking> {
129        crate::interrupt::disable(Cpu::current(), Interrupt::LCD_CAM);
130        LcdCam {
131            lcd: self.lcd.into_blocking(),
132            cam: self.cam,
133        }
134    }
135}
136
137/// LCD_CAM bit order
138#[derive(Debug, Clone, Copy, PartialEq, Default)]
139#[cfg_attr(feature = "defmt", derive(defmt::Format))]
140pub enum BitOrder {
141    /// Do not change bit order.
142    #[default]
143    Native   = 0,
144    /// Inverts bit order.
145    Inverted = 1,
146}
147
148/// LCD_CAM byte order
149#[derive(Debug, Clone, Copy, PartialEq, Default)]
150#[cfg_attr(feature = "defmt", derive(defmt::Format))]
151pub enum ByteOrder {
152    /// Do not change byte order.
153    #[default]
154    Native   = 0,
155    /// Inverts byte order.
156    Inverted = 1,
157}
158
159pub(crate) static LCD_DONE_WAKER: AtomicWaker = AtomicWaker::new();
160
161#[handler]
162fn interrupt_handler() {
163    // TODO: this is a shared interrupt with Camera and here we ignore that!
164    if Instance::is_lcd_done_set() {
165        Instance::unlisten_lcd_done();
166        LCD_DONE_WAKER.wake()
167    }
168}
169
170pub(crate) struct Instance;
171
172// NOTE: the LCD_CAM interrupt registers are shared between LCD and Camera and
173// this is only implemented for the LCD side, when the Camera is implemented a
174// CriticalSection will be needed to protect these shared registers.
175impl Instance {
176    fn enable_listenlcd_done(en: bool) {
177        LCD_CAM::regs()
178            .lc_dma_int_ena()
179            .modify(|_, w| w.lcd_trans_done_int_ena().bit(en));
180    }
181
182    pub(crate) fn listen_lcd_done() {
183        Self::enable_listenlcd_done(true);
184    }
185
186    pub(crate) fn unlisten_lcd_done() {
187        Self::enable_listenlcd_done(false);
188    }
189
190    pub(crate) fn is_lcd_done_set() -> bool {
191        LCD_CAM::regs()
192            .lc_dma_int_raw()
193            .read()
194            .lcd_trans_done_int_raw()
195            .bit()
196    }
197}
198pub(crate) struct ClockDivider {
199    /// Integral clock divider value, 2 to 256.
200    pub div_num: u32,
201
202    /// Fractional clock divider numerator value, 0 to 63.
203    pub div_b: u32,
204
205    /// Fractional clock divider denominator value, 1 to 63.
206    pub div_a: u32,
207}
208
209impl ClockDivider {
210    fn new(divider: FractionalDivider) -> Self {
211        Self {
212            div_num: divider.integer,
213            div_b: divider.numerator,
214            // An integral divider has no denominator, but the clock tree only accepts
215            // denominators of 1 or more.
216            div_a: divider.denominator.max(1),
217        }
218    }
219}
220
221/// Clock configuration errors.
222#[derive(Debug, Clone, Copy, PartialEq)]
223#[cfg_attr(feature = "defmt", derive(defmt::Format))]
224pub enum ClockError {
225    /// Desired frequency was too low for the dividers to divide to.
226    FrequencyTooLow,
227}
228
229pub(crate) fn calculate_clkm(
230    desired_frequency: u32,
231    source_frequencies: &[u32],
232) -> Result<(usize, ClockDivider), ClockError> {
233    let mut result_error = 0;
234    let mut result = None;
235
236    for (i, &source_frequency) in source_frequencies.iter().enumerate() {
237        let Some(divider) = calculate_closest_divider(source_frequency, desired_frequency) else {
238            continue;
239        };
240
241        // A divider may land either side of the desired frequency, so pick the source that gets
242        // closest to it.
243        let error = divider
244            .output_frequency(source_frequency)
245            .abs_diff(desired_frequency);
246        if result.is_none() || error < result_error {
247            result = Some((i, divider));
248            result_error = error;
249        }
250    }
251
252    let (index, divider) = result.ok_or(ClockError::FrequencyTooLow)?;
253
254    Ok((index, ClockDivider::new(divider)))
255}
256
257fn calculate_closest_divider(
258    source_frequency: u32,
259    desired_frequency: u32,
260) -> Option<FractionalDivider> {
261    // For current chips, LCD and CAM have the same divider range.
262    let (min_divider, max_divider) = property!("clock_tree.lcd_cam.lcd_clock.div_num");
263    let (_, max_denominator) = property!("clock_tree.lcd_cam.lcd_clock.div_a");
264
265    if source_frequency / desired_frequency < min_divider {
266        // Source clock isn't fast enough to reach the desired frequency.
267        // Return max output.
268        return Some(FractionalDivider {
269            integer: min_divider,
270            numerator: 0,
271            denominator: 0,
272        });
273    }
274
275    let divider = FractionalDivider::new(source_frequency, desired_frequency, max_denominator);
276
277    // Source is too fast to divide down to the desired frequency.
278    (divider.integer <= max_divider).then_some(divider)
279}