intel: eDP PLL + HDMI TMDS scrambling — ported from Linux 7.1
edp_pll.rs (110 lines): eDP dedicated PLL for laptop panels Fixed-frequency PLL (no link training adaptation needed) Div0/div1 fractional divider computation PSR mode entry/exit (PLL power state for panel self refresh) Lock polling with timeout hdmi_scrambler.rs (80 lines): HDMI 2.0 TMDS scrambling 16-bit LFSR: G(x) = x^16 + x^5 + x^4 + x^3 + 1 Auto-enable above 3.4 Gbps TMDS threshold SCDC scrambling status readback via DP AUX Per-DDI port scrambling control Ported from Linux 7.1: intel_dpll_mgr.c eDP path → EdpPll intel_hdmi.c scrambling path → HdmiScrambler Intel driver: 87 files, 0 errors
This commit is contained in:
@@ -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<MmioRegion>,
|
||||
enabled: bool,
|
||||
frequency_khz: u32,
|
||||
refclk_khz: u32,
|
||||
}
|
||||
|
||||
impl EdpPll {
|
||||
pub fn new(mmio: Arc<MmioRegion>) -> 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 }
|
||||
}
|
||||
@@ -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<MmioRegion>,
|
||||
enabled: bool,
|
||||
port: u8,
|
||||
tmds_clock_khz: u32,
|
||||
scrambling_active: bool,
|
||||
}
|
||||
|
||||
impl HdmiScrambler {
|
||||
pub fn new(mmio: Arc<MmioRegion>, 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user