use bitvec::{
array::BitArray,
field::BitField,
order::{Lsb0, Msb0},
view::BitView,
};
use crate::prelude::internal::*;
#[tracing::instrument(skip_all)]
pub(super) fn parse(bytes: &[u8; 18]) -> Result<DetailedTimingDefinition, EdidError> {
if [bytes[0], bytes[1]] == [0x00, 0x00] {
tracing::error!("passed wrong 18 byte desc. please report this with logs.");
return Err(EdidError::DescriptorUnexpectedHeader(
bytes[0..5].try_into()?,
));
}
let pixel_clock_khz = pixel_clock(&[bytes[0], bytes[1]]);
let horizontal_addressable_video_px = upper_nibble(bytes[4], bytes[2]);
let horizontal_blanking_px = lower_nibble(bytes[4], bytes[3]);
let vertical_addressable_video_lines = upper_nibble(bytes[7], bytes[5]);
let vertical_blanking_lines = lower_nibble(bytes[7], bytes[6]);
let shared_bits = bytes[11].view_bits::<Lsb0>();
let horizontal_front_porch: u16 = {
let upper = shared_bits[6..=7].load::<u8>();
let lower = bytes[8];
bytemuck::must_cast([lower, upper])
}; let horizontal_sync_pulse_width_px: u16 = {
let upper = shared_bits[4..=5].load::<u8>();
let lower = bytes[9];
bytemuck::must_cast([lower, upper])
}; let vertical_front_porch_lines: u8 = {
let upper_half = bytes[10].view_bits::<Lsb0>();
let mut arr: BitArray<u8, Msb0> = BitArray::ZERO;
arr.set(0x05, upper_half[7]);
arr.set(0x04, upper_half[6]);
arr.set(0x03, upper_half[5]);
arr.set(0x02, upper_half[4]);
arr.set(0x01, shared_bits[2]);
arr.set(0x00, shared_bits[3]);
arr.load::<u8>()
}; let vertical_sync_pulse_width_lines: u8 = {
let lower_half = bytes[0x10].view_bits::<Lsb0>();
let mut arr: BitArray<u8, Msb0> = BitArray::ZERO;
arr.set(5, lower_half[3]);
arr.set(4, lower_half[2]);
arr.set(3, lower_half[1]);
arr.set(2, lower_half[0]);
arr.set(1, shared_bits[0]);
arr.set(0, shared_bits[1]);
arr.load::<u8>()
};
let horizontal_addressable_video_size_mm = {
let mm = upper_nibble(bytes[14], bytes[12]);
if mm == 0 {
None
} else {
Some(mm)
}
};
let vertical_addressable_video_size_mm = {
let mm = lower_nibble(bytes[14], bytes[13]);
if mm == 0 {
None
} else {
Some(mm)
}
};
let horizontal_border_px = bytes[15];
let vertical_border_lines = bytes[16];
let (signal_interface_type, stereo_support, sync_signal) = part_2(bytes[17]);
Ok(DetailedTimingDefinition {
pixel_clock_khz,
horizontal_addressable_video_px,
horizontal_blanking_px,
vertical_addressable_video_lines,
vertical_blanking_lines,
horizontal_front_porch,
horizontal_sync_pulse_width_px,
vertical_front_porch_lines,
vertical_sync_pulse_width_lines,
horizontal_addressable_video_size_mm,
vertical_addressable_video_size_mm,
horizontal_border_px,
vertical_border_lines,
signal_interface_type,
stereo_support,
sync_signal,
})
}
#[tracing::instrument]
fn pixel_clock(bytes: &[u8; 2]) -> u16 {
bytes.view_bits::<Msb0>().load::<u16>()
}
#[tracing::instrument]
fn upper_nibble(shared_byte: u8, byte: u8) -> u16 {
let upper = shared_byte.view_bits::<Lsb0>()[0x04..=0x07].load::<u8>();
let combined: u16 = bytemuck::must_cast([byte, upper]);
combined
}
#[tracing::instrument]
fn lower_nibble(shared_byte: u8, byte: u8) -> u16 {
let upper = shared_byte.view_bits::<Lsb0>()[0x00..=0x03].load::<u8>();
let combined: u16 = bytemuck::must_cast([byte, upper]);
combined
}
#[tracing::instrument]
fn part_2(byte: u8) -> (SignalInterfaceType, StereoViewingSupport, SyncSignal) {
let bits = byte.view_bits::<Lsb0>();
let sit = if bits[7] {
SignalInterfaceType::Interlaced
} else {
SignalInterfaceType::NonInterlaced
};
let stereo_viewing = match (bits[6], bits[5], bits[0]) {
(false, false, _) => StereoViewingSupport::NormalDisplay,
(false, true, false) => StereoViewingSupport::FieldSequentialRight,
(true, false, false) => StereoViewingSupport::FieldSequentialLeft,
(false, true, true) => StereoViewingSupport::TwoWayInterleavedRight,
(true, false, true) => StereoViewingSupport::TwoWayInterleavedLeft,
(true, true, false) => StereoViewingSupport::FourWayInterleaved,
(true, true, true) => StereoViewingSupport::SideBySide,
};
let sync_signal = if bits[4] {
SyncSignal::Digital(match (bits[3], bits[2], bits[1]) {
(false, false, _) => DigitalSyncSignal::Composite,
(false, true, _) => DigitalSyncSignal::CompositeSerrations,
(true, false, false) => DigitalSyncSignal::SeparateNegVNegH,
(true, false, true) => DigitalSyncSignal::SeparateNegVPosH,
(true, true, false) => DigitalSyncSignal::SeparatePosVNegH,
(true, true, true) => DigitalSyncSignal::SeparatePosVPosH,
})
} else {
let bipolar = bits[3];
let with_serrations = bits[2];
let sync_mode = if bits[1] {
AnalogSyncOn::Rgb
} else {
AnalogSyncOn::Green
};
SyncSignal::Analog {
bipolar,
with_serrations,
sync_mode,
}
};
(sit, stereo_viewing, sync_signal)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_pixel_clock_ordering() {
let one = [0x01, 0x00];
assert_eq!(pixel_clock(&one), 1_u16);
let max_minus_one = [0xFE, 0xFF];
assert_eq!(pixel_clock(&max_minus_one), 0b1111_1111_1111_1110);
}
#[test]
fn check_nibble_ordering() {
logger();
let shared = 0b1111_0000;
let byte = 0b0000_0001;
assert_eq!(upper_nibble(shared, byte), 0b0000_1111_0000_0001);
assert_eq!(lower_nibble(shared, byte), 0b0000_0000_0000_0001);
let shared = 0b1111_1111;
assert_eq!(upper_nibble(shared, byte), 0b0000_1111_0000_0001);
assert_eq!(lower_nibble(shared, byte), 0b0000_1111_0000_0001);
let shared = 0b1100_0001;
let byte = 0b1111_0000;
assert_eq!(upper_nibble(shared, byte), 0b0000_1100_1111_0000);
assert_eq!(lower_nibble(shared, byte), 0b0000_0001_1111_0000);
}
fn _sam02e3_2c47316eff13_preferred_tm() {
logger();
let path = "linuxhw_edid_EDID_Digital_Samsung_SAM02E3_2C47316EFF13.input";
let input = edid_by_filename(path);
let bytes: [u8; 18] = input[0x36..=0x47].try_into().unwrap();
let got = super::parse(&bytes).unwrap();
let expected = DetailedTimingDefinition {
pixel_clock_khz: 10650_u16,
horizontal_addressable_video_px: 1440,
horizontal_blanking_px: 80 + 152 + 232, vertical_addressable_video_lines: 900_u16,
vertical_blanking_lines: 3 + 6 + 25,
horizontal_front_porch: 80,
horizontal_sync_pulse_width_px: 152,
vertical_front_porch_lines: 3,
vertical_sync_pulse_width_lines: 6,
horizontal_addressable_video_size_mm: Some(367),
vertical_addressable_video_size_mm: Some(229),
horizontal_border_px: 0, vertical_border_lines: 0,
signal_interface_type: SignalInterfaceType::NonInterlaced,
stereo_support: StereoViewingSupport::NormalDisplay,
sync_signal: SyncSignal::Digital(DigitalSyncSignal::SeparatePosVNegH),
};
assert_eq!(got, expected);
}
}