intel: HDMI infoframes + VBT parser (Phase 1 + Phase 3)
Add hdmi.rs — AVI infoframe programming for HDMI monitors. - program_avi(): computes VIC (Video Identification Code) from standard mode table (640x480 through 3840x2160), aspect ratio, scan info, colorimetry, quantization. Programs HSW_TVIDEO_DIP_CTL (0x61180) and AVI_DATA (0x61184) registers per-pipe - disable(): clear VIDEO_DIP_ENABLE bit - checksum(): 256-byte wrap check for infoframe validation Add vbt.rs — Video BIOS Table parser. - parse(): validate signature, extract version and BDB offset - parse_bdb(): walk BDB blocks, parse child device config (block 33) extracting DVO port, DDC pin, HDMI/DP/eDP support flags - port_type_for_index(): map port index to PortType using VBT data - ChildDeviceConfig with aux_channel detection Linux reference: intel_hdmi.c, intel_bios.c (VBT parsing)
This commit is contained in:
@@ -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<MmioRegion>,
|
||||
}
|
||||
|
||||
impl HdmiInfoframes {
|
||||
pub fn new(mmio: Arc<MmioRegion>) -> 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(())
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ChildDeviceConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChildDeviceConfig {
|
||||
pub device_type: u16,
|
||||
pub dvo_port: u8,
|
||||
pub ddc_pin: u8,
|
||||
pub aux_channel: Option<u8>,
|
||||
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<Self> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user