diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_cdclk.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_cdclk.rs new file mode 100644 index 0000000000..58cd6491b5 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_cdclk.rs @@ -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, + regs: &'static dyn IntelRegs, +} + +impl DisplayClock { + pub fn new(mmio: Arc, regs: &'static dyn IntelRegs) -> Self { + Self { mmio, regs } + } + + pub fn init(&self) -> Result { + 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 { + 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 + } + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_dmc.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_dmc.rs new file mode 100644 index 0000000000..6cf8893f6c --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_dmc.rs @@ -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, + regs: &'static dyn IntelRegs, +} + +impl DmcFirmware { + pub fn new(mmio: Arc, 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 + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_power.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_power.rs new file mode 100644 index 0000000000..dec7d5b839 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_power.rs @@ -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, + regs: &'static dyn IntelRegs, +} + +impl DisplayPower { + pub fn new(mmio: Arc, 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 + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/gmbus.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/gmbus.rs new file mode 100644 index 0000000000..e802ffc106 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/gmbus.rs @@ -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, + regs: &'static dyn IntelRegs, +} + +impl GmbusController { + pub fn new(mmio: Arc, 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 { + 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> { + 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) + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/info.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/info.rs new file mode 100644 index 0000000000..f1f5229010 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/info.rs @@ -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)", + } +} 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 4e7cd5796d..f783fc8a48 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 @@ -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, + regs: &'static Gen9Regs, irq_handle: Mutex>, display: IntelDisplay, gem: Mutex, @@ -47,6 +63,10 @@ pub struct IntelDriver { encoders: Mutex>, gtt: Mutex, ring: Mutex, + 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, }) } diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/regs.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/regs.rs new file mode 100644 index 0000000000..ad61f7a7ee --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/regs.rs @@ -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; +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/regs_gen9.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/regs_gen9.rs new file mode 100644 index 0000000000..3c2207a6fc --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/regs_gen9.rs @@ -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); + } +}