use core::future::Future;
use core::pin::pin;
use core::task::{Context, Poll, Waker};
use display_types::cea861::hdmi_forum::HdmiForumFrl;
use hdmi_hal::phy::{EqParams, FrlOutput, HdmiPhy, LanePatterns};
use crate::lts::{self, TrainingIo};
use crate::scdc::ScdcClient;
use crate::trace::TrainingEvent;
use crate::types::{
ClearableUpdateFlags, FfeLevels, FrlConfig, LtpRequests, SourceTestConfig, UpdateFlags,
};
use crate::warning::Trained;
#[cfg(feature = "alloc")]
use crate::trace::TrainingTrace;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FallbackReason {
FltReadyTimeout,
TrainingTimeout,
FrlStartTimeout,
RatesExhausted,
RetrainsExhausted,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrainingOutcome {
#[non_exhaustive]
Success {
achieved_rate: HdmiForumFrl,
},
#[non_exhaustive]
FallbackRequired {
reason: FallbackReason,
},
#[non_exhaustive]
NoTimeoutHold {
rate: HdmiForumFrl,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrainingError<ScdcErr, PhyErr> {
#[non_exhaustive]
Scdc {
error: ScdcErr,
exit: TmdsExit<ScdcErr, PhyErr>,
},
#[non_exhaustive]
Phy {
error: PhyErr,
exit: TmdsExit<ScdcErr, PhyErr>,
},
#[non_exhaustive]
ExitFailed {
reason: FallbackReason,
error: ExitError<ScdcErr, PhyErr>,
},
#[non_exhaustive]
InvalidRates {
index: usize,
rate: HdmiForumFrl,
},
NoRates,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TmdsExit<ScdcErr, PhyErr> {
Exited,
Skipped,
HeldUnderTest,
Failed(ExitError<ScdcErr, PhyErr>),
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExitError<ScdcErr, PhyErr> {
pub scdc: Option<ScdcErr>,
pub phy: Option<PhyErr>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TrainingConfig {
pub ffe_levels: FfeLevels,
pub flt_ready_polls: u32,
pub ltp_polls: u32,
pub frl_start_polls: u32,
pub no_timeout_poll_cap: u32,
pub max_retrains: u32,
pub exit_to_tmds_on_error: bool,
}
impl Default for TrainingConfig {
fn default() -> Self {
Self {
ffe_levels: FfeLevels::new(3).expect("3 is a valid FFE level"),
flt_ready_polls: 50,
ltp_polls: 100,
frl_start_polls: 100,
no_timeout_poll_cap: 500,
max_retrains: 3,
exit_to_tmds_on_error: true,
}
}
}
type Error<C, P> = TrainingError<<C as ScdcClient>::Error, <P as HdmiPhy>::Error>;
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 scdc(&self) -> &C {
&self.scdc
}
pub fn scdc_mut(&mut self) -> &mut C {
&mut self.scdc
}
pub fn phy(&self) -> &P {
&self.phy
}
pub fn phy_mut(&mut self) -> &mut P {
&mut self.phy
}
pub fn exit_to_tmds(&mut self) -> Result<(), ExitError<C::Error, P::Error>> {
let mut io = SyncIo {
scdc: &mut self.scdc,
phy: &mut self.phy,
};
block_on(lts::exit_to_tmds(&mut io, &mut |_| {}))
}
pub fn train_at_rate(
&mut self,
rate: HdmiForumFrl,
config: &TrainingConfig,
) -> Result<Trained, Error<C, P>> {
self.train(&[rate], config)
}
pub fn train(
&mut self,
rates: &[HdmiForumFrl],
config: &TrainingConfig,
) -> Result<Trained, Error<C, P>> {
self.train_with_events(rates, config, &mut |_| {})
}
#[cfg(feature = "alloc")]
pub fn train_at_rate_traced(
&mut self,
rate: HdmiForumFrl,
config: &TrainingConfig,
) -> (Result<Trained, Error<C, P>>, TrainingTrace) {
self.train_traced(&[rate], config)
}
#[cfg(feature = "alloc")]
pub fn train_traced(
&mut self,
rates: &[HdmiForumFrl],
config: &TrainingConfig,
) -> (Result<Trained, Error<C, P>>, TrainingTrace) {
let mut events = Vec::new();
let result = self.train_with_events(rates, config, &mut |event| events.push(event));
(result, TrainingTrace::new(rates.to_vec(), *config, events))
}
pub fn train_with_events<F: FnMut(TrainingEvent)>(
&mut self,
rates: &[HdmiForumFrl],
config: &TrainingConfig,
record: &mut F,
) -> Result<Trained, Error<C, P>> {
let mut io = SyncIo {
scdc: &mut self.scdc,
phy: &mut self.phy,
};
block_on(lts::run(&mut io, rates, config, record))
}
}
struct SyncIo<'a, C, P> {
scdc: &'a mut C,
phy: &'a mut P,
}
impl<C: ScdcClient, P: HdmiPhy> TrainingIo for SyncIo<'_, C, P> {
type ScdcError = C::Error;
type PhyError = P::Error;
async fn read_flt_ready(&mut self) -> Result<bool, C::Error> {
self.scdc.read_flt_ready()
}
async fn read_update_flags(&mut self) -> Result<UpdateFlags, C::Error> {
self.scdc.read_update_flags()
}
async fn clear_update_flags(&mut self, flags: ClearableUpdateFlags) -> Result<(), C::Error> {
self.scdc.clear_update_flags(flags)
}
async fn read_ltp_requests(&mut self) -> Result<LtpRequests, C::Error> {
self.scdc.read_ltp_requests()
}
async fn read_source_test_config(&mut self) -> Result<SourceTestConfig, C::Error> {
self.scdc.read_source_test_config()
}
async fn write_config_0_defaults(&mut self) -> Result<(), C::Error> {
self.scdc.write_config_0_defaults()
}
async fn write_frl_config(&mut self, config: FrlConfig) -> Result<(), C::Error> {
self.scdc.write_frl_config(config)
}
async fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), P::Error> {
self.phy.set_frl_rate(rate)
}
async fn send_ltp(&mut self, patterns: LanePatterns) -> Result<(), P::Error> {
self.phy.send_ltp(patterns)
}
async fn set_frl_output(&mut self, output: FrlOutput) -> Result<(), P::Error> {
self.phy.set_frl_output(output)
}
async fn adjust_equalization(&mut self, params: EqParams) -> Result<(), P::Error> {
self.phy.adjust_equalization(params)
}
}
fn block_on<T>(future: impl Future<Output = T>) -> T {
let mut future = pin!(future);
ready(
future
.as_mut()
.poll(&mut Context::from_waker(Waker::noop())),
)
}
fn ready<T>(poll: Poll<T>) -> T {
match poll {
Poll::Ready(output) => output,
Poll::Pending => unreachable!("plumbob's sync training waited on I/O"),
}
}
#[cfg(test)]
mod sim;
#[cfg(test)]
mod tests;