use display_types::cea861::hdmi_forum::HdmiForumFrl;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum LtpPattern {
AllOnes = 1,
AllZeros = 2,
NyquistClock = 3,
DdeCompliance = 4,
Lfsr0 = 5,
Lfsr1 = 6,
Lfsr2 = 7,
Lfsr3 = 8,
}
impl LtpPattern {
pub const fn value(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct LanePatterns {
pub lane0: Option<LtpPattern>,
pub lane1: Option<LtpPattern>,
pub lane2: Option<LtpPattern>,
pub lane3: Option<LtpPattern>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FrlOutput {
GapOnly,
Active,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TxFfeLevel(u8);
impl TxFfeLevel {
pub const MAX: Self = Self(7);
pub const fn new(level: u8) -> Option<Self> {
if level <= Self::MAX.0 {
Some(Self(level))
} else {
None
}
}
pub const fn value(self) -> u8 {
self.0
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct LaneEqParams {
pub tx_ffe_level: TxFfeLevel,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct TxFfeRestrictions {
pub pre_shoot_only: bool,
pub de_emphasis_only: bool,
pub no_ffe: bool,
}
impl TxFfeRestrictions {
pub const fn new(pre_shoot_only: bool, de_emphasis_only: bool, no_ffe: bool) -> Self {
Self {
pre_shoot_only,
de_emphasis_only,
no_ffe,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct EqParams {
pub lane0: LaneEqParams,
pub lane1: LaneEqParams,
pub lane2: LaneEqParams,
pub lane3: Option<LaneEqParams>,
pub tx_ffe_restrictions: TxFfeRestrictions,
}
impl EqParams {
pub fn new() -> Self {
Self::default()
}
}
pub trait HdmiPhy {
type Error;
fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), Self::Error>;
fn send_ltp(&mut self, patterns: LanePatterns) -> Result<(), Self::Error>;
fn set_frl_output(&mut self, output: FrlOutput) -> Result<(), Self::Error>;
fn adjust_equalization(&mut self, params: EqParams) -> Result<(), Self::Error>;
fn set_scrambling(&mut self, enabled: bool) -> Result<(), Self::Error>;
}
#[cfg(test)]
mod tests {
use super::*;
use display_types::cea861::hdmi_forum::HdmiForumFrl;
struct MockPhy {
frl_rate: Option<HdmiForumFrl>,
scrambling: Option<bool>,
eq_calls: u32,
last_eq: Option<EqParams>,
frl_output: Option<FrlOutput>,
last_ltp: Option<LanePatterns>,
}
impl MockPhy {
fn new() -> Self {
Self {
frl_rate: None,
scrambling: None,
eq_calls: 0,
last_eq: None,
frl_output: None,
last_ltp: None,
}
}
}
impl HdmiPhy for MockPhy {
type Error = core::convert::Infallible;
fn send_ltp(&mut self, patterns: LanePatterns) -> Result<(), Self::Error> {
self.last_ltp = Some(patterns);
Ok(())
}
fn set_frl_output(&mut self, output: FrlOutput) -> Result<(), Self::Error> {
self.frl_output = Some(output);
Ok(())
}
fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), Self::Error> {
self.frl_rate = Some(rate);
Ok(())
}
fn adjust_equalization(&mut self, params: EqParams) -> Result<(), Self::Error> {
self.eq_calls += 1;
self.last_eq = Some(params);
Ok(())
}
fn set_scrambling(&mut self, enabled: bool) -> Result<(), Self::Error> {
self.scrambling = Some(enabled);
Ok(())
}
}
#[test]
fn ltp_pattern_values_match_scdc_encoding() {
assert_eq!(LtpPattern::AllOnes.value(), 1);
assert_eq!(LtpPattern::AllZeros.value(), 2);
assert_eq!(LtpPattern::NyquistClock.value(), 3);
assert_eq!(LtpPattern::DdeCompliance.value(), 4);
assert_eq!(LtpPattern::Lfsr0.value(), 5);
assert_eq!(LtpPattern::Lfsr1.value(), 6);
assert_eq!(LtpPattern::Lfsr2.value(), 7);
assert_eq!(LtpPattern::Lfsr3.value(), 8);
}
#[test]
fn ltp_pattern_clone_eq() {
let a = LtpPattern::Lfsr1;
assert_eq!(a, a);
assert_ne!(LtpPattern::Lfsr0, LtpPattern::Lfsr1);
}
#[test]
fn lane_patterns_default_is_no_pattern() {
let p = LanePatterns::default();
assert_eq!(p.lane0, None);
assert_eq!(p.lane1, None);
assert_eq!(p.lane2, None);
assert_eq!(p.lane3, None);
}
#[test]
fn send_ltp_records_patterns() {
let mut phy = MockPhy::new();
let patterns = LanePatterns {
lane0: Some(LtpPattern::Lfsr0),
lane1: Some(LtpPattern::Lfsr1),
lane2: Some(LtpPattern::Lfsr2),
lane3: None,
};
phy.send_ltp(patterns).unwrap();
assert_eq!(phy.last_ltp, Some(patterns));
}
#[test]
fn send_ltp_updates_on_each_call() {
let mut phy = MockPhy::new();
let first = LanePatterns {
lane0: Some(LtpPattern::Lfsr0),
..LanePatterns::default()
};
let second = LanePatterns {
lane0: Some(LtpPattern::NyquistClock),
..first
};
phy.send_ltp(first).unwrap();
phy.send_ltp(second).unwrap();
assert_eq!(phy.last_ltp, Some(second));
}
#[test]
fn eq_params_constructors_are_equivalent() {
let _a = EqParams::new();
let _b = EqParams::default();
}
#[test]
fn set_frl_rate_records_rate() {
let mut phy = MockPhy::new();
phy.set_frl_rate(HdmiForumFrl::Rate6Gbps4Lanes).unwrap();
assert_eq!(phy.frl_rate, Some(HdmiForumFrl::Rate6Gbps4Lanes));
}
#[test]
fn set_frl_rate_not_supported_is_valid() {
let mut phy = MockPhy::new();
phy.set_frl_rate(HdmiForumFrl::NotSupported).unwrap();
assert_eq!(phy.frl_rate, Some(HdmiForumFrl::NotSupported));
}
#[test]
fn set_frl_rate_can_be_updated() {
let mut phy = MockPhy::new();
phy.set_frl_rate(HdmiForumFrl::Rate3Gbps3Lanes).unwrap();
phy.set_frl_rate(HdmiForumFrl::Rate12Gbps4Lanes).unwrap();
assert_eq!(phy.frl_rate, Some(HdmiForumFrl::Rate12Gbps4Lanes));
}
#[test]
fn set_scrambling_enable() {
let mut phy = MockPhy::new();
phy.set_scrambling(true).unwrap();
assert_eq!(phy.scrambling, Some(true));
}
#[test]
fn set_scrambling_disable() {
let mut phy = MockPhy::new();
phy.set_scrambling(false).unwrap();
assert_eq!(phy.scrambling, Some(false));
}
#[test]
fn set_scrambling_can_be_toggled() {
let mut phy = MockPhy::new();
phy.set_scrambling(true).unwrap();
phy.set_scrambling(false).unwrap();
assert_eq!(phy.scrambling, Some(false));
}
#[test]
fn set_frl_output_records_mode() {
let mut phy = MockPhy::new();
phy.set_frl_output(FrlOutput::GapOnly).unwrap();
assert_eq!(phy.frl_output, Some(FrlOutput::GapOnly));
}
#[test]
fn set_frl_output_can_be_switched() {
let mut phy = MockPhy::new();
phy.set_frl_output(FrlOutput::GapOnly).unwrap();
phy.set_frl_output(FrlOutput::Active).unwrap();
assert_eq!(phy.frl_output, Some(FrlOutput::Active));
}
#[test]
fn value_types_can_be_hashed() {
extern crate std;
use std::collections::HashSet;
let patterns: HashSet<LanePatterns> = [LanePatterns::default(), LanePatterns::default()]
.into_iter()
.collect();
assert_eq!(patterns.len(), 1);
let outputs: HashSet<FrlOutput> = [FrlOutput::GapOnly, FrlOutput::Active].into();
assert_eq!(outputs.len(), 2);
let levels: HashSet<TxFfeLevel> = [TxFfeLevel::MAX, TxFfeLevel::default()].into();
assert_eq!(levels.len(), 2);
let eq: HashSet<EqParams> = [EqParams::new(), EqParams::new()].into();
assert_eq!(eq.len(), 1);
let ltp: HashSet<LtpPattern> = [LtpPattern::Lfsr0, LtpPattern::Lfsr0].into();
assert_eq!(ltp.len(), 1);
}
#[test]
fn tx_ffe_level_accepts_0_to_7() {
for level in 0..=7 {
assert_eq!(TxFfeLevel::new(level).map(TxFfeLevel::value), Some(level));
}
}
#[test]
fn tx_ffe_level_rejects_above_7() {
assert_eq!(TxFfeLevel::new(8), None);
assert_eq!(TxFfeLevel::new(u8::MAX), None);
}
#[test]
fn tx_ffe_level_max_and_default() {
assert_eq!(TxFfeLevel::MAX.value(), 7);
assert_eq!(TxFfeLevel::default().value(), 0);
assert!(TxFfeLevel::default() < TxFfeLevel::MAX);
}
#[test]
fn lane_eq_params_default_tx_ffe_level_is_0() {
assert_eq!(LaneEqParams::default().tx_ffe_level, TxFfeLevel::default());
}
#[test]
fn tx_ffe_restrictions_new_takes_the_bits_in_register_order() {
let r = TxFfeRestrictions::new(true, false, false);
assert!(r.pre_shoot_only && !r.de_emphasis_only && !r.no_ffe);
let r = TxFfeRestrictions::new(false, true, false);
assert!(!r.pre_shoot_only && r.de_emphasis_only && !r.no_ffe);
let r = TxFfeRestrictions::new(false, false, true);
assert!(!r.pre_shoot_only && !r.de_emphasis_only && r.no_ffe);
let both = TxFfeRestrictions::new(true, true, false);
assert!(both.pre_shoot_only && both.de_emphasis_only);
}
#[test]
fn eq_params_restrict_nothing_by_default() {
assert_eq!(
TxFfeRestrictions::default(),
TxFfeRestrictions::new(false, false, false)
);
assert_eq!(
EqParams::new().tx_ffe_restrictions,
TxFfeRestrictions::default()
);
}
#[test]
fn adjust_equalization_records_per_lane_levels() {
let mut phy = MockPhy::new();
let mut params = EqParams::new();
params.lane1.tx_ffe_level = TxFfeLevel::MAX;
phy.adjust_equalization(params).unwrap();
let last = phy.last_eq.unwrap();
assert_eq!(last, params);
assert_eq!(last.lane0.tx_ffe_level.value(), 0);
assert_eq!(last.lane1.tx_ffe_level.value(), 7);
}
#[test]
fn adjust_equalization_tracks_call_count() {
let mut phy = MockPhy::new();
phy.adjust_equalization(EqParams::new()).unwrap();
phy.adjust_equalization(EqParams::new()).unwrap();
assert_eq!(phy.eq_calls, 2);
}
}