Re: [PATCH v3] drm/amd/display: unify HDMI/DVI/DP SST HPD debounce delay

From: Ivan Lipski

Date: Thu Jul 09 2026 - 13:34:08 EST



On 7/9/26 10:15, Nick Haghiri wrote:
Per review feedback on the DP SST debounce extension, fold
hdmi_hpd_debounce_delay_ms and dp_hpd_debounce_delay_ms into a single
hpd_debounce_delay_ms module parameter (eDP and MST still excluded),
and rename the shared connector fields, work, and cached sink from
hdmi_* to generic hpd_*. Also extend eligibility to DVI, which shares
the same HPD toggle behavior and has no MST/eDP variants to exclude.

Also add an apply_edid_quirks() case for the MSI MPG 274U
(drm_edid_encode_panel_id('M', 'S', 'I', 0x3CF0)) that enables a
1500ms default debounce delay for that panel, overridable by the
module parameter.

Signed-off-by: Nick Haghiri <nick@xxxxxxxxxxx>
---
Ivan, generalized to a single hpd_debounce_delay_ms for both HDMI and
DP SST as suggested, and added the apply_edid_quirks() case for the
MSI MPG 274U (1500ms default, module param still overrides it).

Went with replacing hdmi_hpd_debounce_delay_ms rather than keeping a
back-compat alias, since it's only a couple cycles old. Shout if you'd
rather I keep the alias.

Alex, done. debounce_eligible now also checks dc_is_dvi_signal(),
reusing the same hpd_debounce_delay_ms param. Updated text and
comments to reflect scope now being HDMI, DVI, and DP.
---
Changes in v3:
- Extend HPD debounce eligibility to DVI (dc_is_dvi_signal()), per Alex
- Update comments/docs to reflect HDMI/DVI/DP SST scope
- Link to v2: https://patch.msgid.link/20260708-hpd-debounce-unify-v2-1-d214832551b3@xxxxxxxxxxx

Hey Nick,

Thank you very much for this patch.

My only concern is that a monitor that requires HPD debounce delay on HDMI may not need it when connected via DP, a vice-versa, so I think we should differentiate them.

I think a proper approach for this would be adding two new integer fields to the dc_panel_patch struct for DP and HDMI each, so a delay in ms could be explicitly set for a specific monitor and specific connector type.

So in the apply_edid_quirks, we could have something like this in apply_edid_quirk

/* Workaround for monitors that toggle HPD on entering deep sleep */
case drm_edid_encode_panel_id('M', 'S', 'I', 0x3CF0):
if (!aconnector->hpd_debounce_delay_ms) {
drm_dbg_driver(dev, "Enabling HPD debounce on panel id %X\n", panel_id);
edid_caps.panel_patch.dp_hpd_debounce_delay_ms =
min(1500U, AMDGPU_DM_MAX_HPD_DEBOUNCE_MS);
}
break;

That would also require changing logic of determining the `debounce_eligible` as we'd need to check for initialized dp_hpd_debounce_delay_ms && SIGNAL_TYPE_DISPLAY_PORT or (hdmi_hpd_debounce_delay_ms && (HDMI || DVI)).

sunpeng.li@xxxxxxx, alexander.deucher@xxxxxxx, what do you think?

---
drivers/gpu/drm/amd/amdgpu/amdgpu.h | 3 +-
drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 26 ++-------
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 18 ++----
.../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 29 +++-------
.../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 8 +++
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 65 +++++++++++-----------
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 2 +-
7 files changed, 64 insertions(+), 87 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
index c085a6cc1..af1748984 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
@@ -270,8 +270,7 @@ extern int amdgpu_wbrf;
extern int amdgpu_user_queue;
extern int amdgpu_ptl;
-extern uint amdgpu_hdmi_hpd_debounce_delay_ms;
-extern uint amdgpu_dp_hpd_debounce_delay_ms;
+extern uint amdgpu_hpd_debounce_delay_ms;
#define AMDGPU_SG_THRESHOLD (256*1024*1024)
#define AMDGPU_WAIT_IDLE_TIMEOUT_IN_MS 3000
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
index 78df53b8c..a82d807f7 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
@@ -245,8 +245,7 @@ int amdgpu_damage_clips = -1; /* auto */
int amdgpu_umsch_mm_fwlog;
int amdgpu_rebar = -1; /* auto */
int amdgpu_user_queue = -1;
-uint amdgpu_hdmi_hpd_debounce_delay_ms;
-uint amdgpu_dp_hpd_debounce_delay_ms;
+uint amdgpu_hpd_debounce_delay_ms;
int amdgpu_ptl = -1; /* auto */
DECLARE_DYNDBG_CLASSMAP(drm_debug_classes, DD_CLASS_TYPE_DISJOINT_BITS, 0,
@@ -1105,25 +1104,12 @@ MODULE_PARM_DESC(user_queue, "Enable user queues (-1 = auto (default), 0 = disab
module_param_named(user_queue, amdgpu_user_queue, int, 0444);
/*
- * DOC: hdmi_hpd_debounce_delay_ms (uint)
- * HDMI HPD disconnect debounce delay in milliseconds.
- *
- * Used to filter short disconnect->reconnect HPD toggles some HDMI sinks
- * generate while entering/leaving power save. Set to 0 to disable by default.
- */
-MODULE_PARM_DESC(hdmi_hpd_debounce_delay_ms, "HDMI HPD disconnect debounce delay in milliseconds (0 to disable (by default), 1500 is common)");
-module_param_named(hdmi_hpd_debounce_delay_ms, amdgpu_hdmi_hpd_debounce_delay_ms, uint, 0644);
-
-/*
- * DOC: dp_hpd_debounce_delay_ms (uint)
- * DisplayPort SST HPD disconnect debounce delay in milliseconds.
- *
- * Used to filter short disconnect->reconnect HPD toggles some DisplayPort SST
- * sinks generate while entering/leaving power save. Set to 0 to disable by
- * default. eDP and MST are not affected.
+ * DOC: hpd_debounce_delay_ms (uint)
+ * HDMI/DVI/DP SST HPD disconnect debounce delay in milliseconds. eDP and MST
+ * are not affected. Overrides any per-panel default set via EDID quirks.
*/
-MODULE_PARM_DESC(dp_hpd_debounce_delay_ms, "DisplayPort SST HPD disconnect debounce delay in milliseconds (0 to disable (by default), 1500 is common)");
-module_param_named(dp_hpd_debounce_delay_ms, amdgpu_dp_hpd_debounce_delay_ms, uint, 0644);
+MODULE_PARM_DESC(hpd_debounce_delay_ms, "HDMI/DVI/DP SST HPD disconnect debounce delay in milliseconds (0 to disable (by default) or use the EDID quirk default, 1500 is common)");
+module_param_named(hpd_debounce_delay_ms, amdgpu_hpd_debounce_delay_ms, uint, 0644);
/**
* DOC: ptl (int)
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 abc17f547..5235a5ef6 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -61,14 +61,9 @@ enum amd_vsdb_panel_type {
#define AMDGPU_HDR_MULT_DEFAULT (0x100000000LL)
/*
- * Maximum HDMI HPD debounce delay in milliseconds
+ * Maximum HDMI/DVI/DP SST HPD debounce delay in milliseconds
*/
-#define AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS 5000
-
-/*
- * Maximum DisplayPort SST HPD debounce delay in milliseconds
- */
-#define AMDGPU_DM_MAX_DP_HPD_DEBOUNCE_MS 5000
+#define AMDGPU_DM_MAX_HPD_DEBOUNCE_MS 5000
/*
#include "include/amdgpu_dal_power_if.h"
#include "amdgpu_dm_irq.h"
@@ -878,11 +873,10 @@ struct amdgpu_dm_connector {
enum adaptive_sync_type as_type;
struct amdgpu_hdmi_vsdb_info vsdb_info;
- /* HDMI HPD debounce support */
- unsigned int hdmi_hpd_debounce_delay_ms;
- unsigned int dp_hpd_debounce_delay_ms;
- struct delayed_work hdmi_hpd_debounce_work;
- struct dc_sink *hdmi_prev_sink;
+ /* HDMI/DVI/DP SST HPD debounce support */
+ unsigned int hpd_debounce_delay_ms;
+ struct delayed_work hpd_debounce_work;
+ struct dc_sink *hpd_prev_sink;
/* HDMI compliance automation */
bool hdmi_comp_auto;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index c79a8ada8..798825ec1 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1748,12 +1748,10 @@ static void amdgpu_dm_connector_destroy(struct drm_connector *connector)
drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr);
/* Cancel and flush any pending HPD debounce work */
- if (aconnector->hdmi_hpd_debounce_delay_ms || aconnector->dp_hpd_debounce_delay_ms) {
- cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work);
- if (aconnector->hdmi_prev_sink) {
- dc_sink_release(aconnector->hdmi_prev_sink);
- aconnector->hdmi_prev_sink = NULL;
- }
+ cancel_delayed_work_sync(&aconnector->hpd_debounce_work);
+ if (aconnector->hpd_prev_sink) {
+ dc_sink_release(aconnector->hpd_prev_sink);
+ aconnector->hpd_prev_sink = NULL;
}
if (aconnector->bl_idx != -1) {
@@ -2828,20 +2826,11 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
mutex_init(&aconnector->hpd_lock);
mutex_init(&aconnector->handle_mst_msg_ready);
- /*
- * If an HPD debounce delay is set, clamp each signal's delay to its
- * maximum. The debounce work and cached sink are shared by both the
- * HDMI and DisplayPort SST paths.
- */
- aconnector->hdmi_hpd_debounce_delay_ms = amdgpu_hdmi_hpd_debounce_delay_ms ?
- min(amdgpu_hdmi_hpd_debounce_delay_ms, AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS) : 0;
- aconnector->dp_hpd_debounce_delay_ms = amdgpu_dp_hpd_debounce_delay_ms ?
- min(amdgpu_dp_hpd_debounce_delay_ms, AMDGPU_DM_MAX_DP_HPD_DEBOUNCE_MS) : 0;
-
- if (aconnector->hdmi_hpd_debounce_delay_ms || aconnector->dp_hpd_debounce_delay_ms) {
- INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work);
- aconnector->hdmi_prev_sink = NULL;
- }
+ /* May be overridden later by an EDID quirk in apply_edid_quirks() */
+ aconnector->hpd_debounce_delay_ms = amdgpu_hpd_debounce_delay_ms ?
+ min(amdgpu_hpd_debounce_delay_ms, AMDGPU_DM_MAX_HPD_DEBOUNCE_MS) : 0;
+ aconnector->hpd_prev_sink = NULL;
+ INIT_DELAYED_WORK(&aconnector->hpd_debounce_work, amdgpu_dm_hpd_debounce_work);
dm->hdmi_frl_status_polling_delay_ms = 200;
INIT_DELAYED_WORK(&dm->hdmi_frl_status_polling_work, hdmi_frl_status_polling_work);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
index 9c4e0a4e2..71c897416 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c
@@ -137,6 +137,14 @@ static void apply_edid_quirks(struct dc_link *link, struct edid *edid,
drm_dbg_driver(dev, "Skip PHY SSC reduction on panel id %X\n", panel_id);
link->wa_flags.skip_phy_ssc_reduction = true;
break;
+ /* Workaround for monitors that toggle HPD on entering deep sleep */
+ case drm_edid_encode_panel_id('M', 'S', 'I', 0x3CF0):
+ if (!aconnector->hpd_debounce_delay_ms) {
+ drm_dbg_driver(dev, "Enabling HPD debounce on panel id %X\n", panel_id);
+ aconnector->hpd_debounce_delay_ms =
+ min(1500U, AMDGPU_DM_MAX_HPD_DEBOUNCE_MS);
+ }
+ break;
default:
return;
}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
index 2a732d19b..151db9893 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
@@ -1217,13 +1217,13 @@ EXPORT_IF_KUNIT(are_sinks_equal);
/**
- * DOC: amdgpu_dm_hdmi_hpd_debounce_work
+ * DOC: amdgpu_dm_hpd_debounce_work
*
- * HDMI HPD debounce delay in milliseconds. When an HDMI display toggles HPD
+ * HDMI/DVI/DP SST HPD debounce delay in milliseconds. When a display toggles HPD
* (such as during power save transitions), this delay determines how long to
* wait before processing the HPD event. This allows distinguishing between a
- * physical unplug (>hdmi_hpd_debounce_delay)
- * and a spontaneous RX HPD toggle (<hdmi_hpd_debounce_delay).
+ * physical unplug (>hpd_debounce_delay)
+ * and a spontaneous RX HPD toggle (<hpd_debounce_delay).
*
* If the toggle is less than this delay, the driver compares sink capabilities
* and permits a hotplug event if they changed.
@@ -1231,11 +1231,11 @@ EXPORT_IF_KUNIT(are_sinks_equal);
* The default value of 1500ms was chosen based on experimental testing with
* various monitors that exhibit spontaneous HPD toggling behavior.
*/
-void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work)
+void amdgpu_dm_hpd_debounce_work(struct work_struct *work)
{
struct amdgpu_dm_connector *aconnector =
container_of(to_delayed_work(work), struct amdgpu_dm_connector,
- hdmi_hpd_debounce_work);
+ hpd_debounce_work);
struct drm_connector *connector = &aconnector->base;
struct drm_device *dev = connector->dev;
struct amdgpu_device *adev = drm_to_adev(dev);
@@ -1259,11 +1259,12 @@ void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work)
/* Apply workaround delay for certain panels */
amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink);
/* Compare sinks to determine if this was a spontaneous HPD toggle */
- if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hdmi_prev_sink)) {
+ if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hpd_prev_sink)) {
/*
- * Sinks match - this was a spontaneous HDMI HPD toggle.
+ * Sinks match - this was a spontaneous HPD toggle.
*/
- drm_dbg_kms(dev, "HDMI HPD: Sink unchanged after debounce, internal re-enable\n");
+ drm_dbg_kms(dev,
+ "HPD: Sink unchanged after debounce, internal re-enable\n");
fake_reconnect = true;
}
@@ -1280,9 +1281,9 @@ void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work)
}
/* Release the cached sink reference */
- if (aconnector->hdmi_prev_sink) {
- dc_sink_release(aconnector->hdmi_prev_sink);
- aconnector->hdmi_prev_sink = NULL;
+ if (aconnector->hpd_prev_sink) {
+ dc_sink_release(aconnector->hpd_prev_sink);
+ aconnector->hpd_prev_sink = NULL;
}
scoped_guard(mutex, &adev->dm.dc_lock) {
@@ -1302,7 +1303,7 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector,
struct dc *dc = aconnector->dc_link->ctx->dc;
bool ret = false;
bool debounce_required = false;
- unsigned int debounce_delay_ms = 0;
+ bool debounce_eligible;
if (adev->dm.disable_hpd_irq)
return;
@@ -1326,16 +1327,16 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector,
drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
/*
- * Check for an HDMI or DisplayPort SST disconnect with debounce
- * enabled. eDP and MST are intentionally excluded.
+ * Check for an HDMI, DVI, or DisplayPort SST disconnect with
+ * debounce enabled. eDP and MST are intentionally excluded.
*/
- if (dc_is_hdmi_signal(aconnector->dc_link->connector_signal))
- debounce_delay_ms = aconnector->hdmi_hpd_debounce_delay_ms;
- else if (aconnector->dc_link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT &&
- aconnector->dc_link->type != dc_connection_mst_branch)
- debounce_delay_ms = aconnector->dp_hpd_debounce_delay_ms;
+ debounce_eligible = dc_is_hdmi_signal(aconnector->dc_link->connector_signal) ||
+ dc_is_dvi_signal(aconnector->dc_link->connector_signal) ||
+ (aconnector->dc_link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT &&
+ aconnector->dc_link->type != dc_connection_mst_branch);
- debounce_required = (debounce_delay_ms > 0 &&
+ debounce_required = (debounce_eligible &&
+ aconnector->hpd_debounce_delay_ms > 0 &&
new_connection_type == dc_connection_none &&
aconnector->dc_link->local_sink != NULL);
@@ -1353,28 +1354,28 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector,
/*
* Disconnect detected - schedule delayed work instead of
* processing immediately. This allows us to coalesce spurious
- * HDMI/DP HPD signals from physical unplugs.
+ * HDMI/DVI/DP HPD signals from physical unplugs.
*/
drm_dbg_kms(dev, "HPD: Disconnect detected, scheduling debounce work (%u ms)\n",
- debounce_delay_ms);
+ aconnector->hpd_debounce_delay_ms);
/* Cache the current sink for later comparison */
- if (aconnector->hdmi_prev_sink)
- dc_sink_release(aconnector->hdmi_prev_sink);
- aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink;
- if (aconnector->hdmi_prev_sink)
- dc_sink_retain(aconnector->hdmi_prev_sink);
+ if (aconnector->hpd_prev_sink)
+ dc_sink_release(aconnector->hpd_prev_sink);
+ aconnector->hpd_prev_sink = aconnector->dc_link->local_sink;
+ if (aconnector->hpd_prev_sink)
+ dc_sink_retain(aconnector->hpd_prev_sink);
/* Schedule delayed detection. */
if (mod_delayed_work(system_percpu_wq,
- &aconnector->hdmi_hpd_debounce_work,
- msecs_to_jiffies(debounce_delay_ms)))
+ &aconnector->hpd_debounce_work,
+ msecs_to_jiffies(aconnector->hpd_debounce_delay_ms)))
drm_dbg_kms(dev, "HPD: Re-scheduled debounce work\n");
} else {
- /* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */
- if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work))
+ /* If the aconnector->hpd_debounce_work is scheduled, exit early */
+ if (delayed_work_pending(&aconnector->hpd_debounce_work))
return;
scoped_guard(mutex, &adev->dm.dc_lock) {
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
index bccb5d354..66fb82961 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h
@@ -113,7 +113,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev);
struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev);
void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm);
int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev);
-void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work);
+void amdgpu_dm_hpd_debounce_work(struct work_struct *work);
/* IRQ handlers */
struct amdgpu_crtc *amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev,

---
base-commit: c92b5b607c1c8ab786fccf03d62352b82f6539ae
change-id: 20260708-hpd-debounce-unify-896ad367bf02

Best regards,
--
Nick Haghiri <nick@xxxxxxxxxxx>

--
Thanks,
Ivan Lipski