use crate::error::ProtocolError;
use crate::register::{
CedCount, CedCounters, CedReading, CedWarning, ClearableUpdateFlags, Config0, FfeLevels,
FrlConfig, HdmiLanes, LtpReq, LtpRequests, RsCorrectionCount, ScramblerStatus,
SourceTestConfig, StatusFlags, TmdsConfig, UpdateFlags,
};
pub const SINK_VERSION: u8 = 0x01;
pub const SOURCE_VERSION: u8 = 0x02;
pub const UPDATE_0: u8 = 0x10;
pub const TMDS_CONFIG: u8 = 0x20;
pub const SCRAMBLER_STATUS: u8 = 0x21;
pub const CONFIG_0: u8 = 0x30;
pub const CONFIG_1: u8 = 0x31;
pub const SOURCE_TEST_CONFIG: u8 = 0x35;
pub const STATUS_FLAGS_0: u8 = 0x40;
pub const STATUS_FLAGS_1: u8 = 0x41;
pub const STATUS_FLAGS_2: u8 = 0x42;
pub const ERR_DET_0_L: u8 = 0x50;
pub const ERR_DET_0_H: u8 = 0x51;
pub const ERR_DET_1_L: u8 = 0x52;
pub const ERR_DET_1_H: u8 = 0x53;
pub const ERR_DET_2_L: u8 = 0x54;
pub const ERR_DET_2_H: u8 = 0x55;
pub const ERR_DET_CHECKSUM: u8 = 0x56;
pub const ERR_DET_3_L: u8 = 0x57;
pub const ERR_DET_3_H: u8 = 0x58;
pub const RS_CORRECTION_L: u8 = 0x59;
pub const RS_CORRECTION_H: u8 = 0x5A;
pub const CED_BLOCK_START: u8 = ERR_DET_0_L;
pub const CED_BLOCK_LEN: usize = 9;
pub fn encode_tmds_config(config: TmdsConfig) -> u8 {
(config.scrambling_enable as u8) | ((config.high_tmds_clock_ratio as u8) << 1)
}
pub fn decode_scrambler_status(byte: u8) -> ScramblerStatus {
ScramblerStatus {
scrambling_active: byte & 0x01 != 0,
}
}
pub fn decode_update_flags(update_0: u8) -> UpdateFlags {
UpdateFlags {
status_update: update_0 & 0x01 != 0,
ced_update: update_0 & 0x02 != 0,
rr_test: update_0 & 0x04 != 0,
source_test_update: update_0 & 0x08 != 0,
frl_start: update_0 & 0x10 != 0,
flt_update: update_0 & 0x20 != 0,
rsed_update: update_0 & 0x40 != 0,
}
}
pub fn encode_clear_update_flags(flags: ClearableUpdateFlags) -> u8 {
(flags.status_update as u8)
| ((flags.ced_update as u8) << 1)
| ((flags.source_test_update as u8) << 3)
| ((flags.frl_start as u8) << 4)
| ((flags.flt_update as u8) << 5)
| ((flags.rsed_update as u8) << 6)
}
pub fn encode_config_0(config: Config0) -> u8 {
(config.rr_enable as u8) | ((config.flt_no_retrain as u8) << 1)
}
pub fn encode_config_1(config: FrlConfig) -> Result<u8, ProtocolError> {
if config.ffe_levels.value() > FfeLevels::max_for(config.frl_rate).value() {
return Err(ProtocolError::FfeLevelsOutOfRange {
rate: config.frl_rate,
levels: config.ffe_levels.value(),
});
}
Ok((config.frl_rate as u8) | (config.ffe_levels.value() << 4))
}
pub fn decode_source_test_config(byte: u8) -> SourceTestConfig {
SourceTestConfig {
txffe_pre_shoot_only: byte & 0x02 != 0,
txffe_de_emphasis_only: byte & 0x04 != 0,
txffe_no_ffe: byte & 0x08 != 0,
flt_no_timeout: byte & 0x20 != 0,
dsc_frl_max: byte & 0x40 != 0,
frl_max: byte & 0x80 != 0,
}
}
pub fn decode_status_flags(status_flags_0: u8) -> StatusFlags {
StatusFlags {
clock_detected: status_flags_0 & 0x01 != 0,
ch0_locked: status_flags_0 & 0x02 != 0,
ch1_locked: status_flags_0 & 0x04 != 0,
ch2_locked: status_flags_0 & 0x08 != 0,
ln3_locked: status_flags_0 & 0x10 != 0,
flt_ready: status_flags_0 & 0x40 != 0,
dsc_decode_fail: status_flags_0 & 0x80 != 0,
}
}
pub fn decode_ltp_requests(status_flags_1: u8, status_flags_2: u8) -> LtpRequests {
LtpRequests {
lane0: LtpReq::from_nibble(status_flags_1),
lane1: LtpReq::from_nibble(status_flags_1 >> 4),
lane2: LtpReq::from_nibble(status_flags_2),
lane3: LtpReq::from_nibble(status_flags_2 >> 4),
}
}
pub fn decode_ced_block(block: &[u8; CED_BLOCK_LEN], lanes: HdmiLanes) -> CedReading {
let [l0, h0, l1, h1, l2, h2, checksum, l3, h3] = *block;
let decode = |lo: u8, hi: u8| -> Option<CedCount> {
(hi & 0x80 != 0).then(|| CedCount::new(((hi as u16) << 8) | lo as u16))
};
let covered = match lanes {
HdmiLanes::Three => &block[..7],
HdmiLanes::Four => &block[..],
};
let sum = covered
.iter()
.fold(0u8, |sum, &byte| sum.wrapping_add(byte));
let mut reading = CedReading::new(CedCounters {
lane0: decode(l0, h0),
lane1: decode(l1, h1),
lane2: decode(l2, h2),
lane3: match lanes {
HdmiLanes::Three => None,
HdmiLanes::Four => decode(l3, h3),
},
});
if sum != 0 {
reading.push(CedWarning::ChecksumMismatch {
expected: sum.wrapping_sub(checksum).wrapping_neg(),
found: checksum,
});
}
if lanes == HdmiLanes::Three {
if let Some(count) = decode(l3, h3) {
reading.push(CedWarning::UnusedLaneReported { count });
}
}
reading
}
pub fn decode_rs_correction(low: u8, high: u8) -> Option<RsCorrectionCount> {
(high & 0x80 != 0).then(|| RsCorrectionCount::new(((high as u16) << 8) | low as u16))
}
#[cfg(test)]
mod tests {
use super::*;
fn block(counts: [(u8, u8); 4], lanes: HdmiLanes) -> [u8; CED_BLOCK_LEN] {
let [(l0, h0), (l1, h1), (l2, h2), (l3, h3)] = counts;
let mut block = [l0, h0, l1, h1, l2, h2, 0, l3, h3];
let covered = match lanes {
HdmiLanes::Three => &block[..7],
HdmiLanes::Four => &block[..],
};
let sum = covered
.iter()
.fold(0u8, |sum, &byte| sum.wrapping_add(byte));
block[6] = sum.wrapping_neg();
block
}
fn count(value: u16) -> Option<CedCount> {
Some(CedCount::new(value))
}
const VALID_LANES: [(u8, u8); 4] = [(0x02, 0x83), (0x04, 0x85), (0x06, 0x87), (0x08, 0x89)];
#[test]
fn three_lanes_with_a_valid_checksum() {
let lanes = [(0x02, 0x83), (0x04, 0x85), (0x06, 0x87), (0x00, 0x00)];
let reading = decode_ced_block(&block(lanes, HdmiLanes::Three), HdmiLanes::Three);
assert_eq!(
reading.counters,
CedCounters::new(count(0x0302), count(0x0504), count(0x0706), None)
);
assert_eq!(reading.warnings(), []);
}
#[test]
fn four_lanes_with_a_valid_checksum() {
let reading = decode_ced_block(&block(VALID_LANES, HdmiLanes::Four), HdmiLanes::Four);
assert_eq!(
reading.counters,
CedCounters::new(count(0x0302), count(0x0504), count(0x0706), count(0x0908))
);
assert_eq!(reading.warnings(), []);
}
#[test]
fn a_clear_validity_bit_is_none() {
let lanes = [(0xFF, 0x7F), (0x04, 0x85), (0x00, 0x00), (0x01, 0x00)];
let reading = decode_ced_block(&block(lanes, HdmiLanes::Four), HdmiLanes::Four);
assert_eq!(
reading.counters,
CedCounters::new(None, count(0x0504), None, None)
);
assert_eq!(reading.warnings(), []);
}
#[test]
fn a_torn_read_is_a_checksum_mismatch_with_the_counts_still_decoded() {
let lanes = [(0xFF, 0x80), (0x00, 0x00), (0x00, 0x00), (0x00, 0x00)];
let mut torn = block(lanes, HdmiLanes::Three);
torn[1] = 0x81;
let reading = decode_ced_block(&torn, HdmiLanes::Three);
assert_eq!(reading.counters.lane0, count(0x01FF));
assert_eq!(
reading.warnings(),
[CedWarning::ChecksumMismatch {
expected: torn[6].wrapping_sub(1),
found: torn[6],
}]
);
}
#[test]
fn expected_is_the_checksum_that_makes_the_block_valid() {
let valid = block(VALID_LANES, HdmiLanes::Four);
let mut wrong = valid;
wrong[6] = valid[6].wrapping_add(0x10);
assert_eq!(
decode_ced_block(&wrong, HdmiLanes::Four).warnings(),
[CedWarning::ChecksumMismatch {
expected: valid[6],
found: wrong[6],
}]
);
}
#[test]
fn the_lane_count_decides_what_the_checksum_covers() {
let lanes = [(0x02, 0x83), (0x04, 0x85), (0x06, 0x87), (0x08, 0x09)];
let three = block(lanes, HdmiLanes::Three);
assert_eq!(decode_ced_block(&three, HdmiLanes::Three).warnings(), []);
assert!(matches!(
decode_ced_block(&three, HdmiLanes::Four).warnings(),
[CedWarning::ChecksumMismatch { .. }]
));
let four = block(lanes, HdmiLanes::Four);
assert!(matches!(
decode_ced_block(&four, HdmiLanes::Three).warnings(),
[CedWarning::ChecksumMismatch { .. }]
));
}
#[test]
fn lane_3_reported_in_3_lane_mode_is_a_warning_not_a_count() {
let reading = decode_ced_block(&block(VALID_LANES, HdmiLanes::Three), HdmiLanes::Three);
assert_eq!(reading.counters.lane3, None);
assert_eq!(
reading.warnings(),
[CedWarning::UnusedLaneReported {
count: CedCount::new(0x0908),
}]
);
}
#[test]
fn the_warnings_follow_the_checksum_byte_and_lane_3_exhaustively() {
for lanes in [HdmiLanes::Three, HdmiLanes::Four] {
for h3 in [0x00, 0x89] {
for checksum in 0..=u8::MAX {
let mut bytes = block(
[(0x02, 0x83), (0x04, 0x85), (0x06, 0x87), (0x08, h3)],
lanes,
);
bytes[6] = checksum;
let covered = match lanes {
HdmiLanes::Three => &bytes[..7],
HdmiLanes::Four => &bytes[..],
};
let sum = covered.iter().fold(0u8, |sum, &b| sum.wrapping_add(b));
let mismatch = (sum != 0).then_some(CedWarning::ChecksumMismatch {
expected: checksum.wrapping_sub(sum),
found: checksum,
});
let unused = (lanes == HdmiLanes::Three && h3 & 0x80 != 0).then_some(
CedWarning::UnusedLaneReported {
count: CedCount::new(u16::from(h3) << 8 | 0x08),
},
);
let reading = decode_ced_block(&bytes, lanes);
assert!(
reading
.iter_warnings()
.copied()
.eq([mismatch, unused].into_iter().flatten()),
"{checksum:#04x}, {lanes:?}, {h3:#04x}"
);
}
}
}
}
#[test]
fn both_warnings_at_once_in_the_order_found() {
let mut wrong = block(VALID_LANES, HdmiLanes::Three);
wrong[6] = wrong[6].wrapping_add(1);
let reading = decode_ced_block(&wrong, HdmiLanes::Three);
assert!(matches!(
reading.warnings(),
[
CedWarning::ChecksumMismatch { .. },
CedWarning::UnusedLaneReported { .. },
]
));
assert!(reading.iter_warnings().eq(reading.warnings()));
}
#[test]
fn readings_compare_and_print_only_their_warnings() {
let clean = decode_ced_block(&block(VALID_LANES, HdmiLanes::Four), HdmiLanes::Four);
let again = decode_ced_block(&block(VALID_LANES, HdmiLanes::Four), HdmiLanes::Four);
let warned = decode_ced_block(&block(VALID_LANES, HdmiLanes::Three), HdmiLanes::Three);
assert_eq!(clean, again);
assert_ne!(clean.clone(), warned);
let mut printed = DebugBuf::default();
core::fmt::write(&mut printed, format_args!("{clean:?}")).unwrap();
assert!(printed.as_str().ends_with("warnings: [] }"));
}
struct DebugBuf {
bytes: [u8; 256],
len: usize,
}
impl Default for DebugBuf {
fn default() -> Self {
Self {
bytes: [0; 256],
len: 0,
}
}
}
impl DebugBuf {
fn as_str(&self) -> &str {
core::str::from_utf8(&self.bytes[..self.len]).unwrap()
}
}
impl core::fmt::Write for DebugBuf {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let end = self.len + s.len();
let slot = self.bytes.get_mut(self.len..end).ok_or(core::fmt::Error)?;
slot.copy_from_slice(s.as_bytes());
self.len = end;
Ok(())
}
}
}