diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/edp_pll.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/edp_pll.rs new file mode 100644 index 0000000000..c1810f2902 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/edp_pll.rs @@ -0,0 +1,109 @@ +use std::sync::Arc; + +use log::{debug, info}; +use redox_driver_sys::memory::MmioRegion; + +use crate::driver::{DriverError, Result}; + +// ── eDP PLL — Embedded DisplayPort PLL ────────────────────────────────── +// eDP uses a dedicated PLL separate from the WRPLL/LCPLL used for external +// DP/HDMI. The eDP PLL operates at a fixed frequency derived from the panel's +// native mode timing. Unlike external DP, eDP doesn't need link training +// adaptation — the panel runs at its native resolution only. + +const EDP_PLL_CTL: usize = 0x46400; +const EDP_PLL_CTL_ENABLE: u32 = 1 << 31; +const EDP_PLL_CTL_LOCK: u32 = 1 << 30; +const EDP_PLL_CTL_REFCLK_SHIFT: u32 = 24; +const EDP_PLL_CTL_DIV0_SHIFT: u32 = 16; +const EDP_PLL_CTL_DIV1_SHIFT: u32 = 8; + +const EDP_PLL_FREQ: usize = 0x46404; +const EDP_PLL_PSR: usize = 0x46408; +const EDP_PLL_TIMEOUT_MS: u64 = 10; + +pub struct EdpPll { + mmio: Arc, + enabled: bool, + frequency_khz: u32, + refclk_khz: u32, +} + +impl EdpPll { + pub fn new(mmio: Arc) -> Self { + Self { mmio, enabled: false, frequency_khz: 0, refclk_khz: 19200 } + } + + pub fn init(&mut self) -> Result<()> { + let ctl_val = self.mmio.read32(EDP_PLL_CTL); + if ctl_val & EDP_PLL_CTL_ENABLE == 0 { + self.mmio.write32(EDP_PLL_CTL, ctl_val | EDP_PLL_CTL_ENABLE); + self.wait_lock()?; + } + self.enabled = true; + self.frequency_khz = self.read_frequency(); + info!("redox-drm-intel: eDP PLL initialized at {} kHz", self.frequency_khz); + Ok(()) + } + + pub fn configure(&mut self, pixel_clock_khz: u32) -> Result<()> { + let refclk = self.refclk_khz as u64; + let target = pixel_clock_khz as u64 * 5; + let div0 = (target / refclk).min(63) as u32; + let remainder = target - (div0 as u64 * refclk); + let div1 = if remainder > 0 { ((remainder * 256 / refclk).min(255)) as u32 } else { 0 }; + + let mut ctl = self.mmio.read32(EDP_PLL_CTL); + ctl &= !((0x3F << EDP_PLL_CTL_DIV0_SHIFT) | (0xFF << EDP_PLL_CTL_DIV1_SHIFT)); + ctl |= (div0 & 0x3F) << EDP_PLL_CTL_DIV0_SHIFT; + ctl |= (div1 & 0xFF) << EDP_PLL_CTL_DIV1_SHIFT; + self.mmio.write32(EDP_PLL_CTL, ctl); + + self.frequency_khz = target as u32 / 5; + debug!("redox-drm-intel: eDP PLL configured for {} kHz (div0={}, div1={})", + pixel_clock_khz, div0, div1); + Ok(()) + } + + pub fn disable(&mut self) -> Result<()> { + if !self.enabled { return Ok(()); } + let ctl = self.mmio.read32(EDP_PLL_CTL); + self.mmio.write32(EDP_PLL_CTL, ctl & !EDP_PLL_CTL_ENABLE); + self.enabled = false; + Ok(()) + } + + pub fn enter_psr(&mut self) -> Result<()> { + let val = self.mmio.read32(EDP_PLL_PSR); + self.mmio.write32(EDP_PLL_PSR, val | (1 << 0)); + debug!("redox-drm-intel: eDP PLL entered PSR mode"); + Ok(()) + } + + pub fn exit_psr(&mut self) -> Result<()> { + let val = self.mmio.read32(EDP_PLL_PSR); + self.mmio.write32(EDP_PLL_PSR, val & !(1 << 0)); + debug!("redox-drm-intel: eDP PLL exited PSR mode"); + Ok(()) + } + + fn read_frequency(&self) -> u32 { + let val = self.mmio.read32(EDP_PLL_FREQ); + (val & 0xFFFF) as u32 * 1000 + } + + fn wait_lock(&self) -> Result<()> { + let deadline = std::time::Instant::now() + + std::time::Duration::from_millis(EDP_PLL_TIMEOUT_MS); + loop { + if self.mmio.read32(EDP_PLL_CTL) & EDP_PLL_CTL_LOCK != 0 { return Ok(()); } + if std::time::Instant::now() > deadline { + return Err(DriverError::Initialization("eDP PLL lock timeout")); + } + std::hint::spin_loop(); + } + } + + pub fn is_enabled(&self) -> bool { self.enabled } + pub fn frequency_khz(&self) -> u32 { self.frequency_khz } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi_scrambler.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi_scrambler.rs new file mode 100644 index 0000000000..89caa06cd8 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi_scrambler.rs @@ -0,0 +1,74 @@ +use std::sync::Arc; + +use log::{debug, info}; +use redox_driver_sys::memory::MmioRegion; + +use crate::driver::{DriverError, Result}; + +// ── HDMI TMDS Scrambling — HDMI 2.0+ ───────────────────────────────────── +// HDMI 2.0 increases TMDS clock from 3.4 Gbps to 6.0 Gbps per lane. +// Above 3.4 Gbps, TMDS scrambling is required to reduce EMI. +// The scrambler uses a 16-bit LFSR polynomial: G(x) = x^16 + x^5 + x^4 + x^3 + 1 +// Scrambling is controlled per DDI port through SCDC registers on the sink +// and DDI_HDMI_SCRAMBLER_CTL on the Intel source side. + +const HDMI_SCRAMBLER_CTL_BASE: usize = 0x64120; +const HDMI_SCRAMBLER_CTL_ENABLE: u32 = 1 << 31; +const HDMI_SCRAMBLER_FREQ_THRESHOLD: u32 = 3_400_000; + +const SCDC_SCRAMBLING_STATUS: u32 = 0x200; +const SCDC_TMDS_CONFIG: u32 = 0x200; +const SCDC_SCRAMBLING_ENABLE: u8 = 1 << 0; +const SCDC_SCRAMBLING_STATUS_OK: u8 = 1 << 0; + +pub struct HdmiScrambler { + mmio: Arc, + enabled: bool, + port: u8, + tmds_clock_khz: u32, + scrambling_active: bool, +} + +impl HdmiScrambler { + pub fn new(mmio: Arc, port: u8) -> Self { + Self { mmio, enabled: false, port, tmds_clock_khz: 0, scrambling_active: false } + } + + pub fn set_tmds_clock(&mut self, clock_khz: u32) { + self.tmds_clock_khz = clock_khz; + } + + pub fn needs_scrambling(&self) -> bool { + self.tmds_clock_khz > HDMI_SCRAMBLER_FREQ_THRESHOLD + } + + pub fn enable(&mut self) -> Result<()> { + if self.enabled || !self.needs_scrambling() { return Ok(()); } + + let ctl = HDMI_SCRAMBLER_CTL_BASE + self.port as usize * 0x100; + self.mmio.write32(ctl, self.mmio.read32(ctl) | HDMI_SCRAMBLER_CTL_ENABLE); + + self.enabled = true; + self.scrambling_active = true; + info!("redox-drm-intel: HDMI scrambling enabled on port {} (TMDS={} kHz)", + self.port, self.tmds_clock_khz); + Ok(()) + } + + pub fn disable(&mut self) -> Result<()> { + if !self.enabled { return Ok(()); } + let ctl = HDMI_SCRAMBLER_CTL_BASE + self.port as usize * 0x100; + self.mmio.write32(ctl, self.mmio.read32(ctl) & !HDMI_SCRAMBLER_CTL_ENABLE); + self.enabled = false; + self.scrambling_active = false; + Ok(()) + } + + pub fn get_scrambling_status(&self, aux: &super::dp_aux::DpAux) -> bool { + aux.read_dpcd(SCDC_SCRAMBLING_STATUS, 1) + .map_or(false, |d| !d.is_empty() && d[0] & SCDC_SCRAMBLING_STATUS_OK != 0) + } + + pub fn is_enabled(&self) -> bool { self.enabled } + pub fn is_active(&self) -> bool { self.scrambling_active } +}