intel: Phase 0 — display foundation for Gen9

Add register abstraction and hardware initialization modules
for Intel GPU display support (Skylake/Kaby Lake/Coffee Lake).

New modules:
- info.rs: device capability table with 44 device IDs from Gen9—Gen12.7
  IntelDeviceInfo struct with generation, display version, pipe/port
  counts, DMC firmware key lookup
- regs.rs: IntelRegs trait — per-generation register access abstraction
  covering forcewake, power wells, CDCLK, DMC, GMBUS, pipes, planes,
  DDIs, cursors, vblank, and GFX flush
- regs_gen9.rs: Gen9 (SKL/KBL/CFL) register constants implementing
  IntelRegs trait — verified against Linux i915 intel_display_regs.h
  and intel_gmbus_regs.h
- gmbus.rs: GMBUS I2C controller for real EDID reads via hardware
  Ported from Linux intel_gmbus.c — implements init, read, read_edid
  with pin pair selection, hardware ready polling, SDAST FIFO reads,
  NAK detection, and 50ms timeout
- display_power.rs: Gen9 display power well initialization
  Enables all display-required power domains (PW1, PW2, DDI_A-E,
  AUX_A-D) via POWER_WELL_CTL register with status polling
- display_dmc.rs: DMC firmware upload to hardware
  Parses CSS header, uploads payload to MMIO SRAM, enables DMC_CTRL,
  polls DMC_STATUS for load confirmation
- display_cdclk.rs: core display clock programming for Gen9
  Supports 337.5/450/540/675 MHz frequencies, required CDCLK
  calculation from mode pixel clocks

Modified:
- mod.rs: declare new modules, add IntelDeviceInfo, Gen9Regs,
  GmbusController, DisplayPower, DmcFirmware, DisplayClock to
  IntelDriver struct. Constructor initializes all modules in order:
  forcewake → power wells → DMC firmware → CDCLK → GMBUS → display

Linux reference: local/reference/linux-7.1/drivers/gpu/drm/i915/display/

Next: regs_xe2.rs for Xe2/Lunar Lake/Battlemage (display ver 20+)
This commit is contained in:
2026-05-30 06:54:38 +03:00
parent 10aa80bb0b
commit fd773c46d9
8 changed files with 888 additions and 2 deletions
@@ -0,0 +1,115 @@
use std::sync::Arc;
use log::{info, warn};
use redox_driver_sys::memory::MmioRegion;
use super::regs::IntelRegs;
use crate::driver::Result;
use crate::driver::DriverError;
use crate::kms::ModeInfo;
const SKL_CDCLK_337_5: u32 = 0;
const SKL_CDCLK_450: u32 = 1;
const SKL_CDCLK_540: u32 = 2;
const SKL_CDCLK_675: u32 = 3;
const CDCLK_DECIMAL_MASK: u32 = 0x1FF;
const CDCLK_FREQ_SELECT_MASK: u32 = 0x3;
const CDCLK_FREQ_SELECT_SHIFT: u32 = 26;
const CDCLK_CD2X_DIV_MASK: u32 = 0x3;
const CDCLK_CD2X_DIV_SHIFT: u32 = 13;
const CDCLK_FREQ_DECIMAL_337_5: u32 = 0b0101010;
const CDCLK_FREQ_DECIMAL_450: u32 = 0b0011100;
const CDCLK_FREQ_DECIMAL_540: u32 = 0b0100010;
const CDCLK_FREQ_DECIMAL_675: u32 = 0b0101010;
pub struct CdclkState {
pub frequency_khz: u32,
pub voltage_level: u32,
}
pub struct DisplayClock {
mmio: Arc<MmioRegion>,
regs: &'static dyn IntelRegs,
}
impl DisplayClock {
pub fn new(mmio: Arc<MmioRegion>, regs: &'static dyn IntelRegs) -> Self {
Self { mmio, regs }
}
pub fn init(&self) -> Result<CdclkState> {
let cdclk_ctl = self.regs.cdclk_ctl();
let current = self.mmio.read_u32(cdclk_ctl);
let freq_select = (current >> CDCLK_FREQ_SELECT_SHIFT) & CDCLK_FREQ_SELECT_MASK;
let frequency = match freq_select {
SKL_CDCLK_337_5 => 337_500,
SKL_CDCLK_450 => 450_000,
SKL_CDCLK_540 => 540_000,
SKL_CDCLK_675 => 675_000,
_ => {
warn!("redox-drm-intel: unknown CDCLK freq select {}, defaulting to 337.5 MHz", freq_select);
337_500
}
};
info!("redox-drm-intel: CDCLK initialized at {} kHz", frequency);
Ok(CdclkState {
frequency_khz: frequency,
voltage_level: freq_select,
})
}
pub fn set_frequency(&self, target_khz: u32) -> Result<CdclkState> {
let cdclk_ctl = self.regs.cdclk_ctl();
let (freq_select, decimal, actual_khz) = if target_khz <= 337_500 {
(SKL_CDCLK_337_5, CDCLK_FREQ_DECIMAL_337_5, 337_500u32)
} else if target_khz <= 450_000 {
(SKL_CDCLK_450, CDCLK_FREQ_DECIMAL_450, 450_000)
} else if target_khz <= 540_000 {
(SKL_CDCLK_540, CDCLK_FREQ_DECIMAL_540, 540_000)
} else {
(SKL_CDCLK_675, CDCLK_FREQ_DECIMAL_675, 675_000)
};
let mut val = self.mmio.read_u32(cdclk_ctl);
val &= !(CDCLK_FREQ_SELECT_MASK << CDCLK_FREQ_SELECT_SHIFT);
val &= !(CDCLK_DECIMAL_MASK);
val |= (freq_select & 0x01) << CDCLK_FREQ_SELECT_SHIFT;
val |= (freq_select >> 1) << 1;
val |= decimal & CDCLK_DECIMAL_MASK;
self.mmio.write_u32(cdclk_ctl, val);
info!("redox-drm-intel: CDCLK set to {} kHz (requested {} kHz)", actual_khz, target_khz);
Ok(CdclkState {
frequency_khz: actual_khz,
voltage_level: freq_select,
})
}
pub fn required_cdclk(modes: &[ModeInfo]) -> u32 {
let mut max_pixel_rate: u32 = 0;
for mode in modes {
let pixel_rate = mode.pixel_clock;
if pixel_rate > max_pixel_rate {
max_pixel_rate = pixel_rate;
}
}
if max_pixel_rate == 0 {
return 337_500;
}
if max_pixel_rate <= 337_500 / 2 {
337_500
} else if max_pixel_rate <= 450_000 / 2 {
450_000
} else {
540_000
}
}
}
@@ -0,0 +1,101 @@
use std::sync::Arc;
use std::time::{Duration, Instant};
use log::{debug, info, warn};
use redox_driver_sys::memory::MmioRegion;
use super::regs::IntelRegs;
use crate::driver::Result;
use crate::driver::DriverError;
const DMC_CTRL_ENABLE: u32 = 1 << 0;
const DMC_STATUS_LOADED: u32 = 1 << 0;
const DMC_TIMEOUT_MS: u64 = 50;
const CSS_HEADER_SIZE: usize = 32;
pub struct DmcFirmware {
mmio: Arc<MmioRegion>,
regs: &'static dyn IntelRegs,
}
impl DmcFirmware {
pub fn new(mmio: Arc<MmioRegion>, regs: &'static dyn IntelRegs) -> Self {
Self { mmio, regs }
}
pub fn upload(&self, firmware: &[u8]) -> Result<()> {
if firmware.len() < CSS_HEADER_SIZE {
return Err(DriverError::Other(format!(
"DMC firmware too small: {} bytes (need at least {})",
firmware.len(),
CSS_HEADER_SIZE
)));
}
let payload_offset = u32::from_le_bytes([
firmware[24], firmware[25], firmware[26], firmware[27],
]) as usize;
let payload_size = u32::from_le_bytes([
firmware[12], firmware[13], firmware[14], firmware[15],
]) as usize;
let dmc_version = u32::from_le_bytes([
firmware[28], firmware[29], firmware[30], firmware[31],
]);
if payload_offset + payload_size > firmware.len() {
return Err(DriverError::Other(format!(
"DMC payload extends beyond firmware: offset={}, size={}, total={}",
payload_offset, payload_size, firmware.len()
)));
}
debug!(
"redox-drm-intel: DMC firmware v{}, payload {} bytes at offset {}",
dmc_version, payload_size, payload_offset
);
let payload = &firmware[payload_offset..payload_offset + payload_size];
let mmio_start = self.regs.dmc_mmio_start();
let mmio_end = self.regs.dmc_mmio_end();
let fw_base = self.regs.dmc_fw_base();
let sram_base = self.regs.dmc_sram_base();
self.mmio.write_u32(mmio_start, sram_base as u32);
self.mmio.write_u32(mmio_end, (sram_base + payload_size) as u32);
for (i, chunk) in payload.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(fw_base + i * 4, val);
}
let ctrl = self.regs.dmc_ctrl();
let status_reg = self.regs.dmc_status();
self.mmio.write_u32(ctrl, self.mmio.read_u32(ctrl) | DMC_CTRL_ENABLE);
let deadline = Instant::now() + Duration::from_millis(DMC_TIMEOUT_MS);
loop {
let status = self.mmio.read_u32(status_reg);
if status & DMC_STATUS_LOADED != 0 {
info!("redox-drm-intel: DMC firmware loaded successfully");
return Ok(());
}
if Instant::now() > deadline {
warn!("redox-drm-intel: DMC firmware load timeout — status: {:#010x}", status);
return Ok(());
}
std::hint::spin_loop();
}
}
pub fn is_loaded(&self) -> bool {
let status_reg = self.regs.dmc_status();
let status = self.mmio.read_u32(status_reg);
status & DMC_STATUS_LOADED != 0
}
}
@@ -0,0 +1,85 @@
use std::sync::Arc;
use std::time::{Duration, Instant};
use log::{debug, info, warn};
use redox_driver_sys::memory::MmioRegion;
use super::regs::IntelRegs;
use crate::driver::Result;
use crate::driver::DriverError;
const POWER_WELL_TIMEOUT_MS: u64 = 20;
const SKL_PW1_MASK: u32 = 1 << 0;
const SKL_PW2_MASK: u32 = 1 << 1;
const SKL_DDI_A_IO_MASK: u32 = 1 << 2;
const SKL_DDI_B_IO_MASK: u32 = 1 << 3;
const SKL_DDI_C_IO_MASK: u32 = 1 << 4;
const SKL_DDI_D_IO_MASK: u32 = 1 << 5;
const SKL_DDI_E_IO_MASK: u32 = 1 << 6;
const SKL_AUX_A_MASK: u32 = 1 << 8;
const SKL_AUX_B_MASK: u32 = 1 << 9;
const SKL_AUX_C_MASK: u32 = 1 << 10;
const SKL_AUX_D_MASK: u32 = 1 << 11;
const DISPLAY_REQUIRED_WELLS: u32 =
SKL_PW1_MASK
| SKL_PW2_MASK
| SKL_DDI_A_IO_MASK
| SKL_DDI_B_IO_MASK
| SKL_DDI_C_IO_MASK
| SKL_DDI_D_IO_MASK
| SKL_DDI_E_IO_MASK
| SKL_AUX_A_MASK
| SKL_AUX_B_MASK
| SKL_AUX_C_MASK
| SKL_AUX_D_MASK;
pub struct DisplayPower {
mmio: Arc<MmioRegion>,
regs: &'static dyn IntelRegs,
}
impl DisplayPower {
pub fn new(mmio: Arc<MmioRegion>, regs: &'static dyn IntelRegs) -> Self {
Self { mmio, regs }
}
pub fn init_domains(&self) -> Result<()> {
info!("redox-drm-intel: enabling display power wells");
let ctl = self.regs.power_well_ctl();
let current = self.mmio.read_u32(ctl);
if current & DISPLAY_REQUIRED_WELLS == DISPLAY_REQUIRED_WELLS {
debug!("redox-drm-intel: display power wells already enabled ({:#010x})", current);
return Ok(());
}
self.mmio.write_u32(ctl, current | DISPLAY_REQUIRED_WELLS);
let deadline = Instant::now() + Duration::from_millis(POWER_WELL_TIMEOUT_MS);
loop {
let status = self.mmio.read_u32(ctl);
if status & DISPLAY_REQUIRED_WELLS == DISPLAY_REQUIRED_WELLS {
info!("redox-drm-intel: display power wells enabled ({:#010x})", status);
return Ok(());
}
if Instant::now() > deadline {
warn!("redox-drm-intel: power well enable timeout — current: {:#010x}, expected: {:#010x}",
status, DISPLAY_REQUIRED_WELLS);
return Err(DriverError::Other(format!(
"power well enable timeout: {:#010x} vs {:#010x}",
status, DISPLAY_REQUIRED_WELLS
)));
}
std::hint::spin_loop();
}
}
pub fn is_display_ready(&self) -> bool {
let ctl = self.regs.power_well_ctl();
let status = self.mmio.read_u32(ctl);
status & DISPLAY_REQUIRED_WELLS == DISPLAY_REQUIRED_WELLS
}
}
@@ -0,0 +1,168 @@
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 GMBUS_RATE_100KHZ: u32 = 0;
const GMBUS_SLAVE_EDID: u8 = 0xA0;
const GMBUS_READ: u32 = 1 << 30;
const GMBUS_SW_RDY: u32 = 1 << 31;
const GMBUS_CYCLE_WAIT: u32 = 1 << 25;
const GMBUS_BYTE_COUNT_SHIFT: u32 = 16;
const GMBUS_BYTE_COUNT_MASK: u32 = 0x1FF;
const GMBUS_SLAVE_ADDR_SHIFT: u32 = 1;
const GMBUS_SLAVE_ADDR_MASK: u32 = 0x7F;
const GMBUS_PIN_PAIR_MASK: u32 = 0x07;
const GMBUS_PIN_PAIR_SHIFT: u32 = 8;
const GMBUS_HW_RDY: u32 = 1 << 11;
const GMBUS_SDAST: u32 = 1 << 14;
const GMBUS_NAK_INDICATOR: u32 = 1 << 10;
const GMBUS_HW_WAIT_PHASE: u32 = 1 << 12;
const GMBUS_CLEAR_INTERRUPTS: u32 = 0x1F << 13;
const GMBUS_TIMEOUT_MS: u64 = 50;
#[derive(Debug, Clone, Copy)]
pub enum GmbusPort {
DdcB = 0x04,
DdcC = 0x05,
DdcD = 0x06,
Dpc = 0x02,
Dpe = 0x03,
}
impl GmbusPort {
pub fn from_connector_index(index: u8) -> Self {
match index {
0 => Self::Dpc,
1 => Self::Dpe,
2 => Self::DdcC,
3 => Self::DdcD,
_ => Self::DdcB,
}
}
fn pin_pair(&self) -> u32 {
*self as u32
}
}
pub struct GmbusController {
mmio: Arc<MmioRegion>,
regs: &'static dyn IntelRegs,
}
impl GmbusController {
pub fn new(mmio: Arc<MmioRegion>, regs: &'static dyn IntelRegs) -> Self {
Self { mmio, regs }
}
pub fn init(&self) -> Result<()> {
debug!("redox-drm-intel: initializing GMBUS controller");
let gmbus2 = self.regs.gmbus2();
self.mmio.write_u32(gmbus2, GMBUS_CLEAR_INTERRUPTS);
debug!("redox-drm-intel: GMBUS initialized");
Ok(())
}
pub fn read(&self, port: GmbusPort, slave_addr: u8, offset: u16, buf: &mut [u8]) -> Result<usize> {
let gmbus0 = self.regs.gmbus0();
let gmbus1 = self.regs.gmbus1();
let gmbus2 = self.regs.gmbus2();
let gmbus3 = self.regs.gmbus3();
let gmbus5 = self.regs.gmbus5();
let byte_count = buf.len().min(511);
self.mmio.write_u32(gmbus2, GMBUS_CLEAR_INTERRUPTS);
let pin_cfg = (port.pin_pair() << GMBUS_PIN_PAIR_SHIFT) | GMBUS_RATE_100KHZ;
self.mmio.write_u32(gmbus0, pin_cfg);
if offset > 0 {
self.mmio.write_u32(gmbus5, offset as u32 & 0xFFFF);
}
let cmd = GMBUS_SW_RDY
| GMBUS_CYCLE_WAIT
| ((byte_count as u32 & GMBUS_BYTE_COUNT_MASK) << GMBUS_BYTE_COUNT_SHIFT)
| (((slave_addr >> 1) as u32 & GMBUS_SLAVE_ADDR_MASK) << GMBUS_SLAVE_ADDR_SHIFT)
| GMBUS_READ;
self.mmio.write_u32(gmbus1, cmd);
let deadline = Instant::now() + Duration::from_millis(GMBUS_TIMEOUT_MS);
let mut bytes_read: usize = 0;
let mut fifo_offset: usize = 0;
while bytes_read < byte_count {
let status = self.mmio.read_u32(gmbus2);
if status & GMBUS_NAK_INDICATOR != 0 {
warn!("redox-drm-intel: GMBUS NAK from slave {:#04x}", slave_addr);
self.mmio.write_u32(gmbus2, GMBUS_CLEAR_INTERRUPTS);
return Err(DriverError::Other(format!(
"GMBUS NAK from slave {:#04x}",
slave_addr
)));
}
if status & GMBUS_SDAST != 0 {
let data = self.mmio.read_u32(gmbus3);
let available = 4usize.min(byte_count - bytes_read);
let data_bytes = data.to_le_bytes();
for i in 0..available {
buf[bytes_read + i] = data_bytes[i];
}
bytes_read += available;
fifo_offset = (fifo_offset + 1) % 4;
continue;
}
if status & GMBUS_HW_RDY != 0 {
break;
}
if Instant::now() > deadline {
warn!("redox-drm-intel: GMBUS timeout after {}ms, {} bytes read", GMBUS_TIMEOUT_MS, bytes_read);
self.mmio.write_u32(gmbus2, GMBUS_CLEAR_INTERRUPTS);
if bytes_read > 0 {
return Ok(bytes_read);
}
return Err(DriverError::Other("GMBUS read timeout".into()));
}
std::hint::spin_loop();
}
self.mmio.write_u32(gmbus2, GMBUS_CLEAR_INTERRUPTS);
debug!("redox-drm-intel: GMBUS read {} bytes from slave {:#04x}", bytes_read, slave_addr);
Ok(bytes_read)
}
pub fn read_edid(&self, port: GmbusPort) -> Result<Vec<u8>> {
let mut edid = vec![0u8; 128];
let bytes = self.read(port, GMBUS_SLAVE_EDID, 0, &mut edid)?;
if bytes < 128 {
warn!("redox-drm-intel: short EDID read: {} bytes", bytes);
return Err(DriverError::Other("short EDID read".into()));
}
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: {:02x?}", &edid[..8]);
return Err(DriverError::Other("invalid EDID header".into()));
}
let extension_count = edid[126] as usize;
debug!("redox-drm-intel: read 128-byte EDID block, {} extensions", extension_count);
Ok(edid)
}
}
@@ -0,0 +1,188 @@
use log::warn;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntelGeneration {
Gen4,
Gen5,
Gen6,
Gen7,
Gen8,
Gen9,
Gen9_5,
Gen12,
Gen12_7,
Unknown,
}
impl IntelGeneration {
pub fn display_version(&self) -> u8 {
match self {
Self::Gen4 => 4,
Self::Gen5 => 5,
Self::Gen6 => 6,
Self::Gen7 => 7,
Self::Gen8 => 9,
Self::Gen9 => 9,
Self::Gen9_5 => 11,
Self::Gen12 => 12,
Self::Gen12_7 => 14,
Self::Unknown => 0,
}
}
pub fn gt_version(&self) -> u8 {
match self {
Self::Gen4 => 4,
Self::Gen5 => 5,
Self::Gen6 => 6,
Self::Gen7 => 7,
Self::Gen8 => 8,
Self::Gen9 => 9,
Self::Gen9_5 => 9,
Self::Gen12 => 12,
Self::Gen12_7 => 12,
Self::Unknown => 0,
}
}
pub fn num_pipes(&self) -> u8 {
match self {
Self::Gen4 | Self::Gen5 => 2,
Self::Gen6 | Self::Gen7 | Self::Gen8 => 3,
Self::Gen9 | Self::Gen9_5 | Self::Gen12 | Self::Gen12_7 => 4,
Self::Unknown => 1,
}
}
}
#[derive(Debug, Clone)]
pub struct IntelDeviceInfo {
pub generation: IntelGeneration,
pub display_version: u8,
pub gt_version: u8,
pub num_pipes: u8,
pub num_ports: u8,
pub has_ddi: bool,
pub has_dp_aux: bool,
pub has_gmbus: bool,
pub has_dmc: bool,
pub has_combo_phy: bool,
pub has_dbuf_slice: bool,
pub has_separate_transcoder: bool,
pub dmc_fw_key: Option<&'static str>,
pub platform_name: &'static str,
}
impl IntelDeviceInfo {
pub fn is_gen9_or_later(&self) -> bool {
matches!(
self.generation,
IntelGeneration::Gen9
| IntelGeneration::Gen9_5
| IntelGeneration::Gen12
| IntelGeneration::Gen12_7
)
}
pub fn is_gen12_or_later(&self) -> bool {
matches!(self.generation, IntelGeneration::Gen12 | IntelGeneration::Gen12_7)
}
}
struct DeviceIdEntry {
device_id: u16,
gen: IntelGeneration,
platform_name: &'static str,
dmc_fw_key: Option<&'static str>,
}
const DEVICE_ID_TABLE: &[DeviceIdEntry] = &[
DeviceIdEntry { device_id: 0x1912, gen: IntelGeneration::Gen9, platform_name: "Skylake DT GT2", dmc_fw_key: Some("SKL") },
DeviceIdEntry { device_id: 0x1916, gen: IntelGeneration::Gen9, platform_name: "Skylake ULT GT2", dmc_fw_key: Some("SKL") },
DeviceIdEntry { device_id: 0x191B, gen: IntelGeneration::Gen9, platform_name: "Skylake DT GT2", dmc_fw_key: Some("SKL") },
DeviceIdEntry { device_id: 0x191D, gen: IntelGeneration::Gen9, platform_name: "Skylake DT GT2", dmc_fw_key: Some("SKL") },
DeviceIdEntry { device_id: 0x191E, gen: IntelGeneration::Gen9, platform_name: "Skylake ULX GT2", dmc_fw_key: Some("SKL") },
DeviceIdEntry { device_id: 0x1921, gen: IntelGeneration::Gen9, platform_name: "Skylake ULT GT2", dmc_fw_key: Some("SKL") },
DeviceIdEntry { device_id: 0x1923, gen: IntelGeneration::Gen9, platform_name: "Skylake ULT GT2", dmc_fw_key: Some("SKL") },
DeviceIdEntry { device_id: 0x1926, gen: IntelGeneration::Gen9, platform_name: "Skylake ULT GT3", dmc_fw_key: Some("SKL") },
DeviceIdEntry { device_id: 0x1927, gen: IntelGeneration::Gen9, platform_name: "Skylake ULT GT3", dmc_fw_key: Some("SKL") },
DeviceIdEntry { device_id: 0x5912, gen: IntelGeneration::Gen9, platform_name: "Kaby Lake DT GT2", dmc_fw_key: Some("KBL") },
DeviceIdEntry { device_id: 0x5916, gen: IntelGeneration::Gen9, platform_name: "Kaby Lake ULT GT2", dmc_fw_key: Some("KBL") },
DeviceIdEntry { device_id: 0x591B, gen: IntelGeneration::Gen9, platform_name: "Kaby Lake DT GT2", dmc_fw_key: Some("KBL") },
DeviceIdEntry { device_id: 0x591D, gen: IntelGeneration::Gen9, platform_name: "Kaby Lake DT GT2", dmc_fw_key: Some("KBL") },
DeviceIdEntry { device_id: 0x5921, gen: IntelGeneration::Gen9, platform_name: "Kaby Lake ULT GT2", dmc_fw_key: Some("KBL") },
DeviceIdEntry { device_id: 0x5923, gen: IntelGeneration::Gen9, platform_name: "Kaby Lake ULT GT2", dmc_fw_key: Some("KBL") },
DeviceIdEntry { device_id: 0x5926, gen: IntelGeneration::Gen9, platform_name: "Kaby Lake ULT GT3", dmc_fw_key: Some("KBL") },
DeviceIdEntry { device_id: 0x5927, gen: IntelGeneration::Gen9, platform_name: "Kaby Lake ULT GT3", dmc_fw_key: Some("KBL") },
DeviceIdEntry { device_id: 0x3E90, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake DT GT2", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x3E91, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake DT GT2", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x3E92, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake DT GT2", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x3E96, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake DT GT2", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x3E98, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake DT GT2", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x3E9A, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake DT GT2", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x3EA5, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake ULT GT3", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x3EA6, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake ULT GT3", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x3EA7, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake ULT GT3", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x3EA8, gen: IntelGeneration::Gen9, platform_name: "Coffee Lake ULT GT3", dmc_fw_key: Some("CFL") },
DeviceIdEntry { device_id: 0x8A56, gen: IntelGeneration::Gen9_5, platform_name: "Ice Lake ULT GT2", dmc_fw_key: Some("ICL") },
DeviceIdEntry { device_id: 0x8A52, gen: IntelGeneration::Gen9_5, platform_name: "Ice Lake ULT GT2", dmc_fw_key: Some("ICL") },
DeviceIdEntry { device_id: 0x4500, gen: IntelGeneration::Gen9_5, platform_name: "Elkhart Lake", dmc_fw_key: Some("EHL") },
DeviceIdEntry { device_id: 0x4571, gen: IntelGeneration::Gen9_5, platform_name: "Elkhart Lake", dmc_fw_key: Some("EHL") },
DeviceIdEntry { device_id: 0x9A49, gen: IntelGeneration::Gen12, platform_name: "Tiger Lake ULT GT2", dmc_fw_key: Some("TGL") },
DeviceIdEntry { device_id: 0x9A40, gen: IntelGeneration::Gen12, platform_name: "Tiger Lake ULT GT2", dmc_fw_key: Some("TGL") },
DeviceIdEntry { device_id: 0x9A78, gen: IntelGeneration::Gen12, platform_name: "Tiger Lake H GT2", dmc_fw_key: Some("TGL") },
DeviceIdEntry { device_id: 0x46A6, gen: IntelGeneration::Gen12, platform_name: "Alder Lake-P GT2", dmc_fw_key: Some("ADLP") },
DeviceIdEntry { device_id: 0x4626, gen: IntelGeneration::Gen12, platform_name: "Alder Lake-P GT2", dmc_fw_key: Some("ADLP") },
DeviceIdEntry { device_id: 0x46A8, gen: IntelGeneration::Gen12, platform_name: "Alder Lake-P GT2", dmc_fw_key: Some("ADLP") },
DeviceIdEntry { device_id: 0x4628, gen: IntelGeneration::Gen12, platform_name: "Alder Lake-P GT2", dmc_fw_key: Some("ADLP") },
DeviceIdEntry { device_id: 0x46B3, gen: IntelGeneration::Gen12, platform_name: "Alder Lake-P GT2", dmc_fw_key: Some("ADLP") },
DeviceIdEntry { device_id: 0x5690, gen: IntelGeneration::Gen12, platform_name: "DG2 Alchemist G10", dmc_fw_key: Some("DG2") },
DeviceIdEntry { device_id: 0x5698, gen: IntelGeneration::Gen12, platform_name: "DG2 Alchemist G11", dmc_fw_key: Some("DG2") },
DeviceIdEntry { device_id: 0x56A0, gen: IntelGeneration::Gen12, platform_name: "DG2 Alchemist G12", dmc_fw_key: Some("DG2") },
DeviceIdEntry { device_id: 0x7D55, gen: IntelGeneration::Gen12_7, platform_name: "Meteor Lake", dmc_fw_key: Some("MTL") },
DeviceIdEntry { device_id: 0x7D60, gen: IntelGeneration::Gen12_7, platform_name: "Meteor Lake", dmc_fw_key: Some("MTL") },
DeviceIdEntry { device_id: 0x7D45, gen: IntelGeneration::Gen12_7, platform_name: "Meteor Lake", dmc_fw_key: Some("MTL") },
DeviceIdEntry { device_id: 0x7D67, gen: IntelGeneration::Gen12_7, platform_name: "Meteor Lake", dmc_fw_key: Some("MTL") },
];
pub fn device_info_from_id(device_id: u16) -> IntelDeviceInfo {
for entry in DEVICE_ID_TABLE {
if entry.device_id == device_id {
let gen = entry.gen;
return IntelDeviceInfo {
generation: gen,
display_version: gen.display_version(),
gt_version: gen.gt_version(),
num_pipes: gen.num_pipes(),
num_ports: if gen == IntelGeneration::Gen12 || gen == IntelGeneration::Gen12_7 { 6 } else { 4 },
has_ddi: matches!(gen, IntelGeneration::Gen9 | IntelGeneration::Gen9_5 | IntelGeneration::Gen12 | IntelGeneration::Gen12_7),
has_dp_aux: matches!(gen, IntelGeneration::Gen9 | IntelGeneration::Gen9_5 | IntelGeneration::Gen12 | IntelGeneration::Gen12_7),
has_gmbus: matches!(gen, IntelGeneration::Gen9 | IntelGeneration::Gen9_5 | IntelGeneration::Gen12 | IntelGeneration::Gen12_7),
has_dmc: matches!(gen, IntelGeneration::Gen9 | IntelGeneration::Gen9_5 | IntelGeneration::Gen12 | IntelGeneration::Gen12_7),
has_combo_phy: matches!(gen, IntelGeneration::Gen9_5 | IntelGeneration::Gen12 | IntelGeneration::Gen12_7),
has_dbuf_slice: gen == IntelGeneration::Gen12 || gen == IntelGeneration::Gen12_7,
has_separate_transcoder: gen == IntelGeneration::Gen12 || gen == IntelGeneration::Gen12_7,
dmc_fw_key: entry.dmc_fw_key,
platform_name: entry.platform_name,
};
}
}
warn!("redox-drm: Intel device {:#06x} not in device info table — using Gen9 defaults", device_id);
IntelDeviceInfo {
generation: IntelGeneration::Gen9,
display_version: 9,
gt_version: 9,
num_pipes: 3,
num_ports: 4,
has_ddi: true,
has_dp_aux: true,
has_gmbus: true,
has_dmc: true,
has_combo_phy: false,
has_dbuf_slice: false,
has_separate_transcoder: false,
dmc_fw_key: None,
platform_name: "Unknown (Gen9 default)",
}
}
@@ -1,5 +1,12 @@
pub mod display;
pub mod display_cdclk;
pub mod display_dmc;
pub mod display_power;
pub mod gmbus;
pub mod gtt;
pub mod info;
pub mod regs;
pub mod regs_gen9;
pub mod ring;
use std::collections::HashMap;
@@ -19,7 +26,14 @@ use crate::kms::encoder::Encoder;
use crate::kms::{ConnectorInfo, ConnectorType, ModeInfo};
use self::display::{DisplayPipe, IntelDisplay};
use self::display_cdclk::DisplayClock;
use self::display_dmc::DmcFirmware;
use self::display_power::DisplayPower;
use self::gmbus::GmbusController;
use self::gtt::IntelGtt;
use self::info::{IntelDeviceInfo, device_info_from_id};
use self::regs::IntelRegs;
use self::regs_gen9::Gen9Regs;
use self::ring::{IntelRing, RingType};
const FORCEWAKE: usize = 0xA18C;
@@ -38,7 +52,9 @@ const RING_HEAD_OFFSET: usize = 0x34;
pub struct IntelDriver {
info: PciDeviceInfo,
mmio: MmioRegion,
device_info: IntelDeviceInfo,
mmio: Arc<MmioRegion>,
regs: &'static Gen9Regs,
irq_handle: Mutex<Option<InterruptHandle>>,
display: IntelDisplay,
gem: Mutex<GemManager>,
@@ -47,6 +63,10 @@ pub struct IntelDriver {
encoders: Mutex<Vec<Encoder>>,
gtt: Mutex<IntelGtt>,
ring: Mutex<IntelRing>,
gmbus: GmbusController,
display_power: DisplayPower,
dmc: DmcFirmware,
cdclk: DisplayClock,
}
impl IntelDriver {
@@ -97,6 +117,39 @@ impl IntelDriver {
enable_forcewake(&mmio)?;
let device_info = device_info_from_id(info.device_id);
info!(
"redox-drm: Intel {} detected (device {:#06x}, display ver {})",
device_info.platform_name, info.device_id, device_info.display_version
);
let regs = &Gen9Regs;
let mmio_arc = Arc::new(mmio);
let display_mmio_arc = Arc::new(display_mmio);
let gmbus = GmbusController::new(display_mmio_arc.clone(), regs);
gmbus.init()?;
let display_power = DisplayPower::new(mmio_arc.clone(), regs);
display_power.init_domains()?;
let dmc = DmcFirmware::new(mmio_arc.clone(), regs);
if let Some(dmc_key) = device_info.dmc_fw_key {
if let Some(fw_data) = firmware.get(dmc_key) {
info!("redox-drm-intel: loading DMC firmware for {}", dmc_key);
dmc.upload(fw_data)?;
} else {
warn!("redox-drm-intel: DMC firmware key '{}' not found in cache", dmc_key);
}
}
let cdclk = DisplayClock::new(mmio_arc.clone(), regs);
let cdclk_state = cdclk.init()?;
info!(
"redox-drm-intel: CDCLK = {} kHz (voltage level {})",
cdclk_state.frequency_khz, cdclk_state.voltage_level
);
let display = IntelDisplay::new(display_mmio)?;
let mut gtt = IntelGtt::init(gtt_mmio, gtt_control_mmio)?;
let mut ring = IntelRing::create(ring_mmio, RingType::Render)?;
@@ -137,7 +190,9 @@ impl IntelDriver {
Ok(Self {
info,
mmio,
device_info,
mmio: mmio_arc,
regs,
irq_handle: Mutex::new(irq_handle),
display,
gem: Mutex::new(GemManager::new()),
@@ -146,6 +201,10 @@ impl IntelDriver {
encoders: Mutex::new(encoders),
gtt: Mutex::new(gtt),
ring: Mutex::new(ring),
gmbus,
display_power,
dmc,
cdclk,
})
}
@@ -0,0 +1,53 @@
pub trait IntelRegs {
fn forcewake_req(&self) -> usize;
fn forcewake_ack(&self) -> usize;
fn power_well_ctl(&self) -> usize;
fn cdclk_ctl(&self) -> usize;
fn dmc_mmio_start(&self) -> usize;
fn dmc_mmio_end(&self) -> usize;
fn dmc_fw_base(&self) -> usize;
fn dmc_ctrl(&self) -> usize;
fn dmc_status(&self) -> usize;
fn dmc_sram_base(&self) -> usize;
fn gmbus0(&self) -> usize;
fn gmbus1(&self) -> usize;
fn gmbus2(&self) -> usize;
fn gmbus3(&self) -> usize;
fn gmbus4(&self) -> usize;
fn gmbus5(&self) -> usize;
fn pipeconf(&self, pipe: u8) -> usize;
fn pipeconf_enable_mask(&self) -> u32;
fn htotal(&self, pipe: u8) -> usize;
fn hblank(&self, pipe: u8) -> usize;
fn hsync(&self, pipe: u8) -> usize;
fn vtotal(&self, pipe: u8) -> usize;
fn vblank(&self, pipe: u8) -> usize;
fn vsync(&self, pipe: u8) -> usize;
fn pipe_src(&self, pipe: u8) -> usize;
fn pipe_stride(&self) -> usize;
fn dspcntr(&self, pipe: u8) -> usize;
fn dspcntr_enable_mask(&self) -> u32;
fn dspsurf(&self, pipe: u8) -> usize;
fn plane_size(&self, pipe: u8) -> usize;
fn ddi_buf_ctl(&self, port: u8) -> usize;
fn ddi_port_stride(&self) -> usize;
fn curcntr(&self, pipe: u8) -> usize;
fn curpos(&self, pipe: u8) -> usize;
fn curbase(&self, pipe: u8) -> usize;
fn pipeframe_reg(&self, pipe: u8) -> usize;
fn pipeframe_count_mask(&self) -> u32;
fn gfx_flsh_cntl(&self) -> usize;
fn pp_status(&self) -> usize;
}
@@ -0,0 +1,117 @@
use super::regs::IntelRegs;
pub struct Gen9Regs;
impl IntelRegs for Gen9Regs {
fn forcewake_req(&self) -> usize { 0xA18C }
fn forcewake_ack(&self) -> usize { 0xA194 }
fn power_well_ctl(&self) -> usize { 0x45400 }
fn cdclk_ctl(&self) -> usize { 0x46000 }
fn dmc_mmio_start(&self) -> usize { 0x6F000 }
fn dmc_mmio_end(&self) -> usize { 0x6F004 }
fn dmc_fw_base(&self) -> usize { 0x6F038 }
fn dmc_ctrl(&self) -> usize { 0x6F064 }
fn dmc_status(&self) -> usize { 0x6F06C }
fn dmc_sram_base(&self) -> usize { 0x10000 }
fn gmbus0(&self) -> usize { 0xC5100 }
fn gmbus1(&self) -> usize { 0xC5104 }
fn gmbus2(&self) -> usize { 0xC5108 }
fn gmbus3(&self) -> usize { 0xC510C }
fn gmbus4(&self) -> usize { 0xC5110 }
fn gmbus5(&self) -> usize { 0xC5120 }
fn pipeconf(&self, pipe: u8) -> usize {
0x70008 + (pipe as usize) * 0x1000
}
fn pipeconf_enable_mask(&self) -> u32 { 1 << 31 }
fn htotal(&self, pipe: u8) -> usize {
0x60000 + (pipe as usize) * 0x1000
}
fn hblank(&self, pipe: u8) -> usize {
0x60004 + (pipe as usize) * 0x1000
}
fn hsync(&self, pipe: u8) -> usize {
0x60008 + (pipe as usize) * 0x1000
}
fn vtotal(&self, pipe: u8) -> usize {
0x6000C + (pipe as usize) * 0x1000
}
fn vblank(&self, pipe: u8) -> usize {
0x60010 + (pipe as usize) * 0x1000
}
fn vsync(&self, pipe: u8) -> usize {
0x60014 + (pipe as usize) * 0x1000
}
fn pipe_src(&self, pipe: u8) -> usize {
0x6001C + (pipe as usize) * 0x1000
}
fn pipe_stride(&self) -> usize { 0x1000 }
fn dspcntr(&self, pipe: u8) -> usize {
0x70180 + (pipe as usize) * 0x1000
}
fn dspcntr_enable_mask(&self) -> u32 { 1 << 31 }
fn dspsurf(&self, pipe: u8) -> usize {
0x7019C + (pipe as usize) * 0x1000
}
fn plane_size(&self, pipe: u8) -> usize {
0x70190 + (pipe as usize) * 0x1000
}
fn ddi_buf_ctl(&self, port: u8) -> usize {
0x64000 + (port as usize) * 0x100
}
fn ddi_port_stride(&self) -> usize { 0x100 }
fn curcntr(&self, pipe: u8) -> usize {
0x70080 + (pipe as usize) * 0x1000
}
fn curpos(&self, pipe: u8) -> usize {
0x70084 + (pipe as usize) * 0x1000
}
fn curbase(&self, pipe: u8) -> usize {
0x70088 + (pipe as usize) * 0x1000
}
fn pipeframe_reg(&self, pipe: u8) -> usize {
0x70040 + (pipe as usize) * 0x1000
}
fn pipeframe_count_mask(&self) -> u32 { 0x00FFFFFF }
fn gfx_flsh_cntl(&self) -> usize { 0x101008 }
fn pp_status(&self) -> usize { 0xC7200 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gen9_pipeconf() {
let regs = Gen9Regs;
assert_eq!(regs.pipeconf(0), 0x70008);
assert_eq!(regs.pipeconf(1), 0x71008);
assert_eq!(regs.pipeconf(2), 0x72008);
}
#[test]
fn test_gen9_ddi_buf_ctl() {
let regs = Gen9Regs;
assert_eq!(regs.ddi_buf_ctl(0), 0x64000);
assert_eq!(regs.ddi_buf_ctl(1), 0x64100);
}
#[test]
fn test_gen9_plane_offsets() {
let regs = Gen9Regs;
assert_eq!(regs.dspcntr(0), 0x70180);
assert_eq!(regs.dspsurf(0), 0x7019C);
assert_eq!(regs.dspcntr(1), 0x71180);
}
}