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:
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user