intel: multi-generation power wells — Gen9 + Xe2
Rewrite display_power.rs to support both Gen9 (Skylake) and Xe2 (Arrow Lake/Battlemage) power well initialization. Gen9 path (unchanged): single POWER_WELL_CTL register at 0x45400 with bitmask for PW1/PW2/DDI_A-E/AUX_A-D domains. Xe2 path (new): multiple power well controllers: - HSW_PWR_WELL_CTL1 (0x45400) — PW1/PW2 per-index REQ/STATE - ICL_PWR_WELL_CTL_AUX1 (0x45440) — 4 AUX channels - ICL_PWR_WELL_CTL_DDI1 (0x45450) — 4 DDI ports - DC_STATE_EN (0x45504) — DC power state control Each well uses 2-bit per-index encoding (REQ=0x2, STATE=0x1). DisplayPower::new() now takes &IntelDeviceInfo to select generation-appropriate initialization path. Linux reference: intel_display_power_well.c (xelpdp_aux_power_well_*)
This commit is contained in:
@@ -4,73 +4,105 @@ use std::time::{Duration, Instant};
|
||||
use log::{debug, info, warn};
|
||||
use redox_driver_sys::memory::MmioRegion;
|
||||
|
||||
use super::info::IntelDeviceInfo;
|
||||
use super::regs::IntelRegs;
|
||||
use crate::driver::Result;
|
||||
use crate::driver::DriverError;
|
||||
|
||||
const POWER_WELL_TIMEOUT_MS: u64 = 20;
|
||||
|
||||
const HSW_PWR_WELL_CTL1: usize = 0x45400;
|
||||
const HSW_PWR_WELL_CTL2: usize = 0x45404;
|
||||
const ICL_PWR_WELL_CTL_AUX1: usize = 0x45440;
|
||||
const ICL_PWR_WELL_CTL_AUX2: usize = 0x45444;
|
||||
const ICL_PWR_WELL_CTL_DDI1: usize = 0x45450;
|
||||
const ICL_PWR_WELL_CTL_DDI2: usize = 0x45454;
|
||||
const DC_STATE_EN: usize = 0x45504;
|
||||
|
||||
const PW_REQ: u32 = 0x2;
|
||||
const PW_STATE: u32 = 0x1;
|
||||
const SKL_PW1_MASK: u32 = 1 << 0;
|
||||
const SKL_PW2_MASK: u32 = 1 << 1;
|
||||
const SKL_DDI_A_IO_MASK: u32 = 1 << 2;
|
||||
const SKL_DDI_B_IO_MASK: u32 = 1 << 3;
|
||||
const SKL_DDI_C_IO_MASK: u32 = 1 << 4;
|
||||
const SKL_DDI_D_IO_MASK: u32 = 1 << 5;
|
||||
const SKL_DDI_E_IO_MASK: u32 = 1 << 6;
|
||||
const SKL_AUX_A_MASK: u32 = 1 << 8;
|
||||
const SKL_AUX_B_MASK: u32 = 1 << 9;
|
||||
const SKL_AUX_C_MASK: u32 = 1 << 10;
|
||||
const SKL_AUX_D_MASK: u32 = 1 << 11;
|
||||
|
||||
const DISPLAY_REQUIRED_WELLS: u32 =
|
||||
SKL_PW1_MASK
|
||||
| SKL_PW2_MASK
|
||||
| SKL_DDI_A_IO_MASK
|
||||
| SKL_DDI_B_IO_MASK
|
||||
| SKL_DDI_C_IO_MASK
|
||||
| SKL_DDI_D_IO_MASK
|
||||
| SKL_DDI_E_IO_MASK
|
||||
| SKL_AUX_A_MASK
|
||||
| SKL_AUX_B_MASK
|
||||
| SKL_AUX_C_MASK
|
||||
| SKL_AUX_D_MASK;
|
||||
const SKL_DDI_MASK: u32 = (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
|
||||
const SKL_AUX_MASK: u32 = (1 << 8) | (1 << 9) | (1 << 10) | (1 << 11);
|
||||
const SKL_ALL_WELLS: u32 = SKL_PW1_MASK | SKL_PW2_MASK | SKL_DDI_MASK | SKL_AUX_MASK;
|
||||
|
||||
pub struct DisplayPower {
|
||||
mmio: Arc<MmioRegion>,
|
||||
regs: &'static dyn IntelRegs,
|
||||
is_xe2: bool,
|
||||
}
|
||||
|
||||
impl DisplayPower {
|
||||
pub fn new(mmio: Arc<MmioRegion>, regs: &'static dyn IntelRegs) -> Self {
|
||||
Self { mmio, regs }
|
||||
pub fn new(mmio: Arc<MmioRegion>, regs: &'static dyn IntelRegs, device_info: &IntelDeviceInfo) -> Self {
|
||||
let is_xe2 = device_info.generation == super::info::IntelGeneration::GenXe2;
|
||||
Self { mmio, regs, is_xe2 }
|
||||
}
|
||||
|
||||
pub fn init_domains(&self) -> Result<()> {
|
||||
info!("redox-drm-intel: enabling display power wells");
|
||||
if self.is_xe2 {
|
||||
self.init_xe2_domains()
|
||||
} else {
|
||||
self.init_gen9_domains()
|
||||
}
|
||||
}
|
||||
|
||||
fn init_gen9_domains(&self) -> Result<()> {
|
||||
info!("redox-drm-intel: enabling Gen9 display power wells");
|
||||
let ctl = self.regs.power_well_ctl();
|
||||
let current = self.mmio.read_u32(ctl);
|
||||
|
||||
if current & DISPLAY_REQUIRED_WELLS == DISPLAY_REQUIRED_WELLS {
|
||||
debug!("redox-drm-intel: display power wells already enabled ({:#010x})", current);
|
||||
if current & SKL_ALL_WELLS == SKL_ALL_WELLS {
|
||||
debug!("redox-drm-intel: power wells already enabled ({:#010x})", current);
|
||||
return Ok(());
|
||||
}
|
||||
self.mmio.write_u32(ctl, current | SKL_ALL_WELLS);
|
||||
self.poll_well(ctl, SKL_ALL_WELLS, "Gen9")
|
||||
}
|
||||
|
||||
self.mmio.write_u32(ctl, current | DISPLAY_REQUIRED_WELLS);
|
||||
fn init_xe2_domains(&self) -> Result<()> {
|
||||
info!("redox-drm-intel: enabling Xe2 display power wells");
|
||||
|
||||
self.enable_xe2_well(HSW_PWR_WELL_CTL1, 0, "PW1")?;
|
||||
self.enable_xe2_well(HSW_PWR_WELL_CTL1, 1, "PW2")?;
|
||||
|
||||
self.mmio.write_u32(ICL_PWR_WELL_CTL_AUX1,
|
||||
PW_REQ << 0 | PW_REQ << 2 | PW_REQ << 4 | PW_REQ << 6);
|
||||
self.mmio.write_u32(ICL_PWR_WELL_CTL_DDI1,
|
||||
PW_REQ << 0 | PW_REQ << 2 | PW_REQ << 4 | PW_REQ << 6);
|
||||
|
||||
self.poll_well(ICL_PWR_WELL_CTL_AUX1,
|
||||
PW_STATE | PW_STATE << 2 | PW_STATE << 4 | PW_STATE << 6,
|
||||
"Xe2 AUX")?;
|
||||
|
||||
self.mmio.write_u32(DC_STATE_EN, self.mmio.read_u32(DC_STATE_EN) | 0x1);
|
||||
info!("redox-drm-intel: Xe2 display power domains enabled");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enable_xe2_well(&self, reg: usize, idx: u32, name: &str) -> Result<()> {
|
||||
let req_bit = PW_REQ << (idx * 2);
|
||||
let state_bit = PW_STATE << (idx * 2);
|
||||
let current = self.mmio.read_u32(reg);
|
||||
if current & state_bit != 0 {
|
||||
debug!("redox-drm-intel: {} power well already enabled", name);
|
||||
return Ok(());
|
||||
}
|
||||
self.mmio.write_u32(reg, current | req_bit);
|
||||
self.poll_well(reg, state_bit, name)
|
||||
}
|
||||
|
||||
fn poll_well(&self, reg: usize, mask: u32, name: &str) -> Result<()> {
|
||||
let deadline = Instant::now() + Duration::from_millis(POWER_WELL_TIMEOUT_MS);
|
||||
loop {
|
||||
let status = self.mmio.read_u32(ctl);
|
||||
if status & DISPLAY_REQUIRED_WELLS == DISPLAY_REQUIRED_WELLS {
|
||||
info!("redox-drm-intel: display power wells enabled ({:#010x})", status);
|
||||
let status = self.mmio.read_u32(reg);
|
||||
if status & mask == mask {
|
||||
debug!("redox-drm-intel: {} power well ready ({:#010x})", name, status);
|
||||
return Ok(());
|
||||
}
|
||||
if Instant::now() > deadline {
|
||||
warn!("redox-drm-intel: power well enable timeout — current: {:#010x}, expected: {:#010x}",
|
||||
status, DISPLAY_REQUIRED_WELLS);
|
||||
warn!("redox-drm-intel: {} power well timeout: {:#010x}", name, status);
|
||||
return Err(DriverError::Other(format!(
|
||||
"power well enable timeout: {:#010x} vs {:#010x}",
|
||||
status, DISPLAY_REQUIRED_WELLS
|
||||
"{} power well timeout: {:#010x}", name, status
|
||||
)));
|
||||
}
|
||||
std::hint::spin_loop();
|
||||
@@ -78,8 +110,14 @@ impl DisplayPower {
|
||||
}
|
||||
|
||||
pub fn is_display_ready(&self) -> bool {
|
||||
let ctl = self.regs.power_well_ctl();
|
||||
let status = self.mmio.read_u32(ctl);
|
||||
status & DISPLAY_REQUIRED_WELLS == DISPLAY_REQUIRED_WELLS
|
||||
if self.is_xe2 {
|
||||
let pw1 = self.mmio.read_u32(HSW_PWR_WELL_CTL1);
|
||||
let aux = self.mmio.read_u32(ICL_PWR_WELL_CTL_AUX1);
|
||||
(pw1 & PW_STATE) != 0 && (aux & PW_STATE) != 0
|
||||
} else {
|
||||
let ctl = self.regs.power_well_ctl();
|
||||
let status = self.mmio.read_u32(ctl);
|
||||
status & SKL_ALL_WELLS == SKL_ALL_WELLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ impl IntelDriver {
|
||||
None
|
||||
};
|
||||
|
||||
let display_power = DisplayPower::new(mmio_arc.clone(), regs);
|
||||
let display_power = DisplayPower::new(mmio_arc.clone(), regs, &device_info);
|
||||
display_power.init_domains()?;
|
||||
|
||||
let dmc = DmcFirmware::new(mmio_arc.clone(), regs);
|
||||
|
||||
+1
-1
Submodule local/sources/base updated: 4327f0b48b...12eb4be1c7
Reference in New Issue
Block a user