use display_types::cea861::hdmi_forum::HdmiForumFrl;
use hdmi_hal::phy::{
EqParams, FrlOutput, LaneEqParams, LanePatterns, LtpPattern, TxFfeLevel, TxFfeRestrictions,
};
use crate::trace::TrainingEvent;
use crate::training::{
ExitError, FallbackReason, TmdsExit, TrainingConfig, TrainingError, TrainingOutcome,
};
use crate::types::{
ClearableUpdateFlags, FfeLevels, FrlConfig, LtpReq, LtpRequests, SourceTestConfig, UpdateFlags,
};
use crate::warning::Trained;
#[allow(async_fn_in_trait)]
pub trait TrainingIo {
type ScdcError;
type PhyError;
async fn read_flt_ready(&mut self) -> Result<bool, Self::ScdcError>;
async fn read_update_flags(&mut self) -> Result<UpdateFlags, Self::ScdcError>;
async fn clear_update_flags(
&mut self,
flags: ClearableUpdateFlags,
) -> Result<(), Self::ScdcError>;
async fn read_ltp_requests(&mut self) -> Result<LtpRequests, Self::ScdcError>;
async fn read_source_test_config(&mut self) -> Result<SourceTestConfig, Self::ScdcError>;
async fn write_config_0_defaults(&mut self) -> Result<(), Self::ScdcError>;
async fn write_frl_config(&mut self, config: FrlConfig) -> Result<(), Self::ScdcError>;
async fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), Self::PhyError>;
async fn send_ltp(&mut self, patterns: LanePatterns) -> Result<(), Self::PhyError>;
async fn set_frl_output(&mut self, output: FrlOutput) -> Result<(), Self::PhyError>;
async fn adjust_equalization(&mut self, params: EqParams) -> Result<(), Self::PhyError>;
}
pub fn run<Io: TrainingIo, F: FnMut(TrainingEvent)>(
io: &mut Io,
rates: &[HdmiForumFrl],
config: &TrainingConfig,
record: &mut F,
) -> impl Future<Output = Result<Trained, Error<Io>>> {
Machine { io }.run(rates, config, record)
}
type Error<Io> = TrainingError<<Io as TrainingIo>::ScdcError, <Io as TrainingIo>::PhyError>;
enum Fault<ScdcErr, PhyErr> {
Scdc(ScdcErr),
Phy(PhyErr),
}
type Failure<Io> = Fault<<Io as TrainingIo>::ScdcError, <Io as TrainingIo>::PhyError>;
pub async fn exit_to_tmds<Io: TrainingIo, F: FnMut(TrainingEvent)>(
io: &mut Io,
record: &mut F,
) -> Result<(), ExitError<Io::ScdcError, Io::PhyError>> {
Machine { io }.exit_to_tmds(record).await
}
pub(crate) const FLT_UPDATE: ClearableUpdateFlags =
ClearableUpdateFlags::new(false, false, false, false, true, false);
pub(crate) const FRL_START: ClearableUpdateFlags =
ClearableUpdateFlags::new(false, false, false, true, false, false);
pub(crate) const SOURCE_TEST_UPDATE: ClearableUpdateFlags =
ClearableUpdateFlags::new(false, false, true, false, false, false);
fn invalid_rate(rates: &[HdmiForumFrl]) -> Option<(usize, &HdmiForumFrl)> {
rates.iter().enumerate().find(|&(index, &rate)| {
rate == HdmiForumFrl::NotSupported
|| index
.checked_sub(1)
.is_some_and(|previous| rate as u8 >= rates[previous] as u8)
})
}
fn lane_count(rate: HdmiForumFrl) -> usize {
match rate {
HdmiForumFrl::Rate3Gbps3Lanes | HdmiForumFrl::Rate6Gbps3Lanes => 3,
_ => 4,
}
}
fn lane_eq(level: u8) -> LaneEqParams {
let mut params = LaneEqParams::default();
params.tx_ffe_level = TxFfeLevel::new(level).unwrap_or(TxFfeLevel::MAX);
params
}
#[derive(Debug)]
pub(crate) struct Lanes {
count: usize,
patterns: [Option<LtpPattern>; 4],
levels: [u8; 4],
}
impl Lanes {
pub(crate) fn new(rate: HdmiForumFrl) -> Self {
Self {
count: lane_count(rate),
patterns: [None; 4],
levels: [0; 4],
}
}
fn stop_patterns(&mut self) {
self.patterns = [None; 4];
}
fn apply<F: FnMut(TrainingEvent)>(
&mut self,
requests: LtpRequests,
no_timeout: bool,
max_level: u8,
record: &mut F,
) -> bool {
let requests = [
requests.lane0,
requests.lane1,
requests.lane2,
requests.lane3,
];
let mut ffe_changed = false;
for (lane, request) in requests.into_iter().enumerate() {
let in_use = lane < self.count;
match request {
LtpReq::Reserved(_) => {}
_ if !in_use => {}
LtpReq::NyquistClock if !no_timeout => {}
LtpReq::FfeChange => {
if self.levels[lane] < max_level {
self.levels[lane] += 1;
ffe_changed = true;
record(TrainingEvent::FfeRaised {
lane: lane as u8,
level: self.levels[lane],
});
}
}
request => {
if let Some(pattern) = request.pattern() {
self.patterns[lane] = Some(pattern);
}
}
}
}
ffe_changed
}
fn record_undefined<F: FnMut(TrainingEvent)>(&self, requests: LtpRequests, record: &mut F) {
let requests = [
requests.lane0,
requests.lane1,
requests.lane2,
requests.lane3,
];
for (lane, request) in requests.into_iter().enumerate() {
if let LtpReq::Reserved(value) = request {
record(TrainingEvent::UndefinedLtpRequest {
lane: lane as u8,
value,
in_use: lane < self.count,
});
}
}
}
fn all(&self, requests: LtpRequests, request: LtpReq) -> bool {
[
requests.lane0,
requests.lane1,
requests.lane2,
requests.lane3,
][..self.count]
.iter()
.all(|r| *r == request)
}
fn patterns(&self) -> LanePatterns {
let lane = |i: usize| {
if i < self.count {
self.patterns[i]
} else {
None
}
};
LanePatterns {
lane0: lane(0),
lane1: lane(1),
lane2: lane(2),
lane3: lane(3),
}
}
pub(crate) fn eq_params(&self) -> EqParams {
let mut params = EqParams::new();
params.lane0 = lane_eq(self.levels[0]);
params.lane1 = lane_eq(self.levels[1]);
params.lane2 = lane_eq(self.levels[2]);
params.lane3 = (self.count == 4).then(|| lane_eq(self.levels[3]));
params
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
Prepare,
ConfigureRate,
Train,
Pass,
LowerRate,
Success,
Fallback(FallbackReason),
Hold,
}
enum End {
Success(HdmiForumFrl),
Fallback(FallbackReason),
Hold(HdmiForumFrl),
}
#[derive(Debug)]
struct Attempt {
rate: HdmiForumFrl,
lanes: Lanes,
no_timeout: bool,
restrictions: TxFfeRestrictions,
retrains: u32,
}
impl Attempt {
fn eq_params(&self) -> EqParams {
let mut params = self.lanes.eq_params();
params.tx_ffe_restrictions = self.restrictions;
params
}
fn limit(&self, config: &TrainingConfig, normal: u32) -> u32 {
if self.no_timeout {
config.no_timeout_poll_cap
} else {
normal
}
}
fn timed_out<F: FnMut(TrainingEvent)>(
&self,
polls: u32,
timeout: TrainingEvent,
reason: FallbackReason,
record: &mut F,
) -> State {
if self.no_timeout {
record(TrainingEvent::NoTimeoutCapReached { polls });
State::Hold
} else {
record(timeout);
State::Fallback(reason)
}
}
}
struct Observer<'a, F> {
trained: Trained,
under_test: bool,
record: &'a mut F,
}
impl<F: FnMut(TrainingEvent)> Observer<'_, F> {
fn observe(&mut self, event: TrainingEvent) {
if let TrainingEvent::SourceTestConfigRead { config } = event {
self.under_test = config.flt_no_timeout;
}
self.trained.observe(&event);
(self.record)(event);
}
}
struct Machine<'a, Io> {
io: &'a mut Io,
}
impl<Io: TrainingIo> Machine<'_, Io> {
async fn run<F: FnMut(TrainingEvent)>(
mut self,
rates: &[HdmiForumFrl],
config: &TrainingConfig,
record: &mut F,
) -> Result<Trained, Error<Io>> {
if let Some((index, &rate)) = invalid_rate(rates) {
return Err(TrainingError::InvalidRates { index, rate });
}
let Some((&rate, lower)) = rates.split_first() else {
return Err(TrainingError::NoRates);
};
let mut observer = Observer {
trained: Trained::new(TrainingOutcome::FallbackRequired {
reason: FallbackReason::RatesExhausted,
}),
under_test: false,
record,
};
let end = self
.states(rate, lower, config, &mut |event| observer.observe(event))
.await;
let outcome = match end {
Ok(End::Success(achieved_rate)) => TrainingOutcome::Success { achieved_rate },
Ok(End::Hold(rate)) => TrainingOutcome::NoTimeoutHold { rate },
Ok(End::Fallback(reason)) => {
match self
.exit_to_tmds(&mut |event| observer.observe(event))
.await
{
Ok(()) => TrainingOutcome::FallbackRequired { reason },
Err(error) => return Err(TrainingError::ExitFailed { reason, error }),
}
}
Err(fault) => {
let exit = if !config.exit_to_tmds_on_error {
TmdsExit::Skipped
} else if observer.under_test {
TmdsExit::HeldUnderTest
} else {
match self
.exit_to_tmds(&mut |event| observer.observe(event))
.await
{
Ok(()) => TmdsExit::Exited,
Err(error) => TmdsExit::Failed(error),
}
};
return Err(match fault {
Fault::Scdc(error) => TrainingError::Scdc { error, exit },
Fault::Phy(error) => TrainingError::Phy { error, exit },
});
}
};
observer.trained.outcome = outcome;
Ok(observer.trained)
}
async fn states<F: FnMut(TrainingEvent)>(
&mut self,
rate: HdmiForumFrl,
lower: &[HdmiForumFrl],
config: &TrainingConfig,
record: &mut F,
) -> Result<End, Failure<Io>> {
let mut rates = lower.iter().copied();
let mut attempt = Attempt {
rate,
lanes: Lanes::new(rate),
no_timeout: false,
restrictions: TxFfeRestrictions::default(),
retrains: 0,
};
let mut state = State::Prepare;
loop {
state = match state {
State::Prepare => self.prepare(&mut attempt, config, record).await?,
State::ConfigureRate => {
self.configure_rate(&attempt, config, record).await?;
State::Train
}
State::Train => self.train_lanes(&mut attempt, config, record).await?,
State::Pass => self.pass(&mut attempt, config, record).await?,
State::LowerRate => match rates.next() {
Some(next) => self.lower_rate(&mut attempt, next, record).await?,
None => {
record(TrainingEvent::RatesExhausted);
State::Fallback(FallbackReason::RatesExhausted)
}
},
State::Success => return Ok(End::Success(attempt.rate)),
State::Fallback(reason) => return Ok(End::Fallback(reason)),
State::Hold => return Ok(End::Hold(attempt.rate)),
};
}
}
async fn prepare<F: FnMut(TrainingEvent)>(
&mut self,
attempt: &mut Attempt,
config: &TrainingConfig,
record: &mut F,
) -> Result<State, Failure<Io>> {
let update = self.read_update_flags().await?.source_test_update;
self.read_source_test(attempt, update, record).await?;
let limit = attempt.limit(config, config.flt_ready_polls);
match self.poll_flt_ready(limit).await? {
Some(polls) => record(TrainingEvent::FltReady { after_polls: polls }),
None => {
return Ok(attempt.timed_out(
limit,
TrainingEvent::FltReadyTimeout { polls: limit },
FallbackReason::FltReadyTimeout,
record,
));
}
}
Ok(State::ConfigureRate)
}
async fn configure_rate<F: FnMut(TrainingEvent)>(
&mut self,
attempt: &Attempt,
config: &TrainingConfig,
record: &mut F,
) -> Result<(), Failure<Io>> {
self.clear(FLT_UPDATE).await?;
self.io
.set_frl_rate(attempt.rate)
.await
.map_err(Fault::Phy)?;
self.adjust_equalization(attempt.eq_params()).await?;
self.send_ltp(attempt.lanes.patterns()).await?;
self.set_frl_output(FrlOutput::GapOnly).await?;
self.io
.write_config_0_defaults()
.await
.map_err(Fault::Scdc)?;
self.write_rate(attempt.rate, config, record).await
}
async fn train_lanes<F: FnMut(TrainingEvent)>(
&mut self,
attempt: &mut Attempt,
config: &TrainingConfig,
record: &mut F,
) -> Result<State, Failure<Io>> {
let max_level = config.ffe_levels.limited_to(attempt.rate).value();
let mut polls = 0;
loop {
let limit = attempt.limit(config, config.ltp_polls);
if polls >= limit {
return Ok(attempt.timed_out(
polls,
TrainingEvent::TrainingTimeout { polls },
FallbackReason::TrainingTimeout,
record,
));
}
let flags = self.read_update_flags().await?;
polls += 1;
if flags.source_test_update && self.read_source_test(attempt, true, record).await? {
self.adjust_equalization(attempt.eq_params()).await?;
}
if !flags.flt_update {
continue;
}
let requests = self.io.read_ltp_requests().await.map_err(Fault::Scdc)?;
let passed = attempt.lanes.all(requests, LtpReq::None);
if !passed {
record(TrainingEvent::LtpRequested { requests });
}
attempt.lanes.record_undefined(requests, record);
if passed {
record(TrainingEvent::TrainingPassed { after_polls: polls });
return Ok(State::Pass);
}
if attempt.lanes.all(requests, LtpReq::RateChange) {
return Ok(State::LowerRate);
}
let ffe_changed = attempt
.lanes
.apply(requests, attempt.no_timeout, max_level, record);
self.send_ltp(attempt.lanes.patterns()).await?;
if ffe_changed {
self.adjust_equalization(attempt.eq_params()).await?;
}
self.clear(FLT_UPDATE).await?;
}
}
async fn pass<F: FnMut(TrainingEvent)>(
&mut self,
attempt: &mut Attempt,
config: &TrainingConfig,
record: &mut F,
) -> Result<State, Failure<Io>> {
attempt.lanes.stop_patterns();
self.send_ltp(attempt.lanes.patterns()).await?;
self.set_frl_output(FrlOutput::GapOnly).await?;
self.clear(FLT_UPDATE).await?;
let limit = attempt.limit(config, config.frl_start_polls);
for polls in 1..=limit {
let flags = self.read_update_flags().await?;
if flags.source_test_update && self.read_source_test(attempt, true, record).await? {
self.adjust_equalization(attempt.eq_params()).await?;
}
if flags.flt_update {
if flags.frl_start {
self.clear(FRL_START).await?;
record(TrainingEvent::FrlStartWithRetrain);
}
if attempt.retrains >= config.max_retrains {
record(TrainingEvent::RetrainsExhausted {
retrains: attempt.retrains,
});
return Ok(State::Fallback(FallbackReason::RetrainsExhausted));
}
attempt.retrains += 1;
record(TrainingEvent::RetrainRequested);
return Ok(State::Train);
}
if flags.frl_start {
self.clear(FRL_START).await?;
record(TrainingEvent::FrlStart { after_polls: polls });
return Ok(State::Success);
}
}
Ok(attempt.timed_out(
limit,
TrainingEvent::FrlStartTimeout { polls: limit },
FallbackReason::FrlStartTimeout,
record,
))
}
async fn lower_rate<F: FnMut(TrainingEvent)>(
&mut self,
attempt: &mut Attempt,
rate: HdmiForumFrl,
record: &mut F,
) -> Result<State, Failure<Io>> {
self.send_ltp(LanePatterns::default()).await?;
record(TrainingEvent::RateLowered {
from: attempt.rate,
to: rate,
});
attempt.rate = rate;
attempt.lanes = Lanes::new(rate);
Ok(State::ConfigureRate)
}
async fn exit_to_tmds<F: FnMut(TrainingEvent)>(
&mut self,
record: &mut F,
) -> Result<(), ExitError<Io::ScdcError, Io::PhyError>> {
let patterns = self.io.send_ltp(LanePatterns::default()).await;
let phy_rate = self.io.set_frl_rate(HdmiForumFrl::NotSupported).await;
let config = self
.io
.write_frl_config(FrlConfig {
frl_rate: HdmiForumFrl::NotSupported,
ffe_levels: FfeLevels::default(),
})
.await;
let flt_update = match self.io.read_update_flags().await {
Ok(flags) if flags.flt_update => self.io.clear_update_flags(FLT_UPDATE).await,
Ok(_) => Ok(()),
Err(e) => Err(e),
};
let phy = patterns.and(phy_rate).err();
let scdc = config.and(flt_update).err();
if phy.is_none() && scdc.is_none() {
record(TrainingEvent::ExitedToTmds);
return Ok(());
}
record(TrainingEvent::ExitToTmdsFailed {
scdc: scdc.is_some(),
phy: phy.is_some(),
});
Err(ExitError { scdc, phy })
}
async fn write_rate<F: FnMut(TrainingEvent)>(
&mut self,
rate: HdmiForumFrl,
config: &TrainingConfig,
record: &mut F,
) -> Result<(), Failure<Io>> {
let ffe_levels = config.ffe_levels.limited_to(rate);
self.io
.write_frl_config(FrlConfig {
frl_rate: rate,
ffe_levels,
})
.await
.map_err(Fault::Scdc)?;
record(TrainingEvent::RateConfigured { rate, ffe_levels });
Ok(())
}
async fn poll_flt_ready(&mut self, limit: u32) -> Result<Option<u32>, Failure<Io>> {
for polls in 1..=limit {
if self.io.read_flt_ready().await.map_err(Fault::Scdc)? {
return Ok(Some(polls));
}
}
Ok(None)
}
async fn read_source_test<F: FnMut(TrainingEvent)>(
&mut self,
attempt: &mut Attempt,
update: bool,
record: &mut F,
) -> Result<bool, Failure<Io>> {
let source_test = self
.io
.read_source_test_config()
.await
.map_err(Fault::Scdc)?;
attempt.no_timeout = source_test.flt_no_timeout;
let restrictions = TxFfeRestrictions::new(
source_test.txffe_pre_shoot_only,
source_test.txffe_de_emphasis_only,
source_test.txffe_no_ffe,
);
let changed = restrictions != attempt.restrictions;
attempt.restrictions = restrictions;
record(TrainingEvent::SourceTestConfigRead {
config: source_test,
});
if update {
self.clear(SOURCE_TEST_UPDATE).await?;
}
Ok(changed)
}
async fn read_update_flags(&mut self) -> Result<UpdateFlags, Failure<Io>> {
self.io.read_update_flags().await.map_err(Fault::Scdc)
}
async fn clear(&mut self, flags: ClearableUpdateFlags) -> Result<(), Failure<Io>> {
self.io.clear_update_flags(flags).await.map_err(Fault::Scdc)
}
async fn send_ltp(&mut self, patterns: LanePatterns) -> Result<(), Failure<Io>> {
self.io.send_ltp(patterns).await.map_err(Fault::Phy)
}
async fn set_frl_output(&mut self, output: FrlOutput) -> Result<(), Failure<Io>> {
self.io.set_frl_output(output).await.map_err(Fault::Phy)
}
async fn adjust_equalization(&mut self, params: EqParams) -> Result<(), Failure<Io>> {
self.io
.adjust_equalization(params)
.await
.map_err(Fault::Phy)
}
}