intel_lt_phy_set_signal_levels() currently maps DDI lane N to PHY lane N / 2, TX N % 2, which matches the DisplayPort lane mapping. HDMI FRL uses a different mapping because the LT PHY swaps main links 0 and 2 to support DP++, with the swap applied before port reversal.
The resulting HDMI lane-to-transmitter mapping is: HDMI lane Normal Reversed --------- ------------ ------------ 0 LN1_TX1 LN0_TX2 1 LN0_TX2 LN1_TX1 2 LN0_TX1 LN1_TX2 3 LN1_TX2 LN0_TX1 (x4 only) Using the DP mapping for HDMI FRL causes per-lane FFE settings to be programmed on the wrong transmitter. For example, an FFE request for lane 0 is applied to lane 2 and vice versa. Fix this by using an HDMI FRL-specific lookup table indexed by lane reversal state and HDMI lane number. Retain the existing mapping for DisplayPort and HDMI TMDS, where lane-specific signal levels are not required. The TX enable masks in intel_lt_phy_enable_disable_tx() already match the Bspec-defined HDMI FRL mapping and require no changes. Bspec: 74701 Signed-off-by: Ankit Nautiyal <[email protected]> --- drivers/gpu/drm/i915/display/intel_lt_phy.c | 33 +++++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_lt_phy.c b/drivers/gpu/drm/i915/display/intel_lt_phy.c index ba03e5f1bb87..6d6ff9fbcce8 100644 --- a/drivers/gpu/drm/i915/display/intel_lt_phy.c +++ b/drivers/gpu/drm/i915/display/intel_lt_phy.c @@ -2419,6 +2419,24 @@ void intel_lt_phy_set_signal_levels(struct intel_encoder *encoder, struct ref_tracker *wakeref; int n_entries, ln; struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + /* + * HDMI 2.1 FRL distributes the DDI lanes across the PHY transmitters in + * a fixed, non-identity order unlike DP which uses + * lane = ln / 2, tx = ln % 2. tx selects LT_PHY_TXY_*(tx) + * within the message-bus lane. Indexed [lane_reversal][ddi_lane]; the + * x3 case uses the first three entries of each row. + */ + static const struct { + u8 lane_mask; + u8 tx; + } frl_lane_map[2][4] = { + /* not reversed: LN1_TX1, LN0_TX2, LN0_TX1, LN1_TX2 */ + { { INTEL_LT_PHY_LANE1, 0 }, { INTEL_LT_PHY_LANE0, 1 }, + { INTEL_LT_PHY_LANE0, 0 }, { INTEL_LT_PHY_LANE1, 1 } }, + /* reversed: LN0_TX2, LN1_TX1, LN1_TX2, LN0_TX1 */ + { { INTEL_LT_PHY_LANE0, 1 }, { INTEL_LT_PHY_LANE1, 0 }, + { INTEL_LT_PHY_LANE1, 1 }, { INTEL_LT_PHY_LANE0, 0 } }, + }; if (intel_tc_port_in_tbt_alt_mode(dig_port)) return; @@ -2435,9 +2453,18 @@ void intel_lt_phy_set_signal_levels(struct intel_encoder *encoder, for (ln = 0; ln < crtc_state->lane_count; ln++) { int level = intel_ddi_level(encoder, crtc_state, ln); - int lane = ln / 2; - int tx = ln % 2; - u8 lane_mask = lane == 0 ? INTEL_LT_PHY_LANE0 : INTEL_LT_PHY_LANE1; + u8 lane_mask; + int tx; + + if (crtc_state->frl.enable) { + lane_mask = frl_lane_map[dig_port->lane_reversal][ln].lane_mask; + tx = frl_lane_map[dig_port->lane_reversal][ln].tx; + } else { + lane_mask = (ln / 2) == 0 ? INTEL_LT_PHY_LANE0 : + INTEL_LT_PHY_LANE1; + tx = ln % 2; + } + if (!(lane_mask & owned_lane_mask)) continue; -- 2.50.1
