#![cfg_attr(docsrs, procmacros::doc_replace)]
pub mod dbi;
pub mod dpi;
pub(crate) mod vdma;
use core::marker::PhantomData;
use dpi::DsiDpi;
use procmacros::BuilderLite;
pub use vdma::VdmaDmaChannel;
use crate::{
peripherals::{MIPI_DSI, MIPI_DSI_BRIDGE, MIPI_DSI_HOST, PMU},
rom,
soc::clocks::{
ClockTree,
MipiDsiInstance,
MipiDsiPhyCfgClkConfig,
MipiDsiPhyPllRefclkConfig,
MipiDsiPhyPllRefclkSclk,
},
system::{GenericPeripheralGuard, Peripheral},
};
const TIMEOUT_CLOCK_FREQ_MHZ: f32 = 10.0;
const ESCAPE_CLOCK_FREQ_MHZ: f32 = 18.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DataLanes {
_1 = 1,
_2 = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Config {
num_data_lanes: DataLanes,
lane_bit_rate_mbps: f32,
phy_pll_refclk: MipiDsiPhyPllRefclkConfig,
force_clock_lane_hs: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
num_data_lanes: DataLanes::_2,
lane_bit_rate_mbps: 500.0,
phy_pll_refclk: MipiDsiPhyPllRefclkConfig::new(MipiDsiPhyPllRefclkSclk::Xtal, 0),
force_clock_lane_hs: false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ConfigError {
InvalidLaneBitRate,
PllNoSolution,
}
pub(crate) struct DphyGuard<'d> {
pub(crate) _dsi: MIPI_DSI<'d>,
pub(crate) vdma_channel_id: u8,
pub(crate) _dsi_guard: GenericPeripheralGuard<{ Peripheral::MipiDsi as u8 }>,
pub(crate) _vdma_guard: GenericPeripheralGuard<{ Peripheral::Vdma as u8 }>,
pub(crate) _phantom: PhantomData<&'d mut ()>,
}
impl Drop for DphyGuard<'_> {
fn drop(&mut self) {
dphy_power_down();
ClockTree::with(|clocks| {
MipiDsiInstance::MipiDsi.release_phy_pll_refclk(clocks);
MipiDsiInstance::MipiDsi.release_phy_cfg_clk(clocks);
});
}
}
pub struct MipiDsi<'d> {
pub(crate) guard: DphyGuard<'d>,
pub(crate) lane_bit_rate_mbps: f32,
}
impl<'d> MipiDsi<'d> {
pub fn new(
dsi: MIPI_DSI<'d>,
vdma_channel: impl VdmaDmaChannel + 'd,
config: Config,
) -> Result<Self, ConfigError> {
if config.lane_bit_rate_mbps < 80.0 || config.lane_bit_rate_mbps > 1500.0 {
return Err(ConfigError::InvalidLaneBitRate);
}
let vdma_channel_id = vdma_channel.channel_id();
dphy_ldo_power_on();
let dsi_guard: GenericPeripheralGuard<{ Peripheral::MipiDsi as u8 }> =
GenericPeripheralGuard::new();
let vdma_guard: GenericPeripheralGuard<{ Peripheral::Vdma as u8 }> =
GenericPeripheralGuard::new();
let ref_freq_hz = ClockTree::with(|clocks| {
MipiDsiInstance::MipiDsi.configure_phy_cfg_clk(clocks, MipiDsiPhyCfgClkConfig::PllF20m);
MipiDsiInstance::MipiDsi.request_phy_cfg_clk(clocks);
MipiDsiInstance::MipiDsi.configure_phy_pll_refclk(clocks, config.phy_pll_refclk);
MipiDsiInstance::MipiDsi.request_phy_pll_refclk(clocks);
MipiDsiInstance::phy_pll_refclk_config_frequency(clocks, config.phy_pll_refclk)
});
let ref_freq_mhz = ref_freq_hz as f32 / 1_000_000.0;
let dphy_guard = DphyGuard {
_dsi: dsi,
vdma_channel_id,
_dsi_guard: dsi_guard,
_vdma_guard: vdma_guard,
_phantom: PhantomData,
};
let host = MIPI_DSI_HOST::regs();
let bridge = MIPI_DSI_BRIDGE::regs();
host.phy_if_cfg()
.modify(|_, w| unsafe { w.n_lanes().bits(config.num_data_lanes as u8 - 1) });
host.pwr_up().modify(|_, w| w.shutdownz().set_bit());
host.phy_rstz().modify(|_, w| w.phy_shutdownz().set_bit());
host.phy_rstz().modify(|_, w| w.phy_rstz().clear_bit());
host.phy_rstz().modify(|_, w| w.phy_rstz().set_bit());
host.phy_rstz()
.modify(|_, w| w.phy_enableclk().set_bit().phy_forcepll().set_bit());
bridge.en().modify(|_, w| w.dsi_brig_rst().set_bit());
bridge.en().modify(|_, w| w.dsi_brig_rst().clear_bit());
let (pll_m, pll_n, actual_rate) = compute_pll(ref_freq_mhz, config.lane_bit_rate_mbps)
.ok_or(ConfigError::PllNoSolution)?;
let hs_freq_sel = hs_freq_range(actual_rate);
phy_write(0x44, hs_freq_sel << 1);
phy_write(0x19, 0x30); phy_write(0x17, pll_n - 1);
phy_write(0x18, ((pll_m - 1) & 0x1F) as u8);
phy_write(0x18, 0x80 | (((pll_m - 1) >> 5) & 0x0F) as u8);
while !host.phy_status().read().phy_lock().bit_is_set() {}
let lane_mask: u32 = {
let mut m = 1 << 2; if config.num_data_lanes as u8 >= 1 {
m |= 1 << 4;
}
if config.num_data_lanes as u8 >= 2 {
m |= 1 << 7;
}
m
};
while (host.phy_status().read().bits() & lane_mask) != lane_mask {}
host.mode_cfg().modify(|_, w| w.cmd_video_mode().set_bit());
if config.force_clock_lane_hs {
host.lpclk_ctrl().modify(|_, w| {
w.auto_clklane_ctrl().clear_bit();
w.phy_txrequestclkhs().set_bit()
});
} else {
host.lpclk_ctrl().modify(|_, w| {
w.auto_clklane_ctrl().set_bit();
w.phy_txrequestclkhs().set_bit()
});
}
host.phy_tmr_cfg().modify(|_, w| unsafe {
w.phy_hs2lp_time().bits(50);
w.phy_lp2hs_time().bits(104)
});
host.phy_tmr_lpclk_cfg().modify(|_, w| unsafe {
w.phy_clkhs2lp_time().bits(46);
w.phy_clklp2hs_time().bits(128)
});
host.pckhdl_cfg().modify(|_, w| {
w.crc_rx_en().set_bit();
w.ecc_rx_en().set_bit();
w.eotp_tx_en().set_bit()
});
let byte_clock_mhz = actual_rate / 8.0;
let to_div = fround_u8(byte_clock_mhz / TIMEOUT_CLOCK_FREQ_MHZ);
let esc_div = fround_u8(byte_clock_mhz / ESCAPE_CLOCK_FREQ_MHZ);
host.clkmgr_cfg().modify(|_, w| unsafe {
w.to_clk_division()
.bits(to_div)
.tx_esc_clk_division()
.bits(esc_div)
});
host.to_cnt_cfg().write(|w| unsafe { w.bits(0) });
host.hs_rd_to_cnt().write(|w| unsafe { w.bits(0) });
host.lp_rd_to_cnt().write(|w| unsafe { w.bits(0) });
host.hs_wr_to_cnt().write(|w| unsafe { w.bits(0) });
host.lp_wr_to_cnt().write(|w| unsafe { w.bits(0) });
host.bta_to_cnt().write(|w| unsafe { w.bits(0) });
host.phy_tmr_rd_cfg()
.modify(|_, w| unsafe { w.max_rd_time().bits(6000) });
host.phy_if_cfg()
.modify(|_, w| unsafe { w.phy_stop_wait_time().bits(0x3F) });
Ok(Self {
guard: dphy_guard,
lane_bit_rate_mbps: actual_rate,
})
}
pub fn dbi(&mut self, virtual_channel: u8) -> dbi::DsiDbi<'_, 'd> {
dbi::DsiDbi::new(self, virtual_channel)
}
pub fn dpi(
self,
config: dpi::DpiConfig,
framebuffers: &[&'d mut [u8]],
) -> Result<DsiDpi<'d>, ConfigError> {
DsiDpi::new(self, config, framebuffers)
}
}
pub(crate) fn dphy_ldo_power_on() {
PMU::regs()
.ext_ldo_p0_0p2a()
.write(|w| unsafe { w.bits(0x4020_0180) });
PMU::regs()
.ext_ldo_p0_0p2a_ana()
.write(|w| unsafe { w.bits((9u32 << 28) | (6u32 << 23)) });
rom::ets_delay_us(500);
}
pub(crate) fn dphy_power_down() {
let host = MIPI_DSI_HOST::regs();
host.phy_rstz().modify(|_, w| {
w.phy_rstz().clear_bit();
w.phy_shutdownz().clear_bit();
w.phy_enableclk().clear_bit()
});
host.pwr_up().modify(|_, w| w.shutdownz().clear_bit());
PMU::regs()
.ext_ldo_p0_0p2a()
.write(|w| unsafe { w.bits(0x4020_0000) });
}
const PHY_PLL_RANGES: &[(core::ops::Range<u16>, u8)] = &[
(80..90, 0x00),
(90..100, 0x10),
(100..110, 0x20),
(110..130, 0x01),
(130..140, 0x11),
(140..150, 0x21),
(150..170, 0x02),
(170..180, 0x12),
(180..200, 0x22),
(200..220, 0x03),
(220..240, 0x13),
(240..250, 0x23),
(250..270, 0x04),
(270..300, 0x14),
(300..330, 0x05),
(330..360, 0x15),
(360..400, 0x25),
(400..450, 0x06),
(450..500, 0x16),
(500..550, 0x07),
(550..600, 0x17),
(600..650, 0x08),
(650..700, 0x18),
(700..750, 0x09),
(750..800, 0x19),
(800..850, 0x29),
(850..900, 0x39),
(900..950, 0x0A),
(950..1000, 0x1A),
(1000..1050, 0x2A),
(1050..1100, 0x3A),
(1100..1150, 0x0B),
(1150..1200, 0x1B),
(1200..1250, 0x2B),
(1250..1300, 0x3B),
(1300..1350, 0x0C),
(1350..1400, 0x1C),
(1400..1450, 0x2C),
(1450..1501, 0x3C),
];
fn hs_freq_range(lane_mbps: f32) -> u8 {
let mbps = lane_mbps as u16;
PHY_PLL_RANGES
.iter()
.find(|(range, _)| range.contains(&mbps))
.map_or(0x00, |&(_, sel)| sel)
}
fn compute_pll(ref_freq_mhz: f32, target_mbps: f32) -> Option<(u16, u8, f32)> {
let min_n = ((ref_freq_mhz / 40.0) as u8).max(1);
let max_n = (ref_freq_mhz / 5.0) as u8;
let mut best_m: u16 = 0;
let mut best_n: u8 = 0;
let mut best_delta = f32::INFINITY;
for n in min_n..=max_n {
let m_raw = (target_mbps * n as f32 / ref_freq_mhz) as u16;
let m = m_raw & !1;
if m == 0 {
continue;
}
let actual = ref_freq_mhz * m as f32 / n as f32;
let delta = (target_mbps - actual).abs();
if delta < best_delta {
best_delta = delta;
best_m = m;
best_n = n;
if best_delta < 0.01 {
break;
}
}
}
if best_m == 0 || best_n == 0 {
return None;
}
let actual = ref_freq_mhz * best_m as f32 / best_n as f32;
Some((best_m, best_n, actual))
}
#[inline]
fn fround_u8(x: f32) -> u8 {
(x + 0.5) as u8
}
fn phy_write(addr: u8, val: u8) {
let h = MIPI_DSI_HOST::regs();
h.phy_tst_ctrl0().write(|w| {
w.phy_testclk().clear_bit();
w.phy_testclr().clear_bit()
});
h.phy_tst_ctrl1().write(|w| unsafe {
w.phy_testen().set_bit();
w.phy_testdin().bits(addr)
});
h.phy_tst_ctrl0().write(|w| {
w.phy_testclk().set_bit();
w.phy_testclr().clear_bit()
});
h.phy_tst_ctrl0().write(|w| {
w.phy_testclk().clear_bit();
w.phy_testclr().clear_bit()
});
h.phy_tst_ctrl1().write(|w| unsafe {
w.phy_testen().clear_bit();
w.phy_testdin().bits(val)
});
h.phy_tst_ctrl0().write(|w| {
w.phy_testclk().set_bit();
w.phy_testclr().clear_bit()
});
h.phy_tst_ctrl0().write(|w| {
w.phy_testclk().clear_bit();
w.phy_testclr().clear_bit()
});
}