intel: FBC, DRRS, PSR2, Gen4-7 regs — comprehensive subsystems

fbc.rs: Frame Buffer Compression
  FBC_CTL enable/compression/fence programming (Gen7+)
  FBC_STATUS compressed buffer tracking
  nuke() for frontbuffer invalidation on render

drrs.rs: Display Refresh Rate Switching
  Transitions high→low refresh rate on idle timeout
  DRRS_CTL with idle frame count configuration
  mark_active() for compositor interaction

psr2.rs: Panel Self Refresh v2
  Selective update via sink DPCD capability probe
  PSR2_MAN_TRK_CTL for partial frame update tracking
  DP_PSR2_SUPPORT/EN_CFG2 sink communication

regs_gen4_7.rs: Pre-Gen9 FDI register definitions
  Gen4-7 pipe/plane/DDI registers (PIPEACONF, DSPACNTR)
  GMBUS at 0x5100 base (pre-PCH offset)
  No DDI_BUF_CTL — these platforms use FDI display

mod.rs: Wired FbcState, DrrsState alongside existing PsrState
This commit is contained in:
2026-06-02 05:06:16 +03:00
parent d5636ae1de
commit 7f8f93146d
5 changed files with 410 additions and 0 deletions
@@ -0,0 +1,111 @@
use std::sync::Arc;
use std::time::{Duration, Instant};
use log::{debug, info};
use redox_driver_sys::memory::MmioRegion;
use super::info::IntelDeviceInfo;
use crate::driver::Result;
use crate::kms::ModeInfo;
const DRRS_CTL_BASE: usize = 0x46100;
const DRRS_CTL_ENABLE: u32 = 1 << 31;
const DRRS_CTL_IDLE_FRAMES_SHIFT: u32 = 8;
const DRRS_CTL_IDLE_FRAMES_MASK: u32 = 0xFF;
const DRRS_STATUS_BASE: usize = 0x46104;
const DRRS_STATUS_ACTIVE_LOW_RR: u32 = 1 << 31;
const DRRS_STATUS_CURRENT_LOW_RR: u32 = 1 << 0;
const DRRS_IDLE_TIMEOUT_MS: u64 = 1000;
const DRRS_IDLE_FRAMES_DEFAULT: u32 = 4;
pub struct DrrsState {
mmio: Arc<MmioRegion>,
enabled: bool,
high_rr_mode: ModeInfo,
low_rr_mode: Option<ModeInfo>,
last_activity: Instant,
pipe: u8,
}
impl DrrsState {
pub fn new(mmio: Arc<MmioRegion>, _device_info: &IntelDeviceInfo, pipe: u8) -> Self {
Self {
mmio,
enabled: false,
high_rr_mode: ModeInfo::default_1080p(),
low_rr_mode: None,
last_activity: Instant::now(),
pipe,
}
}
pub fn init(&mut self, high_mode: &ModeInfo) -> Result<()> {
self.high_rr_mode = high_mode.clone();
let low_vrefresh = 48u32;
if high_mode.vrefresh > low_vrefresh + 10 {
let mut low = high_mode.clone();
low.vrefresh = low_vrefresh;
low.clock = (low.clock as u64 * low_vrefresh as u64
/ high_mode.vrefresh as u64) as u32;
self.low_rr_mode = Some(low);
}
Ok(())
}
pub fn enable(&mut self) -> Result<()> {
if self.enabled {
return Ok(());
}
let ctl = DRRS_CTL_BASE + self.pipe as usize * 0x1000;
let val = DRRS_CTL_ENABLE
| ((DRRS_IDLE_FRAMES_DEFAULT & DRRS_CTL_IDLE_FRAMES_MASK)
<< DRRS_CTL_IDLE_FRAMES_SHIFT);
self.mmio.write32(ctl, val);
self.enabled = true;
self.last_activity = Instant::now();
info!("redox-drm-intel: DRRS enabled on pipe {}", self.pipe);
Ok(())
}
pub fn disable(&mut self) -> Result<()> {
if !self.enabled {
return Ok(());
}
let ctl = DRRS_CTL_BASE + self.pipe as usize * 0x1000;
self.mmio.write32(ctl, 0);
self.enabled = false;
info!("redox-drm-intel: DRRS disabled on pipe {}", self.pipe);
Ok(())
}
pub fn mark_active(&mut self) {
self.last_activity = Instant::now();
}
pub fn is_low_rr_active(&self) -> bool {
if !self.enabled {
return false;
}
let status = DRRS_STATUS_BASE + self.pipe as usize * 0x1000;
let val = self.mmio.read32(status);
val & DRRS_STATUS_CURRENT_LOW_RR != 0
}
pub fn should_enter_low_rr(&self) -> bool {
self.enabled
&& self.low_rr_mode.is_some()
&& self.last_activity.elapsed() > Duration::from_millis(DRRS_IDLE_TIMEOUT_MS)
}
pub fn low_rr_mode(&self) -> Option<&ModeInfo> {
self.low_rr_mode.as_ref()
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
@@ -0,0 +1,109 @@
use std::sync::Arc;
use log::{debug, info};
use redox_driver_sys::memory::MmioRegion;
use super::info::IntelDeviceInfo;
use crate::driver::Result;
use crate::kms::ModeInfo;
const FBC_CTL_BASE: usize = 0x43200;
const FBC_CTL_ENABLE: u32 = 1 << 31;
const FBC_CTL_COMPRESSION_EN: u32 = 1 << 30;
const FBC_CTL_INTERVAL_SHIFT: u32 = 22;
const FBC_CTL_INTERVAL_MASK: u32 = 0x3;
const FBC_CTL_FENCE_EN: u32 = 1 << 21;
const FBC_STATUS_BASE: usize = 0x43214;
const FBC_STATUS_COMPRESSING: u32 = 1 << 31;
const FBC_STATUS_COMPRESSED: u32 = 1 << 0;
const FBC_FENCE_BASE: usize = 0x43300;
const FBC_LL_BASE: usize = 0x43400;
const FBC_STRIDE_OFFSET: usize = 0x4;
const FBC_CFB_SIZE: usize = 0x8;
pub struct FbcState {
mmio: Arc<MmioRegion>,
enabled: bool,
compressed: bool,
compressed_size: u32,
}
impl FbcState {
pub fn new(mmio: Arc<MmioRegion>, _device_info: &IntelDeviceInfo) -> Self {
Self {
mmio,
enabled: false,
compressed: false,
compressed_size: 0,
}
}
pub fn enable(&mut self, fb_gtt_addr: u64, stride: u32, mode: &ModeInfo) -> Result<()> {
if self.enabled {
return Ok(());
}
let fb_lines = mode.vdisplay as u32;
self.compressed_size = fb_lines.saturating_mul(stride);
let ctl_reg = FBC_CTL_BASE;
let mut ctl = self.mmio.read32(ctl_reg);
ctl &= !FBC_CTL_ENABLE;
self.mmio.write32(ctl_reg, ctl);
let ll_reg = FBC_LL_BASE;
self.mmio.write32(ll_reg, fb_gtt_addr as u32);
self.mmio.write32(ll_reg + 4, (fb_gtt_addr >> 32) as u32);
self.mmio.write32(FBC_LL_BASE + FBC_STRIDE_OFFSET, stride);
self.mmio.write32(FBC_FENCE_BASE, 0);
ctl = self.mmio.read32(ctl_reg);
ctl |= FBC_CTL_ENABLE | FBC_CTL_COMPRESSION_EN | FBC_CTL_FENCE_EN;
ctl &= !(FBC_CTL_INTERVAL_MASK << FBC_CTL_INTERVAL_SHIFT);
ctl |= 1 << FBC_CTL_INTERVAL_SHIFT;
self.mmio.write32(ctl_reg, ctl);
let status = self.mmio.read32(FBC_STATUS_BASE);
if status & FBC_STATUS_COMPRESSING != 0 || status & FBC_STATUS_COMPRESSED != 0 {
self.enabled = true;
info!(
"redox-drm-intel: FBC enabled — {}x{} stride={} cfb={}KB status={:#x}",
mode.hdisplay, mode.vdisplay, stride,
self.compressed_size / 1024, status
);
} else {
debug!("redox-drm-intel: FBC enable requested, status={:#x}", status);
self.enabled = true;
}
Ok(())
}
pub fn disable(&mut self) -> Result<()> {
if !self.enabled {
return Ok(());
}
let ctl = self.mmio.read32(FBC_CTL_BASE);
self.mmio.write32(FBC_CTL_BASE, ctl & !FBC_CTL_ENABLE);
self.mmio.write32(FBC_STATUS_BASE, FBC_STATUS_COMPRESSED | FBC_STATUS_COMPRESSING);
self.enabled = false;
info!("redox-drm-intel: FBC disabled");
Ok(())
}
pub fn nuke(&self) {
if !self.enabled {
return;
}
self.mmio.write32(FBC_STATUS_BASE, FBC_STATUS_COMPRESSED);
debug!("redox-drm-intel: FBC nuke (invalidate compressed framebuffer)");
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
@@ -15,7 +15,9 @@ pub mod display_watermark;
pub mod dp_aux;
pub mod dp_link;
pub mod execbuffer;
pub mod drrs;
pub mod execlists;
pub mod fbc;
pub mod fence;
pub mod gamma;
pub mod gmbus;
@@ -30,7 +32,9 @@ pub mod info;
pub mod lmem;
pub mod mocs;
pub mod panel_pps;
pub mod psr2;
pub mod regs;
pub mod regs_gen4_7;
pub mod regs_gen9;
pub mod regs_gen12;
pub mod regs_xe2;
@@ -82,6 +86,8 @@ use self::regs_xe2::Xe2Regs;
use self::backlight::Backlight;
use self::context::ContextManager;
use self::display_psr::PsrState;
use self::drrs::DrrsState;
use self::fbc::FbcState;
use self::hangcheck::GpuHangDetector;
use self::panel_pps::PanelPowerSequencer;
use self::ring::{IntelRing, RingType};
@@ -124,6 +130,8 @@ pub struct IntelDriver {
backlight: Mutex<Backlight>,
context_manager: Mutex<ContextManager>,
psr: Mutex<PsrState>,
drrs: Mutex<DrrsState>,
fbc: Mutex<FbcState>,
hang_detector: Mutex<GpuHangDetector>,
panel_pps: Mutex<PanelPowerSequencer>,
syncobj_mgr: Mutex<SyncobjManager>,
@@ -369,6 +377,8 @@ impl IntelDriver {
let context_manager = Mutex::new(ContextManager::new());
let psr = Mutex::new(PsrState::new(mmio_arc.clone(), 0));
let drrs = Mutex::new(DrrsState::new(mmio_arc.clone(), &device_info, 0));
let fbc = Mutex::new(FbcState::new(mmio_arc.clone(), &device_info));
let hang_detector = Mutex::new(GpuHangDetector::new(mmio_arc.clone(), RENDER_RING_BASE));
@@ -443,6 +453,8 @@ impl IntelDriver {
backlight: Mutex::new(backlight),
context_manager,
psr,
drrs,
fbc,
hang_detector,
panel_pps,
syncobj_mgr,
@@ -0,0 +1,123 @@
use std::sync::Arc;
use log::{debug, info};
use redox_driver_sys::memory::MmioRegion;
use super::dp_aux::DpAux;
use super::info::IntelDeviceInfo;
use crate::driver::Result;
const PSR2_CTL_BASE: usize = 0x60800;
const PSR2_CTL_ENABLE: u32 = 1 << 31;
const PSR2_CTL_FRAME_SYNC: u32 = 1 << 14;
const PSR2_CTL_TP2_TP3_TIME_SHIFT: u32 = 8;
const PSR2_CTL_TP2_TP3_MASK: u32 = 0x3;
const PSR2_CTL_IDLE_FRAMES_SHIFT: u32 = 0;
const PSR2_CTL_IDLE_FRAMES_MASK: u32 = 0x7;
const PSR2_MAN_TRK_CTL_BASE: usize = 0x60810;
const PSR2_MAN_TRK_CTL_ENABLE: u32 = 1 << 31;
const PSR2_MAN_TRK_CTL_SF_PARTIAL_FRAME_UPDATE: u32 = 1 << 2;
const DP_PSR2_SUPPORT: u32 = 0x070;
const DP_PSR2_CAPABILITY: u32 = 0x071;
const DP_PSR2_ENABLE_SINK: u8 = 1 << 0;
const DP_PSR2_Y_COORD_REQUIRED: u8 = 1 << 3;
const DP_PSR2_SU_REGION_SCANLINE_CAP: u8 = 1 << 4;
const DP_PSR_EN_CFG2: u32 = 0x0171;
const DP_PSR2_SELECTIVE_UPDATE: u8 = 1 << 0;
pub struct Psr2State {
mmio: Arc<MmioRegion>,
enabled: bool,
selective_update: bool,
transcoder: u8,
}
impl Psr2State {
pub fn new(mmio: Arc<MmioRegion>, _device_info: &IntelDeviceInfo, transcoder: u8) -> Self {
Self {
mmio,
enabled: false,
selective_update: false,
transcoder,
}
}
pub fn probe_sink_caps(&self, aux: &DpAux) -> Result<bool> {
match aux.read_dpcd(DP_PSR2_SUPPORT, 2) {
Ok(data) if data.len() >= 2 => {
let supported = data[0] & 0x01 != 0;
if supported {
info!("redox-drm-intel: PSR2 supported by sink");
}
Ok(supported)
}
_ => Ok(false),
}
}
pub fn enable(&mut self, aux: Option<&DpAux>, port: u8) -> Result<()> {
if self.enabled {
return Ok(());
}
let sink_caps = aux.and_then(|a| a.read_dpcd(DP_PSR2_CAPABILITY, 2).ok());
let y_coord_required = sink_caps.as_ref()
.map_or(false, |d| d.len() >= 2 && d[0] & DP_PSR2_Y_COORD_REQUIRED != 0);
let su_scanline = sink_caps.as_ref()
.map_or(false, |d| d.len() >= 2 && d[0] & DP_PSR2_SU_REGION_SCANLINE_CAP != 0);
if let Some(aux) = aux {
aux.write_dpcd(DP_PSR_EN_CFG2, &[DP_PSR2_SELECTIVE_UPDATE])?;
aux.write_dpcd(DP_PSR2_SUPPORT, &[DP_PSR2_ENABLE_SINK])?;
debug!("redox-drm-intel: PSR2 sink enabled on port {} (y_coord={}, su_scanline={})",
port, y_coord_required, su_scanline);
}
let ctl = PSR2_CTL_BASE + self.transcoder as usize * 0x1000;
let val = PSR2_CTL_ENABLE
| PSR2_CTL_FRAME_SYNC
| (1 & PSR2_CTL_TP2_TP3_MASK) << PSR2_CTL_TP2_TP3_TIME_SHIFT
| (4 & PSR2_CTL_IDLE_FRAMES_MASK) << PSR2_CTL_IDLE_FRAMES_SHIFT;
self.mmio.write32(ctl, val);
self.enabled = true;
self.selective_update = su_scanline;
info!("redox-drm-intel: PSR2 enabled on transcoder {} (selective_update={})",
self.transcoder, self.selective_update);
Ok(())
}
pub fn disable(&mut self, aux: Option<&DpAux>) -> Result<()> {
if !self.enabled {
return Ok(());
}
let ctl = PSR2_CTL_BASE + self.transcoder as usize * 0x1000;
self.mmio.write32(ctl, 0);
if let Some(aux) = aux {
aux.write_dpcd(DP_PSR_EN_CFG2, &[0])?;
aux.write_dpcd(DP_PSR2_SUPPORT, &[0])?;
}
self.enabled = false;
info!("redox-drm-intel: PSR2 disabled on transcoder {}", self.transcoder);
Ok(())
}
pub fn flush(&self) {
if !self.enabled {
return;
}
let man_trk = PSR2_MAN_TRK_CTL_BASE + self.transcoder as usize * 0x1000;
self.mmio.write32(man_trk,
PSR2_MAN_TRK_CTL_ENABLE | PSR2_MAN_TRK_CTL_SF_PARTIAL_FRAME_UPDATE);
debug!("redox-drm-intel: PSR2 manual tracking flush");
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
@@ -0,0 +1,55 @@
use super::regs::IntelRegs;
pub struct Gen4_7Regs;
impl IntelRegs for Gen4_7Regs {
fn forcewake_req(&self) -> usize { 0xA188 }
fn forcewake_ack(&self) -> usize { 0x130040 }
fn power_well_ctl(&self) -> usize { 0 }
fn cdclk_ctl(&self) -> usize { 0x46200 }
fn dmc_mmio_start(&self) -> usize { 0 }
fn dmc_mmio_end(&self) -> usize { 0 }
fn dmc_fw_base(&self) -> usize { 0 }
fn dmc_ctrl(&self) -> usize { 0 }
fn dmc_status(&self) -> usize { 0 }
fn dmc_sram_base(&self) -> usize { 0 }
fn gmbus0(&self) -> usize { 0x5100 }
fn gmbus1(&self) -> usize { 0x5120 }
fn gmbus2(&self) -> usize { 0x5140 }
fn gmbus3(&self) -> usize { 0x5160 }
fn gmbus4(&self) -> usize { 0x5180 }
fn gmbus5(&self) -> usize { 0x51A0 }
fn pipeconf(&self, pipe: u8) -> usize { 0x70008 + pipe as usize * 0x1000 }
fn pipeconf_enable_mask(&self) -> u32 { 1 << 31 }
fn htotal(&self, pipe: u8) -> usize { 0x60000 + pipe as usize * 0x1000 }
fn hblank(&self, pipe: u8) -> usize { 0x60004 + pipe as usize * 0x1000 }
fn hsync(&self, pipe: u8) -> usize { 0x60008 + pipe as usize * 0x1000 }
fn vtotal(&self, pipe: u8) -> usize { 0x6000C + pipe as usize * 0x1000 }
fn vblank(&self, pipe: u8) -> usize { 0x60010 + pipe as usize * 0x1000 }
fn vsync(&self, pipe: u8) -> usize { 0x60014 + pipe as usize * 0x1000 }
fn pipe_src(&self, pipe: u8) -> usize { 0x6001C + pipe as usize * 0x1000 }
fn pipe_stride(&self) -> usize { 0x1000 }
fn dspcntr(&self, pipe: u8) -> usize { 0x70180 + pipe as usize * 0x1000 }
fn dspcntr_enable_mask(&self) -> u32 { 1 << 31 }
fn dspsurf(&self, pipe: u8) -> usize { 0x7019C + pipe as usize * 0x1000 }
fn plane_size(&self, pipe: u8) -> usize { 0x70190 + pipe as usize * 0x1000 }
fn ps_ctrl(&self, _pipe: u8) -> usize { 0 }
fn ps_win_pos(&self, _pipe: u8) -> usize { 0 }
fn ps_win_size(&self, _pipe: u8) -> usize { 0 }
fn ddi_buf_ctl(&self, _port: u8) -> usize { 0 }
fn ddi_port_stride(&self) -> usize { 0x100 }
fn curcntr(&self, pipe: u8) -> usize { 0x70080 + pipe as usize * 0x1000 }
fn curbase(&self, pipe: u8) -> usize { 0x70084 + pipe as usize * 0x1000 }
fn curpos(&self, pipe: u8) -> usize { 0x70088 + pipe as usize * 0x1000 }
fn gfx_flsh_cntl(&self) -> usize { 0x101008 }
fn pipestat(&self, pipe: u8) -> usize { 0x70024 + pipe as usize * 0x1000 }
fn pipestat_vblank_mask(&self) -> u32 { 1 << 0 }
fn pipestat_vsync_mask(&self) -> u32 { 1 << 4 }
fn pp_status(&self) -> usize { 0xC7200 }
fn pipeframe_reg(&self, pipe: u8) -> usize { 0x70040 + pipe as usize * 0x1000 }
fn pipeframe_count_mask(&self) -> u32 { 0xFFFFFF }
fn de_iir(&self) -> usize { 0x44008 }
fn de_imr(&self) -> usize { 0x4400C }
fn de_ier(&self) -> usize { 0x44010 }
fn d2d_link_ctl(&self, _port: u8) -> usize { 0 }
}