Skip to main content

culvert_async/client/
plumbob_client.rs

1//! `plumbob_async::ScdcClient` implementation for [`Scdc`].
2
3use hdmi_hal_async::scdc::ScdcTransport;
4use plumbob_async::ScdcClient;
5
6use crate::{FfeLevels, FrlConfig, LtpReq, ScdcError};
7
8use super::Scdc;
9
10impl<T: ScdcTransport> ScdcClient for Scdc<T> {
11    type Error = ScdcError<T::Error>;
12
13    async fn write_frl_config(
14        &mut self,
15        config: plumbob_async::FrlConfig,
16    ) -> Result<(), Self::Error> {
17        self.write_frl_config(FrlConfig {
18            frl_rate: config.rate,
19            dsc_frl_max: config.dsc_frl_max,
20            ffe_levels: ffe_levels(config.ffe_levels),
21        })
22        .await
23    }
24
25    async fn read_training_status(&mut self) -> Result<plumbob_async::TrainingStatus, Self::Error> {
26        let f = self.read_status_flags().await?;
27        Ok(plumbob_async::TrainingStatus {
28            flt_ready: f.flt_ready,
29            frl_start: f.frl_start,
30            ltp_req: ltp_req(f.ltp_req),
31        })
32    }
33
34    async fn read_ced(&mut self) -> Result<plumbob_async::CedCounters, Self::Error> {
35        let c = self.read_ced().await?;
36        Ok(plumbob_async::CedCounters {
37            lane0: c.lane0.map(ced_count),
38            lane1: c.lane1.map(ced_count),
39            lane2: c.lane2.map(ced_count),
40            lane3: c.lane3.map(ced_count),
41        })
42    }
43}
44
45fn ffe_levels(f: plumbob_async::FfeLevels) -> FfeLevels {
46    match f {
47        plumbob_async::FfeLevels::Ffe0 => FfeLevels::Ffe0,
48        plumbob_async::FfeLevels::Ffe1 => FfeLevels::Ffe1,
49        plumbob_async::FfeLevels::Ffe2 => FfeLevels::Ffe2,
50        plumbob_async::FfeLevels::Ffe3 => FfeLevels::Ffe3,
51        plumbob_async::FfeLevels::Ffe4 => FfeLevels::Ffe4,
52        plumbob_async::FfeLevels::Ffe5 => FfeLevels::Ffe5,
53        plumbob_async::FfeLevels::Ffe6 => FfeLevels::Ffe6,
54        plumbob_async::FfeLevels::Ffe7 => FfeLevels::Ffe7,
55    }
56}
57
58fn ltp_req(req: LtpReq) -> plumbob_async::LtpReq {
59    match req {
60        LtpReq::None => plumbob_async::LtpReq::None,
61        LtpReq::Lfsr0 => plumbob_async::LtpReq::Lfsr0,
62        LtpReq::Lfsr1 => plumbob_async::LtpReq::Lfsr1,
63        LtpReq::Lfsr2 => plumbob_async::LtpReq::Lfsr2,
64        LtpReq::Lfsr3 => plumbob_async::LtpReq::Lfsr3,
65        _ => unreachable!("unknown LtpReq variant"),
66    }
67}
68
69fn ced_count(c: crate::CedCount) -> plumbob_async::CedCount {
70    plumbob_async::CedCount::new(c.value())
71}
72
73#[cfg(test)]
74mod tests {
75    use super::super::Scdc;
76    use super::super::test_transport::TestTransport;
77    use display_types::cea861::hdmi_forum::HdmiForumFrl;
78    use plumbob_async::{FfeLevels, FrlConfig, LtpReq, ScdcClient};
79
80    // --- write_frl_config ---
81
82    #[pollster::test]
83    async fn write_frl_config_rate_and_ffe() {
84        // Rate12Gbps4Lanes = discriminant 6; Ffe3 = 3 → bits[7:5] = 0b011 = 0x60
85        let mut scdc = Scdc::new(TestTransport::new());
86        ScdcClient::write_frl_config(
87            &mut scdc,
88            FrlConfig {
89                rate: HdmiForumFrl::Rate12Gbps4Lanes,
90                ffe_levels: FfeLevels::Ffe3,
91                dsc_frl_max: false,
92            },
93        )
94        .await
95        .unwrap();
96        assert_eq!(scdc.into_transport().get(0x30), 0x06 | 0x60);
97    }
98
99    #[pollster::test]
100    async fn write_frl_config_dsc_frl_max() {
101        let mut scdc = Scdc::new(TestTransport::new());
102        ScdcClient::write_frl_config(
103            &mut scdc,
104            FrlConfig {
105                rate: HdmiForumFrl::NotSupported,
106                ffe_levels: FfeLevels::Ffe0,
107                dsc_frl_max: true,
108            },
109        )
110        .await
111        .unwrap();
112        assert_eq!(scdc.into_transport().get(0x30), 0x10);
113    }
114
115    #[pollster::test]
116    async fn write_frl_config_all_ffe_levels() {
117        use FfeLevels::*;
118        for (ffe, expected_bits) in [
119            (Ffe0, 0x00u8),
120            (Ffe1, 0x20),
121            (Ffe2, 0x40),
122            (Ffe3, 0x60),
123            (Ffe4, 0x80),
124            (Ffe5, 0xA0),
125            (Ffe6, 0xC0),
126            (Ffe7, 0xE0),
127        ] {
128            let mut scdc = Scdc::new(TestTransport::new());
129            ScdcClient::write_frl_config(
130                &mut scdc,
131                FrlConfig {
132                    rate: HdmiForumFrl::NotSupported,
133                    ffe_levels: ffe,
134                    dsc_frl_max: false,
135                },
136            )
137            .await
138            .unwrap();
139            assert_eq!(
140                scdc.into_transport().get(0x30),
141                expected_bits,
142                "ffe={ffe:?}"
143            );
144        }
145    }
146
147    // --- read_training_status ---
148
149    #[pollster::test]
150    async fn read_training_status_flt_ready() {
151        let mut sim = TestTransport::new();
152        sim.set(0x40, 0x40); // flt_ready bit
153        let status = Scdc::new(sim).read_training_status().await.unwrap();
154        assert!(status.flt_ready);
155        assert!(!status.frl_start);
156        assert_eq!(status.ltp_req, LtpReq::None);
157    }
158
159    #[pollster::test]
160    async fn read_training_status_frl_start() {
161        let mut sim = TestTransport::new();
162        sim.set(0x41, 0x01); // frl_start bit
163        let status = Scdc::new(sim).read_training_status().await.unwrap();
164        assert!(!status.flt_ready);
165        assert!(status.frl_start);
166    }
167
168    #[pollster::test]
169    async fn read_training_status_ltp_req_variants() {
170        for (nibble, expected) in [
171            (0u8, LtpReq::None),
172            (1, LtpReq::Lfsr0),
173            (2, LtpReq::Lfsr1),
174            (3, LtpReq::Lfsr2),
175            (4, LtpReq::Lfsr3),
176        ] {
177            let mut sim = TestTransport::new();
178            sim.set(0x41, nibble << 4);
179            assert_eq!(
180                Scdc::new(sim).read_training_status().await.unwrap().ltp_req,
181                expected,
182                "nibble={nibble}"
183            );
184        }
185    }
186
187    #[pollster::test]
188    async fn read_training_status_unknown_ltp_req_is_error() {
189        let mut sim = TestTransport::new();
190        sim.set(0x41, 5 << 4); // nibble 5 — undefined by spec
191        assert!(Scdc::new(sim).read_training_status().await.is_err());
192    }
193
194    // --- read_ced ---
195
196    #[pollster::test]
197    async fn read_ced_lane0_valid() {
198        let mut sim = TestTransport::new();
199        sim.set(0x50, 0x23); // low byte
200        sim.set(0x51, 0x81); // high byte: validity bit set, counter hi = 0x01
201        let ced = Scdc::new(sim).read_ced().await.unwrap();
202        assert_eq!(ced.lane0.map(|c| c.value()), Some(0x0123));
203    }
204
205    #[pollster::test]
206    async fn read_ced_lane_invalid_when_validity_clear() {
207        let mut sim = TestTransport::new();
208        sim.set(0x50, 0xFF);
209        sim.set(0x51, 0x7F); // validity bit clear
210        let ced = Scdc::new(sim).read_ced().await.unwrap();
211        assert_eq!(ced.lane0, None);
212    }
213
214    #[pollster::test]
215    async fn read_ced_all_lanes_propagate() {
216        let mut sim = TestTransport::new();
217        for (base, val) in [(0x50u8, 1u8), (0x52, 2), (0x54, 3), (0x56, 4)] {
218            sim.set(base, val);
219            sim.set(base + 1, 0x80);
220        }
221        let ced = Scdc::new(sim).read_ced().await.unwrap();
222        assert_eq!(ced.lane0.map(|c| c.value()), Some(1));
223        assert_eq!(ced.lane1.map(|c| c.value()), Some(2));
224        assert_eq!(ced.lane2.map(|c| c.value()), Some(3));
225        assert_eq!(ced.lane3.map(|c| c.value()), Some(4));
226    }
227
228    // --- error propagation ---
229
230    #[pollster::test]
231    async fn write_frl_config_transport_error() {
232        assert!(
233            ScdcClient::write_frl_config(
234                &mut Scdc::new(TestTransport::failing_after(0)),
235                FrlConfig {
236                    rate: HdmiForumFrl::NotSupported,
237                    ffe_levels: FfeLevels::Ffe0,
238                    dsc_frl_max: false,
239                }
240            )
241            .await
242            .is_err()
243        );
244    }
245
246    #[pollster::test]
247    async fn read_training_status_transport_error() {
248        assert!(
249            Scdc::new(TestTransport::failing_after(0))
250                .read_training_status()
251                .await
252                .is_err()
253        );
254    }
255
256    #[pollster::test]
257    async fn read_ced_transport_error() {
258        for n in 0..8 {
259            assert!(
260                Scdc::new(TestTransport::failing_after(n))
261                    .read_ced()
262                    .await
263                    .is_err(),
264                "should fail when transport fails after {n} ops"
265            );
266        }
267    }
268}