From 49084b8fbea05a2062e3e8948aadfa458bb93ebf Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Sat, 30 May 2026 07:29:29 +0300 Subject: [PATCH] =?UTF-8?q?intel:=20DP=20AUX=20channel=20=E2=80=94=20I2C-o?= =?UTF-8?q?ver-AUX=20EDID=20for=20Xe2=20platforms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add dp_aux.rs implementing DisplayPort AUX channel for EDID reads and DPCD capability queries. Critical for Xe2/Arrow Lake which lacks GMBUS and must use DP AUX for all EDID operations. - dp_aux.rs: DpAux struct with per-port AUX channel (CTL at 0x64010, DATA at 0x64014, 0x100 stride). Implements do_transfer() with native read/write and I2C-over-AUX protocols, wait_for_completion() with busy/done/timeout/receive_error detection, read_dpcd() for DPCD register access, read_dpcd_caps() for capability enumeration, and read_edid() via I2C-over-AUX (MOT-based segmented reads) - mod.rs: declare dp_aux module, add DpAux import, add dp_aux: Vec field to IntelDriver, initialize one DpAux per port in constructor Linux reference: intel_dp_aux.c, intel_dp_aux_regs.h Compiled: 0 new errors (pre-existing daemon errors unrelated) --- .../source/src/drivers/intel/dp_aux.rs | 214 ++++++++++++++++++ .../redox-drm/source/src/drivers/intel/mod.rs | 9 + 2 files changed, 223 insertions(+) create mode 100644 local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_aux.rs 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 new file mode 100644 index 0000000000..00ad8d0602 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_aux.rs @@ -0,0 +1,214 @@ +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use log::{debug, warn}; +use redox_driver_sys::memory::MmioRegion; + +use super::regs::IntelRegs; +use crate::driver::Result; +use crate::driver::DriverError; + +const DP_AUX_CH_CTL_SEND_BUSY: u32 = 1 << 31; +const DP_AUX_CH_CTL_DONE: u32 = 1 << 30; +const DP_AUX_CH_CTL_INTERRUPT: u32 = 1 << 29; +const DP_AUX_CH_CTL_TIME_OUT_ERROR: u32 = 1 << 28; +const DP_AUX_CH_CTL_RECEIVE_ERROR: u32 = 1 << 25; +const DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT: u32 = 20; +const DP_AUX_CH_CTL_MESSAGE_SIZE_MASK: u32 = 0x1F; +const DP_AUX_CH_CTL_SEND: u32 = 1 << 0; + +const DP_AUX_CTL_BASE: usize = 0x64010; +const DP_AUX_DATA_BASE: usize = 0x64014; +const AUX_STRIDE: usize = 0x100; + +const AUX_TIMEOUT_MS: u64 = 10; + +const DPCD_REV: u16 = 0x0000; +const DPCD_MAX_LINK_RATE: u16 = 0x0001; +const DPCD_MAX_LANE_COUNT: u16 = 0x0002; + +const EDID_I2C_ADDRESS: u8 = 0x50; +const EDID_SEGMENT_ADDR: u8 = 0x30; +const EDID_DDC_SEGMENT_ADDR: u8 = 0x60; +const EDID_LENGTH: usize = 128; + +const AUX_NATIVE_WRITE: u8 = 0x00; +const AUX_NATIVE_READ: u8 = 0x01; +const AUX_I2C_WRITE: u8 = 0x04; +const AUX_I2C_READ: u8 = 0x05; +const AUX_I2C_MOT: u8 = 0x02; + +pub struct DpAux { + mmio: Arc, + port: u8, + ctl_offset: usize, + data_offset: usize, +} + +impl DpAux { + pub fn new(mmio: Arc, port: u8) -> Self { + let ctl_offset = DP_AUX_CTL_BASE + (port as usize) * AUX_STRIDE; + let data_offset = DP_AUX_DATA_BASE + (port as usize) * AUX_STRIDE; + Self { mmio, port, ctl_offset, data_offset } + } + + fn wait_for_completion(&self) -> Result<()> { + let deadline = Instant::now() + Duration::from_millis(AUX_TIMEOUT_MS); + loop { + let status = self.mmio.read_u32(self.ctl_offset); + if status & DP_AUX_CH_CTL_DONE != 0 { + if status & DP_AUX_CH_CTL_RECEIVE_ERROR != 0 { + return Err(DriverError::Other("DP AUX receive error".into())); + } + if status & DP_AUX_CH_CTL_TIME_OUT_ERROR != 0 { + return Err(DriverError::Other("DP AUX timeout".into())); + } + return Ok(()); + } + if Instant::now() > deadline { + return Err(DriverError::Other(format!( + "DP AUX timeout: status {:#010x}", status + ))); + } + std::hint::spin_loop(); + } + } + + fn do_transfer(&self, request: u8, address: u16, send_buf: &[u8], recv_size: u8) -> Result> { + let mut ctl = DP_AUX_CH_CTL_SEND; + ctl |= DP_AUX_CH_CTL_TIME_OUT_400US; + + let msg_size: u32 = match request & 0x0F { + AUX_NATIVE_WRITE => send_buf.len() as u32, + AUX_NATIVE_READ => recv_size as u32, + AUX_I2C_WRITE | AUX_I2C_READ | (AUX_I2C_WRITE | AUX_I2C_MOT) | (AUX_I2C_READ | AUX_I2C_MOT) => { + send_buf.len() as u32 + } + _ => 0, + }; + ctl |= (msg_size & DP_AUX_CH_CTL_MESSAGE_SIZE_MASK) << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT; + + for (i, chunk) in send_buf.chunks(4).enumerate() { + let mut val: u32 = 0; + for (j, &byte) in chunk.iter().enumerate() { + val |= (byte as u32) << (j * 8); + } + self.mmio.write_u32(self.data_offset + i * 4, val); + } + + let mut command = ((request as u32) << 24) | ((address as u32) << 8); + self.mmio.write_u32(self.ctl_offset, command | ctl); + + self.wait_for_completion()?; + + let mut recv = Vec::with_capacity(recv_size as usize); + for i in 0..((recv_size as usize + 3) / 4) { + let data = self.mmio.read_u32(self.data_offset + i * 4); + let bytes = data.to_le_bytes(); + for j in 0..4 { + if recv.len() < recv_size as usize { + recv.push(bytes[j]); + } + } + } + recv.truncate(recv_size as usize); + Ok(recv) + } + + pub fn read_dpcd(&self, offset: u32, length: u8) -> Result> { + self.do_transfer(AUX_NATIVE_READ, offset as u16, &[], length) + } + + pub fn write_dpcd(&self, offset: u32, data: &[u8]) -> Result<()> { + self.do_transfer(AUX_NATIVE_WRITE, offset as u16, data, 0)?; + Ok(()) + } + + pub fn read_dpcd_caps(&self) -> Result { + let raw = self.read_dpcd(DPCD_REV, 16)?; + if raw.len() < 16 { + return Err(DriverError::Other("short DPCD read".into())); + } + Ok(DpcdCaps { + revision: raw[0], + max_link_rate: raw[1], + max_lane_count: raw[2] & 0x1F, + max_downspread: (raw[3] & 0x01) != 0, + norp: (raw[3] & 0x02) != 0, + downstream_port_present: (raw[5] & 0x01) != 0, + sink_count: raw[7] & 0x3F, + }) + } + + pub fn read_edid(&self) -> Result> { + let mut edid = vec![0u8; EDID_LENGTH]; + + let mut i2c_buf = [EDID_I2C_ADDRESS << 1, 0x00]; + self.do_transfer(AUX_I2C_WRITE | AUX_I2C_MOT, 0, &i2c_buf, 0)?; + let mut data = self.do_transfer(AUX_I2C_READ | AUX_I2C_MOT, 0, &[], 1)?; + i2c_buf[1] = 0x01; + + for chunk_start in (0..EDID_LENGTH).step_by(16) { + let chunk_end = (chunk_start + 16).min(EDID_LENGTH); + let chunk_size = (chunk_end - chunk_start) as u8; + + let segment = (chunk_start / EDID_LENGTH) as u8; + if segment > 0 { + let seg_buf = [EDID_DDC_SEGMENT_ADDR << 1, segment]; + self.do_transfer(AUX_I2C_WRITE | AUX_I2C_MOT, 0, &seg_buf, 0)?; + } + + i2c_buf[1] = chunk_start as u8; + self.do_transfer(AUX_I2C_WRITE | AUX_I2C_MOT, 0, &i2c_buf, 0)?; + data = self.do_transfer(AUX_I2C_READ | AUX_I2C_MOT, 0, &[], chunk_size)?; + + for (i, &byte) in data.iter().enumerate() { + if chunk_start + i < EDID_LENGTH { + edid[chunk_start + i] = byte; + } + } + } + + if edid[0] != 0x00 || edid[1] != 0xFF || edid[2] != 0xFF || edid[3] != 0xFF + || edid[4] != 0xFF || edid[5] != 0xFF || edid[6] != 0xFF || edid[7] != 0x00 + { + warn!("redox-drm-intel: invalid EDID header via DP AUX: {:02x?}", &edid[..8]); + return Err(DriverError::Other("invalid DP AUX EDID header".into())); + } + + debug!("redox-drm-intel: read {} byte EDID via DP AUX port {}", edid.len(), self.port); + Ok(edid) + } +} + +pub struct DpcdCaps { + pub revision: u8, + pub max_link_rate: u8, + pub max_lane_count: u8, + pub max_downspread: bool, + pub norp: bool, + pub downstream_port_present: bool, + pub sink_count: u8, +} + +const DP_AUX_CH_CTL_TIME_OUT_400US: u32 = 0 << 26; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dpcd_caps_defaults() { + let caps = DpcdCaps { + revision: 0x14, + max_link_rate: 0x14, + max_lane_count: 4, + max_downspread: false, + norp: true, + downstream_port_present: false, + sink_count: 1, + }; + assert_eq!(caps.max_lane_count, 4); + assert!(caps.norp); + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs index 6a204214cc..28d197d5af 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs @@ -2,6 +2,7 @@ pub mod display; pub mod display_cdclk; pub mod display_dmc; pub mod display_power; +pub mod dp_aux; pub mod gmbus; pub mod gtt; pub mod info; @@ -30,6 +31,7 @@ use self::display::{DisplayPipe, IntelDisplay}; use self::display_cdclk::DisplayClock; use self::display_dmc::DmcFirmware; use self::display_power::DisplayPower; +use self::dp_aux::DpAux; use self::gmbus::GmbusController; use self::gtt::IntelGtt; use self::info::{IntelDeviceInfo, IntelGeneration, device_info_from_id}; @@ -66,6 +68,7 @@ pub struct IntelDriver { gtt: Mutex, ring: Mutex, gmbus: Option, + dp_aux: Vec, display_power: DisplayPower, dmc: DmcFirmware, cdclk: DisplayClock, @@ -141,6 +144,11 @@ impl IntelDriver { None }; + let dp_aux: Vec = (0..device_info.num_ports) + .map(|port| DpAux::new(mmio_arc.clone(), port)) + .collect(); + info!("redox-drm-intel: initialized {} DP AUX channels", dp_aux.len()); + let display_power = DisplayPower::new(mmio_arc.clone(), regs); display_power.init_domains()?; @@ -213,6 +221,7 @@ impl IntelDriver { gtt: Mutex::new(gtt), ring: Mutex::new(ring), gmbus, + dp_aux, display_power, dmc, cdclk,