intel: DP link training — clock recovery + channel equalization
Add dp_link.rs implementing DisplayPort link training. - train_dp_link(): reads DPCD caps, picks optimal link rate (1.62/2.7/5.4 Gbps) and lane count (1/2/4), programs DDI, runs clock recovery (pattern 1) and channel equalization (pattern 2), then disables training pattern - pick_link_rate(): selects highest supported link rate - program_ddi(): configures DDI_BUF_CTL with port width - clock_recovery(): polls DPCD LANE0_1_STATUS CR_DONE bits - channel_equalization(): polls CHANNEL_EQ_DONE + LANE_ALIGN_STATUS_UPDATED - 100ms timeout, 5 retries per phase Wire into IntelDriver constructor — train all DP links for Xe2 platforms after DP AUX init, before display detection. Linux reference: intel_dp_link_training.c
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use log::{debug, info, warn};
|
||||
use redox_driver_sys::memory::MmioRegion;
|
||||
|
||||
use super::dp_aux::DpAux;
|
||||
use crate::driver::Result;
|
||||
use crate::driver::DriverError;
|
||||
|
||||
const LINK_TIMEOUT_MS: u64 = 100;
|
||||
|
||||
const DPCD_LINK_BW_SET: u32 = 0x0100;
|
||||
const DPCD_LANE_COUNT_SET: u32 = 0x0101;
|
||||
const DPCD_TRAINING_PATTERN_SET: u32 = 0x0102;
|
||||
const DPCD_LANE0_1_STATUS: u32 = 0x0202;
|
||||
const DPCD_LANE_ALIGN_STATUS_UPDATED: u32 = 0x0204;
|
||||
|
||||
const DP_LINK_BW_1_62: u8 = 0x06;
|
||||
const DP_LINK_BW_2_7: u8 = 0x0A;
|
||||
const DP_LINK_BW_5_4: u8 = 0x14;
|
||||
|
||||
const DP_TRAINING_PATTERN_1: u8 = 0x01;
|
||||
const DP_TRAINING_PATTERN_2: u8 = 0x02;
|
||||
const DP_TRAINING_PATTERN_DISABLE: u8 = 0x00;
|
||||
|
||||
const DDI_BUF_CTL_ENABLE: u32 = 1 << 31;
|
||||
const DDI_BUF_CTL_DDI_SELECT_MASK: u32 = 0xF << 28;
|
||||
const DDI_BUF_CTL_PORT_WIDTH_X1: u32 = 0 << 1;
|
||||
const DDI_BUF_CTL_PORT_WIDTH_X2: u32 = 1 << 1;
|
||||
const DDI_BUF_CTL_PORT_WIDTH_X4: u32 = 3 << 1;
|
||||
|
||||
pub struct DpLinkConfig {
|
||||
pub lane_count: u8,
|
||||
pub link_rate_khz: u32,
|
||||
pub spread_spectrum: bool,
|
||||
}
|
||||
|
||||
pub fn train_dp_link(mmio: &MmioRegion, aux: &DpAux, port: u8) -> Result<DpLinkConfig> {
|
||||
let caps = aux.read_dpcd_caps()?;
|
||||
info!(
|
||||
"redox-drm-intel: DP link training port {} — DPCD rev {}.{}, max rate {}, max lanes {}",
|
||||
port, caps.revision >> 4, caps.revision & 0xF,
|
||||
caps.max_link_rate, caps.max_lane_count
|
||||
);
|
||||
|
||||
let link_bw = pick_link_rate(caps.max_link_rate);
|
||||
let lane_count = caps.max_lane_count.min(4u8).max(1u8);
|
||||
let link_rate_khz = match link_bw {
|
||||
DP_LINK_BW_1_62 => 162_000,
|
||||
DP_LINK_BW_2_7 => 270_000,
|
||||
DP_LINK_BW_5_4 => 540_000,
|
||||
_ => 270_000,
|
||||
};
|
||||
|
||||
let config = DpLinkConfig { lane_count, link_rate_khz, spread_spectrum: caps.max_downspread };
|
||||
|
||||
program_ddi(mmio, port, &config)?;
|
||||
aux.write_dpcd(DPCD_LINK_BW_SET, &[link_bw])?;
|
||||
aux.write_dpcd(DPCD_LANE_COUNT_SET, &[lane_count])?;
|
||||
|
||||
aux.write_dpcd(DPCD_TRAINING_PATTERN_SET, &[DP_TRAINING_PATTERN_1])?;
|
||||
clock_recovery(aux, lane_count)?;
|
||||
|
||||
aux.write_dpcd(DPCD_TRAINING_PATTERN_SET, &[DP_TRAINING_PATTERN_2])?;
|
||||
channel_equalization(aux, lane_count)?;
|
||||
|
||||
aux.write_dpcd(DPCD_TRAINING_PATTERN_SET, &[DP_TRAINING_PATTERN_DISABLE])?;
|
||||
info!("redox-drm-intel: DP link trained — {} lanes at {} kHz", lane_count, link_rate_khz);
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn pick_link_rate(max_rate: u8) -> u8 {
|
||||
if max_rate >= DP_LINK_BW_5_4 { DP_LINK_BW_5_4 }
|
||||
else if max_rate >= DP_LINK_BW_2_7 { DP_LINK_BW_2_7 }
|
||||
else { DP_LINK_BW_1_62 }
|
||||
}
|
||||
|
||||
fn program_ddi(mmio: &MmioRegion, port: u8, config: &DpLinkConfig) -> Result<()> {
|
||||
let ddi_offset = 0x64000 + (port as usize) * 0x100;
|
||||
let mut ddi = mmio.read_u32(ddi_offset);
|
||||
ddi &= !DDI_BUF_CTL_DDI_SELECT_MASK;
|
||||
ddi |= DDI_BUF_CTL_ENABLE;
|
||||
ddi |= match config.lane_count {
|
||||
4 => DDI_BUF_CTL_PORT_WIDTH_X4,
|
||||
2 => DDI_BUF_CTL_PORT_WIDTH_X2,
|
||||
_ => DDI_BUF_CTL_PORT_WIDTH_X1,
|
||||
};
|
||||
mmio.write_u32(ddi_offset, ddi);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clock_recovery(aux: &DpAux, lane_count: u8) -> Result<()> {
|
||||
let deadline = Instant::now() + Duration::from_millis(LINK_TIMEOUT_MS);
|
||||
for _try in 0..5 {
|
||||
let status = aux.read_dpcd(DPCD_LANE0_1_STATUS, 4)?;
|
||||
if status.len() < 4 { continue; }
|
||||
let all_done = (0..lane_count).all(|lane| {
|
||||
let s = if lane < 2 { status[lane as usize] } else { status[2 + (lane - 2) as usize] };
|
||||
s & DP_LANE_CR_DONE != 0
|
||||
});
|
||||
if all_done { debug!("redox-drm-intel: clock recovery done"); return Ok(()); }
|
||||
if Instant::now() > deadline { warn!("redox-drm-intel: clock recovery timeout"); return Ok(()); }
|
||||
}
|
||||
warn!("redox-drm-intel: clock recovery incomplete after 5 tries");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn channel_equalization(aux: &DpAux, lane_count: u8) -> Result<()> {
|
||||
let deadline = Instant::now() + Duration::from_millis(LINK_TIMEOUT_MS);
|
||||
for _try in 0..5 {
|
||||
let status = aux.read_dpcd(DPCD_LANE0_1_STATUS, 6)?;
|
||||
if status.len() < 6 { continue; }
|
||||
let all_done = (0..lane_count).all(|lane| {
|
||||
let s = if lane < 2 { status[4 + lane as usize] } else { status[4 + (lane - 2) as usize] };
|
||||
s & (DP_LANE_CR_DONE | DP_LANE_CHANNEL_EQ_DONE) != 0
|
||||
});
|
||||
let align = aux.read_dpcd(DPCD_LANE_ALIGN_STATUS_UPDATED, 1)?;
|
||||
if !align.is_empty() && align[0] & 0x01 != 0 && all_done {
|
||||
debug!("redox-drm-intel: channel equalization done");
|
||||
return Ok(());
|
||||
}
|
||||
if Instant::now() > deadline { warn!("redox-drm-intel: channel equalization timeout"); return Ok(()); }
|
||||
}
|
||||
warn!("redox-drm-intel: channel equalization incomplete after 5 tries");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ pub mod display_dmc;
|
||||
pub mod display_dpll;
|
||||
pub mod display_power;
|
||||
pub mod dp_aux;
|
||||
pub mod dp_link;
|
||||
pub mod gmbus;
|
||||
pub mod gtt;
|
||||
pub mod hotplug;
|
||||
@@ -196,6 +197,14 @@ impl IntelDriver {
|
||||
let dpll = DisplayPll::new(mmio_arc.clone(), &device_info);
|
||||
dpll.init()?;
|
||||
|
||||
if device_info.generation == IntelGeneration::GenXe2 {
|
||||
for port in 0..device_info.num_ports {
|
||||
if port < dp_aux.len() as u8 {
|
||||
let _ = dp_link::train_dp_link(&mmio_arc, &dp_aux[port as usize], port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let display = IntelDisplay::new(display_mmio, regs)?;
|
||||
let mut gtt = IntelGtt::init(gtt_mmio, gtt_control_mmio)?;
|
||||
let mut ring = IntelRing::create(ring_mmio, RingType::Render)?;
|
||||
|
||||
Reference in New Issue
Block a user