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    /// Note that this will replace any previously registered interrupt
107    /// handlers.
108    #[instability::unstable]
109    pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
110        for core in crate::system::Cpu::other() {
111            crate::interrupt::disable(core, Interrupt::LCD_CAM);
112        }
113        crate::interrupt::bind_handler(Interrupt::LCD_CAM, handler);
114    }
115}
116
117impl crate::private::Sealed for LcdCam<'_, Blocking> {}
118// TODO: This interrupt is shared with the Camera module, we should handle this
119// in a similar way to the gpio::IO
120#[instability::unstable]
121impl crate::interrupt::InterruptConfigurable for LcdCam<'_, Blocking> {
122    fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
123        self.set_interrupt_handler(handler);
124    }
125}
126
127impl<'d> LcdCam<'d, Async> {
128    /// Reconfigures the peripheral for blocking operation.
129    pub fn into_blocking(self) -> LcdCam<'d, Blocking> {
130        crate::interrupt::disable(Cpu::current(), Interrupt::LCD_CAM);
131        LcdCam {
132            lcd: self.lcd.into_blocking(),
133            cam: self.cam,
134        }
135    }
136}
137
138/// LCD_CAM bit order
139#[derive(Debug, Clone, Copy, PartialEq, Default)]
140#[cfg_attr(feature = "defmt", derive(defmt::Format))]
141pub enum BitOrder {
142    /// Do not change bit order.
143    #[default]
144    Native   = 0,
145    /// Invert bit order.
146    Inverted = 1,
147}
148
149/// LCD_CAM byte order
150#[derive(Debug, Clone, Copy, PartialEq, Default)]
151#[cfg_attr(feature = "defmt", derive(defmt::Format))]
152pub enum ByteOrder {
153    /// Do not change byte order.
154    #[default]
155    Native   = 0,
156    /// Invert byte order.
157    Inverted = 1,
158}
159
160pub(crate) static LCD_DONE_WAKER: AtomicWaker = AtomicWaker::new();
161
162#[handler]
163fn interrupt_handler() {
164    // TODO: this is a shared interrupt with Camera and here we ignore that!
165    if Instance::is_lcd_done_set() {
166        Instance::unlisten_lcd_done();
167        LCD_DONE_WAKER.wake()
168    }
169}
170
171pub(crate) struct Instance;
172
173// NOTE: the LCD_CAM interrupt registers are shared between LCD and Camera and
174// this is only implemented for the LCD side, when the Camera is implemented a
175// CriticalSection will be needed to protect these shared registers.
176impl Instance {
177    fn enable_listenlcd_done(en: bool) {
178        LCD_CAM::regs()
179            .lc_dma_int_ena()
180            .modify(|_, w| w.lcd_trans_done_int_ena().bit(en));
181    }
182
183    pub(crate) fn listen_lcd_done() {
184        Self::enable_listenlcd_done(true);
185    }
186
187    pub(crate) fn unlisten_lcd_done() {
188        Self::enable_listenlcd_done(false);
189    }
190
191    pub(crate) fn is_lcd_done_set() -> bool {
192        LCD_CAM::regs()
193            .lc_dma_int_raw()
194            .read()
195            .lcd_trans_done_int_raw()
196            .bit()
197    }
198}
199pub(crate) struct ClockDivider {
200    /// Integral clock divider value, 2 to 256.
201    pub div_num: u32,
202
203    /// Fractional clock divider numerator value, 0 to 63.
204    pub div_b: u32,
205
206    /// Fractional clock divider denominator value, 1 to 63.
207    pub div_a: u32,
208}
209
210impl ClockDivider {
211    fn new(divider: FractionalDivider) -> Self {
212        Self {
213            div_num: divider.integer,
214            div_b: divider.numerator,
215            // An integral divider has no denominator, but the clock tree only accepts
216            // denominators of 1 or more.
217            div_a: divider.denominator.max(1),
218        }
219    }
220}
221
222/// Clock configuration errors.
223#[derive(Debug, Clone, Copy, PartialEq)]
224#[cfg_attr(feature = "defmt", derive(defmt::Format))]
225pub enum ClockError {
226    /// Desired frequency was too low for the dividers to divide to
227    FrequencyTooLow,
228}
229
230pub(crate) fn calculate_clkm(
231    desired_frequency: u32,
232    source_frequencies: &[u32],
233) -> Result<(usize, ClockDivider), ClockError> {
234    let mut result_error = 0;
235    let mut result = None;
236
237    for (i, &source_frequency) in source_frequencies.iter().enumerate() {
238        let Some(divider) = calculate_closest_divider(source_frequency, desired_frequency) else {
239            continue;
240        };
241
242        // A divider may land either side of the desired frequency, so pick the source that gets
243        // closest to it.
244        let error = divider
245            .output_frequency(source_frequency)
246            .abs_diff(desired_frequency);
247        if result.is_none() || error < result_error {
248            result = Some((i, divider));
249            result_error = error;
250        }
251    }
252
253    let (index, divider) = result.ok_or(ClockError::FrequencyTooLow)?;
254
255    Ok((index, ClockDivider::new(divider)))
256}
257
258fn calculate_closest_divider(
259    source_frequency: u32,
260    desired_frequency: u32,
261) -> Option<FractionalDivider> {
262    // For current chips, LCD and CAM have the same divider range.
263    let (min_divider, max_divider) = property!("clock_tree.lcd_cam.lcd_clock.div_num");
264    let (_, max_denominator) = property!("clock_tree.lcd_cam.lcd_clock.div_a");
265
266    if source_frequency / desired_frequency < min_divider {
267        // Source clock isn't fast enough to reach the desired frequency.
268        // Return max output.
269        return Some(FractionalDivider {
270            integer: min_divider,
271            numerator: 0,
272            denominator: 0,
273        });
274    }
275
276    let divider = FractionalDivider::new(source_frequency, desired_frequency, max_denominator);
277
278    // Source is too fast to divide down to the desired frequency.
279    (divider.integer <= max_divider).then_some(divider)
280}