Skip to main content

culvert_async/client/
plumbob_client.rs

1//! `plumbob_async::ScdcClient` implementation for [`Scdc`].
2//!
3//! Each trait method awaits one culvert-async method. The conversions from culvert's
4//! register types to plumbob's are culvert's own (its `plumbob` feature, which this
5//! feature enables), so the sync and async adapters convert identically.
6
7use hdmi_hal_async::scdc::ScdcTransport;
8use plumbob_async::ScdcClient;
9
10use crate::{Config0, ScdcError};
11
12use super::Scdc;
13
14/// Typed SCDC access for plumbob-async's link training.
15///
16/// `Scdc` does not wait between calls. plumbob's state machine polls `read_flt_ready` and
17/// `read_update_flags` in a loop whose limits assume one poll every 2 ms by default; await
18/// that interval inside those two methods, in an `ScdcClient` of your own that wraps
19/// `Scdc<T>` and forwards the other six (`doc/architecture.md` shows one). A deadline or
20/// cancel signal belongs there too, following plumbob's "Stopping early" rule. Not in the
21/// transport: there a wait applies to every register access, and a transport cannot tell
22/// the polled reads from LTS:L's.
23#[cfg_attr(docsrs, doc(cfg(feature = "plumbob-async")))]
24impl<T: ScdcTransport> ScdcClient for Scdc<T> {
25    type Error = ScdcError<T::Error>;
26
27    async fn read_flt_ready(&mut self) -> Result<bool, Self::Error> {
28        Ok(Scdc::read_status_flags(self).await?.flt_ready)
29    }
30
31    async fn read_update_flags(&mut self) -> Result<plumbob_async::UpdateFlags, Self::Error> {
32        Scdc::read_update_flags(self).await.map(Into::into)
33    }
34
35    async fn clear_update_flags(
36        &mut self,
37        flags: plumbob_async::ClearableUpdateFlags,
38    ) -> Result<(), Self::Error> {
39        Scdc::clear_update_flags(self, flags.into()).await
40    }
41
42    async fn read_ltp_requests(&mut self) -> Result<plumbob_async::LtpRequests, Self::Error> {
43        Scdc::read_ltp_requests(self).await.map(Into::into)
44    }
45
46    async fn read_source_test_config(
47        &mut self,
48    ) -> Result<plumbob_async::SourceTestConfig, Self::Error> {
49        Scdc::read_source_test_config(self).await.map(Into::into)
50    }
51
52    async fn write_config_0_defaults(&mut self) -> Result<(), Self::Error> {
53        Scdc::write_config_0(self, Config0::default()).await
54    }
55
56    async fn write_frl_config(
57        &mut self,
58        config: plumbob_async::FrlConfig,
59    ) -> Result<(), Self::Error> {
60        Scdc::write_frl_config(self, config.into()).await
61    }
62
63    async fn read_ced(&mut self) -> Result<plumbob_async::CedCounters, Self::Error> {
64        Scdc::read_ced(self).await.map(Into::into)
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::super::Scdc;
71    use super::super::test_transport::TestTransport;
72    use crate::{ProtocolError, ScdcError};
73    use display_types::HdmiForumFrl;
74    use plumbob_async::{
75        ClearableUpdateFlags, FfeLevels, FrlConfig, LtpReq, LtpRequests, ScdcClient,
76        SourceTestConfig, UpdateFlags,
77    };
78
79    fn scdc_with(regs: &[(u8, u8)]) -> Scdc<TestTransport> {
80        let mut t = TestTransport::new();
81        for &(addr, value) in regs {
82            t.set(addr, value);
83        }
84        Scdc::new(t)
85    }
86
87    // --- read_flt_ready: Status_Flags_0 (0x40) bit 6
88
89    #[pollster::test]
90    async fn read_flt_ready_reads_status_flags_0_bit_6() {
91        assert!(
92            ScdcClient::read_flt_ready(&mut scdc_with(&[(0x40, 0x40)]))
93                .await
94                .unwrap()
95        );
96        assert!(
97            !ScdcClient::read_flt_ready(&mut scdc_with(&[(0x40, 0xBF)]))
98                .await
99                .unwrap()
100        );
101    }
102
103    // --- read_update_flags: Update_0 (0x10), all seven flags
104
105    #[pollster::test]
106    async fn read_update_flags_maps_every_flag() {
107        let read = |byte| {
108            pollster::block_on(ScdcClient::read_update_flags(&mut scdc_with(&[(
109                0x10, byte,
110            )])))
111            .unwrap()
112        };
113        let flag = |n: usize| {
114            let b = |i| i == n;
115            UpdateFlags::new(b(0), b(1), b(2), b(3), b(4), b(5), b(6))
116        };
117        for n in 0..7 {
118            assert_eq!(read(1 << n), flag(n), "bit {n}");
119        }
120        assert_eq!(
121            read(0x7F),
122            UpdateFlags::new(true, true, true, true, true, true, true)
123        );
124        // Bit 7 is reserved.
125        assert_eq!(read(0x80), UpdateFlags::default());
126    }
127
128    // --- clear_update_flags: write 1 to clear, only the given flags
129
130    #[pollster::test]
131    async fn clear_update_flags_writes_each_given_bit() {
132        let clear = |flags| {
133            let mut scdc = Scdc::new(TestTransport::new());
134            pollster::block_on(ScdcClient::clear_update_flags(&mut scdc, flags)).unwrap();
135            scdc.into_transport().get(0x10)
136        };
137        // Bit order without RR_Test (bit 2), which has no field.
138        for (n, bit) in [0, 1, 3, 4, 5, 6].into_iter().enumerate() {
139            let b = |i| i == n;
140            let flags = ClearableUpdateFlags::new(b(0), b(1), b(2), b(3), b(4), b(5));
141            assert_eq!(clear(flags), 1 << bit, "bit {bit}");
142        }
143        assert_eq!(clear(ClearableUpdateFlags::default()), 0x00);
144    }
145
146    // --- read_ltp_requests: Status_Flags_1/2 (0x41/0x42), one nibble per lane
147
148    #[pollster::test]
149    async fn read_ltp_requests_maps_each_lane() {
150        let mut scdc = scdc_with(&[(0x41, 0x65), (0x42, 0x87)]);
151        assert_eq!(
152            ScdcClient::read_ltp_requests(&mut scdc).await.unwrap(),
153            LtpRequests::new(LtpReq::Lfsr0, LtpReq::Lfsr1, LtpReq::Lfsr2, LtpReq::Lfsr3)
154        );
155    }
156
157    #[pollster::test]
158    async fn read_ltp_requests_maps_every_request_value() {
159        let cases = [
160            (0x0, LtpReq::None),
161            (0x1, LtpReq::AllOnes),
162            (0x2, LtpReq::AllZeros),
163            (0x3, LtpReq::NyquistClock),
164            (0x4, LtpReq::DdeCompliance),
165            (0x5, LtpReq::Lfsr0),
166            (0x6, LtpReq::Lfsr1),
167            (0x7, LtpReq::Lfsr2),
168            (0x8, LtpReq::Lfsr3),
169            (0xE, LtpReq::FfeChange),
170            (0xF, LtpReq::RateChange),
171        ];
172        for (nibble, expected) in cases {
173            let requests = ScdcClient::read_ltp_requests(&mut scdc_with(&[(0x41, nibble)]))
174                .await
175                .unwrap();
176            assert_eq!(requests.lane0, expected, "{nibble:#x}");
177        }
178    }
179
180    #[pollster::test]
181    async fn read_ltp_requests_passes_undefined_values_on() {
182        let requests = ScdcClient::read_ltp_requests(&mut scdc_with(&[(0x42, 0x90)]))
183            .await
184            .unwrap();
185        assert_eq!(requests.lane3, LtpReq::Reserved(0x9));
186        assert_eq!(requests.lane2, LtpReq::None);
187    }
188
189    // --- read_source_test_config: Source_Test_Configuration (0x35), all six bits
190
191    #[pollster::test]
192    async fn read_source_test_config_maps_every_bit() {
193        let read = |byte| {
194            pollster::block_on(ScdcClient::read_source_test_config(&mut scdc_with(&[(
195                0x35, byte,
196            )])))
197            .unwrap()
198        };
199        // Bits 1–3 and 5–7, in the order of the fields; bits 0 and 4 are reserved.
200        for (n, bit) in [1, 2, 3, 5, 6, 7].into_iter().enumerate() {
201            let b = |i| i == n;
202            let expected = SourceTestConfig::new(b(0), b(1), b(2), b(3), b(4), b(5));
203            assert_eq!(read(1 << bit), expected, "bit {bit}");
204        }
205        assert_eq!(read(0x11), SourceTestConfig::default());
206    }
207
208    // --- write_config_0_defaults: Config_0 (0x30) cleared
209
210    #[pollster::test]
211    async fn write_config_0_defaults_clears_config_0() {
212        let mut scdc = scdc_with(&[(0x30, 0xFF)]);
213        ScdcClient::write_config_0_defaults(&mut scdc)
214            .await
215            .unwrap();
216        assert_eq!(scdc.into_transport().get(0x30), 0x00);
217    }
218
219    // --- write_frl_config: Config_1 (0x31), rate in bits 3:0, FFE levels in bits 7:4
220
221    #[pollster::test]
222    async fn write_frl_config_encodes_rate_and_ffe_levels() {
223        let mut scdc = Scdc::new(TestTransport::new());
224        ScdcClient::write_frl_config(
225            &mut scdc,
226            FrlConfig::new(HdmiForumFrl::Rate12Gbps4Lanes, FfeLevels::new(3).unwrap()),
227        )
228        .await
229        .unwrap();
230        assert_eq!(scdc.into_transport().get(0x31), 0x36);
231    }
232
233    #[pollster::test]
234    async fn write_frl_config_turns_frl_off() {
235        let mut scdc = scdc_with(&[(0x31, 0x36)]);
236        ScdcClient::write_frl_config(
237            &mut scdc,
238            FrlConfig::new(HdmiForumFrl::NotSupported, FfeLevels::default()),
239        )
240        .await
241        .unwrap();
242        assert_eq!(scdc.into_transport().get(0x31), 0x00);
243    }
244
245    #[pollster::test]
246    async fn write_frl_config_keeps_culverts_rate_check() {
247        // plumbob limits levels per rate itself; culvert still rejects a level above it.
248        let mut scdc = Scdc::new(TestTransport::new());
249        let result = ScdcClient::write_frl_config(
250            &mut scdc,
251            FrlConfig::new(HdmiForumFrl::Rate6Gbps4Lanes, FfeLevels::MAX),
252        )
253        .await;
254        assert!(matches!(
255            result,
256            Err(ScdcError::Protocol(ProtocolError::FfeLevelsOutOfRange {
257                levels: 7,
258                ..
259            }))
260        ));
261    }
262
263    // --- read_ced: ERR_DET, validity bit 7 of each high byte
264
265    #[pollster::test]
266    async fn read_ced_maps_each_lane() {
267        let mut scdc = scdc_with(&[
268            (0x50, 0x34),
269            (0x51, 0x92),
270            (0x52, 0x01),
271            (0x53, 0x00),
272            (0x54, 0xFF),
273            (0x55, 0xFF),
274            (0x57, 0x00),
275            (0x58, 0x80),
276        ]);
277        let ced = ScdcClient::read_ced(&mut scdc).await.unwrap();
278        assert_eq!(ced.lane0.map(|c| c.value()), Some(0x1234));
279        assert_eq!(ced.lane1, None);
280        assert_eq!(ced.lane2.map(|c| c.value()), Some(0x7FFF));
281        assert_eq!(ced.lane3.map(|c| c.value()), Some(0));
282    }
283
284    // --- transport errors
285
286    #[pollster::test]
287    async fn transport_errors_are_returned() {
288        let fails = || Scdc::new(TestTransport::failing_after(0));
289        assert!(matches!(
290            ScdcClient::read_flt_ready(&mut fails()).await,
291            Err(ScdcError::Transport(()))
292        ));
293        assert!(
294            ScdcClient::read_update_flags(&mut fails())
295                .await
296                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
297        );
298        assert!(
299            ScdcClient::clear_update_flags(&mut fails(), ClearableUpdateFlags::default())
300                .await
301                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
302        );
303        assert!(
304            ScdcClient::read_ltp_requests(&mut fails())
305                .await
306                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
307        );
308        assert!(
309            ScdcClient::read_source_test_config(&mut fails())
310                .await
311                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
312        );
313        assert!(
314            ScdcClient::write_config_0_defaults(&mut fails())
315                .await
316                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
317        );
318        assert!(
319            ScdcClient::write_frl_config(
320                &mut fails(),
321                FrlConfig::new(HdmiForumFrl::Rate6Gbps4Lanes, FfeLevels::default())
322            )
323            .await
324            .is_err_and(|error| matches!(error, ScdcError::Transport(())))
325        );
326        assert!(
327            ScdcClient::read_ced(&mut fails())
328                .await
329                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
330        );
331    }
332}