diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi.rs new file mode 100644 index 0000000000..e165c85a2a --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/hdmi.rs @@ -0,0 +1,129 @@ +use std::sync::Arc; + +use log::debug; +use redox_driver_sys::memory::MmioRegion; + +use super::regs::IntelRegs; +use crate::driver::Result; +use crate::kms::ModeInfo; + +const HSW_TVIDEO_DIP_CTL_BASE: usize = 0x61180; +const HSW_TVIDEO_DIP_AVI_DATA_BASE: usize = 0x61184; +const PIPE_STRIDE: usize = 0x1000; + +const VIDEO_DIP_ENABLE: u32 = 1 << 31; +const VIDEO_DIP_PORT_SELECT_HDMI: u32 = 0 << 29; +const VIDEO_DIP_AVI: u32 = 4 << 24; +const VIDEO_DIP_FREQ_VSYNC: u32 = 1 << 16; + +const AVI_HEADER: u8 = 0x82; +const AVI_VERSION: u8 = 0x02; +const AVI_LENGTH: u8 = 13; +const AVI_CHECKSUM_OFFSET: usize = 3; + +pub struct HdmiInfoframes { + mmio: Arc, +} + +impl HdmiInfoframes { + pub fn new(mmio: Arc) -> Self { + Self { mmio } + } + + pub fn program_avi(&self, pipe: u8, port: u8, mode: &ModeInfo) -> Result<()> { + let dip_ctl = HSW_TVIDEO_DIP_CTL_BASE + (pipe as usize) * PIPE_STRIDE; + let dip_data = HSW_TVIDEO_DIP_AVI_DATA_BASE + (pipe as usize) * PIPE_STRIDE; + + let mut packet = [0u8; 17]; + packet[0] = AVI_HEADER; + packet[1] = AVI_VERSION; + packet[2] = AVI_LENGTH; + + let vic = Self::compute_vic(mode); + packet[3] = vic; + + packet[4] = 0x00; + + let h_active = mode.hdisplay; + let v_active = mode.vdisplay; + packet[5] = (h_active & 0xFF) as u8; + packet[6] = ((h_active >> 8) & 0xFF) as u8; + packet[7] = (v_active & 0xFF) as u8; + packet[8] = ((v_active >> 8) & 0xFF) as u8; + + let ar = Self::compute_aspect_ratio(mode); + let colorimetry = 0; + let scan_info = if mode.flags & 0x01 != 0 { 2 } else { 0 }; + packet[9] = (scan_info << 4) | (ar << 2) | colorimetry; + + let quantization = 0; + packet[10] = (quantization << 6) | (quantization << 4) | (quantization << 2) | quantization; + + packet[11] = 0; + packet[12] = 0; + packet[13] = ((v_active >> 8) & 0x0F) as u8; + packet[14] = ((v_active >> 8) & 0xF0) as u8 | ((h_active >> 8) & 0x0F) as u8; + packet[15] = ((h_active >> 12) & 0x0F) as u8; + + let checksum = Self::checksum(&packet[..16]); + packet[16] = checksum; + + self.mmio.write_u32(dip_ctl, 0); + + for i in 0..5 { + let mut word: u32 = 0; + for j in 0..4 { + let idx = i * 4 + j; + if idx < 17 { + word |= (packet[idx] as u32) << (j * 8); + } + } + self.mmio.write_u32(dip_data + i * 4, word); + } + + let ctl_val = VIDEO_DIP_ENABLE + | VIDEO_DIP_PORT_SELECT_HDMI + | VIDEO_DIP_AVI + | VIDEO_DIP_FREQ_VSYNC; + self.mmio.write_u32(dip_ctl, ctl_val); + + debug!("redox-drm-intel: AVI infoframe programmed for pipe {} (VIC {})", pipe, vic); + Ok(()) + } + + fn compute_vic(mode: &ModeInfo) -> u8 { + match (mode.hdisplay, mode.vdisplay, mode.vrefresh) { + (640, 480, 60) => 1, + (720, 480, 60) => 2, + (1280, 720, 60) => 4, + (1920, 1080, 60) => 16, + (1920, 1080, 50) => 31, + (1920, 1080, 24) => 32, + (3840, 2160, 60) => 97, + (3840, 2160, 30) => 95, + (2560, 1440, 60) => 0, + _ => 0, + } + } + + fn compute_aspect_ratio(mode: &ModeInfo) -> u8 { + let ratio = mode.hdisplay as f32 / mode.vdisplay as f32; + if ratio > 2.1 { 3 } + else if ratio > 1.6 { 2 } + else { 1 } + } + + fn checksum(data: &[u8]) -> u8 { + let mut sum: u32 = 0; + for &byte in data { + sum = sum.wrapping_add(byte as u32); + } + (256u32.wrapping_sub(sum % 256) % 256) as u8 + } + + pub fn disable(&self, pipe: u8) -> Result<()> { + let dip_ctl = HSW_TVIDEO_DIP_CTL_BASE + (pipe as usize) * PIPE_STRIDE; + self.mmio.write_u32(dip_ctl, 0); + Ok(()) + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs index 715ae873aa..38622b5d6e 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs @@ -13,6 +13,7 @@ pub mod execlists; pub mod fence; pub mod gmbus; pub mod gtt; +pub mod hdmi; pub mod hotplug; pub mod info; pub mod regs; @@ -20,6 +21,7 @@ pub mod regs_gen9; pub mod regs_gen12; pub mod regs_xe2; pub mod ring; +pub mod vbt; use std::collections::HashMap; use std::sync::Mutex; diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/vbt.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/vbt.rs new file mode 100644 index 0000000000..b3842e1ae0 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/vbt.rs @@ -0,0 +1,167 @@ +use log::{debug, info, warn}; + +const VBT_SIGNATURE: &[u8; 4] = b"$VBT"; +const BDB_HEADER_SIZE: usize = 3; +const BDB_GENERAL_DEFINITIONS: u8 = 2; +const BDB_LVDS_OPTIONS: u8 = 40; +const BDB_MIPI_SEQUENCE: u8 = 53; + +#[derive(Debug, Clone)] +pub struct VbtInfo { + pub signature_valid: bool, + pub version: u16, + pub bdb_offset: usize, + pub has_lvds: bool, + pub has_mipi: bool, + pub child_devices: Vec, +} + +#[derive(Debug, Clone)] +pub struct ChildDeviceConfig { + pub device_type: u16, + pub dvo_port: u8, + pub ddc_pin: u8, + pub aux_channel: Option, + pub hdmi_support: bool, + pub dp_support: bool, + pub edp_support: bool, + pub device_id: u16, +} + +#[derive(Debug, Clone, Copy)] +pub enum PortType { + DP, + HDMI, + EDP, + LVDS, + Unknown, +} + +impl VbtInfo { + pub fn parse(data: &[u8]) -> Option { + if data.len() < 8 || &data[0..4] != VBT_SIGNATURE { + warn!("redox-drm-intel: invalid VBT signature"); + return None; + } + + let version = u16::from_le_bytes([data[4], data[5]]); + let vbt_size = u16::from_le_bytes([data[6], data[7]]) as usize; + let bdb_offset = if vbt_size > 0 && (8 + data[8] as usize) < data.len() { + data[8] as usize + } else { + 0 + }; + + debug!("redox-drm-intel: VBT v{}, size {} bytes, BDB at offset {}", version, vbt_size, bdb_offset); + + let mut info = VbtInfo { + signature_valid: true, + version, + bdb_offset, + has_lvds: false, + has_mipi: false, + child_devices: Vec::new(), + }; + + if bdb_offset > 0 && bdb_offset + BDB_HEADER_SIZE < data.len() { + info.parse_bdb(&data[bdb_offset..]); + } + + Some(info) + } + + fn parse_bdb(&mut self, bdb: &[u8]) { + let bdb_version = u16::from_le_bytes([bdb[0], bdb[1]]); + let bdb_length = u16::from_le_bytes([bdb[2], bdb[3]]) as usize; + + debug!("redox-drm-intel: BDB v{}, {} bytes", bdb_version, bdb_length); + + let mut offset: usize = 4; + while offset + 3 <= bdb.len().min(bdb_length + 4) && offset + 3 <= bdb.len() { + let block_id = bdb[offset]; + let block_size = bdb[offset + 1] as usize; + offset += 2; + + if block_size == 0 || offset + block_size > bdb.len() { + offset += block_size.min(bdb.len() - offset); + continue; + } + + let block_data = &bdb[offset..offset + block_size]; + + match block_id { + BDB_GENERAL_DEFINITIONS => { + if block_size > 0 { + self.has_lvds = false; + self.has_mipi = false; + } + } + 33 => { + let count = block_size / 2; + for i in 0..count { + let device = u16::from_le_bytes([ + block_data[i * 2], block_data[i * 2 + 1], + ]); + let config = self.parse_child_device(device); + self.child_devices.push(config); + } + } + _ => {} + } + + offset += block_size; + } + } + + fn parse_child_device(&self, device: u16) -> ChildDeviceConfig { + let dvo_port = ((device >> 0) & 0x0F) as u8; + let ddc_pin = ((device >> 4) & 0x0F) as u8; + let hdmi = (device >> 8) & 0x01 != 0; + let dp = (device >> 9) & 0x01 != 0; + let edp = (device >> 10) & 0x01 != 0; + let device_id = device; + let aux_channel = if dp || edp { Some(ddc_pin) } else { None }; + + ChildDeviceConfig { + device_type: device, + dvo_port, + ddc_pin, + aux_channel, + hdmi_support: hdmi, + dp_support: dp, + edp_support: edp, + device_id, + } + } + + pub fn port_type_for_index(&self, index: usize) -> PortType { + if let Some(dev) = self.child_devices.get(index) { + if dev.edp_support { return PortType::EDP; } + if dev.dp_support { return PortType::DP; } + if dev.hdmi_support { return PortType::HDMI; } + } + if index < 2 { PortType::DP } else { PortType::HDMI } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_invalid_signature() { + let data = vec![0u8; 100]; + assert!(VbtInfo::parse(&data).is_none()); + } + + #[test] + fn test_valid_empty_vbt() { + let mut data = vec![0u8; 32]; + data[0..4].copy_from_slice(b"$VBT"); + data[4..6].copy_from_slice(&1u16.to_le_bytes()); + data[6..8].copy_from_slice(&32u16.to_le_bytes()); + let info = VbtInfo::parse(&data); + assert!(info.is_some()); + assert!(info.unwrap().signature_valid); + } +}