diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/context.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/context.rs index 911b03582e..f08cf9cc20 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/context.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/context.rs @@ -5,6 +5,21 @@ use log::{debug, info}; use super::gtt::IntelGtt; use crate::driver::{ContextHandle, DriverError, Result}; +// ── Intel PPGTT + GPU Context Manager ─────────────────────────────────── +// PPGTT (Per-Process Graphics Translation Tables) provide virtual address +// spaces for GPU contexts. Gen8+ uses 4-level page tables (PDP→PD→PT→PTE) +// with 512 entries per level. Each entry maps a 4KB page. +// +// Context manager maintains a BTreeMap of IntelContext objects keyed by +// ContextHandle. Each context owns an IntelPpgtt with its own page tables +// allocated from the GGTT aperture. Context creation allocates an LRC +// (Logical Ring Context) descriptor for GPU state save/restore. +// +// Page table levels: +// PDP (Page Directory Pointer): top level, 512 entries → 1GB each +// PD (Page Directory): 512 entries → 2MB each +// PT (Page Table): 512 entries → 4KB each + const PAGE_SIZE: u64 = 4096; const PPGTT_PTE_PRESENT: u64 = 1 << 0; const PPGTT_PTE_RW: u64 = 1 << 1; diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/lspcon.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/lspcon.rs new file mode 100644 index 0000000000..6d319abb3e --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/lspcon.rs @@ -0,0 +1,102 @@ +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use log::{debug, info, warn}; +use redox_driver_sys::memory::MmioRegion; + +use super::dp_aux::DpAux; +use crate::driver::{DriverError, Result}; + +// ── LSPCON — Level Shifter/Protocol Converter ───────────────────────────── +// LSPCON converts HDMI 1.4 to HDMI 2.0 by adding TMDS scrambling and +// increasing the TMDS clock from 3.4 Gbps to 6.0 Gbps. It appears as a +// DP-to-HDMI bridge on the DDI port and communicates via DP AUX. +// +// LSPCON modes: +// LS mode (Level Shifter): passes through HDMI 1.4 TMDS unchanged +// PCON mode (Protocol Converter): adds HDMI 2.0 TMDS scrambling +// FRL mode: HDMI 2.1 FRL passthrough (LSPCON 2.0+) +// +// Detection: read LSPCON vendor OUI via DP AUX at I2C address 0x80. +// Parade Technologies OUI: 0x0011CF +// MegaChips OUI: 0x008067 + +const LSPCON_I2C_ADDR: u8 = 0x80; +const LSPCON_VENDOR_OUI_OFFSET: u32 = 0x0050; +const LSPCON_MODE_OFFSET: u32 = 0x0052; +const LSPCON_MODE_LS: u8 = 0; +const LSPCON_MODE_PCON: u8 = 1; +const LSPCON_MODE_FRL: u8 = 2; + +const PARADE_OUI: [u8; 3] = [0xCF, 0x11, 0x00]; +const MEGACHIPS_OUI: [u8; 3] = [0x67, 0x80, 0x00]; +const LSPCON_TIMEOUT_MS: u64 = 500; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum LspconMode { LevelShifter, ProtocolConverter, Frl } + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum LspconVendor { Parade, MegaChips, Unknown } + +pub struct Lspcon { + detected: bool, + vendor: LspconVendor, + mode: LspconMode, + port: u8, +} + +impl Lspcon { + pub fn new(port: u8) -> Self { + Self { detected: false, vendor: LspconVendor::Unknown, mode: LspconMode::LevelShifter, port } + } + + pub fn detect(&mut self, aux: &DpAux) -> Result { + match aux.read_dpcd(LSPCON_VENDOR_OUI_OFFSET, 4) { + Ok(data) if data.len() >= 3 => { + if &data[0..3] == &PARADE_OUI { + self.vendor = LspconVendor::Parade; + self.detected = true; + info!("redox-drm-intel: LSPCON Parade Technologies detected on port {}", self.port); + } else if &data[0..3] == &MEGACHIPS_OUI { + self.vendor = LspconVendor::MegaChips; + self.detected = true; + info!("redox-drm-intel: LSPCON MegaChips detected on port {}", self.port); + } + } + _ => {} + } + Ok(self.detected) + } + + pub fn set_mode(&mut self, aux: &DpAux, mode: LspconMode) -> Result<()> { + if !self.detected { return Ok(()); } + + let mode_val = match mode { + LspconMode::LevelShifter => LSPCON_MODE_LS, + LspconMode::ProtocolConverter => LSPCON_MODE_PCON, + LspconMode::Frl => LSPCON_MODE_FRL, + }; + + aux.write_dpcd(LSPCON_MODE_OFFSET, &[mode_val])?; + + let deadline = Instant::now() + Duration::from_millis(LSPCON_TIMEOUT_MS); + loop { + if let Ok(data) = aux.read_dpcd(LSPCON_MODE_OFFSET, 1) { + if !data.is_empty() && data[0] == mode_val { + self.mode = mode; + info!("redox-drm-intel: LSPCON mode → {:?}", mode); + return Ok(()); + } + } + if Instant::now() > deadline { + warn!("redox-drm-intel: LSPCON mode change timeout"); + return Err(DriverError::Initialization("LSPCON mode change timeout")); + } + std::hint::spin_loop(); + } + } + + pub fn is_detected(&self) -> bool { self.detected } + pub fn vendor(&self) -> LspconVendor { self.vendor } + pub fn mode(&self) -> LspconMode { self.mode } +}