From 945b87b7c14e99425edf2be677ba931fad50cc07 Mon Sep 17 00:00:00 2001 From: vasilito Date: Thu, 9 Jul 2026 14:50:42 +0300 Subject: [PATCH] redox-drm Intel: implement EDID I2C/DDC reading via GMBUS Replaced the 'EDID I2C/DDC not yet implemented' stub with a real Intel GMBUS controller implementation for reading EDID blocks from connected displays. Added GMBUS register definitions (Intel PRM Display chapter): - GMBUS0 (0xC5100): pin pair select (DDC=3), rate (100kHz) - GMBUS1 (0xC5104): SW_RDY, CYCLE_WAIT, CYCLE_INDEX, size, addr - GMBUS2 (0xC5108): HW_RDY, ACTIVE, INUSE status bits - GMBUS3 (0xC510C): 32-bit data register - GMBUS4 (0xC5110): interrupt mask Implemented gmbus_read() helper: 1. Selects DDC pin pair at 100kHz 2. Programs GMBUS1 with SW_RDY | CYCLE_WAIT | CYCLE_INDEX, transfer size, EDID block offset index, and I2C slave address 3. Polls GMBUS2 for HW_RDY (100k iteration timeout) 4. Reads data words from GMBUS3 into output buffer 5. Stops cycle via GMBUS1 SW_RDY | CYCLE_STOP read_edid_block() delegates to gmbus_read() with slave=0x50 (standard EDID address) and offset=block*128. Previously: synthetic 1024x768 EDID fallback on all connectors. Now: attempts real EDID read via GMBUS, falls back to synthetic if GMBUS read fails (display disconnected or I2C NAK). --- .../source/src/drivers/intel/display.rs | 83 ++++++++++++++++++- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/display.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display.rs index aaed19547a..dfc0f4cc72 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/display.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display.rs @@ -32,6 +32,35 @@ const PIPECONF_ENABLE: u32 = 1 << 31; const DSPCNTR_ENABLE: u32 = 1 << 31; const DDI_BUF_CTL_ENABLE: u32 = 1 << 31; +// GMBUS registers for I2C/DDC EDID reading (Intel PRM Display chapter). +const GMBUS0: usize = 0xC5100; +const GMBUS1: usize = 0xC5104; +const GMBUS2: usize = 0xC5108; +const GMBUS3: usize = 0xC510C; +const GMBUS4: usize = 0xC5110; + +const GMBUS0_PIN_PAIR_MASK: u32 = 0x7; +const GMBUS0_RATE_100K: u32 = 0 << 8; + +const GMBUS1_SW_RDY: u32 = 1 << 31; +const GMBUS1_CYCLE_STOP: u32 = 1 << 27; +const GMBUS1_CYCLE_INDEX: u32 = 1 << 26; +const GMBUS1_CYCLE_WAIT: u32 = 1 << 25; +const GMBUS1_SIZE_SHIFT: u32 = 16; +const GMBUS1_INDEX_SHIFT: u32 = 8; +const GMBUS1_SLAVE_ADDR_SHIFT: u32 = 1; + +const GMBUS2_HW_RDY: u32 = 1 << 11; +const GMBUS2_ACTIVE: u32 = 1 << 9; +const GMBUS2_INUSE: u32 = 1 << 15; + +/// Pin pair 3 for DDC (Display Data Channel) on Intel GPUs. +const GMBUS_PIN_DDC: u32 = 3; +/// EDID I2C slave address (7-bit). +const EDID_SLAVE_ADDR: u8 = 0x50; +/// EDID block size in bytes. +const EDID_BLOCK_SIZE: usize = 128; + #[derive(Clone, Copy, Debug)] pub struct DisplayPipe { pub index: u8, @@ -160,10 +189,56 @@ impl IntelDisplay { synthetic_edid() } - fn read_edid_block(&self, _port: u8, _block: u8, _buf: &mut [u8]) -> Result<()> { - Err(DriverError::Initialization( - "EDID I2C/DDC not yet implemented".into(), - )) + fn read_edid_block(&self, _port: u8, block: u8, buf: &mut [u8]) -> Result<()> { + if buf.len() < EDID_BLOCK_SIZE { + return Err(DriverError::Initialization( + "EDID buffer too small".into(), + )); + } + self.gmbus_read(EDID_SLAVE_ADDR, block as u16 * EDID_BLOCK_SIZE as u16, &mut buf[..EDID_BLOCK_SIZE]) + } + + /// Read data from an I2C device via the Intel GMBUS controller. + /// `slave_addr` is a 7-bit I2C address, `index` is the register + /// offset within the device (e.g., EDID block offset). + fn gmbus_read(&self, slave_addr: u8, index: u16, buf: &mut [u8]) -> Result<()> { + let len = buf.len(); + if len == 0 || len > 256 { + return Err(DriverError::Initialization( + "GMBUS read size out of range (1-256)".into(), + )); + } + // Select DDC pin pair, 100 kHz + self.write32(GMBUS0, GMBUS_PIN_DDC | GMBUS0_RATE_100K)?; + // Program GMBUS1: software ready + wait + size + index + slave addr + let cmd = GMBUS1_SW_RDY + | GMBUS1_CYCLE_WAIT + | GMBUS1_CYCLE_INDEX + | ((len as u32 - 1) << GMBUS1_SIZE_SHIFT) + | ((index as u32) << GMBUS1_INDEX_SHIFT) + | ((slave_addr as u32) << GMBUS1_SLAVE_ADDR_SHIFT); + self.write32(GMBUS1, cmd)?; + // Wait for hardware ready + let mut timeout = 100_000; + loop { + let status = self.read32(GMBUS2)?; + if status & GMBUS2_HW_RDY != 0 { + break; + } + timeout = timeout.checked_sub(1).ok_or_else(|| { + DriverError::Initialization("GMBUS HW_RDY timeout".into()) + })?; + } + // Read data words from GMBUS3 + for chunk in buf.chunks_mut(4) { + let val = self.read32(GMBUS3)?; + let bytes = val.to_ne_bytes(); + let copy_len = chunk.len().min(4); + chunk.copy_from_slice(&bytes[..copy_len]); + } + // Stop the cycle + self.write32(GMBUS1, GMBUS1_SW_RDY | GMBUS1_CYCLE_STOP)?; + Ok(()) } pub fn read_dpcd(&self, port: u8) -> Vec {