]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
drm/amd/display: Poll analog connectors (v3)
authorTimur Kristóf <timur.kristof@gmail.com>
Fri, 26 Sep 2025 18:01:56 +0000 (20:01 +0200)
committerAlex Deucher <alexander.deucher@amd.com>
Tue, 28 Oct 2025 14:09:35 +0000 (10:09 -0400)
VGA connectors don't support any hotplug detection, so the kernel
needs to periodically poll them to see if a display is connected.

DVI-I connectors have hotplug detection for digital signals, and
some analog DVI cables pull up that pin to work with that.
However, in general not all DVI cables do this so we can't rely on
this feature, therefore we need to poll DVI-I connectors as well.

v2:
Call drm_kms_helper_poll_fini in amdgpu_dm_hpd_fini.
Disable/enable polling on suspend/resume.
Don't call full link detection when already connected.

v3:
Encounter CLANG build failure. Remove unused variable:
drivers/gpu/drm/amd/amdgpu/../display/amdgpu_dm/amdgpu_dm_irq.c:980:7:
error: variable 'use_polling' set but not used [-Werror,-Wunused-but-
set-variable]
980 |         bool use_polling = false;

Signed-off-by: Timur Kristóf <timur.kristof@gmail.com>
Signed-off-by: Wayne Lin <wayne.lin@amd.com>
Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c

index 3bd404b727b65de2a5f51854540333a920d3e527..d18d72a520f2efcdf2965105112d62186a320dc9 100644 (file)
@@ -3853,7 +3853,9 @@ void amdgpu_dm_update_connector_after_detect(
        drm_dbg_kms(dev, "DCHPD: connector_id=%d: Old sink=%p New sink=%p\n",
                    aconnector->connector_id, aconnector->dc_sink, sink);
 
-       guard(mutex)(&dev->mode_config.mutex);
+       /* When polling, DRM has already locked the mutex for us. */
+       if (!drm_kms_helper_is_poll_worker())
+               mutex_lock(&dev->mode_config.mutex);
 
        /*
         * 1. Update status of the drm connector
@@ -3916,6 +3918,10 @@ void amdgpu_dm_update_connector_after_detect(
        }
 
        update_subconnector_property(aconnector);
+
+       /* When polling, the mutex will be unlocked for us by DRM. */
+       if (!drm_kms_helper_is_poll_worker())
+               mutex_unlock(&dev->mode_config.mutex);
 }
 
 static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector)
@@ -7226,12 +7232,63 @@ finish:
        return stream;
 }
 
+/**
+ * amdgpu_dm_connector_poll() - Poll a connector to see if it's connected to a display
+ *
+ * Used for connectors that don't support HPD (hotplug detection)
+ * to periodically checked whether the connector is connected to a display.
+ */
+static enum drm_connector_status
+amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force)
+{
+       struct drm_connector *connector = &aconnector->base;
+       struct drm_device *dev = connector->dev;
+       struct amdgpu_device *adev = drm_to_adev(dev);
+       struct dc_link *link = aconnector->dc_link;
+       enum dc_connection_type conn_type = dc_connection_none;
+       enum drm_connector_status status = connector_status_disconnected;
+
+       mutex_lock(&aconnector->hpd_lock);
+
+       if (dc_link_detect_connection_type(aconnector->dc_link, &conn_type) &&
+           conn_type != dc_connection_none) {
+               mutex_lock(&adev->dm.dc_lock);
+
+               /* Only call full link detection when a sink isn't created yet,
+                * ie. just when the display is plugged in, otherwise we risk flickering.
+                */
+               if (link->local_sink ||
+                       dc_link_detect(link, DETECT_REASON_HPD))
+                       status = connector_status_connected;
+
+               mutex_unlock(&adev->dm.dc_lock);
+       }
+
+       if (connector->status != status) {
+               if (status == connector_status_disconnected) {
+                       if (link->local_sink)
+                               dc_sink_release(link->local_sink);
+
+                       link->local_sink = NULL;
+                       link->dpcd_sink_count = 0;
+                       link->type = dc_connection_none;
+               }
+
+               amdgpu_dm_update_connector_after_detect(aconnector);
+       }
+
+       mutex_unlock(&aconnector->hpd_lock);
+       return status;
+}
+
 /**
  * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display
  *
  * A connector is considered connected when it has a sink that is not NULL.
  * For connectors that support HPD (hotplug detection), the connection is
  * handled in the HPD interrupt.
+ * For connectors that may not support HPD, such as analog connectors,
+ * DRM will call this function repeatedly to poll them.
  *
  * Notes:
  * 1. This interface is NOT called in context of HPD irq.
@@ -7251,6 +7308,14 @@ amdgpu_dm_connector_detect(struct drm_connector *connector, bool force)
        else if (aconnector->base.force == DRM_FORCE_OFF)
                return connector_status_disconnected;
 
+       /* Poll analog connectors and only when either
+        * disconnected or connected to an analog display.
+        */
+       if (drm_kms_helper_is_poll_worker() &&
+               dc_connector_supports_analog(aconnector->dc_link->link_id.id) &&
+               (!aconnector->dc_sink || aconnector->dc_sink->edid_caps.analog))
+               return amdgpu_dm_connector_poll(aconnector, force);
+
        return (aconnector->dc_sink ? connector_status_connected :
                        connector_status_disconnected);
 }
@@ -8675,9 +8740,13 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
                        link->link_enc->features.dp_ycbcr420_supported ? true : false;
                break;
        case DRM_MODE_CONNECTOR_DVID:
-       case DRM_MODE_CONNECTOR_DVII:
                aconnector->base.polled = DRM_CONNECTOR_POLL_HPD;
                break;
+       case DRM_MODE_CONNECTOR_DVII:
+       case DRM_MODE_CONNECTOR_VGA:
+               aconnector->base.polled =
+                       DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
+               break;
        default:
                break;
        }
index a1c722112c22444b4d0e5bdb638f65fce0a33a9d..0a2a3f233a0e2e4dc7aa066fae6b4801fb0fc33a 100644 (file)
@@ -476,6 +476,7 @@ void amdgpu_dm_irq_fini(struct amdgpu_device *adev)
 
 void amdgpu_dm_irq_suspend(struct amdgpu_device *adev)
 {
+       struct drm_device *dev = adev_to_drm(adev);
        int src;
        struct list_head *hnd_list_h;
        struct list_head *hnd_list_l;
@@ -512,6 +513,9 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev)
        }
 
        DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);
+
+       if (dev->mode_config.poll_enabled)
+               drm_kms_helper_poll_disable(dev);
 }
 
 void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev)
@@ -537,6 +541,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev)
 
 void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev)
 {
+       struct drm_device *dev = adev_to_drm(adev);
        int src;
        struct list_head *hnd_list_h, *hnd_list_l;
        unsigned long irq_table_flags;
@@ -557,6 +562,9 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev)
        }
 
        DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags);
+
+       if (dev->mode_config.poll_enabled)
+               drm_kms_helper_poll_enable(dev);
 }
 
 /*
@@ -893,6 +901,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev)
        struct drm_connector_list_iter iter;
        int irq_type;
        int i;
+       bool use_polling = false;
 
        /* First, clear all hpd and hpdrx interrupts */
        for (i = DC_IRQ_SOURCE_HPD1; i <= DC_IRQ_SOURCE_HPD6RX; i++) {
@@ -906,6 +915,8 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev)
                struct amdgpu_dm_connector *amdgpu_dm_connector;
                const struct dc_link *dc_link;
 
+               use_polling |= connector->polled != DRM_CONNECTOR_POLL_HPD;
+
                if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
                        continue;
 
@@ -947,6 +958,9 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev)
                }
        }
        drm_connector_list_iter_end(&iter);
+
+       if (use_polling)
+               drm_kms_helper_poll_init(dev);
 }
 
 /**
@@ -997,4 +1011,7 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev)
                }
        }
        drm_connector_list_iter_end(&iter);
+
+       if (dev->mode_config.poll_enabled)
+               drm_kms_helper_poll_fini(dev);
 }