Skip to main content

plumbob_async/trainer/
mod.rs

1use display_types::cea861::hdmi_forum::HdmiForumFrl;
2use hdmi_hal_async::phy::HdmiPhy;
3use hdmi_hal_async::{EqParams, FrlOutput, LanePatterns};
4use plumbob::ClearableUpdateFlags;
5use plumbob::lts::{self, TrainingIo};
6
7#[cfg(feature = "alloc")]
8use alloc::vec::Vec;
9
10use crate::client::ScdcClient;
11use crate::{
12    ExitError, FrlConfig, LtpRequests, SourceTestConfig, Trained, TrainingConfig, TrainingError,
13    TrainingEvent, UpdateFlags,
14};
15
16#[cfg(feature = "alloc")]
17use crate::TrainingTrace;
18
19type Error<C, P> = TrainingError<<C as ScdcClient>::Error, <P as HdmiPhy>::Error>;
20
21/// The central async training type. Owns an async `ScdcClient` and an async `HdmiPhy`.
22///
23/// The async counterpart to [`plumbob::FrlTrainer`]: it runs plumbob's state machine,
24/// [`plumbob::lts::run`], awaiting each SCDC and PHY operation, so it yields to the
25/// executor wherever the I/O waits. Reusable across training attempts; use
26/// [`into_parts`](Self::into_parts) to recover the SCDC client and PHY.
27///
28/// # Cancellation
29///
30/// [`train`](Self::train) is not cancel-safe. Dropping its future — a `select!` against
31/// a hot-unplug, a `timeout` — stops training wherever it is: the sink may be configured
32/// for an FRL rate and the PHY driving training patterns, and no LTS:L runs, since a
33/// future cannot await anything once dropped. Call [`exit_to_tmds`](Self::exit_to_tmds)
34/// afterwards to return both ends to TMDS. Bounding training is better left to the poll
35/// limits in [`TrainingConfig`], which end every attempt in a defined state.
36pub struct FrlTrainer<C, P> {
37    scdc: C,
38    phy: P,
39}
40
41impl<C: ScdcClient, P: HdmiPhy> FrlTrainer<C, P> {
42    /// Constructs a new `FrlTrainer` owning the given async SCDC client and PHY.
43    pub fn new(scdc: C, phy: P) -> Self {
44        Self { scdc, phy }
45    }
46
47    /// Consumes the trainer and returns the SCDC client and PHY.
48    pub fn into_parts(self) -> (C, P) {
49        (self.scdc, self.phy)
50    }
51
52    /// The SCDC client.
53    pub fn scdc(&self) -> &C {
54        &self.scdc
55    }
56
57    /// The SCDC client, mutably.
58    pub fn scdc_mut(&mut self) -> &mut C {
59        &mut self.scdc
60    }
61
62    /// The PHY.
63    pub fn phy(&self) -> &P {
64        &self.phy
65    }
66
67    /// The PHY, mutably: after `Success`, for example, to start video with
68    /// `set_frl_output(FrlOutput::Active)`.
69    pub fn phy_mut(&mut self) -> &mut P {
70        &mut self.phy
71    }
72
73    /// LTS:L on demand: stops the training patterns, returns the PHY to TMDS, turns FRL
74    /// off in `Config_1` and clears `FLT_update` if it is set.
75    ///
76    /// The async counterpart to [`plumbob::FrlTrainer::exit_to_tmds`]: for taking an FRL
77    /// link down when the display is disabled or unplugged, before a mode change, after
78    /// an error that did not lead to LTS:L (`TmdsExit::Skipped` or `HeldUnderTest`), and
79    /// after a dropped
80    /// [`train`](Self::train) future (see [Cancellation](Self#cancellation)). Safe to call
81    /// in any state, and again. Every step is attempted even when one fails.
82    pub async fn exit_to_tmds(&mut self) -> Result<(), ExitError<C::Error, P::Error>> {
83        let mut io = AsyncIo {
84            scdc: &mut self.scdc,
85            phy: &mut self.phy,
86        };
87        lts::exit_to_tmds(&mut io, &mut |_| {}).await
88    }
89
90    /// Trains at a single rate: `train(&[rate], config)`.
91    pub async fn train_at_rate(
92        &mut self,
93        rate: HdmiForumFrl,
94        config: &TrainingConfig,
95    ) -> Result<Trained, Error<C, P>> {
96        self.train(&[rate], config).await
97    }
98
99    /// Trains over `rates` in order, stepping down when the sink requests it.
100    ///
101    /// The async counterpart to [`plumbob::FrlTrainer::train`], with the same outcomes and
102    /// errors. Not cancel-safe: see [Cancellation](Self#cancellation).
103    pub async fn train(
104        &mut self,
105        rates: &[HdmiForumFrl],
106        config: &TrainingConfig,
107    ) -> Result<Trained, Error<C, P>> {
108        self.train_with_events(rates, config, &mut |_| {}).await
109    }
110
111    /// Like [`train_at_rate`](Self::train_at_rate), and also returns a [`TrainingTrace`]
112    /// of the attempt, whatever its result.
113    #[cfg(feature = "alloc")]
114    pub async fn train_at_rate_traced(
115        &mut self,
116        rate: HdmiForumFrl,
117        config: &TrainingConfig,
118    ) -> (Result<Trained, Error<C, P>>, TrainingTrace) {
119        self.train_traced(&[rate], config).await
120    }
121
122    /// Like [`train`](Self::train), and also returns a [`TrainingTrace`] of the attempt.
123    ///
124    /// The trace is returned whatever the result, so an attempt that ended in a
125    /// [`TrainingError`] can be explained as well.
126    #[cfg(feature = "alloc")]
127    pub async fn train_traced(
128        &mut self,
129        rates: &[HdmiForumFrl],
130        config: &TrainingConfig,
131    ) -> (Result<Trained, Error<C, P>>, TrainingTrace) {
132        let mut events = Vec::new();
133        let result = self
134            .train_with_events(rates, config, &mut |event| events.push(event))
135            .await;
136        (result, TrainingTrace::new(rates.to_vec(), *config, events))
137    }
138
139    /// Like [`train`](Self::train), calling `record` with each [`TrainingEvent`] as it
140    /// occurs: the events `train_traced` (with `alloc`) collects, without an
141    /// allocator. `record` is a plain callback; it is called between I/O operations, never
142    /// while one is awaited.
143    pub async fn train_with_events<F: FnMut(TrainingEvent)>(
144        &mut self,
145        rates: &[HdmiForumFrl],
146        config: &TrainingConfig,
147        record: &mut F,
148    ) -> Result<Trained, Error<C, P>> {
149        let mut io = AsyncIo {
150            scdc: &mut self.scdc,
151            phy: &mut self.phy,
152        };
153        lts::run(&mut io, rates, config, record).await
154    }
155}
156
157/// [`TrainingIo`] over an async `ScdcClient` and `HdmiPhy`.
158struct AsyncIo<'a, C, P> {
159    scdc: &'a mut C,
160    phy: &'a mut P,
161}
162
163impl<C: ScdcClient, P: HdmiPhy> TrainingIo for AsyncIo<'_, C, P> {
164    type ScdcError = C::Error;
165    type PhyError = P::Error;
166
167    async fn read_flt_ready(&mut self) -> Result<bool, C::Error> {
168        self.scdc.read_flt_ready().await
169    }
170
171    async fn read_update_flags(&mut self) -> Result<UpdateFlags, C::Error> {
172        self.scdc.read_update_flags().await
173    }
174
175    async fn clear_update_flags(&mut self, flags: ClearableUpdateFlags) -> Result<(), C::Error> {
176        self.scdc.clear_update_flags(flags).await
177    }
178
179    async fn read_ltp_requests(&mut self) -> Result<LtpRequests, C::Error> {
180        self.scdc.read_ltp_requests().await
181    }
182
183    async fn read_source_test_config(&mut self) -> Result<SourceTestConfig, C::Error> {
184        self.scdc.read_source_test_config().await
185    }
186
187    async fn write_config_0_defaults(&mut self) -> Result<(), C::Error> {
188        self.scdc.write_config_0_defaults().await
189    }
190
191    async fn write_frl_config(&mut self, config: FrlConfig) -> Result<(), C::Error> {
192        self.scdc.write_frl_config(config).await
193    }
194
195    async fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), P::Error> {
196        self.phy.set_frl_rate(rate).await
197    }
198
199    async fn send_ltp(&mut self, patterns: LanePatterns) -> Result<(), P::Error> {
200        self.phy.send_ltp(patterns).await
201    }
202
203    async fn set_frl_output(&mut self, output: FrlOutput) -> Result<(), P::Error> {
204        self.phy.set_frl_output(output).await
205    }
206
207    async fn adjust_equalization(&mut self, params: EqParams) -> Result<(), P::Error> {
208        self.phy.adjust_equalization(params).await
209    }
210}
211
212#[cfg(test)]
213mod tests;