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:
2026-05-30 08:22:43 +03:00
parent 9698efe138
commit 58f8e8c6a7
3 changed files with 79 additions and 41 deletions
@@ -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);
Submodule local/sources/base updated: 4327f0b48b...12eb4be1c7