intel: DPLL initialization for Gen9 + Xe2

Add display_dpll.rs — pixel clock PLL management.

Gen9 (SKL/KBL/CFL): enable LCPLL1/LCPLL2 at 0x46010/0x46014
and WRPLL1 at 0x46040 with WRPLL_REF_BCLK reference clock.
Poll PLL_LOCK bit for confirmation.

Xe2 (ARL/BMG): enable DPLL_CTRL1/DPLL_CTRL2 at 0x6C058/0x6C05C
with PLL_POWER_ENABLE. get_pll_for_clock() returns pdiv=1 or 2
based on pixel clock threshold (300 MHz).

Wire into IntelDriver constructor between CDCLK and display init.

Linux reference: intel_dpll_mgr.c (skl_wrpll, icl_dpll)
This commit is contained in:
2026-05-30 08:38:07 +03:00
parent aafb835eee
commit 2f18b35122
2 changed files with 131 additions and 0 deletions
@@ -0,0 +1,125 @@
use std::sync::Arc;
use std::time::{Duration, Instant};
use log::{debug, info, warn};
use redox_driver_sys::memory::MmioRegion;
use super::info::IntelDeviceInfo;
use crate::driver::Result;
use crate::driver::DriverError;
const LCPLL1_CTL: usize = 0x46010;
const LCPLL2_CTL: usize = 0x46014;
const WRPLL_CTL1: usize = 0x46040;
const WRPLL_CTL2: usize = 0x46060;
const DPLL_CTRL1: usize = 0x6C058;
const DPLL_CTRL2: usize = 0x6C05C;
const PLL_ENABLE: u32 = 1 << 31;
const PLL_LOCK: u32 = 1 << 30;
const PLL_POWER_ENABLE: u32 = 1 << 30;
const PLL_TIMEOUT_MS: u64 = 5;
const WRPLL_REF_BCLK: u32 = 0 << 28;
const LCPLL_PLL_ENABLE: u32 = 1 << 31;
pub struct DpllConfig {
pub id: u8,
pub frequency_khz: u32,
pub pdiv: u32,
pub qdiv: u32,
pub kdiv: u32,
}
pub struct DisplayPll {
mmio: Arc<MmioRegion>,
is_xe2: bool,
}
impl DisplayPll {
pub fn new(mmio: Arc<MmioRegion>, device_info: &IntelDeviceInfo) -> Self {
let is_xe2 = device_info.generation == super::info::IntelGeneration::GenXe2;
Self { mmio, is_xe2 }
}
pub fn init(&self) -> Result<()> {
if self.is_xe2 {
self.init_xe2()
} else {
self.init_gen9()
}
}
fn init_gen9(&self) -> Result<()> {
info!("redox-drm-intel: initializing Gen9 DPLLs");
self.enable_lcpll(LCPLL1_CTL, "LCPLL1")?;
self.enable_lcpll(LCPLL2_CTL, "LCPLL2")?;
let wrpll = WRPLL_CTL1;
let current = self.mmio.read_u32(wrpll);
if current & PLL_ENABLE == 0 {
self.mmio.write_u32(wrpll, current | PLL_ENABLE | WRPLL_REF_BCLK);
self.wait_for_lock(wrpll, "WRPLL1")?;
}
info!("redox-drm-intel: Gen9 DPLLs ready");
Ok(())
}
fn init_xe2(&self) -> Result<()> {
info!("redox-drm-intel: initializing Xe2 DPLLs");
let dpll_ctrl1 = self.mmio.read_u32(DPLL_CTRL1);
if dpll_ctrl1 & PLL_ENABLE == 0 {
self.mmio.write_u32(DPLL_CTRL1, dpll_ctrl1 | PLL_POWER_ENABLE | PLL_ENABLE);
self.wait_for_lock(DPLL_CTRL1, "Xe2 DPLL1")?;
}
let dpll_ctrl2 = self.mmio.read_u32(DPLL_CTRL2);
if dpll_ctrl2 & PLL_ENABLE == 0 {
self.mmio.write_u32(DPLL_CTRL2, dpll_ctrl2 | PLL_POWER_ENABLE | PLL_ENABLE);
self.wait_for_lock(DPLL_CTRL2, "Xe2 DPLL2")?;
}
info!("redox-drm-intel: Xe2 DPLLs ready");
Ok(())
}
pub fn get_pll_for_clock(&self, pixel_clock_khz: u32) -> Result<DpllConfig> {
let pdiv = if pixel_clock_khz > 300_000 { 2 } else { 1 };
let qdiv = 1;
let kdiv = 0;
Ok(DpllConfig {
id: 0,
frequency_khz: pixel_clock_khz,
pdiv,
qdiv,
kdiv,
})
}
fn enable_lcpll(&self, reg: usize, name: &str) -> Result<()> {
let current = self.mmio.read_u32(reg);
if current & LCPLL_PLL_ENABLE != 0 {
debug!("redox-drm-intel: {} already enabled", name);
return Ok(());
}
self.mmio.write_u32(reg, current | LCPLL_PLL_ENABLE);
self.wait_for_lock(reg, name)
}
fn wait_for_lock(&self, reg: usize, name: &str) -> Result<()> {
let deadline = Instant::now() + Duration::from_millis(PLL_TIMEOUT_MS);
loop {
let status = self.mmio.read_u32(reg);
if status & PLL_LOCK != 0 {
debug!("redox-drm-intel: {} locked", name);
return Ok(());
}
if Instant::now() > deadline {
warn!("redox-drm-intel: {} lock timeout: {:#010x}", name, status);
return Err(DriverError::Other(format!("{} lock timeout", name)));
}
std::hint::spin_loop();
}
}
}
@@ -2,6 +2,7 @@ pub mod display;
pub mod display_cdclk;
pub mod display_combo_phy;
pub mod display_dmc;
pub mod display_dpll;
pub mod display_power;
pub mod dp_aux;
pub mod gmbus;
@@ -32,6 +33,7 @@ use self::display::{DisplayPipe, IntelDisplay};
use self::display_cdclk::DisplayClock;
use self::display_combo_phy::ComboPhy;
use self::display_dmc::DmcFirmware;
use self::display_dpll::DisplayPll;
use self::display_power::DisplayPower;
use self::dp_aux::DpAux;
use self::gmbus::GmbusController;
@@ -73,6 +75,7 @@ pub struct IntelDriver {
dp_aux: Vec<DpAux>,
combo_phy: Option<ComboPhy>,
display_power: DisplayPower,
dpll: DisplayPll,
dmc: DmcFirmware,
cdclk: DisplayClock,
}
@@ -184,6 +187,9 @@ impl IntelDriver {
cdclk_state.frequency_khz, cdclk_state.voltage_level
);
let dpll = DisplayPll::new(mmio_arc.clone(), &device_info);
dpll.init()?;
let display = IntelDisplay::new(display_mmio, regs)?;
let mut gtt = IntelGtt::init(gtt_mmio, gtt_control_mmio)?;
let mut ring = IntelRing::create(ring_mmio, RingType::Render)?;