Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 0/8] Add support for DisplayPort link training information report
@ 2026-10-22 16:37 Kory Maincent
  2026-10-22 16:37 ` [PATCH v4 1/8] drm: Move sink link rates reading helpers out of i915 Kory Maincent
                   ` (7 more replies)
  0 siblings, 8 replies; 14+ messages in thread
From: Kory Maincent @ 2026-10-22 16:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
	Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
  Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
	Drew Davenport, Louis Chauvet, dri-devel, linux-kernel, intel-gfx,
	intel-xe, linux-mediatek, linux-arm-kernel, Kory Maincent

DisplayPort link training negotiates the physical-layer parameters needed
for a reliable connection: lane count, link rate, and optionally Display
Stream Compression (DSC). Currently, each driver exposes this state in
its own way, often through driver-specific debugfs entries, with no
standard interface for userspace diagnostic and monitoring tools.

This series introduces generic helpers, for exposing DP link capabilities
and state as standard sysfs entries.

The aim of such development is to guide users to select the most suitable
DisplayPort connector for their needs. For example, if you have a USB-C
hub with lesser capabilities than your computer’s native DisplayPort
connector (such as HBR2 versus HBR3 support), the system could recommend
connecting high-resolution displays directly to the computer’s port
instead of through the hub to ensure optimal performance.

The new drm_dp_source_set_caps and drm_dp_sink_set_caps helper expose
source and sink training capabilities to userspace via sysfs attributes
under dp_link. Finally drm_dp_set_cur_link_params helper is provided to
report current link information at runtime.

Three drivers are updated as reference implementations: i915 (direct
connector path), ANX7625 and MediaTek (via the bridge connector framework
using a new DRM_BRIDGE_OP_DP flag).

Here is a sysfs example:

cur_link_info_dsc            sink_link_caps_nlanes
cur_link_info_link_rate      source_link_caps_dsc
cur_link_info_nlanes         source_link_caps_link_rates
sink_link_caps_dsc           source_link_caps_nlanes
sink_link_caps_link_rates

1620000 2160000 2700000 3240000 4320000 5400000

1620000 2700000 5400000 10000000 20000000

5400000

This series is rebased on top of drm-misc and may conflict with the
current state of the drm i915 tree. Which tree should pick this up,
given that it mixes core development with driver changes?

Patch 1,2: Move helpers out of i915
Patch 3: Introduce DisplayPort link training capabilities and state helpers
Patch 4: Read i915 DSC capabilities in any case
Patch 5  Use dp_connector helpers in i915 to expose link training state
Patch 6: Introduce DRM_BRIDGE_OP_DP and wire bridge connectors
Patch 7: Use dp_connector helpers in anx7625 to expose link training state
Patch 8: Use dp_connector helpers in mediatek to expose link training state

Changes in v4:
- Add a new mutex to protect sysfs group update
- Remove the check on the connection_mutex
- Add the newly supported DisplayPort tunnel features in the series
- Link to v3: https://patch.msgid.link/20261012-feat_link_cap-v3-0-7517f4aba0f3@bootlin.com

Changes in v3:
- Rework the core to use new helpers instead of an extension of
  drmm_connector_init().
- Add a new mutex to protect sysfs read write access instead of using
  the global connection_mutex.
- Move helpers out of i915.
- Add the support in the anx7625 driver.
- Link to v2: https://patch.msgid.link/20260619-feat_link_cap-v2-0-a3dec4c02ad9@bootlin.com

Changes in v2:
- Removed work converting i915 to DRM managed resource
- Remove voltage swing and pre-emphasis properties
- Expose link training state via sysfs dp_link/ group instead of
  connector properties
- Add comprehensive sysfs attributes for both source and sink capabilities
- Add new helpers for managing sink capabilities and for current link
  parameters
- Link to v1: https://lore.kernel.org/r/20260409-feat_link_cap-v1-0-7069e8199ce2@bootlin.com

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Kory Maincent (8):
      drm: Move sink link rates reading helpers out of i915
      drm: Move DSC capability reading helper out of i915
      drm: Introduce DisplayPort link training capabilities and state helpers
      drm/i915/display/dp: Read DPCD DSC capabilities in any case
      drm/i915/display/dp: Use dp_connector helpers to expose link training state
      drm/bridge: Expose link training state via new DRM_BRIDGE_OP_DP flag
      drm/analogix/anx7625: Use dp_connector helpers to expose link training state
      drm/mediatek: Use dp_connector helpers to expose link training state

 Documentation/ABI/testing/sysfs-class-drm-dp       |  85 +++
 drivers/gpu/drm/bridge/analogix/anx7625.c          |  68 +++
 drivers/gpu/drm/bridge/analogix/anx7625.h          |   1 +
 drivers/gpu/drm/display/Kconfig                    |   1 +
 drivers/gpu/drm/display/drm_bridge_connector.c     |  29 +
 drivers/gpu/drm/display/drm_dp_helper.c            | 590 +++++++++++++++++++++
 drivers/gpu/drm/drm_connector.c                    |  13 +
 drivers/gpu/drm/drm_sysfs.c                        | 160 ++++++
 drivers/gpu/drm/i915/display/intel_display_types.h |   2 +
 drivers/gpu/drm/i915/display/intel_dp.c            | 264 +++------
 .../gpu/drm/i915/display/intel_dp_link_training.c  |  14 +
 drivers/gpu/drm/mediatek/mtk_dp.c                  |  61 ++-
 include/drm/display/drm_dp.h                       |   1 +
 include/drm/display/drm_dp_helper.h                |  25 +
 include/drm/drm_bridge.h                           |  13 +
 include/drm/drm_connector.h                        |  59 +++
 include/drm/drm_sysfs.h                            |   1 +
 17 files changed, 1200 insertions(+), 187 deletions(-)
---
base-commit: 932326f098877d9650c80ed428d82fc2dc14e841
change-id: 20260226-feat_link_cap-20cbb6f31d40

Best regards,
--  
Köry Maincent, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v4 1/8] drm: Move sink link rates reading helpers out of i915
  2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
@ 2026-10-22 16:37 ` Kory Maincent
  2026-10-22 16:56   ` sashiko-bot
  2026-10-22 16:37 ` [PATCH v4 2/8] drm: Move DSC capability reading helper " Kory Maincent
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Kory Maincent @ 2026-10-22 16:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
	Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
  Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
	Drew Davenport, Louis Chauvet, dri-devel, linux-kernel, intel-gfx,
	intel-xe, linux-mediatek, linux-arm-kernel, Kory Maincent

In preparation for adding generic DRM properties to report source and
sink capabilities, move the sink link rates reading functions out of
the i915 driver into the generic DRM helpers. These functions provide
everything needed to properly extract link rate capabilities from the
DPCD.

Keep the Intel-specific quirk handling in the driver.

No functional change intended.

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Change in v4:
- Modify the series to incorporate the new tunnel features supported in the
  i915 driver.

Change in v3:
- New patch
---
 drivers/gpu/drm/display/drm_dp_helper.c | 254 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/i915/display/intel_dp.c | 184 ++++-------------------
 include/drm/display/drm_dp.h            |   1 +
 include/drm/display/drm_dp_helper.h     |  11 ++
 4 files changed, 294 insertions(+), 156 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c
index e623ccb4c1d8a..3988da0a01fac 100644
--- a/drivers/gpu/drm/display/drm_dp_helper.c
+++ b/drivers/gpu/drm/display/drm_dp_helper.c
@@ -37,6 +37,7 @@
 
 #include <drm/display/drm_dp_helper.h>
 #include <drm/display/drm_dp_mst_helper.h>
+#include <drm/display/drm_dp_tunnel.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_fixed.h>
 #include <drm/drm_print.h>
@@ -4912,3 +4913,256 @@ int drm_dp_max_dprx_data_rate(int max_link_rate, int max_lanes)
 				  1000000 * 8);
 }
 EXPORT_SYMBOL(drm_dp_max_dprx_data_rate);
+
+/**
+ * drm_dp_read_dpcd_supported_link_rates() - read DPCD supported link rates
+ * @aux: DisplayPort AUX channel
+ * @link_rates: Buffer to store the resulting link rates supported
+ *
+ * Attempts to read the DPCD supported link rates for @aux.
+ *
+ * Returns: The number or supported link rates, negative error code otherwise.
+ */
+static int drm_dp_read_dpcd_supported_link_rates(struct drm_dp_aux *aux,
+						 int link_rates[DP_MAX_SUPPORTED_RATES])
+{
+	__le16 dpcd_link_rates[DP_MAX_SUPPORTED_RATES] = {0};
+	int ret, i;
+
+	ret = drm_dp_dpcd_read_data(aux, DP_SUPPORTED_LINK_RATES,
+				    dpcd_link_rates, sizeof(dpcd_link_rates));
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(dpcd_link_rates) && dpcd_link_rates[i]; i++)
+		link_rates[i] = le16_to_cpu(dpcd_link_rates[i]) * 20;
+
+	return i;
+}
+
+#if IS_ENABLED(CONFIG_DRM_DISPLAY_DP_TUNNEL)
+
+static int drm_dp_max_dprx_rate(const struct drm_dp_tunnel *tunnel,
+				const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+{
+	if (tunnel && drm_dp_tunnel_bw_alloc_is_enabled(tunnel))
+		return drm_dp_tunnel_max_dprx_rate(tunnel);
+	else
+		return drm_dp_bw_code_to_link_rate(dpcd[DP_MAX_LINK_RATE]);
+}
+
+static u8 drm_dprx_128b132b_link_rates(const struct drm_dp_tunnel *tunnel,
+				       u8 uhbr_rates)
+{
+	if (!drm_dp_tunnel_bw_alloc_is_enabled(tunnel))
+		return uhbr_rates;
+
+	static_assert(DP_TUNNELING_10GBPS_PER_LANE_SUPPORT == DP_UHBR10 &&
+		      DP_TUNNELING_13_5GBPS_PER_LANE_SUPPORT == DP_UHBR13_5 &&
+		      DP_TUNNELING_20GBPS_PER_LANE_SUPPORT == DP_UHBR20);
+
+	return drm_dp_tunnel_128b132b_dprx_rates(tunnel);
+}
+
+static bool drm_dprx_supports_128b132b(const struct drm_dp_tunnel *tunnel,
+				       const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+{
+	if (tunnel && drm_dp_tunnel_bw_alloc_is_enabled(tunnel))
+		return drm_dp_tunnel_128b132b_supported(tunnel);
+	else
+		return drm_dp_128b132b_supported(dpcd);
+}
+
+#else
+
+static int drm_dp_max_dprx_rate(const struct drm_dp_tunnel *tunnel,
+				const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+{
+	return drm_dp_bw_code_to_link_rate(dpcd[DP_MAX_LINK_RATE]);
+}
+
+static u8 drm_dprx_128b132b_link_rates(const struct drm_dp_tunnel *tunnel,
+				       u8 uhbr_rates)
+{
+	return uhbr_rates;
+}
+
+static bool drm_dprx_supports_128b132b(const struct drm_dp_tunnel *tunnel,
+				       const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+{
+		return drm_dp_128b132b_supported(dpcd);
+}
+
+#endif
+
+/**
+ * drm_dp_get_128b132b_link_rates() - get the supported 128b/132b link rates
+ * @lttpr_common_caps: LTTPR common capabilities
+ * @tunnel: DP tunnel object (NULL if not supported)
+ * @uhbr_rates: UHBR rate mask read from the DPCD
+ *		DP_128B132B_SUPPORTED_LINK_RATES register
+ * @link_rates: Buffer of DP_MAX_128B132B_SUPPORTED_RATES entries to store the
+ *		resulting link rates
+ *
+ * Limit the UHBR rate mask @uhbr_rates by the capabilities of the DP tunnel
+ * and of the LTTPRs, if any is present, then expand the remaining rates into
+ * @link_rates in ascending order.
+ *
+ * Returns: The number of link rates stored in @link_rates.
+ */
+static int
+drm_dp_get_128b132b_link_rates(const u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE],
+			       const struct drm_dp_tunnel *tunnel,
+			       u8 uhbr_rates,
+			       int link_rates[DP_MAX_128B132B_SUPPORTED_RATES])
+{
+	int i = 0;
+
+	if (tunnel)
+		uhbr_rates = drm_dprx_128b132b_link_rates(tunnel, uhbr_rates);
+
+	if (lttpr_common_caps && drm_dp_lttpr_count(lttpr_common_caps) > 0) {
+		/* We have a repeater */
+		if (lttpr_common_caps[0] >= 0x20 &&
+		    dp_lttpr_common_cap(lttpr_common_caps,
+					DP_MAIN_LINK_CHANNEL_CODING_PHY_REPEATER) &
+		    DP_PHY_REPEATER_128B132B_SUPPORTED) {
+			/* Repeater supports 128b/132b, valid UHBR rates */
+			uhbr_rates &= dp_lttpr_common_cap(lttpr_common_caps,
+							  DP_PHY_REPEATER_128B132B_RATES);
+		} else {
+			/* Does not support 128b/132b */
+			uhbr_rates = 0;
+		}
+	}
+
+	if (uhbr_rates & DP_UHBR10)
+		link_rates[i++] = 1000000;
+	if (uhbr_rates & DP_UHBR13_5)
+		link_rates[i++] = 1350000;
+	if (uhbr_rates & DP_UHBR20)
+		link_rates[i++] = 2000000;
+
+	return i;
+}
+
+/**
+ * drm_dp_read_dpcd_sink_rates() - read the sink rates from the DPCD
+ * @aux: DisplayPort AUX channel
+ * @dpcd: DPCD receiver capabilities
+ * @lttpr_common_caps: LTTPR common capabilities
+ * @desc: DisplayPort sink descriptor
+ * @tunnel: Tunnel object (NULL if not supported)
+ * @sink_rates: Array of DP_MAX_SUPPORTED_RATES entries to store the sink rates
+ *
+ * Read the link rates supported by a DisplayPort sink from the DPCD
+ * DP_MAX_LINK_RATE and DP_128B132B_SUPPORTED_LINK_RATES registers, limited by
+ * the capabilities of the LTTPRs or the tunnel if any is present.
+ * The rates are stored in @sink_rates in ascending order.
+ *
+ * The DPCD receiver capabilities, the LTTPR common capabilities, the tunnel
+ * and the sink descriptor are passed by the caller instead of being read
+ * from the DPCD. Drivers which already read them, like i915, can pass them
+ * to avoid duplicate DPCD reads.
+ *
+ * Returns: The number of supported sink rates.
+ */
+int drm_dp_read_dpcd_sink_rates(struct drm_dp_aux *aux,
+				const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+				const u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE],
+				const struct drm_dp_desc *desc,
+				const struct drm_dp_tunnel *tunnel,
+				int sink_rates[DP_MAX_SUPPORTED_RATES])
+{
+	static const int dp_rates[] = {
+		162000, 270000, 540000, 810000
+	};
+	int max_rate, max_lttpr_rate = 0, ret, i;
+	u8 uhbr_rates = 0;
+
+	BUILD_BUG_ON(ARRAY_SIZE(dp_rates) + DP_MAX_128B132B_SUPPORTED_RATES >
+		     DP_MAX_SUPPORTED_RATES);
+
+	if (drm_dp_has_quirk(desc, DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS)) {
+		/* Needed, e.g., for Apple MBP 2017, 15 inch eDP Retina panel */
+		static const int quirk_rates[] = { 162000, 270000, 324000 };
+
+		memcpy(sink_rates, quirk_rates, sizeof(quirk_rates));
+
+		return ARRAY_SIZE(quirk_rates);
+	}
+
+	/*
+	 * Sink rates for 8b/10b.
+	 */
+	max_rate = drm_dp_max_dprx_rate(tunnel, dpcd);
+	if (lttpr_common_caps)
+		max_lttpr_rate = drm_dp_lttpr_max_link_rate(lttpr_common_caps);
+
+	if (max_lttpr_rate)
+		max_rate = min(max_rate, max_lttpr_rate);
+
+	for (i = 0; i < ARRAY_SIZE(dp_rates); i++) {
+		if (dp_rates[i] > max_rate)
+			break;
+		sink_rates[i] = dp_rates[i];
+	}
+
+	/*
+	 * The following register must be read unconditionally for the later
+	 * DP tunnel 128b132b detection to work, see DP Standard v2.1 5.14.3 .
+	 */
+	ret = drm_dp_dpcd_read_byte(aux, DP_128B132B_SUPPORTED_LINK_RATES,
+				    &uhbr_rates);
+	if (ret < 0) {
+		drm_dbg_kms(aux->drm_dev,
+			    "%s: Failed to read DPCD 128B/132B supported link rates (%d)\n",
+			    aux->name, ret);
+		uhbr_rates = 0;
+	}
+
+	/*
+	 * Sink rates for 128b/132b. If set, sink should support all 8b/10b
+	 * rates and 10 Gbps.
+	 */
+	if (drm_dprx_supports_128b132b(tunnel, dpcd))
+		i += drm_dp_get_128b132b_link_rates(lttpr_common_caps,
+						    tunnel,
+						    uhbr_rates,
+						    &sink_rates[i]);
+
+	return i;
+}
+EXPORT_SYMBOL_GPL(drm_dp_read_dpcd_sink_rates);
+
+/**
+ * drm_dp_read_edp_sink_rates() - read the sink rates from the eDP DPCD
+ * @aux: DisplayPort AUX channel
+ * @edp_dpcd_rev: eDP DPCD revision of the sink
+ * @sink_rates: Array of DP_MAX_SUPPORTED_RATES entries to store the sink rates
+ *
+ * Read the link rates supported by an eDP sink from the DPCD
+ * DP_SUPPORTED_LINK_RATES registers, available on eDP 1.4 and later sinks.
+ *
+ * Returns: The number of supported sink rates, 0 if the sink does not support
+ * the DP_SUPPORTED_LINK_RATES register.
+ */
+int drm_dp_read_edp_sink_rates(struct drm_dp_aux *aux, u8 edp_dpcd_rev,
+			       int sink_rates[DP_MAX_SUPPORTED_RATES])
+{
+	int num_sink_rates;
+
+	if (edp_dpcd_rev < DP_EDP_14)
+		return 0;
+
+	num_sink_rates = drm_dp_read_dpcd_supported_link_rates(aux, sink_rates);
+	if (num_sink_rates < 0) {
+		drm_dbg_kms(aux->drm_dev,
+			    "%s: Unable to read eDP supported link rates (%d)\n",
+			    aux->name, num_sink_rates);
+		return 0;
+	}
+
+	return num_sink_rates;
+}
+EXPORT_SYMBOL_GPL(drm_dp_read_edp_sink_rates);
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
index 0cd5e6b5034cf..087afcea5395f 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -155,28 +155,6 @@ int intel_dp_link_symbol_clock(int rate)
 	return DIV_ROUND_CLOSEST(rate * 10, intel_dp_link_symbol_size(rate));
 }
 
-static int max_dprx_rate(struct intel_dp *intel_dp)
-{
-	struct intel_display *display = to_intel_display(intel_dp);
-	int max_rate;
-
-	if (intel_dp_tunnel_bw_alloc_is_enabled(intel_dp))
-		max_rate = drm_dp_tunnel_max_dprx_rate(intel_dp->tunnel);
-	else
-		max_rate = drm_dp_bw_code_to_link_rate(intel_dp->dpcd[DP_MAX_LINK_RATE]);
-
-	/*
-	 * Some platforms + eDP panels may not reliably support HBR3
-	 * due to signal integrity limitations, despite advertising it.
-	 * Cap the link rate to HBR2 to avoid unstable configurations for the
-	 * known machines.
-	 */
-	if (intel_dp_is_edp(intel_dp) && intel_has_quirk(display, QUIRK_EDP_LIMIT_RATE_HBR2))
-		max_rate = min(max_rate, 540000);
-
-	return max_rate;
-}
-
 static int max_dprx_lane_count(struct intel_dp *intel_dp)
 {
 	if (intel_dp_tunnel_bw_alloc_is_enabled(intel_dp))
@@ -191,102 +169,21 @@ static void intel_dp_set_default_sink_rates(struct intel_dp *intel_dp)
 	intel_dp->num_sink_rates = 1;
 }
 
-static bool dprx_supports_128b132b(struct intel_dp *intel_dp)
-{
-	if (intel_dp_tunnel_bw_alloc_is_enabled(intel_dp))
-		return drm_dp_tunnel_128b132b_supported(intel_dp->tunnel);
-	else
-		return drm_dp_128b132b_supported(intel_dp->dpcd);
-}
-
-static u8 dprx_128b132b_link_rates(struct intel_dp *intel_dp, u8 no_bwa_rates)
-{
-	u8 ret;
-
-	if (!intel_dp_tunnel_bw_alloc_is_enabled(intel_dp))
-		return no_bwa_rates;
-
-	ret = drm_dp_tunnel_128b132b_dprx_rates(intel_dp->tunnel);
-	static_assert(DP_TUNNELING_10GBPS_PER_LANE_SUPPORT == DP_UHBR10 &&
-		      DP_TUNNELING_13_5GBPS_PER_LANE_SUPPORT == DP_UHBR13_5 &&
-		      DP_TUNNELING_20GBPS_PER_LANE_SUPPORT == DP_UHBR20);
-
-	return ret;
-}
-
 /* update sink rates from dpcd */
 static void intel_dp_set_dpcd_sink_rates(struct intel_dp *intel_dp)
 {
-	static const int dp_rates[] = {
-		162000, 270000, 540000, 810000
-	};
-	int i, max_rate;
-	int max_lttpr_rate;
-	u8 uhbr_rates = 0;
-
-	if (drm_dp_has_quirk(&intel_dp->desc, DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS)) {
-		/* Needed, e.g., for Apple MBP 2017, 15 inch eDP Retina panel */
-		static const int quirk_rates[] = { 162000, 270000, 324000 };
-
-		memcpy(intel_dp->sink_rates, quirk_rates, sizeof(quirk_rates));
-		intel_dp->num_sink_rates = ARRAY_SIZE(quirk_rates);
-
-		return;
-	}
-
-	/*
-	 * Sink rates for 8b/10b.
-	 */
-	max_rate = max_dprx_rate(intel_dp);
-	max_lttpr_rate = drm_dp_lttpr_max_link_rate(intel_dp->lttpr_common_caps);
-	if (max_lttpr_rate)
-		max_rate = min(max_rate, max_lttpr_rate);
+	int num_sink_rates;
 
-	for (i = 0; i < ARRAY_SIZE(dp_rates); i++) {
-		if (dp_rates[i] > max_rate)
-			break;
-		intel_dp->sink_rates[i] = dp_rates[i];
-	}
+	num_sink_rates = drm_dp_read_dpcd_sink_rates(&intel_dp->aux,
+						     intel_dp->dpcd,
+						     intel_dp->lttpr_common_caps,
+						     &intel_dp->desc,
+						     intel_dp->tunnel,
+						     intel_dp->sink_rates);
+	if (num_sink_rates < 0)
+		num_sink_rates = 0;
 
-	/*
-	 * The following register must be read unconditionally for the later
-	 * DP tunnel 128b132b detection to work, see DP Standard v2.1 5.14.3 .
-	 */
-	drm_dp_dpcd_read_byte(&intel_dp->aux, DP_128B132B_SUPPORTED_LINK_RATES, &uhbr_rates);
-
-	/*
-	 * Sink rates for 128b/132b. If set, sink should support all 8b/10b
-	 * rates and 10 Gbps.
-	 */
-	if (dprx_supports_128b132b(intel_dp)) {
-		BUILD_BUG_ON(ARRAY_SIZE(intel_dp->sink_rates) < ARRAY_SIZE(dp_rates) + 3);
-
-		uhbr_rates = dprx_128b132b_link_rates(intel_dp, uhbr_rates);
-
-		if (drm_dp_lttpr_count(intel_dp->lttpr_common_caps)) {
-			/* We have a repeater */
-			if (intel_dp->lttpr_common_caps[0] >= 0x20 &&
-			    intel_dp->lttpr_common_caps[DP_MAIN_LINK_CHANNEL_CODING_PHY_REPEATER -
-							DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV] &
-			    DP_PHY_REPEATER_128B132B_SUPPORTED) {
-				/* Repeater supports 128b/132b, valid UHBR rates */
-				uhbr_rates &= intel_dp->lttpr_common_caps[DP_PHY_REPEATER_128B132B_RATES -
-									  DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV];
-			} else {
-				/* Does not support 128b/132b */
-				uhbr_rates = 0;
-			}
-		}
-
-		if (uhbr_rates & DP_UHBR10)
-			intel_dp->sink_rates[i++] = 1000000;
-		if (uhbr_rates & DP_UHBR13_5)
-			intel_dp->sink_rates[i++] = 1350000;
-		if (uhbr_rates & DP_UHBR20)
-			intel_dp->sink_rates[i++] = 2000000;
-	}
-
-	intel_dp->num_sink_rates = i;
+	intel_dp->num_sink_rates = num_sink_rates;
 }
 
 static void intel_dp_set_sink_rates(struct intel_dp *intel_dp)
@@ -4722,60 +4619,35 @@ static void
 intel_edp_set_sink_rates(struct intel_dp *intel_dp)
 {
 	struct intel_display *display = to_intel_display(intel_dp);
+	int num_sink_rates;
 
-	intel_dp->num_sink_rates = 0;
+	num_sink_rates = drm_dp_read_edp_sink_rates(&intel_dp->aux,
+						    intel_dp->edp_dpcd[0],
+						    intel_dp->sink_rates);
+	if (num_sink_rates < 0)
+		num_sink_rates = 0;
 
-	if (intel_dp->edp_dpcd[0] >= DP_EDP_14) {
-		__le16 sink_rates[DP_MAX_SUPPORTED_RATES];
-		int ret;
-		int i;
-
-		ret = drm_dp_dpcd_read_data(&intel_dp->aux,
-					    DP_SUPPORTED_LINK_RATES,
-					    sink_rates, sizeof(sink_rates));
-		if (ret < 0) {
-			drm_dbg_kms(display->drm,
-				    "Unable to read eDP supported link rates, using default rates\n");
-			memset(sink_rates, 0, sizeof(sink_rates));
-		}
-
-		for (i = 0; i < ARRAY_SIZE(sink_rates); i++) {
-			int rate;
-
-			/* Value read multiplied by 200kHz gives the per-lane
-			 * link rate in kHz. The source rates are, however,
-			 * stored in terms of LS_Clk kHz. The full conversion
-			 * back to symbols is
-			 * (val * 200kHz)*(8/10 ch. encoding)*(1/8 bit to Byte)
-			 */
-			rate = le16_to_cpu(sink_rates[i]) * 200 / 10;
-
-			if (rate == 0)
-				break;
-
-			/*
-			 * Some platforms cannot reliably drive HBR3 rates due to PHY limitations,
-			 * even if the sink advertises support. Reject any sink rates above HBR2 on
-			 * the known machines for stable output.
-			 */
-			if (rate > 540000 &&
-			    intel_has_quirk(display, QUIRK_EDP_LIMIT_RATE_HBR2))
-				break;
-
-			intel_dp->sink_rates[i] = rate;
-		}
-		intel_dp->num_sink_rates = i;
-	}
+	intel_dp->num_sink_rates = num_sink_rates;
 
 	/*
 	 * Use DP_LINK_RATE_SET if DP_SUPPORTED_LINK_RATES are available,
 	 * default to DP_MAX_LINK_RATE and DP_LINK_BW_SET otherwise.
 	 */
-	if (intel_dp->num_sink_rates)
+	if (num_sink_rates)
 		intel_dp->use_rate_select = true;
 	else
 		intel_dp_set_sink_rates(intel_dp);
 
+	/*
+	 * Some platforms cannot reliably drive HBR3 rates due to PHY limitations,
+	 * even if the sink advertises support. Reject any sink rates above HBR2 on
+	 * the known machines for stable output.
+	 */
+	if (intel_dp->num_sink_rates && intel_has_quirk(display, QUIRK_EDP_LIMIT_RATE_HBR2))
+		intel_dp->num_sink_rates = intel_dp_rate_limit_len(intel_dp->sink_rates,
+								   intel_dp->num_sink_rates,
+								   540000);
+
 	intel_edp_set_data_override_rates(intel_dp);
 }
 
diff --git a/include/drm/display/drm_dp.h b/include/drm/display/drm_dp.h
index e63c7f075f54c..9db924ff7c53e 100644
--- a/include/drm/display/drm_dp.h
+++ b/include/drm/display/drm_dp.h
@@ -1228,6 +1228,7 @@
 # define DP_UHBR10                             (1 << 0)
 # define DP_UHBR20                             (1 << 1)
 # define DP_UHBR13_5                           (1 << 2)
+# define DP_MAX_128B132B_SUPPORTED_RATES	3
 
 #define DP_128B132B_TRAINING_AUX_RD_INTERVAL                    0x2216 /* 2.0 */
 # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_1MS_UNIT          (1 << 7)
diff --git a/include/drm/display/drm_dp_helper.h b/include/drm/display/drm_dp_helper.h
index 5b05a7fbe174a..d7f4c6f2ba4c7 100644
--- a/include/drm/display/drm_dp_helper.h
+++ b/include/drm/display/drm_dp_helper.h
@@ -32,6 +32,8 @@
 struct drm_device;
 struct drm_dp_aux;
 struct drm_panel;
+struct drm_dp_tunnel;
+struct drm_dp_desc;
 
 bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE],
 			  int lane_count);
@@ -700,6 +702,15 @@ static inline int drm_dp_dpcd_write_byte(struct drm_dp_aux *aux,
 int drm_dp_read_dpcd_caps(struct drm_dp_aux *aux,
 			  u8 dpcd[DP_RECEIVER_CAP_SIZE]);
 
+int drm_dp_read_dpcd_sink_rates(struct drm_dp_aux *aux,
+				const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+				const u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE],
+				const struct drm_dp_desc *desc,
+				const struct drm_dp_tunnel *tunnel,
+				int sink_rates[DP_MAX_SUPPORTED_RATES]);
+int drm_dp_read_edp_sink_rates(struct drm_dp_aux *aux, u8 edp_dpcd_rev,
+			       int sink_rates[DP_MAX_SUPPORTED_RATES]);
+
 int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
 				 u8 status[DP_LINK_STATUS_SIZE]);
 

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 2/8] drm: Move DSC capability reading helper out of i915
  2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
  2026-10-22 16:37 ` [PATCH v4 1/8] drm: Move sink link rates reading helpers out of i915 Kory Maincent
@ 2026-10-22 16:37 ` Kory Maincent
  2026-10-22 16:37 ` [PATCH v4 3/8] drm: Introduce DisplayPort link training capabilities and state helpers Kory Maincent
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Kory Maincent @ 2026-10-22 16:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
	Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
  Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
	Drew Davenport, Louis Chauvet, dri-devel, linux-kernel, intel-gfx,
	intel-xe, linux-mediatek, linux-arm-kernel, Kory Maincent

In preparation for adding generic DRM properties to report source and
sink capabilities, move the DSC capability reading helper out of the
i915 driver into the generic DRM helpers.

No functional change intended.

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Change in v3:
- New patch
---
 drivers/gpu/drm/display/drm_dp_helper.c | 30 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/i915/display/intel_dp.c | 28 ++++------------------------
 include/drm/display/drm_dp_helper.h     |  2 ++
 3 files changed, 36 insertions(+), 24 deletions(-)

diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c
index 3988da0a01fac..8473dbcd8808a 100644
--- a/drivers/gpu/drm/display/drm_dp_helper.c
+++ b/drivers/gpu/drm/display/drm_dp_helper.c
@@ -5166,3 +5166,33 @@ int drm_dp_read_edp_sink_rates(struct drm_dp_aux *aux, u8 edp_dpcd_rev,
 	return num_sink_rates;
 }
 EXPORT_SYMBOL_GPL(drm_dp_read_edp_sink_rates);
+
+/**
+ * drm_dp_read_dsc_dpcd() - read the DSC capabilities from the DPCD
+ * @aux: DisplayPort AUX channel
+ * @dsc_dpcd: Buffer to store the DSC capabilities read from the DPCD
+ *
+ * Attempts to read the sink DSC capabilities from the DPCD of @aux.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int drm_dp_read_dsc_dpcd(struct drm_dp_aux *aux,
+			 u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE])
+{
+	int ret;
+
+	ret = drm_dp_dpcd_read_data(aux, DP_DSC_SUPPORT, dsc_dpcd,
+				    DP_DSC_RECEIVER_CAP_SIZE);
+	if (ret) {
+		drm_dbg_kms(aux->drm_dev,
+			    "Could not read DSC DPCD register 0x%x Error: %pe\n",
+			    DP_DSC_SUPPORT, ERR_PTR(ret));
+		return ret;
+	}
+
+	drm_dbg_kms(aux->drm_dev, "DSC DPCD: %*ph\n",
+		    DP_DSC_RECEIVER_CAP_SIZE,
+		    dsc_dpcd);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(drm_dp_read_dsc_dpcd);
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
index 087afcea5395f..89d4b3d348fa8 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -4382,26 +4382,6 @@ static bool intel_dp_get_sst_split_sdp_status(u8 dprx)
 	return dprx & DP_SST_SPLIT_SDP_CAP;
 }
 
-static int intel_dp_read_dsc_dpcd(struct drm_dp_aux *aux,
-				  u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE])
-{
-	int ret;
-
-	ret = drm_dp_dpcd_read_data(aux, DP_DSC_SUPPORT, dsc_dpcd,
-				    DP_DSC_RECEIVER_CAP_SIZE);
-	if (ret) {
-		drm_dbg_kms(aux->drm_dev,
-			    "Could not read DSC DPCD register 0x%x Error: %pe\n",
-			    DP_DSC_SUPPORT, ERR_PTR(ret));
-		return ret;
-	}
-
-	drm_dbg_kms(aux->drm_dev, "DSC DPCD: %*ph\n",
-		    DP_DSC_RECEIVER_CAP_SIZE,
-		    dsc_dpcd);
-	return 0;
-}
-
 static void init_dsc_overall_throughput_limits(struct intel_connector *connector, bool is_branch)
 {
 	u8 branch_caps[DP_DSC_BRANCH_CAP_SIZE];
@@ -4450,8 +4430,8 @@ void intel_dp_get_dsc_sink_cap(u8 dpcd_rev,
 	if (dpcd_rev < DP_DPCD_REV_14)
 		return;
 
-	if (intel_dp_read_dsc_dpcd(connector->dp.dsc_decompression_aux,
-				   connector->dp.dsc_dpcd) < 0)
+	if (drm_dp_read_dsc_dpcd(connector->dp.dsc_decompression_aux,
+				 connector->dp.dsc_dpcd) < 0)
 		return;
 
 	if (drm_dp_dpcd_readb(connector->dp.dsc_decompression_aux, DP_FEC_CAPABILITY,
@@ -4482,8 +4462,8 @@ static void intel_edp_get_dsc_sink_cap(u8 edp_dpcd_rev, struct intel_connector *
 	if (edp_dpcd_rev < DP_EDP_14)
 		return;
 
-	if (intel_dp_read_dsc_dpcd(connector->dp.dsc_decompression_aux,
-				   connector->dp.dsc_dpcd) < 0)
+	if (drm_dp_read_dsc_dpcd(connector->dp.dsc_decompression_aux,
+				 connector->dp.dsc_dpcd) < 0)
 		return;
 
 	if (connector->dp.dsc_dpcd[0] & DP_DSC_DECOMPRESSION_IS_SUPPORTED)
diff --git a/include/drm/display/drm_dp_helper.h b/include/drm/display/drm_dp_helper.h
index d7f4c6f2ba4c7..7bc3b36f38b82 100644
--- a/include/drm/display/drm_dp_helper.h
+++ b/include/drm/display/drm_dp_helper.h
@@ -215,6 +215,8 @@ drm_dp_is_branch(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
 }
 
 /* DP/eDP DSC support */
+int drm_dp_read_dsc_dpcd(struct drm_dp_aux *aux,
+			 u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]);
 u8 drm_dp_dsc_sink_bpp_incr(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]);
 u32 drm_dp_dsc_slice_count_to_mask(int slice_count);
 u32 drm_dp_dsc_sink_slice_count_mask(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE],

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 3/8] drm: Introduce DisplayPort link training capabilities and state helpers
  2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
  2026-10-22 16:37 ` [PATCH v4 1/8] drm: Move sink link rates reading helpers out of i915 Kory Maincent
  2026-10-22 16:37 ` [PATCH v4 2/8] drm: Move DSC capability reading helper " Kory Maincent
@ 2026-10-22 16:37 ` Kory Maincent
  2026-10-22 17:02   ` sashiko-bot
  2026-10-22 16:38 ` [PATCH v4 4/8] drm/i915/display/dp: Read DPCD DSC capabilities in any case Kory Maincent
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Kory Maincent @ 2026-10-22 16:37 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
	Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
  Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
	Drew Davenport, Louis Chauvet, dri-devel, linux-kernel, intel-gfx,
	intel-xe, linux-mediatek, linux-arm-kernel, Kory Maincent

Add helpers to expose link training capabilities and state to userspace
via sysfs attributes.

Link rates are passed by the driver in deca-kbps, following the DRM
convention, but exposed to sysfs userspace in kbps for clarity.

The aim of such development is to guide users to select the most suitable
DisplayPort connector for their needs. For example, if you have a USB-C
hub with lesser capabilities than your computer’s native DisplayPort
connector (such as HBR2 versus HBR3 support), the system could recommend
connecting high-resolution displays directly to the computer’s port
instead of through the hub to ensure optimal performance.

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
If the needs arise we can add sysfs files for sink and source common
capabilities and hardware achievable capabilities. Let's begin with this
for now.

Changes in v4:
- Fix a possible deadlock issue.
- Remove the connection_mutex check as it is useless now with the new
  connector->dp.mutex.
- Add a new sysfs_mutex to protect concurrent sysfs_update_group() call.

Changes in v3:
- Use drm_connector_dp struct for source caps, sync caps and current link
  information.
- Remove drmm_connector_dp_init and use an helper instead to report
  source capabilities.
- Report all sink link rates capabilities instead of only the max link
  rates.
- Expose sysfs attributes only if nlanes is set.
- Add a new mutex to protect sysfs read write access instead of using
  the global connection_mutex.
- Add documentation

Changes in v2:
- Remove voltage swing and pre-emphasis properties
- Expose link training state via sysfs dp_link/ group instead of
  connector properties
- Rename variables from link_train to link as they relate directly to
  the link capabilities
- Add comprehensive sysfs attributes for both source and sink capabilities
- Add separate helpers for managing sink capabilities and for current and
  maximum link parameters
---
 Documentation/ABI/testing/sysfs-class-drm-dp |  85 ++++++++
 drivers/gpu/drm/display/drm_dp_helper.c      | 306 +++++++++++++++++++++++++++
 drivers/gpu/drm/drm_connector.c              |  13 ++
 drivers/gpu/drm/drm_sysfs.c                  | 160 ++++++++++++++
 include/drm/display/drm_dp_helper.h          |  12 ++
 include/drm/drm_connector.h                  |  59 ++++++
 include/drm/drm_sysfs.h                      |   1 +
 7 files changed, 636 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-drm-dp b/Documentation/ABI/testing/sysfs-class-drm-dp
new file mode 100644
index 0000000000000..21c95a1b8dce0
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-drm-dp
@@ -0,0 +1,85 @@
+What:		/sys/class/drm/card<X>-<connector>/dp_link/
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Directory of DisplayPort link attributes. It exists for
+		DisplayPort and eDP connectors and is empty until the
+		driver reports the source and the sink capabilities.
+
+What:		/sys/class/drm/card<X>-<connector>/dp_link/source_link_caps_nlanes
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Maximum number of DisplayPort lanes the source of this
+		connector can drive.
+
+		Valid values: 1, 2 or 4
+
+What:		/sys/class/drm/card<X>-<connector>/dp_link/source_link_caps_link_rates
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Space-separated list of the DisplayPort link rates the
+		source of this connector can drive, in kbps, sorted in
+		ascending order.
+
+What:		/sys/class/drm/card<X>-<connector>/dp_link/source_link_caps_dsc
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Indicates whether the source of this connector supports
+		Display Stream Compression (DSC).
+
+		Valid values: 0 (not supported), 1 (supported)
+
+What:		/sys/class/drm/card<X>-<connector>/dp_link/sink_link_caps_nlanes
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Maximum number of DisplayPort lanes supported by the
+		connected sink, as reported by the sink capabilities.
+
+		Valid values: 1, 2 or 4
+
+What:		/sys/class/drm/card<X>-<connector>/dp_link/sink_link_caps_link_rates
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Space-separated list of the DisplayPort link rates
+		supported by the connected sink, in kbps, sorted in
+		ascending order. The list is empty if the sink link
+		rates could not be read.
+
+What:		/sys/class/drm/card<X>-<connector>/dp_link/sink_link_caps_dsc
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Indicates whether the connected sink supports Display
+		Stream Compression (DSC).
+
+		Valid values: 0 (not supported), 1 (supported)
+
+What:		/sys/class/drm/card<X>-<connector>/dp_link/cur_link_info_nlanes
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Number of lanes used by the currently trained DisplayPort
+		link of this connector.
+
+		Valid values: 1, 2 or 4
+
+What:		/sys/class/drm/card<X>-<connector>/dp_link/cur_link_info_link_rate
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Link rate of the currently trained DisplayPort link of
+		this connector, in kbps.
+
+What:		/sys/class/drm/card<X>-<connector>/dp_link/cur_link_info_dsc
+Date:		October 2026
+Contact:	dri-devel@lists.freedesktop.org
+Description:
+		Indicates whether Display Stream Compression (DSC) is
+		active on the currently trained DisplayPort link.
+
+		Valid values: 0 (disabled), 1 (enabled)
diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c
index 8473dbcd8808a..5e53c6a9d35fd 100644
--- a/drivers/gpu/drm/display/drm_dp_helper.c
+++ b/drivers/gpu/drm/display/drm_dp_helper.c
@@ -41,6 +41,7 @@
 #include <drm/drm_edid.h>
 #include <drm/drm_fixed.h>
 #include <drm/drm_print.h>
+#include <drm/drm_sysfs.h>
 #include <drm/drm_vblank.h>
 #include <drm/drm_panel.h>
 
@@ -5135,6 +5136,30 @@ int drm_dp_read_dpcd_sink_rates(struct drm_dp_aux *aux,
 }
 EXPORT_SYMBOL_GPL(drm_dp_read_dpcd_sink_rates);
 
+/**
+ * drm_dp_get_max_lane_count - Get the max lane count from DPCD and LTTPR
+ *			       capabilities
+ * @dpcd: DPCD receiver capabilities
+ * @lttpr_common_caps: LTTPR common capabilities (NULL if no LTTPR supported)
+ *
+ * Returns: Number of max lane count
+ */
+int drm_dp_get_max_lane_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+			      const u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE])
+{
+	int nlanes, lttpr_nlanes;
+
+	nlanes = dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK;
+	if (lttpr_common_caps) {
+		lttpr_nlanes = drm_dp_lttpr_max_lane_count(lttpr_common_caps);
+		if (lttpr_nlanes)
+			nlanes = min(nlanes, lttpr_nlanes);
+	}
+
+	return nlanes;
+}
+EXPORT_SYMBOL_GPL(drm_dp_get_max_lane_count);
+
 /**
  * drm_dp_read_edp_sink_rates() - read the sink rates from the eDP DPCD
  * @aux: DisplayPort AUX channel
@@ -5196,3 +5221,284 @@ int drm_dp_read_dsc_dpcd(struct drm_dp_aux *aux,
 	return 0;
 }
 EXPORT_SYMBOL_GPL(drm_dp_read_dsc_dpcd);
+
+static int drm_dp_copy_caps(struct drm_connector_dp_link_caps *dest_caps,
+			    const struct drm_connector_dp_link_caps *src_caps)
+{
+	int *link_rates = NULL;
+	int nlink_rates = 0;
+
+	if (src_caps->link_rates && src_caps->nlink_rates > 0) {
+		link_rates = kmemdup_array(src_caps->link_rates,
+					   src_caps->nlink_rates,
+					   sizeof(*src_caps->link_rates),
+					   GFP_KERNEL);
+		if (!link_rates)
+			return -ENOMEM;
+
+		nlink_rates = src_caps->nlink_rates;
+	}
+
+	kfree(dest_caps->link_rates);
+
+	dest_caps->nlanes = src_caps->nlanes;
+	dest_caps->nlink_rates = nlink_rates;
+	dest_caps->link_rates = link_rates;
+	dest_caps->dsc = src_caps->dsc;
+
+	return 0;
+}
+
+/**
+ * drm_dp_source_set_caps - Set DisplayPort source capabilities
+ * @connector: DisplayPort connector
+ * @link_caps: DisplayPort link training capabilities. The link_rates pointer
+ *	       is not kept by the DRM core
+ *
+ * This function sets the DisplayPort source link training capabilities
+ * for the given connector. This function has to be called after
+ * drm_connector_init() and before drm_dev_register().
+ */
+int drm_dp_source_set_caps(struct drm_connector *connector,
+			   const struct drm_connector_dp_link_caps *link_caps)
+{
+	int ret;
+
+	if (!connector)
+		return -ENODEV;
+
+	if (!(connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort ||
+	      connector->connector_type == DRM_MODE_CONNECTOR_eDP))
+		return 0;
+
+	WARN_ON(connector->dev->registered);
+
+	/* No need to take the dp mutex as the connector sysfs is not yet
+	 * created
+	 */
+	ret = drm_dp_copy_caps(&connector->dp.source_link_caps, link_caps);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(drm_dp_source_set_caps);
+
+/**
+ * drm_dp_sink_set_caps - Set DisplayPort sink link capabilities
+ * @connector: DisplayPort connector
+ * @link_caps: DisplayPort link training capabilities. The link_rates pointer
+ *	       is not kept by the DRM core
+ *
+ * This function sets the DisplayPort sink (monitor) link training
+ * capabilities for the given connector. This function lets the controller
+ * driver read the DPCD on its own, to avoid too many read of the same
+ * DPCD data.
+ * This should be called when a sink is connected.
+ *
+ * Returns: 0 if the capabilities were set successfully, negative error code
+ * otherwise.
+ */
+int drm_dp_sink_set_caps(struct drm_connector *connector,
+			 const struct drm_connector_dp_link_caps *link_caps)
+{
+	bool update;
+	int ret;
+
+	if (!connector)
+		return -ENODEV;
+
+	scoped_guard(mutex, &connector->dp.mutex) {
+		/* Only refresh the group when the visibility changes. */
+		update = !connector->dp.sink_link_caps.nlanes != !link_caps->nlanes;
+
+		ret = drm_dp_copy_caps(&connector->dp.sink_link_caps, link_caps);
+		if (ret)
+			return ret;
+	}
+
+	if (update)
+		drm_sysfs_connector_dp_link_update(connector);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(drm_dp_sink_set_caps);
+
+/**
+ * drm_dp_sink_sync_caps - Fetch and set DisplayPort sink link capabilities
+ * @connector: DisplayPort connector
+ * @aux: DisplayPort AUX channel
+ * @tunnel: Tunnel object (NULL if not supported)
+ *
+ * This function fetches the DisplayPort sink link capabilities from DPCD and
+ * sets them for the given connector.
+ * This should be called when a sink is connected.
+ *
+ * Returns: 0 if the capabilities were fetched and set successfully, negative
+ * error code otherwise.
+ */
+int drm_dp_sink_sync_caps(struct drm_connector *connector, struct drm_dp_aux *aux,
+			  const struct drm_dp_tunnel *tunnel)
+{
+	u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE] = {0};
+	struct drm_connector_dp_link_caps link_caps;
+	int sink_rates[DP_MAX_SUPPORTED_RATES] = {0};
+	u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE] = {0};
+	u8 dpcd_caps[DP_RECEIVER_CAP_SIZE] = {0};
+	struct drm_dp_desc desc = {0};
+	int num_sink_rates, ret;
+	u8 edp_dpcd_rev = 0;
+
+	if (!connector)
+		return -ENODEV;
+
+	ret = drm_dp_read_dpcd_caps(aux, dpcd_caps);
+	if (ret < 0) {
+		drm_dbg_kms(aux->drm_dev, "%s: error reading DPCD capabilities (%d)\n",
+			    aux->name, ret);
+		return ret;
+	}
+
+	if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
+		ret = drm_dp_dpcd_read_byte(aux, DP_EDP_DPCD_REV, &edp_dpcd_rev);
+		if (ret < 0)
+			drm_dbg_kms(aux->drm_dev, "%s: No DPCD eDP revision found\n",
+				    aux->name);
+	}
+
+	if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort &&
+	    dpcd_caps[DP_DPCD_REV] >= DP_DPCD_REV_13) {
+		ret = drm_dp_read_lttpr_common_caps(aux, dpcd_caps, lttpr_common_caps);
+		if (ret < 0) {
+			drm_dbg_kms(aux->drm_dev,
+				    "%s: error reading LTTPR caps, assuming no LTTPR (%d)\n",
+				    aux->name, ret);
+			/* LTTPR capabilities are read block by block. Clean the
+			 * entire capabilities if the read fails to avoid
+			 * partial garbage
+			 */
+			memset(lttpr_common_caps, 0, sizeof(lttpr_common_caps));
+		}
+	}
+
+	ret = drm_dp_read_desc(aux, &desc, drm_dp_is_branch(dpcd_caps));
+	if (ret)
+		drm_dbg_kms(aux->drm_dev, "%s: No DPCD descriptor found\n",
+			    aux->name);
+
+	if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
+		num_sink_rates = drm_dp_read_edp_sink_rates(aux, edp_dpcd_rev,
+							    sink_rates);
+
+		/*
+		 * Use the rates of the DP_MAX_LINK_RATE and
+		 * DP_128B132B_SUPPORTED_LINK_RATES registers if the sink does
+		 * not support the DP_SUPPORTED_LINK_RATES register.
+		 */
+		if (num_sink_rates <= 0)
+			num_sink_rates = drm_dp_read_dpcd_sink_rates(aux, dpcd_caps,
+								     lttpr_common_caps,
+								     &desc,
+								     tunnel,
+								     sink_rates);
+	} else {
+		num_sink_rates = drm_dp_read_dpcd_sink_rates(aux, dpcd_caps,
+							     lttpr_common_caps,
+							     &desc,
+							     tunnel,
+							     sink_rates);
+	}
+
+	if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort &&
+	     dpcd_caps[DP_DPCD_REV] >= DP_DPCD_REV_14) ||
+	    (connector->connector_type == DRM_MODE_CONNECTOR_eDP &&
+	     edp_dpcd_rev >= DP_EDP_14)) {
+		ret = drm_dp_read_dsc_dpcd(aux, dsc_dpcd);
+		if (ret < 0)
+			drm_dbg_kms(aux->drm_dev,
+				    "%s: error reading DPCD DSC capabilities (%d)\n",
+				    aux->name, ret);
+	}
+
+	link_caps.nlanes = drm_dp_get_max_lane_count(dpcd_caps, lttpr_common_caps);
+	link_caps.nlink_rates = num_sink_rates;
+	link_caps.link_rates = sink_rates;
+	link_caps.dsc = drm_dp_sink_supports_dsc(dsc_dpcd);
+
+	return drm_dp_sink_set_caps(connector, &link_caps);
+}
+EXPORT_SYMBOL_GPL(drm_dp_sink_sync_caps);
+
+/**
+ * drm_dp_set_cur_link_params - Set current DisplayPort link parameters
+ * @connector: DisplayPort connector
+ * @link_rate: Current link rate in deca-kbps
+ * @lane_count: Current lane count
+ * @dsc_en: Display Stream Compression enabled
+ *
+ * This function sets the current active DisplayPort link parameters after
+ * link training has completed. These parameters represent the actual link
+ * configuration being used for display output.
+ */
+void drm_dp_set_cur_link_params(struct drm_connector *connector,
+				int link_rate, int lane_count, bool dsc_en)
+{
+	int *_link_rates;
+	bool update;
+
+	if (!connector)
+		return;
+
+	scoped_guard(mutex, &connector->dp.mutex) {
+		/* Only refresh the group when the visibility changes. */
+		update = !connector->dp.cur_link_info.nlanes != !lane_count;
+
+		if (!connector->dp.cur_link_info.link_rates) {
+			_link_rates = kzalloc_obj(*_link_rates);
+			if (!_link_rates)
+				return;
+
+			connector->dp.cur_link_info.link_rates = _link_rates;
+			connector->dp.cur_link_info.nlink_rates = 1;
+		}
+
+		*connector->dp.cur_link_info.link_rates = link_rate;
+		connector->dp.cur_link_info.nlanes = lane_count;
+		connector->dp.cur_link_info.dsc = dsc_en;
+	}
+
+	if (update)
+		drm_sysfs_connector_dp_link_update(connector);
+}
+EXPORT_SYMBOL_GPL(drm_dp_set_cur_link_params);
+
+/**
+ * drm_dp_sink_reset_caps - Reset DisplayPort sink capabilities
+ * @connector: DisplayPort connector
+ *
+ * This function clears all DisplayPort sink link capabilities and parameters.
+ * This should be called when a sink is disconnected to clear stale
+ * capability information.
+ */
+void drm_dp_sink_reset_caps(struct drm_connector *connector)
+{
+	struct drm_connector_dp_link_caps zero_caps = {0};
+	bool update;
+
+	if (!connector)
+		return;
+
+	scoped_guard(mutex, &connector->dp.mutex) {
+		update = connector->dp.sink_link_caps.nlanes ||
+			 connector->dp.cur_link_info.nlanes;
+
+		kfree(connector->dp.cur_link_info.link_rates);
+		memcpy(&connector->dp.cur_link_info, &zero_caps, sizeof(zero_caps));
+		kfree(connector->dp.sink_link_caps.link_rates);
+		memcpy(&connector->dp.sink_link_caps, &zero_caps, sizeof(zero_caps));
+	}
+
+	if (update)
+		drm_sysfs_connector_dp_link_update(connector);
+}
+EXPORT_SYMBOL_GPL(drm_dp_sink_reset_caps);
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 893716a9e4895..157c5aacd5942 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -285,6 +285,8 @@ static int drm_connector_init_only(struct drm_device *dev,
 	mutex_init(&connector->edid_override_mutex);
 	mutex_init(&connector->hdmi.infoframes.lock);
 	mutex_init(&connector->hdmi_audio.lock);
+	mutex_init(&connector->dp.mutex);
+	mutex_init(&connector->dp.sysfs_mutex);
 	connector->edid_blob_ptr = NULL;
 	connector->epoch_counter = 0;
 	connector->tile_blob_ptr = NULL;
@@ -941,6 +943,17 @@ void drm_connector_cleanup(struct drm_connector *connector)
 		connector->tile_group = NULL;
 	}
 
+	scoped_guard(mutex, &connector->dp.mutex) {
+		kfree(connector->dp.source_link_caps.link_rates);
+		connector->dp.source_link_caps.link_rates = NULL;
+		kfree(connector->dp.sink_link_caps.link_rates);
+		connector->dp.sink_link_caps.link_rates = NULL;
+		kfree(connector->dp.cur_link_info.link_rates);
+		connector->dp.cur_link_info.link_rates = NULL;
+	}
+	mutex_destroy(&connector->dp.mutex);
+	mutex_destroy(&connector->dp.sysfs_mutex);
+
 	list_for_each_entry_safe(mode, t, &connector->probed_modes, head)
 		drm_mode_remove(connector, mode);
 
diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
index 7b16d32b6ff65..ac9762a066db4 100644
--- a/drivers/gpu/drm/drm_sysfs.c
+++ b/drivers/gpu/drm/drm_sysfs.c
@@ -340,6 +340,151 @@ static const struct attribute_group *connector_dev_groups[] = {
 	NULL
 };
 
+static ssize_t drm_link_rates_show(int num_link_rates, const int *link_rates, char *buf)
+{
+	ssize_t size = 0;
+
+	if (!num_link_rates)
+		return 0;
+
+	/* We are using deca-kbps in the kernel historically, due to
+	 * calculation over the link rates that could overflow 32 bits.
+	 * Report link rates in kbps to avoid confusing the user
+	 */
+	size += sysfs_emit_at(buf, size, "%d", link_rates[0] * 10);
+	for (int i = 1; i < num_link_rates; i++)
+		size += sysfs_emit_at(buf, size, " %d", link_rates[i] * 10);
+
+	size += sysfs_emit_at(buf, size, "\n");
+	return size;
+}
+
+#define DRM_CONNECTOR_DP_ATTR_SHOW_LINK_RATES_NESTED(_name)                    \
+static ssize_t _name##_link_rates_show(struct device *device,                  \
+			    struct device_attribute *attr,                     \
+			    char *buf)                                         \
+{                                                                              \
+	struct drm_connector *connector = to_drm_connector(device);            \
+	ssize_t size;                                                          \
+	scoped_guard(mutex, &connector->dp.mutex)			       \
+		size = drm_link_rates_show(connector->dp._name.nlink_rates,    \
+					   connector->dp._name.link_rates, buf); \
+	return size;                                                           \
+}
+
+#define DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE_NESTED(_name, _info)                 \
+static ssize_t _name##_##_info##_show(struct device *device,                   \
+				   struct device_attribute *attr,              \
+				   char *buf)                                  \
+{                                                                              \
+	struct drm_connector *connector = to_drm_connector(device);            \
+	ssize_t size;                                                          \
+	scoped_guard(mutex, &connector->dp.mutex)			       \
+		size = sysfs_emit(buf, "%d\n", connector->dp._name._info);     \
+	return size;                                                           \
+}
+
+static ssize_t cur_link_info_link_rate_show(struct device *device,
+					    struct device_attribute *attr,
+					    char *buf)
+{
+	struct drm_connector *connector = to_drm_connector(device);
+	ssize_t size;
+
+	scoped_guard(mutex, &connector->dp.mutex)
+		size = drm_link_rates_show(connector->dp.cur_link_info.nlink_rates,
+					   connector->dp.cur_link_info.link_rates, buf);
+	return size;
+}
+
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE_NESTED(source_link_caps, nlanes);
+DRM_CONNECTOR_DP_ATTR_SHOW_LINK_RATES_NESTED(source_link_caps);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE_NESTED(source_link_caps, dsc);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE_NESTED(sink_link_caps, nlanes);
+DRM_CONNECTOR_DP_ATTR_SHOW_LINK_RATES_NESTED(sink_link_caps);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE_NESTED(sink_link_caps, dsc);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE_NESTED(cur_link_info, nlanes);
+DRM_CONNECTOR_DP_ATTR_SHOW_SIMPLE_NESTED(cur_link_info, dsc);
+
+static DEVICE_ATTR_RO(source_link_caps_nlanes);
+static DEVICE_ATTR_RO(source_link_caps_link_rates);
+static DEVICE_ATTR_RO(source_link_caps_dsc);
+static DEVICE_ATTR_RO(sink_link_caps_nlanes);
+static DEVICE_ATTR_RO(sink_link_caps_link_rates);
+static DEVICE_ATTR_RO(sink_link_caps_dsc);
+static DEVICE_ATTR_RO(cur_link_info_nlanes);
+static DEVICE_ATTR_RO(cur_link_info_link_rate);
+static DEVICE_ATTR_RO(cur_link_info_dsc);
+
+static struct attribute *connector_dp_link_attrs[] = {
+	&dev_attr_source_link_caps_nlanes.attr,
+	&dev_attr_source_link_caps_link_rates.attr,
+	&dev_attr_source_link_caps_dsc.attr,
+	&dev_attr_sink_link_caps_nlanes.attr,
+	&dev_attr_sink_link_caps_link_rates.attr,
+	&dev_attr_sink_link_caps_dsc.attr,
+	&dev_attr_cur_link_info_nlanes.attr,
+	&dev_attr_cur_link_info_link_rate.attr,
+	&dev_attr_cur_link_info_dsc.attr,
+	NULL
+};
+
+static umode_t dp_link_attr_visible(struct kobject *kobj,
+				    struct attribute *a, int n)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct drm_connector *connector = to_drm_connector(dev);
+	struct drm_connector_dp_link_caps *caps;
+
+	if (a == &dev_attr_source_link_caps_nlanes.attr ||
+	    a == &dev_attr_source_link_caps_link_rates.attr ||
+	    a == &dev_attr_source_link_caps_dsc.attr)
+		caps = &connector->dp.source_link_caps;
+	else if (a == &dev_attr_sink_link_caps_nlanes.attr ||
+		 a == &dev_attr_sink_link_caps_link_rates.attr ||
+		 a == &dev_attr_sink_link_caps_dsc.attr)
+		caps = &connector->dp.sink_link_caps;
+	else
+		caps = &connector->dp.cur_link_info;
+
+	/* Checking nlanes is enough to decide whether the attributes should
+	 * be exposed: without lanes there is no working DisplayPort link
+	 */
+	return caps->nlanes ? a->mode : 0;
+}
+
+static const struct attribute_group connector_dp_link_group = {
+	.name = "dp_link",
+	.attrs = connector_dp_link_attrs,
+	.is_visible = dp_link_attr_visible,
+};
+
+/**
+ * drm_sysfs_connector_dp_link_update - Update the visibility of the DP link
+ *                                      attributes
+ * @connector: DisplayPort connector
+ */
+void drm_sysfs_connector_dp_link_update(struct drm_connector *connector)
+{
+	int ret;
+
+	if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort &&
+	    connector->connector_type != DRM_MODE_CONNECTOR_eDP)
+		return;
+
+	scoped_guard(mutex, &connector->dp.sysfs_mutex) {
+		if (!connector->kdev)
+			return;
+
+		ret = sysfs_update_group(&connector->kdev->kobj,
+					 &connector_dp_link_group);
+	}
+	if (ret)
+		drm_err(connector->dev,
+			"failed to update DP link sysfs attributes: %d\n", ret);
+}
+EXPORT_SYMBOL_GPL(drm_sysfs_connector_dp_link_update);
+
 int drm_sysfs_connector_add(struct drm_connector *connector)
 {
 	struct drm_device *dev = connector->dev;
@@ -376,6 +521,18 @@ int drm_sysfs_connector_add(struct drm_connector *connector)
 
 	connector->kdev = kdev;
 
+	if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort ||
+	    connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
+		scoped_guard(mutex, &connector->dp.sysfs_mutex)
+			r = sysfs_create_group(&connector->kdev->kobj,
+					       &connector_dp_link_group);
+
+		if (r) {
+			drm_err(dev, "failed to create DP connector sysfs: %d\n", r);
+			goto err_dp_sysfs;
+		}
+	}
+
 	if (dev_fwnode(kdev)) {
 		r = component_add(kdev, &typec_connector_ops);
 		if (r)
@@ -384,6 +541,9 @@ int drm_sysfs_connector_add(struct drm_connector *connector)
 
 	return 0;
 
+err_dp_sysfs:
+	connector->kdev = NULL;
+	device_del(kdev);
 err_free:
 	put_device(kdev);
 	return r;
diff --git a/include/drm/display/drm_dp_helper.h b/include/drm/display/drm_dp_helper.h
index 7bc3b36f38b82..ad899ac18bfd8 100644
--- a/include/drm/display/drm_dp_helper.h
+++ b/include/drm/display/drm_dp_helper.h
@@ -712,6 +712,8 @@ int drm_dp_read_dpcd_sink_rates(struct drm_dp_aux *aux,
 				int sink_rates[DP_MAX_SUPPORTED_RATES]);
 int drm_dp_read_edp_sink_rates(struct drm_dp_aux *aux, u8 edp_dpcd_rev,
 			       int sink_rates[DP_MAX_SUPPORTED_RATES]);
+int drm_dp_get_max_lane_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+			      const u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE]);
 
 int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
 				 u8 status[DP_LINK_STATUS_SIZE]);
@@ -1051,4 +1053,14 @@ ssize_t drm_dp_vsc_sdp_pack(const struct drm_dp_vsc_sdp *vsc, struct dp_sdp *sdp
 int drm_dp_link_symbol_cycles(int lane_count, int pixels, int dsc_slice_count,
 			      int bpp_x16, int symbol_size, bool is_mst);
 
+int drm_dp_source_set_caps(struct drm_connector *connector,
+			   const struct drm_connector_dp_link_caps *link_caps);
+int drm_dp_sink_sync_caps(struct drm_connector *connector, struct drm_dp_aux *aux,
+			  const struct drm_dp_tunnel *tunnel);
+int drm_dp_sink_set_caps(struct drm_connector *connector,
+			 const struct drm_connector_dp_link_caps *link_caps);
+void drm_dp_sink_reset_caps(struct drm_connector *connector);
+void drm_dp_set_cur_link_params(struct drm_connector *connector, int link_rate,
+				int lane_count, bool dsc_en);
+
 #endif /* _DRM_DP_HELPER_H_ */
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 2ee7c59329aae..543b6282daebc 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -2282,6 +2282,60 @@ struct drm_connector_cec {
 	void *data;
 };
 
+/**
+ * struct drm_connector_dp_link_caps - DRM DisplayPort link capabilities
+ */
+struct drm_connector_dp_link_caps {
+	/**
+	 * @nlanes: Maximum number of lanes number supported
+	 */
+	int nlanes;
+
+	/**
+	 * @nlink_rates: Number of link rates supported
+	 */
+	int nlink_rates;
+
+	/**
+	 * @link_rates: Array listing the supported link rates in deca-kbps
+	 */
+	int *link_rates;
+
+	/**
+	 * @dsc: Display Stream Compression supported
+	 */
+	bool dsc;
+};
+
+/**
+ * struct drm_connector_dp - DRM Connector DisplayPort-related structure
+ */
+struct drm_connector_dp {
+	/**
+	 * @source_link_caps: Source link capabilities
+	 */
+	struct drm_connector_dp_link_caps source_link_caps;
+	/**
+	 * @sink_link_caps: Sink link capabilities
+	 */
+	struct drm_connector_dp_link_caps sink_link_caps;
+	/**
+	 * @cur_link_info: Current negotiated link information
+	 */
+	struct drm_connector_dp_link_caps cur_link_info;
+
+	/**
+	 * @mutex: Lock to protect read write access
+	 */
+	struct mutex mutex;
+
+	/**
+	 * @sysfs_mutex: Lock to serialize the sysfs attribute group
+	 * updates.
+	 */
+	struct mutex sysfs_mutex;
+};
+
 /**
  * struct drm_connector - central DRM connector control structure
  *
@@ -2714,6 +2768,11 @@ struct drm_connector {
 	 * @cec: CEC-related data.
 	 */
 	struct drm_connector_cec cec;
+
+	/**
+	 * @dp: DisplayPort-related variable and properties.
+	 */
+	struct drm_connector_dp dp;
 };
 
 #define obj_to_connector(x) container_of(x, struct drm_connector, base)
diff --git a/include/drm/drm_sysfs.h b/include/drm/drm_sysfs.h
index 7695873a94565..366a02e27f366 100644
--- a/include/drm/drm_sysfs.h
+++ b/include/drm/drm_sysfs.h
@@ -10,4 +10,5 @@ void drm_sysfs_hotplug_event(struct drm_device *dev);
 void drm_sysfs_connector_hotplug_event(struct drm_connector *connector);
 void drm_sysfs_connector_property_event(struct drm_connector *connector,
 					struct drm_property *property);
+void drm_sysfs_connector_dp_link_update(struct drm_connector *connector);
 #endif

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 4/8] drm/i915/display/dp: Read DPCD DSC capabilities in any case
  2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
                   ` (2 preceding siblings ...)
  2026-10-22 16:37 ` [PATCH v4 3/8] drm: Introduce DisplayPort link training capabilities and state helpers Kory Maincent
@ 2026-10-22 16:38 ` Kory Maincent
  2026-10-22 16:38 ` [PATCH v4 5/8] drm/i915/display/dp: Use dp_connector helpers to expose link training state Kory Maincent
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Kory Maincent @ 2026-10-22 16:38 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
	Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
  Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
	Drew Davenport, Louis Chauvet, dri-devel, linux-kernel, intel-gfx,
	intel-xe, linux-mediatek, linux-arm-kernel, Kory Maincent

In preparation for the support of reporting the sink capabilities we
need to read the DSC sink capabilities even if the source does not
support it.

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Change in v3:
- New patch
---
 drivers/gpu/drm/i915/display/intel_dp.c | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
index 89d4b3d348fa8..bbd76c0ff04b6 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -4473,12 +4473,7 @@ static void intel_edp_get_dsc_sink_cap(u8 edp_dpcd_rev, struct intel_connector *
 static void
 intel_dp_detect_dsc_caps(struct intel_dp *intel_dp, struct intel_connector *connector)
 {
-	struct intel_display *display = to_intel_display(intel_dp);
-
 	/* Read DP Sink DSC Cap DPCD regs for DP v1.4 */
-	if (!HAS_DSC(display))
-		return;
-
 	if (intel_dp_is_edp(intel_dp))
 		intel_edp_get_dsc_sink_cap(intel_dp->edp_dpcd[0],
 					   connector);

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 5/8] drm/i915/display/dp: Use dp_connector helpers to expose link training state
  2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
                   ` (3 preceding siblings ...)
  2026-10-22 16:38 ` [PATCH v4 4/8] drm/i915/display/dp: Read DPCD DSC capabilities in any case Kory Maincent
@ 2026-10-22 16:38 ` Kory Maincent
  2026-10-22 17:03   ` sashiko-bot
  2026-10-22 16:38 ` [PATCH v4 6/8] drm/bridge: Expose link training state via new DRM_BRIDGE_OP_DP flag Kory Maincent
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Kory Maincent @ 2026-10-22 16:38 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
	Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
  Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
	Drew Davenport, Louis Chauvet, dri-devel, linux-kernel, intel-gfx,
	intel-xe, linux-mediatek, linux-arm-kernel, Kory Maincent

Adopt the new DRM core DisplayPort helpers, which expose source and sink
capabilities along with the maximum and current link parameters to
userspace through the dp_link/ sysfs attributes.

Keep the sink's supported link rates unclamped in the new
dpcd_sink_rates/num_dpcd_sink_rates fields, rather than limiting them to
the rates the source supports.

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Changes in v3:
- Move the call to new helpers around.
- Use real sink rates instead of the modified ones.

Changes in v2:
- Remove voltage swing and pre emphasis properties.
---
 drivers/gpu/drm/i915/display/intel_display_types.h |  2 +
 drivers/gpu/drm/i915/display/intel_dp.c            | 55 +++++++++++++++++++++-
 .../gpu/drm/i915/display/intel_dp_link_training.c  | 14 ++++++
 3 files changed, 69 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
index 5f0fe18c0614e..025523ae58e6c 100644
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
@@ -1845,6 +1845,8 @@ struct intel_dp {
 	/* sink rates as reported by DP_MAX_LINK_RATE/DP_SUPPORTED_LINK_RATES */
 	int num_sink_rates;
 	int sink_rates[DP_MAX_SUPPORTED_RATES];
+	int num_dpcd_sink_rates;
+	int dpcd_sink_rates[DP_MAX_SUPPORTED_RATES];
 	bool use_rate_select;
 	/* Max sink lane count as reported by DP_MAX_LANE_COUNT */
 	int max_sink_lane_count;
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
index bbd76c0ff04b6..925d0ec939f55 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -167,6 +167,7 @@ static void intel_dp_set_default_sink_rates(struct intel_dp *intel_dp)
 {
 	intel_dp->sink_rates[0] = 162000;
 	intel_dp->num_sink_rates = 1;
+	intel_dp->num_dpcd_sink_rates = 0;
 }
 
 /* update sink rates from dpcd */
@@ -179,10 +180,14 @@ static void intel_dp_set_dpcd_sink_rates(struct intel_dp *intel_dp)
 						     intel_dp->lttpr_common_caps,
 						     &intel_dp->desc,
 						     intel_dp->tunnel,
-						     intel_dp->sink_rates);
+						     intel_dp->dpcd_sink_rates);
 	if (num_sink_rates < 0)
 		num_sink_rates = 0;
 
+	intel_dp->num_dpcd_sink_rates = num_sink_rates;
+	memcpy(intel_dp->sink_rates, intel_dp->dpcd_sink_rates,
+	       sizeof(int) * num_sink_rates);
+
 	intel_dp->num_sink_rates = num_sink_rates;
 }
 
@@ -4598,10 +4603,14 @@ intel_edp_set_sink_rates(struct intel_dp *intel_dp)
 
 	num_sink_rates = drm_dp_read_edp_sink_rates(&intel_dp->aux,
 						    intel_dp->edp_dpcd[0],
-						    intel_dp->sink_rates);
+						    intel_dp->dpcd_sink_rates);
 	if (num_sink_rates < 0)
 		num_sink_rates = 0;
 
+	intel_dp->num_dpcd_sink_rates = num_sink_rates;
+	memcpy(intel_dp->sink_rates, intel_dp->dpcd_sink_rates,
+	       sizeof(int) * num_sink_rates);
+
 	intel_dp->num_sink_rates = num_sink_rates;
 
 	/*
@@ -6141,6 +6150,20 @@ void intel_dp_dpcd_set_probe(struct intel_dp *intel_dp, bool force_on_external)
 			      intel_dp_needs_dpcd_probe(intel_dp, force_on_external));
 }
 
+static void intel_dp_set_sink_caps(struct intel_connector *connector,
+				   struct intel_dp *intel_dp)
+{
+	struct drm_connector_dp_link_caps link_caps;
+
+	link_caps.nlanes = drm_dp_get_max_lane_count(intel_dp->dpcd,
+						     intel_dp->lttpr_common_caps);
+	link_caps.link_rates = intel_dp->dpcd_sink_rates;
+	link_caps.nlink_rates = intel_dp->num_dpcd_sink_rates;
+	link_caps.dsc = drm_dp_sink_supports_dsc(connector->dp.dsc_dpcd);
+
+	drm_dp_sink_set_caps(&connector->base, &link_caps);
+}
+
 static int
 intel_dp_detect(struct drm_connector *_connector,
 		struct drm_modeset_acquire_ctx *ctx,
@@ -6212,6 +6235,8 @@ intel_dp_detect(struct drm_connector *_connector,
 
 		intel_dp_tunnel_uhbr_lanes_wa_reset(intel_dp);
 
+		drm_dp_sink_reset_caps(_connector);
+
 		goto out_unset_edid;
 	}
 
@@ -6243,6 +6268,8 @@ intel_dp_detect(struct drm_connector *_connector,
 
 	intel_dp_print_rates(intel_dp);
 
+	intel_dp_set_sink_caps(connector, intel_dp);
+
 	if (intel_dp->is_mst) {
 		/*
 		 * If we are in MST mode then this connector
@@ -6990,11 +7017,14 @@ intel_dp_init_connector(struct intel_digital_port *dig_port,
 			struct intel_connector *connector)
 {
 	struct intel_display *display = to_intel_display(dig_port);
+	struct drm_connector_dp_link_caps link_caps;
 	struct intel_dp *intel_dp = &dig_port->dp;
 	struct intel_encoder *encoder = &dig_port->base;
 	struct drm_device *dev = encoder->base.dev;
 	enum port port = encoder->port;
+	int *rates;
 	int type;
+	int ret;
 
 	if (drm_WARN(dev, dig_port->max_lanes < 1,
 		     "Not enough lanes (%d) for DP on [ENCODER:%d:%s]\n",
@@ -7069,6 +7099,27 @@ intel_dp_init_connector(struct intel_digital_port *dig_port,
 	intel_dp_set_common_link_params(intel_dp);
 	intel_dp_reset_link_params(intel_dp);
 
+	rates = kmemdup_array(intel_dp->source_rates,
+			      intel_dp->num_source_rates,
+			      sizeof(*rates),
+			      GFP_KERNEL);
+	if (!rates) {
+		intel_dp_aux_fini(intel_dp);
+		goto fail;
+	}
+
+	link_caps.nlanes = dig_port->max_lanes;
+	link_caps.nlink_rates = intel_dp->num_source_rates;
+	link_caps.link_rates = rates;
+	link_caps.dsc = HAS_DSC(display);
+
+	ret = drm_dp_source_set_caps(&connector->base, &link_caps);
+	if (ret)
+		drm_dbg_kms(display->drm,
+			    "failed to set source link rates, skipping.\n");
+
+	kfree(rates);
+
 	/* init MST on ports that can support it */
 	intel_dp_mst_encoder_init(dig_port, connector->base.base.id);
 
diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
index cb92cff906146..c77cb124f1419 100644
--- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c
+++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
@@ -1758,6 +1758,18 @@ static void link_recovery_reset(struct intel_dp_link_training *link_training)
 	link_training->recovery_state = INTEL_DP_LINK_RECOVERY_IDLE;
 }
 
+static void intel_dp_report_link_train(struct intel_dp *intel_dp)
+{
+	struct intel_connector *connector = intel_dp->attached_connector;
+
+	if (!connector)
+		return;
+
+	drm_dp_set_cur_link_params(&connector->base, intel_dp->link_rate,
+				   intel_dp->lane_count,
+				   connector->dp.dsc_decompression_enabled);
+}
+
 /**
  * intel_dp_stop_link_train - stop link training
  * @intel_dp: DP struct
@@ -1787,6 +1799,8 @@ void intel_dp_stop_link_train(struct intel_dp *intel_dp,
 	intel_dp_program_link_training_pattern(intel_dp, crtc_state, DP_PHY_DPRX,
 					       DP_TRAINING_PATTERN_DISABLE);
 
+	intel_dp_report_link_train(intel_dp);
+
 	if (intel_dp_is_uhbr(crtc_state)) {
 		ret = poll_timeout_us(ret = intel_dp_128b132b_intra_hop(intel_dp, crtc_state),
 				      ret == 0,

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 6/8] drm/bridge: Expose link training state via new DRM_BRIDGE_OP_DP flag
  2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
                   ` (4 preceding siblings ...)
  2026-10-22 16:38 ` [PATCH v4 5/8] drm/i915/display/dp: Use dp_connector helpers to expose link training state Kory Maincent
@ 2026-10-22 16:38 ` Kory Maincent
  2026-10-22 16:38 ` [PATCH v4 7/8] drm/analogix/anx7625: Use dp_connector helpers to expose link training state Kory Maincent
  2026-10-22 16:38 ` [PATCH v4 8/8] drm/mediatek: " Kory Maincent
  7 siblings, 0 replies; 14+ messages in thread
From: Kory Maincent @ 2026-10-22 16:38 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
	Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
  Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
	Drew Davenport, Louis Chauvet, dri-devel, linux-kernel, intel-gfx,
	intel-xe, linux-mediatek, linux-arm-kernel, Kory Maincent

Introduce DRM_BRIDGE_OP_DP, a new bridge operation flag for bridges
that provide DisplayPort connector operations with link training support.
Bridges advertising this flag must fill the dp_link_caps field in
struct drm_bridge with their link capabilities.

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Changes in v3:
- Mone on from drmm_connector_dp_init() to drm_dp_source_set_caps().

Change in v2:
- New patch
---
 drivers/gpu/drm/display/Kconfig                |  1 +
 drivers/gpu/drm/display/drm_bridge_connector.c | 29 ++++++++++++++++++++++++++
 include/drm/drm_bridge.h                       | 13 ++++++++++++
 3 files changed, 43 insertions(+)

diff --git a/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig
index df09cf9a8ca19..ce2f6d62c76d0 100644
--- a/drivers/gpu/drm/display/Kconfig
+++ b/drivers/gpu/drm/display/Kconfig
@@ -19,6 +19,7 @@ config DRM_BRIDGE_CONNECTOR
 	select DRM_DISPLAY_HDMI_AUDIO_HELPER
 	select DRM_DISPLAY_HDMI_CEC_HELPER
 	select DRM_DISPLAY_HDMI_STATE_HELPER
+	select DRM_DISPLAY_DP_HELPER
 	help
 	  DRM connector implementation terminating DRM bridge chains.
 
diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 81f3e26f6fdf5..5b58e86da6259 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -26,6 +26,7 @@
 #include <drm/display/drm_hdmi_cec_helper.h>
 #include <drm/display/drm_hdmi_helper.h>
 #include <drm/display/drm_hdmi_state_helper.h>
+#include <drm/display/drm_dp_helper.h>
 
 /**
  * DOC: overview
@@ -109,6 +110,13 @@ struct drm_bridge_connector {
 	 * HDMI Audio infrastructure, if any (see &DRM_BRIDGE_OP_HDMI_AUDIO).
 	 */
 	struct drm_bridge *bridge_hdmi_audio;
+	/**
+	 * @bridge_dp:
+	 *
+	 * The bridge in the chain that implements necessary support for the
+	 * DisplayPort connector infrastructure, if any (see &DRM_BRIDGE_OP_DP).
+	 */
+	struct drm_bridge *bridge_dp;
 	/**
 	 * @bridge_dp_audio:
 	 *
@@ -835,6 +843,7 @@ static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
 	drm_bridge_put(bridge_connector->bridge_hdmi_audio);
 	drm_bridge_put(bridge_connector->bridge_dp_audio);
 	drm_bridge_put(bridge_connector->bridge_hdmi_cec);
+	drm_bridge_put(bridge_connector->bridge_dp);
 }
 
 /**
@@ -968,6 +977,15 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 			bridge_connector->bridge_hdmi_audio = drm_bridge_get(bridge);
 		}
 
+		if (bridge->ops & DRM_BRIDGE_OP_DP) {
+			if (bridge_connector->bridge_dp)
+				return ERR_PTR(-EBUSY);
+			if (!bridge->dp_link_caps)
+				return ERR_PTR(-EINVAL);
+
+			bridge_connector->bridge_dp = drm_bridge_get(bridge);
+		}
+
 		if (bridge->ops & DRM_BRIDGE_OP_DP_AUDIO) {
 			if (bridge_connector->bridge_dp_audio)
 				return ERR_PTR(-EBUSY);
@@ -1079,6 +1097,17 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 					       connector_type, ddc);
 		if (ret)
 			return ERR_PTR(ret);
+	} else if (bridge_connector->bridge_dp) {
+		ret = drmm_connector_init(drm, connector,
+					  &drm_bridge_connector_funcs,
+					  connector_type, ddc);
+		if (ret)
+			return ERR_PTR(ret);
+
+		ret = drm_dp_source_set_caps(connector,
+					     bridge_connector->bridge_dp->dp_link_caps);
+		if (ret)
+			return ERR_PTR(ret);
 	} else {
 		ret = drmm_connector_init(drm, connector,
 					  &drm_bridge_connector_funcs,
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 1981d24a700d2..d1430b2b72518 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -1021,6 +1021,14 @@ enum drm_bridge_ops {
 	 * &drm_bridge_funcs->hdmi_clear_spd_infoframe callbacks.
 	 */
 	DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME = BIT(10),
+	/**
+	 * @DRM_BRIDGE_OP_DP: The bridge provides DisplayPort connector
+	 * operations, including link training support. Bridges that set
+	 * this flag must provide DisplayPort-related information and
+	 * fill the &drm_bridge->dp_link_caps link training
+	 * capabilities.
+	 */
+	DRM_BRIDGE_OP_DP = BIT(11),
 };
 
 /**
@@ -1214,6 +1222,11 @@ struct drm_bridge {
 	 */
 	void *hpd_data;
 
+	/**
+	 * @dp_link_caps: DisplayPort link capabilities
+	 */
+	const struct drm_connector_dp_link_caps *dp_link_caps;
+
 	/**
 	 * @next_bridge: Pointer to the following bridge, automatically put
 	 * when this bridge is freed (i.e. at destroy time). This is for

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 7/8] drm/analogix/anx7625: Use dp_connector helpers to expose link training state
  2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
                   ` (5 preceding siblings ...)
  2026-10-22 16:38 ` [PATCH v4 6/8] drm/bridge: Expose link training state via new DRM_BRIDGE_OP_DP flag Kory Maincent
@ 2026-10-22 16:38 ` Kory Maincent
  2026-10-22 17:03   ` sashiko-bot
  2026-10-22 16:38 ` [PATCH v4 8/8] drm/mediatek: " Kory Maincent
  7 siblings, 1 reply; 14+ messages in thread
From: Kory Maincent @ 2026-10-22 16:38 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
	Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
  Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
	Drew Davenport, Louis Chauvet, dri-devel, linux-kernel, intel-gfx,
	intel-xe, linux-mediatek, linux-arm-kernel, Kory Maincent

Adopt the DRM core DisplayPort helpers by setting DRM_BRIDGE_OP_DP and
populating the bridge dp_link_caps at probe time. Report the sink
capabilities on hotplug, reset them on disconnect, and report the link
parameters once link training succeeds.

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Change in v3:
- New patch
---
 drivers/gpu/drm/bridge/analogix/anx7625.c | 68 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/bridge/analogix/anx7625.h |  1 +
 2 files changed, 69 insertions(+)

diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c
index c3af88e14f8b2..2b1235efc9fa4 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.c
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.c
@@ -2558,6 +2558,31 @@ static int anx7625_bridge_atomic_check(struct drm_bridge *bridge,
 	return 0;
 }
 
+static void anx7625_report_link_train(struct anx7625_data *ctx,
+				      struct drm_connector *connector)
+{
+	int bw_code, rate, nlanes;
+
+	/*
+	 * The DP link training is handled by the internal OCM firmware, read
+	 * back the negotiated link rate and lane count.
+	 */
+	bw_code = anx7625_reg_read(ctx, ctx->i2c.tx_p0_client,
+				   SP_TX_LINK_BW_SET_REG);
+	if (bw_code < 0)
+		return;
+
+	rate = drm_dp_bw_code_to_link_rate(bw_code & SP_TX_LINK_BW_SET_MASK);
+
+	nlanes = anx7625_reg_read(ctx, ctx->i2c.tx_p0_client,
+				  SP_TX_LANE_COUNT_SET_REG);
+	if (nlanes < 0)
+		return;
+	nlanes &= DP_MAX_LANE_COUNT_MASK;
+
+	drm_dp_set_cur_link_params(connector, rate, nlanes, false);
+}
+
 static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge,
 					 struct drm_atomic_commit *state)
 {
@@ -2565,6 +2590,7 @@ static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge,
 	struct device *dev = ctx->dev;
 	struct drm_connector *connector;
 	struct drm_connector_state *conn_state;
+	int ret;
 
 	dev_dbg(dev, "drm atomic enable\n");
 
@@ -2580,6 +2606,17 @@ static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge,
 
 	anx7625_dp_start(ctx);
 
+	/* On DisplayPort case, the caps are set in the detect callback. */
+	if (bridge->type == DRM_MODE_CONNECTOR_eDP) {
+		ret = drm_dp_sink_sync_caps(connector, &ctx->aux, NULL);
+		if (ret)
+			DRM_DEV_DEBUG_DRIVER(dev,
+					     "error sink caps synchronisation (%d)\n",
+					     ret);
+	}
+
+	anx7625_report_link_train(ctx, connector);
+
 	conn_state = drm_atomic_get_new_connector_state(state, connector);
 
 	if (WARN_ON(!conn_state))
@@ -2616,6 +2653,12 @@ static void anx7625_bridge_atomic_disable(struct drm_bridge *bridge,
 		dev_dbg(dev, "update CP to DESIRE\n");
 	}
 
+	/* On DisplayPort unplug, the caps are already reset in the detect
+	 * callback.
+	 */
+	if (bridge->type == DRM_MODE_CONNECTOR_eDP)
+		drm_dp_sink_reset_caps(ctx->connector);
+
 	ctx->connector = NULL;
 	anx7625_dp_stop(ctx);
 
@@ -2634,11 +2677,25 @@ anx7625_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector
 	struct anx7625_data *ctx = bridge_to_anx7625(bridge);
 	struct device *dev = ctx->dev;
 	enum drm_connector_status status;
+	int ret;
 
 	DRM_DEV_DEBUG_DRIVER(dev, "drm bridge detect\n");
 
 	status = anx7625_sink_detect(ctx);
 	anx7625_audio_update_connector_status(ctx, status);
+
+	if (status == connector_status_connected) {
+		ret = drm_dp_sink_sync_caps(connector, &ctx->aux, NULL);
+		if (ret)
+			DRM_DEV_DEBUG_DRIVER(dev,
+					     "error sink caps synchronisation (%d)\n",
+					     ret);
+
+		anx7625_report_link_train(ctx, connector);
+	} else {
+		drm_dp_sink_reset_caps(connector);
+	}
+
 	return status;
 }
 
@@ -2765,6 +2822,14 @@ static const struct dev_pm_ops anx7625_pm_ops = {
 			   anx7625_runtime_pm_resume, NULL)
 };
 
+static int anx7625_dp_rates[] = {162000, 270000, 540000, 675000};
+
+static struct drm_connector_dp_link_caps anx7625_dp_link_caps = {
+	.nlanes = 2,
+	.nlink_rates = ARRAY_SIZE(anx7625_dp_rates),
+	.link_rates = anx7625_dp_rates,
+};
+
 static int anx7625_link_bridge(struct drm_dp_aux *aux)
 {
 	struct anx7625_data *platform = container_of(aux, struct anx7625_data, aux);
@@ -2787,6 +2852,9 @@ static int anx7625_link_bridge(struct drm_dp_aux *aux)
 				    DRM_MODE_CONNECTOR_DisplayPort;
 	platform->bridge.support_hdcp = true;
 
+	platform->bridge.dp_link_caps = &anx7625_dp_link_caps;
+	platform->bridge.ops |= DRM_BRIDGE_OP_DP;
+
 	drm_bridge_add(&platform->bridge);
 
 	if (!platform->pdata.is_dpi) {
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h b/drivers/gpu/drm/bridge/analogix/anx7625.h
index 957d234ec07c8..b9a0f62f6398c 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.h
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.h
@@ -90,6 +90,7 @@
 #define HDCP2TX_FW_EN			BIT(4)
 
 #define SP_TX_LINK_BW_SET_REG		0xA0
+#define SP_TX_LINK_BW_SET_MASK		0x1F
 #define SP_TX_LANE_COUNT_SET_REG	0xA1
 
 #define M_VID_0 0xC0

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 8/8] drm/mediatek: Use dp_connector helpers to expose link training state
  2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
                   ` (6 preceding siblings ...)
  2026-10-22 16:38 ` [PATCH v4 7/8] drm/analogix/anx7625: Use dp_connector helpers to expose link training state Kory Maincent
@ 2026-10-22 16:38 ` Kory Maincent
  2026-10-22 17:01   ` sashiko-bot
  7 siblings, 1 reply; 14+ messages in thread
From: Kory Maincent @ 2026-10-22 16:38 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Jani Nikula, Rodrigo Vivi, Joonas Lahtinen,
	Tvrtko Ursulin, Andrzej Hajda, Neil Armstrong, Robert Foss,
	Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Dmitry Baryshkov, Daniel Stone
  Cc: Thomas Petazzoni, Mark Yacoub, Sean Paul, Manasi Navare,
	Drew Davenport, Louis Chauvet, dri-devel, linux-kernel, intel-gfx,
	intel-xe, linux-mediatek, linux-arm-kernel, Kory Maincent

Adopt the DRM core DisplayPort helpers by setting DRM_BRIDGE_OP_DP and
populating the bridge dp_link_caps at probe time. Report the sink
capabilities on hotplug, reset them on disconnect, and report the link
parameters once link training succeeds.

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---
Not tested. I believed I had a board with this device, but it actually uses
an anx7625 bridge. I'm unable to test it; it would be appreciated if
someone else could, so the change isn't dropped.

Changes in v3:
- Sync and report sink capabilities from the HPD thread for DisplayPort,
  or in atomic_enable for eDP. Reset them on unplug, or atomic disable
  and only report link parameters after successful training.
- Add connection_mutex lock for DisplayPort in the IRQ thread.

Changes in v2:
- Remove voltage swing and pre emphasis properties.
---
 drivers/gpu/drm/mediatek/mtk_dp.c | 61 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 57 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/mediatek/mtk_dp.c b/drivers/gpu/drm/mediatek/mtk_dp.c
index f656b85b84212..b87e171d88d83 100644
--- a/drivers/gpu/drm/mediatek/mtk_dp.c
+++ b/drivers/gpu/drm/mediatek/mtk_dp.c
@@ -1812,6 +1812,17 @@ static void mtk_dp_train_change_mode(struct mtk_dp *mtk_dp)
 	mtk_dp_reset_swing_pre_emphasis(mtk_dp);
 }
 
+static void mtk_dp_report_link_train(struct mtk_dp *mtk_dp)
+{
+	u32 rate, nlanes;
+	bool dsc = false;
+
+	rate = drm_dp_bw_code_to_link_rate(mtk_dp->train_info.link_rate);
+	nlanes = mtk_dp->train_info.lane_count;
+
+	drm_dp_set_cur_link_params(mtk_dp->conn, rate, nlanes, dsc);
+}
+
 static int mtk_dp_training(struct mtk_dp *mtk_dp)
 {
 	int ret;
@@ -2015,6 +2026,8 @@ static irqreturn_t mtk_dp_hpd_event_thread(int hpd, void *dev)
 			ret = mtk_dp_training(mtk_dp);
 			if (ret)
 				drm_err(mtk_dp->drm_dev, "Training failed, %d\n", ret);
+			else
+				mtk_dp_report_link_train(mtk_dp);
 
 			mtk_dp->enabled = true;
 		}
@@ -2145,9 +2158,12 @@ mtk_dp_bdg_detect(struct drm_bridge *bridge, struct drm_connector *connector)
 	struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge);
 	enum drm_connector_status ret = connector_status_disconnected;
 	bool enabled = mtk_dp->enabled;
+	int err;
 
-	if (!mtk_dp->train_info.cable_plugged_in)
+	if (!mtk_dp->train_info.cable_plugged_in) {
+		drm_dp_sink_reset_caps(connector);
 		return ret;
+	}
 
 	if (!enabled)
 		mtk_dp_aux_panel_poweron(mtk_dp, true);
@@ -2160,9 +2176,18 @@ mtk_dp_bdg_detect(struct drm_bridge *bridge, struct drm_connector *connector)
 	 * whether we connect to a sink device.
 	 */
 
-	if (drm_dp_read_sink_count(&mtk_dp->aux) > 0)
+	if (drm_dp_read_sink_count(&mtk_dp->aux) > 0) {
 		ret = connector_status_connected;
 
+		err = drm_dp_sink_sync_caps(connector, &mtk_dp->aux, NULL);
+		if (err)
+			drm_dbg_kms(mtk_dp->aux.drm_dev,
+				    "%s: error sink caps synchronisation (%d)\n",
+				    mtk_dp->aux.name, err);
+	} else {
+		drm_dp_sink_reset_caps(connector);
+	}
+
 	if (!enabled)
 		mtk_dp_aux_panel_poweron(mtk_dp, false);
 
@@ -2390,6 +2415,12 @@ static void mtk_dp_bridge_atomic_enable(struct drm_bridge *bridge,
 	if (mtk_dp->data->bridge_type == DRM_MODE_CONNECTOR_eDP) {
 		mtk_dp_aux_panel_poweron(mtk_dp, true);
 
+		ret = drm_dp_sink_sync_caps(mtk_dp->conn, &mtk_dp->aux, NULL);
+		if (ret)
+			drm_dbg_kms(mtk_dp->aux.drm_dev,
+				    "%s: error sink caps synchronisation (%d)\n",
+				    mtk_dp->aux.name, ret);
+
 		/* Training */
 		ret = mtk_dp_training(mtk_dp);
 		if (ret) {
@@ -2398,6 +2429,8 @@ static void mtk_dp_bridge_atomic_enable(struct drm_bridge *bridge,
 		}
 	}
 
+	mtk_dp_report_link_train(mtk_dp);
+
 	ret = mtk_dp_video_config(mtk_dp);
 	if (ret)
 		goto power_off_aux;
@@ -2435,6 +2468,7 @@ static void mtk_dp_bridge_atomic_disable(struct drm_bridge *bridge,
 	if (mtk_dp->data->bridge_type == DRM_MODE_CONNECTOR_eDP) {
 		mtk_dp->enabled = false;
 		mtk_dp_aux_panel_poweron(mtk_dp, false);
+		drm_dp_sink_reset_caps(mtk_dp->conn);
 	}
 
 	mtk_dp_update_plugged_status(mtk_dp);
@@ -2740,11 +2774,14 @@ static int mtk_dp_edp_link_panel(struct drm_dp_aux *mtk_aux)
 	return 0;
 }
 
+static int mtk_dp_rates[] = {162000, 270000, 540000, 810000};
+
 static int mtk_dp_probe(struct platform_device *pdev)
 {
+	struct drm_connector_dp_link_caps *source_caps;
 	struct mtk_dp *mtk_dp;
 	struct device *dev = &pdev->dev;
-	int ret;
+	int ret, i, max_rate;
 
 	mtk_dp = devm_drm_bridge_alloc(dev, struct mtk_dp, bridge,
 				       &mtk_dp_bridge_funcs);
@@ -2758,6 +2795,20 @@ static int mtk_dp_probe(struct platform_device *pdev)
 	if (ret)
 		return dev_err_probe(dev, ret, "Failed to parse dt\n");
 
+	source_caps = devm_kzalloc(dev, sizeof(*source_caps), GFP_KERNEL);
+	if (!source_caps)
+		return -ENOMEM;
+
+	source_caps->nlanes = mtk_dp->max_lanes;
+	source_caps->link_rates = mtk_dp_rates;
+
+	max_rate = drm_dp_bw_code_to_link_rate(mtk_dp->max_linkrate);
+	for (i = 0; i < ARRAY_SIZE(mtk_dp_rates); i++)
+		if (mtk_dp_rates[i] > max_rate)
+			break;
+
+	source_caps->nlink_rates = i;
+
 	/*
 	 * Request the interrupt and install service routine only if we are
 	 * on full DisplayPort.
@@ -2809,6 +2860,8 @@ static int mtk_dp_probe(struct platform_device *pdev)
 
 	mtk_dp->bridge.of_node = dev->of_node;
 	mtk_dp->bridge.type = mtk_dp->data->bridge_type;
+	mtk_dp->bridge.dp_link_caps = source_caps;
+	mtk_dp->bridge.ops = DRM_BRIDGE_OP_DP;
 
 	if (mtk_dp->bridge.type == DRM_MODE_CONNECTOR_eDP) {
 		/*
@@ -2849,7 +2902,7 @@ static int mtk_dp_probe(struct platform_device *pdev)
 			}
 		}
 	} else {
-		mtk_dp->bridge.ops = DRM_BRIDGE_OP_DETECT |
+		mtk_dp->bridge.ops |= DRM_BRIDGE_OP_DETECT |
 				     DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
 		ret = devm_drm_bridge_add(dev, &mtk_dp->bridge);
 		if (ret)

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 1/8] drm: Move sink link rates reading helpers out of i915
  2026-10-22 16:37 ` [PATCH v4 1/8] drm: Move sink link rates reading helpers out of i915 Kory Maincent
@ 2026-10-22 16:56   ` sashiko-bot
  0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-22 16:56 UTC (permalink / raw)
  To: Kory Maincent; +Cc: dri-devel, intel-gfx, intel-xe

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] drm/i915/dp: HBR2 quirk in intel_edp_set_sink_rates() skips fallback
--

commit 0b240b52afdb3eb8ae0e254bde919dbb5abd0487
Author: Kory Maincent <kory.maincent@bootlin.com>

drm: Move sink link rates reading helpers out of i915

This commit moves the DisplayPort and eDP sink link rate reading helpers out
of the i915 driver into generic display helpers in drm_dp_helper.c, while
keeping the Intel-specific rate limit quirk handling in intel_dp.c.

> diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
> index 0cd5e6b5034cf..087afcea5395f 100644
> --- a/drivers/gpu/drm/i915/display/intel_dp.c
> +++ b/drivers/gpu/drm/i915/display/intel_dp.c

[ ... ]

> @@ -4722,60 +4619,35 @@ static void
>  intel_edp_set_sink_rates(struct intel_dp *intel_dp)
>  {
>  	struct intel_display *display = to_intel_display(intel_dp);
> +	int num_sink_rates;
>  
> -	intel_dp->num_sink_rates = 0;
> +	num_sink_rates = drm_dp_read_edp_sink_rates(&intel_dp->aux,
> +						    intel_dp->edp_dpcd[0],
> +						    intel_dp->sink_rates);
> +	if (num_sink_rates < 0)
> +		num_sink_rates = 0;
>  
> -	if (intel_dp->edp_dpcd[0] >= DP_EDP_14) {
> -		__le16 sink_rates[DP_MAX_SUPPORTED_RATES];
> -		int ret;
> -		int i;
> -
> -		ret = drm_dp_dpcd_read_data(&intel_dp->aux,
> -					    DP_SUPPORTED_LINK_RATES,
> -					    sink_rates, sizeof(sink_rates));
> -		if (ret < 0) {
> -			drm_dbg_kms(display->drm,
> -				    "Unable to read eDP supported link rates, using default rates\n");
> -			memset(sink_rates, 0, sizeof(sink_rates));
> -		}
> -
> -		for (i = 0; i < ARRAY_SIZE(sink_rates); i++) {
> -			int rate;
> -
> -			/* Value read multiplied by 200kHz gives the per-lane
> -			 * link rate in kHz. The source rates are, however,
> -			 * stored in terms of LS_Clk kHz. The full conversion
> -			 * back to symbols is
> -			 * (val * 200kHz)*(8/10 ch. encoding)*(1/8 bit to Byte)
> -			 */
> -			rate = le16_to_cpu(sink_rates[i]) * 200 / 10;
> -
> -			if (rate == 0)
> -				break;
> -
> -			/*
> -			 * Some platforms cannot reliably drive HBR3 rates due to PHY limitations,
> -			 * even if the sink advertises support. Reject any sink rates above HBR2 on
> -			 * the known machines for stable output.
> -			 */
> -			if (rate > 540000 &&
> -			    intel_has_quirk(display, QUIRK_EDP_LIMIT_RATE_HBR2))
> -				break;
> -
> -			intel_dp->sink_rates[i] = rate;
> -		}
> -		intel_dp->num_sink_rates = i;
> -	}
> +	intel_dp->num_sink_rates = num_sink_rates;
>  
>  	/*
>  	 * Use DP_LINK_RATE_SET if DP_SUPPORTED_LINK_RATES are available,
>  	 * default to DP_MAX_LINK_RATE and DP_LINK_BW_SET otherwise.
>  	 */
> -	if (intel_dp->num_sink_rates)
> +	if (num_sink_rates)
>  		intel_dp->use_rate_select = true;
>  	else
>  		intel_dp_set_sink_rates(intel_dp);
>  
> +	/*
> +	 * Some platforms cannot reliably drive HBR3 rates due to PHY limitations,
> +	 * even if the sink advertises support. Reject any sink rates above HBR2 on
> +	 * the known machines for stable output.
> +	 */
> +	if (intel_dp->num_sink_rates && intel_has_quirk(display, QUIRK_EDP_LIMIT_RATE_HBR2))
> +		intel_dp->num_sink_rates = intel_dp_rate_limit_len(intel_dp->sink_rates,
> +								   intel_dp->num_sink_rates,
> +								   540000);
> +
>  	intel_edp_set_data_override_rates(intel_dp);
>  }

[Severity: Medium]
In drivers/gpu/drm/i915/display/intel_dp.c, does moving the hbr2 rate limit
check in intel_edp_set_sink_rates() after the num_sink_rates check skip the
fallback to intel_dp_set_sink_rates() when all rates read by
drm_dp_read_edp_sink_rates() exceed 540000 kHz?

Previously, intel_edp_set_sink_rates() rejected rates above 540000 kHz inside
the loop before checking if intel_dp->num_sink_rates was non-zero:

    if (rate > 540000 &&
        intel_has_quirk(display, QUIRK_EDP_LIMIT_RATE_HBR2))
            break;

If all advertised rates in that table exceeded 540000 kHz,
intel_dp->num_sink_rates remained 0, intel_dp->use_rate_select stayed false,
and execution fell back to intel_dp_set_sink_rates() where max_dprx_rate()
capped the maximum link rate to 540000 kHz.

With this change, drm_dp_read_edp_sink_rates() returns the unfiltered count,
so num_sink_rates is non-zero, intel_dp->use_rate_select is set to true, and
intel_dp_set_sink_rates() is skipped. When intel_dp_rate_limit_len() then
filters out the rates above 540000 kHz, intel_dp->num_sink_rates becomes 0
while intel_dp->use_rate_select remains true.

During connector initialization via intel_dp_init_connector() ->
intel_edp_init_connector() -> intel_edp_init_dpcd() ->
intel_edp_set_sink_rates(), the subsequent call to
intel_dp_set_common_link_params() -> intel_dp_get_common_rates() will then
hit both warnings and force common_rates[0] = 162000:

drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_get_common_rates() {
    drm_WARN_ON(display->drm,
                !intel_dp->num_source_rates || !intel_dp->num_sink_rates);

    *num_common_rates = intersect_rates(intel_dp->source_rates,
                                        intel_dp->num_source_rates,
                                        intel_dp->sink_rates,
                                        intel_dp->num_sink_rates,
                                        common_rates);

    /* Paranoia, there should always be something in common. */
    if (drm_WARN_ON(display->drm, *num_common_rates == 0)) {
            common_rates[0] = 162000;
            *num_common_rates = 1;
    }
}

And during modeset link computation via intel_dp_compute_rate() ->
intel_dp_rate_select(), because intel_dp->use_rate_select is true while
intel_dp->num_sink_rates is 0, intel_dp_rate_select() will fail to find the
rate, warn, and return index 0:

drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_rate_select() {
    int i = intel_dp_rate_index(intel_dp->sink_rates,
                                intel_dp->num_sink_rates, rate);

    if (drm_WARN_ON(display->drm, i < 0))
            i = 0;

    return i;
}

Should intel_dp_rate_limit_len() be applied to num_sink_rates before checking
if (num_sink_rates), and applied again if it falls back to
intel_dp_set_sink_rates()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-feat_link_cap-v4-0-a4f3f6e8cad9@bootlin.com?part=1

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 8/8] drm/mediatek: Use dp_connector helpers to expose link training state
  2026-10-22 16:38 ` [PATCH v4 8/8] drm/mediatek: " Kory Maincent
@ 2026-10-22 17:01   ` sashiko-bot
  0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-22 17:01 UTC (permalink / raw)
  To: Kory Maincent; +Cc: dri-devel, intel-gfx, intel-xe

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] drm/mediatek: NULL mtk_dp->conn in mtk_dp_report_link_train() on hotplug
- [Medium] drm/mediatek: mtk_dp_bridge_atomic_enable() reports untrained DP link state
- [Medium] drm/mediatek: unsynchronized DP link state reporting in mtk_dp_hpd_event_thread()
--

commit da87270e7bdd066871fe7e9da8a36687e44518d5
Author: Kory Maincent <kory.maincent@bootlin.com>

drm/mediatek: Use dp_connector helpers to expose link training state

This commit adopts the DRM core DisplayPort helpers in mtk_dp by setting
DRM_BRIDGE_OP_DP and populating dp_link_caps at probe time. It also updates
the hotplug, detect, and atomic enable/disable paths to synchronize sink
capabilities and report link training parameters.

> diff --git a/drivers/gpu/drm/mediatek/mtk_dp.c b/drivers/gpu/drm/mediatek/mtk_dp.c
> index f656b85b84212..b87e171d88d83 100644
> --- a/drivers/gpu/drm/mediatek/mtk_dp.c
> +++ b/drivers/gpu/drm/mediatek/mtk_dp.c
> @@ -1812,6 +1812,17 @@ static void mtk_dp_train_change_mode(struct mtk_dp *mtk_dp)
>  	mtk_dp_reset_swing_pre_emphasis(mtk_dp);
>  }
>  
> +static void mtk_dp_report_link_train(struct mtk_dp *mtk_dp)
> +{
> +	u32 rate, nlanes;
> +	bool dsc = false;
> +
> +	rate = drm_dp_bw_code_to_link_rate(mtk_dp->train_info.link_rate);
> +	nlanes = mtk_dp->train_info.lane_count;
> +
> +	drm_dp_set_cur_link_params(mtk_dp->conn, rate, nlanes, dsc);
> +}

[Severity: Medium]
Will mtk_dp->conn be NULL when mtk_dp_report_link_train() is called from
mtk_dp_hpd_event_thread() on initial DisplayPort hotplug?

In drivers/gpu/drm/mediatek/mtk_dp.c, mtk_dp->conn is only assigned in
mtk_dp_bridge_atomic_enable():

	mtk_dp->conn = drm_atomic_get_new_connector_for_encoder(state,
								bridge->encoder);

Neither mtk_dp_bdg_detect() nor mtk_dp_bridge_attach() initializes
mtk_dp->conn. Moreover, on initial hotplug, mtk_dp_bridge_mode_valid()
rejects all modes until mtk_dp_training() sets train_info.link_rate and
train_info.lane_count in mtk_dp_hpd_event_thread(), so
mtk_dp_bridge_atomic_enable() cannot have run prior to the first
mtk_dp_report_link_train() call in mtk_dp_hpd_event_thread().

When mtk_dp_hpd_event_thread() runs link training and calls
mtk_dp_report_link_train() -> drm_dp_set_cur_link_params() before a modeset
has enabled the bridge, mtk_dp->conn is still NULL, causing
drm_dp_set_cur_link_params() to return early without recording cur_link_info
or updating sysfs.

>  
>  static int mtk_dp_training(struct mtk_dp *mtk_dp)
>  {
>  	int ret;
> @@ -2015,6 +2026,8 @@ static irqreturn_t mtk_dp_hpd_event_thread(int hpd, void *dev)
>  			ret = mtk_dp_training(mtk_dp);
>  			if (ret)
>  				drm_err(mtk_dp->drm_dev, "Training failed, %d\n", ret);
> +			else
> +				mtk_dp_report_link_train(mtk_dp);
>  
>  			mtk_dp->enabled = true;
>  		}
> @@ -2145,9 +2158,12 @@ mtk_dp_bdg_detect(struct drm_bridge *bridge, struct drm_connector *connector)
>  	struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge);
>  	enum drm_connector_status ret = connector_status_disconnected;
>  	bool enabled = mtk_dp->enabled;
> +	int err;
>  
> -	if (!mtk_dp->train_info.cable_plugged_in)
> +	if (!mtk_dp->train_info.cable_plugged_in) {
> +		drm_dp_sink_reset_caps(connector);
>  		return ret;
> +	}
>  
>  	if (!enabled)
>  		mtk_dp_aux_panel_poweron(mtk_dp, true);
> @@ -2160,9 +2176,18 @@ mtk_dp_bdg_detect(struct drm_bridge *bridge, struct drm_connector *connector)
>  	 * whether we connect to a sink device.
>  	 */
>  
> -	if (drm_dp_read_sink_count(&mtk_dp->aux) > 0)
> +	if (drm_dp_read_sink_count(&mtk_dp->aux) > 0) {
>  		ret = connector_status_connected;
>  
> +		err = drm_dp_sink_sync_caps(connector, &mtk_dp->aux, NULL);
> +		if (err)
> +			drm_dbg_kms(mtk_dp->aux.drm_dev,
> +				    "%s: error sink caps synchronisation (%d)\n",
> +				    mtk_dp->aux.name, err);
> +	} else {
> +		drm_dp_sink_reset_caps(connector);
> +	}
> +
>  	if (!enabled)
>  		mtk_dp_aux_panel_poweron(mtk_dp, false);

[Severity: Medium]
Can mtk_dp_bdg_detect() race with mtk_dp_hpd_event_thread() during hotplug
processing and corrupt the link training or reported link state?

In mtk_dp_hpd_event_thread(), drm_helper_hpd_irq_event() is called before
mtk_dp_training(), mtk_dp_report_link_train(), and mtk_dp->enabled = true:

	if (status & MTK_DP_THREAD_CABLE_STATE_CHG) {
		if (mtk_dp->bridge.dev)
			drm_helper_hpd_irq_event(mtk_dp->bridge.dev);
		...
		} else {
			mtk_dp_aux_panel_poweron(mtk_dp, true);
			...
			ret = mtk_dp_training(mtk_dp);
			if (ret)
				drm_err(mtk_dp->drm_dev, "Training failed, %d\n", ret);
			else
				mtk_dp_report_link_train(mtk_dp);

			mtk_dp->enabled = true;
		}
	}

Inside drm_helper_hpd_irq_event(), mtk_dp_bdg_detect() runs under
mode_config.mutex, syncs or resets sink capabilities, and then
drm_helper_hpd_irq_event() drops mode_config.mutex and sends a hotplug
uevent to userspace.

Because mtk_dp_hpd_event_thread() performs capability parsing,
mtk_dp_training(), and mtk_dp_report_link_train() without holding
connection_mutex, mode_config.mutex, or any driver lock while
mtk_dp->enabled is still false, a concurrent userspace probe calling
mtk_dp_bdg_detect() sees enabled == false, runs drm_dp_sink_sync_caps() over
AUX concurrently with mtk_dp_training(), and then calls
mtk_dp_aux_panel_poweron(mtk_dp, false), powering down the panel and AUX in
the middle of link training.

In addition, if a branch device asserts HPD with no downstream sink
(cable_plugged_in is true and drm_dp_read_sink_count(&mtk_dp->aux) <= 0)
after mtk_dp->conn has been set, mtk_dp_bdg_detect() calls
drm_dp_sink_reset_caps(connector), after which mtk_dp_hpd_event_thread()
runs mtk_dp_report_link_train(mtk_dp) and repopulates cur_link_info on a
disconnected connector.

> @@ -2390,6 +2415,12 @@ static void mtk_dp_bridge_atomic_enable(struct drm_bridge *bridge,
>  	if (mtk_dp->data->bridge_type == DRM_MODE_CONNECTOR_eDP) {
>  		mtk_dp_aux_panel_poweron(mtk_dp, true);
>  
> +		ret = drm_dp_sink_sync_caps(mtk_dp->conn, &mtk_dp->aux, NULL);
> +		if (ret)
> +			drm_dbg_kms(mtk_dp->aux.drm_dev,
> +				    "%s: error sink caps synchronisation (%d)\n",
> +				    mtk_dp->aux.name, ret);
> +
>  		/* Training */
>  		ret = mtk_dp_training(mtk_dp);
>  		if (ret) {
> @@ -2398,6 +2429,8 @@ static void mtk_dp_bridge_atomic_enable(struct drm_bridge *bridge,
>  		}
>  	}
>  
> +	mtk_dp_report_link_train(mtk_dp);
> +
>  	ret = mtk_dp_video_config(mtk_dp);
>  	if (ret)
>  		goto power_off_aux;

[Severity: Medium]
Should mtk_dp_report_link_train() in mtk_dp_bridge_atomic_enable() be
restricted to eDP, or guarded on successful DisplayPort link training?

In mtk_dp_initialize_priv_data(), mtk_dp->train_info.link_rate is
initialized to DP_LINK_BW_5_4 and mtk_dp->train_info.lane_count is
initialized to mtk_dp->max_lanes.

For full DisplayPort (DRM_MODE_CONNECTOR_DisplayPort), mtk_dp_training() is
called in mtk_dp_hpd_event_thread() rather than in
mtk_dp_bridge_atomic_enable(). However, in mtk_dp_hpd_event_thread(),
drm_helper_hpd_irq_event() is called before mtk_dp_training(), and if
mtk_dp_training() fails, train_info.link_rate and train_info.lane_count
retain their initial defaults or stale values from a previous sink while
mtk_dp->enabled is still set to true.

When a modeset is committed (either via drm_helper_hpd_irq_event() ->
drm_client_dev_hotplug() before mtk_dp_training() runs, or by userspace
after mtk_dp_training() fails), mtk_dp_bridge_atomic_enable()
unconditionally calls mtk_dp_report_link_train(mtk_dp) outside the eDP
branch, exposing untrained or stale train_info parameters via
drm_dp_set_cur_link_params().

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-feat_link_cap-v4-0-a4f3f6e8cad9@bootlin.com?part=8

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 3/8] drm: Introduce DisplayPort link training capabilities and state helpers
  2026-10-22 16:37 ` [PATCH v4 3/8] drm: Introduce DisplayPort link training capabilities and state helpers Kory Maincent
@ 2026-10-22 17:02   ` sashiko-bot
  0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-22 17:02 UTC (permalink / raw)
  To: Kory Maincent; +Cc: dri-devel, intel-gfx, intel-xe

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] drm: use-after-free on connector->kdev in drm_sysfs_connector_remove()
- [High] drm: kdev race in drm_sysfs_connector_add() causes -EEXIST failure
- [Medium] drm: unlocked nlanes read in dp_link_attr_visible() races with updates
- [Medium] drm: missing LTTPR validation in drm_dp_sink_sync_caps()
--

commit 9bbc7a53acf715de24f598695d290cb7880247c1
Author: Kory Maincent <kory.maincent@bootlin.com>

drm: Introduce DisplayPort link training capabilities and state helpers

This commit adds DisplayPort link training capability and state helpers to
drm_dp_helper.c and exposes source, sink, and current link parameters
through a dp_link sysfs attribute group on DisplayPort and eDP connectors.

> diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c
> index 8473dbcd8808a..5e53c6a9d35fd 100644
> --- a/drivers/gpu/drm/display/drm_dp_helper.c
> +++ b/drivers/gpu/drm/display/drm_dp_helper.c

[ ... ]

> @@ -5135,6 +5136,30 @@ int drm_dp_read_dpcd_sink_rates(struct drm_dp_aux *aux,
>  }
>  EXPORT_SYMBOL_GPL(drm_dp_read_dpcd_sink_rates);
>  
> +/**
> + * drm_dp_get_max_lane_count - Get the max lane count from DPCD and LTTPR
> + *			       capabilities
> + * @dpcd: DPCD receiver capabilities
> + * @lttpr_common_caps: LTTPR common capabilities (NULL if no LTTPR supported)
> + *
> + * Returns: Number of max lane count
> + */
> +int drm_dp_get_max_lane_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
> +			      const u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE])
> +{
> +	int nlanes, lttpr_nlanes;
> +
> +	nlanes = dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK;
> +	if (lttpr_common_caps) {
> +		lttpr_nlanes = drm_dp_lttpr_max_lane_count(lttpr_common_caps);
> +		if (lttpr_nlanes)
> +			nlanes = min(nlanes, lttpr_nlanes);
> +	}
> +
> +	return nlanes;
> +}
> +EXPORT_SYMBOL_GPL(drm_dp_get_max_lane_count);
> +
>  /**
>   * drm_dp_read_edp_sink_rates() - read the sink rates from the eDP DPCD
>   * @aux: DisplayPort AUX channel
> @@ -5196,3 +5221,284 @@ int drm_dp_read_dsc_dpcd(struct drm_dp_aux *aux,

[ ... ]

> +int drm_dp_sink_sync_caps(struct drm_connector *connector, struct drm_dp_aux *aux,
> +			  const struct drm_dp_tunnel *tunnel)
> +{
> +	u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE] = {0};
> +	struct drm_connector_dp_link_caps link_caps;
> +	int sink_rates[DP_MAX_SUPPORTED_RATES] = {0};
> +	u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE] = {0};
> +	u8 dpcd_caps[DP_RECEIVER_CAP_SIZE] = {0};
> +	struct drm_dp_desc desc = {0};
> +	int num_sink_rates, ret;
> +	u8 edp_dpcd_rev = 0;
> +
> +	if (!connector)
> +		return -ENODEV;
> +
> +	ret = drm_dp_read_dpcd_caps(aux, dpcd_caps);
> +	if (ret < 0) {
> +		drm_dbg_kms(aux->drm_dev, "%s: error reading DPCD capabilities (%d)\n",
> +			    aux->name, ret);
> +		return ret;
> +	}
> +
> +	if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
> +		ret = drm_dp_dpcd_read_byte(aux, DP_EDP_DPCD_REV, &edp_dpcd_rev);
> +		if (ret < 0)
> +			drm_dbg_kms(aux->drm_dev, "%s: No DPCD eDP revision found\n",
> +				    aux->name);
> +	}
> +
> +	if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort &&
> +	    dpcd_caps[DP_DPCD_REV] >= DP_DPCD_REV_13) {
> +		ret = drm_dp_read_lttpr_common_caps(aux, dpcd_caps, lttpr_common_caps);
> +		if (ret < 0) {
> +			drm_dbg_kms(aux->drm_dev,
> +				    "%s: error reading LTTPR caps, assuming no LTTPR (%d)\n",
> +				    aux->name, ret);
> +			/* LTTPR capabilities are read block by block. Clean the
> +			 * entire capabilities if the read fails to avoid
> +			 * partial garbage
> +			 */
> +			memset(lttpr_common_caps, 0, sizeof(lttpr_common_caps));
> +		}
> +	}

[ ... ]

> +	link_caps.nlanes = drm_dp_get_max_lane_count(dpcd_caps, lttpr_common_caps);
> +	link_caps.nlink_rates = num_sink_rates;
> +	link_caps.link_rates = sink_rates;
> +	link_caps.dsc = drm_dp_sink_supports_dsc(dsc_dpcd);
> +
> +	return drm_dp_sink_set_caps(connector, &link_caps);
> +}
> +EXPORT_SYMBOL_GPL(drm_dp_sink_sync_caps);

[Severity: Medium]
Can drm_dp_sink_sync_caps() and drm_dp_get_max_lane_count() clamp
sink_link_caps.nlanes and sink_link_caps.link_rates using unvalidated
repeater bytes when a sink does not have repeaters present?

In drivers/gpu/drm/display/drm_dp_helper.c, drm_dp_sink_sync_caps() calls
drm_dp_read_lttpr_common_caps(aux, dpcd_caps, lttpr_common_caps) for any
DisplayPort 1.3 or newer sink and only zeroes lttpr_common_caps if the read
returns a negative error code.

If a sink without repeaters succeeds on that read and returns non-zero
bytes where lttpr_common_caps[0] < 0x14 or
drm_dp_lttpr_count(lttpr_common_caps) <= 0, neither
drm_dp_sink_sync_caps(), drm_dp_get_max_lane_count(), nor the 8b/10b path
in drm_dp_read_dpcd_sink_rates() validates the repeater revision or count
the way drm_dp_get_128b132b_link_rates() does:

drivers/gpu/drm/display/drm_dp_helper.c:drm_dp_get_128b132b_link_rates() {
	...
	if (lttpr_common_caps && drm_dp_lttpr_count(lttpr_common_caps) > 0) {
		/* We have a repeater */
		if (lttpr_common_caps[0] >= 0x20 &&
	...
}

In addition, drm_dp_sink_sync_caps() passes tunnel to
drm_dp_read_dpcd_sink_rates(), but drm_dp_get_max_lane_count() does not
account for drm_dp_tunnel_max_dprx_lane_count(tunnel).

Should lttpr_common_caps be validated (checking lttpr_common_caps[0] >=
0x14 and drm_dp_lttpr_count(lttpr_common_caps) > 0) before clamping
nlanes and 8b/10b link rates, and should tunnel lane count limits also be
applied?

[ ... ]

> diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
> index 7b16d32b6ff65..ac9762a066db4 100644
> --- a/drivers/gpu/drm/drm_sysfs.c
> +++ b/drivers/gpu/drm/drm_sysfs.c
> @@ -340,6 +340,151 @@ static const struct attribute_group *connector_dev_groups[] = {

[ ... ]

> +static umode_t dp_link_attr_visible(struct kobject *kobj,
> +				    struct attribute *a, int n)
> +{
> +	struct device *dev = kobj_to_dev(kobj);
> +	struct drm_connector *connector = to_drm_connector(dev);
> +	struct drm_connector_dp_link_caps *caps;
> +
> +	if (a == &dev_attr_source_link_caps_nlanes.attr ||
> +	    a == &dev_attr_source_link_caps_link_rates.attr ||
> +	    a == &dev_attr_source_link_caps_dsc.attr)
> +		caps = &connector->dp.source_link_caps;
> +	else if (a == &dev_attr_sink_link_caps_nlanes.attr ||
> +		 a == &dev_attr_sink_link_caps_link_rates.attr ||
> +		 a == &dev_attr_sink_link_caps_dsc.attr)
> +		caps = &connector->dp.sink_link_caps;
> +	else
> +		caps = &connector->dp.cur_link_info;
> +
> +	/* Checking nlanes is enough to decide whether the attributes should
> +	 * be exposed: without lanes there is no working DisplayPort link
> +	 */
> +	return caps->nlanes ? a->mode : 0;
> +}

[Severity: Medium]
What prevents a data race on caps->nlanes in dp_link_attr_visible() when
sink capabilities or active link parameters are updated concurrently?

In drivers/gpu/drm/display/drm_dp_helper.c, drm_dp_sink_set_caps(),
drm_dp_set_cur_link_params(), and drm_dp_sink_reset_caps() modify
sink_link_caps.nlanes and cur_link_info.nlanes under connector->dp.mutex
and release that lock before drm_sysfs_connector_dp_link_update() acquires
connector->dp.sysfs_mutex:

drivers/gpu/drm/display/drm_dp_helper.c:drm_dp_sink_set_caps() {
	...
	scoped_guard(mutex, &connector->dp.mutex) {
		/* Only refresh the group when the visibility changes. */
		update = !connector->dp.sink_link_caps.nlanes != !link_caps->nlanes;

		ret = drm_dp_copy_caps(&connector->dp.sink_link_caps, link_caps);
		if (ret)
			return ret;
	}

	if (update)
		drm_sysfs_connector_dp_link_update(connector);
	...
}

Meanwhile, dp_link_attr_visible() in drivers/gpu/drm/drm_sysfs.c reads
caps->nlanes without holding connector->dp.mutex during
sysfs_create_group(), sysfs_update_group(), or sysfs_group_change_owner().

If a writer modifies caps->nlanes on another processor while
create_files() in fs/sysfs/group.c iterates over connector_dp_link_attrs,
could dp_link_attr_visible() observe different values of caps->nlanes
across the three attributes in the same group, or cause a concurrent
sysfs_group_change_owner() call to see non-zero visibility before the
kernfs nodes are created?

> +
> +static const struct attribute_group connector_dp_link_group = {
> +	.name = "dp_link",
> +	.attrs = connector_dp_link_attrs,
> +	.is_visible = dp_link_attr_visible,
> +};
> +
> +/**
> + * drm_sysfs_connector_dp_link_update - Update the visibility of the DP link
> + *                                      attributes
> + * @connector: DisplayPort connector
> + */
> +void drm_sysfs_connector_dp_link_update(struct drm_connector *connector)
> +{
> +	int ret;
> +
> +	if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort &&
> +	    connector->connector_type != DRM_MODE_CONNECTOR_eDP)
> +		return;
> +
> +	scoped_guard(mutex, &connector->dp.sysfs_mutex) {
> +		if (!connector->kdev)
> +			return;
> +
> +		ret = sysfs_update_group(&connector->kdev->kobj,
> +					 &connector_dp_link_group);
> +	}
> +	if (ret)
> +		drm_err(connector->dev,
> +			"failed to update DP link sysfs attributes: %d\n", ret);
> +}
> +EXPORT_SYMBOL_GPL(drm_sysfs_connector_dp_link_update);

[Severity: High]
Could drm_sysfs_connector_dp_link_update() race with
drm_sysfs_connector_remove() and trigger a use-after-free on
connector->kdev?

In drivers/gpu/drm/drm_sysfs.c, drm_sysfs_connector_dp_link_update()
checks connector->kdev and passes &connector->kdev->kobj to
sysfs_update_group() under connector->dp.sysfs_mutex, without holding
connector->mutex.

However, when drm_connector_unregister() calls
drm_sysfs_connector_remove(), connector->kdev is unregistered and freed
before connector->kdev is cleared and without taking
connector->dp.sysfs_mutex:

drivers/gpu/drm/drm_sysfs.c:drm_sysfs_connector_remove() {
	if (!connector->kdev)
		return;
	...
	device_unregister(connector->kdev);
	connector->kdev = NULL;
}

Since device_unregister(connector->kdev) calls device_del() and
put_device() (freeing connector->kdev via drm_sysfs_release()) before
clearing connector->kdev, wouldn't a concurrent call to
drm_sysfs_connector_dp_link_update() from drm_dp_sink_set_caps(),
drm_dp_sink_sync_caps(), drm_dp_set_cur_link_params(), or
drm_dp_sink_reset_caps() observe a non-null connector->kdev and
dereference &connector->kdev->kobj while or after it is freed?

> +
>  int drm_sysfs_connector_add(struct drm_connector *connector)
>  {
>  	struct drm_device *dev = connector->dev;
> @@ -376,6 +521,18 @@ int drm_sysfs_connector_add(struct drm_connector *connector)
>  
>  	connector->kdev = kdev;
>  
> +	if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort ||
> +	    connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
> +		scoped_guard(mutex, &connector->dp.sysfs_mutex)
> +			r = sysfs_create_group(&connector->kdev->kobj,
> +					       &connector_dp_link_group);
> +
> +		if (r) {
> +			drm_err(dev, "failed to create DP connector sysfs: %d\n", r);
> +			goto err_dp_sysfs;
> +		}
> +	}
> +
>  	if (dev_fwnode(kdev)) {
>  		r = component_add(kdev, &typec_connector_ops);
>  		if (r)
> @@ -384,6 +541,9 @@ int drm_sysfs_connector_add(struct drm_connector *connector)
>  
>  	return 0;
>  
> +err_dp_sysfs:
> +	connector->kdev = NULL;
> +	device_del(kdev);
>  err_free:
>  	put_device(kdev);
>  	return r;

[Severity: High]
What happens if drm_sysfs_connector_dp_link_update() runs between
assigning connector->kdev = kdev and acquiring connector->dp.sysfs_mutex
in drm_sysfs_connector_add()?

In drivers/gpu/drm/drm_sysfs.c, drm_sysfs_connector_add() publishes
connector->kdev = kdev outside connector->dp.sysfs_mutex before calling
sysfs_create_group(&connector->kdev->kobj, &connector_dp_link_group).

If drm_sysfs_connector_dp_link_update() runs in that window (for example,
from a concurrent hotplug or detect worker), it acquires
connector->dp.sysfs_mutex first, sees a non-null connector->kdev, and
calls sysfs_update_group(). Because the dp_link directory does not exist
yet, internal_create_group() in fs/sysfs/group.c creates the dp_link
directory when source_link_caps.nlanes is non-zero.

When drm_sysfs_connector_add() then acquires connector->dp.sysfs_mutex
and calls sysfs_create_group(), kernfs_create_dir_ns() fails because the
directory already exists and triggers sysfs_warn_dup(). That jumps to
err_dp_sysfs, where connector->kdev is cleared and device_del(kdev) and
put_device(kdev) run outside connector->dp.sysfs_mutex, aborting
connector registration and leaving kdev teardown unsynchronized with
drm_sysfs_connector_dp_link_update().

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-feat_link_cap-v4-0-a4f3f6e8cad9@bootlin.com?part=3

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 7/8] drm/analogix/anx7625: Use dp_connector helpers to expose link training state
  2026-10-22 16:38 ` [PATCH v4 7/8] drm/analogix/anx7625: Use dp_connector helpers to expose link training state Kory Maincent
@ 2026-10-22 17:03   ` sashiko-bot
  0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-22 17:03 UTC (permalink / raw)
  To: Kory Maincent; +Cc: dri-devel, intel-gfx, intel-xe

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] drm/analogix/anx7625: wrong eDP check in anx7625_bridge_atomic_disable()
--

commit 49aa21f51dedc9ed0b5c807bbb55946e8c40288d
Author: Kory Maincent <kory.maincent@bootlin.com>

drm/analogix/anx7625: Use dp_connector helpers to expose link training state

This commit adopts the DisplayPort connector helpers in the anx7625 bridge
driver by setting DRM_BRIDGE_OP_DP, populating dp_link_caps, and reporting
sink capabilities and link training parameters.

> diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c
> index c3af88e14f8b2..2b1235efc9fa4 100644
> --- a/drivers/gpu/drm/bridge/analogix/anx7625.c
> +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c

[ ... ]

> @@ -2580,6 +2606,17 @@ static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge,
>  
>  	anx7625_dp_start(ctx);
>  
> +	/* On DisplayPort case, the caps are set in the detect callback. */
> +	if (bridge->type == DRM_MODE_CONNECTOR_eDP) {
> +		ret = drm_dp_sink_sync_caps(connector, &ctx->aux, NULL);
> +		if (ret)
> +			DRM_DEV_DEBUG_DRIVER(dev,
> +					     "error sink caps synchronisation (%d)\n",
> +					     ret);
> +	}
> +
> +	anx7625_report_link_train(ctx, connector);
> +
>  	conn_state = drm_atomic_get_new_connector_state(state, connector);
>  
>  	if (WARN_ON(!conn_state))
> @@ -2616,6 +2653,12 @@ static void anx7625_bridge_atomic_disable(struct drm_bridge *bridge,
>  		dev_dbg(dev, "update CP to DESIRE\n");
>  	}
>  
> +	/* On DisplayPort unplug, the caps are already reset in the detect
> +	 * callback.
> +	 */
> +	if (bridge->type == DRM_MODE_CONNECTOR_eDP)
> +		drm_dp_sink_reset_caps(ctx->connector);

[Severity: Medium]
In anx7625_bridge_atomic_enable() and anx7625_bridge_atomic_disable(), does
checking bridge->type == DRM_MODE_CONNECTOR_eDP work as intended on
DisplayPort boards that wire port 1 to a downstream dp-connector bridge?

In anx7625_parse_dt_panel(), pdata->panel_bridge is populated with whatever
bridge is connected on port 1:

drivers/gpu/drm/bridge/analogix/anx7625.c:anx7625_parse_dt_panel() {
	pdata->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0);
	...
}

Then in anx7625_link_bridge(), DRM_BRIDGE_OP_DETECT is enabled whenever the
panel is not on the aux-bus, while bridge.type is set to
DRM_MODE_CONNECTOR_eDP whenever pdata.panel_bridge is non-NULL:

drivers/gpu/drm/bridge/analogix/anx7625.c:anx7625_link_bridge() {
	...
	if (!platform->pdata.panel_bridge || !anx7625_of_panel_on_aux_bus(dev))
		platform->bridge.ops |= DRM_BRIDGE_OP_HPD | DRM_BRIDGE_OP_DETECT;
	platform->bridge.type = platform->pdata.panel_bridge ?
				    DRM_MODE_CONNECTOR_eDP :
				    DRM_MODE_CONNECTOR_DisplayPort;
	...
}

On boards using a downstream dp-connector bridge on port 1 (such as
qcs615-ride, lemans-ride, or qcs8300-ride), pdata.panel_bridge is non-NULL,
so bridge->type is DRM_MODE_CONNECTOR_eDP even though DRM_BRIDGE_OP_DETECT
is set and the connector's connector_type is DRM_MODE_CONNECTOR_DisplayPort.

When the display is disabled via anx7625_bridge_atomic_disable() while the
DisplayPort monitor remains plugged in (for example during dpms off or a
modeset disable), bridge->type == DRM_MODE_CONNECTOR_eDP evaluates to true
and calls drm_dp_sink_reset_caps(ctx->connector), clearing
connector->dp.sink_link_caps and connector->dp.cur_link_info for the
still-connected sink until a future hotplug event occurs. Similarly,
anx7625_bridge_atomic_enable() redundantly calls drm_dp_sink_sync_caps()
even though anx7625_bridge_detect() already synchronized the caps on
hotplug.

Should these checks test !(bridge->ops & DRM_BRIDGE_OP_DETECT) (or check
ctx->connector->connector_type == DRM_MODE_CONNECTOR_eDP) instead of
bridge->type?

> +
>  	ctx->connector = NULL;
>  	anx7625_dp_stop(ctx);
>  

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-feat_link_cap-v4-0-a4f3f6e8cad9@bootlin.com?part=7

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 5/8] drm/i915/display/dp: Use dp_connector helpers to expose link training state
  2026-10-22 16:38 ` [PATCH v4 5/8] drm/i915/display/dp: Use dp_connector helpers to expose link training state Kory Maincent
@ 2026-10-22 17:03   ` sashiko-bot
  0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-22 17:03 UTC (permalink / raw)
  To: Kory Maincent; +Cc: dri-devel, intel-gfx, intel-xe

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] drm/i915/dp: UAF and panel resource leak on kmemdup_array() failure in intel_dp_init_connector()
- [Medium] drm/i915/display/dp: missing cur_link_info update on fastboot state readout
- [Medium] drm/i915/dp: inaccurate source and sink lane count caps in sysfs
- [Medium] drm/i915/dp: intel_dp_report_link_train() reports wrong DSC state on MST
--

commit cf7507ada926b43d170f5de858f0ce21a02b59f4
Author: Kory Maincent <kory.maincent@bootlin.com>

drm/i915/display/dp: Use dp_connector helpers to expose link training state

This commit wires up the DRM core DisplayPort sysfs helpers in the i915
driver to report source capabilities, sink capabilities, and active link
training parameters under dp_link/. It also stores the unclamped DPCD sink
rates in dpcd_sink_rates and num_dpcd_sink_rates so the raw sink rates can
be exposed to userspace.

> diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
> index bbd76c0ff04b6..925d0ec939f55 100644
> --- a/drivers/gpu/drm/i915/display/intel_dp.c
> +++ b/drivers/gpu/drm/i915/display/intel_dp.c

[ ... ]

> @@ -6141,6 +6150,20 @@ void intel_dp_dpcd_set_probe(struct intel_dp *intel_dp, bool force_on_external)
>  			      intel_dp_needs_dpcd_probe(intel_dp, force_on_external));
>  }
>  
> +static void intel_dp_set_sink_caps(struct intel_connector *connector,
> +				   struct intel_dp *intel_dp)
> +{
> +	struct drm_connector_dp_link_caps link_caps;
> +
> +	link_caps.nlanes = drm_dp_get_max_lane_count(intel_dp->dpcd,
> +						     intel_dp->lttpr_common_caps);
> +	link_caps.link_rates = intel_dp->dpcd_sink_rates;
> +	link_caps.nlink_rates = intel_dp->num_dpcd_sink_rates;
> +	link_caps.dsc = drm_dp_sink_supports_dsc(connector->dp.dsc_dpcd);
> +
> +	drm_dp_sink_set_caps(&connector->base, &link_caps);
> +}
> +
>  static int
>  intel_dp_detect(struct drm_connector *_connector,
>  		struct drm_modeset_acquire_ctx *ctx,

[ ... ]

> @@ -6243,6 +6268,8 @@ intel_dp_detect(struct drm_connector *_connector,
>  
>  	intel_dp_print_rates(intel_dp);
>  
> +	intel_dp_set_sink_caps(connector, intel_dp);
> +
>  	if (intel_dp->is_mst) {
>  		/*
>  		 * If we are in MST mode then this connector
> @@ -6990,11 +7017,14 @@ intel_dp_init_connector(struct intel_digital_port *dig_port,
>  			struct intel_connector *connector)
>  {
>  	struct intel_display *display = to_intel_display(dig_port);
> +	struct drm_connector_dp_link_caps link_caps;
>  	struct intel_dp *intel_dp = &dig_port->dp;
>  	struct intel_encoder *encoder = &dig_port->base;
>  	struct drm_device *dev = encoder->base.dev;
>  	enum port port = encoder->port;
> +	int *rates;
>  	int type;
> +	int ret;
>  
>  	if (drm_WARN(dev, dig_port->max_lanes < 1,
>  		     "Not enough lanes (%d) for DP on [ENCODER:%d:%s]\n",
> @@ -7069,6 +7099,27 @@ intel_dp_init_connector(struct intel_digital_port *dig_port,
>  	intel_dp_set_common_link_params(intel_dp);
>  	intel_dp_reset_link_params(intel_dp);
>  
> +	rates = kmemdup_array(intel_dp->source_rates,
> +			      intel_dp->num_source_rates,
> +			      sizeof(*rates),
> +			      GFP_KERNEL);
> +	if (!rates) {
> +		intel_dp_aux_fini(intel_dp);
> +		goto fail;
> +	}

[Severity: High]
Can this error path cause a use-after-free of dig_port and leak panel
resources when kmemdup_array() fails on an eDP connector?

At this point in
drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_init_connector(),
intel_edp_init_connector() has already succeeded. For eDP,
intel_edp_init_connector() enables PPS VDD, initializes connector->panel,
and calls intel_pps_init_late() in
drivers/gpu/drm/i915/display/intel_pps.c, which schedules
intel_dp->pps.panel_vdd_work:

drivers/gpu/drm/i915/display/intel_dp.c:intel_edp_init_connector()
  intel_panel_init(connector, drm_edid);
  intel_edp_backlight_setup(intel_dp, connector);
  intel_edp_add_properties(intel_dp);
  intel_pps_init_late(intel_dp);
    edp_panel_vdd_schedule_off(intel_dp);

Jumping to fail only flushes display->power.domains.async_put_work and
cleans up the base DRM connector without cancelling panel_vdd_work or
tearing down the panel:

drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_init_connector()
fail:
	intel_display_power_flush_work(display);
	drm_connector_cleanup(&connector->base);

	return false;

When intel_dp_init_connector() returns false, callers such as
intel_ddi_init() in drivers/gpu/drm/i915/display/intel_ddi.c and
g4x_dp_init() in drivers/gpu/drm/i915/display/g4x_dp.c immediately free
connector and dig_port while dig_port->dp.pps.panel_vdd_work is still
queued on display->wq.unordered, and intel_dp->pps.vdd_wakeref along with
connector->panel resources (fixed_edid, fixed_modes, VBT panel data, and
backlight) are leaked.

Also, since drm_dp_source_set_caps() already duplicates
link_caps.link_rates internally via drm_dp_copy_caps() and its failure is
treated as non-fatal below, could link_caps.link_rates pass
intel_dp->source_rates directly (or treat the allocation failure as
non-fatal) instead of aborting connector init?

> +
> +	link_caps.nlanes = dig_port->max_lanes;
> +	link_caps.nlink_rates = intel_dp->num_source_rates;
> +	link_caps.link_rates = rates;
> +	link_caps.dsc = HAS_DSC(display);
> +
> +	ret = drm_dp_source_set_caps(&connector->base, &link_caps);

[Severity: Medium]
Should source and sink lane count capabilities account for VBT lane limits
and DP tunnel bandwidth allocation mode?

In drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_init_connector(),
intel_dp->source_rates is clamped to VBT limits by
intel_dp_set_source_rates(), but link_caps.nlanes uses raw
dig_port->max_lanes instead of intel_dp_max_source_lane_count(dig_port):

drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_max_source_lane_count()
{
	int vbt_max_lanes = intel_bios_dp_max_lane_count(dig_port->base.devdata);
	int max_lanes = dig_port->max_lanes;

	if (vbt_max_lanes)
		max_lanes = min(max_lanes, vbt_max_lanes);

	return max_lanes;
}

Similarly, in
drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_set_sink_caps(),
link_caps.link_rates uses intel_dp->dpcd_sink_rates (populated via
drm_dp_read_dpcd_sink_rates() with intel_dp->tunnel), whereas
link_caps.nlanes calls drm_dp_get_max_lane_count(intel_dp->dpcd,
intel_dp->lttpr_common_caps) directly. When USB4/Thunderbolt DP tunnel
bandwidth allocation mode is active, intel_dp->dpcd[DP_MAX_LANE_COUNT] can
be masked by the tunnel, unlike max_dprx_lane_count(intel_dp) /
intel_dp->max_sink_lane_count which queries
drm_dp_tunnel_max_dprx_lane_count(intel_dp->tunnel).

Would using intel_dp_max_source_lane_count(dig_port) for source_link_caps
and intel_dp->max_sink_lane_count (or max_dprx_lane_count(intel_dp)) for
sink_link_caps report more accurate lane counts in dp_link/ sysfs?

> +	if (ret)
> +		drm_dbg_kms(display->drm,
> +			    "failed to set source link rates, skipping.\n");
> +
> +	kfree(rates);
> +
>  	/* init MST on ports that can support it */
>  	intel_dp_mst_encoder_init(dig_port, connector->base.base.id);
>  
> diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
> index cb92cff906146..c77cb124f1419 100644
> --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c
> +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
> @@ -1758,6 +1758,18 @@ static void link_recovery_reset(struct intel_dp_link_training *link_training)
>  	link_training->recovery_state = INTEL_DP_LINK_RECOVERY_IDLE;
>  }
>  
> +static void intel_dp_report_link_train(struct intel_dp *intel_dp)
> +{
> +	struct intel_connector *connector = intel_dp->attached_connector;
> +
> +	if (!connector)
> +		return;
> +
> +	drm_dp_set_cur_link_params(&connector->base, intel_dp->link_rate,
> +				   intel_dp->lane_count,
> +				   connector->dp.dsc_decompression_enabled);
> +}

[Severity: Medium]
How is cur_link_info_dsc supposed to behave when DisplayPort MST is
enabled?

In MST mode,
drivers/gpu/drm/i915/display/intel_dp_mst.c:mst_stream_pre_enable() enables
DSC decompression on the dynamic MST stream connector rather than on
intel_dp->attached_connector, and only calls primary_encoder->pre_enable()
(which reaches intel_dp_stop_link_train()) for the first stream:

drivers/gpu/drm/i915/display/intel_dp_mst.c:mst_stream_pre_enable()
	intel_dp_sink_enable_decompression(state, connector, pipe_config);

	if (first_mst_stream) {
		primary_encoder->pre_enable(state, primary_encoder,
					    pipe_config, NULL);

Meanwhile, primary_encoder->pre_enable() skips
intel_dp_sink_enable_decompression() when is_mst is true, so
intel_dp->attached_connector->dp.dsc_decompression_enabled remains false.

As a result, in
drivers/gpu/drm/i915/display/intel_dp_link_training.c:intel_dp_report_link_train(),
connector->dp.dsc_decompression_enabled is always false for MST links,
while the root intel_dp->attached_connector is marked
connector_status_disconnected in
drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_detect() and the active
MST stream connectors have unpopulated dp_link/ sysfs attributes.

> +
>  /**
>   * intel_dp_stop_link_train - stop link training
>   * @intel_dp: DP struct
> @@ -1787,6 +1799,8 @@ void intel_dp_stop_link_train(struct intel_dp *intel_dp,
>  	intel_dp_program_link_training_pattern(intel_dp, crtc_state, DP_PHY_DPRX,
>  					       DP_TRAINING_PATTERN_DISABLE);
>  
> +	intel_dp_report_link_train(intel_dp);

[Severity: Medium]
What happens to cur_link_info when an active DP or eDP link is inherited
from firmware via fastboot without a full modeset?

During driver probe or resume, hardware state readout runs
intel_modeset_setup_hw_state() -> intel_modeset_readout_hw_state(), which
calls intel_dp_sync_state() and intel_dp_connector_sync_state() in
drivers/gpu/drm/i915/display/intel_dp.c:

drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_sync_state()
	if (crtc_state) {
		intel_dp_reset_link_params(intel_dp);
		intel_dp_set_link_params(intel_dp, crtc_state->port_clock, crtc_state->lane_count);
		intel_dp->link.active = true;
	}

drivers/gpu/drm/i915/display/intel_dp.c:intel_dp_connector_sync_state()
	if (crtc_state && crtc_state->dsc.compression_enable) {
		drm_WARN_ON(display->drm,
			    !connector->dp.dsc_decompression_aux);
		connector->dp.dsc_decompression_enabled = true;
	} else {
		connector->dp.dsc_decompression_enabled = false;
	}

When subsequent commits take the fastset path instead of a full modeset,
intel_dp_stop_link_train() in
drivers/gpu/drm/i915/display/intel_dp_link_training.c is never called.
Consequently, drm_dp_set_cur_link_params() is never invoked, leaving
connector->dp.cur_link_info.nlanes at 0 and hiding the cur_link_info_*
sysfs attributes on an active display.

Should drm_dp_set_cur_link_params() also be called during hardware state
readout when crtc_state is active?

> +
>  	if (intel_dp_is_uhbr(crtc_state)) {
>  		ret = poll_timeout_us(ret = intel_dp_128b132b_intra_hop(intel_dp, crtc_state),
>  				      ret == 0,

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-feat_link_cap-v4-0-a4f3f6e8cad9@bootlin.com?part=5

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-10-22 17:04 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
2026-10-22 16:37 ` [PATCH v4 1/8] drm: Move sink link rates reading helpers out of i915 Kory Maincent
2026-10-22 16:56   ` sashiko-bot
2026-10-22 16:37 ` [PATCH v4 2/8] drm: Move DSC capability reading helper " Kory Maincent
2026-10-22 16:37 ` [PATCH v4 3/8] drm: Introduce DisplayPort link training capabilities and state helpers Kory Maincent
2026-10-22 17:02   ` sashiko-bot
2026-10-22 16:38 ` [PATCH v4 4/8] drm/i915/display/dp: Read DPCD DSC capabilities in any case Kory Maincent
2026-10-22 16:38 ` [PATCH v4 5/8] drm/i915/display/dp: Use dp_connector helpers to expose link training state Kory Maincent
2026-10-22 17:03   ` sashiko-bot
2026-10-22 16:38 ` [PATCH v4 6/8] drm/bridge: Expose link training state via new DRM_BRIDGE_OP_DP flag Kory Maincent
2026-10-22 16:38 ` [PATCH v4 7/8] drm/analogix/anx7625: Use dp_connector helpers to expose link training state Kory Maincent
2026-10-22 17:03   ` sashiko-bot
2026-10-22 16:38 ` [PATCH v4 8/8] drm/mediatek: " Kory Maincent
2026-10-22 17:01   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox