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).
This commit is contained in:
2026-07-09 14:50:42 +03:00
parent fb59077312
commit 945b87b7c1
@@ -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<u8> {