### Scdc usage example Source: https://docs.rs/culvert/latest/culvert/struct.Scdc.html Creates a new Scdc client, reads the sink version, and writes the source version. The transport is passed to the constructor. ```rust let mut scdc = Scdc::new(transport); let sink_version = scdc.read_sink_version()?; scdc.write_source_version(1)?; ``` -------------------------------- ### Crate-level documentation and module declarations in lib.rs Source: https://docs.rs/culvert/latest/src/culvert/lib.rs.html?search= The lib.rs file starts with crate-level documentation explaining the crate's purpose, then declares modules and re-exports public types. It uses #![no_std] and #![forbid(unsafe_code)]. ```rust 1//! Typed access to the HDMI 2.1 SCDC (Status and Control Data Channel) register map. 2//! 3//! Culvert sits on top of [`hdmi_hal::scdc::ScdcTransport`] and provides named structs, 4//! bitfield types, and typed operations for scrambling control, FRL training primitives, 5//! and CED (Character Error Detection) reporting. 6//! 7//! The central type is [`Scdc`], a thin stateless client that wraps a transport 8//! and exposes one typed method per register group. Sequencing of register operations — 9//! rate selection, timeout handling, retry logic — belongs in the link training crate 10//! above. 11#![no_std] 12#![forbid(unsafe_code)] 13#![deny(missing_docs)] 14 15mod client; 16mod error; 17mod register; 18 19pub use client::Scdc; 20pub use error::{ProtocolError, ScdcError}; 21pub use register::{ 22 CedCount, CedCounters, FfeLevels, FrlConfig, FrlRate, LtpReq, ScramblerStatus, StatusFlags, 23 TmdsConfig, UpdateFlags, 24}; ``` -------------------------------- ### Define Scdc client struct with new and into_transport methods in Rust Source: https://docs.rs/culvert/latest/src/culvert/client/mod.rs.html Complete `Scdc` client definition from `culvert/client/mod.rs`. It uses `hdmi_hal::scdc::ScdcTransport`, conditionally includes `plumbob_client` behind the `plumbob` feature, and provides `new()` and `into_transport()` methods. The doc comment shows an ignored usage example with `read_sink_version()` and `write_source_version(1)`, but that example is not displayed as a separate block. ```rust //! The [`Scdc`] client. use hdmi_hal::scdc::ScdcTransport; mod ced; mod frl; #[cfg(feature = "plumbob")] mod plumbob_client; mod scrambling; mod update; mod version; #[cfg(test)] mod test_transport; /// Typed client for the HDMI 2.1 SCDC (Status and Control Data Channel) register map. /// /// `Scdc` wraps a [`ScdcTransport`] and exposes one typed method per register group. /// It holds no protocol state; all sequencing logic belongs in the caller. /// /// # Example /// /// ```ignore /// let mut scdc = Scdc::new(transport); /// let sink_version = scdc.read_sink_version()?; /// scdc.write_source_version(1)?; /// ``` pub struct Scdc { transport: T, } impl Scdc { /// Creates a new `Scdc` client wrapping the given transport. pub fn new(transport: T) -> Self { Self { transport } } /// Consumes the client and returns the underlying transport. pub fn into_transport(self) -> T { self.transport } } ``` -------------------------------- ### ScdcClient implementation and tests for plumbob Source: https://docs.rs/culvert/latest/src/culvert/client/plumbob_client.rs.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Implementation of `plumbob::ScdcClient` for `Scdc` along with conversion helpers and a test module. The impl maps `plumbob::FrlConfig`, `TrainingStatus`, and `CedCounters` to the internal register types using the provided `Scdc` methods. The test module includes three `write_frl_config` tests verifying rate/FFE encoding, DSC FRL max bit, and all FFE level bit patterns, plus a `read_training_status` test checking the FLT_READY bit. ```text 1//! `plumbob::ScdcClient` implementation for [`Scdc`]. 2 3use hdmi_hal::scdc::ScdcTransport; 4use plumbob::ScdcClient; 5 6use crate::error::ScdcError; 7use crate::register::{CedCount, FfeLevels, FrlConfig, LtpReq}; 8 9use super::Scdc; 10 11impl ScdcClient for Scdc { 12 type Error = ScdcError; 13 14 fn write_frl_config(&mut self, config: plumbob::FrlConfig) -> Result<(), Self::Error> { 15 self.write_frl_config(FrlConfig { 16 frl_rate: config.rate, 17 dsc_frl_max: config.dsc_frl_max, 18 ffe_levels: ffe_levels(config.ffe_levels), 19 }) 20 } 21 22 fn read_training_status(&mut self) -> Result { 23 let f = self.read_status_flags()?; 24 Ok(plumbob::TrainingStatus { 25 flt_ready: f.flt_ready, 26 frl_start: f.frl_start, 27 ltp_req: ltp_req(f.ltp_req), 28 }) 29 } 30 fn read_ced(&mut self) -> Result { 31 let c = self.read_ced()?; 32 Ok(plumbob::CedCounters { 33 lane0: c.lane0.map(ced_count), 34 lane1: c.lane1.map(ced_count), 35 lane2: c.lane2.map(ced_count), 36 lane3: c.lane3.map(ced_count), 37 }) 38 } 39} 40 41fn ffe_levels(f: plumbob::FfeLevels) -> FfeLevels { 42 match f { 43 plumbob::FfeLevels::Ffe0 => FfeLevels::Ffe0, 44 plumbob::FfeLevels::Ffe1 => FfeLevels::Ffe1, 45 plumbob::FfeLevels::Ffe2 => FfeLevels::Ffe2, 46 plumbob::FfeLevels::Ffe3 => FfeLevels::Ffe3, 47 plumbob::FfeLevels::Ffe4 => FfeLevels::Ffe4, 48 plumbob::FfeLevels::Ffe5 => FfeLevels::Ffe5, 49 plumbob::FfeLevels::Ffe6 => FfeLevels::Ffe6, 50 plumbob::FfeLevels::Ffe7 => FfeLevels::Ffe7, 51 } 52} 53 54fn ltp_req(req: LtpReq) -> plumbob::LtpReq { 55 match req { 56 LtpReq::None => plumbob::LtpReq::None, 57 LtpReq::Lfsr0 => plumbob::LtpReq::Lfsr0, 58 LtpReq::Lfsr1 => plumbob::LtpReq::Lfsr1, 59 LtpReq::Lfsr2 => plumbob::LtpReq::Lfsr2, 60 LtpReq::Lfsr3 => plumbob::LtpReq::Lfsr3, 61 } 62} 63 64fn ced_count(c: CedCount) -> plumbob::CedCount { 65 plumbob::CedCount::new(c.value()) 66} 67 68#[cfg(test)] 69mod tests { 70 use super::super::Scdc; 71 use super::super::test_transport::TestTransport; 72 use display_types::HdmiForumFrl; 73 use plumbob::{FfeLevels, FrlConfig, LtpReq, ScdcClient}; 74 75 // --- write_frl_config --- 76 77 #[test] 78 fn write_frl_config_rate_and_ffe() { 79 // Rate12Gbps4Lanes = discriminant 6; Ffe3 = 3 → bits[7:5] = 0b011 = 0x60 80 let mut scdc = Scdc::new(TestTransport::new()); 81 ScdcClient::write_frl_config( 82 &mut scdc, 83 FrlConfig { 84 rate: HdmiForumFrl::Rate12Gbps4Lanes, 85 ffe_levels: FfeLevels::Ffe3, 86 dsc_frl_max: false, 87 }, 88 ) 89 .unwrap(); 90 assert_eq!(scdc.into_transport().get(0x30), 0x06 | 0x60); 91 } 92 93 #[test] 94 fn write_frl_config_dsc_frl_max() { 95 let mut scdc = Scdc::new(TestTransport::new()); 96 ScdcClient::write_frl_config( 97 &mut scdc, 98 FrlConfig { 99 rate: HdmiForumFrl::NotSupported, 100 ffe_levels: FfeLevels::Ffe0, 101 dsc_frl_max: true, 102 }, 103 ) 104 .unwrap(); 105 assert_eq!(scdc.into_transport().get(0x30), 0x10); 106 } 107 108 #[test] 109 fn write_frl_config_all_ffe_levels() { 110 use FfeLevels::*; 111 for (ffe, expected_bits) in [ 112 (Ffe0, 0x00u8), 113 (Ffe1, 0x20), 114 (Ffe2, 0x40), 115 (Ffe3, 0x60), 116 (Ffe4, 0x80), 117 (Ffe5, 0xA0), 118 (Ffe6, 0xC0), 119 (Ffe7, 0xE0), 120 ] { 121 let mut scdc = Scdc::new(TestTransport::new()); 122 ScdcClient::write_frl_config( 123 &mut scdc, 124 FrlConfig { 125 rate: HdmiForumFrl::NotSupported, 126 ffe_levels: ffe, 127 dsc_frl_max: false, 128 }, 129 ) 130 .unwrap(); 131 assert_eq!( 132 scdc.into_transport().get(0x30), 133 expected_bits, 134 "ffe={ffe:?}" 135 ); 136 } 137 } 138 139 // --- read_training_status --- 140 141 #[test] 142 fn read_training_status_flt_ready() { 143 let mut sim = TestTransport::new(); 144 sim.set(0x40, 0x40); // flt_ready bit 145 let status = Scdc::new(sim).read_training_status().unwrap(); 146 assert!(status.flt_ready); 147 assert!(!status.frl_start); ``` -------------------------------- ### Test read_training_status_frl_start Source: https://docs.rs/culvert/latest/src/culvert/client/plumbob_client.rs.html?search= Verifies that when the frl_start bit (0x01) is set in register 0x41, read_training_status returns flt_ready as false and frl_start as true. ```rust #[test] fn read_training_status_frl_start() { let mut sim = TestTransport::new(); sim.set(0x41, 0x01); // frl_start bit let status = Scdc::new(sim).read_training_status().unwrap(); assert!(!status.flt_ready); assert!(status.frl_start); } ``` -------------------------------- ### Scdc::new Source: https://docs.rs/culvert/latest/culvert/struct.Scdc.html Creates a new Scdc client wrapping the given transport. ```APIDOC ## Scdc::new ### Description Creates a new Scdc client wrapping the given transport. ### Method new ### Parameters - **transport** - The transport to use for SCDC communication. ### Returns A new `Scdc` instance. ``` -------------------------------- ### Scdc::new Source: https://docs.rs/culvert/latest/culvert/struct.Scdc.html Creates a new Scdc client wrapping the given transport. ```APIDOC ## new ### Description Creates a new `Scdc` client wrapping the given transport. ### Method pub fn new(transport: T) -> Self ### Parameters - **transport** (T) - Required - The transport to wrap. ### Returns - `Self` - A new `Scdc` client. ``` -------------------------------- ### FrlConfig Source: https://docs.rs/culvert/latest/src/culvert/register/mod.rs.html?search=u32+-%3E+bool Configuration written to Config_0 (0x30). Contains FRL rate, DSC FRL max flag, and FFE levels. ```APIDOC ## FrlConfig ### Description Configuration written to `Config_0` (0x30). ### Fields - **frl_rate** (HdmiForumFrl) - FRL rate to request. Use `HdmiForumFrl::NotSupported` to clear FRL mode. - **dsc_frl_max** (bool) - Request DSC at the maximum supported FRL rate (`DSC_FRL_Max`). - **ffe_levels** (FfeLevels) - Number of FFE levels to advertise to the sink. ``` -------------------------------- ### CedCount::new Source: https://docs.rs/culvert/latest/culvert/struct.CedCount.html?search=std%3A%3Avec Constructs a CedCount from a raw u16, masking to 15 bits. ```APIDOC ## CedCount::new ### Description Constructs a `CedCount`, masking to 15 bits. ### Method `pub fn new(raw: u16) -> Self` ### Parameters - **raw** (u16) - Required - The raw value to construct the count from. ### Returns - **Self** - A new `CedCount` instance with the value masked to 15 bits. ``` -------------------------------- ### Unit tests for write_frl_config Source: https://docs.rs/culvert/latest/src/culvert/client/plumbob_client.rs.html?search=u32+-%3E+bool Tests the `write_frl_config` method: verifies register 0x30 values for rate and FFE level combinations, DSC FRL max flag, and all FFE levels. ```rust #[cfg(test)] mod tests { use super::super::Scdc; use super::super::test_transport::TestTransport; use display_types::HdmiForumFrl; use plumbob::{FfeLevels, FrlConfig, LtpReq, ScdcClient}; // --- write_frl_config --- #[test] fn write_frl_config_rate_and_ffe() { // Rate12Gbps4Lanes = discriminant 6; Ffe3 = 3 → bits[7:5] = 0b011 = 0x60 let mut scdc = Scdc::new(TestTransport::new()); ScdcClient::write_frl_config( &mut scdc, FrlConfig { rate: HdmiForumFrl::Rate12Gbps4Lanes, ffe_levels: FfeLevels::Ffe3, dsc_frl_max: false, }, ) .unwrap(); assert_eq!(scdc.into_transport().get(0x30), 0x06 | 0x60); } #[test] fn write_frl_config_dsc_frl_max() { let mut scdc = Scdc::new(TestTransport::new()); ScdcClient::write_frl_config( &mut scdc, FrlConfig { rate: HdmiForumFrl::NotSupported, ffe_levels: FfeLevels::Ffe0, dsc_frl_max: true, }, ) .unwrap(); assert_eq!(scdc.into_transport().get(0x30), 0x10); } #[test] fn write_frl_config_all_ffe_levels() { use FfeLevels::*; for (ffe, expected_bits) in [ (Ffe0, 0x00u8), (Ffe1, 0x20), (Ffe2, 0x40), (Ffe3, 0x60), (Ffe4, 0x80), (Ffe5, 0xA0), (Ffe6, 0xC0), (Ffe7, 0xE0), ] { let mut scdc = Scdc::new(TestTransport::new()); ScdcClient::write_frl_config( &mut scdc, FrlConfig { rate: HdmiForumFrl::NotSupported, ffe_levels: ffe, dsc_frl_max: false, }, ) .unwrap(); assert_eq!( scdc.into_transport().get(0x30), expected_bits, "ffe={ffe:?}" ); } } } ``` -------------------------------- ### Test CedCounters::new with lane3 None Source: https://docs.rs/culvert/latest/src/culvert/register/mod.rs.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Verifies that passing None for the lane3 parameter results in counters.lane3 being None. ```rust let counters = CedCounters::new( Some(CedCount::new(0)), Some(CedCount::new(0)), Some(CedCount::new(0)), None, ); assert!(counters.lane3.is_none()); ``` -------------------------------- ### Scdc::write_frl_config Source: https://docs.rs/culvert/latest/culvert/struct.Scdc.html Writes FRL training configuration to Config_0 (0x30). Encodes FRL_Rate into bits[3:0], DSC_FRL_Max into bit[4], and FFE_Levels into bits[7:5]. ```APIDOC ## write_frl_config ### Description Writes FRL training configuration to `Config_0` (0x30). Encodes `FRL_Rate` into bits[3:0], `DSC_FRL_Max` into bit[4], and `FFE_Levels` into bits[7:5]. ### Method pub fn write_frl_config(&mut self, config: FrlConfig) -> Result<(), ScdcError> ### Parameters - **config** (FrlConfig) - Required - The FRL configuration to write. ### Returns - `Result<(), ScdcError>` - Ok on success, or an error. ``` -------------------------------- ### Write write_frl_config Source: https://docs.rs/culvert/latest/src/culvert/client/frl.rs.html?search= Documents the write_frl_config method for configuring FRL training registers. ```APIDOC ## [Write] write_frl_config ### Description Writes the FRL training configuration to the Config_0 register (0x30). Encodes the provided FRL configuration fields into the appropriate bit locations within the register byte. ### Method write ### Endpoint Scdc::write_frl_config(config: FrlConfig) -> Result<(), ScdcError> ### Parameters #### Request Body - **config** (FrlConfig) - Required - The FRL configuration containing: `frl_rate` (HdmiForumFrl), `dsc_frl_max` (bool), and `ffe_levels` (FfeLevels). ### Request Example ```rust let config = FrlConfig { frl_rate: HdmiForumFrl::Rate12Gbps4Lanes, dsc_frl_max: false, ffe_levels: FfeLevels::Ffe0, }; scdc.write_frl_config(config)?; ``` ### Response #### Success Response Returns `Ok(())` if the write operation succeeds. #### Error Response Returns `Err(ScdcError::Transport)` if the underlying transport read fails. #### Response Example ```rust let result: Result<(), ScdcError> = scdc.write_frl_config(config); ``` ``` -------------------------------- ### Test status_flags_all_flags0_bits_set Source: https://docs.rs/culvert/latest/src/culvert/client/frl.rs.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Sets all flags0 bits (0x7F) and the frl_start bit (0x01) in the simulated transport, then asserts that all corresponding status flags are true and ltp_req is None. ```rust #[test] fn status_flags_all_flags0_bits_set() { // Ensures the true-branch of every flags0 bit expression is exercised. let mut sim = TestTransport::new(); sim.set(0x40, 0x7F); // clock_detected | cable_connected | ch0–ch3_locked | flt_ready sim.set(0x41, 0x01); // frl_start let f = Scdc::new(sim).read_status_flags().unwrap(); assert!(f.clock_detected && f.cable_connected); assert!(f.ch0_locked && f.ch1_locked && f.ch2_locked && f.ch3_locked); assert!(f.flt_ready && f.frl_start); assert_eq!(f.ltp_req, LtpReq::None); } ``` -------------------------------- ### Implement FRL config write and status read for Scdc Source: https://docs.rs/culvert/latest/src/culvert/client/frl.rs.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Defines `write_frl_config` to encode FRL rate, DSC FRL max, and FFE levels into the `Config_0` register (0x30), and `read_status_flags` to read status from `Status_Flags_0` (0x40) and `Status_Flags_1` (0x41), returning an error for unknown LTP request values. ```rust use hdmi_hal::scdc::ScdcTransport; use crate::error::{ProtocolError, ScdcError}; use crate::register::address; use crate::register::{FrlConfig, LtpReq, StatusFlags}; use super::Scdc; impl Scdc { /// Writes FRL training configuration to `Config_0` (0x30). /// /// Encodes `FRL_Rate` into bits\[3:0\], `DSC_FRL_Max` into bit\[4\], and /// `FFE_Levels` into bits\[7:5\]. pub fn write_frl_config(&mut self, config: FrlConfig) -> Result<(), ScdcError> { let byte = (config.frl_rate as u8) | ((config.dsc_frl_max as u8) << 4) | ((config.ffe_levels as u8) << 5); self.transport .write(address::CONFIG_0, byte) .map_err(ScdcError::Transport) } /// Reads FRL status from `Status_Flags_0` (0x40) and `Status_Flags_1` (0x41). /// /// Returns [`crate::ProtocolError::UnknownLtpReq`] if the sink reports an /// LTP request value not defined by the HDMI 2.1 specification. pub fn read_status_flags(&mut self) -> Result> { let flags0 = self .transport .read(address::STATUS_FLAGS_0) .map_err(ScdcError::Transport)?; let flags1 = self .transport .read(address::STATUS_FLAGS_1) .map_err(ScdcError::Transport)?; let ltp_req = match (flags1 >> 4) & 0x0F { 0 => LtpReq::None, 1 => LtpReq::Lfsr0, 2 => LtpReq::Lfsr1, 3 => LtpReq::Lfsr2, 4 => LtpReq::Lfsr3, raw => return Err(ScdcError::Protocol(ProtocolError::UnknownLtpReq(raw))), }; Ok(StatusFlags { clock_detected: flags0 & 0x01 != 0, cable_connected: flags0 & 0x02 != 0, ch0_locked: flags0 & 0x04 != 0, ch1_locked: flags0 & 0x08 != 0, ch2_locked: flags0 & 0x10 != 0, ch3_locked: flags0 & 0x20 != 0, flt_ready: flags0 & 0x40 != 0, frl_start: flags1 & 0x01 != 0, ltp_req, }) } } ``` -------------------------------- ### Unit tests for write_frl_config and read_training_status Source: https://docs.rs/culvert/latest/src/culvert/client/plumbob_client.rs.html Test module covering write_frl_config behavior for rate/FFE combinations, DSC FRL max flag, all FFE levels, and read_training_status FLT_READY bit. Uses TestTransport to simulate register reads/writes. ```rust #[cfg(test)] mod tests { use super::super::Scdc; use super::super::test_transport::TestTransport; use display_types::HdmiForumFrl; use plumbob::{FfeLevels, FrlConfig, LtpReq, ScdcClient}; // --- write_frl_config --- #[test] fn write_frl_config_rate_and_ffe() { // Rate12Gbps4Lanes = discriminant 6; Ffe3 = 3 → bits[7:5] = 0b011 = 0x60 let mut scdc = Scdc::new(TestTransport::new()); ScdcClient::write_frl_config( &mut scdc, FrlConfig { rate: HdmiForumFrl::Rate12Gbps4Lanes, ffe_levels: FfeLevels::Ffe3, dsc_frl_max: false, }, ) .unwrap(); assert_eq!(scdc.into_transport().get(0x30), 0x06 | 0x60); } #[test] fn write_frl_config_dsc_frl_max() { let mut scdc = Scdc::new(TestTransport::new()); ScdcClient::write_frl_config( &mut scdc, FrlConfig { rate: HdmiForumFrl::NotSupported, ffe_levels: FfeLevels::Ffe0, dsc_frl_max: true, }, ) .unwrap(); assert_eq!(scdc.into_transport().get(0x30), 0x10); } #[test] fn write_frl_config_all_ffe_levels() { use FfeLevels::*; for (ffe, expected_bits) in [ (Ffe0, 0x00u8), (Ffe1, 0x20), (Ffe2, 0x40), (Ffe3, 0x60), (Ffe4, 0x80), (Ffe5, 0xA0), (Ffe6, 0xC0), (Ffe7, 0xE0), ] { let mut scdc = Scdc::new(TestTransport::new()); ScdcClient::write_frl_config( &mut scdc, FrlConfig { rate: HdmiForumFrl::NotSupported, ffe_levels: ffe, dsc_frl_max: false, }, ) .unwrap(); assert_eq!( scdc.into_transport().get(0x30), expected_bits, "ffe={ffe:?}" ); } } // --- read_training_status --- #[test] fn read_training_status_flt_ready() { let mut sim = TestTransport::new(); sim.set(0x40, 0x40); // flt_ready bit let status = Scdc::new(sim).read_training_status().unwrap(); assert!(status.flt_ready); } } ``` -------------------------------- ### SCDC version register read/write implementation Source: https://docs.rs/culvert/latest/src/culvert/client/version.rs.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Implements read_sink_version and write_source_version methods for the Scdc struct, using the provided transport to read and write SCDC version registers. The read method returns the sink's version from Sink_Version (0x01), and the write method sets the source's version to Source_Version (0x02). Errors from the transport are wrapped in ScdcError::Transport. ```rust use hdmi_hal::scdc::ScdcTransport; use crate::error::ScdcError; use crate::register::address; use super::Scdc; impl Scdc { /// Reads the sink's SCDC protocol version from `Sink_Version` (0x01). pub fn read_sink_version(&mut self) -> Result> { self.transport .read(address::SINK_VERSION) .map_err(ScdcError::Transport) } /// Writes the source's SCDC protocol version to `Source_Version` (0x02). pub fn write_source_version(&mut self, version: u8) -> Result<(), ScdcError> { self.transport .write(address::SOURCE_VERSION, version) .map_err(ScdcError::Transport) } } ``` -------------------------------- ### ScdcClient implementation for Scdc Source: https://docs.rs/culvert/latest/src/culvert/client/plumbob_client.rs.html Implements the plumbob::ScdcClient trait for Scdc, providing write_frl_config, read_training_status, and read_ced methods. Uses helper functions ffe_levels, ltp_req, and ced_count to convert between plumbob and internal types. ```rust //! `plumbob::ScdcClient` implementation for [`Scdc`]. use hdmi_hal::scdc::ScdcTransport; use plumbob::ScdcClient; use crate::error::ScdcError; use crate::register::{CedCount, FfeLevels, FrlConfig, LtpReq}; use super::Scdc; impl ScdcClient for Scdc { type Error = ScdcError; fn write_frl_config(&mut self, config: plumbob::FrlConfig) -> Result<(), Self::Error> { self.write_frl_config(FrlConfig { frl_rate: config.rate, dsc_frl_max: config.dsc_frl_max, ffe_levels: ffe_levels(config.ffe_levels), }) } fn read_training_status(&mut self) -> Result { let f = self.read_status_flags()?; Ok(plumbob::TrainingStatus { flt_ready: f.flt_ready, frl_start: f.frl_start, ltp_req: ltp_req(f.ltp_req), }) } fn read_ced(&mut self) -> Result { let c = self.read_ced()?; Ok(plumbob::CedCounters { lane0: c.lane0.map(ced_count), lane1: c.lane1.map(ced_count), lane2: c.lane2.map(ced_count), lane3: c.lane3.map(ced_count), }) } } fn ffe_levels(f: plumbob::FfeLevels) -> FfeLevels { match f { plumbob::FfeLevels::Ffe0 => FfeLevels::Ffe0, plumbob::FfeLevels::Ffe1 => FfeLevels::Ffe1, plumbob::FfeLevels::Ffe2 => FfeLevels::Ffe2, plumbob::FfeLevels::Ffe3 => FfeLevels::Ffe3, plumbob::FfeLevels::Ffe4 => FfeLevels::Ffe4, plumbob::FfeLevels::Ffe5 => FfeLevels::Ffe5, plumbob::FfeLevels::Ffe6 => FfeLevels::Ffe6, plumbob::FfeLevels::Ffe7 => FfeLevels::Ffe7, } } fn ltp_req(req: LtpReq) -> plumbob::LtpReq { match req { LtpReq::None => plumbob::LtpReq::None, LtpReq::Lfsr0 => plumbob::LtpReq::Lfsr0, LtpReq::Lfsr1 => plumbob::LtpReq::Lfsr1, LtpReq::Lfsr2 => plumbob::LtpReq::Lfsr2, LtpReq::Lfsr3 => plumbob::LtpReq::Lfsr3, } } fn ced_count(c: CedCount) -> plumbob::CedCount { plumbob::CedCount::new(c.value()) } ``` -------------------------------- ### StatusFlags::new Source: https://docs.rs/culvert/latest/culvert/struct.StatusFlags.html Constructs a new StatusFlags instance with the given field values. ```APIDOC ## StatusFlags::new ### Description Constructs a `StatusFlags` instance with the provided field values. ### Method `pub fn new(clock_detected: bool, cable_connected: bool, ch0_locked: bool, ch1_locked: bool, ch2_locked: bool, ch3_locked: bool, flt_ready: bool, frl_start: bool, ltp_req: LtpReq) -> Self` ### Parameters - `clock_detected` (bool) - Required - A TMDS or FRL clock signal is detected on the cable. - `cable_connected` (bool) - Required - A cable is detected on the HDMI connector. - `ch0_locked` (bool) - Required - Lane 0 has achieved symbol lock. - `ch1_locked` (bool) - Required - Lane 1 has achieved symbol lock. - `ch2_locked` (bool) - Required - Lane 2 has achieved symbol lock. - `ch3_locked` (bool) - Required - Lane 3 has achieved symbol lock (FRL 4-lane only). - `flt_ready` (bool) - Required - The sink is ready to begin FRL link training (`FLT_Ready`). - `frl_start` (bool) - Required - The sink signals that FRL training may begin (`FRL_Start`). - `ltp_req` (LtpReq) - Required - The link training pattern currently requested by the sink. ### Returns - `Self` - A new `StatusFlags` instance. ### Example ```rust let flags = StatusFlags::new( true, // clock_detected true, // cable_connected true, // ch0_locked true, // ch1_locked true, // ch2_locked true, // ch3_locked true, // flt_ready true, // frl_start LtpReq::Ltp4, // ltp_req ); ``` ``` -------------------------------- ### StatusFlags::new Source: https://docs.rs/culvert/latest/culvert/struct.StatusFlags.html?search= Constructs a new StatusFlags instance with the given boolean flags and LtpReq value. ```APIDOC ## StatusFlags::new ### Description Constructs a `StatusFlags` instance with the provided boolean flags and `LtpReq` value. ### Method `pub fn new(clock_detected: bool, cable_connected: bool, ch0_locked: bool, ch1_locked: bool, ch2_locked: bool, ch3_locked: bool, flt_ready: bool, frl_start: bool, ltp_req: LtpReq) -> Self` ### Parameters - `clock_detected` (bool) - Required - Indicates whether a clock is detected. - `cable_connected` (bool) - Required - Indicates whether a cable is connected. - `ch0_locked` (bool) - Required - Indicates whether channel 0 is locked. - `ch1_locked` (bool) - Required - Indicates whether channel 1 is locked. - `ch2_locked` (bool) - Required - Indicates whether channel 2 is locked. - `ch3_locked` (bool) - Required - Indicates whether channel 3 is locked. - `flt_ready` (bool) - Required - Indicates whether the filter is ready. - `frl_start` (bool) - Required - Indicates whether the FRL start condition is met. - `ltp_req` (LtpReq) - Required - The LTP request value. ### Returns - `Self` - A new `StatusFlags` instance. ``` -------------------------------- ### SCDC version read/write implementation and tests Source: https://docs.rs/culvert/latest/src/culvert/client/version.rs.html Defines read_sink_version and write_source_version methods on Scdc for SCDC protocol version registers. Includes unit tests using TestTransport to verify register reads/writes and transport error propagation. ```rust use hdmi_hal::scdc::ScdcTransport; use crate::error::ScdcError; use crate::register::address; use super::Scdc; impl Scdc { /// Reads the sink's SCDC protocol version from `Sink_Version` (0x01). pub fn read_sink_version(&mut self) -> Result> { self.transport .read(address::SINK_VERSION) .map_err(ScdcError::Transport) } /// Writes the source's SCDC protocol version to `Source_Version` (0x02). pub fn write_source_version(&mut self, version: u8) -> Result<(), ScdcError> { self.transport .write(address::SOURCE_VERSION, version) .map_err(ScdcError::Transport) } } #[cfg(test)] mod tests { use super::super::Scdc; use super::super::test_transport::TestTransport; #[test] fn read_sink_version_returns_register_value() { let mut sim = TestTransport::new(); sim.set(0x01, 0x01); assert_eq!(Scdc::new(sim).read_sink_version().unwrap(), 0x01); } #[test] fn write_source_version_writes_register() { let mut scdc = Scdc::new(TestTransport::new()); scdc.write_source_version(0x01).unwrap(); assert_eq!(scdc.into_transport().get(0x02), 0x01); } #[test] fn transport_error_propagates() { assert!( Scdc::new(TestTransport::failing_after(0)) .read_sink_version() .is_err() ); assert!( Scdc::new(TestTransport::failing_after(0)) .write_source_version(1) .is_err() ); } } ``` -------------------------------- ### Unit tests for FRL config and status flags Source: https://docs.rs/culvert/latest/src/culvert/client/frl.rs.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Tests cover encoding of FRL rate, DSC FRL max, and FFE levels into the Config_0 register, and reading status flags including LTP request variants. Uses `TestTransport` to simulate register reads and writes. ```rust #[cfg(test)] mod tests { use super::super::Scdc; use super::super::test_transport::TestTransport; use crate::error::{ProtocolError, ScdcError}; use crate::register::{FfeLevels, FrlConfig, LtpReq, StatusFlags}; use display_types::HdmiForumFrl; #[test] fn frl_config_rate_field() { let mut scdc = Scdc::new(TestTransport::new()); scdc.write_frl_config(FrlConfig { frl_rate: HdmiForumFrl::Rate12Gbps4Lanes, // discriminant 6 dsc_frl_max: false, ffe_levels: FfeLevels::Ffe0, }) .unwrap(); assert_eq!(scdc.into_transport().get(0x30), 0x06); } #[test] fn frl_config_dsc_frl_max_field() { let mut scdc = Scdc::new(TestTransport::new()); scdc.write_frl_config(FrlConfig { frl_rate: HdmiForumFrl::NotSupported, dsc_frl_max: true, ffe_levels: FfeLevels::Ffe0, }) .unwrap(); assert_eq!(scdc.into_transport().get(0x30), 0x10); } #[test] fn frl_config_ffe_levels_field() { let mut scdc = Scdc::new(TestTransport::new()); scdc.write_frl_config(FrlConfig { frl_rate: HdmiForumFrl::NotSupported, dsc_frl_max: false, ffe_levels: FfeLevels::Ffe7, // discriminant 7 → bits[7:5] = 0b111 = 0xE0 }) .unwrap(); assert_eq!(scdc.into_transport().get(0x30), 0xE0); } #[test] fn status_flags_all_zero() { let mut scdc = Scdc::new(TestTransport::new()); assert_eq!( scdc.read_status_flags().unwrap(), StatusFlags { clock_detected: false, cable_connected: false, ch0_locked: false, ch1_locked: false, ch2_locked: false, ch3_locked: false, flt_ready: false, frl_start: false, ltp_req: LtpReq::None, } ); } #[test] fn status_flags_ltp_req_variants() { for (nibble, expected) in [ (0u8, LtpReq::None), (1, LtpReq::Lfsr0), (2, LtpReq::Lfsr1), (3, LtpReq::Lfsr2), (4, LtpReq::Lfsr3), ] { let mut sim = TestTransport::new(); sim.set(0x41, nibble << 4); assert_eq!( Scdc::new(sim).read_status_flags().unwrap().ltp_req, expected ); } } } ``` -------------------------------- ### FrlRate::from_raw Source: https://docs.rs/culvert/latest/culvert/enum.FrlRate.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Decodes the Max_FRL_Rate nibble from the HDMI 2.1 SCDS into an HdmiForumFrl value. ```APIDOC ## FrlRate::from_raw ### Description Decodes the `Max_FRL_Rate` nibble from the HDMI 2.1 SCDS. ### Method `pub fn from_raw(raw: u8) -> HdmiForumFrl` ### Parameters - **raw** (u8) - Required - The raw nibble value from the HDMI 2.1 SCDS. ### Returns - **HdmiForumFrl** - The decoded FRL rate value. ``` -------------------------------- ### FrlRate::from_raw Source: https://docs.rs/culvert/latest/culvert/enum.FrlRate.html?search= Decodes the Max_FRL_Rate nibble from the HDMI 2.1 SCDS into an HdmiForumFrl value. ```APIDOC ## FrlRate::from_raw ### Description Decodes the `Max_FRL_Rate` nibble from the HDMI 2.1 SCDS. ### Method `pub fn from_raw(raw: u8) -> HdmiForumFrl` ### Parameters - **raw** (u8) - Required - The raw nibble value to decode. ### Returns - **HdmiForumFrl** - The decoded FRL rate value. ``` -------------------------------- ### Scdc client module source code Source: https://docs.rs/culvert/latest/src/culvert/client/mod.rs.html?search= The full source code of the culvert client module, defining the Scdc struct and its methods. ```rust 1//! The [`Scdc`] client. 2 3use hdmi_hal::scdc::ScdcTransport; 4 5mod ced; 6mod frl; 7#[cfg(feature = "plumbob")] 8mod plumbob_client; 9mod scrambling; 10mod update; 11mod version; 12 13#[cfg(test)] 14mod test_transport; 15 16/// Typed client for the HDMI 2.1 SCDC (Status and Control Data Channel) register map. 17/// 18/// `Scdc` wraps a [`ScdcTransport`] and exposes one typed method per register group. 19/// It holds no protocol state; all sequencing logic belongs in the caller. 20/// 21/// # Example 22/// /// ```ignore /// let mut scdc = Scdc::new(transport); /// let sink_version = scdc.read_sink_version()?; /// scdc.write_source_version(1)?; /// ``` 28pub struct Scdc { 29 transport: T, 30} 31 32impl Scdc { 33 /// Creates a new `Scdc` client wrapping the given transport. 34 pub fn new(transport: T) -> Self { 35 Self { transport } 36 } 37 38 /// Consumes the client and returns the underlying transport. 39 pub fn into_transport(self) -> T { 40 self.transport 41 } 42} ``` -------------------------------- ### CedCount::new Source: https://docs.rs/culvert/latest/culvert/struct.CedCount.html Constructs a new CedCount from a raw u16 value, masking to 15 bits. ```APIDOC ## CedCount::new ### Description Constructs a `CedCount`, masking to 15 bits. ### Method `pub fn new(raw: u16) -> Self` ### Parameters - **raw** (u16) - The raw value to construct from. ### Returns - **Self** - A new `CedCount` instance with the value masked to 15 bits. ``` -------------------------------- ### CedCount::new Source: https://docs.rs/culvert/latest/culvert/struct.CedCount.html?search=Option%3CT%3E%2C+%28T+-%3E+U%29+-%3E+Option%3CU%3E Constructs a new CedCount from a raw u16 value, masking to 15 bits. ```APIDOC ## CedCount::new ### Description Constructs a `CedCount`, masking to 15 bits. ### Method `pub fn new(raw: u16) -> Self` ### Parameters - **raw** (u16) - Required - The raw value to construct the `CedCount` from. ### Returns - **Self** - A new `CedCount` instance with the value masked to 15 bits. ```