Skip to main content

hdmi_hal/phy/
mod.rs

1use display_types::cea861::hdmi_forum::HdmiForumFrl;
2
3/// A link training pattern to be driven on the physical lanes.
4///
5/// Produced by the link training state machine and passed to [`HdmiPhy::send_ltp`].
6/// The discriminants are the pattern values a sink requests in the SCDC Status_Flags
7/// LTP fields. Request values that are not patterns (0 for no pattern, and the TxFFE
8/// and rate change requests) are handled by the link training layer and have no variant.
9#[non_exhaustive]
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[repr(u8)]
12pub enum LtpPattern {
13    /// All ones.
14    AllOnes = 1,
15    /// All zeros.
16    AllZeros = 2,
17    /// Nyquist clock pattern.
18    NyquistClock = 3,
19    /// DDE (Data Dependent Equalization) compliance pattern.
20    DdeCompliance = 4,
21    /// LFSR pattern 0.
22    Lfsr0 = 5,
23    /// LFSR pattern 1.
24    Lfsr1 = 6,
25    /// LFSR pattern 2.
26    Lfsr2 = 7,
27    /// LFSR pattern 3.
28    Lfsr3 = 8,
29}
30
31impl LtpPattern {
32    /// Returns the pattern's SCDC value (1–8).
33    pub const fn value(self) -> u8 {
34        self as u8
35    }
36}
37
38/// The link training pattern for each lane, passed to [`HdmiPhy::send_ltp`].
39///
40/// Always the full per-lane set: the PHY applies it as given, and the link training
41/// layer tracks which pattern each lane carries. `None` means no training pattern on
42/// that lane; `None` on every lane stops the training patterns. `lane3` is `None` in
43/// 3-lane FRL mode.
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
45pub struct LanePatterns {
46    /// Pattern for lane 0.
47    pub lane0: Option<LtpPattern>,
48    /// Pattern for lane 1.
49    pub lane1: Option<LtpPattern>,
50    /// Pattern for lane 2.
51    pub lane2: Option<LtpPattern>,
52    /// Pattern for lane 3. `None` in 3-lane FRL mode.
53    pub lane3: Option<LtpPattern>,
54}
55
56/// What the transmitter sends on the FRL lanes once training patterns are stopped.
57///
58/// Passed to [`HdmiPhy::set_frl_output`].
59///
60/// Deliberately exhaustive, unlike [`LtpPattern`]. The two variants are the two states of
61/// an FRL link, training and carrying content, not a list that grows with each HDMI
62/// version. A PHY has to implement both, and has no sensible behaviour for a mode it does
63/// not know: a wildcard arm could only fail. If a later HDMI version adds a mode, it should
64/// reach every implementation as a compile error in a breaking release, not as a runtime
65/// error from a PHY that was never updated.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67pub enum FrlOutput {
68    /// Gap characters only, with no video, data islands or control periods. Used during
69    /// link training and while waiting for the sink to start FRL.
70    GapOnly,
71    /// Video, data islands and control periods. Set by the caller once link training
72    /// has succeeded.
73    Active,
74}
75
76/// A TxFFE (transmitter feed-forward equalization) level index, 0–7.
77///
78/// The level a lane's transmitter applies during FRL training. The range is checked on
79/// construction; the lower per-rate limit the source advertises to the sink is the link
80/// training layer's concern.
81#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub struct TxFfeLevel(u8);
83
84impl TxFfeLevel {
85    /// The highest TxFFE level, 7.
86    pub const MAX: Self = Self(7);
87
88    /// Returns the level for `level`, or `None` if it is above 7.
89    pub const fn new(level: u8) -> Option<Self> {
90        if level <= Self::MAX.0 {
91            Some(Self(level))
92        } else {
93            None
94        }
95    }
96
97    /// Returns the level index (0–7).
98    pub const fn value(self) -> u8 {
99        self.0
100    }
101}
102
103/// Per-lane equalization parameters carried by [`EqParams`].
104#[non_exhaustive]
105#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
106pub struct LaneEqParams {
107    /// TxFFE level for the lane. Defaults to 0.
108    pub tx_ffe_level: TxFfeLevel,
109}
110
111/// Restrictions on the TxFFE components, as a compliance tester sets them.
112///
113/// TxFFE combines two components: pre-shoot (pre-cursor) and de-emphasis (post-cursor).
114/// The sink's `Source_Test_Configuration` register (SCDC 0x35, bits 1–3) can restrict them,
115/// and the three bits are carried here exactly as written, for the whole link. Each bit's
116/// plain meaning:
117///
118/// - `pre_shoot_only` (`TxFFE_Pre_Shoot_Only`): pre-shoot only, no de-emphasis.
119/// - `de_emphasis_only` (`TxFFE_De_Emphasis_Only`): de-emphasis only, no pre-shoot.
120/// - `no_ffe` (`TxFFE_No_FFE`): neither.
121///
122/// The default (no bit set) restricts nothing. The bits accompany a compliance test: the
123/// reference drivers that act on them do so while the sink requests the DDE compliance
124/// pattern (LTP 4).
125///
126/// How to combine them is the PHY's to decide, because the reference implementations
127/// disagree and the specification text is not available to this crate. The AMD display
128/// driver (`dpcs30_program_eq_setting`) treats each "only" bit as removing the other
129/// component, so both set gives no FFE, and `no_ffe` overrides both. The Rockchip HDPTX
130/// PHY driver treats bits 1–2 as enabling their component, so both set gives full FFE,
131/// and does not read `no_ffe`. A backend documents which it follows. The level in each
132/// lane's [`LaneEqParams`] still applies within what the restrictions allow.
133#[non_exhaustive]
134#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
135pub struct TxFfeRestrictions {
136    /// `TxFFE_Pre_Shoot_Only` (bit 1): use pre-shoot only.
137    pub pre_shoot_only: bool,
138    /// `TxFFE_De_Emphasis_Only` (bit 2): use de-emphasis only.
139    pub de_emphasis_only: bool,
140    /// `TxFFE_No_FFE` (bit 3): use no TxFFE.
141    pub no_ffe: bool,
142}
143
144impl TxFfeRestrictions {
145    /// Constructs `TxFfeRestrictions` from the three bits, in register order.
146    pub const fn new(pre_shoot_only: bool, de_emphasis_only: bool, no_ffe: bool) -> Self {
147        Self {
148            pre_shoot_only,
149            de_emphasis_only,
150            no_ffe,
151        }
152    }
153}
154
155/// Equalization parameters passed from link training to the PHY.
156///
157/// Carries each lane's TxFFE level, raised during FRL link training when the sink requests
158/// it (an FFE change, 0xE, in its link training requests), and the link-wide
159/// [`TxFfeRestrictions`] a compliance tester may set. `lane3` is `None` in 3-lane FRL mode.
160#[non_exhaustive]
161#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
162pub struct EqParams {
163    /// Equalization parameters for lane 0.
164    pub lane0: LaneEqParams,
165    /// Equalization parameters for lane 1.
166    pub lane1: LaneEqParams,
167    /// Equalization parameters for lane 2.
168    pub lane2: LaneEqParams,
169    /// Equalization parameters for lane 3. `None` in 3-lane FRL mode.
170    pub lane3: Option<LaneEqParams>,
171    /// Restrictions on the TxFFE components, for every lane. Defaults to none.
172    pub tx_ffe_restrictions: TxFfeRestrictions,
173}
174
175impl EqParams {
176    /// Create a new `EqParams` with default values.
177    pub fn new() -> Self {
178        Self::default()
179    }
180}
181
182/// Link-level control of an HDMI 2.1 transmitter PHY.
183///
184/// Covers what FRL link training needs from the transmitter: FRL rate selection, link
185/// training patterns, FRL output mode, scrambling, and each lane's TxFFE level (FRL's
186/// transmitter equalization, a combination of pre-shoot and de-emphasis) through
187/// [`adjust_equalization`](Self::adjust_equalization). Further PHY configuration (lane
188/// mapping, TMDS pre-emphasis and swing, spread-spectrum clocking and power states) is not
189/// covered yet; it is planned for when the mode-setting layer needs it. Vendor-specific
190/// register sequences are an implementation detail of each backend.
191pub trait HdmiPhy {
192    /// Error type returned by PHY operations.
193    type Error;
194
195    /// Select the FRL rate (or TMDS). Triggers the required lane reconfiguration sequence.
196    ///
197    /// Returns once the PHY transmits at `rate`, after any bring-up its hardware needs to
198    /// get there — the Xilinx HDMI 2.1 transmitter, for example, holds a Nyquist clock
199    /// pattern until its link is up. Only the PHY knows when that is, so the sequence
200    /// belongs here; the link training layer sends no pattern of its own around it.
201    fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), Self::Error>;
202
203    /// Drive the given link training patterns on the physical lanes, one per lane.
204    fn send_ltp(&mut self, patterns: LanePatterns) -> Result<(), Self::Error>;
205
206    /// Select what the transmitter sends on the FRL lanes: gap characters only, or video,
207    /// data islands and control periods.
208    fn set_frl_output(&mut self, output: FrlOutput) -> Result<(), Self::Error>;
209
210    /// Apply equalization: each lane's TxFFE level, within the link-wide
211    /// [`TxFfeRestrictions`] in `params`. Each call carries the complete settings, so the
212    /// restrictions in force are always the last ones given.
213    fn adjust_equalization(&mut self, params: EqParams) -> Result<(), Self::Error>;
214
215    /// Enable or disable scrambling on the PHY.
216    fn set_scrambling(&mut self, enabled: bool) -> Result<(), Self::Error>;
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use display_types::cea861::hdmi_forum::HdmiForumFrl;
223
224    /// A minimal `HdmiPhy` that records what it is given. The tests that drive it method by
225    /// method check little beyond recording — they show that the trait can be implemented
226    /// and used for each method, and they keep this implementation covered. Behaviour is
227    /// tested where it lives: in plumbob, against its simulated PHY.
228    struct MockPhy {
229        frl_rate: Option<HdmiForumFrl>,
230        scrambling: Option<bool>,
231        eq_calls: u32,
232        last_eq: Option<EqParams>,
233        frl_output: Option<FrlOutput>,
234        last_ltp: Option<LanePatterns>,
235    }
236
237    impl MockPhy {
238        fn new() -> Self {
239            Self {
240                frl_rate: None,
241                scrambling: None,
242                eq_calls: 0,
243                last_eq: None,
244                frl_output: None,
245                last_ltp: None,
246            }
247        }
248    }
249
250    impl HdmiPhy for MockPhy {
251        type Error = core::convert::Infallible;
252
253        fn send_ltp(&mut self, patterns: LanePatterns) -> Result<(), Self::Error> {
254            self.last_ltp = Some(patterns);
255            Ok(())
256        }
257
258        fn set_frl_output(&mut self, output: FrlOutput) -> Result<(), Self::Error> {
259            self.frl_output = Some(output);
260            Ok(())
261        }
262
263        fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), Self::Error> {
264            self.frl_rate = Some(rate);
265            Ok(())
266        }
267
268        fn adjust_equalization(&mut self, params: EqParams) -> Result<(), Self::Error> {
269            self.eq_calls += 1;
270            self.last_eq = Some(params);
271            Ok(())
272        }
273
274        fn set_scrambling(&mut self, enabled: bool) -> Result<(), Self::Error> {
275            self.scrambling = Some(enabled);
276            Ok(())
277        }
278    }
279
280    #[test]
281    fn ltp_pattern_values_match_scdc_encoding() {
282        assert_eq!(LtpPattern::AllOnes.value(), 1);
283        assert_eq!(LtpPattern::AllZeros.value(), 2);
284        assert_eq!(LtpPattern::NyquistClock.value(), 3);
285        assert_eq!(LtpPattern::DdeCompliance.value(), 4);
286        assert_eq!(LtpPattern::Lfsr0.value(), 5);
287        assert_eq!(LtpPattern::Lfsr1.value(), 6);
288        assert_eq!(LtpPattern::Lfsr2.value(), 7);
289        assert_eq!(LtpPattern::Lfsr3.value(), 8);
290    }
291
292    #[test]
293    fn ltp_pattern_clone_eq() {
294        let a = LtpPattern::Lfsr1;
295        assert_eq!(a, a);
296        assert_ne!(LtpPattern::Lfsr0, LtpPattern::Lfsr1);
297    }
298
299    #[test]
300    fn lane_patterns_default_is_no_pattern() {
301        let p = LanePatterns::default();
302        assert_eq!(p.lane0, None);
303        assert_eq!(p.lane1, None);
304        assert_eq!(p.lane2, None);
305        assert_eq!(p.lane3, None);
306    }
307
308    #[test]
309    fn send_ltp_records_patterns() {
310        let mut phy = MockPhy::new();
311        let patterns = LanePatterns {
312            lane0: Some(LtpPattern::Lfsr0),
313            lane1: Some(LtpPattern::Lfsr1),
314            lane2: Some(LtpPattern::Lfsr2),
315            lane3: None,
316        };
317        phy.send_ltp(patterns).unwrap();
318        assert_eq!(phy.last_ltp, Some(patterns));
319    }
320
321    #[test]
322    fn send_ltp_updates_on_each_call() {
323        let mut phy = MockPhy::new();
324        let first = LanePatterns {
325            lane0: Some(LtpPattern::Lfsr0),
326            ..LanePatterns::default()
327        };
328        let second = LanePatterns {
329            lane0: Some(LtpPattern::NyquistClock),
330            ..first
331        };
332        phy.send_ltp(first).unwrap();
333        phy.send_ltp(second).unwrap();
334        assert_eq!(phy.last_ltp, Some(second));
335    }
336
337    #[test]
338    fn eq_params_constructors_are_equivalent() {
339        let _a = EqParams::new();
340        let _b = EqParams::default();
341    }
342
343    #[test]
344    fn set_frl_rate_records_rate() {
345        let mut phy = MockPhy::new();
346        phy.set_frl_rate(HdmiForumFrl::Rate6Gbps4Lanes).unwrap();
347        assert_eq!(phy.frl_rate, Some(HdmiForumFrl::Rate6Gbps4Lanes));
348    }
349
350    #[test]
351    fn set_frl_rate_not_supported_is_valid() {
352        let mut phy = MockPhy::new();
353        phy.set_frl_rate(HdmiForumFrl::NotSupported).unwrap();
354        assert_eq!(phy.frl_rate, Some(HdmiForumFrl::NotSupported));
355    }
356
357    #[test]
358    fn set_frl_rate_can_be_updated() {
359        let mut phy = MockPhy::new();
360        phy.set_frl_rate(HdmiForumFrl::Rate3Gbps3Lanes).unwrap();
361        phy.set_frl_rate(HdmiForumFrl::Rate12Gbps4Lanes).unwrap();
362        assert_eq!(phy.frl_rate, Some(HdmiForumFrl::Rate12Gbps4Lanes));
363    }
364
365    #[test]
366    fn set_scrambling_enable() {
367        let mut phy = MockPhy::new();
368        phy.set_scrambling(true).unwrap();
369        assert_eq!(phy.scrambling, Some(true));
370    }
371
372    #[test]
373    fn set_scrambling_disable() {
374        let mut phy = MockPhy::new();
375        phy.set_scrambling(false).unwrap();
376        assert_eq!(phy.scrambling, Some(false));
377    }
378
379    #[test]
380    fn set_scrambling_can_be_toggled() {
381        let mut phy = MockPhy::new();
382        phy.set_scrambling(true).unwrap();
383        phy.set_scrambling(false).unwrap();
384        assert_eq!(phy.scrambling, Some(false));
385    }
386
387    #[test]
388    fn set_frl_output_records_mode() {
389        let mut phy = MockPhy::new();
390        phy.set_frl_output(FrlOutput::GapOnly).unwrap();
391        assert_eq!(phy.frl_output, Some(FrlOutput::GapOnly));
392    }
393
394    #[test]
395    fn set_frl_output_can_be_switched() {
396        let mut phy = MockPhy::new();
397        phy.set_frl_output(FrlOutput::GapOnly).unwrap();
398        phy.set_frl_output(FrlOutput::Active).unwrap();
399        assert_eq!(phy.frl_output, Some(FrlOutput::Active));
400    }
401
402    #[test]
403    fn value_types_can_be_hashed() {
404        extern crate std;
405        use std::collections::HashSet;
406        let patterns: HashSet<LanePatterns> = [LanePatterns::default(), LanePatterns::default()]
407            .into_iter()
408            .collect();
409        assert_eq!(patterns.len(), 1);
410        let outputs: HashSet<FrlOutput> = [FrlOutput::GapOnly, FrlOutput::Active].into();
411        assert_eq!(outputs.len(), 2);
412        let levels: HashSet<TxFfeLevel> = [TxFfeLevel::MAX, TxFfeLevel::default()].into();
413        assert_eq!(levels.len(), 2);
414        let eq: HashSet<EqParams> = [EqParams::new(), EqParams::new()].into();
415        assert_eq!(eq.len(), 1);
416        let ltp: HashSet<LtpPattern> = [LtpPattern::Lfsr0, LtpPattern::Lfsr0].into();
417        assert_eq!(ltp.len(), 1);
418    }
419
420    #[test]
421    fn tx_ffe_level_accepts_0_to_7() {
422        for level in 0..=7 {
423            assert_eq!(TxFfeLevel::new(level).map(TxFfeLevel::value), Some(level));
424        }
425    }
426
427    #[test]
428    fn tx_ffe_level_rejects_above_7() {
429        assert_eq!(TxFfeLevel::new(8), None);
430        assert_eq!(TxFfeLevel::new(u8::MAX), None);
431    }
432
433    #[test]
434    fn tx_ffe_level_max_and_default() {
435        assert_eq!(TxFfeLevel::MAX.value(), 7);
436        assert_eq!(TxFfeLevel::default().value(), 0);
437        assert!(TxFfeLevel::default() < TxFfeLevel::MAX);
438    }
439
440    #[test]
441    fn lane_eq_params_default_tx_ffe_level_is_0() {
442        assert_eq!(LaneEqParams::default().tx_ffe_level, TxFfeLevel::default());
443    }
444
445    #[test]
446    fn tx_ffe_restrictions_new_takes_the_bits_in_register_order() {
447        let r = TxFfeRestrictions::new(true, false, false);
448        assert!(r.pre_shoot_only && !r.de_emphasis_only && !r.no_ffe);
449        let r = TxFfeRestrictions::new(false, true, false);
450        assert!(!r.pre_shoot_only && r.de_emphasis_only && !r.no_ffe);
451        let r = TxFfeRestrictions::new(false, false, true);
452        assert!(!r.pre_shoot_only && !r.de_emphasis_only && r.no_ffe);
453        // Combinations are carried as written; the PHY decides what they mean.
454        let both = TxFfeRestrictions::new(true, true, false);
455        assert!(both.pre_shoot_only && both.de_emphasis_only);
456    }
457
458    #[test]
459    fn eq_params_restrict_nothing_by_default() {
460        assert_eq!(
461            TxFfeRestrictions::default(),
462            TxFfeRestrictions::new(false, false, false)
463        );
464        assert_eq!(
465            EqParams::new().tx_ffe_restrictions,
466            TxFfeRestrictions::default()
467        );
468    }
469
470    #[test]
471    fn adjust_equalization_records_per_lane_levels() {
472        let mut phy = MockPhy::new();
473        let mut params = EqParams::new();
474        params.lane1.tx_ffe_level = TxFfeLevel::MAX;
475        phy.adjust_equalization(params).unwrap();
476        let last = phy.last_eq.unwrap();
477        assert_eq!(last, params);
478        assert_eq!(last.lane0.tx_ffe_level.value(), 0);
479        assert_eq!(last.lane1.tx_ffe_level.value(), 7);
480    }
481
482    #[test]
483    fn adjust_equalization_tracks_call_count() {
484        let mut phy = MockPhy::new();
485        phy.adjust_equalization(EqParams::new()).unwrap();
486        phy.adjust_equalization(EqParams::new()).unwrap();
487        assert_eq!(phy.eq_calls, 2);
488    }
489}