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 inner value is the raw pattern index from the SCDC Status_Flags register
7/// (`bits[7:4]`): 1 = LFSR0, 2 = LFSR1, 3 = LFSR2, 4 = LFSR3. A value of 0
8/// (no pattern) is the exit condition for the training loop and is never passed
9/// to this method.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct LtpPattern(u8);
12
13impl LtpPattern {
14    /// Constructs an `LtpPattern` from the raw pattern index.
15    ///
16    /// The caller is responsible for ensuring `raw` is a meaningful pattern index
17    /// (1–4 for LFSR0–LFSR3, or 0 for the exit condition). This type does not
18    /// validate the value; semantic checking belongs in the protocol layer.
19    pub fn new(raw: u8) -> Self {
20        Self(raw)
21    }
22
23    /// Returns the raw pattern index.
24    pub fn value(self) -> u8 {
25        self.0
26    }
27}
28
29/// Per-lane equalization parameters carried by [`EqParams`].
30///
31/// Fields will be defined as the link training layer is implemented and per-lane
32/// hardware requirements become known.
33#[non_exhaustive]
34#[derive(Debug, Clone, Copy, Default)]
35pub struct LaneEqParams {}
36
37/// Equalization parameters passed from link training feedback to the PHY.
38///
39/// Carries per-lane adjustment data derived from character error detection (CED)
40/// feedback during the FRL training loop. `lane3` is `None` in 3-lane FRL mode.
41///
42/// Per-lane field contents will be defined as the link training layer is implemented.
43#[non_exhaustive]
44#[derive(Debug, Clone, Copy, Default)]
45pub struct EqParams {
46    /// Equalization parameters for lane 0.
47    pub lane0: LaneEqParams,
48    /// Equalization parameters for lane 1.
49    pub lane1: LaneEqParams,
50    /// Equalization parameters for lane 2.
51    pub lane2: LaneEqParams,
52    /// Equalization parameters for lane 3. `None` in 3-lane FRL mode.
53    pub lane3: Option<LaneEqParams>,
54}
55
56impl EqParams {
57    /// Create a new `EqParams` with default values.
58    pub fn new() -> Self {
59        Self::default()
60    }
61}
62
63/// PHY lane configuration for an HDMI 2.1 transmitter or receiver.
64///
65/// Abstracts the register sequences required to configure an HDMI 2.1 PHY: lane
66/// mapping, pre-emphasis, equalization, scrambling, and FRL rate selection.
67/// Vendor-specific register sequences are an implementation detail of each backend.
68pub trait HdmiPhy {
69    /// Error type returned by PHY operations.
70    type Error;
71
72    /// Select the FRL rate (or TMDS). Triggers the required lane reconfiguration sequence.
73    fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), Self::Error>;
74
75    /// Drive the given link training pattern on the physical lanes.
76    fn send_ltp(&mut self, pattern: LtpPattern) -> Result<(), Self::Error>;
77
78    /// Adjust equalization parameters after link training feedback.
79    fn adjust_equalization(&mut self, params: EqParams) -> Result<(), Self::Error>;
80
81    /// Enable or disable scrambling on the PHY.
82    fn set_scrambling(&mut self, enabled: bool) -> Result<(), Self::Error>;
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use display_types::cea861::hdmi_forum::HdmiForumFrl;
89
90    struct MockPhy {
91        frl_rate: Option<HdmiForumFrl>,
92        scrambling: Option<bool>,
93        eq_calls: u32,
94        last_ltp: Option<LtpPattern>,
95    }
96
97    impl MockPhy {
98        fn new() -> Self {
99            Self {
100                frl_rate: None,
101                scrambling: None,
102                eq_calls: 0,
103                last_ltp: None,
104            }
105        }
106    }
107
108    impl HdmiPhy for MockPhy {
109        type Error = core::convert::Infallible;
110
111        fn send_ltp(&mut self, pattern: LtpPattern) -> Result<(), Self::Error> {
112            self.last_ltp = Some(pattern);
113            Ok(())
114        }
115
116        fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), Self::Error> {
117            self.frl_rate = Some(rate);
118            Ok(())
119        }
120
121        fn adjust_equalization(&mut self, _params: EqParams) -> Result<(), Self::Error> {
122            self.eq_calls += 1;
123            Ok(())
124        }
125
126        fn set_scrambling(&mut self, enabled: bool) -> Result<(), Self::Error> {
127            self.scrambling = Some(enabled);
128            Ok(())
129        }
130    }
131
132    #[test]
133    fn ltp_pattern_value() {
134        assert_eq!(LtpPattern::new(1).value(), 1);
135        assert_eq!(LtpPattern::new(4).value(), 4);
136    }
137
138    #[test]
139    fn ltp_pattern_clone_eq() {
140        let a = LtpPattern::new(2);
141        assert_eq!(a, a);
142        assert_ne!(LtpPattern::new(1), LtpPattern::new(2));
143    }
144
145    #[test]
146    fn send_ltp_records_pattern() {
147        let mut phy = MockPhy::new();
148        phy.send_ltp(LtpPattern::new(1)).unwrap();
149        assert_eq!(phy.last_ltp, Some(LtpPattern::new(1)));
150    }
151
152    #[test]
153    fn send_ltp_updates_on_each_call() {
154        let mut phy = MockPhy::new();
155        phy.send_ltp(LtpPattern::new(1)).unwrap();
156        phy.send_ltp(LtpPattern::new(3)).unwrap();
157        assert_eq!(phy.last_ltp, Some(LtpPattern::new(3)));
158    }
159
160    #[test]
161    fn eq_params_constructors_are_equivalent() {
162        let _a = EqParams::new();
163        let _b = EqParams::default();
164    }
165
166    #[test]
167    fn set_frl_rate_records_rate() {
168        let mut phy = MockPhy::new();
169        phy.set_frl_rate(HdmiForumFrl::Rate6Gbps4Lanes).unwrap();
170        assert_eq!(phy.frl_rate, Some(HdmiForumFrl::Rate6Gbps4Lanes));
171    }
172
173    #[test]
174    fn set_frl_rate_not_supported_is_valid() {
175        let mut phy = MockPhy::new();
176        phy.set_frl_rate(HdmiForumFrl::NotSupported).unwrap();
177        assert_eq!(phy.frl_rate, Some(HdmiForumFrl::NotSupported));
178    }
179
180    #[test]
181    fn set_frl_rate_can_be_updated() {
182        let mut phy = MockPhy::new();
183        phy.set_frl_rate(HdmiForumFrl::Rate3Gbps3Lanes).unwrap();
184        phy.set_frl_rate(HdmiForumFrl::Rate12Gbps4Lanes).unwrap();
185        assert_eq!(phy.frl_rate, Some(HdmiForumFrl::Rate12Gbps4Lanes));
186    }
187
188    #[test]
189    fn set_scrambling_enable() {
190        let mut phy = MockPhy::new();
191        phy.set_scrambling(true).unwrap();
192        assert_eq!(phy.scrambling, Some(true));
193    }
194
195    #[test]
196    fn set_scrambling_disable() {
197        let mut phy = MockPhy::new();
198        phy.set_scrambling(false).unwrap();
199        assert_eq!(phy.scrambling, Some(false));
200    }
201
202    #[test]
203    fn set_scrambling_can_be_toggled() {
204        let mut phy = MockPhy::new();
205        phy.set_scrambling(true).unwrap();
206        phy.set_scrambling(false).unwrap();
207        assert_eq!(phy.scrambling, Some(false));
208    }
209
210    #[test]
211    fn adjust_equalization_tracks_call_count() {
212        let mut phy = MockPhy::new();
213        phy.adjust_equalization(EqParams::new()).unwrap();
214        phy.adjust_equalization(EqParams::new()).unwrap();
215        assert_eq!(phy.eq_calls, 2);
216    }
217}