diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_phy.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_phy.rs new file mode 100644 index 0000000000..7b8ee0e04c --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/dp_phy.rs @@ -0,0 +1,128 @@ +use std::sync::Arc; + +use log::{debug, info}; +use redox_driver_sys::memory::MmioRegion; + +use super::dp_aux::DpAux; +use crate::driver::{DriverError, Result}; + +// ── DP PHY Test Patterns — DPCD 0x248 ──────────────────────────────────── +// DP 1.2+ PHY compliance testing uses standardized test patterns +// transmitted during link training. Patterns exercise the physical +// layer: clock recovery (TPS1), channel equalization (TPS2), +// and symbol lock (TPS3). HBR3 adds TPS4 for 8.1 Gbps validation. + +const DP_PHY_TEST_PATTERN: u32 = 0x0248; +const DP_TEST_LANE_COUNT: u32 = 0x0247; +const DP_TEST_LINK_RATE: u32 = 0x0219; + +const DP_PHY_TEST_PATTERN_NONE: u8 = 0; +const DP_PHY_TEST_PATTERN_D10_2: u8 = 1; +const DP_PHY_TEST_PATTERN_SERM: u8 = 2; +const DP_PHY_TEST_PATTERN_PRBS7: u8 = 3; +const DP_PHY_TEST_PATTERN_80BIT: u8 = 4; +const DP_PHY_TEST_PATTERN_HBR2: u8 = 5; +const DP_PHY_TEST_PATTERN_CP2520: u8 = 6; + +const DDI_DP_PATTERN_CTL_BASE: usize = 0x64020; +const DDI_DP_PATTERN_CTL_ENABLE: u32 = 1 << 0; +const DDI_DP_PATTERN_SELECT_SHIFT: u32 = 4; + +pub struct DpPhyTest { + mmio: Arc, + active: bool, + port: u8, + pattern: u8, +} + +impl DpPhyTest { + pub fn new(mmio: Arc, port: u8) -> Self { + Self { mmio, active: false, port, pattern: DP_PHY_TEST_PATTERN_NONE } + } + + pub fn set_pattern(&mut self, aux: &DpAux, pattern: u8) -> Result<()> { + aux.write_dpcd(DP_PHY_TEST_PATTERN, &[pattern])?; + self.pattern = pattern; + + let ctl = DDI_DP_PATTERN_CTL_BASE + self.port as usize * 0x100; + if pattern != DP_PHY_TEST_PATTERN_NONE { + let val = DDI_DP_PATTERN_CTL_ENABLE | ((pattern as u32 & 0x7) << DDI_DP_PATTERN_SELECT_SHIFT); + self.mmio.write32(ctl, val); + self.active = true; + info!("redox-drm-intel: DP PHY test pattern {} enabled on port {}", pattern, self.port); + } else { + self.mmio.write32(ctl, 0); + self.active = false; + info!("redox-drm-intel: DP PHY test pattern disabled on port {}", self.port); + } + Ok(()) + } + + pub fn disable(&mut self, aux: &DpAux) -> Result<()> { + self.set_pattern(aux, DP_PHY_TEST_PATTERN_NONE) + } + + pub fn is_active(&self) -> bool { self.active } + pub fn current_pattern(&self) -> u8 { self.pattern } +} + +pub struct HdmiDeepColor { + max_bpc: u8, + current_bpc: u8, + supported_bpc: Vec, +} + +impl HdmiDeepColor { + pub fn new() -> Self { + Self { max_bpc: 8, current_bpc: 8, supported_bpc: vec![8] } + } + + pub fn probe_sink(&mut self, edid: &[u8]) { + self.supported_bpc.clear(); + self.supported_bpc.push(8); + + if edid.len() >= 256 { + let mut offset = 128u16; + while offset < edid.len() as u16 && offset + 3 <= edid.len() as u16 { + let tag = edid[offset as usize] >> 5; + let len = (edid[offset as usize] & 0x1F) as u16; + if tag == 0x02 { + let data = &edid[offset as usize + 1..]; + let mut dtd = 0u16; + while dtd < len { + let dtd_tag = data[dtd as usize] >> 5; + let dtd_len = (data[dtd as usize] & 0x1F) as u16; + if dtd_tag == 0x07 && dtd_len >= 6 { + let oui = ((data[dtd as usize + 1] as u32) << 16) + | ((data[dtd as usize + 2] as u32) << 8) + | (data[dtd as usize + 3] as u32); + if oui == 0x000C03 { + let dc = data[dtd as usize + 6]; + if dc & 0x40 != 0 { self.supported_bpc.push(10); } + if dc & 0x80 != 0 { self.supported_bpc.push(12); } + if dc & 0x20 != 0 { self.supported_bpc.push(16); } + } + } + dtd += dtd_len + 1; + } + } + offset += len + 1; + } + } + + self.max_bpc = self.supported_bpc.iter().copied().max().unwrap_or(8); + } + + pub fn set_bpc(&mut self, bpc: u8) -> Result<()> { + if !self.supported_bpc.contains(&bpc) { + return Err(DriverError::Buffer(format!("HDMI deep color: {} bpc not supported", bpc))); + } + self.current_bpc = bpc; + debug!("redox-drm-intel: HDMI deep color set to {} bpc", bpc); + Ok(()) + } + + pub fn max_bpc(&self) -> u8 { self.max_bpc } + pub fn current_bpc(&self) -> u8 { self.current_bpc } + pub fn supported_bpc(&self) -> &[u8] { &self.supported_bpc } +}