use core::{
marker::PhantomData,
pin::Pin,
sync::atomic,
task::{Context, Poll},
};
use esp_sync::NonReentrantMutex;
use crate::{
asynch::AtomicWaker,
dma::aligned::InternalMemory,
interrupt,
mipi_dsi::{
ConfigError,
MipiDsi,
vdma::{VdmaChannel, VdmaLinkItem},
},
peripherals::{Interrupt, MIPI_DSI_BRIDGE, MIPI_DSI_HOST, VDMA},
soc::clocks::{ClockTree, MipiDsiDpiClkConfig, MipiDsiInstance},
system::Cpu,
};
static VDMA_ISR_CHANNEL: atomic::AtomicU8 = atomic::AtomicU8::new(0);
const MAX_FBS: usize = 3;
const NUM_LLIS: usize = 2;
const DMA_BURST_LEN: u32 = 256;
const FIFO_EMPTY_THRESHOLD: u32 = 1024 - DMA_BURST_LEN;
static LLI_STORAGE: NonReentrantMutex<InternalMemory<[VdmaLinkItem; NUM_LLIS]>> =
NonReentrantMutex::new(InternalMemory::new(
[const { VdmaLinkItem::zeroed() }; NUM_LLIS],
));
pub use crate::soc::clocks::MipiDsiDpiClkSclk as DpiClockSource;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ColorFormat {
Rgb888,
Rgb565,
}
impl ColorFormat {
pub(crate) fn bits_per_pixel(self) -> u32 {
match self {
Self::Rgb888 => 24,
Self::Rgb565 => 16,
}
}
fn raw_type(self) -> u8 {
match self {
Self::Rgb888 => 0,
Self::Rgb565 => 2,
}
}
fn dpi_type(self) -> u8 {
match self {
Self::Rgb888 => 0,
Self::Rgb565 => 2,
}
}
fn host_color_coding(self) -> u8 {
match self {
Self::Rgb888 => 5, Self::Rgb565 => 0, }
}
}
#[derive(Clone, Copy, Debug)]
pub struct FrameTiming {
pub h_active: u32,
pub hsw: u32,
pub hbp: u32,
pub hfp: u32,
pub v_active: u32,
pub vsw: u32,
pub vbp: u32,
pub vfp: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct DpiConfig {
pub virtual_channel: u8,
pub pixel_clock_mhz: f32,
pub dpi_clk_src: DpiClockSource,
pub in_color_format: ColorFormat,
pub out_color_format: ColorFormat,
pub timing: FrameTiming,
}
#[crate::ram]
#[crate::handler]
fn vdma_block_done_isr() {
let channel_id = VDMA_ISR_CHANNEL.load(atomic::Ordering::Relaxed) as usize;
let ch = VDMA::regs().ch(channel_id);
ch.intclear0().write(|w| unsafe { w.bits(0xFFFFFFFF) });
LLI_STORAGE.with(|storage| {
let mut storage = storage.get_mut();
for lli in storage.iter() {
lli.rearm();
}
storage.writeback();
});
}
static VSYNC_WAKER: AtomicWaker = AtomicWaker::new();
#[crate::handler]
fn dsi_bridge_isr() {
let bridge = MIPI_DSI_BRIDGE::regs();
let st = bridge.int_st().read();
if st.vsync().bit_is_set() {
bridge.int_ena().modify(|_, w| w.vsync().clear_bit());
VSYNC_WAKER.wake();
}
}
pub struct DsiDpi<'d> {
_guard: crate::mipi_dsi::DphyGuard<'d>,
fb_ptrs: [*mut u8; MAX_FBS],
fb_size: usize,
num_fbs: usize,
current_fb: usize,
_phantom: PhantomData<&'d mut [u8]>,
}
impl Drop for DsiDpi<'_> {
fn drop(&mut self) {
let channel_id = self._guard.vdma_channel_id as usize;
let ch = VDMA::regs().ch(channel_id);
unsafe {
ch.intsignal_enable0().write_with_zero(|w| w);
ch.intstatus_enable0().write_with_zero(|w| w);
}
interrupt::disable(Cpu::current(), Interrupt::DMA);
let shift = channel_id as u32;
let val: u32 = 0x0100 << shift; unsafe { VDMA::regs().chen0().write(|w| w.bits(val)) };
MIPI_DSI_BRIDGE::regs()
.dpi_misc_config()
.modify(|_, w| w.dpi_en().clear_bit());
ClockTree::with(|clocks| MipiDsiInstance::MipiDsi.release_dpi_clk(clocks));
}
}
impl<'d> DsiDpi<'d> {
pub(crate) fn new(
bus: MipiDsi<'d>,
config: DpiConfig,
framebuffers: &[&'d mut [u8]],
) -> Result<Self, ConfigError> {
let num_fbs = framebuffers.len();
debug_assert!((1..=MAX_FBS).contains(&num_fbs));
let fb_size = framebuffers[0].len();
let (_dpi_clk_config, real_dpi_mhz) = ClockTree::with(|clocks| {
let src_hz = MipiDsiInstance::dpi_clk_config_frequency(
clocks,
MipiDsiDpiClkConfig::new(config.dpi_clk_src, 0),
);
let src_mhz = src_hz as f32 / 1_000_000.0;
let div = ((src_mhz / config.pixel_clock_mhz) + 0.5) as u32;
let div = div.max(1);
let cfg = MipiDsiDpiClkConfig::new(config.dpi_clk_src, div - 1);
MipiDsiInstance::MipiDsi.configure_dpi_clk(clocks, cfg);
MipiDsiInstance::MipiDsi.request_dpi_clk(clocks);
(cfg, src_mhz / div as f32)
});
let host = MIPI_DSI_HOST::regs();
let bridge = MIPI_DSI_BRIDGE::regs();
host.dpi_vcid()
.modify(|_, w| unsafe { w.dpi_vcid().bits(config.virtual_channel) });
host.dpi_color_coding().modify(|_, w| unsafe {
w.dpi_color_coding()
.bits(config.out_color_format.host_color_coding())
});
host.dpi_cfg_pol().write(|w| unsafe { w.bits(0) });
host.vid_mode_cfg().modify(|_, w| unsafe {
w.vid_mode_type().bits(2); w.lp_vsa_en().set_bit();
w.lp_vbp_en().set_bit();
w.lp_vfp_en().set_bit();
w.lp_hbp_en().set_bit();
w.lp_hfp_en().set_bit();
w.lp_cmd_en().set_bit()
});
let t = &config.timing;
host.vid_pkt_size()
.modify(|_, w| unsafe { w.vid_pkt_size().bits(t.h_active as u16) });
host.vid_num_chunks()
.modify(|_, w| unsafe { w.vid_num_chunks().bits(0) });
host.vid_null_size()
.modify(|_, w| unsafe { w.vid_null_size().bits(0) });
let ratio = bus.lane_bit_rate_mbps / config.pixel_clock_mhz / 8.0;
let htotal = t.hsw + t.hbp + t.h_active + t.hfp;
let host_hsw = fround_u32(t.hsw as f32 * ratio);
let host_hbp = fround_u32(t.hbp as f32 * ratio);
let host_act = fround_u32(t.h_active as f32 * ratio);
let host_hfp = fround_u32(t.hfp as f32 * ratio);
let host_htotal = fround_u32(htotal as f32 * ratio);
let comp = host_htotal as i32 - (host_hsw + host_hbp + host_act + host_hfp) as i32;
let host_act = (host_act as i32 + comp).max(0) as u32;
host.vid_hsa_time()
.modify(|_, w| unsafe { w.vid_hsa_time().bits(host_hsw as u16) });
host.vid_hbp_time()
.modify(|_, w| unsafe { w.vid_hbp_time().bits(host_hbp as u16) });
host.vid_hline_time().modify(|_, w| unsafe {
w.vid_hline_time()
.bits((host_hsw + host_hbp + host_act + host_hfp) as u16)
});
host.vid_vsa_lines()
.modify(|_, w| unsafe { w.vsa_lines().bits(t.vsw as u16) });
host.vid_vbp_lines()
.modify(|_, w| unsafe { w.vbp_lines().bits(t.vbp as u16) });
host.vid_vactive_lines()
.modify(|_, w| unsafe { w.v_active_lines().bits(t.v_active as u16) });
host.vid_vfp_lines()
.modify(|_, w| unsafe { w.vfp_lines().bits(t.vfp as u16) });
let brg_hfp = {
let c = fround_u32(real_dpi_mhz / config.pixel_clock_mhz * htotal as f32) as i32
- htotal as i32;
(t.hfp as i32 + c).max(0) as u32
};
bridge.dpi_h_cfg0().modify(|_, w| unsafe {
w.htotal()
.bits((t.hsw + t.hbp + t.h_active + brg_hfp) as u16);
w.hdisp().bits(t.h_active as u16)
});
bridge.dpi_h_cfg1().modify(|_, w| unsafe {
w.hsync().bits(t.hsw as u16);
w.hbank().bits(t.hbp as u16)
});
bridge.dpi_v_cfg0().modify(|_, w| unsafe {
w.vtotal().bits((t.vsw + t.vbp + t.v_active + t.vfp) as u16);
w.vdisp().bits(t.v_active as u16)
});
bridge.dpi_v_cfg1().modify(|_, w| unsafe {
w.vsync().bits(t.vsw as u16);
w.vbank().bits(t.vbp as u16)
});
let total_bits = t.h_active * t.v_active * config.in_color_format.bits_per_pixel();
bridge.raw_num_cfg().modify(|_, w| unsafe {
w.raw_num_total().bits(total_bits.div_ceil(64));
w.unalign_64bit_en().bit(!total_bits.is_multiple_of(64));
w.raw_num_total_set().set_bit()
});
bridge
.dpi_misc_config()
.modify(|_, w| unsafe { w.fifo_underrun_discard_vcnt().bits(t.h_active as u16) });
bridge.pixel_type().modify(|_, w| unsafe {
w.raw_type().bits(config.in_color_format.raw_type());
w.dpi_type().bits(config.out_color_format.dpi_type());
w.data_in_type().clear_bit()
});
bridge.dma_flow_ctrl().modify(|_, w| unsafe {
w.dsi_dma_flow_controller().clear_bit();
w.dma_flow_multiblk_num().bits(1)
});
bridge
.dma_frame_interval()
.modify(|_, w| w.dma_multiblk_en().clear_bit());
bridge
.dma_req_cfg()
.modify(|_, w| unsafe { w.dma_burst_len().bits(DMA_BURST_LEN as u16) });
bridge.raw_buf_almost_empty_thrd().modify(|_, w| unsafe {
w.dsi_raw_buf_almost_empty_thrd()
.bits(FIFO_EMPTY_THRESHOLD as u16)
});
bridge.en().modify(|_, w| w.dsi_en().set_bit());
bridge
.dpi_config_update()
.write(|w| w.dpi_config_update().set_bit());
interrupt::bind_handler(Interrupt::DSI_BRIDGE, dsi_bridge_isr);
for fb in framebuffers.iter() {
unsafe {
crate::soc::cache_writeback_addr(fb.as_ptr() as u32, fb_size as u32);
}
}
let fb_ptrs = core::array::from_fn::<_, MAX_FBS, _>(|i| {
if i < num_fbs {
framebuffers[i].as_ptr().cast_mut()
} else {
core::ptr::null_mut::<u8>()
}
});
let vdma_channel_id = bus.guard.vdma_channel_id;
VDMA_ISR_CHANNEL.store(vdma_channel_id, atomic::Ordering::Relaxed);
LLI_STORAGE.with(|storage| {
let mut storage = storage.get_mut();
for i in 0..NUM_LLIS {
let next = &raw const storage[(i + 1) % NUM_LLIS];
storage[i].configure(fb_ptrs[0] as u32, fb_size, next);
}
storage.writeback();
VdmaChannel::new(vdma_channel_id).start(&storage[0]);
});
{
let ch = VDMA::regs().ch(vdma_channel_id as usize);
ch.intstatus_enable0()
.write(|w| w.ch1_enable_block_tfr_done_intstat().set_bit());
ch.intsignal_enable0()
.write(|w| w.ch1_enable_block_tfr_done_intsignal().set_bit());
}
interrupt::bind_handler(Interrupt::DMA, vdma_block_done_isr);
host.mode_cfg()
.modify(|_, w| w.cmd_video_mode().clear_bit());
bridge.dpi_misc_config().modify(|_, w| w.dpi_en().set_bit());
bridge
.dpi_config_update()
.write(|w| w.dpi_config_update().set_bit());
let MipiDsi { guard, .. } = bus;
Ok(Self {
_guard: guard,
fb_ptrs,
fb_size,
num_fbs,
current_fb: 0,
_phantom: PhantomData,
})
}
pub fn framebuffer_mut(&mut self) -> &mut [u8] {
let back = (self.current_fb + 1) % self.num_fbs;
unsafe { core::slice::from_raw_parts_mut(self.fb_ptrs[back], self.fb_size) }
}
pub fn commit(&mut self) {
let back = (self.current_fb + 1) % self.num_fbs;
unsafe {
crate::soc::cache_writeback_addr(self.fb_ptrs[back] as u32, self.fb_size as u32);
}
let src = self.fb_ptrs[back] as u32;
LLI_STORAGE.with(|storage| {
let storage = storage.get_mut();
for lli in storage.iter() {
lli.set_source(src);
}
});
self.current_fb = back;
}
pub fn wait_for_vsync(&mut self) {
let bridge = MIPI_DSI_BRIDGE::regs();
while !bridge.int_raw().read().vsync().bit_is_set() {}
bridge.int_clr().write(|w| w.vsync().clear_bit_by_one());
}
pub fn wait_for_vsync_async(&mut self) -> impl Future<Output = ()> {
VsyncFuture { _dpi: self }
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
struct VsyncFuture<'a, 'b> {
_dpi: &'a mut DsiDpi<'b>,
}
impl Future for VsyncFuture<'_, '_> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let bridge = MIPI_DSI_BRIDGE::regs();
if bridge.int_raw().read().vsync().bit_is_set() {
bridge.int_clr().write(|w| w.vsync().clear_bit_by_one());
Poll::Ready(())
} else {
VSYNC_WAKER.register(cx.waker());
bridge.int_ena().modify(|_, w| w.vsync().set_bit());
Poll::Pending
}
}
}
impl Drop for VsyncFuture<'_, '_> {
fn drop(&mut self) {
let bridge = MIPI_DSI_BRIDGE::regs();
bridge.int_ena().modify(|_, w| w.vsync().clear_bit());
}
}
#[inline]
fn fround_u32(x: f32) -> u32 {
(x + 0.5) as u32
}