Skip to content
Draft
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
4 changes: 2 additions & 2 deletions esp-hal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ For help getting started with this HAL, please refer to [The Rust on ESP Book] a

| Driver | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
| ----------------- |:-----:|:--------:|:--------:|:--------:|:--------:|:---------:|:--------:|:--------:|:--------:|:--------:|:---------:|
| Camera interface | ❌ | | | | | | | ❌ | ❌ | ⚒️ | |
| Camera interface | ❌ | | | | | | | ❌ | ❌ | ⚒️ | ⚒️ |
| MIPI-CSI | | | | | | | | ❌ | | | |
| MIPI-DSI | | | | | | | | ⚒️ | | | |
| RGB display | ⚒️ | | | | | | | ❌ | ❌ | ⚒️ | |
| RGB display | ⚒️ | | | | | | | ❌ | ❌ | ⚒️ | ⚒️ |
| SPI LCD interface | | | | | | | | | [❌][5374] [^1] | | |

### Signal processing
Expand Down
2 changes: 1 addition & 1 deletion esp-hal/src/dma/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ pub trait DmaChannel: Sized + crate::private::Sealed {
type Tx: DmaTxChannel + From<Self>;

/// Splits the DMA channel into its RX and TX halves.
#[cfg(any(esp32c5, esp32c6, esp32h2, esp32s3))] // TODO relax this to allow splitting on all chips
#[cfg(dma_separate_in_out_interrupts)] // TODO relax this to allow splitting on all chips
fn split(self) -> (Self::Rx, Self::Tx) {
// This function is exposed safely on chips that have separate IN and OUT
// interrupt handlers.
Expand Down
14 changes: 10 additions & 4 deletions esp-hal/src/lcd_cam/cam.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,15 @@ use crate::{
OutputSignal,
interconnect::{PeripheralInput, PeripheralOutput},
},
lcd_cam::{BitOrder, ByteOrder, CamDmaRxChannel, ClockError, ErasedRxChannel, calculate_clkm},
lcd_cam::{
BitOrder,
ByteOrder,
CamDmaRxChannel,
ClockError,
ErasedRxChannel,
calculate_clkm,
ll,
},
pac,
peripherals::LCD_CAM,
soc::clocks::{ClockTree, LcdCamCamClockConfig, LcdCamInstance},
Expand Down Expand Up @@ -240,9 +248,7 @@ impl<'d> Camera<'d> {
w.cam_vsync_inv().bit(config.invert_vsync)
});

self.regs()
.cam_rgb_yuv()
.write(|w| w.cam_conv_bypass().clear_bit());
ll::set_cam_conv_bypass(self.regs());

self.regs()
.cam_ctrl()
Expand Down
127 changes: 49 additions & 78 deletions esp-hal/src/lcd_cam/lcd/dpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ use crate::{
ErasedTxChannel,
LcdDmaTxChannel,
lcd::{ClockConfig, ClockMode, DelayMode, Lcd},
ll,
},
pac,
time::Rate,
Expand Down Expand Up @@ -165,34 +166,31 @@ where
self.lcd
.configure_clocks(&ClockConfig {
clock_mode: config.clock_mode,
// Due to https://www.espressif.com/sites/default/files/documentation/esp32-s3_errata_en.pdf
// the LCD_PCLK divider must be at least 2. To make up for this the user
// provided frequency is doubled to match.
frequency: config.frequency * 2,
// ESP32-S3 errata requires LCD_PCLK to divide LCD_CLK by at least 2.
// Double the requested frequency so the extra divider still matches.
frequency: if cfg!(esp32s3) {
config.frequency * 2
} else {
config.frequency
},
})
.map_err(ConfigError::Clock)?;

self.regs()
.lcd_user()
.modify(|_, w| w.lcd_reset().set_bit());

self.regs()
.lcd_rgb_yuv()
.write(|w| w.lcd_conv_bypass().clear_bit());
ll::set_lcd_conv_bypass(self.regs());

self.regs().lcd_user().modify(|_, w| {
if config.format.enable_2byte_mode {
w.lcd_8bits_order().bit(false);
w.lcd_byte_order()
.bit(config.format.byte_order == ByteOrder::Inverted);
} else {
w.lcd_8bits_order()
.bit(config.format.byte_order == ByteOrder::Inverted);
w.lcd_byte_order().bit(false);
}
w.lcd_bit_order()
.bit(config.format.bit_order == BitOrder::Inverted);
w.lcd_2byte_en().bit(config.format.enable_2byte_mode);

// Only valid in Intel8080 mode.
w.lcd_cmd().clear_bit();
Expand All @@ -201,42 +199,31 @@ where
// This needs to be explicitly set for RGB mode.
w.lcd_dout().set_bit()
});
ll::set_8bits_order(
self.regs(),
!config.format.enable_2byte_mode && config.format.byte_order == ByteOrder::Inverted,
);
ll::set_2byte_mode(self.regs(), config.format.enable_2byte_mode);

let timing = &config.timing;
self.regs().lcd_ctrl().modify(|_, w| unsafe {
// Enable RGB mode, and input VSYNC, HSYNC, and DE signals.
w.lcd_rgb_mode_en().set_bit();

w.lcd_hb_front()
.bits((timing.horizontal_blank_front_porch as u16).saturating_sub(1));
w.lcd_va_height()
.bits((timing.vertical_active_height as u16).saturating_sub(1));
w.lcd_vt_height()
.bits((timing.vertical_total_height as u16).saturating_sub(1))
});
self.regs().lcd_ctrl1().modify(|_, w| unsafe {
w.lcd_vb_front()
.bits((timing.vertical_blank_front_porch as u8).saturating_sub(1));
w.lcd_ha_width()
.bits((timing.horizontal_active_width as u16).saturating_sub(1));
w.lcd_ht_width()
.bits((timing.horizontal_total_width as u16).saturating_sub(1))
});
self.regs().lcd_ctrl2().modify(|_, w| unsafe {
w.lcd_vsync_width()
.bits((timing.vsync_width as u8).saturating_sub(1));
w.lcd_vsync_idle_pol().bit(config.vsync_idle_level.into());
w.lcd_de_idle_pol().bit(config.de_idle_level.into());
w.lcd_hs_blank_en().bit(config.hs_blank_en);
w.lcd_hsync_width()
.bits((timing.hsync_width as u8).saturating_sub(1));
w.lcd_hsync_idle_pol().bit(config.hsync_idle_level.into());
w.lcd_hsync_position().bits(timing.hsync_position as u8)
});

ll::configure_rgb_timing(
self.regs(),
(timing.horizontal_blank_front_porch as u16).saturating_sub(1),
(timing.vertical_active_height as u16).saturating_sub(1),
(timing.vertical_total_height as u16).saturating_sub(1),
(timing.vertical_blank_front_porch as u16).saturating_sub(1),
(timing.horizontal_active_width as u16).saturating_sub(1),
(timing.horizontal_total_width as u16).saturating_sub(1),
(timing.vsync_width as u16).saturating_sub(1),
config.vsync_idle_level.into(),
config.de_idle_level.into(),
config.hs_blank_en,
(timing.hsync_width as u8).saturating_sub(1),
config.hsync_idle_level.into(),
timing.hsync_position as u8,
);
self.regs().lcd_misc().modify(|_, w| unsafe {
// TODO: Find out what this field actually does.
// Set the threshold for Async Tx FIFO full event. (5 bits)
#[cfg(not(esp32s31))]
w.lcd_afifo_threshold_num().bits((1 << 5) - 1);

// Doesn't matter for RGB mode.
Expand All @@ -250,30 +237,13 @@ where
// Enable blank region when LCD sends data out.
w.lcd_bk_en().bit(!config.disable_black_region)
});
self.regs().lcd_dly_mode().modify(|_, w| unsafe {
w.lcd_de_mode().bits(config.de_mode as u8);
w.lcd_hsync_mode().bits(config.hsync_mode as u8);
w.lcd_vsync_mode().bits(config.vsync_mode as u8);
w
});
self.regs().lcd_data_dout_mode().modify(|_, w| unsafe {
w.dout0_mode().bits(config.output_bit_mode as u8);
w.dout1_mode().bits(config.output_bit_mode as u8);
w.dout2_mode().bits(config.output_bit_mode as u8);
w.dout3_mode().bits(config.output_bit_mode as u8);
w.dout4_mode().bits(config.output_bit_mode as u8);
w.dout5_mode().bits(config.output_bit_mode as u8);
w.dout6_mode().bits(config.output_bit_mode as u8);
w.dout7_mode().bits(config.output_bit_mode as u8);
w.dout8_mode().bits(config.output_bit_mode as u8);
w.dout9_mode().bits(config.output_bit_mode as u8);
w.dout10_mode().bits(config.output_bit_mode as u8);
w.dout11_mode().bits(config.output_bit_mode as u8);
w.dout12_mode().bits(config.output_bit_mode as u8);
w.dout13_mode().bits(config.output_bit_mode as u8);
w.dout14_mode().bits(config.output_bit_mode as u8);
w.dout15_mode().bits(config.output_bit_mode as u8)
});
ll::set_sync_delay(
self.regs(),
config.de_mode as u8,
config.hsync_mode as u8,
config.vsync_mode as u8,
);
ll::set_data_bit_delay(self.regs(), config.output_bit_mode as u8);

self.regs()
.lcd_user()
Expand Down Expand Up @@ -481,16 +451,8 @@ where
next_frame_en: bool,
mut buf: TX,
) -> Result<DpiTransfer<'d, TX, Dm>, (DmaError, Self, TX)> {
let result = unsafe {
self.tx_channel
.prepare_transfer(DmaPeripheral::LCD_CAM, &mut buf)
}
.and_then(|_| self.tx_channel.start_transfer());
if let Err(err) = result {
return Err((err, self, buf));
}

// Reset LCD control unit and Async Tx FIFO
// Reset before DMA start. AXI-GDMA can fill the LCD AFIFO immediately; a later
// FIFO reset would drop the first pixels of the frame.
self.regs()
.lcd_user()
.modify(|_, w| w.lcd_reset().set_bit());
Expand All @@ -504,6 +466,15 @@ where
w.lcd_next_frame_en().bit(next_frame_en)
});

let result = unsafe {
self.tx_channel
.prepare_transfer(DmaPeripheral::LCD_CAM, &mut buf)
}
.and_then(|_| self.tx_channel.start_transfer());
if let Err(err) = result {
return Err((err, self, buf));
}

// Start the transfer.
self.regs().lcd_user().modify(|_, w| {
w.lcd_update().set_bit();
Expand Down
67 changes: 20 additions & 47 deletions esp-hal/src/lcd_cam/lcd/i8080.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ use crate::{
Lcd,
LcdDmaTxChannel,
lcd::{ClockConfig, ClockMode, DelayMode},
ll,
},
pac,
time::Rate,
Expand Down Expand Up @@ -118,28 +119,27 @@ where
self.lcd
.configure_clocks(&ClockConfig {
clock_mode: config.clock_mode,
// Due to https://www.espressif.com/sites/default/files/documentation/esp32-s3_errata_en.pdf
// the LCD_PCLK divider must be at least 2. To make up for this the user
// provided frequency is doubled to match.
frequency: config.frequency * 2,
// ESP32-S3 errata requires LCD_PCLK to divide LCD_CLK by at least 2.
// Double the requested frequency so the extra divider still matches.
frequency: if cfg!(esp32s3) {
config.frequency * 2
} else {
config.frequency
},
})
.map_err(ConfigError::Clock)?;

self.regs()
.lcd_ctrl()
.write(|w| w.lcd_rgb_mode_en().clear_bit());
self.regs()
.lcd_rgb_yuv()
.write(|w| w.lcd_conv_bypass().clear_bit());
ll::set_rgb_mode_en(self.regs(), false);
ll::set_lcd_conv_bypass(self.regs());

self.regs().lcd_user().modify(|_, w| {
w.lcd_8bits_order().bit(false);
w.lcd_bit_order().bit(false);
w.lcd_byte_order().bit(false);
w.lcd_2byte_en().bit(false)
w.lcd_byte_order().bit(false)
});
ll::set_8bits_order(self.regs(), false);
ll::set_2byte_mode(self.regs(), false);
self.regs().lcd_misc().write(|w| unsafe {
// Set the threshold for Async Tx FIFO full event. (5 bits)
#[cfg(not(esp32s31))]
w.lcd_afifo_threshold_num().bits(0);
// Configure the setup cycles in LCD non-RGB mode. Setup cycles
// expected = this value + 1. (6 bit)
Expand Down Expand Up @@ -169,27 +169,8 @@ where
// The default value of LCD_CD
w.lcd_cd_idle_edge().bit(config.cd_idle_edge)
});
self.regs()
.lcd_dly_mode()
.write(|w| unsafe { w.lcd_cd_mode().bits(config.cd_mode as u8) });
self.regs().lcd_data_dout_mode().write(|w| unsafe {
w.dout0_mode().bits(config.output_bit_mode as u8);
w.dout1_mode().bits(config.output_bit_mode as u8);
w.dout2_mode().bits(config.output_bit_mode as u8);
w.dout3_mode().bits(config.output_bit_mode as u8);
w.dout4_mode().bits(config.output_bit_mode as u8);
w.dout5_mode().bits(config.output_bit_mode as u8);
w.dout6_mode().bits(config.output_bit_mode as u8);
w.dout7_mode().bits(config.output_bit_mode as u8);
w.dout8_mode().bits(config.output_bit_mode as u8);
w.dout9_mode().bits(config.output_bit_mode as u8);
w.dout10_mode().bits(config.output_bit_mode as u8);
w.dout11_mode().bits(config.output_bit_mode as u8);
w.dout12_mode().bits(config.output_bit_mode as u8);
w.dout13_mode().bits(config.output_bit_mode as u8);
w.dout14_mode().bits(config.output_bit_mode as u8);
w.dout15_mode().bits(config.output_bit_mode as u8)
});
ll::set_cd_delay(self.regs(), config.cd_mode as u8);
ll::set_data_bit_delay(self.regs(), config.output_bit_mode as u8);

self.regs()
.lcd_user()
Expand All @@ -214,9 +195,7 @@ where
/// mode.
pub fn set_8bits_order(&mut self, byte_order: ByteOrder) -> &mut Self {
let is_inverted = byte_order != ByteOrder::default();
self.regs()
.lcd_user()
.modify(|_, w| w.lcd_8bits_order().bit(is_inverted));
ll::set_8bits_order(self.regs(), is_inverted);
self
}

Expand Down Expand Up @@ -388,19 +367,14 @@ where
w.lcd_cmd().set_bit();
w.lcd_cmd_2_cycle_en().clear_bit()
});
self.regs()
.lcd_cmd_val()
.write(|w| unsafe { w.lcd_cmd_value().bits(value.into() as _) });
ll::write_command(self.regs(), value.into() as u32, None);
}
Command::Two(first, second) => {
self.regs().lcd_user().modify(|_, w| {
w.lcd_cmd().set_bit();
w.lcd_cmd_2_cycle_en().set_bit()
});
let cmd = first.into() as u32 | ((second.into() as u32) << 16);
self.regs()
.lcd_cmd_val()
.write(|w| unsafe { w.lcd_cmd_value().bits(cmd) });
ll::write_command(self.regs(), first.into() as u32, Some(second.into() as u32));
}
}

Expand All @@ -417,9 +391,8 @@ where
} else {
w.lcd_dummy().clear_bit()
}
.lcd_2byte_en()
.bit(is_2byte_mode)
});
ll::set_2byte_mode(self.regs(), is_2byte_mode);

// Use continous mode for DMA. FROM the S3 TRM:
// > In a continuous output, LCD module keeps sending data till:
Expand Down
10 changes: 8 additions & 2 deletions esp-hal/src/lcd_cam/lcd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,14 @@ impl<'d, Dm: DriverMode> Lcd<'d, Dm> {
self.regs().lcd_clock().write(|w| unsafe {
// Force enable the clock for all configuration registers.
w.clk_en().set_bit();
w.lcd_clk_equ_sysclk().clear_bit();
w.lcd_clkcnt_n().bits(2 - 1); // Must not be 0.
if cfg!(esp32s3) {
// ESP32-S3 errata: LCD_PCLK must divide LCD_CLK by at least 2.
w.lcd_clk_equ_sysclk().clear_bit();
w.lcd_clkcnt_n().bits(2 - 1); // Must not be 0.
} else {
// ESP32-S31: PCLK tracks LCD_CLK.
w.lcd_clk_equ_sysclk().set_bit();
}
w.lcd_ck_idle_edge()
.bit(config.clock_mode.polarity == Polarity::IdleHigh);
w.lcd_ck_out_edge()
Expand Down
Loading