1use super::*;
7
8#[derive(Default, Debug)]
10pub struct FfUcxProtocol;
11
12const 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#[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#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
60pub struct FfUcxConfig {
61 midi_tx_low_offset: FfLatterMidiTxLowOffset,
63 pub clk_src: FfUcxClkSrc,
65 pub opt_out_signal: OpticalOutputSignal,
67 pub word_out_single: bool,
69 pub effect_on_inputs: bool,
71 pub word_in_terminate: bool,
73 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#[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#[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#[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#[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 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#[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}