intel: Phase 1 — DP/HDMI protocol completeness
- dp_aux: add LinkStatus check (DPCD 0x202-0x207), sink_count read (0x200) - hdmi: expand compute_cea_vic to 27 CEA modes, add VSIF (HDMI 1.4+), add is_hdmi_sink() EDID CEA-861 extension block detection - vbt: support modern 38-byte child device config format (BDB block 33/34), parse_child_device_table handles both legacy 2-byte and v2 entries
This commit is contained in:
@@ -207,6 +207,101 @@ impl DpAux {
|
||||
debug!("redox-drm-intel: read {} byte EDID via DP AUX port {}", edid.len(), self.port);
|
||||
Ok(edid)
|
||||
}
|
||||
|
||||
pub fn check_link_status(&self) -> Result<LinkStatus> {
|
||||
let lane01_status = self.read_dpcd(0x0202, 4)?;
|
||||
let adjusted_req = self.read_dpcd(0x0206, 2)?;
|
||||
|
||||
if lane01_status.len() < 4 || adjusted_req.len() < 2 {
|
||||
return Err(DriverError::Initialization("short link status read".into()));
|
||||
}
|
||||
|
||||
Ok(LinkStatus {
|
||||
lane0_cr_done: (lane01_status[0] & 0x01) != 0,
|
||||
lane1_cr_done: (lane01_status[0] & 0x10) != 0,
|
||||
lane2_cr_done: (lane01_status[1] & 0x01) != 0,
|
||||
lane3_cr_done: (lane01_status[1] & 0x10) != 0,
|
||||
lane0_channel_eq_done: (lane01_status[0] & 0x02) != 0,
|
||||
lane1_channel_eq_done: (lane01_status[0] & 0x20) != 0,
|
||||
lane2_channel_eq_done: (lane01_status[1] & 0x02) != 0,
|
||||
lane3_channel_eq_done: (lane01_status[1] & 0x20) != 0,
|
||||
lane0_symbol_locked: (lane01_status[0] & 0x04) != 0,
|
||||
lane1_symbol_locked: (lane01_status[0] & 0x40) != 0,
|
||||
lane2_symbol_locked: (lane01_status[1] & 0x04) != 0,
|
||||
lane3_symbol_locked: (lane01_status[1] & 0x40) != 0,
|
||||
interlane_aligned: (lane01_status[2] & 0x01) != 0,
|
||||
adjusted_req_lane0: adjusted_req[0] & 0x0F,
|
||||
adjusted_req_lane1: (adjusted_req[0] >> 4) & 0x0F,
|
||||
adjusted_req_lane2: adjusted_req[1] & 0x0F,
|
||||
adjusted_req_lane3: (adjusted_req[1] >> 4) & 0x0F,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_sink_count(&self) -> Result<u8> {
|
||||
let data = self.read_dpcd(0x0200, 1)?;
|
||||
if data.is_empty() {
|
||||
return Err(DriverError::Initialization("empty sink count read".into()));
|
||||
}
|
||||
Ok(data[0] & 0x3F)
|
||||
}
|
||||
|
||||
pub fn sink_count_changed(&self, previous_count: u8) -> Result<bool> {
|
||||
let current = self.read_sink_count()?;
|
||||
if current != previous_count {
|
||||
debug!(
|
||||
"redox-drm-intel: DP sink count changed: {} → {}",
|
||||
previous_count, current
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LinkStatus {
|
||||
pub lane0_cr_done: bool,
|
||||
pub lane1_cr_done: bool,
|
||||
pub lane2_cr_done: bool,
|
||||
pub lane3_cr_done: bool,
|
||||
pub lane0_channel_eq_done: bool,
|
||||
pub lane1_channel_eq_done: bool,
|
||||
pub lane2_channel_eq_done: bool,
|
||||
pub lane3_channel_eq_done: bool,
|
||||
pub lane0_symbol_locked: bool,
|
||||
pub lane1_symbol_locked: bool,
|
||||
pub lane2_symbol_locked: bool,
|
||||
pub lane3_symbol_locked: bool,
|
||||
pub interlane_aligned: bool,
|
||||
pub adjusted_req_lane0: u8,
|
||||
pub adjusted_req_lane1: u8,
|
||||
pub adjusted_req_lane2: u8,
|
||||
pub adjusted_req_lane3: u8,
|
||||
}
|
||||
|
||||
impl LinkStatus {
|
||||
pub fn all_lanes_cr_done(&self, lane_count: u8) -> bool {
|
||||
match lane_count {
|
||||
1 => self.lane0_cr_done,
|
||||
2 => self.lane0_cr_done && self.lane1_cr_done,
|
||||
3 => self.lane0_cr_done && self.lane1_cr_done && self.lane2_cr_done,
|
||||
_ => self.lane0_cr_done && self.lane1_cr_done && self.lane2_cr_done && self.lane3_cr_done,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_lanes_eq_done(&self, lane_count: u8) -> bool {
|
||||
match lane_count {
|
||||
1 => self.lane0_channel_eq_done,
|
||||
2 => self.lane0_channel_eq_done && self.lane1_channel_eq_done,
|
||||
3 => self.lane0_channel_eq_done && self.lane1_channel_eq_done && self.lane2_channel_eq_done,
|
||||
_ => self.lane0_channel_eq_done && self.lane1_channel_eq_done
|
||||
&& self.lane2_channel_eq_done && self.lane3_channel_eq_done,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link_good(&self, lane_count: u8) -> bool {
|
||||
self.interlane_aligned && self.all_lanes_cr_done(lane_count) && self.all_lanes_eq_done(lane_count)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DpcdCaps {
|
||||
|
||||
@@ -30,6 +30,15 @@ const AUDIO_SAMPLE_RATE_48KHZ: u8 = 0b011;
|
||||
const AUDIO_CHANNEL_COUNT_2CH: u8 = 1;
|
||||
const AUDIO_SPEAKER_FL_FR: u8 = 0x01;
|
||||
|
||||
const HDMI_IEEE_OUI: [u8; 3] = [0x03, 0x0C, 0x00];
|
||||
const VSIF_HEADER: u8 = 0x81;
|
||||
const VSIF_VERSION: u8 = 0x01;
|
||||
const VSIF_LENGTH: u8 = 5;
|
||||
|
||||
const HDMI_VSIF_VIDEO_FORMAT_MASK: u8 = 0x07;
|
||||
const HDMI_VSIF_VIDEO_FORMAT_HDMI: u8 = 0x02;
|
||||
const HDMI_VSIF_VIDEO_FORMAT_DVI: u8 = 0x00;
|
||||
|
||||
pub struct HdmiInfoframes {
|
||||
mmio: Arc<MmioRegion>,
|
||||
}
|
||||
@@ -156,6 +165,53 @@ impl HdmiInfoframes {
|
||||
(256u32.wrapping_sub(sum % 256) % 256) as u8
|
||||
}
|
||||
|
||||
pub fn program_vsif(&self, pipe: u8, _port: u8) -> Result<()> {
|
||||
let dip_ctl = HSW_TVIDEO_DIP_CTL_BASE + (pipe as usize) * PIPE_STRIDE;
|
||||
let dip_data = HSW_TVIDEO_DIP_AVI_DATA_BASE + (pipe as usize) * PIPE_STRIDE;
|
||||
|
||||
let mut packet = [0u8; 9];
|
||||
packet[0] = VSIF_HEADER;
|
||||
packet[1] = VSIF_VERSION;
|
||||
packet[2] = VSIF_LENGTH;
|
||||
packet[3] = HDMI_IEEE_OUI[0];
|
||||
packet[4] = HDMI_IEEE_OUI[1];
|
||||
packet[5] = HDMI_IEEE_OUI[2];
|
||||
|
||||
let video_format = HDMI_VSIF_VIDEO_FORMAT_HDMI;
|
||||
let hdmi_vic = 0u8;
|
||||
packet[6] = video_format | (hdmi_vic << 5);
|
||||
|
||||
let _3d_structure = 0u8;
|
||||
let _3d_ext_data = 0u8;
|
||||
packet[7] = _3d_structure;
|
||||
packet[8] = _3d_ext_data;
|
||||
|
||||
let checksum = Self::checksum(&packet[..8]);
|
||||
packet[8] = checksum;
|
||||
|
||||
self.mmio.write32(dip_ctl, 0);
|
||||
|
||||
for i in 0..3 {
|
||||
let mut word: u32 = 0;
|
||||
for j in 0..4 {
|
||||
let idx = i * 4 + j;
|
||||
if idx < 9 {
|
||||
word |= (packet[idx] as u32) << (j * 8);
|
||||
}
|
||||
}
|
||||
self.mmio.write32(dip_data + i * 4, word);
|
||||
}
|
||||
|
||||
let ctl_val = VIDEO_DIP_ENABLE
|
||||
| VIDEO_DIP_PORT_SELECT_HDMI
|
||||
| (1u32 << 24)
|
||||
| VIDEO_DIP_FREQ_VSYNC;
|
||||
self.mmio.write32(dip_ctl, ctl_val);
|
||||
|
||||
debug!("redox-drm-intel: HDMI VSIF programmed for pipe {}", pipe);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable(&self, pipe: u8) -> Result<()> {
|
||||
let dip_ctl = HSW_TVIDEO_DIP_CTL_BASE + (pipe as usize) * PIPE_STRIDE;
|
||||
self.mmio.write32(dip_ctl, 0);
|
||||
@@ -164,16 +220,33 @@ impl HdmiInfoframes {
|
||||
}
|
||||
|
||||
fn compute_cea_vic(hdisplay: u16, vdisplay: u16, vrefresh: u32) -> u8 {
|
||||
match (hdisplay, vdisplay, vrefresh as u16) {
|
||||
match (hdisplay, vdisplay, vrefresh) {
|
||||
(640, 480, 60) => 1,
|
||||
(720, 480, 60) => 2,
|
||||
(720, 480, _) if vrefresh >= 59 && vrefresh <= 61 => 3,
|
||||
(1280, 720, 60) => 4,
|
||||
(1920, 1080, _) if vrefresh >= 50 && vrefresh <= 51 => 5,
|
||||
(720, 576, 50) => 17,
|
||||
(720, 576, _) if vrefresh >= 49 && vrefresh <= 51 => 18,
|
||||
(1280, 720, 50) => 19,
|
||||
(1920, 1080, 50) => 20,
|
||||
(1920, 1080, 60) => 16,
|
||||
(1920, 1080, 50) => 31,
|
||||
(1920, 1080, 24) => 32,
|
||||
(1920, 1080, 25) => 33,
|
||||
(1920, 1080, 30) => 34,
|
||||
(2880, 480, 60) => 35,
|
||||
(2880, 576, 50) => 37,
|
||||
(1920, 1080, _) if vrefresh >= 119 && vrefresh <= 121 => 63,
|
||||
(3840, 2160, 60) => 97,
|
||||
(3840, 2160, 50) => 96,
|
||||
(3840, 2160, 30) => 95,
|
||||
(3840, 2160, 25) => 94,
|
||||
(3840, 2160, 24) => 93,
|
||||
(4096, 2160, 60) => 102,
|
||||
(4096, 2160, 50) => 101,
|
||||
(4096, 2160, 30) => 100,
|
||||
(4096, 2160, 25) => 99,
|
||||
(4096, 2160, 24) => 98,
|
||||
(2560, 1440, _) => 0,
|
||||
_ => 0,
|
||||
}
|
||||
@@ -185,3 +258,44 @@ fn compute_aspect_ratio(mode: &ModeInfo) -> u8 {
|
||||
else if ratio > 1.6 { 2 }
|
||||
else { 1 }
|
||||
}
|
||||
|
||||
pub fn is_hdmi_sink(edid: &[u8]) -> bool {
|
||||
if edid.len() < 256 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let digital = edid.len() > 20 && (edid[20] & 0x80) != 0;
|
||||
if !digital {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut offset = 128;
|
||||
while offset + 3 <= edid.len() && offset < 256 {
|
||||
let tag = edid[offset] >> 5;
|
||||
let length = (edid[offset] & 0x1F) as usize;
|
||||
|
||||
if tag == 0x02 && length >= 3 && offset + 3 + length <= edid.len() {
|
||||
let data = &edid[offset + 1..offset + 1 + length];
|
||||
let mut dtd_start = 0usize;
|
||||
while dtd_start < data.len() {
|
||||
let dtd_tag = data[dtd_start] >> 5;
|
||||
let dtd_len = (data[dtd_start] & 0x1F) as usize;
|
||||
if dtd_len < 3 {
|
||||
dtd_start += dtd_len + 1;
|
||||
continue;
|
||||
}
|
||||
if dtd_start + 3 <= data.len()
|
||||
&& data[dtd_start + 1] == HDMI_IEEE_OUI[2]
|
||||
&& data[dtd_start + 2] == HDMI_IEEE_OUI[1]
|
||||
&& data[dtd_start + 3] == HDMI_IEEE_OUI[0]
|
||||
{
|
||||
return true;
|
||||
}
|
||||
dtd_start += dtd_len + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
offset += length + 1;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -7,6 +7,15 @@ const BDB_HEADER_SIZE: usize = 3;
|
||||
const BDB_GENERAL_DEFINITIONS: u8 = 2;
|
||||
const BDB_LVDS_OPTIONS: u8 = 40;
|
||||
const BDB_MIPI_SEQUENCE: u8 = 53;
|
||||
const BDB_CHILD_DEVICE_TABLE: u8 = 33;
|
||||
const BDB_CHILD_DEVICE_TABLE_OLD: u8 = 34;
|
||||
|
||||
const CHILD_DEVICE_LEGACY_SIZE: usize = 2;
|
||||
const CHILD_DEVICE_V2_MIN_SIZE: usize = 38;
|
||||
|
||||
const DEVICE_TYPE_HDMI: u16 = 0x2000;
|
||||
const DEVICE_TYPE_DP: u16 = 0x4000;
|
||||
const DEVICE_TYPE_EDP: u16 = 0x8000;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VbtInfo {
|
||||
@@ -98,15 +107,8 @@ impl VbtInfo {
|
||||
self.has_mipi = false;
|
||||
}
|
||||
}
|
||||
33 => {
|
||||
let count = block_size / 2;
|
||||
for i in 0..count {
|
||||
let device = u16::from_le_bytes([
|
||||
block_data[i * 2], block_data[i * 2 + 1],
|
||||
]);
|
||||
let config = self.parse_child_device(device);
|
||||
self.child_devices.push(config);
|
||||
}
|
||||
BDB_CHILD_DEVICE_TABLE | BDB_CHILD_DEVICE_TABLE_OLD => {
|
||||
self.parse_child_device_table(block_data);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -115,7 +117,71 @@ impl VbtInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_child_device(&self, device: u16) -> ChildDeviceConfig {
|
||||
fn parse_child_device_table(&mut self, data: &[u8]) {
|
||||
if data.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let entry_size = if data.len() % CHILD_DEVICE_V2_MIN_SIZE == 0 {
|
||||
CHILD_DEVICE_V2_MIN_SIZE
|
||||
} else if data.len() % CHILD_DEVICE_LEGACY_SIZE == 0 {
|
||||
CHILD_DEVICE_LEGACY_SIZE
|
||||
} else {
|
||||
CHILD_DEVICE_LEGACY_SIZE
|
||||
};
|
||||
|
||||
let count = data.len() / entry_size;
|
||||
debug!(
|
||||
"redox-drm-intel: VBT child device table: {} entries × {} bytes each",
|
||||
count, entry_size
|
||||
);
|
||||
|
||||
for i in 0..count {
|
||||
let offset = i * entry_size;
|
||||
if offset + entry_size > data.len() {
|
||||
break;
|
||||
}
|
||||
let entry = &data[offset..offset + entry_size];
|
||||
let config = if entry_size >= CHILD_DEVICE_V2_MIN_SIZE {
|
||||
self.parse_child_device_v2(entry)
|
||||
} else {
|
||||
let device = u16::from_le_bytes([entry[0], entry[1]]);
|
||||
self.parse_child_device_legacy(device)
|
||||
};
|
||||
self.child_devices.push(config);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_child_device_v2(&self, entry: &[u8]) -> ChildDeviceConfig {
|
||||
let device_type = u16::from_le_bytes([entry[2], entry[3]]);
|
||||
let dvo_port = entry.get(16).copied().unwrap_or(0);
|
||||
let ddc_pin = entry.get(17).copied().unwrap_or(0);
|
||||
let aux_channel_raw = entry.get(18).copied().unwrap_or(0);
|
||||
|
||||
let hdmi = (device_type & DEVICE_TYPE_HDMI) != 0
|
||||
|| entry.get(26).map_or(false, |&b| (b & 0x08) != 0);
|
||||
let dp = (device_type & DEVICE_TYPE_DP) != 0;
|
||||
let edp = (device_type & DEVICE_TYPE_EDP) != 0;
|
||||
|
||||
let aux_channel = if dp || edp {
|
||||
Some(if aux_channel_raw > 0 { aux_channel_raw } else { ddc_pin })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
ChildDeviceConfig {
|
||||
device_type,
|
||||
dvo_port,
|
||||
ddc_pin,
|
||||
aux_channel,
|
||||
hdmi_support: hdmi,
|
||||
dp_support: dp,
|
||||
edp_support: edp,
|
||||
device_id: device_type,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_child_device_legacy(&self, device: u16) -> ChildDeviceConfig {
|
||||
let dvo_port = ((device >> 0) & 0x0F) as u8;
|
||||
let ddc_pin = ((device >> 4) & 0x0F) as u8;
|
||||
let hdmi = (device >> 8) & 0x01 != 0;
|
||||
@@ -165,6 +231,7 @@ pub fn connector_type_from_vbt(vbt: Option<&VbtInfo>, port: u8, pp_status: u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connector_type_heuristic(port, pp_status)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user