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

Reply via email to