diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 593a808ff83..65fd89f3abe 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add DmaTransactionTxOwned, DmaTransactionRxOwned, DmaTransactionTxRxOwned, functions to do owning transfers added to SPI half-duplex (#1672) - uart: Implement `embedded_io::ReadReady` for `Uart` and `UartRx` (#1702) - ESP32-S3: Expose optional HSYNC input in LCD_CAM (#1707) +- ESP32-S3: Add async support to the LCD_CAM I8080 driver (#1834) - ESP32-C6: Support lp-core as wake-up source (#1723) - Add support for GPIO wake-up source (#1724) - gpio: add DummyPin (#1769) diff --git a/esp-hal/src/lcd_cam/lcd/i8080.rs b/esp-hal/src/lcd_cam/lcd/i8080.rs index f2413ee17a8..309bc8d7bd4 100644 --- a/esp-hal/src/lcd_cam/lcd/i8080.rs +++ b/esp-hal/src/lcd_cam/lcd/i8080.rs @@ -57,10 +57,12 @@ //! # } //! ``` -use core::{fmt::Formatter, mem::size_of}; +use core::{fmt::Formatter, marker::PhantomData, mem::size_of}; use fugit::HertzU32; +#[cfg(feature = "async")] +use crate::lcd_cam::asynch::LcdDoneFuture; use crate::{ clock::Clocks, dma::{ @@ -86,22 +88,24 @@ use crate::{ }, peripheral::{Peripheral, PeripheralRef}, peripherals::LCD_CAM, + Mode, }; -pub struct I8080<'d, CH: DmaChannel, P> { +pub struct I8080<'d, CH: DmaChannel, P, DM: Mode> { lcd_cam: PeripheralRef<'d, LCD_CAM>, tx_channel: ChannelTx<'d, CH>, tx_chain: DescriptorChain, _pins: P, + _phantom: PhantomData, } -impl<'d, CH: DmaChannel, P: TxPins> I8080<'d, CH, P> +impl<'d, CH: DmaChannel, P: TxPins, DM: Mode> I8080<'d, CH, P, DM> where CH::P: LcdCamPeripheral, P::Word: Into, { pub fn new( - lcd: Lcd<'d>, + lcd: Lcd<'d, DM>, mut channel: ChannelTx<'d, CH>, descriptors: &'static mut [DmaDescriptor], mut pins: P, @@ -249,11 +253,12 @@ where tx_channel: channel, tx_chain: DescriptorChain::new(descriptors), _pins: pins, + _phantom: PhantomData, } } } -impl<'d, CH: DmaChannel, P: TxPins> DmaSupport for I8080<'d, CH, P> { +impl<'d, CH: DmaChannel, P: TxPins, DM: Mode> DmaSupport for I8080<'d, CH, P, DM> { fn peripheral_wait_dma(&mut self, _is_tx: bool, _is_rx: bool) { let lcd_user = self.lcd_cam.lcd_user(); // Wait until LCD_START is cleared by hardware. @@ -266,7 +271,7 @@ impl<'d, CH: DmaChannel, P: TxPins> DmaSupport for I8080<'d, CH, P> { } } -impl<'d, CH: DmaChannel, P: TxPins> DmaSupportTx for I8080<'d, CH, P> { +impl<'d, CH: DmaChannel, P: TxPins, DM: Mode> DmaSupportTx for I8080<'d, CH, P, DM> { type TX = ChannelTx<'d, CH>; fn tx(&mut self) -> &mut Self::TX { @@ -278,7 +283,7 @@ impl<'d, CH: DmaChannel, P: TxPins> DmaSupportTx for I8080<'d, CH, P> { } } -impl<'d, CH: DmaChannel, P: TxPins> I8080<'d, CH, P> +impl<'d, CH: DmaChannel, P: TxPins, DM: Mode> I8080<'d, CH, P, DM> where P::Word: Into, { @@ -364,7 +369,35 @@ where } } -impl<'d, CH: DmaChannel, P> I8080<'d, CH, P> { +#[cfg(feature = "async")] +impl<'d, CH: DmaChannel, P: TxPins> I8080<'d, CH, P, crate::Async> +where + P::Word: Into, +{ + pub async fn send_dma_async<'t, TXBUF>( + &'t mut self, + cmd: impl Into>, + dummy: u8, + data: &'t TXBUF, + ) -> Result<(), DmaError> + where + TXBUF: ReadBuffer, + { + let (ptr, len) = unsafe { data.read_buffer() }; + + self.setup_send(cmd.into(), dummy); + self.start_write_bytes_dma(ptr as _, len * size_of::())?; + self.start_send(); + + LcdDoneFuture::new().await; + if self.tx_channel.has_error() { + return Err(DmaError::DescriptorError); + } + Ok(()) + } +} + +impl<'d, CH: DmaChannel, P, DM: Mode> I8080<'d, CH, P, DM> { fn setup_send>(&mut self, cmd: Command, dummy: u8) { // Reset LCD control unit and Async Tx FIFO self.lcd_cam @@ -476,7 +509,7 @@ impl<'d, CH: DmaChannel, P> I8080<'d, CH, P> { } } -impl<'d, CH: DmaChannel, P> core::fmt::Debug for I8080<'d, CH, P> { +impl<'d, CH: DmaChannel, P, DM: Mode> core::fmt::Debug for I8080<'d, CH, P, DM> { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("I8080").finish() } diff --git a/esp-hal/src/lcd_cam/lcd/mod.rs b/esp-hal/src/lcd_cam/lcd/mod.rs index 8b980b9d5fd..a1e5dc85153 100644 --- a/esp-hal/src/lcd_cam/lcd/mod.rs +++ b/esp-hal/src/lcd_cam/lcd/mod.rs @@ -14,8 +14,9 @@ use crate::{peripheral::PeripheralRef, peripherals::LCD_CAM}; pub mod i8080; -pub struct Lcd<'d> { +pub struct Lcd<'d, DM: crate::Mode> { pub(crate) lcd_cam: PeripheralRef<'d, LCD_CAM>, + pub(crate) _mode: core::marker::PhantomData, } #[derive(Debug, Clone, Copy, PartialEq, Default)] diff --git a/esp-hal/src/lcd_cam/mod.rs b/esp-hal/src/lcd_cam/mod.rs index a106f0a797b..ec1b2b3b334 100644 --- a/esp-hal/src/lcd_cam/mod.rs +++ b/esp-hal/src/lcd_cam/mod.rs @@ -8,20 +8,23 @@ pub mod cam; pub mod lcd; +use core::marker::PhantomData; + use crate::{ + interrupt::InterruptHandler, lcd_cam::{cam::Cam, lcd::Lcd}, peripheral::Peripheral, peripherals::LCD_CAM, - system, - system::PeripheralClockControl, + system::{self, PeripheralClockControl}, + InterruptConfigurable, }; -pub struct LcdCam<'d> { - pub lcd: Lcd<'d>, +pub struct LcdCam<'d, DM: crate::Mode> { + pub lcd: Lcd<'d, DM>, pub cam: Cam<'d>, } -impl<'d> LcdCam<'d> { +impl<'d> LcdCam<'d, crate::Blocking> { pub fn new(lcd_cam: impl Peripheral

+ 'd) -> Self { crate::into_ref!(lcd_cam); @@ -30,6 +33,54 @@ impl<'d> LcdCam<'d> { Self { lcd: Lcd { lcd_cam: unsafe { lcd_cam.clone_unchecked() }, + _mode: PhantomData, + }, + cam: Cam { + lcd_cam: unsafe { lcd_cam.clone_unchecked() }, + }, + } + } +} + +impl<'d> crate::private::Sealed for LcdCam<'d, crate::Blocking> {} +// TODO: This interrupt is shared with the Camera module, we should handle this +// in a similar way to the gpio::IO +impl<'d> InterruptConfigurable for LcdCam<'d, crate::Blocking> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + unsafe { + crate::interrupt::bind_interrupt( + crate::peripherals::Interrupt::LCD_CAM, + handler.handler(), + ); + crate::interrupt::enable(crate::peripherals::Interrupt::LCD_CAM, handler.priority()) + .unwrap(); + } + } +} + +#[cfg(feature = "async")] +impl<'d> LcdCam<'d, crate::Async> { + pub fn new_async(lcd_cam: impl Peripheral

+ 'd) -> Self { + crate::into_ref!(lcd_cam); + + PeripheralClockControl::enable(system::Peripheral::LcdCam); + + unsafe { + crate::interrupt::bind_interrupt( + crate::peripherals::Interrupt::LCD_CAM, + asynch::interrupt_handler.handler(), + ); + } + crate::interrupt::enable( + crate::peripherals::Interrupt::LCD_CAM, + asynch::interrupt_handler.priority(), + ) + .unwrap(); + + Self { + lcd: Lcd { + lcd_cam: unsafe { lcd_cam.clone_unchecked() }, + _mode: PhantomData, }, cam: Cam { lcd_cam: unsafe { lcd_cam.clone_unchecked() }, @@ -60,7 +111,99 @@ pub enum ByteOrder { Inverted = 1, } +#[doc(hidden)] +#[cfg(feature = "async")] +pub mod asynch { + use core::task::Poll; + + use embassy_sync::waitqueue::AtomicWaker; + use procmacros::handler; + + use super::private::Instance; + + static TX_WAKER: AtomicWaker = AtomicWaker::new(); + + pub(crate) struct LcdDoneFuture {} + + impl LcdDoneFuture { + pub(crate) fn new() -> Self { + Self {} + } + } + + impl core::future::Future for LcdDoneFuture { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + TX_WAKER.register(cx.waker()); + if Instance::is_lcd_done_set() { + Instance::clear_lcd_done(); + Poll::Ready(()) + } else { + Instance::listen_lcd_done(); + Poll::Pending + } + } + } + + impl Drop for LcdDoneFuture { + fn drop(&mut self) { + Instance::unlisten_lcd_done(); + } + } + + #[handler] + pub(crate) fn interrupt_handler() { + // TODO: this is a shared interrupt with Camera and here we ignore that! + if Instance::is_lcd_done_set() { + Instance::unlisten_lcd_done(); + TX_WAKER.wake() + } + } +} + mod private { + #[cfg(feature = "async")] + pub(crate) struct Instance; + + // NOTE: the LCD_CAM interrupt registers are shared between LCD and Camera and + // this is only implemented for the LCD side, when the Camera is implemented a + // CriticalSection will be needed to protect these shared registers. + #[cfg(feature = "async")] + impl Instance { + pub(crate) fn listen_lcd_done() { + let lcd_cam = unsafe { crate::peripherals::LCD_CAM::steal() }; + lcd_cam + .lc_dma_int_ena() + .modify(|_, w| w.lcd_trans_done_int_ena().set_bit()); + } + + pub(crate) fn unlisten_lcd_done() { + let lcd_cam = unsafe { crate::peripherals::LCD_CAM::steal() }; + lcd_cam + .lc_dma_int_ena() + .modify(|_, w| w.lcd_trans_done_int_ena().clear_bit()); + } + + pub(crate) fn is_lcd_done_set() -> bool { + let lcd_cam = unsafe { crate::peripherals::LCD_CAM::steal() }; + lcd_cam + .lc_dma_int_raw() + .read() + .lcd_trans_done_int_raw() + .bit() + } + + pub(crate) fn clear_lcd_done() { + let lcd_cam = unsafe { crate::peripherals::LCD_CAM::steal() }; + lcd_cam + .lc_dma_int_clr() + .write(|w| w.lcd_trans_done_int_clr().set_bit()); + } + } pub struct ClockDivider { // Integral LCD clock divider value. (8 bits) // Value 0 is treated as 256 diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 02c1a7c76d3..c7047cfbbcf 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -56,6 +56,14 @@ harness = false name = "i2s_async" harness = false +[[test]] +name = "lcd_cam_i8080" +harness = false + +[[test]] +name = "lcd_cam_i8080_async" +harness = false + [[test]] name = "spi_full_duplex" harness = false diff --git a/hil-test/tests/lcd_cam_i8080.rs b/hil-test/tests/lcd_cam_i8080.rs new file mode 100644 index 00000000000..ea8ab112385 --- /dev/null +++ b/hil-test/tests/lcd_cam_i8080.rs @@ -0,0 +1,129 @@ +//! lcd_cam i8080 tests + +//% CHIPS: esp32s3 + +#![no_std] +#![no_main] + +use defmt_rtt as _; +use esp_backtrace as _; +use esp_hal::{ + clock::{ClockControl, Clocks}, + dma::{Dma, DmaDescriptor, DmaPriority}, + dma_buffers, + gpio::dummy_pin::DummyPin, + lcd_cam::{ + lcd::{ + i8080, + i8080::{Command, TxEightBits, I8080}, + }, + LcdCam, + }, + peripherals::Peripherals, + prelude::*, + system::SystemControl, +}; + +const DATA_SIZE: usize = 1024 * 10; + +struct Context<'d> { + lcd_cam: LcdCam<'d, esp_hal::Blocking>, + clocks: Clocks<'d>, + dma: Dma<'d>, + tx_buffer: &'static [u8], + tx_descriptors: &'static mut [DmaDescriptor], +} + +impl<'d> Context<'d> { + pub fn init() -> Self { + let peripherals = Peripherals::take(); + let system = SystemControl::new(peripherals.SYSTEM); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + let dma = Dma::new(peripherals.DMA); + let lcd_cam = LcdCam::new(peripherals.LCD_CAM); + let (tx_buffer, tx_descriptors, _, _) = dma_buffers!(DATA_SIZE, 0); + + Self { + lcd_cam, + clocks, + dma, + tx_buffer, + tx_descriptors, + } + } +} + +#[cfg(test)] +#[embedded_test::tests] +mod tests { + use super::*; + + #[init] + fn init() -> Context<'static> { + Context::init() + } + + #[test] + fn test_i8080_8bit(ctx: Context<'static>) { + let channel = ctx.dma.channel0.configure(false, DmaPriority::Priority0); + + let pins = TxEightBits::new( + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + ); + + let mut i8080 = I8080::new( + ctx.lcd_cam.lcd, + channel.tx, + ctx.tx_descriptors, + pins, + 20.MHz(), + i8080::Config::default(), + &ctx.clocks, + ); + + let xfer = i8080 + .send_dma(Command::::None, 0, &ctx.tx_buffer) + .unwrap(); + xfer.wait().unwrap(); + } + + #[test] + fn test_i8080_8bit_async_channel(ctx: Context<'static>) { + let channel = ctx + .dma + .channel0 + .configure_for_async(false, DmaPriority::Priority0); + let pins = TxEightBits::new( + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + ); + + let mut i8080 = I8080::new( + ctx.lcd_cam.lcd, + channel.tx, + ctx.tx_descriptors, + pins, + 20.MHz(), + i8080::Config::default(), + &ctx.clocks, + ); + + let xfer = i8080 + .send_dma(Command::::None, 0, &ctx.tx_buffer) + .unwrap(); + xfer.wait().unwrap(); + } +} diff --git a/hil-test/tests/lcd_cam_i8080_async.rs b/hil-test/tests/lcd_cam_i8080_async.rs new file mode 100644 index 00000000000..4d8432660d2 --- /dev/null +++ b/hil-test/tests/lcd_cam_i8080_async.rs @@ -0,0 +1,128 @@ +//! lcd_cam i8080 tests + +//% CHIPS: esp32s3 + +#![no_std] +#![no_main] + +use defmt_rtt as _; +use esp_backtrace as _; +use esp_hal::{ + clock::{ClockControl, Clocks}, + dma::{Dma, DmaDescriptor, DmaPriority}, + dma_buffers, + gpio::dummy_pin::DummyPin, + lcd_cam::{ + lcd::{ + i8080, + i8080::{Command, TxEightBits, I8080}, + }, + LcdCam, + }, + peripherals::Peripherals, + prelude::*, + system::SystemControl, +}; + +const DATA_SIZE: usize = 1024 * 10; + +struct Context<'d> { + lcd_cam: LcdCam<'d, esp_hal::Async>, + clocks: Clocks<'d>, + dma: Dma<'d>, + tx_buffer: &'static [u8], + tx_descriptors: &'static mut [DmaDescriptor], +} + +impl<'d> Context<'d> { + pub fn init() -> Self { + let peripherals = Peripherals::take(); + let system = SystemControl::new(peripherals.SYSTEM); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + let dma = Dma::new(peripherals.DMA); + let lcd_cam = LcdCam::new_async(peripherals.LCD_CAM); + let (tx_buffer, tx_descriptors, _, _) = dma_buffers!(DATA_SIZE, 0); + + Self { + lcd_cam, + clocks, + dma, + tx_buffer, + tx_descriptors, + } + } +} + +#[cfg(test)] +#[embedded_test::tests(executor = esp_hal_embassy::Executor::new())] +mod tests { + use super::*; + + #[init] + async fn init() -> Context<'static> { + Context::init() + } + + #[test] + async fn test_i8080_8bit(ctx: Context<'static>) { + let channel = ctx.dma.channel0.configure(false, DmaPriority::Priority0); + let pins = TxEightBits::new( + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + ); + + let mut i8080 = I8080::new( + ctx.lcd_cam.lcd, + channel.tx, + ctx.tx_descriptors, + pins, + 20.MHz(), + i8080::Config::default(), + &ctx.clocks, + ); + + i8080 + .send_dma_async(Command::::None, 0, &ctx.tx_buffer) + .await + .unwrap(); + } + + #[test] + async fn test_i8080_8bit_async_channel(ctx: Context<'static>) { + let channel = ctx + .dma + .channel0 + .configure_for_async(false, DmaPriority::Priority0); + let pins = TxEightBits::new( + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + DummyPin::new(), + ); + + let mut i8080 = I8080::new( + ctx.lcd_cam.lcd, + channel.tx, + ctx.tx_descriptors, + pins, + 20.MHz(), + i8080::Config::default(), + &ctx.clocks, + ); + + i8080 + .send_dma_async(Command::::None, 0, &ctx.tx_buffer) + .await + .unwrap(); + } +}