use display_types::cea861::hdmi_forum::HdmiForumFrl;
use hdmi_hal::phy::HdmiPhy;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::scdc::ScdcClient;
use crate::trace::TrainingEvent;
use crate::types::{FfeLevels, FrlConfig, LtpReq, TrainingStatus};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrainingOutcome {
Success {
achieved_rate: HdmiForumFrl,
},
FallbackRequired,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrainingError<ScdcErr, PhyErr> {
Scdc(ScdcErr),
Phy(PhyErr),
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TrainingConfig {
pub ffe_levels: FfeLevels,
pub dsc_frl_max: bool,
pub flt_ready_timeout: u32,
pub frl_start_timeout: u32,
pub ltp_timeout: u32,
}
impl Default for TrainingConfig {
fn default() -> Self {
Self {
ffe_levels: FfeLevels::Ffe0,
dsc_frl_max: false,
flt_ready_timeout: 1000,
frl_start_timeout: 1000,
ltp_timeout: 1000,
}
}
}
pub struct FrlTrainer<C, P> {
scdc: C,
phy: P,
}
impl<C: ScdcClient, P: HdmiPhy> FrlTrainer<C, P> {
pub fn new(scdc: C, phy: P) -> Self {
Self { scdc, phy }
}
pub fn into_parts(self) -> (C, P) {
(self.scdc, self.phy)
}
pub fn train_at_rate(
&mut self,
rate: HdmiForumFrl,
config: &TrainingConfig,
) -> Result<TrainingOutcome, TrainingError<C::Error, P::Error>> {
self.train_inner(rate, config, &mut |_| {})
}
#[cfg(feature = "alloc")]
pub fn train_at_rate_traced(
&mut self,
rate: HdmiForumFrl,
config: &TrainingConfig,
) -> Result<(TrainingOutcome, crate::trace::TrainingTrace), TrainingError<C::Error, P::Error>>
{
let mut events = Vec::new();
let outcome = self.train_inner(rate, config, &mut |e| events.push(e))?;
Ok((
outcome,
crate::trace::TrainingTrace {
rate,
config: *config,
events,
},
))
}
fn poll_until<F>(
&mut self,
timeout: u32,
condition: impl Fn(&TrainingStatus) -> bool,
on_success: impl FnOnce(u32) -> TrainingEvent,
on_timeout: impl FnOnce(u32) -> TrainingEvent,
record: &mut F,
) -> Result<Option<TrainingOutcome>, TrainingError<C::Error, P::Error>>
where
F: FnMut(TrainingEvent),
{
let mut i = 0u32;
loop {
let status = self
.scdc
.read_training_status()
.map_err(TrainingError::Scdc)?;
if condition(&status) {
record(on_success(i));
return Ok(None);
}
i += 1;
if i >= timeout {
record(on_timeout(i));
return Ok(Some(TrainingOutcome::FallbackRequired));
}
}
}
fn train_inner<F>(
&mut self,
rate: HdmiForumFrl,
config: &TrainingConfig,
record: &mut F,
) -> Result<TrainingOutcome, TrainingError<C::Error, P::Error>>
where
F: FnMut(TrainingEvent),
{
self.scdc
.write_frl_config(FrlConfig {
rate,
ffe_levels: config.ffe_levels,
dsc_frl_max: config.dsc_frl_max,
})
.map_err(TrainingError::Scdc)?;
self.phy.set_frl_rate(rate).map_err(TrainingError::Phy)?;
record(TrainingEvent::RateConfigured {
rate,
ffe_levels: config.ffe_levels,
});
if let Some(outcome) = self.poll_until(
config.flt_ready_timeout,
|s| s.flt_ready,
|i| TrainingEvent::FltReadyReceived {
after_iterations: i,
},
|i| TrainingEvent::FltReadyTimeout {
iterations_elapsed: i,
},
record,
)? {
return Ok(outcome);
}
if let Some(outcome) = self.poll_until(
config.frl_start_timeout,
|s| s.frl_start,
|i| TrainingEvent::FrlStartReceived {
after_iterations: i,
},
|i| TrainingEvent::FrlStartTimeout {
iterations_elapsed: i,
},
record,
)? {
return Ok(outcome);
}
self.ltp_loop(rate, config, record)
}
fn ltp_loop<F>(
&mut self,
rate: HdmiForumFrl,
config: &TrainingConfig,
record: &mut F,
) -> Result<TrainingOutcome, TrainingError<C::Error, P::Error>>
where
F: FnMut(TrainingEvent),
{
let mut i = 0u32;
let mut last_ltp: Option<LtpReq> = None;
loop {
let status = self
.scdc
.read_training_status()
.map_err(TrainingError::Scdc)?;
if status.ltp_req == LtpReq::None {
record(TrainingEvent::AllLanesSatisfied {
after_iterations: i,
});
return Ok(TrainingOutcome::Success {
achieved_rate: rate,
});
}
if Some(status.ltp_req) != last_ltp {
record(TrainingEvent::LtpPatternRequested {
pattern: status.ltp_req,
});
last_ltp = Some(status.ltp_req);
}
let _ced = self.scdc.read_ced().map_err(TrainingError::Scdc)?;
self.phy
.send_ltp(status.ltp_req.into())
.map_err(TrainingError::Phy)?;
i += 1;
if i >= config.ltp_timeout {
record(TrainingEvent::LtpLoopTimeout {
iterations_elapsed: i,
});
return Ok(TrainingOutcome::FallbackRequired);
}
}
}
}
#[cfg(test)]
mod sim;
#[cfg(test)]
mod tests;