diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_uhbr.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_uhbr.rs new file mode 100644 index 0000000000..cbd3ce94f7 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_uhbr.rs @@ -0,0 +1,85 @@ +use std::time::{Duration, Instant}; + +use log::{debug, info, warn}; + +use super::dp_aux::DpAux; +use crate::driver::{DriverError, Result}; + +// ── DP 2.0 128b/132b Link Training ───────────────────────────────────── +// DP 2.0 (UHBR) replaces 8b/10b encoding with 128b/132b, increasing +// efficiency from 80% to ~97%. Link training for 128b/132b differs from +// 8b/10b: it uses a single training pattern (TPS4) instead of the +// two-phase clock recovery + channel equalization of 8b/10b. +// +// UHBR link training sequence: +// 1. Write link rate + lane count to DPCD +// 2. Write TPS4 pattern to DPCD 0x0102 +// 3. Enable 128b/132b channel coding +// 4. Wait for lane status + interlane alignment +// 5. Disable training pattern + +const DPCD_LINK_BW_SET: u32 = 0x0100; +const DPCD_LANE_COUNT_SET: u32 = 0x0101; +const DPCD_TRAINING_PATTERN_SET: u32 = 0x0102; +const DPCD_LANE0_1_STATUS: u32 = 0x0202; +const DPCD_LANE_ALIGN_STATUS_UPDATED: u32 = 0x0204; + +const DPCD_128B132B_CHANNEL_CODING: u32 = 0x0220; +const DPCD_128B132B_ENABLE: u8 = 1 << 0; + +const DP_TRAINING_PATTERN_4: u8 = 0x07; +const DP_TRAINING_PATTERN_DISABLE: u8 = 0x00; + +const DP_LANE_CR_DONE: u8 = 1 << 0; +const DP_LANE_CHANNEL_EQ_DONE: u8 = 1 << 1; +const DP_LANE_SYMBOL_LOCKED: u8 = 1 << 2; + +const LINK_TRAINING_TIMEOUT_MS: u64 = 100; + +pub struct UhbrLinkTraining; + +impl UhbrLinkTraining { + pub fn train(aux: &DpAux, link_rate: u8, lane_count: u8) -> Result<()> { + info!("redox-drm-intel: UHBR link training — rate={:#x} lanes={}", link_rate, lane_count); + + aux.write_dpcd(DPCD_LINK_BW_SET, &[link_rate])?; + aux.write_dpcd(DPCD_LANE_COUNT_SET, &[lane_count])?; + + aux.write_dpcd(DPCD_128B132B_CHANNEL_CODING, &[DPCD_128B132B_ENABLE])?; + + aux.write_dpcd(DPCD_TRAINING_PATTERN_SET, &[DP_TRAINING_PATTERN_4])?; + + let deadline = Instant::now() + Duration::from_millis(LINK_TRAINING_TIMEOUT_MS); + loop { + let status = aux.read_dpcd(DPCD_LANE0_1_STATUS, 4)?; + if status.len() < 4 { continue; } + + let all_locked = (0..lane_count).all(|lane| { + let shift = (lane % 2) * 4; + let byte = status[(lane / 2) as usize]; + (byte >> shift) & (DP_LANE_CR_DONE | DP_LANE_CHANNEL_EQ_DONE | DP_LANE_SYMBOL_LOCKED) + == (DP_LANE_CR_DONE | DP_LANE_CHANNEL_EQ_DONE | DP_LANE_SYMBOL_LOCKED) + }); + + if all_locked { + let align = aux.read_dpcd(DPCD_LANE_ALIGN_STATUS_UPDATED, 1)?; + if !align.is_empty() && align[0] & 0x01 != 0 { + aux.write_dpcd(DPCD_TRAINING_PATTERN_SET, &[DP_TRAINING_PATTERN_DISABLE])?; + info!("redox-drm-intel: UHBR link trained — {} lanes locked", lane_count); + return Ok(()); + } + } + + if Instant::now() > deadline { + warn!("redox-drm-intel: UHBR link training timeout"); + aux.write_dpcd(DPCD_TRAINING_PATTERN_SET, &[DP_TRAINING_PATTERN_DISABLE])?; + return Err(DriverError::Initialization("UHBR link training timeout")); + } + std::hint::spin_loop(); + } + } + + pub fn is_uhbr(link_rate: u8) -> bool { + matches!(link_rate, 0x01 | 0x04 | 0x07) + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mg_pll.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mg_pll.rs new file mode 100644 index 0000000000..f5106c23dc --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mg_pll.rs @@ -0,0 +1,130 @@ +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use log::{debug, info, warn}; +use redox_driver_sys::memory::MmioRegion; + +use crate::driver::{DriverError, Result}; + +// ── MG PLL — Multi-Gigabit PLL for USB-C / Type-C DisplayPort ────────── +// Introduced with Ice Lake (Gen11). MG PLL provides the high-frequency +// clock for USB-C DP Alt Mode and Thunderbolt displays. +// Each MG PLL can drive up to two Type-C ports. +// Programming sequence: enable power → program dividers → enable → wait lock. + +const MG_PLL_CTL1_BASE: usize = 0x168000; +const MG_PLL_CTL2_BASE: usize = 0x168080; +const MG_PLL_BIAS_BASE: usize = 0x168100; +const MG_PLL_TDC_BASE: usize = 0x168200; +const MG_PLL_STRIDE: usize = 0x1000; + +const MG_PLL_ENABLE: u32 = 1 << 31; +const MG_PLL_LOCK: u32 = 1 << 30; +const MG_PLL_POWER_ENABLE: u32 = 1 << 29; +const MG_PLL_REFCLK_38_4: u32 = 2 << 24; +const MG_PLL_DIV0_SHIFT: u32 = 16; +const MG_PLL_DIV1_SHIFT: u32 = 8; + +const MG_PLL_DCO_INT_MASK: u32 = 0xFF; +const MG_PLL_DCO_FRAC_MASK: u32 = 0xFFFF; +const MG_PLL_TIMEOUT_MS: u64 = 5; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MgPllPort { PortA, PortB, PortC, PortD } + +impl MgPllPort { + fn index(&self) -> usize { + match self { MgPllPort::PortA => 0, MgPllPort::PortB => 1, + MgPllPort::PortC => 2, MgPllPort::PortD => 3 } + } +} + +pub struct MgPll { + mmio: Arc, + enabled: bool, + port: MgPllPort, + frequency_khz: u32, + refclk_khz: u32, +} + +impl MgPll { + pub fn new(mmio: Arc, port: MgPllPort) -> Self { + Self { mmio, enabled: false, port, frequency_khz: 0, refclk_khz: 38400 } + } + + pub fn init(&mut self) -> Result<()> { + let ctl1 = MG_PLL_CTL1_BASE + self.port.index() * MG_PLL_STRIDE; + + let mut val = self.mmio.read32(ctl1); + if val & MG_PLL_ENABLE == 0 { + val |= MG_PLL_POWER_ENABLE; + self.mmio.write32(ctl1, val); + std::thread::sleep(Duration::from_micros(10)); + + val |= MG_PLL_ENABLE | MG_PLL_REFCLK_38_4; + self.mmio.write32(ctl1, val); + + self.wait_lock(ctl1)?; + } + + self.enabled = true; + info!("redox-drm-intel: MG PLL {:?} initialized", self.port); + Ok(()) + } + + pub fn configure(&mut self, pixel_clock_khz: u32) -> Result<()> { + let ctl1 = MG_PLL_CTL1_BASE + self.port.index() * MG_PLL_STRIDE; + let ctl2 = MG_PLL_CTL2_BASE + self.port.index() * MG_PLL_STRIDE; + + let refclk = self.refclk_khz as u64; + let target = pixel_clock_khz as u64; + let dco_target = target * 5; + + let div0 = (dco_target / refclk).min(63) as u32; + let remainder = dco_target - (div0 as u64 * refclk); + let div1_fraction = if remainder > 0 { + ((remainder * 0x10000 / refclk).min(0xFFFF)) as u32 + } else { 0 }; + + let mut val = self.mmio.read32(ctl1); + val &= !((0x3F << MG_PLL_DIV0_SHIFT) | (0xFF << MG_PLL_DIV1_SHIFT)); + val |= (div0 & 0x3F) << MG_PLL_DIV0_SHIFT; + val |= ((div1_fraction >> 8) & 0xFF) << MG_PLL_DIV1_SHIFT; + self.mmio.write32(ctl1, val); + + self.mmio.write32(ctl2, + (div0 & MG_PLL_DCO_INT_MASK) | ((div1_fraction & MG_PLL_DCO_FRAC_MASK) << 8)); + + self.frequency_khz = (refclk * div0 as u64 / 5) as u32; + debug!("redox-drm-intel: MG PLL {:?} configured for {} kHz", self.port, pixel_clock_khz); + Ok(()) + } + + pub fn disable(&mut self) -> Result<()> { + if !self.enabled { return Ok(()); } + let ctl1 = MG_PLL_CTL1_BASE + self.port.index() * MG_PLL_STRIDE; + let val = self.mmio.read32(ctl1); + self.mmio.write32(ctl1, val & !(MG_PLL_ENABLE | MG_PLL_POWER_ENABLE)); + self.enabled = false; + Ok(()) + } + + pub fn program_tdc(&self, tdc_val: u32) { + let tdc = MG_PLL_TDC_BASE + self.port.index() * MG_PLL_STRIDE; + self.mmio.write32(tdc, tdc_val); + } + + fn wait_lock(&self, reg: usize) -> Result<()> { + let deadline = Instant::now() + Duration::from_millis(MG_PLL_TIMEOUT_MS); + loop { + if self.mmio.read32(reg) & MG_PLL_LOCK != 0 { return Ok(()); } + if Instant::now() > deadline { + return Err(DriverError::Initialization(format!("MG PLL {:?} lock timeout", self.port))); + } + std::hint::spin_loop(); + } + } + + pub fn is_enabled(&self) -> bool { self.enabled } + pub fn frequency_khz(&self) -> u32 { self.frequency_khz } +}