Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement async for lcd_cam i8080 #1834

Merged
merged 14 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
51 changes: 42 additions & 9 deletions esp-hal/src/lcd_cam/lcd/i8080.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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<DM>,
}

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<u16>,
{
pub fn new(
lcd: Lcd<'d>,
lcd: Lcd<'d, DM>,
mut channel: ChannelTx<'d, CH>,
descriptors: &'static mut [DmaDescriptor],
mut pins: P,
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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<u16>,
{
Expand Down Expand Up @@ -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<u16>,
{
pub async fn send_dma_async<'t, TXBUF>(
&'t mut self,
cmd: impl Into<Command<P::Word>>,
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::<P::Word>())?;
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<T: Copy + Into<u16>>(&mut self, cmd: Command<T>, dummy: u8) {
// Reset LCD control unit and Async Tx FIFO
self.lcd_cam
Expand Down Expand Up @@ -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()
}
Expand Down
3 changes: 2 additions & 1 deletion esp-hal/src/lcd_cam/lcd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DM>,
}

#[derive(Debug, Clone, Copy, PartialEq, Default)]
Expand Down
153 changes: 148 additions & 5 deletions esp-hal/src/lcd_cam/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<P = LCD_CAM> + 'd) -> Self {
crate::into_ref!(lcd_cam);

Expand All @@ -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<P = LCD_CAM> + '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() },
Expand Down Expand Up @@ -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<Self::Output> {
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
Expand Down
8 changes: 8 additions & 0 deletions hil-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading