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 <[email protected]>
---
 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

Reply via email to