firewire_fireface_protocols/latter/
ucx.rs

1// SPDX-License-Identifier: LGPL-3.0-or-later
2// Copyright (c) 2021 Takashi Sakamoto
3
4//! Protocol defined by RME GmbH for Fireface UCX.
5
6use super::*;
7
8/// Unique protocol for UCX.
9#[derive(Default, Debug)]
10pub struct FfUcxProtocol;
11
12// For configuration register (0x'ffff'0000'0014).
13const CFG_CLK_SRC_MASK: u32 = 0x00000c00;
14const CFG_CLK_SRC_WORD_CLK_FLAG: u32 = 0x00000c00;
15const CFG_CLK_SRC_OPT_IFACE_FLAG: u32 = 0x00000800;
16const CFG_CLK_SRC_COAX_IFACE_FLAG: u32 = 0x00000400;
17const CFG_CLK_SRC_INTERNAL_FLAG: u32 = 0x00000000;
18const CFG_SPDIF_OUT_TO_OPT_IFACE_MASK: u32 = 0x00000100;
19const CFG_WORD_OUT_SINGLE_MASK: u32 = 0x00000010;
20const CFG_DSP_EFFECT_ON_INPUT_MASK: u32 = 0x00000040;
21const CFG_WORD_INPUT_TERMINATE_MASK: u32 = 0x00000008;
22const CFG_SPDIF_OUT_PRO_MASK: u32 = 0x00000020;
23
24/// Signal source of sampling clock.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum FfUcxClkSrc {
27    Internal,
28    Coax,
29    Opt,
30    WordClk,
31}
32
33impl Default for FfUcxClkSrc {
34    fn default() -> Self {
35        Self::Internal
36    }
37}
38
39fn serialize_clock_source(src: &FfUcxClkSrc, quad: &mut u32) {
40    *quad |= match src {
41        FfUcxClkSrc::WordClk => CFG_CLK_SRC_WORD_CLK_FLAG,
42        FfUcxClkSrc::Opt => CFG_CLK_SRC_OPT_IFACE_FLAG,
43        FfUcxClkSrc::Coax => CFG_CLK_SRC_COAX_IFACE_FLAG,
44        FfUcxClkSrc::Internal => CFG_CLK_SRC_INTERNAL_FLAG,
45    };
46}
47
48fn deserialize_clock_source(src: &mut FfUcxClkSrc, quad: &u32) {
49    *src = match *quad & CFG_CLK_SRC_MASK {
50        CFG_CLK_SRC_WORD_CLK_FLAG => FfUcxClkSrc::WordClk,
51        CFG_CLK_SRC_OPT_IFACE_FLAG => FfUcxClkSrc::Opt,
52        CFG_CLK_SRC_COAX_IFACE_FLAG => FfUcxClkSrc::Coax,
53        CFG_CLK_SRC_INTERNAL_FLAG => FfUcxClkSrc::Internal,
54        _ => unreachable!(),
55    };
56}
57
58/// Configuration for UCX.
59#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
60pub struct FfUcxConfig {
61    /// The low offset of destination address for MIDI messages.
62    midi_tx_low_offset: FfLatterMidiTxLowOffset,
63    /// The source of sampling clock.
64    pub clk_src: FfUcxClkSrc,
65    /// The type of signal to optical output interface.
66    pub opt_out_signal: OpticalOutputSignal,
67    /// Whether to fix speed to single even if at double/quadruple rate.
68    pub word_out_single: bool,
69    /// Whether to enable DSP effect on inputs.
70    pub effect_on_inputs: bool,
71    /// Whether to terminate word clock input.
72    pub word_in_terminate: bool,
73    /// For signal format of S/PDIF output.
74    pub spdif_out_format: SpdifFormat,
75}
76
77impl RmeFfOffsetParamsSerialize<FfUcxConfig> for FfUcxProtocol {
78    fn serialize_offsets(state: &FfUcxConfig) -> Vec<u8> {
79        let mut quad = 0;
80
81        serialize_midi_tx_low_offset(&state.midi_tx_low_offset, &mut quad);
82        serialize_clock_source(&state.clk_src, &mut quad);
83
84        if state.opt_out_signal == OpticalOutputSignal::Spdif {
85            quad |= CFG_SPDIF_OUT_TO_OPT_IFACE_MASK;
86        }
87
88        if state.word_out_single {
89            quad |= CFG_WORD_OUT_SINGLE_MASK;
90        }
91
92        if state.effect_on_inputs {
93            quad |= CFG_DSP_EFFECT_ON_INPUT_MASK;
94        }
95
96        if state.word_in_terminate {
97            quad |= CFG_WORD_INPUT_TERMINATE_MASK;
98        }
99
100        if state.spdif_out_format == SpdifFormat::Professional {
101            quad |= CFG_SPDIF_OUT_PRO_MASK;
102        }
103
104        quad.to_le_bytes().to_vec()
105    }
106}
107
108impl RmeFfOffsetParamsDeserialize<FfUcxConfig> for FfUcxProtocol {
109    fn deserialize_offsets(state: &mut FfUcxConfig, raw: &[u8]) {
110        assert!(raw.len() >= LATTER_CONFIG_SIZE);
111
112        let mut r = [0; 4];
113        r.copy_from_slice(&raw[..4]);
114        let quad = u32::from_le_bytes(r);
115
116        deserialize_midi_tx_low_offset(&mut state.midi_tx_low_offset, &quad);
117        deserialize_clock_source(&mut state.clk_src, &quad);
118
119        state.opt_out_signal = if quad & CFG_SPDIF_OUT_TO_OPT_IFACE_MASK > 0 {
120            OpticalOutputSignal::Spdif
121        } else {
122            OpticalOutputSignal::Adat
123        };
124
125        state.word_out_single = quad & CFG_WORD_OUT_SINGLE_MASK > 0;
126        state.effect_on_inputs = quad & CFG_DSP_EFFECT_ON_INPUT_MASK > 0;
127        state.word_in_terminate = quad & CFG_WORD_INPUT_TERMINATE_MASK > 0;
128        state.spdif_out_format = if quad & CFG_SPDIF_OUT_PRO_MASK > 0 {
129            SpdifFormat::Professional
130        } else {
131            SpdifFormat::Consumer
132        };
133    }
134}
135
136impl RmeFfWhollyUpdatableParamsOperation<FfUcxConfig> for FfUcxProtocol {
137    fn update_wholly(
138        req: &mut FwReq,
139        node: &mut FwNode,
140        params: &FfUcxConfig,
141        timeout_ms: u32,
142    ) -> Result<(), Error> {
143        write_config::<FfUcxProtocol, FfUcxConfig>(req, node, params, timeout_ms)
144    }
145}
146
147// For status register (0x'ffff'0000'001c).
148#[allow(dead_code)]
149const STATUS_ACTIVE_CLK_RATE_MASK: u32 = 0x0f000000;
150#[allow(dead_code)]
151const STATUS_WORD_CLK_RATE_MASK: u32 = 0x00f00000;
152#[allow(dead_code)]
153const STATUS_OPT_IFACE_RATE_MASK: u32 = 0x000f0000;
154#[allow(dead_code)]
155const STATUS_COAX_IFACE_RATE_MASK: u32 = 0x0000f000;
156const STATUS_ACTIVE_CLK_SRC_MASK: u32 = 0x00000e00;
157const STATUS_ACTIVE_CLK_SRC_INTERNAL_FLAG: u32 = 0x00000e00;
158const STATUS_ACTIVE_CLK_SRC_WORD_CLK_FLAG: u32 = 0x00000600;
159const STATUS_ACTIVE_CLK_SRC_OPT_IFACE_FLAG: u32 = 0x00000400;
160const STATUS_ACTIVE_CLK_SRC_COAX_IFACE_FLAG: u32 = 0x00000200;
161const STATUS_OPT_OUT_IFACE_FOR_ADAT: u32 = 0x00000100;
162const STATUS_SYNC_MASK: u32 = 0x00000070;
163const STATUS_SYNC_WORD_CLK_MASK: u32 = 0x00000040;
164const STATUS_SYNC_OPT_IFACE_MASK: u32 = 0x00000020;
165const STATUS_SYNC_COAX_IFACE_MASK: u32 = 0x00000010;
166const STATUS_LOCK_MASK: u32 = 0x00000007;
167const STATUS_LOCK_WORD_CLK_MASK: u32 = 0x00000004;
168const STATUS_LOCK_OPT_IFACE_MASK: u32 = 0x00000002;
169const STATUS_LOCK_COAX_IFACE_MASK: u32 = 0x00000001;
170
171/// Lock status of UCX.
172#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
173pub struct FfUcxExtLockStatus {
174    pub word_clk: bool,
175    pub opt_iface: bool,
176    pub coax_iface: bool,
177}
178
179fn serialize_external_lock_status(status: &FfUcxExtLockStatus, quad: &mut u32) {
180    *quad &= !STATUS_LOCK_MASK;
181    if status.word_clk {
182        *quad |= STATUS_LOCK_WORD_CLK_MASK;
183    }
184    if status.opt_iface {
185        *quad |= STATUS_LOCK_OPT_IFACE_MASK;
186    }
187    if status.coax_iface {
188        *quad |= STATUS_LOCK_COAX_IFACE_MASK;
189    }
190}
191
192fn deserialize_external_lock_status(status: &mut FfUcxExtLockStatus, quad: &u32) {
193    status.word_clk = *quad & STATUS_LOCK_WORD_CLK_MASK > 0;
194    status.opt_iface = *quad & STATUS_LOCK_OPT_IFACE_MASK > 0;
195    status.coax_iface = *quad & STATUS_LOCK_COAX_IFACE_MASK > 0;
196}
197
198/// Sync status of UCX.
199#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
200pub struct FfUcxExtSyncStatus {
201    pub word_clk: bool,
202    pub opt_iface: bool,
203    pub coax_iface: bool,
204}
205
206fn serialize_external_sync_status(status: &FfUcxExtSyncStatus, quad: &mut u32) {
207    *quad &= !STATUS_SYNC_MASK;
208    if status.word_clk {
209        *quad |= STATUS_SYNC_WORD_CLK_MASK;
210    }
211    if status.opt_iface {
212        *quad |= STATUS_SYNC_OPT_IFACE_MASK;
213    }
214    if status.coax_iface {
215        *quad |= STATUS_SYNC_COAX_IFACE_MASK;
216    }
217}
218
219fn deserialize_external_sync_status(status: &mut FfUcxExtSyncStatus, quad: &u32) {
220    status.word_clk = *quad & STATUS_SYNC_WORD_CLK_MASK > 0;
221    status.opt_iface = *quad & STATUS_SYNC_OPT_IFACE_MASK > 0;
222    status.coax_iface = *quad & STATUS_SYNC_COAX_IFACE_MASK > 0;
223}
224
225/// Sync status of UCX.
226#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
227pub struct FfUcxExtRateStatus {
228    pub word_clk: Option<ClkNominalRate>,
229    pub opt_iface: Option<ClkNominalRate>,
230    pub coax_iface: Option<ClkNominalRate>,
231}
232
233fn serialize_external_rate(
234    rate: &Option<ClkNominalRate>,
235    quad: &mut u32,
236    shift: usize,
237    lock_flag: u32,
238) {
239    serialize_clock_rate_optional(rate, quad, shift);
240
241    // NOTE: The lock flag should stand.
242    if rate.is_some() {
243        *quad |= lock_flag;
244    }
245}
246
247fn serialize_external_rate_status(status: &FfUcxExtRateStatus, quad: &mut u32) {
248    *quad &=
249        !(STATUS_WORD_CLK_RATE_MASK | STATUS_OPT_IFACE_RATE_MASK | STATUS_COAX_IFACE_RATE_MASK);
250    serialize_external_rate(&status.word_clk, quad, 20, STATUS_LOCK_WORD_CLK_MASK);
251    serialize_external_rate(&status.opt_iface, quad, 16, STATUS_LOCK_OPT_IFACE_MASK);
252    serialize_external_rate(&status.coax_iface, quad, 12, STATUS_LOCK_COAX_IFACE_MASK);
253}
254
255fn deserialize_external_rate_status(status: &mut FfUcxExtRateStatus, quad: &u32) {
256    if *quad & (STATUS_SYNC_WORD_CLK_MASK | STATUS_LOCK_WORD_CLK_MASK) > 0 {
257        deserialize_clock_rate_optional(&mut status.word_clk, quad, 20);
258    } else {
259        status.word_clk = None;
260    }
261    if *quad & (STATUS_SYNC_OPT_IFACE_MASK | STATUS_LOCK_OPT_IFACE_MASK) > 0 {
262        deserialize_clock_rate_optional(&mut status.opt_iface, quad, 16);
263    } else {
264        status.opt_iface = None;
265    }
266    if *quad & (STATUS_SYNC_COAX_IFACE_MASK | STATUS_LOCK_COAX_IFACE_MASK) > 0 {
267        deserialize_clock_rate_optional(&mut status.coax_iface, quad, 12);
268    } else {
269        status.coax_iface = None;
270    }
271}
272
273/// Status of UCX.
274#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
275pub struct FfUcxStatus {
276    pub ext_lock: FfUcxExtLockStatus,
277    pub ext_sync: FfUcxExtSyncStatus,
278    pub ext_rate: FfUcxExtRateStatus,
279    pub opt_out_signal: OpticalOutputSignal,
280    pub active_clk_src: FfUcxClkSrc,
281    pub active_clk_rate: ClkNominalRate,
282}
283
284impl RmeFfOffsetParamsSerialize<FfUcxStatus> for FfUcxProtocol {
285    fn serialize_offsets(state: &FfUcxStatus) -> Vec<u8> {
286        let mut quad = 0;
287
288        serialize_external_lock_status(&state.ext_lock, &mut quad);
289        serialize_external_sync_status(&state.ext_sync, &mut quad);
290        serialize_external_rate_status(&state.ext_rate, &mut quad);
291
292        quad &= !STATUS_OPT_OUT_IFACE_FOR_ADAT;
293        if state.opt_out_signal == OpticalOutputSignal::Adat {
294            quad |= STATUS_OPT_OUT_IFACE_FOR_ADAT;
295        }
296
297        serialize_clock_rate(&state.active_clk_rate, &mut quad, 24);
298
299        quad &= !STATUS_ACTIVE_CLK_SRC_MASK;
300        let val = match state.active_clk_src {
301            FfUcxClkSrc::Internal => STATUS_ACTIVE_CLK_SRC_INTERNAL_FLAG,
302            FfUcxClkSrc::Coax => STATUS_ACTIVE_CLK_SRC_COAX_IFACE_FLAG,
303            FfUcxClkSrc::Opt => STATUS_ACTIVE_CLK_SRC_OPT_IFACE_FLAG,
304            FfUcxClkSrc::WordClk => STATUS_ACTIVE_CLK_SRC_WORD_CLK_FLAG,
305        };
306        quad |= val;
307
308        quad.to_le_bytes().to_vec()
309    }
310}
311
312impl RmeFfOffsetParamsDeserialize<FfUcxStatus> for FfUcxProtocol {
313    fn deserialize_offsets(state: &mut FfUcxStatus, raw: &[u8]) {
314        assert!(raw.len() >= LATTER_STATUS_SIZE);
315
316        let mut r = [0; 4];
317        r.copy_from_slice(&raw[..4]);
318        let quad = u32::from_le_bytes(r);
319
320        deserialize_external_lock_status(&mut state.ext_lock, &quad);
321        deserialize_external_sync_status(&mut state.ext_sync, &quad);
322        deserialize_external_rate_status(&mut state.ext_rate, &quad);
323
324        state.opt_out_signal = if quad & STATUS_OPT_OUT_IFACE_FOR_ADAT > 0 {
325            OpticalOutputSignal::Adat
326        } else {
327            OpticalOutputSignal::Spdif
328        };
329
330        deserialize_clock_rate(&mut state.active_clk_rate, &quad, 24);
331
332        state.active_clk_src = match quad & STATUS_ACTIVE_CLK_SRC_MASK {
333            STATUS_ACTIVE_CLK_SRC_INTERNAL_FLAG => FfUcxClkSrc::Internal,
334            STATUS_ACTIVE_CLK_SRC_COAX_IFACE_FLAG => FfUcxClkSrc::Coax,
335            STATUS_ACTIVE_CLK_SRC_OPT_IFACE_FLAG => FfUcxClkSrc::Opt,
336            STATUS_ACTIVE_CLK_SRC_WORD_CLK_FLAG => FfUcxClkSrc::WordClk,
337            _ => unreachable!(),
338        };
339    }
340}
341
342impl RmeFfCacheableParamsOperation<FfUcxStatus> for FfUcxProtocol {
343    fn cache_wholly(
344        req: &mut FwReq,
345        node: &mut FwNode,
346        status: &mut FfUcxStatus,
347        timeout_ms: u32,
348    ) -> Result<(), Error> {
349        read_status::<FfUcxProtocol, FfUcxStatus>(req, node, status, timeout_ms)
350    }
351}
352
353impl RmeFfLatterSpecification for FfUcxProtocol {
354    const LINE_INPUT_COUNT: usize = 6;
355    const MIC_INPUT_COUNT: usize = 2;
356    const SPDIF_INPUT_COUNT: usize = 2;
357    const ADAT_INPUT_COUNT: usize = 8;
358    const STREAM_INPUT_COUNT: usize = 18;
359
360    const LINE_OUTPUT_COUNT: usize = 6;
361    const HP_OUTPUT_COUNT: usize = 2;
362    const SPDIF_OUTPUT_COUNT: usize = 2;
363    const ADAT_OUTPUT_COUNT: usize = 8;
364}
365
366#[cfg(test)]
367mod test {
368    use super::*;
369
370    #[test]
371    fn clock_source_serdes() {
372        [
373            FfUcxClkSrc::WordClk,
374            FfUcxClkSrc::Opt,
375            FfUcxClkSrc::Coax,
376            FfUcxClkSrc::Internal,
377        ]
378        .iter()
379        .for_each(|orig| {
380            let mut quad = 0;
381            serialize_clock_source(&orig, &mut quad);
382            let mut target = FfUcxClkSrc::default();
383            deserialize_clock_source(&mut target, &quad);
384
385            assert_eq!(&target, orig);
386        });
387    }
388
389    #[test]
390    fn config_serdes() {
391        let orig = FfUcxConfig {
392            midi_tx_low_offset: FfLatterMidiTxLowOffset::A0180,
393            clk_src: FfUcxClkSrc::Opt,
394            opt_out_signal: OpticalOutputSignal::Spdif,
395            word_out_single: true,
396            effect_on_inputs: true,
397            word_in_terminate: true,
398            spdif_out_format: SpdifFormat::Professional,
399        };
400        let quads = FfUcxProtocol::serialize_offsets(&orig);
401        let mut target = FfUcxConfig::default();
402        FfUcxProtocol::deserialize_offsets(&mut target, &quads);
403
404        assert_eq!(target, orig);
405    }
406
407    #[test]
408    fn external_lock_status_serdes() {
409        let orig = FfUcxExtLockStatus {
410            word_clk: true,
411            opt_iface: true,
412            coax_iface: true,
413        };
414        let mut quad = 0;
415        serialize_external_lock_status(&orig, &mut quad);
416        let mut target = FfUcxExtLockStatus::default();
417        deserialize_external_lock_status(&mut target, &quad);
418
419        assert_eq!(target, orig);
420    }
421
422    #[test]
423    fn external_sync_status_serdes() {
424        let orig = FfUcxExtSyncStatus {
425            word_clk: true,
426            opt_iface: true,
427            coax_iface: true,
428        };
429        let mut quad = 0;
430        serialize_external_sync_status(&orig, &mut quad);
431        let mut target = FfUcxExtSyncStatus::default();
432        deserialize_external_sync_status(&mut target, &quad);
433
434        assert_eq!(target, orig);
435    }
436
437    #[test]
438    fn external_rate_status_serdes() {
439        let orig = FfUcxExtRateStatus {
440            word_clk: Some(ClkNominalRate::R88200),
441            opt_iface: Some(ClkNominalRate::R192000),
442            coax_iface: Some(ClkNominalRate::R44100),
443        };
444        let mut quad = 0;
445        serialize_external_rate_status(&orig, &mut quad);
446        let mut target = FfUcxExtRateStatus::default();
447        deserialize_external_rate_status(&mut target, &quad);
448
449        assert_eq!(target, orig);
450    }
451
452    #[test]
453    fn status_serdes() {
454        let orig = FfUcxStatus {
455            ext_lock: FfUcxExtLockStatus {
456                word_clk: true,
457                opt_iface: false,
458                coax_iface: true,
459            },
460            ext_sync: FfUcxExtSyncStatus {
461                word_clk: false,
462                opt_iface: false,
463                coax_iface: true,
464            },
465            ext_rate: FfUcxExtRateStatus {
466                word_clk: Some(ClkNominalRate::R176400),
467                opt_iface: None,
468                coax_iface: Some(ClkNominalRate::R48000),
469            },
470            opt_out_signal: OpticalOutputSignal::Spdif,
471            active_clk_src: FfUcxClkSrc::Opt,
472            active_clk_rate: ClkNominalRate::R88200,
473        };
474        let raw = FfUcxProtocol::serialize_offsets(&orig);
475        let mut target = FfUcxStatus::default();
476        FfUcxProtocol::deserialize_offsets(&mut target, &raw);
477
478        assert_eq!(target, orig);
479    }
480}