intel: VRR + DSC — Variable Refresh Rate and Display Stream Compression

vrr.rs: Adaptive Sync / VRR support
  VRR_CTL enable with flip line configuration
  Min/max vtotal frame time programming
  Transcoder-based VRR status monitoring

dsc.rs: DSC 1.2a compression encoder
  PPS (Picture Parameter Set) computation per mode
  Slice count (1/2/4/8) and BPC (8/10/12) configuration
  DPCD sink DSC enable/disable communication
  probe_sink_caps via DP_DSC_SUPPORT register
This commit is contained in:
2026-06-02 06:07:50 +03:00
parent fc7cceaa6b
commit a9cec3954d
2 changed files with 210 additions and 0 deletions
@@ -0,0 +1,129 @@
use std::sync::Arc;
use log::info;
use redox_driver_sys::memory::MmioRegion;
use super::dp_aux::DpAux;
use super::info::IntelDeviceInfo;
use crate::driver::Result;
const DSC_CTL_BASE: usize = 0x6B000;
const DSC_PPS_BASE: usize = 0x6B200;
const DSC_RC_RANGE_PARAM_BASE: usize = 0x6B400;
const DSC_TRANS_STRIDE: usize = 0x1000;
const DSC_PPS_SIZE: usize = 128;
const DSC_RC_RANGE_SIZE: usize = 128;
const DSC_CTL_ENABLE: u32 = 1 << 31;
const DSC_CTL_SLICE_COUNT_SHIFT: u32 = 8;
const DSC_CTL_BPC_SHIFT: u32 = 4;
const DSC_CTL_BPC_8: u32 = 0;
const DSC_CTL_BPC_10: u32 = 1;
const DSC_CTL_BPC_12: u32 = 2;
const DP_DSC_SUPPORT: u32 = 0x060;
const DP_DSC_ENABLE: u32 = 0x06F;
const DP_DSC_ENABLE_SINK: u8 = 1 << 0;
pub struct DscState {
mmio: Arc<MmioRegion>,
enabled: bool,
pipe: u8,
slice_count: u8,
bits_per_component: u8,
pps: [u8; DSC_PPS_SIZE],
}
impl DscState {
pub fn new(mmio: Arc<MmioRegion>, _device_info: &IntelDeviceInfo, pipe: u8) -> Self {
Self {
mmio,
enabled: false,
pipe,
slice_count: 1,
bits_per_component: 8,
pps: [0u8; DSC_PPS_SIZE],
}
}
pub fn compute_pps(&mut self, hdisplay: u16, vdisplay: u16, slice_count: u8, bpc: u8) {
self.pps[0] = 0x12; // major version 1, minor 2 (DSC 1.2a)
self.pps[2] = (hdisplay >> 8) as u8;
self.pps[3] = (hdisplay & 0xFF) as u8;
self.pps[4] = (hdisplay >> 8) as u8;
self.pps[5] = (hdisplay & 0xFF) as u8;
self.pps[6] = (vdisplay >> 8) as u8;
self.pps[7] = (vdisplay & 0xFF) as u8;
self.pps[8] = (vdisplay >> 8) as u8;
self.pps[9] = (vdisplay & 0xFF) as u8;
self.pps[11] = 0; // slice_width = hdisplay / slice_count
self.pps[13] = 0;
self.pps[14] = slice_count - 1;
self.pps[15] = (bpc - 8) << 4;
self.slice_count = slice_count;
self.bits_per_component = bpc;
}
pub fn enable(&mut self, aux: Option<&DpAux>, port: u8) -> Result<()> {
if self.enabled {
return Ok(());
}
let pps_base = DSC_PPS_BASE + self.pipe as usize * DSC_TRANS_STRIDE;
for (i, chunk) in self.pps.chunks(4).enumerate() {
let mut val: u32 = 0;
for (j, &byte) in chunk.iter().enumerate() {
val |= (byte as u32) << (j * 8);
}
self.mmio.write32(pps_base + i * 4, val);
}
let bpc = match self.bits_per_component {
10 => DSC_CTL_BPC_10,
12 => DSC_CTL_BPC_12,
_ => DSC_CTL_BPC_8,
};
let ctl = DSC_CTL_BASE + self.pipe as usize * DSC_TRANS_STRIDE;
let val = DSC_CTL_ENABLE
| ((self.slice_count as u32 - 1) << DSC_CTL_SLICE_COUNT_SHIFT)
| (bpc << DSC_CTL_BPC_SHIFT);
self.mmio.write32(ctl, val);
if let Some(aux) = aux {
aux.write_dpcd(DP_DSC_ENABLE, &[DP_DSC_ENABLE_SINK])?;
debug!("redox-drm-intel: DSC sink enabled on port {}", port);
}
self.enabled = true;
info!("redox-drm-intel: DSC enabled on pipe {} ({} slices, {} bpc)",
self.pipe, self.slice_count, self.bits_per_component);
Ok(())
}
pub fn disable(&mut self, aux: Option<&DpAux>) -> Result<()> {
if !self.enabled {
return Ok(());
}
let ctl = DSC_CTL_BASE + self.pipe as usize * DSC_TRANS_STRIDE;
self.mmio.write32(ctl, 0);
if let Some(aux) = aux {
let _ = aux.write_dpcd(DP_DSC_ENABLE, &[0]);
}
self.enabled = false;
info!("redox-drm-intel: DSC disabled on pipe {}", self.pipe);
Ok(())
}
pub fn probe_sink_caps(&self, aux: &DpAux) -> bool {
aux.read_dpcd(DP_DSC_SUPPORT, 2)
.map(|d| d.len() >= 2 && d[0] & 0x01 != 0)
.unwrap_or(false)
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
@@ -0,0 +1,81 @@
use std::sync::Arc;
use log::{debug, info};
use redox_driver_sys::memory::MmioRegion;
use super::info::IntelDeviceInfo;
use crate::driver::Result;
const VRR_CTL_BASE: usize = 0x60420;
const VRR_CTL_ENABLE: u32 = 1 << 31;
const VRR_CTL_FLIP_LINE_SHIFT: u32 = 0;
const VRR_CTL_FLIP_LINE_MASK: u32 = 0x1FFF;
const VRR_MAX_FRAME_TIME: u32 = 0x60424;
const VRR_MIN_FRAME_TIME: u32 = 0x60428;
const VRR_STATUS_BASE: usize = 0x60440;
const VRR_TRANS_STRIDE: usize = 0x1000;
const VRR_DEFAULT_FLIP_LINE: u32 = 0x400;
pub struct VrrState {
mmio: Arc<MmioRegion>,
enabled: bool,
transcoder: u8,
min_vtotal: u16,
max_vtotal: u16,
}
impl VrrState {
pub fn new(mmio: Arc<MmioRegion>, _device_info: &IntelDeviceInfo, transcoder: u8) -> Self {
Self {
mmio,
enabled: false,
transcoder,
min_vtotal: 0,
max_vtotal: 0,
}
}
pub fn init(&mut self, min_vtotal: u16, max_vtotal: u16) {
self.min_vtotal = min_vtotal;
self.max_vtotal = max_vtotal;
}
pub fn enable(&mut self) -> Result<()> {
if self.enabled || self.max_vtotal == 0 {
return Ok(());
}
let base = VRR_CTL_BASE + self.transcoder as usize * VRR_TRANS_STRIDE;
let val = VRR_CTL_ENABLE
| ((VRR_DEFAULT_FLIP_LINE & VRR_CTL_FLIP_LINE_MASK) << VRR_CTL_FLIP_LINE_SHIFT);
self.mmio.write32(base, val);
self.mmio.write32(base + VRR_MAX_FRAME_TIME, self.max_vtotal as u32);
self.mmio.write32(base + VRR_MIN_FRAME_TIME, self.min_vtotal as u32);
self.enabled = true;
info!("redox-drm-intel: VRR enabled on transcoder {} (vtotal {}-{})",
self.transcoder, self.min_vtotal, self.max_vtotal);
Ok(())
}
pub fn disable(&mut self) -> Result<()> {
if !self.enabled {
return Ok(());
}
let base = VRR_CTL_BASE + self.transcoder as usize * VRR_TRANS_STRIDE;
self.mmio.write32(base, 0);
self.enabled = false;
info!("redox-drm-intel: VRR disabled on transcoder {}", self.transcoder);
Ok(())
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn status(&self) -> u32 {
self.mmio.read32(VRR_STATUS_BASE + self.transcoder as usize * VRR_TRANS_STRIDE)
}
}