Skip to main content

plumbob/
types.rs

1use display_types::cea861::hdmi_forum::HdmiForumFrl;
2use hdmi_hal::phy::LtpPattern;
3
4/// Link training pattern requested by the sink for one lane: a 4-bit field in
5/// `Status_Flags_1` (lanes 0–1) or `Status_Flags_2` (lanes 2–3).
6///
7/// Values 0x1–0x8 are patterns the PHY drives on that lane; 0x0, 0xE and 0xF are
8/// signals to the state machine and never reach the PHY. The values the specification
9/// leaves undefined (0x9–0xD) are [`Reserved`](Self::Reserved), so a request can always
10/// be represented and the training layer decides what to do with it: plumbob leaves the
11/// lane as it was.
12#[non_exhaustive]
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum LtpReq {
15    /// No pattern requested (0x0): the lane is trained.
16    None,
17    /// All ones (0x1).
18    AllOnes,
19    /// All zeros (0x2).
20    AllZeros,
21    /// Nyquist clock pattern (0x3).
22    NyquistClock,
23    /// DDE (Data Dependent Equalization) compliance pattern (0x4).
24    DdeCompliance,
25    /// LFSR pattern 0 (0x5).
26    Lfsr0,
27    /// LFSR pattern 1 (0x6).
28    Lfsr1,
29    /// LFSR pattern 2 (0x7).
30    Lfsr2,
31    /// LFSR pattern 3 (0x8).
32    Lfsr3,
33    /// Raise this lane's TxFFE level (0xE).
34    FfeChange,
35    /// Drop the FRL rate (0xF).
36    RateChange,
37    /// A value the specification leaves undefined, 0x9–0xD.
38    ///
39    /// Build requests from a raw value with [`from_value`](Self::from_value), which only
40    /// produces `Reserved` for 0x9–0xD. A `Reserved` built by hand with any other value
41    /// is not a request a sink can send: `Reserved(0x5)` has the value of `Lfsr0` but
42    /// does not compare equal to it.
43    Reserved(u8),
44}
45
46impl LtpReq {
47    /// The request with the 4-bit `value`, or `None` above 0xF. Every value has exactly
48    /// one request: 0x9–0xD are [`Reserved`](Self::Reserved).
49    pub const fn from_value(value: u8) -> Option<Self> {
50        Some(match value {
51            0x0 => Self::None,
52            0x1 => Self::AllOnes,
53            0x2 => Self::AllZeros,
54            0x3 => Self::NyquistClock,
55            0x4 => Self::DdeCompliance,
56            0x5 => Self::Lfsr0,
57            0x6 => Self::Lfsr1,
58            0x7 => Self::Lfsr2,
59            0x8 => Self::Lfsr3,
60            0x9..=0xD => Self::Reserved(value),
61            0xE => Self::FfeChange,
62            0xF => Self::RateChange,
63            _ => return None,
64        })
65    }
66
67    /// The request's 4-bit value (for a `Reserved` built by hand, the value it holds).
68    pub const fn value(self) -> u8 {
69        match self {
70            Self::None => 0x0,
71            Self::AllOnes => 0x1,
72            Self::AllZeros => 0x2,
73            Self::NyquistClock => 0x3,
74            Self::DdeCompliance => 0x4,
75            Self::Lfsr0 => 0x5,
76            Self::Lfsr1 => 0x6,
77            Self::Lfsr2 => 0x7,
78            Self::Lfsr3 => 0x8,
79            Self::FfeChange => 0xE,
80            Self::RateChange => 0xF,
81            Self::Reserved(value) => value,
82        }
83    }
84
85    /// The pattern this request asks the PHY to drive, or `None` for the requests
86    /// that are signals to the state machine (0x0, 0xE and 0xF) and for reserved values.
87    pub const fn pattern(self) -> Option<LtpPattern> {
88        match self {
89            Self::None | Self::FfeChange | Self::RateChange | Self::Reserved(_) => None,
90            Self::AllOnes => Some(LtpPattern::AllOnes),
91            Self::AllZeros => Some(LtpPattern::AllZeros),
92            Self::NyquistClock => Some(LtpPattern::NyquistClock),
93            Self::DdeCompliance => Some(LtpPattern::DdeCompliance),
94            Self::Lfsr0 => Some(LtpPattern::Lfsr0),
95            Self::Lfsr1 => Some(LtpPattern::Lfsr1),
96            Self::Lfsr2 => Some(LtpPattern::Lfsr2),
97            Self::Lfsr3 => Some(LtpPattern::Lfsr3),
98        }
99    }
100}
101
102/// The sink's requests for all four lanes (`Status_Flags_1`, 0x41, and `Status_Flags_2`,
103/// 0x42). Lane 3 is ignored in 3-lane FRL.
104///
105/// Mirrors culvert's `LtpRequests`. Build one with [`new`](Self::new).
106#[non_exhaustive]
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct LtpRequests {
109    /// Lane 0 (`Status_Flags_1` bits 3:0).
110    pub lane0: LtpReq,
111    /// Lane 1 (`Status_Flags_1` bits 7:4).
112    pub lane1: LtpReq,
113    /// Lane 2 (`Status_Flags_2` bits 3:0).
114    pub lane2: LtpReq,
115    /// Lane 3 (`Status_Flags_2` bits 7:4). Not in use in 3-lane FRL, where it holds
116    /// whatever the sink leaves there (not necessarily `LtpReq::None`); the link training
117    /// layer ignores it.
118    pub lane3: LtpReq,
119}
120
121impl LtpRequests {
122    /// Constructs `LtpRequests` from the per-lane requests.
123    pub const fn new(lane0: LtpReq, lane1: LtpReq, lane2: LtpReq, lane3: LtpReq) -> Self {
124        Self {
125            lane0,
126            lane1,
127            lane2,
128            lane3,
129        }
130    }
131
132    /// Returns `true` when none of the four lanes requests a pattern. In 3-lane FRL lane 3
133    /// is not in use and may hold anything, so there only lanes 0–2 decide whether
134    /// training passed (as plumbob checks them).
135    pub fn all_trained(&self) -> bool {
136        [self.lane0, self.lane1, self.lane2, self.lane3]
137            .iter()
138            .all(|r| *r == LtpReq::None)
139    }
140}
141
142/// The highest TxFFE level index the source supports, written to `Config_1` bits 7:4.
143///
144/// At most 3 at rates up to 12 Gbps per lane and at most 7 above; see
145/// [`limited_to`](Self::limited_to). The default is 0.
146#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
147pub struct FfeLevels(u8);
148
149impl FfeLevels {
150    /// The highest level the `Config_1` field and the PHY accept, 7.
151    pub const MAX: Self = Self(7);
152
153    /// Returns the level for `level`, or `None` if it is above 7.
154    pub const fn new(level: u8) -> Option<Self> {
155        if level <= Self::MAX.0 {
156            Some(Self(level))
157        } else {
158            None
159        }
160    }
161
162    /// Returns the level index (0–7).
163    pub const fn value(self) -> u8 {
164        self.0
165    }
166
167    /// Returns this level, capped at the maximum for `rate`: 3 at rates up to 12 Gbps
168    /// per lane, 7 above.
169    pub fn limited_to(self, rate: HdmiForumFrl) -> Self {
170        // No rate above 12 Gbps exists in display-types yet; the 7 is for the faster
171        // rates when they are added.
172        let top_3_level_rate = HdmiForumFrl::Rate12Gbps4Lanes;
173        let max = if rate <= top_3_level_rate { 3 } else { 7 };
174        Self(self.0.min(max))
175    }
176}
177
178/// Written to `Config_1`: the FRL rate (bits 3:0) and the FFE levels (bits 7:4).
179///
180/// `HdmiForumFrl::NotSupported` turns FRL off (LTS:L). Mirrors culvert's `FrlConfig`;
181/// build one with [`new`](Self::new).
182#[non_exhaustive]
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub struct FrlConfig {
185    /// FRL rate to request. Use [`HdmiForumFrl::NotSupported`] to clear FRL mode.
186    pub frl_rate: HdmiForumFrl,
187    /// FFE levels to advertise to the sink: the highest TxFFE level the source will use
188    /// at this rate.
189    pub ffe_levels: FfeLevels,
190}
191
192impl FrlConfig {
193    /// Constructs `FrlConfig` from the rate and the FFE levels.
194    pub const fn new(frl_rate: HdmiForumFrl, ffe_levels: FfeLevels) -> Self {
195        Self {
196            frl_rate,
197            ffe_levels,
198        }
199    }
200}
201
202/// `Update_0` (0x10): the flags the sink sets to tell the source that something changed.
203///
204/// Mirrors culvert's `UpdateFlags` field for field. plumbob acts on `source_test_update`,
205/// `frl_start` and `flt_update`, and clears only those (see [`ClearableUpdateFlags`]); the
206/// other flags are read so that an `ScdcClient` reports the whole register, and are left
207/// to whoever handles them. Build one with [`new`](Self::new), or from the default (no flag
208/// set) by setting fields.
209#[non_exhaustive]
210#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
211pub struct UpdateFlags {
212    /// Bit 0: general status has changed.
213    pub status_update: bool,
214    /// Bit 1: CED counters have been updated; re-read the `ERR_DET` registers.
215    pub ced_update: bool,
216    /// Bit 2: read request test, owned by the sink. The source does not clear it, so
217    /// [`ClearableUpdateFlags`] has no field for it.
218    pub rr_test: bool,
219    /// Bit 3: the sink has written `Source_Test_Configuration` (0x35).
220    pub source_test_update: bool,
221    /// Bit 4: link training passed; the source may start FRL transmission.
222    pub frl_start: bool,
223    /// Bit 5: the per-lane link training pattern requests have changed.
224    pub flt_update: bool,
225    /// Bit 6: the Reed-Solomon correction count has been updated.
226    pub rsed_update: bool,
227}
228
229impl UpdateFlags {
230    /// Constructs `UpdateFlags` from the `Update_0` flags, in bit order.
231    pub const fn new(
232        status_update: bool,
233        ced_update: bool,
234        rr_test: bool,
235        source_test_update: bool,
236        frl_start: bool,
237        flt_update: bool,
238        rsed_update: bool,
239    ) -> Self {
240        Self {
241            status_update,
242            ced_update,
243            rr_test,
244            source_test_update,
245            frl_start,
246            flt_update,
247            rsed_update,
248        }
249    }
250
251    /// These flags as a clear: every flag but `RR_Test`, which the source does not clear.
252    pub const fn clearable(self) -> ClearableUpdateFlags {
253        ClearableUpdateFlags {
254            status_update: self.status_update,
255            ced_update: self.ced_update,
256            source_test_update: self.source_test_update,
257            frl_start: self.frl_start,
258            flt_update: self.flt_update,
259            rsed_update: self.rsed_update,
260        }
261    }
262}
263
264/// The `Update_0` flags a source may clear: every flag but `RR_Test` (bit 2).
265///
266/// Taken by [`ScdcClient::clear_update_flags`](crate::ScdcClient::clear_update_flags);
267/// mirrors culvert's `ClearableUpdateFlags` field for field. plumbob clears only the flags
268/// it acts on (`source_test_update`, `frl_start`, `flt_update`). `RR_Test` has no field
269/// because it belongs to the sink's read-request test; culvert's architecture document
270/// ("RR_Test is not cleared") gives the sources for that rule. The default clears nothing.
271#[non_exhaustive]
272#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
273pub struct ClearableUpdateFlags {
274    /// Bit 0: general status has changed.
275    pub status_update: bool,
276    /// Bit 1: CED counters have been updated.
277    pub ced_update: bool,
278    /// Bit 3: the sink has written `Source_Test_Configuration` (0x35).
279    pub source_test_update: bool,
280    /// Bit 4: link training passed.
281    pub frl_start: bool,
282    /// Bit 5: the per-lane link training pattern requests have changed.
283    pub flt_update: bool,
284    /// Bit 6: the Reed-Solomon correction count has been updated.
285    pub rsed_update: bool,
286}
287
288impl ClearableUpdateFlags {
289    /// Constructs `ClearableUpdateFlags` from the flags to clear, in bit order.
290    pub const fn new(
291        status_update: bool,
292        ced_update: bool,
293        source_test_update: bool,
294        frl_start: bool,
295        flt_update: bool,
296        rsed_update: bool,
297    ) -> Self {
298        Self {
299            status_update,
300            ced_update,
301            source_test_update,
302            frl_start,
303            flt_update,
304            rsed_update,
305        }
306    }
307}
308
309/// `Source_Test_Configuration` (0x35): instructions a compliance tester writes for the
310/// source. Bits 0 and 4 are reserved.
311///
312/// Mirrors culvert's `SourceTestConfig` field for field; plumbob records all six bits in
313/// [`TrainingEvent::SourceTestConfigRead`](crate::TrainingEvent::SourceTestConfigRead).
314/// plumbob acts on `flt_no_timeout`, and passes the three TxFFE bits to the PHY with
315/// every equalization (as [`TxFfeRestrictions`](crate::TxFfeRestrictions), re-sent when a
316/// `Source_Test_Update` seen in LTS:3 or LTS:P changes them). `frl_max` and
317/// `dsc_frl_max` ask the source to train at its highest rate; which rates to train at is
318/// the caller's choice, so the layer that builds the rate list reads this register
319/// (through its SCDC implementation) before training and honours them there. Build one
320/// with [`new`](Self::new), or from the default (no bit set) by setting fields.
321#[non_exhaustive]
322#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
323pub struct SourceTestConfig {
324    /// Bit 1: use pre-shoot only for TxFFE.
325    pub txffe_pre_shoot_only: bool,
326    /// Bit 2: use de-emphasis only for TxFFE.
327    pub txffe_de_emphasis_only: bool,
328    /// Bit 3: use no TxFFE.
329    pub txffe_no_ffe: bool,
330    /// Bit 5: the source should not time out link training. plumbob then suspends the
331    /// LTS:2, LTS:3 and LTS:P poll limits (up to `no_timeout_poll_cap`).
332    pub flt_no_timeout: bool,
333    /// Bit 6: the source should use the maximum FRL rate with DSC. With `frl_max` also
334    /// set, the Amlogic (citing the specification) and NVIDIA drivers act on neither; the
335    /// AMD driver trains at the maximum rate.
336    pub dsc_frl_max: bool,
337    /// Bit 7: the source should use the maximum FRL rate. With `dsc_frl_max` also set, the
338    /// Amlogic (citing the specification) and NVIDIA drivers act on neither; the AMD
339    /// driver trains at the maximum rate.
340    pub frl_max: bool,
341}
342
343impl SourceTestConfig {
344    /// Constructs `SourceTestConfig` from its flags, in bit order.
345    pub const fn new(
346        txffe_pre_shoot_only: bool,
347        txffe_de_emphasis_only: bool,
348        txffe_no_ffe: bool,
349        flt_no_timeout: bool,
350        dsc_frl_max: bool,
351        frl_max: bool,
352    ) -> Self {
353        Self {
354            txffe_pre_shoot_only,
355            txffe_de_emphasis_only,
356            txffe_no_ffe,
357            flt_no_timeout,
358            dsc_frl_max,
359            frl_max,
360        }
361    }
362}
363
364/// A 15-bit per-lane character error count.
365///
366/// The high byte's bit 7 is a validity flag consumed by [`CedCounters`];
367/// the counter occupies `bits[14:0]`. Values are always ≤ `0x7FFF`.
368#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub struct CedCount(u16);
370
371impl CedCount {
372    /// Constructs a `CedCount`, masking to 15 bits.
373    pub fn new(raw: u16) -> Self {
374        Self(raw & 0x7FFF)
375    }
376
377    /// Returns the character error count.
378    pub fn value(self) -> u16 {
379        self.0
380    }
381}
382
383/// Per-lane character error counts, for diagnostics; the training procedure does not
384/// consume them.
385///
386/// A lane's counter is `None` when its validity bit is not set. `lane3` is only
387/// populated in 4-lane FRL mode. Mirrors culvert's `CedCounters`; build one with
388/// [`new`](Self::new).
389#[non_exhaustive]
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391pub struct CedCounters {
392    /// Character error count for lane 0, or `None` if the validity bit is not set.
393    pub lane0: Option<CedCount>,
394    /// Character error count for lane 1, or `None` if the validity bit is not set.
395    pub lane1: Option<CedCount>,
396    /// Character error count for lane 2, or `None` if the validity bit is not set.
397    pub lane2: Option<CedCount>,
398    /// Character error count for lane 3, or `None` if the validity bit is not set.
399    /// Always `None` in TMDS mode or 3-lane FRL mode.
400    pub lane3: Option<CedCount>,
401}
402
403impl CedCounters {
404    /// Constructs a `CedCounters`.
405    pub const fn new(
406        lane0: Option<CedCount>,
407        lane1: Option<CedCount>,
408        lane2: Option<CedCount>,
409        lane3: Option<CedCount>,
410    ) -> Self {
411        Self {
412            lane0,
413            lane1,
414            lane2,
415            lane3,
416        }
417    }
418}
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423
424    // --- LtpReq ---
425
426    #[test]
427    fn ltp_req_from_value_gives_one_request_per_value() {
428        for value in 0u8..=0xF {
429            let request = LtpReq::from_value(value).unwrap();
430            assert_eq!(request.value(), value, "value {value:#x}");
431            assert_eq!(
432                matches!(request, LtpReq::Reserved(_)),
433                (0x9..=0xD).contains(&value),
434                "value {value:#x}"
435            );
436        }
437        assert_eq!(LtpReq::from_value(0x10), None);
438        assert_eq!(LtpReq::from_value(0xFF), None);
439    }
440
441    #[test]
442    fn ltp_req_values_match_status_flags_encoding() {
443        assert_eq!(LtpReq::None.value(), 0x0);
444        assert_eq!(LtpReq::AllOnes.value(), 0x1);
445        assert_eq!(LtpReq::AllZeros.value(), 0x2);
446        assert_eq!(LtpReq::NyquistClock.value(), 0x3);
447        assert_eq!(LtpReq::DdeCompliance.value(), 0x4);
448        assert_eq!(LtpReq::Lfsr0.value(), 0x5);
449        assert_eq!(LtpReq::Lfsr1.value(), 0x6);
450        assert_eq!(LtpReq::Lfsr2.value(), 0x7);
451        assert_eq!(LtpReq::Lfsr3.value(), 0x8);
452        assert_eq!(LtpReq::FfeChange.value(), 0xE);
453        assert_eq!(LtpReq::RateChange.value(), 0xF);
454        assert_eq!(LtpReq::Reserved(0xB).value(), 0xB);
455    }
456
457    #[test]
458    fn ltp_req_patterns_match_by_value() {
459        assert_eq!(LtpReq::AllOnes.pattern(), Some(LtpPattern::AllOnes));
460        assert_eq!(LtpReq::AllZeros.pattern(), Some(LtpPattern::AllZeros));
461        assert_eq!(
462            LtpReq::NyquistClock.pattern(),
463            Some(LtpPattern::NyquistClock)
464        );
465        assert_eq!(
466            LtpReq::DdeCompliance.pattern(),
467            Some(LtpPattern::DdeCompliance)
468        );
469        assert_eq!(LtpReq::Lfsr0.pattern(), Some(LtpPattern::Lfsr0));
470        assert_eq!(LtpReq::Lfsr1.pattern(), Some(LtpPattern::Lfsr1));
471        assert_eq!(LtpReq::Lfsr2.pattern(), Some(LtpPattern::Lfsr2));
472        assert_eq!(LtpReq::Lfsr3.pattern(), Some(LtpPattern::Lfsr3));
473    }
474
475    #[test]
476    fn ltp_req_patterns_share_their_value_with_ltp_pattern() {
477        for req in [
478            LtpReq::AllOnes,
479            LtpReq::AllZeros,
480            LtpReq::NyquistClock,
481            LtpReq::DdeCompliance,
482            LtpReq::Lfsr0,
483            LtpReq::Lfsr1,
484            LtpReq::Lfsr2,
485            LtpReq::Lfsr3,
486        ] {
487            assert_eq!(req.pattern().map(LtpPattern::value), Some(req.value()));
488        }
489    }
490
491    #[test]
492    fn ltp_req_signals_have_no_pattern() {
493        assert_eq!(LtpReq::None.pattern(), None);
494        assert_eq!(LtpReq::FfeChange.pattern(), None);
495        assert_eq!(LtpReq::RateChange.pattern(), None);
496        assert_eq!(LtpReq::Reserved(0x9).pattern(), None);
497    }
498
499    // --- FfeLevels ---
500
501    #[test]
502    fn ffe_levels_accepts_0_to_7() {
503        for level in 0..=7 {
504            assert_eq!(FfeLevels::new(level).map(FfeLevels::value), Some(level));
505        }
506    }
507
508    #[test]
509    fn ffe_levels_rejects_above_7() {
510        assert_eq!(FfeLevels::new(8), None);
511        assert_eq!(FfeLevels::new(u8::MAX), None);
512    }
513
514    #[test]
515    fn ffe_levels_max_and_default() {
516        assert_eq!(FfeLevels::MAX.value(), 7);
517        assert_eq!(FfeLevels::default().value(), 0);
518    }
519
520    #[test]
521    fn ffe_levels_limited_to_3_up_to_12_gbps() {
522        for rate in [
523            HdmiForumFrl::Rate3Gbps3Lanes,
524            HdmiForumFrl::Rate6Gbps3Lanes,
525            HdmiForumFrl::Rate6Gbps4Lanes,
526            HdmiForumFrl::Rate8Gbps4Lanes,
527            HdmiForumFrl::Rate10Gbps4Lanes,
528            HdmiForumFrl::Rate12Gbps4Lanes,
529        ] {
530            assert_eq!(FfeLevels::MAX.limited_to(rate).value(), 3);
531        }
532    }
533
534    #[test]
535    fn ffe_levels_below_the_limit_are_unchanged() {
536        let two = FfeLevels::new(2).unwrap();
537        assert_eq!(two.limited_to(HdmiForumFrl::Rate12Gbps4Lanes), two);
538        assert_eq!(
539            FfeLevels::default().limited_to(HdmiForumFrl::Rate3Gbps3Lanes),
540            FfeLevels::default()
541        );
542    }
543
544    // --- FrlConfig, UpdateFlags, SourceTestConfig ---
545
546    #[test]
547    fn update_flags_default_is_all_clear() {
548        let flags = UpdateFlags::default();
549        assert!(!flags.source_test_update);
550        assert!(!flags.frl_start);
551        assert!(!flags.flt_update);
552    }
553
554    #[test]
555    fn source_test_config_default_keeps_timeouts() {
556        assert!(!SourceTestConfig::default().flt_no_timeout);
557    }
558
559    #[test]
560    fn ltp_requests_compare_per_lane() {
561        let a = LtpRequests {
562            lane0: LtpReq::Lfsr0,
563            lane1: LtpReq::Lfsr1,
564            lane2: LtpReq::Lfsr2,
565            lane3: LtpReq::Lfsr3,
566        };
567        assert_eq!(a, a);
568        assert_ne!(
569            a,
570            LtpRequests {
571                lane3: LtpReq::None,
572                ..a
573            }
574        );
575    }
576
577    // --- CedCount ---
578
579    #[test]
580    fn ced_count_masks_validity_bit() {
581        // Bit 15 is the validity flag; it must be stripped.
582        let c = CedCount::new(0xFFFF);
583        assert_eq!(c.value(), 0x7FFF);
584    }
585
586    #[test]
587    fn ced_count_preserves_15_bit_value() {
588        let c = CedCount::new(0x0123);
589        assert_eq!(c.value(), 0x0123);
590    }
591
592    #[test]
593    fn ced_count_zero() {
594        assert_eq!(CedCount::new(0).value(), 0);
595    }
596
597    #[test]
598    fn ced_count_clone_eq() {
599        let a = CedCount::new(42);
600        assert_eq!(a, a.clone());
601        assert_ne!(CedCount::new(1), CedCount::new(2));
602    }
603
604    // --- UpdateFlags, ClearableUpdateFlags ---
605
606    #[test]
607    fn update_flags_new_takes_the_flags_in_bit_order() {
608        let bit = |n: usize| {
609            let b = |i| i == n;
610            UpdateFlags::new(b(0), b(1), b(2), b(3), b(4), b(5), b(6))
611        };
612        let set = |f: UpdateFlags| {
613            [
614                f.status_update,
615                f.ced_update,
616                f.rr_test,
617                f.source_test_update,
618                f.frl_start,
619                f.flt_update,
620                f.rsed_update,
621            ]
622        };
623        for n in 0..7 {
624            let expected: [bool; 7] = core::array::from_fn(|i| i == n);
625            assert_eq!(set(bit(n)), expected, "bit {n}");
626        }
627        assert_eq!(UpdateFlags::default(), bit(7));
628    }
629
630    #[test]
631    fn clearable_keeps_every_flag_but_rr_test() {
632        let all = UpdateFlags::new(true, true, true, true, true, true, true);
633        assert_eq!(
634            all.clearable(),
635            ClearableUpdateFlags::new(true, true, true, true, true, true)
636        );
637        let rr_test_only = UpdateFlags::new(false, false, true, false, false, false, false);
638        assert_eq!(rr_test_only.clearable(), ClearableUpdateFlags::default());
639    }
640
641    #[test]
642    fn source_test_config_new_takes_the_bits_in_bit_order() {
643        let set = |c: SourceTestConfig| {
644            [
645                c.txffe_pre_shoot_only,
646                c.txffe_de_emphasis_only,
647                c.txffe_no_ffe,
648                c.flt_no_timeout,
649                c.dsc_frl_max,
650                c.frl_max,
651            ]
652        };
653        for n in 0..6 {
654            let b = |i| i == n;
655            let expected: [bool; 6] = core::array::from_fn(|i| i == n);
656            assert_eq!(
657                set(SourceTestConfig::new(b(0), b(1), b(2), b(3), b(4), b(5))),
658                expected,
659                "field {n}"
660            );
661        }
662        assert_eq!(set(SourceTestConfig::default()), [false; 6]);
663    }
664
665    #[test]
666    fn clearable_update_flags_new_takes_the_flags_in_bit_order() {
667        let c = ClearableUpdateFlags::new(true, false, true, false, true, false);
668        assert!(c.status_update && c.source_test_update && c.flt_update);
669        assert!(!c.ced_update && !c.frl_start && !c.rsed_update);
670    }
671
672    // --- LtpRequests, CedCounters, FrlConfig constructors ---
673
674    #[test]
675    fn ltp_requests_new_takes_the_lanes_in_order() {
676        let r = LtpRequests::new(LtpReq::Lfsr0, LtpReq::Lfsr1, LtpReq::Lfsr2, LtpReq::Lfsr3);
677        assert_eq!(
678            [r.lane0, r.lane1, r.lane2, r.lane3],
679            [LtpReq::Lfsr0, LtpReq::Lfsr1, LtpReq::Lfsr2, LtpReq::Lfsr3]
680        );
681    }
682
683    #[test]
684    fn all_trained_checks_all_four_lanes() {
685        let none = LtpReq::None;
686        assert!(LtpRequests::new(none, none, none, none).all_trained());
687        assert!(!LtpRequests::new(LtpReq::Lfsr0, none, none, none).all_trained());
688        // Lane 3 counts, even though it is not in use in 3-lane FRL.
689        assert!(!LtpRequests::new(none, none, none, LtpReq::Reserved(0x9)).all_trained());
690    }
691
692    #[test]
693    fn ced_counters_new_takes_the_lanes_in_order() {
694        let c = CedCounters::new(
695            Some(CedCount::new(0)),
696            Some(CedCount::new(1)),
697            Some(CedCount::new(2)),
698            None,
699        );
700        assert_eq!(c.lane0, Some(CedCount::new(0)));
701        assert_eq!(c.lane1, Some(CedCount::new(1)));
702        assert_eq!(c.lane2, Some(CedCount::new(2)));
703        assert_eq!(c.lane3, None);
704    }
705
706    #[test]
707    fn frl_config_new_takes_the_rate_and_levels() {
708        let c = FrlConfig::new(HdmiForumFrl::Rate6Gbps4Lanes, FfeLevels::new(2).unwrap());
709        assert_eq!(c.frl_rate, HdmiForumFrl::Rate6Gbps4Lanes);
710        assert_eq!(c.ffe_levels, FfeLevels::new(2).unwrap());
711    }
712}