From 47fff2b1749e9ba5dfab7528960555cc865360b5 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 14 Jul 2026 18:26:59 +0300 Subject: [PATCH] drm/i915/dp: Disable UHBR link configs with 1/2 lanes Disable 1 and 2 lane link configurations on UHBR tunneled links, according to DP Standard v2.1b "3.5.2.16.3 128b/132b DPRX Lane Count Conversion Failure Indication and Corrective Action". A tunnel can indicate if it's not affected by this limitation, check for that and skip disabling the unsupported lanes for a well-behaving tunnel. The Standard in the same section also describes a workaround for 2 lanes which requires assisstance from the sink, where the sink indicates at the end of the link training sequence if the link training must be retried. This mechanism also requires quirking out some sinks - based on the sink's DPCD OUI and EDID identifications - which doesn't implement this link training feedback indication properly. This patch leaves the implementation of this workaround for a follow-up, but prepares for it already by detecting the supported number of lane counts at a place where both DPCD OUI and EDID is available for the quirk detection. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260714152700.555527-6-imre.deak@intel.com --- .../drm/i915/display/intel_display_types.h | 1 + drivers/gpu/drm/i915/display/intel_dp.c | 7 +++ drivers/gpu/drm/i915/display/intel_dp_mst.c | 5 +++ .../gpu/drm/i915/display/intel_dp_tunnel.c | 45 +++++++++++++++++++ .../gpu/drm/i915/display/intel_dp_tunnel.h | 17 +++++++ 5 files changed, 75 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index 17336ad6d5e9..b7cc361fd955 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1878,6 +1878,7 @@ struct intel_dp { struct drm_dp_tunnel *tunnel; bool tunnel_suspended:1; + u8 disabled_uhbr_lane_mask; struct { struct intel_dp_mst_encoder *stream_encoders[I915_MAX_PIPES]; diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 61b18e056c2a..6bcaba434788 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -3590,6 +3590,8 @@ void intel_dp_reset_link_params(struct intel_dp *intel_dp) * was called. */ intel_dp_link_caps_reset(intel_dp->link.caps); + intel_dp_tunnel_uhbr_lanes_wa_apply(intel_dp); + intel_dp->link.mst_probed_lane_count = 0; intel_dp->link.mst_probed_rate = 0; intel_dp_link_training_reset(intel_dp->link.training); @@ -6242,6 +6244,8 @@ intel_dp_detect(struct drm_connector *_connector, intel_dp_tunnel_disconnect(intel_dp); + intel_dp_tunnel_uhbr_lanes_wa_reset(intel_dp); + goto out_unset_edid; } @@ -6306,6 +6310,9 @@ intel_dp_detect(struct drm_connector *_connector, if (intel_dp_is_edp(intel_dp) || connector->detect_edid) status = connector_status_connected; + if (intel_dp_tunnel_uhbr_lanes_wa_setup(intel_dp)) + intel_dp_tunnel_uhbr_lanes_wa_apply(intel_dp); + out_unset_edid: if (status != connector_status_connected && !intel_dp->is_mst) intel_dp_unset_edid(intel_dp); diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index b5af01159949..3be1643f8d03 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -1453,6 +1453,11 @@ static int mst_connector_get_ddc_modes(struct drm_connector *_connector) drm_edid_free(drm_edid); + if (intel_dp_tunnel_uhbr_lanes_wa_setup(intel_dp)) { + intel_dp_flush_connector_commits(connector); + intel_dp_tunnel_uhbr_lanes_wa_apply(intel_dp); + } + return ret; } diff --git a/drivers/gpu/drm/i915/display/intel_dp_tunnel.c b/drivers/gpu/drm/i915/display/intel_dp_tunnel.c index 49fa4c9699b6..219939634564 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_tunnel.c +++ b/drivers/gpu/drm/i915/display/intel_dp_tunnel.c @@ -831,6 +831,51 @@ void intel_dp_tunnel_atomic_alloc_bw(struct intel_atomic_state *state) atomic_increase_bw(state); } +static u8 lane_count_mask(int lane_count) +{ + return BIT(ilog2(lane_count)); +} + +void intel_dp_tunnel_uhbr_lanes_wa_apply(struct intel_dp *intel_dp) +{ + struct intel_connector *connector = intel_dp->attached_connector; + struct intel_dp_link_caps_order order = + intel_dp_link_caps_connector_compute_order(connector); + struct intel_dp_link_caps *link_caps = intel_dp->link.caps; + struct intel_dp_link_config link_config; + struct intel_dp_link_caps_iter iter; + + if (!intel_dp->disabled_uhbr_lane_mask) + return; + + intel_dp_link_caps_iter_start(&iter, link_caps, order, INTEL_DP_LINK_CAPS_FILTER_ALL); + for_each_dp_link_config(&iter, &link_config) { + if (drm_dp_is_uhbr_rate(link_config.rate) && + lane_count_mask(link_config.lane_count) & intel_dp->disabled_uhbr_lane_mask) + intel_dp_link_caps_disable_config(link_caps, &link_config); + } + intel_dp_link_caps_iter_end(&iter); +} + +bool intel_dp_tunnel_uhbr_lanes_wa_setup(struct intel_dp *intel_dp) +{ + u8 old_mask = intel_dp->disabled_uhbr_lane_mask; + + if (!intel_dp_tunnel_bw_alloc_is_enabled(intel_dp) || + drm_dp_tunnel_128b132b_lane0_mapping_supported(intel_dp->tunnel)) + intel_dp->disabled_uhbr_lane_mask = 0; + else + /* TODO: Add support for keeping 2 lanes enabled as well. */ + intel_dp->disabled_uhbr_lane_mask = lane_count_mask(1) | lane_count_mask(2); + + return intel_dp->disabled_uhbr_lane_mask != old_mask; +} + +void intel_dp_tunnel_uhbr_lanes_wa_reset(struct intel_dp *intel_dp) +{ + intel_dp->disabled_uhbr_lane_mask = 0; +} + /** * intel_dp_tunnel_mgr_init - Initialize the DP tunnel manager * @display: display device diff --git a/drivers/gpu/drm/i915/display/intel_dp_tunnel.h b/drivers/gpu/drm/i915/display/intel_dp_tunnel.h index 8273e681a512..13fc8fbb6e30 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_tunnel.h +++ b/drivers/gpu/drm/i915/display/intel_dp_tunnel.h @@ -54,6 +54,10 @@ int intel_dp_tunnel_atomic_check_state(struct intel_atomic_state *state, void intel_dp_tunnel_atomic_alloc_bw(struct intel_atomic_state *state); +void intel_dp_tunnel_uhbr_lanes_wa_apply(struct intel_dp *intel_dp); +bool intel_dp_tunnel_uhbr_lanes_wa_setup(struct intel_dp *intel_dp); +void intel_dp_tunnel_uhbr_lanes_wa_reset(struct intel_dp *intel_dp); + int intel_dp_tunnel_mgr_init(struct intel_display *display); void intel_dp_tunnel_mgr_cleanup(struct intel_display *display); @@ -129,6 +133,19 @@ intel_dp_tunnel_atomic_alloc_bw(struct intel_atomic_state *state) return 0; } +static inline void intel_dp_tunnel_uhbr_lanes_wa_apply(struct intel_dp *intel_dp) +{ +} + +static inline bool intel_dp_tunnel_uhbr_lanes_wa_setup(struct intel_dp *intel_dp) +{ + return false; +} + +static inline void intel_dp_tunnel_uhbr_lanes_wa_reset(struct intel_dp *intel_dp) +{ +} + static inline int intel_dp_tunnel_mgr_init(struct intel_display *display) {