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 <suraj.kandpal@intel.com>
Signed-off-by: Imre Deak <imre.deak@intel.com>
Link: https://patch.msgid.link/20260714152700.555527-6-imre.deak@intel.com
This commit is contained in:
Imre Deak 2026-07-14 18:26:59 +03:00
parent 0f0950ca8e
commit 47fff2b174
5 changed files with 75 additions and 0 deletions

View File

@ -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];

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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)
{