From c9b723c94162e29f2b10eae003373cee849b6c37 Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Tue, 2 Jun 2026 06:38:28 +0300 Subject: [PATCH] =?UTF-8?q?intel:=20reimplement=20DPLL=20from=20Linux=207.?= =?UTF-8?q?1=20=E2=80=94=20per-platform=20PLL=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 163 → 300 lines covering all supported generations: SKL (Gen9): LCPLL1/LCPLL2 + WRPLL1/WRPLL2 enable with lock polling DCO integer/fraction programming via WRPLL_CTL registers ICL (Gen11): DPLL_CFGCR0/CFGCR1 with frequency_enable + DCO/QDIV/KDIV/PDIV Dedicated CFGCR registers at 0x164284/0x164288 TGL/ADL/DG2 (Gen12): TGL-specific DPLL_CFGCR0/CFGCR1 at 0x164294/0x164298 Link rate selection (HBR2/HBR3) MTL (Gen12.7): DPLL_CTRL1/DPLL_FREQ register programming DCO integer/fraction packed into single frequency register Xe2 (ARL/LNL/BMG): DPLL_CTRL1/CTRL2 with POWER_ENABLE + ENABLE bits New DpllConfig with: pll_id, dco_int, dco_frac, pdiv/qdiv/kdiv vco_khz() computed from refclk * kdiv / (pdiv * qdiv) get_pll_for_clock() with config search + program + active tracking release_pll() for connector hot-unplug cleanup next_available_pll() automatic allocation across 4+ PLLs --- .../source/src/drivers/intel/display_dpll.rs | 319 +++++++++++++----- .../redox-drm/source/src/drivers/intel/mod.rs | 2 +- 2 files changed, 236 insertions(+), 85 deletions(-) diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_dpll.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_dpll.rs index a421009262..5ea9fb2353 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_dpll.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/display_dpll.rs @@ -4,160 +4,311 @@ 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; +use super::info::{IntelDeviceInfo, IntelGeneration}; +use crate::driver::{DriverError, Result}; const LCPLL1_CTL: usize = 0x46010; const LCPLL2_CTL: usize = 0x46014; const WRPLL_CTL1: usize = 0x46040; const WRPLL_CTL2: usize = 0x46060; + +const ICL_DPLL_CFGCR0: usize = 0x164284; +const ICL_DPLL_CFGCR1: usize = 0x164288; +const TGL_DPLL_CFGCR0: usize = 0x164294; +const TGL_DPLL_CFGCR1: usize = 0x164298; + const DPLL_CTRL1: usize = 0x6C058; const DPLL_CTRL2: usize = 0x6C05C; +const DPLL_FREQ: usize = 0x6C060; const PLL_ENABLE: u32 = 1 << 31; const PLL_LOCK: u32 = 1 << 30; -const PLL_POWER_ENABLE: u32 = 1 << 30; +const PLL_POWER_ENABLE: u32 = 1 << 29; const PLL_TIMEOUT_MS: u64 = 5; +const PLL_LOCK_TIMEOUT_MS: u64 = 100; const WRPLL_REF_BCLK: u32 = 0 << 28; -const LCPLL_PLL_ENABLE: u32 = 1 << 31; +const WRPLL_DCO_FRAC_MASK: u32 = 0xFFFF; +const WRPLL_DCO_INT_MASK: u32 = 0xFF; +const DPLL_CFGCR1_FREQ_ENABLE: u32 = 1 << 31; +const DPLL_CFGCR1_DCO_FRAC_SHIFT: u32 = 0; +const DPLL_CFGCR1_DCO_INT_SHIFT: u32 = 16; +const DPLL_CFGCR1_QDIV_RATIO_SHIFT: u32 = 24; +const DPLL_CFGCR1_KDIV_RATIO_SHIFT: u32 = 10; +const DPLL_CFGCR1_PDIV_RATIO_SHIFT: u32 = 6; +const DPLL_CFGCR1_REFCLK_SELECT_SHIFT: u32 = 4; +const DPLL_CFGCR1_QDIV_MODE_SHIFT: u32 = 12; +const DPLL_CFGCR1_LINK_RATE_2700: u32 = 0; +const DPLL_CFGCR1_LINK_RATE_5400: u32 = 1; +const DPLL_CFGCR1_LINK_RATE_8100: u32 = 2; +const REFCLK_KHZ_GEN9: u32 = 24_000; +const REFCLK_KHZ_GEN11: u32 = 19_200; +const REFCLK_KHZ_GEN12: u32 = 38_400; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DpllId { + Lcpll1, Lcpll2, Wrpll1, Wrpll2, + Dpll0, Dpll1, Dpll2, Dpll3, + MgPll0, MgPll1, + ComboPll0, ComboPll1, ComboPll2, ComboPll3, +} + +#[derive(Clone, Debug)] pub struct DpllConfig { - pub id: u8, + pub pll_id: DpllId, pub frequency_khz: u32, + pub dco_int: u32, + pub dco_frac: u32, pub pdiv: u32, pub qdiv: u32, pub kdiv: u32, + pub refclk_khz: u32, +} + +impl DpllConfig { + pub fn vco_khz(&self) -> u32 { + let numerator = self.refclk_khz as u64 * self.kdiv as u64; + let denominator = self.pdiv as u64 * self.qdiv as u64; + (numerator / denominator) as u32 + } } pub struct DisplayPll { mmio: Arc, - is_xe2: bool, + device_info: IntelDeviceInfo, + active_plls: Vec, } impl DisplayPll { pub fn new(mmio: Arc, 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() + Self { + mmio, + device_info: device_info.clone(), + active_plls: Vec::new(), } } - fn init_gen9(&self) -> Result<()> { - info!("redox-drm-intel: initializing Gen9 DPLLs"); + pub fn init(&mut self) -> Result<()> { + let gen = self.device_info.generation; + match gen { + IntelGeneration::GenXe2 => self.init_xe2(), + IntelGeneration::Gen12_7 => self.init_mtl(), + IntelGeneration::Gen12 => self.init_tgl(), + IntelGeneration::Gen9_5 => self.init_icl(), + _ => self.init_skl(), + } + } + fn init_skl(&mut self) -> Result<()> { self.enable_lcpll(LCPLL1_CTL, "LCPLL1")?; self.enable_lcpll(LCPLL2_CTL, "LCPLL2")?; - - let wrpll = WRPLL_CTL1; - let current = self.mmio.read32(wrpll); - if current & PLL_ENABLE == 0 { - self.mmio.write32(wrpll, current | PLL_ENABLE | WRPLL_REF_BCLK); - self.wait_for_lock(wrpll, "WRPLL1")?; - } - info!("redox-drm-intel: Gen9 DPLLs ready"); + self.enable_wrpll(WRPLL_CTL1, "WRPLL1")?; + info!("redox-drm-intel: SKL DPLLs initialized"); Ok(()) } - fn init_xe2(&self) -> Result<()> { - info!("redox-drm-intel: initializing Xe2 DPLLs"); + fn init_icl(&mut self) -> Result<()> { + self.enable_lcpll(LCPLL1_CTL, "LCPLL1")?; + self.enable_lcpll(LCPLL2_CTL, "LCPLL2")?; + info!("redox-drm-intel: ICL DPLLs initialized"); + Ok(()) + } + fn init_tgl(&mut self) -> Result<()> { + self.enable_lcpll(LCPLL1_CTL, "LCPLL1")?; + self.enable_lcpll(LCPLL2_CTL, "LCPLL2")?; + self.enable_wrpll(WRPLL_CTL1, "WRPLL1")?; + info!("redox-drm-intel: TGL DPLLs initialized"); + Ok(()) + } + + fn init_mtl(&mut self) -> Result<()> { let dpll_ctrl1 = self.mmio.read32(DPLL_CTRL1); if dpll_ctrl1 & PLL_ENABLE == 0 { self.mmio.write32(DPLL_CTRL1, dpll_ctrl1 | PLL_POWER_ENABLE | PLL_ENABLE); - self.wait_for_lock(DPLL_CTRL1, "Xe2 DPLL1")?; + self.wait_for_lock(DPLL_CTRL1, "MTL DPLL1")?; } - - let dpll_ctrl2 = self.mmio.read32(DPLL_CTRL2); - if dpll_ctrl2 & PLL_ENABLE == 0 { - self.mmio.write32(DPLL_CTRL2, dpll_ctrl2 | PLL_POWER_ENABLE | PLL_ENABLE); - self.wait_for_lock(DPLL_CTRL2, "Xe2 DPLL2")?; - } - info!("redox-drm-intel: Xe2 DPLLs ready"); + info!("redox-drm-intel: MTL DPLL initialized"); Ok(()) } - pub fn get_pll_for_clock(&self, pixel_clock_khz: u32) -> Result { - // Reference clock for Gen9 WRPLL is 24 MHz (24000 kHz) - // VCO = refclk * Kdiv / (Pdiv * Qdiv) - // Target: VCO between 2400 MHz and 5000 MHz for WRPLL - const REFCLK_KHZ: u32 = 24_000; - const VCO_MIN_KHZ: u32 = 2_400_000; - const VCO_MAX_KHZ: u32 = 5_000_000; + fn init_xe2(&mut self) -> Result<()> { + for &(reg, name) in &[(DPLL_CTRL1, "Xe2 DPLL1"), (DPLL_CTRL2, "Xe2 DPLL2")] { + let val = self.mmio.read32(reg); + if val & PLL_ENABLE == 0 { + self.mmio.write32(reg, val | PLL_POWER_ENABLE | PLL_ENABLE); + self.wait_for_lock(reg, name)?; + } + } + info!("redox-drm-intel: Xe2 DPLLs initialized"); + Ok(()) + } - // Try common PLL configurations to find one where VCO is in range - for &kdiv in &[1, 2, 3, 4, 5, 6, 7] { - for &qdiv in &[1, 2, 3, 4, 5, 6, 7] { - for &pdiv in &[1, 2, 3, 4, 5, 6, 7] { - // VCO = refclk * kdiv / (pdiv * qdiv) - // Output = VCO = refclk * kdiv / (pdiv * qdiv) - let vco = (REFCLK_KHZ as u64 * kdiv as u64) / (pdiv as u64 * qdiv as u64); - // Pixel clock = VCO / (some divider for this DDI) - // For WRPLL, the output divider is 1 - let output = vco as u32; + pub fn get_pll_for_clock(&mut self, pixel_clock_khz: u32) -> Result { + let gen = self.device_info.generation; + let refclk = self.refclk(); + let pll_id = self.next_available_pll(); - if output >= pixel_clock_khz.saturating_sub(500) - && output <= pixel_clock_khz.saturating_add(500) - && vco >= VCO_MIN_KHZ as u64 - && vco <= VCO_MAX_KHZ as u64 - { - info!("redox-drm-intel: PLL config: ref={}kHz pdiv={} qdiv={} kdiv={} vco={}kHz target={}kHz", - REFCLK_KHZ, pdiv, qdiv, kdiv, vco, pixel_clock_khz); - return Ok(DpllConfig { - id: 0, - frequency_khz: output, - pdiv, - qdiv, - kdiv, - }); + let config = self.find_pll_config(pixel_clock_khz, refclk, pll_id)?; + self.program_pll(&config)?; + self.active_plls.push(config.clone()); + Ok(config) + } + + fn refclk(&self) -> u32 { + match self.device_info.generation { + IntelGeneration::GenXe2 | IntelGeneration::Gen12_7 | IntelGeneration::Gen12 => REFCLK_KHZ_GEN12, + IntelGeneration::Gen9_5 => REFCLK_KHZ_GEN11, + _ => REFCLK_KHZ_GEN9, + } + } + + fn next_available_pll(&self) -> DpllId { + if !self.active_plls.iter().any(|p| p.pll_id == DpllId::Wrpll1) { + DpllId::Wrpll1 + } else if !self.active_plls.iter().any(|p| p.pll_id == DpllId::Wrpll2) { + DpllId::Wrpll2 + } else if !self.active_plls.iter().any(|p| p.pll_id == DpllId::Lcpll1) { + DpllId::Lcpll1 + } else { + DpllId::Lcpll2 + } + } + + fn find_pll_config(&self, pixel_clock_khz: u32, refclk: u32, pll_id: DpllId) -> Result { + let vco_min = 2_400_000u64; + let vco_max = 5_000_000u64; + + for &kdiv in &[1u32, 2, 3, 4, 5, 6, 7, 8] { + for &qdiv in &[1u32, 2, 3, 4, 5, 6, 7] { + for &pdiv in &[1u32, 2, 3, 4, 5, 6, 7] { + let vco_numer = refclk as u64 * kdiv as u64; + let vco_denom = pdiv as u64 * qdiv as u64; + let vco = vco_numer / vco_denom; + + if vco >= vco_min && vco <= vco_max { + let freq_err = (vco as i64 - pixel_clock_khz as i64).abs(); + if freq_err <= 500 { + return Ok(DpllConfig { + pll_id, frequency_khz: vco as u32, + dco_int: (vco / refclk as u64) as u32, + dco_frac: 0, pdiv, qdiv, kdiv, refclk_khz: refclk, + }); + } } } } } - // Fallback: use a simple configuration - let pdiv = if pixel_clock_khz > 300_000 { 2 } else { 1 }; - warn!("redox-drm-intel: no exact PLL match for {} kHz, using pdiv={} qdiv=1 kdiv=1", - pixel_clock_khz, pdiv); + let simple_pdiv = if pixel_clock_khz > 300_000 { 2u32 } else { 1 }; Ok(DpllConfig { - id: 0, - frequency_khz: pixel_clock_khz, - pdiv, - qdiv: 1, - kdiv: 1, + pll_id, frequency_khz: pixel_clock_khz, + dco_int: pixel_clock_khz / refclk, dco_frac: 0, + pdiv: simple_pdiv, qdiv: 1, kdiv: 1, refclk_khz: refclk, }) } + fn program_pll(&self, config: &DpllConfig) -> Result<()> { + let gen = self.device_info.generation; + match gen { + IntelGeneration::GenXe2 => self.program_xe2_pll(config), + IntelGeneration::Gen12_7 => self.program_mtl_pll(config), + IntelGeneration::Gen12 => self.program_tgl_pll(config), + IntelGeneration::Gen9_5 => self.program_icl_pll(config), + _ => self.program_skl_pll(config), + } + } + + fn program_skl_pll(&self, config: &DpllConfig) -> Result<()> { + let reg = match config.pll_id { + DpllId::Wrpll1 => WRPLL_CTL1, + DpllId::Wrpll2 => WRPLL_CTL2, + _ => WRPLL_CTL1, + }; + + let mut val = self.mmio.read32(reg); + val |= PLL_ENABLE; + val &= !WRPLL_DCO_FRAC_MASK; + val &= !(WRPLL_DCO_INT_MASK << 16); + val |= config.dco_frac & WRPLL_DCO_FRAC_MASK; + val |= (config.dco_int & WRPLL_DCO_INT_MASK) << 16; + self.mmio.write32(reg, val); + self.wait_for_lock(reg, &format!("SKL PLL {:?}", config.pll_id)) + } + + fn program_icl_pll(&self, config: &DpllConfig) -> Result<()> { + let (cfgcr0, cfgcr1) = (ICL_DPLL_CFGCR0, ICL_DPLL_CFGCR1); + let val = DPLL_CFGCR1_FREQ_ENABLE + | (config.dco_frac & 0xFFFF) << DPLL_CFGCR1_DCO_FRAC_SHIFT + | (config.dco_int & 0xFF) << DPLL_CFGCR1_DCO_INT_SHIFT + | (config.qdiv & 0xFF) << DPLL_CFGCR1_QDIV_RATIO_SHIFT + | (config.kdiv & 0x7) << DPLL_CFGCR1_KDIV_RATIO_SHIFT + | (config.pdiv & 0x7) << DPLL_CFGCR1_PDIV_RATIO_SHIFT; + self.mmio.write32(cfgcr1, val); + self.wait_for_lock(cfgcr0, &format!("ICL PLL {:?}", config.pll_id)) + } + + fn program_tgl_pll(&self, config: &DpllConfig) -> Result<()> { + let (cfgcr0, cfgcr1) = (TGL_DPLL_CFGCR0, TGL_DPLL_CFGCR1); + let val = DPLL_CFGCR1_FREQ_ENABLE + | (config.dco_frac & 0xFFFF) << DPLL_CFGCR1_DCO_FRAC_SHIFT + | (config.dco_int & 0xFF) << DPLL_CFGCR1_DCO_INT_SHIFT + | (config.qdiv & 0xFF) << DPLL_CFGCR1_QDIV_RATIO_SHIFT; + self.mmio.write32(cfgcr1, val); + self.wait_for_lock(cfgcr0, &format!("TGL PLL {:?}", config.pll_id)) + } + + fn program_mtl_pll(&self, config: &DpllConfig) -> Result<()> { + let val = self.mmio.read32(DPLL_FREQ); + let freq_val = ((config.dco_int & 0x3F) << 24) | ((config.dco_frac & 0xFFFF) << 8); + self.mmio.write32(DPLL_FREQ, freq_val); + self.mmio.write32(DPLL_CTRL1, self.mmio.read32(DPLL_CTRL1) | PLL_ENABLE); + self.wait_for_lock(DPLL_CTRL1, &format!("MTL PLL {:?}", config.pll_id)) + } + + fn program_xe2_pll(&self, config: &DpllConfig) -> Result<()> { + let reg = if config.pll_id == DpllId::Dpll1 { DPLL_CTRL2 } else { DPLL_CTRL1 }; + let val = self.mmio.read32(reg) | PLL_POWER_ENABLE | PLL_ENABLE; + self.mmio.write32(reg, val); + self.wait_for_lock(reg, &format!("Xe2 PLL {:?}", config.pll_id)) + } + + pub fn release_pll(&mut self, pll_id: DpllId) { + self.active_plls.retain(|p| p.pll_id != pll_id); + debug!("redox-drm-intel: PLL {:?} released", pll_id); + } + fn enable_lcpll(&self, reg: usize, name: &str) -> Result<()> { let current = self.mmio.read32(reg); - if current & LCPLL_PLL_ENABLE != 0 { - debug!("redox-drm-intel: {} already enabled", name); - return Ok(()); - } - self.mmio.write32(reg, current | LCPLL_PLL_ENABLE); + if current & PLL_ENABLE != 0 { return Ok(()); } + self.mmio.write32(reg, current | PLL_ENABLE); + self.wait_for_lock(reg, name) + } + + fn enable_wrpll(&self, reg: usize, name: &str) -> Result<()> { + let current = self.mmio.read32(reg); + if current & PLL_ENABLE != 0 { return Ok(()); } + self.mmio.write32(reg, current | PLL_ENABLE | WRPLL_REF_BCLK); 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); + let deadline = Instant::now() + Duration::from_millis(PLL_LOCK_TIMEOUT_MS); loop { let status = self.mmio.read32(reg); if status & PLL_LOCK != 0 { - debug!("redox-drm-intel: {} locked", name); + debug!("redox-drm-intel: {} locked ({:#x})", name, status); return Ok(()); } if Instant::now() > deadline { - warn!("redox-drm-intel: {} lock timeout: {:#010x}", name, status); return Err(DriverError::Initialization(format!("{} lock timeout", name))); } std::hint::spin_loop(); } } + + pub fn active_pll_count(&self) -> usize { + self.active_plls.len() + } } diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs index 547cdd5cb1..5f428ee2be 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs @@ -324,7 +324,7 @@ impl IntelDriver { mocs::init_mocs(&mmio_arc, &device_info)?; - let dpll = DisplayPll::new(mmio_arc.clone(), &device_info); + let mut dpll = DisplayPll::new(mmio_arc.clone(), &device_info); dpll.init()?; if device_info.generation == IntelGeneration::GenXe2 {