When dc_link_detect_connection_type thinks that a display is
connected, but dc_link_detect failed, enqueue delayed work to
retry the link detection again.

Useful when eg. HPD pin is high but the display isn't ready and
didn't respond to DDC.

- The display is "slow to wake up", ie. DDC isn't ready,
  for example we couldn't read EDID. Can happen with any
  connector type with certain "slow" displays.
  Some displays may take up to 15~20 sec or more to wake up.

- On hotplug, the HPD pin may make contact before the DDC pins,
  so we couldn't read the EDID. This most often happens with
  DVI connectors, rarely with HDMI. It is not impossible but
  extremely rare with other connector types.

Signed-off-by: Timur Kristóf <[email protected]>
---
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 137 ++++++++++++++++++
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h |  16 ++
 2 files changed, 153 insertions(+)

diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c 
b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 255903d000c2..8bda79c26288 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -158,6 +158,16 @@ MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB);
 #define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin"
 MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB);
 
+/**
+ * define AMDGPU_DM_HPD_MAX_NUM_RETRIES - maximum amount of retries for 
hotplug detection
+ */
+#define AMDGPU_DM_HPD_MAX_NUM_RETRIES 5
+
+/**
+ * define AMDGPU_DM_HPD_RETRY_DELAY_MSEC - millisecond delay between hotplug 
detection retries
+ */
+#define AMDGPU_DM_HPD_RETRY_DELAY_MSEC 1500
+
 /**
  * DOC: overview
  *
@@ -956,6 +966,125 @@ static void dm_handle_hpd_work(struct work_struct *work)
 
 }
 
+/**
+ * dm_handle_delayed_hpd_work() - Handle delayed HPD (hotplug detection)
+ *
+ * @w: Base work item structure
+ *
+ * Used for retrying HPD after a delay. Just calls the normal HPD helper.
+ */
+static void dm_handle_delayed_hpd_work(struct work_struct *work)
+{
+       struct delayed_work *dw = container_of(work, struct delayed_work, work);
+       struct delayed_hpd_work *w = container_of(dw, struct delayed_hpd_work, 
work);
+       struct amdgpu_dm_connector *aconn = w->aconn;
+       enum dc_detect_reason reason = w->reason;
+
+       kfree(w);
+       handle_hpd_irq_helper(aconn, reason);
+}
+
+/**
+ * dm_cancel_delayed_hpd_work() - Cancel pending hotplug detection work for a 
connector
+ *
+ * @aconnector: Connector on which the HPD event occurred
+ */
+static void dm_cancel_delayed_hpd_work(struct amdgpu_dm_connector *aconnector)
+{
+       if (!aconnector || !aconnector->delayed_hpd_work)
+               return;
+
+       cancel_delayed_work(&aconnector->delayed_hpd_work->work);
+       aconnector->delayed_hpd_work = NULL;
+}
+
+/**
+ * dm_cancel_all_delayed_hpd_work() - Cancel all pending hotplug detection 
work on the device
+ *
+ * @dev: DRM device pointer
+ */
+static void dm_cancel_all_delayed_hpd_work(struct drm_device *dev)
+{
+       struct drm_connector *connector;
+       struct drm_connector_list_iter iter;
+
+       drm_connector_list_iter_begin(dev, &iter);
+       drm_for_each_connector_iter(connector, &iter) {
+               if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
+                       continue;
+
+               dm_cancel_delayed_hpd_work(to_amdgpu_dm_connector(connector));
+       }
+       drm_connector_list_iter_end(&iter);
+}
+
+/**
+ * dm_queue_delayed_hpd_work() - Enqueue delayed work to handle hotplug 
detection
+ *
+ * @aconnector: Connector on which the HPD event occurred
+ * @reason: Reason why we are attempting the HPD
+ * @msecs: Millisecond delay after which the delayed work is going to happen
+ *
+ * When dc_link_detect_connection_type thinks that a display is connected,
+ * but dc_link_detect failed, enqueue delayed work to retry the link
+ * detection again.
+ *
+ * Useful when eg. HPD pin is high but the display isn't ready and
+ * didn't respond to DDC.
+ *
+ * - On boot or suspend/resume, the display is "slow to wake up",
+ *   ie. DDC isn't ready, for example we couldn't read DP link caps or EDID.
+ *   Can happen to any connector with certain "slow" displays.
+ *
+ * - On hotplug, the HPD pin may make contact before the DDC pins,
+ *   so we couldn't read the EDID. Can happen to any connector but
+ *   most often to DVI and sometimes to HDMI (rarely to DP).
+ *
+ */
+static void dm_queue_delayed_hpd_work(struct amdgpu_dm_connector *aconnector,
+                                     const enum dc_detect_reason reason,
+                                     const unsigned int msecs)
+{
+       struct drm_device *dev = aconnector->base.dev;
+       struct amdgpu_device *adev = drm_to_adev(dev);
+       struct delayed_hpd_work *w;
+
+       if (!aconnector || !aconnector->dc_link ||
+           aconnector->dc_link->type == dc_connection_none)
+               return;
+
+       /* Don't retry polled connectors, the polling is going to detect it. */
+       if (aconnector->base.polled != DRM_CONNECTOR_POLL_HPD)
+               return;
+
+       ++aconnector->num_hpd_retries;
+
+       drm_dbg(dev, "Can't detect link on %s on try %d\n",
+               aconnector->base.name, aconnector->num_hpd_retries);
+
+       if (aconnector->num_hpd_retries > AMDGPU_DM_HPD_MAX_NUM_RETRIES) {
+               drm_warn(dev, "Too many retries on %s: %d, giving up\n",
+                        aconnector->base.name, aconnector->num_hpd_retries);
+               aconnector->num_hpd_retries = 0;
+               return;
+       }
+
+       w = kzalloc(sizeof(*w), GFP_ATOMIC);
+
+       if (!w)
+               return;
+
+       INIT_DELAYED_WORK(&w->work, dm_handle_delayed_hpd_work);
+       w->aconn = aconnector;
+       w->reason = reason;
+       aconnector->delayed_hpd_work = w;
+
+       drm_warn(dev, "Enqueueing next retry on %s\n",
+                aconnector->base.name);
+       queue_delayed_work(adev->dm.delayed_hpd_wq, &w->work,
+                          msecs_to_jiffies(msecs));
+}
+
 static const char *dmub_notification_type_str(enum dmub_notification_type e)
 {
        switch (e) {
@@ -3194,6 +3323,7 @@ static int dm_hw_fini(struct amdgpu_ip_block *ip_block)
 {
        struct amdgpu_device *adev = ip_block->adev;
 
+       dm_cancel_all_delayed_hpd_work(&adev->ddev);
        amdgpu_dm_hpd_fini(adev);
 
        amdgpu_dm_irq_fini(adev);
@@ -3367,6 +3497,8 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block)
        struct amdgpu_device *adev = ip_block->adev;
        struct amdgpu_display_manager *dm = &adev->dm;
 
+       dm_cancel_all_delayed_hpd_work(&adev->ddev);
+
        if (amdgpu_in_reset(adev)) {
                enum dc_status res;
 
@@ -4396,6 +4528,9 @@ static void handle_hpd_irq_helper(struct 
amdgpu_dm_connector *aconnector,
                        if (aconnector->base.force == DRM_FORCE_UNSPECIFIED ||
                            reason == DETECT_REASON_HPDRX)
                                
drm_kms_helper_connector_hotplug_event(connector);
+               } else {
+                       dm_queue_delayed_hpd_work(aconnector, reason,
+                                                 
AMDGPU_DM_HPD_RETRY_DELAY_MSEC);
                }
        }
 }
@@ -4404,6 +4539,8 @@ static void handle_hpd_irq(void *param)
 {
        struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector 
*)param;
 
+       /* Cancel any pending work */
+       dm_cancel_delayed_hpd_work(aconnector);
        handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD);
 
 }
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h 
b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
index 43056392a1f0..c11633dbd792 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -136,6 +136,18 @@ struct dmub_hpd_work {
        struct amdgpu_device *adev;
 };
 
+/**
+ * struct delayed_hpd_work - Handle delayed HPD (hot plug detection) work
+ *
+ * @work: Base structure, kernel work data for the work event
+ * @aconn: Pointer to connector where the HPD event happened
+ */
+struct delayed_hpd_work {
+       struct delayed_work work;
+       struct amdgpu_dm_connector *aconn;
+       enum dc_detect_reason reason;
+};
+
 /**
  * struct vblank_control_work - Work data for vblank control
  * @work: Kernel work data for the work event
@@ -793,6 +805,10 @@ struct amdgpu_dm_connector {
        /* number of modes generated from EDID at 'dc_sink' */
        int num_modes;
 
+       /* number of retries on hot plug detection */
+       int num_hpd_retries;
+       struct delayed_hpd_work *delayed_hpd_work;
+
        /* The 'old' sink - before an HPD.
         * The 'current' sink is in dc_link->sink. */
        struct dc_sink *dc_sink;
-- 
2.54.0

Reply via email to