diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_aux.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_aux.rs index a3c3a54eba..10809a164a 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_aux.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_aux.rs @@ -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 { + 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 { + 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 { + 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 { diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi.rs index a75a211103..edc4a620fd 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi.rs @@ -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, } @@ -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 +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/vbt.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/vbt.rs index 386e9323d9..b44f98660e 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/vbt.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/vbt.rs @@ -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) }