use display_types::cea861::hdmi_forum::HdmiForumFrl;
use hdmi_hal::phy::LtpPattern;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LtpReq {
None,
AllOnes,
AllZeros,
NyquistClock,
DdeCompliance,
Lfsr0,
Lfsr1,
Lfsr2,
Lfsr3,
FfeChange,
RateChange,
Reserved(u8),
}
impl LtpReq {
pub const fn from_value(value: u8) -> Option<Self> {
Some(match value {
0x0 => Self::None,
0x1 => Self::AllOnes,
0x2 => Self::AllZeros,
0x3 => Self::NyquistClock,
0x4 => Self::DdeCompliance,
0x5 => Self::Lfsr0,
0x6 => Self::Lfsr1,
0x7 => Self::Lfsr2,
0x8 => Self::Lfsr3,
0x9..=0xD => Self::Reserved(value),
0xE => Self::FfeChange,
0xF => Self::RateChange,
_ => return None,
})
}
pub const fn value(self) -> u8 {
match self {
Self::None => 0x0,
Self::AllOnes => 0x1,
Self::AllZeros => 0x2,
Self::NyquistClock => 0x3,
Self::DdeCompliance => 0x4,
Self::Lfsr0 => 0x5,
Self::Lfsr1 => 0x6,
Self::Lfsr2 => 0x7,
Self::Lfsr3 => 0x8,
Self::FfeChange => 0xE,
Self::RateChange => 0xF,
Self::Reserved(value) => value,
}
}
pub const fn pattern(self) -> Option<LtpPattern> {
match self {
Self::None | Self::FfeChange | Self::RateChange | Self::Reserved(_) => None,
Self::AllOnes => Some(LtpPattern::AllOnes),
Self::AllZeros => Some(LtpPattern::AllZeros),
Self::NyquistClock => Some(LtpPattern::NyquistClock),
Self::DdeCompliance => Some(LtpPattern::DdeCompliance),
Self::Lfsr0 => Some(LtpPattern::Lfsr0),
Self::Lfsr1 => Some(LtpPattern::Lfsr1),
Self::Lfsr2 => Some(LtpPattern::Lfsr2),
Self::Lfsr3 => Some(LtpPattern::Lfsr3),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LtpRequests {
pub lane0: LtpReq,
pub lane1: LtpReq,
pub lane2: LtpReq,
pub lane3: LtpReq,
}
impl LtpRequests {
pub const fn new(lane0: LtpReq, lane1: LtpReq, lane2: LtpReq, lane3: LtpReq) -> Self {
Self {
lane0,
lane1,
lane2,
lane3,
}
}
pub fn all_trained(&self) -> bool {
[self.lane0, self.lane1, self.lane2, self.lane3]
.iter()
.all(|r| *r == LtpReq::None)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct FfeLevels(u8);
impl FfeLevels {
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
}
pub fn limited_to(self, rate: HdmiForumFrl) -> Self {
let top_3_level_rate = HdmiForumFrl::Rate12Gbps4Lanes;
let max = if rate <= top_3_level_rate { 3 } else { 7 };
Self(self.0.min(max))
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrlConfig {
pub frl_rate: HdmiForumFrl,
pub ffe_levels: FfeLevels,
}
impl FrlConfig {
pub const fn new(frl_rate: HdmiForumFrl, ffe_levels: FfeLevels) -> Self {
Self {
frl_rate,
ffe_levels,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct UpdateFlags {
pub status_update: bool,
pub ced_update: bool,
pub rr_test: bool,
pub source_test_update: bool,
pub frl_start: bool,
pub flt_update: bool,
pub rsed_update: bool,
}
impl UpdateFlags {
pub const fn new(
status_update: bool,
ced_update: bool,
rr_test: bool,
source_test_update: bool,
frl_start: bool,
flt_update: bool,
rsed_update: bool,
) -> Self {
Self {
status_update,
ced_update,
rr_test,
source_test_update,
frl_start,
flt_update,
rsed_update,
}
}
pub const fn clearable(self) -> ClearableUpdateFlags {
ClearableUpdateFlags {
status_update: self.status_update,
ced_update: self.ced_update,
source_test_update: self.source_test_update,
frl_start: self.frl_start,
flt_update: self.flt_update,
rsed_update: self.rsed_update,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ClearableUpdateFlags {
pub status_update: bool,
pub ced_update: bool,
pub source_test_update: bool,
pub frl_start: bool,
pub flt_update: bool,
pub rsed_update: bool,
}
impl ClearableUpdateFlags {
pub const fn new(
status_update: bool,
ced_update: bool,
source_test_update: bool,
frl_start: bool,
flt_update: bool,
rsed_update: bool,
) -> Self {
Self {
status_update,
ced_update,
source_test_update,
frl_start,
flt_update,
rsed_update,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SourceTestConfig {
pub txffe_pre_shoot_only: bool,
pub txffe_de_emphasis_only: bool,
pub txffe_no_ffe: bool,
pub flt_no_timeout: bool,
pub dsc_frl_max: bool,
pub frl_max: bool,
}
impl SourceTestConfig {
pub const fn new(
txffe_pre_shoot_only: bool,
txffe_de_emphasis_only: bool,
txffe_no_ffe: bool,
flt_no_timeout: bool,
dsc_frl_max: bool,
frl_max: bool,
) -> Self {
Self {
txffe_pre_shoot_only,
txffe_de_emphasis_only,
txffe_no_ffe,
flt_no_timeout,
dsc_frl_max,
frl_max,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CedCount(u16);
impl CedCount {
pub fn new(raw: u16) -> Self {
Self(raw & 0x7FFF)
}
pub fn value(self) -> u16 {
self.0
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CedCounters {
pub lane0: Option<CedCount>,
pub lane1: Option<CedCount>,
pub lane2: Option<CedCount>,
pub lane3: Option<CedCount>,
}
impl CedCounters {
pub const fn new(
lane0: Option<CedCount>,
lane1: Option<CedCount>,
lane2: Option<CedCount>,
lane3: Option<CedCount>,
) -> Self {
Self {
lane0,
lane1,
lane2,
lane3,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ltp_req_from_value_gives_one_request_per_value() {
for value in 0u8..=0xF {
let request = LtpReq::from_value(value).unwrap();
assert_eq!(request.value(), value, "value {value:#x}");
assert_eq!(
matches!(request, LtpReq::Reserved(_)),
(0x9..=0xD).contains(&value),
"value {value:#x}"
);
}
assert_eq!(LtpReq::from_value(0x10), None);
assert_eq!(LtpReq::from_value(0xFF), None);
}
#[test]
fn ltp_req_values_match_status_flags_encoding() {
assert_eq!(LtpReq::None.value(), 0x0);
assert_eq!(LtpReq::AllOnes.value(), 0x1);
assert_eq!(LtpReq::AllZeros.value(), 0x2);
assert_eq!(LtpReq::NyquistClock.value(), 0x3);
assert_eq!(LtpReq::DdeCompliance.value(), 0x4);
assert_eq!(LtpReq::Lfsr0.value(), 0x5);
assert_eq!(LtpReq::Lfsr1.value(), 0x6);
assert_eq!(LtpReq::Lfsr2.value(), 0x7);
assert_eq!(LtpReq::Lfsr3.value(), 0x8);
assert_eq!(LtpReq::FfeChange.value(), 0xE);
assert_eq!(LtpReq::RateChange.value(), 0xF);
assert_eq!(LtpReq::Reserved(0xB).value(), 0xB);
}
#[test]
fn ltp_req_patterns_match_by_value() {
assert_eq!(LtpReq::AllOnes.pattern(), Some(LtpPattern::AllOnes));
assert_eq!(LtpReq::AllZeros.pattern(), Some(LtpPattern::AllZeros));
assert_eq!(
LtpReq::NyquistClock.pattern(),
Some(LtpPattern::NyquistClock)
);
assert_eq!(
LtpReq::DdeCompliance.pattern(),
Some(LtpPattern::DdeCompliance)
);
assert_eq!(LtpReq::Lfsr0.pattern(), Some(LtpPattern::Lfsr0));
assert_eq!(LtpReq::Lfsr1.pattern(), Some(LtpPattern::Lfsr1));
assert_eq!(LtpReq::Lfsr2.pattern(), Some(LtpPattern::Lfsr2));
assert_eq!(LtpReq::Lfsr3.pattern(), Some(LtpPattern::Lfsr3));
}
#[test]
fn ltp_req_patterns_share_their_value_with_ltp_pattern() {
for req in [
LtpReq::AllOnes,
LtpReq::AllZeros,
LtpReq::NyquistClock,
LtpReq::DdeCompliance,
LtpReq::Lfsr0,
LtpReq::Lfsr1,
LtpReq::Lfsr2,
LtpReq::Lfsr3,
] {
assert_eq!(req.pattern().map(LtpPattern::value), Some(req.value()));
}
}
#[test]
fn ltp_req_signals_have_no_pattern() {
assert_eq!(LtpReq::None.pattern(), None);
assert_eq!(LtpReq::FfeChange.pattern(), None);
assert_eq!(LtpReq::RateChange.pattern(), None);
assert_eq!(LtpReq::Reserved(0x9).pattern(), None);
}
#[test]
fn ffe_levels_accepts_0_to_7() {
for level in 0..=7 {
assert_eq!(FfeLevels::new(level).map(FfeLevels::value), Some(level));
}
}
#[test]
fn ffe_levels_rejects_above_7() {
assert_eq!(FfeLevels::new(8), None);
assert_eq!(FfeLevels::new(u8::MAX), None);
}
#[test]
fn ffe_levels_max_and_default() {
assert_eq!(FfeLevels::MAX.value(), 7);
assert_eq!(FfeLevels::default().value(), 0);
}
#[test]
fn ffe_levels_limited_to_3_up_to_12_gbps() {
for rate in [
HdmiForumFrl::Rate3Gbps3Lanes,
HdmiForumFrl::Rate6Gbps3Lanes,
HdmiForumFrl::Rate6Gbps4Lanes,
HdmiForumFrl::Rate8Gbps4Lanes,
HdmiForumFrl::Rate10Gbps4Lanes,
HdmiForumFrl::Rate12Gbps4Lanes,
] {
assert_eq!(FfeLevels::MAX.limited_to(rate).value(), 3);
}
}
#[test]
fn ffe_levels_below_the_limit_are_unchanged() {
let two = FfeLevels::new(2).unwrap();
assert_eq!(two.limited_to(HdmiForumFrl::Rate12Gbps4Lanes), two);
assert_eq!(
FfeLevels::default().limited_to(HdmiForumFrl::Rate3Gbps3Lanes),
FfeLevels::default()
);
}
#[test]
fn update_flags_default_is_all_clear() {
let flags = UpdateFlags::default();
assert!(!flags.source_test_update);
assert!(!flags.frl_start);
assert!(!flags.flt_update);
}
#[test]
fn source_test_config_default_keeps_timeouts() {
assert!(!SourceTestConfig::default().flt_no_timeout);
}
#[test]
fn ltp_requests_compare_per_lane() {
let a = LtpRequests {
lane0: LtpReq::Lfsr0,
lane1: LtpReq::Lfsr1,
lane2: LtpReq::Lfsr2,
lane3: LtpReq::Lfsr3,
};
assert_eq!(a, a);
assert_ne!(
a,
LtpRequests {
lane3: LtpReq::None,
..a
}
);
}
#[test]
fn ced_count_masks_validity_bit() {
let c = CedCount::new(0xFFFF);
assert_eq!(c.value(), 0x7FFF);
}
#[test]
fn ced_count_preserves_15_bit_value() {
let c = CedCount::new(0x0123);
assert_eq!(c.value(), 0x0123);
}
#[test]
fn ced_count_zero() {
assert_eq!(CedCount::new(0).value(), 0);
}
#[test]
fn ced_count_clone_eq() {
let a = CedCount::new(42);
assert_eq!(a, a.clone());
assert_ne!(CedCount::new(1), CedCount::new(2));
}
#[test]
fn update_flags_new_takes_the_flags_in_bit_order() {
let bit = |n: usize| {
let b = |i| i == n;
UpdateFlags::new(b(0), b(1), b(2), b(3), b(4), b(5), b(6))
};
let set = |f: UpdateFlags| {
[
f.status_update,
f.ced_update,
f.rr_test,
f.source_test_update,
f.frl_start,
f.flt_update,
f.rsed_update,
]
};
for n in 0..7 {
let expected: [bool; 7] = core::array::from_fn(|i| i == n);
assert_eq!(set(bit(n)), expected, "bit {n}");
}
assert_eq!(UpdateFlags::default(), bit(7));
}
#[test]
fn clearable_keeps_every_flag_but_rr_test() {
let all = UpdateFlags::new(true, true, true, true, true, true, true);
assert_eq!(
all.clearable(),
ClearableUpdateFlags::new(true, true, true, true, true, true)
);
let rr_test_only = UpdateFlags::new(false, false, true, false, false, false, false);
assert_eq!(rr_test_only.clearable(), ClearableUpdateFlags::default());
}
#[test]
fn source_test_config_new_takes_the_bits_in_bit_order() {
let set = |c: SourceTestConfig| {
[
c.txffe_pre_shoot_only,
c.txffe_de_emphasis_only,
c.txffe_no_ffe,
c.flt_no_timeout,
c.dsc_frl_max,
c.frl_max,
]
};
for n in 0..6 {
let b = |i| i == n;
let expected: [bool; 6] = core::array::from_fn(|i| i == n);
assert_eq!(
set(SourceTestConfig::new(b(0), b(1), b(2), b(3), b(4), b(5))),
expected,
"field {n}"
);
}
assert_eq!(set(SourceTestConfig::default()), [false; 6]);
}
#[test]
fn clearable_update_flags_new_takes_the_flags_in_bit_order() {
let c = ClearableUpdateFlags::new(true, false, true, false, true, false);
assert!(c.status_update && c.source_test_update && c.flt_update);
assert!(!c.ced_update && !c.frl_start && !c.rsed_update);
}
#[test]
fn ltp_requests_new_takes_the_lanes_in_order() {
let r = LtpRequests::new(LtpReq::Lfsr0, LtpReq::Lfsr1, LtpReq::Lfsr2, LtpReq::Lfsr3);
assert_eq!(
[r.lane0, r.lane1, r.lane2, r.lane3],
[LtpReq::Lfsr0, LtpReq::Lfsr1, LtpReq::Lfsr2, LtpReq::Lfsr3]
);
}
#[test]
fn all_trained_checks_all_four_lanes() {
let none = LtpReq::None;
assert!(LtpRequests::new(none, none, none, none).all_trained());
assert!(!LtpRequests::new(LtpReq::Lfsr0, none, none, none).all_trained());
assert!(!LtpRequests::new(none, none, none, LtpReq::Reserved(0x9)).all_trained());
}
#[test]
fn ced_counters_new_takes_the_lanes_in_order() {
let c = CedCounters::new(
Some(CedCount::new(0)),
Some(CedCount::new(1)),
Some(CedCount::new(2)),
None,
);
assert_eq!(c.lane0, Some(CedCount::new(0)));
assert_eq!(c.lane1, Some(CedCount::new(1)));
assert_eq!(c.lane2, Some(CedCount::new(2)));
assert_eq!(c.lane3, None);
}
#[test]
fn frl_config_new_takes_the_rate_and_levels() {
let c = FrlConfig::new(HdmiForumFrl::Rate6Gbps4Lanes, FfeLevels::new(2).unwrap());
assert_eq!(c.frl_rate, HdmiForumFrl::Rate6Gbps4Lanes);
assert_eq!(c.ffe_levels, FfeLevels::new(2).unwrap());
}
}