Skip to main content

culvert_async/client/
scrambling.rs

1use hdmi_hal_async::scdc::ScdcTransport;
2
3use crate::ScdcError;
4use crate::{ScramblerStatus, TmdsConfig};
5use culvert::codec;
6
7use super::Scdc;
8
9impl<T: ScdcTransport> Scdc<T> {
10    /// Writes scrambling configuration to `TMDS_Config` (0x20).
11    ///
12    /// Sets `Scrambling_Enable` (bit 0) and `TMDS_Bit_Clock_Ratio` (bit 1).
13    pub async fn write_tmds_config(
14        &mut self,
15        config: TmdsConfig,
16    ) -> Result<(), ScdcError<T::Error>> {
17        self.transport
18            .write(codec::TMDS_CONFIG, codec::encode_tmds_config(config))
19            .await
20            .map_err(ScdcError::Transport)
21    }
22
23    /// Reads scrambler acknowledgement from `Scrambler_Status` (0x21).
24    ///
25    /// Returns [`ScramblerStatus::scrambling_active`] set when the sink confirms
26    /// that TMDS scrambling is active (bit 0).
27    pub async fn read_scrambler_status(&mut self) -> Result<ScramblerStatus, ScdcError<T::Error>> {
28        let byte = self
29            .transport
30            .read(codec::SCRAMBLER_STATUS)
31            .await
32            .map_err(ScdcError::Transport)?;
33        Ok(codec::decode_scrambler_status(byte))
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::super::Scdc;
40    use super::super::test_transport::TestTransport;
41    use crate::ScdcError;
42    use crate::{ScramblerStatus, TmdsConfig};
43
44    #[pollster::test]
45    async fn tmds_config_scrambling_only() {
46        let mut scdc = Scdc::new(TestTransport::new());
47        scdc.write_tmds_config(TmdsConfig {
48            scrambling_enable: true,
49            high_tmds_clock_ratio: false,
50        })
51        .await
52        .unwrap();
53        assert_eq!(scdc.into_transport().get(0x20), 0x01);
54    }
55
56    #[pollster::test]
57    async fn tmds_config_clock_ratio_only() {
58        let mut scdc = Scdc::new(TestTransport::new());
59        scdc.write_tmds_config(TmdsConfig {
60            scrambling_enable: false,
61            high_tmds_clock_ratio: true,
62        })
63        .await
64        .unwrap();
65        assert_eq!(scdc.into_transport().get(0x20), 0x02);
66    }
67
68    #[pollster::test]
69    async fn tmds_config_both_clear() {
70        let mut scdc = Scdc::new(TestTransport::new());
71        scdc.write_tmds_config(TmdsConfig {
72            scrambling_enable: false,
73            high_tmds_clock_ratio: false,
74        })
75        .await
76        .unwrap();
77        assert_eq!(scdc.into_transport().get(0x20), 0x00);
78    }
79
80    #[pollster::test]
81    async fn scrambler_status_bit0_set() {
82        let mut sim = TestTransport::new();
83        sim.set(0x21, 0xFF); // all bits set; only bit 0 is defined
84        assert_eq!(
85            Scdc::new(sim).read_scrambler_status().await.unwrap(),
86            ScramblerStatus::new(true)
87        );
88    }
89
90    #[pollster::test]
91    async fn scrambler_status_bit0_clear() {
92        let mut sim = TestTransport::new();
93        sim.set(0x21, 0xFE); // bit 0 clear, other bits set (reserved)
94        assert_eq!(
95            Scdc::new(sim).read_scrambler_status().await.unwrap(),
96            ScramblerStatus::new(false)
97        );
98    }
99
100    #[pollster::test]
101    async fn transport_error_propagates() {
102        assert!(
103            Scdc::new(TestTransport::failing_after(0))
104                .write_tmds_config(TmdsConfig {
105                    scrambling_enable: false,
106                    high_tmds_clock_ratio: false
107                })
108                .await
109                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
110        );
111        assert!(
112            Scdc::new(TestTransport::failing_after(0))
113                .read_scrambler_status()
114                .await
115                .is_err_and(|error| matches!(error, ScdcError::Transport(())))
116        );
117    }
118}