[PATCH RFC / WIP] drm/display: add DisplayPort high-level helper code

Dmitry Baryshkov posted 1 patch 14 hours ago
drivers/gpu/drm/display/Kconfig                   |   6 +
drivers/gpu/drm/display/Makefile                  |   2 +
drivers/gpu/drm/display/drm_dp_connector_helper.c | 184 ++++++++++++++++++++++
include/drm/display/drm_dp_connector_helper.h     |  33 ++++
include/drm/drm_connector.h                       | 102 +++++++++++-
5 files changed, 323 insertions(+), 4 deletions(-)
[PATCH RFC / WIP] drm/display: add DisplayPort high-level helper code
Posted by Dmitry Baryshkov 14 hours ago
Add common structure to drm_connector and a set of high-level helpers to
be used by DRM drivers to implement DisplayPort support.

Note: this is currently early WIP patch, sent in order to kick off the
discussion and the hackaton at the XDC 2025.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
 drivers/gpu/drm/display/Kconfig                   |   6 +
 drivers/gpu/drm/display/Makefile                  |   2 +
 drivers/gpu/drm/display/drm_dp_connector_helper.c | 184 ++++++++++++++++++++++
 include/drm/display/drm_dp_connector_helper.h     |  33 ++++
 include/drm/drm_connector.h                       | 102 +++++++++++-
 5 files changed, 323 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig
index df09cf9a8ca19ea894d6f2fad68c0b191e81e3d0..e042a2b7a666e71f5eac748dee8bc506e725533b 100644
--- a/drivers/gpu/drm/display/Kconfig
+++ b/drivers/gpu/drm/display/Kconfig
@@ -40,6 +40,12 @@ config DRM_DISPLAY_DP_AUX_CHARDEV
 	  read and write values to arbitrary DPCD registers on the DP aux
 	  channel.
 
+config DRM_DISPLAY_DP_CONNECTOR_HELPER
+	bool "test dp_conn"
+	select DRM_DISPLAY_DP_HELPER
+	help
+	  DRM display helpers for DisplayPort.
+
 config DRM_DISPLAY_DP_HELPER
 	bool
 	help
diff --git a/drivers/gpu/drm/display/Makefile b/drivers/gpu/drm/display/Makefile
index 0ff4a1ad0222078bf495175915007f1b1f903296..ee7a09adf3baacbbd7ad251ba92dc78458881192 100644
--- a/drivers/gpu/drm/display/Makefile
+++ b/drivers/gpu/drm/display/Makefile
@@ -5,6 +5,8 @@ obj-$(CONFIG_DRM_DISPLAY_DP_AUX_BUS) += drm_dp_aux_bus.o
 drm_display_helper-y := drm_display_helper_mod.o
 drm_display_helper-$(CONFIG_DRM_BRIDGE_CONNECTOR) += \
 	drm_bridge_connector.o
+drm_display_helper-$(CONFIG_DRM_DISPLAY_DP_CONNECTOR_HELPER) += \
+	drm_dp_connector_helper.o
 drm_display_helper-$(CONFIG_DRM_DISPLAY_DP_HELPER) += \
 	drm_dp_dual_mode_helper.o \
 	drm_dp_helper.o \
diff --git a/drivers/gpu/drm/display/drm_dp_connector_helper.c b/drivers/gpu/drm/display/drm_dp_connector_helper.c
new file mode 100644
index 0000000000000000000000000000000000000000..1251a25af1a65d05255e763385eeca24c0b49292
--- /dev/null
+++ b/drivers/gpu/drm/display/drm_dp_connector_helper.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * Based on Nouveau DP code:
+ * Copyright 2009 Red Hat Inc.
+ */
+
+#include <drm/drm_connector.h>
+#include <drm/drm_print.h>
+#include <drm/display/drm_dp_connector_helper.h>
+#include <drm/display/drm_dp_helper.h>
+
+static void drm_connector_dp_init_lttpr_caps(struct drm_connector *connector)
+{
+	struct drm_dp_aux *aux = connector->dp.aux;
+	u8 *lttpr_caps = connector->dp.lttpr_caps;
+	u8 dpcd[DP_RECEIVER_CAP_SIZE];
+	int ret, nr;
+
+	if (connector->dp.caps.forbid_lttpr_init)
+		return;
+
+	/*
+	 * First access should be to the
+	 * DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV,
+	 * otherwise LTTPRs might be not initialized correctly.
+	 */
+	ret = drm_dp_dpcd_probe(aux, DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV);
+	if (ret)
+		goto err;
+
+	ret = drm_dp_read_dpcd_caps(aux, dpcd);
+	if (ret)
+		goto err;
+
+	ret = drm_dp_read_lttpr_common_caps(aux, dpcd, lttpr_caps);
+	if (ret)
+		goto err;
+
+	/* FIXME: don't attempt switching LTTPR mode on active link */
+	nr = drm_dp_lttpr_count(lttpr_caps);
+	ret = drm_dp_lttpr_init(aux, nr);
+	if (ret)
+		goto err;
+
+	connector->dp.lttpr_count = nr;
+
+	return;
+
+err:
+	memset(lttpr_caps, 0, DP_LTTPR_COMMON_CAP_SIZE);
+	connector->dp.lttpr_count = 0;
+}
+
+enum drm_connector_status drm_atomic_helper_connector_dp_detect(struct drm_connector *connector)
+{
+	struct drm_dp_aux *aux = connector->dp.aux;
+	u8 *dpcd = connector->dp.dpcd;
+	struct drm_dp_desc desc;
+	int ret;
+
+	drm_connector_dp_init_lttpr_caps(connector);
+
+	ret = drm_dp_read_dpcd_caps(aux, dpcd);
+	if (ret)
+		return connector_status_disconnected;
+
+	if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
+		u8 value;
+
+		ret = drm_dp_dpcd_read_byte(aux, DP_EDP_DPCD_REV, &value);
+		if (ret < 0)
+			return connector_status_disconnected;
+
+		connector->dp.edp = value;
+	}
+
+	ret = drm_dp_read_desc(aux, &desc, drm_dp_is_branch(dpcd));
+	if (ret < 0)
+		return connector_status_disconnected;
+
+	if (drm_dp_read_sink_count_cap(connector, dpcd, &desc)) {
+		ret = drm_dp_read_sink_count(aux);
+		if (ret < 0)
+			return connector_status_disconnected;
+
+		/* No sink devices */
+		if (!ret)
+			return connector_status_disconnected;
+	}
+
+	return connector_status_connected;
+}
+EXPORT_SYMBOL(drm_atomic_helper_connector_dp_detect);
+
+static bool drm_connector_dp_check_rate(struct drm_connector *connector,
+					u32 rate)
+{
+	for (int j = 0; j < connector->dp.caps.num_supported_rates; j++)
+		if (connector->dp.caps.supported_rates[j] == rate)
+			return true;
+
+	return false;
+}
+
+void drm_atomic_helper_connector_dp_hotplug(struct drm_connector *connector,
+					    enum drm_connector_status status)
+{
+	struct drm_dp_aux *aux = connector->dp.aux;
+	u8 *lttpr_caps = connector->dp.lttpr_caps;
+	u8 *dpcd = connector->dp.dpcd;
+	u32 lane_count;
+	int ret;
+
+	connector->dp.rate_count = 0;
+	if (connector->connector_type == DRM_MODE_CONNECTOR_eDP &&
+	    connector->dp.edp >= DP_EDP_14) {
+		__le16 rates[DP_MAX_SUPPORTED_RATES];
+		int num_rates;
+
+		ret = drm_dp_dpcd_read_data(aux, DP_SUPPORTED_LINK_RATES,
+					    rates, sizeof(rates));
+		if (ret)
+			rates[0] = 0;
+
+		for (num_rates = 0;
+		     num_rates < DP_MAX_SUPPORTED_RATES && rates[num_rates] != 0;
+		     num_rates++)
+			;
+
+		for (int i = num_rates; i > 0; i--) {
+			u32 rate = (le16_to_cpu(rates[i - 1]) * 200) / 10;
+
+			if (!rate)
+				break;
+
+			if (!drm_connector_dp_check_rate(connector, rate))
+				continue;
+
+			connector->dp.rate[connector->dp.rate_count].dpcd = i - 1;
+			connector->dp.rate[connector->dp.rate_count].rate = rate;
+			connector->dp.rate_count++;
+		}
+	}
+
+	if (!connector->dp.rate_count) {
+		const u32 rates[] = { 810000, 540000, 270000, 162000 };
+		u32 max_rate = dpcd[DP_MAX_LINK_RATE] * 27000;
+
+		if (connector->dp.lttpr_count) {
+			int rate = drm_dp_lttpr_max_link_rate(connector->dp.lttpr_caps);
+
+			if (rate && rate < max_rate)
+				max_rate = rate;
+		}
+
+		for (int i = 0; i < ARRAY_SIZE(rates); i++) {
+			u32 rate = rates[i];
+
+			if (rate > max_rate)
+				continue;
+
+			if (!drm_connector_dp_check_rate(connector, rate))
+				continue;
+
+			connector->dp.rate[connector->dp.rate_count].dpcd = -1;
+			connector->dp.rate[connector->dp.rate_count].rate = rate;
+			connector->dp.rate_count++;
+		}
+	}
+
+	lane_count = dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK;
+	if (connector->dp.lttpr_count) {
+		unsigned int lttpr_lane_count = drm_dp_lttpr_max_lane_count(lttpr_caps);
+
+		if (lttpr_lane_count)
+			lane_count = min(lane_count, lttpr_lane_count);
+	}
+
+	connector->dp.dprx_lanes = lane_count;
+
+}
+EXPORT_SYMBOL(drm_atomic_helper_connector_dp_hotplug);
diff --git a/include/drm/display/drm_dp_connector_helper.h b/include/drm/display/drm_dp_connector_helper.h
new file mode 100644
index 0000000000000000000000000000000000000000..a15ae1a4b1a5ef038e2cca3966f88ea1dafa9c13
--- /dev/null
+++ b/include/drm/display/drm_dp_connector_helper.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef __DRM_DP_CONNECTOR_HELPER_H__
+#define __DRM_DP_CONNECTOR_HELPER_H__
+
+enum drm_connector_status;
+
+enum drm_connector_status drm_atomic_helper_connector_dp_detect(struct drm_connector *connector);
+
+void drm_atomic_helper_connector_dp_hotplug(struct drm_connector *connector,
+					    enum drm_connector_status status);
+
+#endif
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 8f34f4b8183d83dccd3e820a444fbf74fb6c16f2..953affeff0d6b7ca574d2b70fc1117dc435a97e8 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -31,6 +31,7 @@
 #include <drm/drm_mode_object.h>
 #include <drm/drm_util.h>
 #include <drm/drm_property.h>
+#include <drm/display/drm_dp.h>
 
 #include <uapi/drm/drm_mode.h>
 
@@ -46,6 +47,7 @@ struct drm_property_blob;
 struct drm_printer;
 struct drm_privacy_screen;
 struct drm_edid;
+struct drm_dp_aux;
 struct edid;
 struct hdmi_codec_daifmt;
 struct hdmi_codec_params;
@@ -1882,6 +1884,91 @@ struct drm_connector_cec {
 	void *data;
 };
 
+struct drm_connector_dp_rate_entry {
+	int dpcd;
+	u32 rate;
+};
+
+/**
+ * struct drm_connector_dp - DRM Connector DisplayPort capabilities
+ */
+struct drm_connector_dp_caps {
+	/**
+	 * @forbid_lttpr_init - forbid LTTPR init and access, e.g. on platforms
+	 * with AUX timeout < 3.2 ms.
+	 */
+	bool forbid_lttpr_init;
+
+	/**
+	 * @supported_rates - array of supported link rates, sorted in
+	 * ascending order
+	 */
+	u32 supported_rates[DP_MAX_SUPPORTED_RATES + 1];
+
+	/**
+	 * @num_supported_rates - number of valied entries in the
+	 * @supported_rates array
+	 */
+	u32 num_supported_rates;
+
+	/**
+	 * @dptx_lanes - number of lanes provided by DPTX
+	 */
+	u32 dptx_lanes;
+};
+
+/**
+ * struct drm_connector_dp - DRM Connector DisplayPort-related structure
+ */
+struct drm_connector_dp {
+	/* field set by the driver before registering the connector */
+
+	struct drm_connector_dp_caps caps;
+
+	/**
+	 * @aux - pointer to the DP AUX instance
+	 */
+	struct drm_dp_aux *aux;
+
+	/*
+	 * fields set by the drm_atomic_helper_connector_dp_detect(),
+	 * protected by connection_mutex
+	 */
+
+	/**
+	 * @dpcd - cached DPCD registers
+	 */
+	u8 dpcd[DP_RECEIVER_CAP_SIZE];
+
+	/**
+	 * @edp - cached eDP panel version
+	 */
+	u8 edp;
+
+	/**
+	 * @lttpr_caps - LTTPR capabilities read from DPRX
+	 */
+	u8 lttpr_caps[DP_LTTPR_COMMON_CAP_SIZE];
+
+	/**
+	 * @lttpr_count - the count of LTTPRs that are present and initialized
+	 * in a non-transparent mode
+	 */
+	u32 lttpr_count;
+
+	/*
+	 * fields set in drm_atomic_helper_connector_dp_hotplug, protected by FIXME
+	 */
+
+	/**
+	 * @dprx_lanes - number of lanes reported by DPRX
+	 */
+	u32 dprx_lanes;
+
+	struct drm_connector_dp_rate_entry rate[DP_MAX_SUPPORTED_RATES];
+	int rate_count;
+};
+
 /**
  * struct drm_connector - central DRM connector control structure
  *
@@ -2291,10 +2378,17 @@ struct drm_connector {
 	 */
 	struct llist_node free_node;
 
-	/**
-	 * @hdmi: HDMI-related variable and properties.
-	 */
-	struct drm_connector_hdmi hdmi;
+	union {
+		/**
+		 * @hdmi: HDMI-related variable and properties.
+		 */
+		struct drm_connector_hdmi hdmi;
+
+		/**
+		 * @dp: DisplayPort-related variable and properties.
+		 */
+		struct drm_connector_dp dp;
+	};
 
 	/**
 	 * @hdmi_audio: HDMI codec properties and non-DRM state.

---
base-commit: 457f4393d02fdb612a93912fb09cef70e6e545c9
change-id: 20251001-drm-dp-connector-a82238bd5b81

Best regards,
-- 
With best wishes
Dmitry