]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
drm/amd/display: Add analog link detection (v2)
authorTimur Kristóf <timur.kristof@gmail.com>
Fri, 26 Sep 2025 18:01:54 +0000 (20:01 +0200)
committerAlex Deucher <alexander.deucher@amd.com>
Tue, 28 Oct 2025 14:09:23 +0000 (10:09 -0400)
Analog displays typically have a DDC connection which can be
used by the GPU to read EDID. This commit adds the capability
to probe analog displays using DDC, reading the EDID header and
deciding whether the analog link is connected based on the data
that was read.

Note that VGA has no HPD (hotplug detection), so we need to
to do analog link detection for VGA before checking HPD.

In case of DVI-I, while the connector supports HPD, not all
analog cables connect the HPD pins, so we can't rely on HPD
either.

For reference, see the legacy display code:
amdgpu_connector_vga_detect
amdgpu_display_ddc_probe

DAC load detection will be implemented in a separate commit.

v2:
Fix crash / black screen on newer GPUs during link detection.
Ignore HPD pin for analog connectors.

Signed-off-by: Timur Kristóf <timur.kristof@gmail.com>
Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_dio.c
drivers/gpu/drm/amd/display/dc/link/link_detection.c
drivers/gpu/drm/amd/display/dc/link/link_dpms.c
drivers/gpu/drm/amd/display/dc/link/link_factory.c

index 80344cbd1f9957d6ef27ed82a0c8cb89f0ff1ab9..befa67b2b2ae002c5385c94235c562df91a93e58 100644 (file)
@@ -58,8 +58,9 @@ void setup_dio_stream_encoder(struct pipe_ctx *pipe_ctx)
                return;
        }
 
-       link_enc->funcs->connect_dig_be_to_fe(link_enc,
-                       pipe_ctx->stream_res.stream_enc->id, true);
+       if (!dc_is_rgb_signal(pipe_ctx->stream->signal))
+               link_enc->funcs->connect_dig_be_to_fe(link_enc,
+                               pipe_ctx->stream_res.stream_enc->id, true);
        if (dc_is_dp_signal(pipe_ctx->stream->signal))
                pipe_ctx->stream->ctx->dc->link_srv->dp_trace_source_sequence(pipe_ctx->stream->link,
                                DPCD_SOURCE_SEQ_AFTER_CONNECT_DIG_FE_BE);
@@ -98,10 +99,13 @@ void reset_dio_stream_encoder(struct pipe_ctx *pipe_ctx)
        if (stream_enc->funcs->enable_stream)
                stream_enc->funcs->enable_stream(stream_enc,
                                pipe_ctx->stream->signal, false);
-       link_enc->funcs->connect_dig_be_to_fe(
-                       link_enc,
-                       pipe_ctx->stream_res.stream_enc->id,
-                       false);
+
+       if (!dc_is_rgb_signal(pipe_ctx->stream->signal))
+               link_enc->funcs->connect_dig_be_to_fe(
+                               link_enc,
+                               pipe_ctx->stream_res.stream_enc->id,
+                               false);
+
        if (dc_is_dp_signal(pipe_ctx->stream->signal))
                pipe_ctx->stream->ctx->dc->link_srv->dp_trace_source_sequence(
                                pipe_ctx->stream->link,
index 801c94efa7fcacd0449d3d5904cec257007f0765..d83fd52cb1b0ab834d036c2f146e46f97064f940 100644 (file)
@@ -862,6 +862,48 @@ static void verify_link_capability(struct dc_link *link, struct dc_sink *sink,
                verify_link_capability_non_destructive(link);
 }
 
+/**
+ * link_detect_evaluate_edid_header() - Evaluate if an EDID header is acceptable.
+ *
+ * Evaluates an 8-byte EDID header to check if it's good enough
+ * for the purpose of determining whether a display is connected
+ * without reading the full EDID.
+ */
+static bool link_detect_evaluate_edid_header(uint8_t edid_header[8])
+{
+       int edid_header_score = 0;
+       int i;
+
+       for (i = 0; i < 8; ++i)
+               edid_header_score += edid_header[i] == ((i == 0 || i == 7) ? 0x00 : 0xff);
+
+       return edid_header_score >= 6;
+}
+
+/**
+ * link_detect_ddc_probe() - Probe the DDC to see if a display is connected.
+ *
+ * Detect whether a display is connected to DDC without reading full EDID.
+ * Reads only the EDID header (the first 8 bytes of EDID) from DDC and
+ * evaluates whether that matches.
+ */
+static bool link_detect_ddc_probe(struct dc_link *link)
+{
+       if (!link->ddc)
+               return false;
+
+       uint8_t edid_header[8] = {0};
+       bool ddc_probed = i2c_read(link->ddc, 0x50, edid_header, sizeof(edid_header));
+
+       if (!ddc_probed)
+               return false;
+
+       if (!link_detect_evaluate_edid_header(edid_header))
+               return false;
+
+       return true;
+}
+
 /*
  * detect_link_and_local_sink() - Detect if a sink is attached to a given link
  *
@@ -946,6 +988,12 @@ static bool detect_link_and_local_sink(struct dc_link *link,
                        break;
                }
 
+               case SIGNAL_TYPE_RGB: {
+                       sink_caps.transaction_type = DDC_TRANSACTION_TYPE_I2C;
+                       sink_caps.signal = SIGNAL_TYPE_RGB;
+                       break;
+               }
+
                case SIGNAL_TYPE_LVDS: {
                        sink_caps.transaction_type = DDC_TRANSACTION_TYPE_I2C;
                        sink_caps.signal = SIGNAL_TYPE_LVDS;
@@ -1139,9 +1187,17 @@ static bool detect_link_and_local_sink(struct dc_link *link,
                                sink = prev_sink;
                                prev_sink = NULL;
                        }
-                       query_hdcp_capability(sink->sink_signal, link);
+
+                       if (!sink->edid_caps.analog)
+                               query_hdcp_capability(sink->sink_signal, link);
                }
 
+               /* DVI-I connector connected to analog display. */
+               if ((link->link_id.id == CONNECTOR_ID_DUAL_LINK_DVII ||
+                    link->link_id.id == CONNECTOR_ID_SINGLE_LINK_DVII) &&
+                       sink->edid_caps.analog)
+                       sink->sink_signal = SIGNAL_TYPE_RGB;
+
                /* HDMI-DVI Dongle */
                if (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A &&
                    !sink->edid_caps.edid_hdmi)
@@ -1238,6 +1294,23 @@ static bool detect_link_and_local_sink(struct dc_link *link,
        return true;
 }
 
+/**
+ * link_detect_analog() - Determines if an analog sink is connected.
+ */
+static bool link_detect_analog(struct dc_link *link, enum dc_connection_type *type)
+{
+       /* Don't care about connectors that don't support an analog signal. */
+       ASSERT(dc_connector_supports_analog(link->link_id.id));
+
+       if (link_detect_ddc_probe(link)) {
+               *type = dc_connection_single;
+               return true;
+       }
+
+       *type = dc_connection_none;
+       return true;
+}
+
 /*
  * link_detect_connection_type() - Determine if there is a sink connected
  *
@@ -1254,6 +1327,17 @@ bool link_detect_connection_type(struct dc_link *link, enum dc_connection_type *
                return true;
        }
 
+       /* Ignore the HPD pin (if any) for analog connectors.
+        * Instead rely on DDC.
+        *
+        * - VGA connectors don't have any HPD at all.
+        * - Some DVI-A cables don't connect the HPD pin.
+        * - Some DVI-A cables pull up the HPD pin.
+        *   (So it's high even when no display is connected.)
+        */
+       if (dc_connector_supports_analog(link->link_id.id))
+               return link_detect_analog(link, type);
+
        if (link->connector_signal == SIGNAL_TYPE_EDP) {
                /*in case it is not on*/
                if (!link->dc->config.edp_no_power_sequencing)
index fdefe27f4ba361d155fc52ae3740263f2ed8849d..4ddcdc222913d078112fee35cf0138390d73091d 100644 (file)
@@ -2258,6 +2258,9 @@ static enum dc_status enable_link(
                enable_link_lvds(pipe_ctx);
                status = DC_OK;
                break;
+       case SIGNAL_TYPE_RGB:
+               status = DC_OK;
+               break;
        case SIGNAL_TYPE_VIRTUAL:
                status = enable_link_virtual(pipe_ctx);
                break;
index ebc3798e7d25de639e82a4fb79861953968992a1..7989baf3843c98573dff0136b0bc269422fd89eb 100644 (file)
@@ -583,6 +583,9 @@ static bool construct_phy(struct dc_link *link,
        case CONNECTOR_ID_DUAL_LINK_DVII:
                link->connector_signal = SIGNAL_TYPE_DVI_DUAL_LINK;
                break;
+       case CONNECTOR_ID_VGA:
+               link->connector_signal = SIGNAL_TYPE_RGB;
+               break;
        case CONNECTOR_ID_DISPLAY_PORT:
        case CONNECTOR_ID_MXM:
        case CONNECTOR_ID_USBC: