Skip to main content

culvert_async/client/
frl.rs

1use hdmi_hal_async::scdc::ScdcTransport;
2
3use crate::ScdcError;
4use crate::{Config0, FrlConfig, LtpRequests, SourceTestConfig, StatusFlags};
5use culvert::codec;
6
7use super::Scdc;
8
9impl<T: ScdcTransport> Scdc<T> {
10    /// Writes FRL training configuration to `Config_1` (0x31).
11    ///
12    /// Encodes `FRL_Rate` into bits\[3:0\] and `FFE_Levels` into bits\[7:4\].
13    ///
14    /// Returns [`crate::ProtocolError::FfeLevelsOutOfRange`] without writing anything if
15    /// the FFE levels exceed [`FfeLevels::max_for`](crate::FfeLevels::max_for) the
16    /// requested rate.
17    pub async fn write_frl_config(&mut self, config: FrlConfig) -> Result<(), ScdcError<T::Error>> {
18        let byte = codec::encode_config_1(config).map_err(ScdcError::Protocol)?;
19        self.transport
20            .write(codec::CONFIG_1, byte)
21            .await
22            .map_err(ScdcError::Transport)
23    }
24
25    /// Writes `Config_0` (0x30): `RR_Enable` (bit 0) and `FLT_No_Retrain` (bit 1).
26    pub async fn write_config_0(&mut self, config: Config0) -> Result<(), ScdcError<T::Error>> {
27        self.transport
28            .write(codec::CONFIG_0, codec::encode_config_0(config))
29            .await
30            .map_err(ScdcError::Transport)
31    }
32
33    /// Reads `Source_Test_Configuration` (0x35), written by the sink to instruct the
34    /// source during compliance testing.
35    pub async fn read_source_test_config(
36        &mut self,
37    ) -> Result<SourceTestConfig, ScdcError<T::Error>> {
38        let byte = self
39            .transport
40            .read(codec::SOURCE_TEST_CONFIG)
41            .await
42            .map_err(ScdcError::Transport)?;
43        Ok(codec::decode_source_test_config(byte))
44    }
45
46    /// Reads `Status_Flags_0` (0x40): clock detection, lane lock, `FLT_Ready` and DSC
47    /// decode failure.
48    pub async fn read_status_flags(&mut self) -> Result<StatusFlags, ScdcError<T::Error>> {
49        let flags0 = self
50            .transport
51            .read(codec::STATUS_FLAGS_0)
52            .await
53            .map_err(ScdcError::Transport)?;
54        Ok(codec::decode_status_flags(flags0))
55    }
56
57    /// Reads the per-lane link training pattern requests from `Status_Flags_1` (0x41,
58    /// lanes 0–1) and `Status_Flags_2` (0x42, lanes 2–3).
59    ///
60    /// A value the HDMI 2.1 specification leaves undefined (0x9–0xD) is
61    /// [`LtpReq::Reserved`](crate::LtpReq::Reserved), not an error.
62    pub async fn read_ltp_requests(&mut self) -> Result<LtpRequests, ScdcError<T::Error>> {
63        let flags1 = self
64            .transport
65            .read(codec::STATUS_FLAGS_1)
66            .await
67            .map_err(ScdcError::Transport)?;
68        let flags2 = self
69            .transport
70            .read(codec::STATUS_FLAGS_2)
71            .await
72            .map_err(ScdcError::Transport)?;
73        Ok(codec::decode_ltp_requests(flags1, flags2))
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::super::Scdc;
80    use super::super::test_transport::{Op, TestTransport};
81    use crate::{Config0, FfeLevels, FrlConfig, LtpReq, SourceTestConfig, StatusFlags};
82    use crate::{ProtocolError, ScdcError};
83    use display_types::HdmiForumFrl;
84
85    #[pollster::test]
86    async fn frl_config_rate_field() {
87        let mut scdc = Scdc::new(TestTransport::new());
88        scdc.write_frl_config(FrlConfig::new(
89            HdmiForumFrl::Rate12Gbps4Lanes, // discriminant 6
90            FfeLevels::default(),
91        ))
92        .await
93        .unwrap();
94        let t = scdc.into_transport();
95        assert_eq!(t.get(0x31), 0x06);
96        assert_eq!(t.get(0x30), 0x00); // Config_0 untouched
97    }
98
99    #[pollster::test]
100    async fn frl_config_ffe_levels_field() {
101        let mut scdc = Scdc::new(TestTransport::new());
102        scdc.write_frl_config(FrlConfig::new(
103            HdmiForumFrl::Rate12Gbps4Lanes,
104            FfeLevels::new(3).unwrap(), // → bits[7:4]
105        ))
106        .await
107        .unwrap();
108        assert_eq!(scdc.into_transport().get(0x31), 0x36);
109    }
110
111    #[pollster::test]
112    async fn frl_config_rejects_ffe_levels_above_rate_maximum() {
113        let mut scdc = Scdc::new(TestTransport::new());
114        let result = scdc
115            .write_frl_config(FrlConfig::new(
116                HdmiForumFrl::Rate12Gbps4Lanes,
117                FfeLevels::new(4).unwrap(),
118            ))
119            .await;
120        assert!(matches!(
121            result,
122            Err(ScdcError::Protocol(ProtocolError::FfeLevelsOutOfRange {
123                rate: HdmiForumFrl::Rate12Gbps4Lanes,
124                levels: 4
125            }))
126        ));
127        // Nothing is written.
128        assert_eq!(scdc.into_transport().get(0x31), 0x00);
129    }
130
131    #[pollster::test]
132    async fn status_flags_all_zero() {
133        let f = Scdc::new(TestTransport::new())
134            .read_status_flags()
135            .await
136            .unwrap();
137        assert_eq!(
138            f,
139            StatusFlags::new(false, false, false, false, false, false, false)
140        );
141    }
142
143    #[pollster::test]
144    async fn status_flags_individual_bits() {
145        let read = |byte: u8| {
146            let mut sim = TestTransport::new();
147            sim.set(0x40, byte);
148            pollster::block_on(Scdc::new(sim).read_status_flags()).unwrap()
149        };
150        assert!(read(0x01).clock_detected);
151        assert!(read(0x02).ch0_locked);
152        assert!(read(0x04).ch1_locked);
153        assert!(read(0x08).ch2_locked);
154        assert!(read(0x10).ln3_locked);
155        assert!(read(0x40).flt_ready);
156        assert!(read(0x80).dsc_decode_fail);
157        // Bit 5 is not defined; setting it alone sets no field.
158        assert_eq!(read(0x20), read(0x00));
159    }
160
161    #[pollster::test]
162    async fn status_flags_reads_only_status_flags_0() {
163        let mut sim = TestTransport::new();
164        sim.set(0x41, 0xFF);
165        sim.set(0x42, 0xFF);
166        let f = Scdc::new(sim).read_status_flags().await.unwrap();
167        assert!(!f.flt_ready && !f.clock_detected);
168    }
169
170    #[pollster::test]
171    async fn ltp_requests_per_lane() {
172        let mut sim = TestTransport::new();
173        sim.set(0x41, 0x65); // lane0 = 0x5 (LFSR0), lane1 = 0x6 (LFSR1)
174        sim.set(0x42, 0xF1); // lane2 = 0x1 (all ones), lane3 = 0xF (rate change)
175        let r = Scdc::new(sim).read_ltp_requests().await.unwrap();
176        assert_eq!(r.lane0, LtpReq::Lfsr0);
177        assert_eq!(r.lane1, LtpReq::Lfsr1);
178        assert_eq!(r.lane2, LtpReq::AllOnes);
179        assert_eq!(r.lane3, LtpReq::RateChange);
180        assert!(!r.all_trained());
181    }
182
183    #[pollster::test]
184    async fn ltp_requests_all_defined_values() {
185        for (nibble, req) in [
186            (0x0u8, LtpReq::None),
187            (0x1, LtpReq::AllOnes),
188            (0x2, LtpReq::AllZeros),
189            (0x3, LtpReq::NyquistClock),
190            (0x4, LtpReq::DdeCompliance),
191            (0x5, LtpReq::Lfsr0),
192            (0x6, LtpReq::Lfsr1),
193            (0x7, LtpReq::Lfsr2),
194            (0x8, LtpReq::Lfsr3),
195            (0xE, LtpReq::FfeChange),
196            (0xF, LtpReq::RateChange),
197        ] {
198            let mut sim = TestTransport::new();
199            sim.set(0x42, nibble << 4); // lane 3
200            assert_eq!(Scdc::new(sim).read_ltp_requests().await.unwrap().lane3, req);
201        }
202    }
203
204    #[pollster::test]
205    async fn ltp_requests_undefined_values_are_reserved() {
206        for nibble in 0x9u8..=0xD {
207            let mut sim = TestTransport::new();
208            sim.set(0x41, nibble); // lane 0
209            let requests = Scdc::new(sim).read_ltp_requests().await.unwrap();
210            assert_eq!(requests.lane0, LtpReq::Reserved(nibble));
211            assert_eq!(requests.lane1, LtpReq::None);
212        }
213    }
214
215    #[pollster::test]
216    async fn ltp_requests_all_trained_when_zero() {
217        assert!(
218            Scdc::new(TestTransport::new())
219                .read_ltp_requests()
220                .await
221                .unwrap()
222                .all_trained()
223        );
224    }
225
226    #[pollster::test]
227    async fn config_0_bits() {
228        for (config, expected) in [
229            (Config0::default(), 0x00u8),
230            (
231                Config0 {
232                    rr_enable: true,
233                    flt_no_retrain: false,
234                },
235                0x01,
236            ),
237            (
238                Config0 {
239                    rr_enable: false,
240                    flt_no_retrain: true,
241                },
242                0x02,
243            ),
244            (
245                Config0 {
246                    rr_enable: true,
247                    flt_no_retrain: true,
248                },
249                0x03,
250            ),
251        ] {
252            let mut scdc = Scdc::new(TestTransport::new());
253            scdc.write_config_0(config).await.unwrap();
254            assert_eq!(scdc.into_transport().get(0x30), expected, "{config:?}");
255        }
256    }
257
258    #[pollster::test]
259    async fn source_test_config_bits() {
260        let read = |byte: u8| {
261            let mut sim = TestTransport::new();
262            sim.set(0x35, byte);
263            pollster::block_on(Scdc::new(sim).read_source_test_config()).unwrap()
264        };
265        let fields = |c: SourceTestConfig| {
266            [
267                c.txffe_pre_shoot_only,
268                c.txffe_de_emphasis_only,
269                c.txffe_no_ffe,
270                c.flt_no_timeout,
271                c.dsc_frl_max,
272                c.frl_max,
273            ]
274        };
275        for (i, bit) in [1u8, 2, 3, 5, 6, 7].into_iter().enumerate() {
276            let mut expected = [false; 6];
277            expected[i] = true;
278            assert_eq!(fields(read(1 << bit)), expected, "bit {bit}");
279        }
280        // Bits 0 and 4 are reserved and set no field.
281        assert_eq!(read(0x11), read(0x00));
282        assert_eq!(
283            read(0x00),
284            SourceTestConfig::new(false, false, false, false, false, false)
285        );
286    }
287
288    #[pollster::test]
289    async fn read_status_flags_reads_status_flags_0_once() {
290        let mut scdc = Scdc::new(TestTransport::new());
291        scdc.read_status_flags().await.unwrap();
292        assert_eq!(
293            scdc.into_transport().log()[..2],
294            [Some(Op::Read(0x40)), None]
295        );
296    }
297
298    #[pollster::test]
299    async fn read_ltp_requests_reads_status_flags_1_then_2() {
300        let mut scdc = Scdc::new(TestTransport::new());
301        scdc.read_ltp_requests().await.unwrap();
302        assert_eq!(
303            scdc.into_transport().log()[..3],
304            [Some(Op::Read(0x41)), Some(Op::Read(0x42)), None]
305        );
306    }
307
308    #[pollster::test]
309    async fn transport_error_propagates() {
310        assert!(
311            Scdc::new(TestTransport::failing_after(0))
312                .write_frl_config(FrlConfig::new(
313                    HdmiForumFrl::NotSupported,
314                    FfeLevels::default(),
315                ))
316                .await
317                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
318        );
319        assert!(
320            Scdc::new(TestTransport::failing_after(0))
321                .read_status_flags()
322                .await
323                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
324        );
325        assert!(
326            Scdc::new(TestTransport::failing_after(0))
327                .write_config_0(Config0::default())
328                .await
329                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
330        );
331        assert!(
332            Scdc::new(TestTransport::failing_after(0))
333                .read_source_test_config()
334                .await
335                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
336        );
337        // First (Status_Flags_1) and second (Status_Flags_2) read of the LTP requests.
338        for n in 0..2 {
339            assert!(
340                Scdc::new(TestTransport::failing_after(n))
341                    .read_ltp_requests()
342                    .await
343                    .is_err_and(|error| matches!(error, ScdcError::Transport(())))
344            );
345        }
346    }
347}