Skip to main content

culvert_async/client/
frl.rs

1use hdmi_hal_async::scdc::ScdcTransport;
2
3use crate::{FrlConfig, LtpReq, ProtocolError, ScdcError, StatusFlags};
4
5use super::Scdc;
6use super::address;
7
8impl<T: ScdcTransport> Scdc<T> {
9    /// Writes FRL training configuration to `Config_0` (0x30).
10    ///
11    /// Encodes `FRL_Rate` into bits\[3:0\], `DSC_FRL_Max` into bit\[4\], and
12    /// `FFE_Levels` into bits\[7:5\].
13    pub async fn write_frl_config(&mut self, config: FrlConfig) -> Result<(), ScdcError<T::Error>> {
14        let byte = (config.frl_rate as u8)
15            | ((config.dsc_frl_max as u8) << 4)
16            | ((config.ffe_levels as u8) << 5);
17        self.transport
18            .write(address::CONFIG_0, byte)
19            .await
20            .map_err(ScdcError::Transport)
21    }
22
23    /// Reads FRL status from `Status_Flags_0` (0x40) and `Status_Flags_1` (0x41).
24    ///
25    /// Returns [`crate::ProtocolError::UnknownLtpReq`] if the sink reports an
26    /// LTP request value not defined by the HDMI 2.1 specification.
27    pub async fn read_status_flags(&mut self) -> Result<StatusFlags, ScdcError<T::Error>> {
28        let flags0 = self
29            .transport
30            .read(address::STATUS_FLAGS_0)
31            .await
32            .map_err(ScdcError::Transport)?;
33        let flags1 = self
34            .transport
35            .read(address::STATUS_FLAGS_1)
36            .await
37            .map_err(ScdcError::Transport)?;
38
39        let ltp_req = match (flags1 >> 4) & 0x0F {
40            0 => LtpReq::None,
41            1 => LtpReq::Lfsr0,
42            2 => LtpReq::Lfsr1,
43            3 => LtpReq::Lfsr2,
44            4 => LtpReq::Lfsr3,
45            raw => return Err(ScdcError::Protocol(ProtocolError::UnknownLtpReq(raw))),
46        };
47
48        Ok(StatusFlags::new(
49            flags0 & 0x01 != 0,
50            flags0 & 0x02 != 0,
51            flags0 & 0x04 != 0,
52            flags0 & 0x08 != 0,
53            flags0 & 0x10 != 0,
54            flags0 & 0x20 != 0,
55            flags0 & 0x40 != 0,
56            flags1 & 0x01 != 0,
57            ltp_req,
58        ))
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::super::Scdc;
65    use super::super::test_transport::TestTransport;
66    use crate::{FfeLevels, FrlConfig, FrlRate, LtpReq, ProtocolError, ScdcError, StatusFlags};
67
68    #[pollster::test]
69    async fn frl_config_rate_field() {
70        let mut scdc = Scdc::new(TestTransport::new());
71        scdc.write_frl_config(FrlConfig {
72            frl_rate: FrlRate::Rate12Gbps4Lanes, // discriminant 6
73            dsc_frl_max: false,
74            ffe_levels: FfeLevels::Ffe0,
75        })
76        .await
77        .unwrap();
78        assert_eq!(scdc.into_transport().get(0x30), 0x06);
79    }
80
81    #[pollster::test]
82    async fn frl_config_dsc_frl_max_field() {
83        let mut scdc = Scdc::new(TestTransport::new());
84        scdc.write_frl_config(FrlConfig {
85            frl_rate: FrlRate::NotSupported,
86            dsc_frl_max: true,
87            ffe_levels: FfeLevels::Ffe0,
88        })
89        .await
90        .unwrap();
91        assert_eq!(scdc.into_transport().get(0x30), 0x10);
92    }
93
94    #[pollster::test]
95    async fn frl_config_ffe_levels_field() {
96        let mut scdc = Scdc::new(TestTransport::new());
97        scdc.write_frl_config(FrlConfig {
98            frl_rate: FrlRate::NotSupported,
99            dsc_frl_max: false,
100            ffe_levels: FfeLevels::Ffe7, // discriminant 7 → bits[7:5] = 0b111 = 0xE0
101        })
102        .await
103        .unwrap();
104        assert_eq!(scdc.into_transport().get(0x30), 0xE0);
105    }
106
107    #[pollster::test]
108    async fn status_flags_all_zero() {
109        let mut scdc = Scdc::new(TestTransport::new());
110        assert_eq!(
111            scdc.read_status_flags().await.unwrap(),
112            StatusFlags::new(
113                false,
114                false,
115                false,
116                false,
117                false,
118                false,
119                false,
120                false,
121                LtpReq::None
122            )
123        );
124    }
125
126    #[pollster::test]
127    async fn status_flags_ltp_req_variants() {
128        for (nibble, expected) in [
129            (0u8, LtpReq::None),
130            (1, LtpReq::Lfsr0),
131            (2, LtpReq::Lfsr1),
132            (3, LtpReq::Lfsr2),
133            (4, LtpReq::Lfsr3),
134        ] {
135            let mut sim = TestTransport::new();
136            sim.set(0x41, nibble << 4);
137            assert_eq!(
138                Scdc::new(sim).read_status_flags().await.unwrap().ltp_req,
139                expected
140            );
141        }
142    }
143
144    #[pollster::test]
145    async fn status_flags_unknown_ltp_req() {
146        for nibble in 5u8..=15 {
147            let mut sim = TestTransport::new();
148            sim.set(0x41, nibble << 4);
149            assert!(matches!(
150                Scdc::new(sim).read_status_flags().await,
151                Err(ScdcError::Protocol(ProtocolError::UnknownLtpReq(n))) if n == nibble
152            ));
153        }
154    }
155
156    #[pollster::test]
157    async fn status_flags_all_flags0_bits_set() {
158        let mut sim = TestTransport::new();
159        sim.set(0x40, 0x7F); // clock_detected | cable_connected | ch0–ch3_locked | flt_ready
160        sim.set(0x41, 0x01); // frl_start
161        let f = Scdc::new(sim).read_status_flags().await.unwrap();
162        assert!(f.clock_detected && f.cable_connected);
163        assert!(f.ch0_locked && f.ch1_locked && f.ch2_locked && f.ch3_locked);
164        assert!(f.flt_ready && f.frl_start);
165        assert_eq!(f.ltp_req, LtpReq::None);
166    }
167
168    #[pollster::test]
169    async fn transport_error_propagates() {
170        assert!(
171            Scdc::new(TestTransport::failing_after(0))
172                .write_frl_config(FrlConfig {
173                    frl_rate: FrlRate::NotSupported,
174                    dsc_frl_max: false,
175                    ffe_levels: FfeLevels::Ffe0,
176                })
177                .await
178                .is_err()
179        );
180        // First read of status flags fails.
181        assert!(
182            Scdc::new(TestTransport::failing_after(0))
183                .read_status_flags()
184                .await
185                .is_err()
186        );
187        // Second read (Status_Flags_1) fails.
188        assert!(
189            Scdc::new(TestTransport::failing_after(1))
190                .read_status_flags()
191                .await
192                .is_err()
193        );
194    }
195}