#![cfg_attr(docsrs, procmacros::doc_replace)]
use core::{
marker::PhantomData,
mem::ManuallyDrop,
ops::{Deref, DerefMut},
};
use crate::{
Blocking,
DriverMode,
dma::{ChannelTx, DmaError, DmaPeripheral, DmaTxBuffer, PeripheralTxChannel, TxChannelFor},
gpio::{Level, OutputConfig, OutputSignal, interconnect::PeripheralOutput},
lcd_cam::{
BitOrder,
ByteOrder,
ClockError,
calculate_clkm,
lcd::{ClockMode, DelayMode, Lcd, Phase, Polarity},
},
pac,
peripherals::LCD_CAM,
soc::clocks::ClockTree,
system::{self, GenericPeripheralGuard},
time::Rate,
};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConfigError {
Clock(ClockError),
}
pub struct Dpi<'d, Dm: DriverMode> {
lcd_cam: LCD_CAM<'d>,
tx_channel: ChannelTx<Blocking, PeripheralTxChannel<LCD_CAM<'d>>>,
_guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>,
_mode: PhantomData<Dm>,
}
impl<'d, Dm> Dpi<'d, Dm>
where
Dm: DriverMode,
{
pub fn new(
lcd: Lcd<'d, Dm>,
channel: impl TxChannelFor<LCD_CAM<'d>>,
config: Config,
) -> Result<Self, ConfigError> {
let tx_channel = ChannelTx::new(channel.degrade());
let mut this = Self {
lcd_cam: lcd.lcd_cam,
tx_channel,
_guard: lcd._guard,
_mode: PhantomData,
};
this.apply_config(&config)?;
Ok(this)
}
fn regs(&self) -> &pac::lcd_cam::RegisterBlock {
self.lcd_cam.register_block()
}
pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> {
let (i, divider) = ClockTree::with(|clocks| {
calculate_clkm(
(config.frequency.as_hz() * 2) as _,
&[
crate::soc::clocks::xtal_clk_frequency(clocks) as usize,
crate::soc::clocks::pll_d2_frequency(clocks) as usize,
crate::soc::clocks::crypto_pwm_clk_frequency(clocks) as usize,
],
)
})
.map_err(ConfigError::Clock)?;
self.regs().lcd_clock().write(|w| unsafe {
w.clk_en().set_bit();
w.lcd_clk_sel().bits((i + 1) as _);
w.lcd_clkm_div_num().bits(divider.div_num as _);
w.lcd_clkm_div_b().bits(divider.div_b as _);
w.lcd_clkm_div_a().bits(divider.div_a as _); w.lcd_clk_equ_sysclk().clear_bit();
w.lcd_clkcnt_n().bits(2 - 1); w.lcd_ck_idle_edge()
.bit(config.clock_mode.polarity == Polarity::IdleHigh);
w.lcd_ck_out_edge()
.bit(config.clock_mode.phase == Phase::ShiftHigh)
});
self.regs()
.lcd_user()
.modify(|_, w| w.lcd_reset().set_bit());
self.regs()
.lcd_rgb_yuv()
.write(|w| w.lcd_conv_bypass().clear_bit());
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);
w.lcd_cmd().clear_bit();
w.lcd_dummy().clear_bit();
w.lcd_dout().set_bit()
});
let timing = &config.timing;
self.regs().lcd_ctrl().modify(|_, w| unsafe {
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)
});
self.regs().lcd_misc().modify(|_, w| unsafe {
w.lcd_afifo_threshold_num().bits((1 << 5) - 1);
w.lcd_vfk_cyclelen().bits(0);
w.lcd_vbk_cyclelen().bits(0);
w.lcd_next_frame_en().clear_bit();
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)
});
self.regs()
.lcd_user()
.modify(|_, w| w.lcd_update().set_bit());
Ok(())
}
pub fn with_vsync(self, pin: impl PeripheralOutput<'d>) -> Self {
let pin = pin.into();
pin.apply_output_config(&OutputConfig::default());
pin.set_output_enable(true);
OutputSignal::LCD_V_SYNC.connect_to(&pin);
self
}
pub fn with_hsync(self, pin: impl PeripheralOutput<'d>) -> Self {
let pin = pin.into();
pin.apply_output_config(&OutputConfig::default());
pin.set_output_enable(true);
OutputSignal::LCD_H_SYNC.connect_to(&pin);
self
}
pub fn with_de(self, pin: impl PeripheralOutput<'d>) -> Self {
let pin = pin.into();
pin.apply_output_config(&OutputConfig::default());
pin.set_output_enable(true);
OutputSignal::LCD_H_ENABLE.connect_to(&pin);
self
}
pub fn with_pclk(self, pin: impl PeripheralOutput<'d>) -> Self {
let pin = pin.into();
pin.apply_output_config(&OutputConfig::default());
pin.set_output_enable(true);
OutputSignal::LCD_PCLK.connect_to(&pin);
self
}
fn with_data_pin(self, signal: OutputSignal, pin: impl PeripheralOutput<'d>) -> Self {
let pin = pin.into();
pin.apply_output_config(&OutputConfig::default());
pin.set_output_enable(true);
signal.connect_to(&pin);
self
}
pub fn with_data0(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_0, pin)
}
pub fn with_data1(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_1, pin)
}
pub fn with_data2(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_2, pin)
}
pub fn with_data3(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_3, pin)
}
pub fn with_data4(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_4, pin)
}
pub fn with_data5(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_5, pin)
}
pub fn with_data6(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_6, pin)
}
pub fn with_data7(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_7, pin)
}
pub fn with_data8(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_8, pin)
}
pub fn with_data9(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_9, pin)
}
pub fn with_data10(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_10, pin)
}
pub fn with_data11(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_11, pin)
}
pub fn with_data12(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_12, pin)
}
pub fn with_data13(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_13, pin)
}
pub fn with_data14(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_14, pin)
}
pub fn with_data15(self, pin: impl PeripheralOutput<'d>) -> Self {
self.with_data_pin(OutputSignal::LCD_DATA_15, pin)
}
pub fn send<TX: DmaTxBuffer>(
mut self,
next_frame_en: bool,
mut buf: TX,
) -> Result<DpiTransfer<'d, TX, Dm>, (DmaError, Self, TX)> {
let result = unsafe {
self.tx_channel
.prepare_transfer(DmaPeripheral::LcdCam, &mut buf)
}
.and_then(|_| self.tx_channel.start_transfer());
if let Err(err) = result {
return Err((err, self, buf));
}
self.regs()
.lcd_user()
.modify(|_, w| w.lcd_reset().set_bit());
self.regs()
.lcd_misc()
.modify(|_, w| w.lcd_afifo_reset().set_bit());
self.regs().lcd_misc().modify(|_, w| {
w.lcd_next_frame_en().bit(next_frame_en)
});
self.regs().lcd_user().modify(|_, w| {
w.lcd_update().set_bit();
w.lcd_start().set_bit()
});
Ok(DpiTransfer {
dpi: ManuallyDrop::new(self),
buffer_view: ManuallyDrop::new(buf.into_view()),
})
}
}
pub struct DpiTransfer<'d, BUF: DmaTxBuffer, Dm: DriverMode> {
dpi: ManuallyDrop<Dpi<'d, Dm>>,
buffer_view: ManuallyDrop<BUF::View>,
}
impl<'d, BUF: DmaTxBuffer, Dm: DriverMode> DpiTransfer<'d, BUF, Dm> {
pub fn is_done(&self) -> bool {
self.dpi.regs().lcd_user().read().lcd_start().bit_is_clear()
}
pub fn stop(mut self) -> (Dpi<'d, Dm>, BUF::Final) {
self.stop_peripherals();
let (dpi, view) = self.release();
(dpi, BUF::from_view(view))
}
pub fn wait(mut self) -> (Result<(), DmaError>, Dpi<'d, Dm>, BUF::Final) {
while !self.is_done() {
core::hint::spin_loop();
}
self.dpi.tx_channel.stop_transfer();
let (dpi, view) = self.release();
let result = if dpi.tx_channel.has_error() {
Err(DmaError::DescriptorError)
} else {
Ok(())
};
(result, dpi, BUF::from_view(view))
}
fn release(mut self) -> (Dpi<'d, Dm>, BUF::View) {
let result = unsafe {
let dpi = ManuallyDrop::take(&mut self.dpi);
let view = ManuallyDrop::take(&mut self.buffer_view);
(dpi, view)
};
core::mem::forget(self);
result
}
fn stop_peripherals(&mut self) {
self.dpi
.regs()
.lcd_user()
.modify(|_, w| w.lcd_start().clear_bit());
self.dpi.tx_channel.stop_transfer();
}
}
impl<BUF: DmaTxBuffer, Dm: DriverMode> Deref for DpiTransfer<'_, BUF, Dm> {
type Target = BUF::View;
fn deref(&self) -> &Self::Target {
&self.buffer_view
}
}
impl<BUF: DmaTxBuffer, Dm: DriverMode> DerefMut for DpiTransfer<'_, BUF, Dm> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer_view
}
}
impl<BUF: DmaTxBuffer, Dm: DriverMode> Drop for DpiTransfer<'_, BUF, Dm> {
fn drop(&mut self) {
self.stop_peripherals();
let view = unsafe {
ManuallyDrop::drop(&mut self.dpi);
ManuallyDrop::take(&mut self.buffer_view)
};
let _ = BUF::from_view(view);
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Config {
clock_mode: ClockMode,
frequency: Rate,
format: Format,
timing: FrameTiming,
vsync_idle_level: Level,
hsync_idle_level: Level,
de_idle_level: Level,
hs_blank_en: bool,
disable_black_region: bool,
de_mode: DelayMode,
hsync_mode: DelayMode,
vsync_mode: DelayMode,
output_bit_mode: DelayMode,
}
impl Default for Config {
fn default() -> Self {
Config {
clock_mode: Default::default(),
frequency: Rate::from_mhz(1),
format: Default::default(),
timing: Default::default(),
vsync_idle_level: Level::Low,
hsync_idle_level: Level::Low,
de_idle_level: Level::Low,
hs_blank_en: true,
disable_black_region: false,
de_mode: Default::default(),
hsync_mode: Default::default(),
vsync_mode: Default::default(),
output_bit_mode: Default::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Format {
pub bit_order: BitOrder,
pub byte_order: ByteOrder,
pub enable_2byte_mode: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct FrameTiming {
pub horizontal_total_width: usize,
pub horizontal_blank_front_porch: usize,
pub horizontal_active_width: usize,
pub vertical_total_height: usize,
pub vertical_blank_front_porch: usize,
pub vertical_active_height: usize,
pub vsync_width: usize,
pub hsync_width: usize,
pub hsync_position: usize,
}