Skip to content

Commit c97da47

Browse files
Ivan Lipskialexdeucher
authored andcommitted
drm/amd/display: Add an HPD filter for HDMI
[Why] Some monitors perform rapid “autoscan” HPD re‑assertions right after a disconnect or powersaving mode enablement. These appear as a quick disconnect→reconnect with an identical EDID. Since Linux has no HDMI hotplug detection (HPD) filter, these quick reconnects are seen as hotplug events, which can unintentionally wake a system with DPMS off. An example: https://gitlab.freedesktop.org/drm/amd/-/issues/2876 Such 'fake reconnects' are considered when the interval between a disconnect and a connect is within 1500ms (experimentally chosen using several monitors), and the two connections have the same EDID. [How] Implement a time-based debounce mechanism: 1. On HDMI disconnect detection, instead of immediately processing the HPD event, save the current sink and schedule delayed work (default 1500ms) 2. If another HDMI disconnect HPD event arrives during the debounce period, it reschedules the pending work, ensuring only the final state is processed. 3. When the debounce timer expires, re-detect the display and compare the new sink with the cached one using EDID comparison. 4. If sinks match (same EDID), this was a spontaneous HPD toggle: - Update connector state internally - Skip hotplug event to prevent desktop rearrangement If sinks differ, this was a real display change: - Process normally with the hotplug event The debounce delay is configurable via module parameter 'hdmi_hpd_debounce_delay_ms'. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/2876 Reviewed-by: Sun peng (Leo) Li <sunpeng.li@amd.com> Signed-off-by: Ivan Lipski <ivan.lipski@amd.com> Tested-by: Dan Wheeler <daniel.wheeler@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com> (cherry picked from commit c918e75)
1 parent 8612bad commit c97da47

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3859,6 +3859,97 @@ void amdgpu_dm_update_connector_after_detect(
38593859
update_subconnector_property(aconnector);
38603860
}
38613861

3862+
static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2)
3863+
{
3864+
if (!sink1 || !sink2)
3865+
return false;
3866+
if (sink1->sink_signal != sink2->sink_signal)
3867+
return false;
3868+
3869+
if (sink1->dc_edid.length != sink2->dc_edid.length)
3870+
return false;
3871+
3872+
if (memcmp(sink1->dc_edid.raw_edid, sink2->dc_edid.raw_edid,
3873+
sink1->dc_edid.length) != 0)
3874+
return false;
3875+
return true;
3876+
}
3877+
3878+
3879+
/**
3880+
* DOC: hdmi_hpd_debounce_work
3881+
*
3882+
* HDMI HPD debounce delay in milliseconds. When an HDMI display toggles HPD
3883+
* (such as during power save transitions), this delay determines how long to
3884+
* wait before processing the HPD event. This allows distinguishing between a
3885+
* physical unplug (>hdmi_hpd_debounce_delay)
3886+
* and a spontaneous RX HPD toggle (<hdmi_hpd_debounce_delay).
3887+
*
3888+
* If the toggle is less than this delay, the driver compares sink capabilities
3889+
* and permits a hotplug event if they changed.
3890+
*
3891+
* The default value of 1500ms was chosen based on experimental testing with
3892+
* various monitors that exhibit spontaneous HPD toggling behavior.
3893+
*/
3894+
static void hdmi_hpd_debounce_work(struct work_struct *work)
3895+
{
3896+
struct amdgpu_dm_connector *aconnector =
3897+
container_of(to_delayed_work(work), struct amdgpu_dm_connector,
3898+
hdmi_hpd_debounce_work);
3899+
struct drm_connector *connector = &aconnector->base;
3900+
struct drm_device *dev = connector->dev;
3901+
struct amdgpu_device *adev = drm_to_adev(dev);
3902+
struct dc *dc = aconnector->dc_link->ctx->dc;
3903+
bool fake_reconnect = false;
3904+
bool reallow_idle = false;
3905+
bool ret = false;
3906+
guard(mutex)(&aconnector->hpd_lock);
3907+
3908+
/* Re-detect the display */
3909+
scoped_guard(mutex, &adev->dm.dc_lock) {
3910+
if (dc->caps.ips_support && dc->ctx->dmub_srv->idle_allowed) {
3911+
dc_allow_idle_optimizations(dc, false);
3912+
reallow_idle = true;
3913+
}
3914+
ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD);
3915+
}
3916+
3917+
if (ret) {
3918+
/* Apply workaround delay for certain panels */
3919+
apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink);
3920+
/* Compare sinks to determine if this was a spontaneous HPD toggle */
3921+
if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hdmi_prev_sink)) {
3922+
/*
3923+
* Sinks match - this was a spontaneous HDMI HPD toggle.
3924+
*/
3925+
drm_dbg_kms(dev, "HDMI HPD: Sink unchanged after debounce, internal re-enable\n");
3926+
fake_reconnect = true;
3927+
}
3928+
3929+
/* Update connector state */
3930+
amdgpu_dm_update_connector_after_detect(aconnector);
3931+
3932+
drm_modeset_lock_all(dev);
3933+
dm_restore_drm_connector_state(dev, connector);
3934+
drm_modeset_unlock_all(dev);
3935+
3936+
/* Only notify OS if sink actually changed */
3937+
if (!fake_reconnect && aconnector->base.force == DRM_FORCE_UNSPECIFIED)
3938+
drm_kms_helper_hotplug_event(dev);
3939+
}
3940+
3941+
/* Release the cached sink reference */
3942+
if (aconnector->hdmi_prev_sink) {
3943+
dc_sink_release(aconnector->hdmi_prev_sink);
3944+
aconnector->hdmi_prev_sink = NULL;
3945+
}
3946+
3947+
scoped_guard(mutex, &adev->dm.dc_lock) {
3948+
if (reallow_idle && dc->caps.ips_support)
3949+
dc_allow_idle_optimizations(dc, true);
3950+
}
3951+
}
3952+
38623953
static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector)
38633954
{
38643955
struct drm_connector *connector = &aconnector->base;
@@ -3868,6 +3959,7 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector)
38683959
struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state);
38693960
struct dc *dc = aconnector->dc_link->ctx->dc;
38703961
bool ret = false;
3962+
bool debounce_required = false;
38713963

38723964
if (adev->dm.disable_hpd_irq)
38733965
return;
@@ -3890,6 +3982,14 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector)
38903982
if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type))
38913983
drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n");
38923984

3985+
/*
3986+
* Check for HDMI disconnect with debounce enabled.
3987+
*/
3988+
debounce_required = (aconnector->hdmi_hpd_debounce_delay_ms > 0 &&
3989+
dc_is_hdmi_signal(aconnector->dc_link->connector_signal) &&
3990+
new_connection_type == dc_connection_none &&
3991+
aconnector->dc_link->local_sink != NULL);
3992+
38933993
if (aconnector->base.force && new_connection_type == dc_connection_none) {
38943994
emulated_link_detect(aconnector->dc_link);
38953995

@@ -3899,7 +3999,34 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector)
38993999

39004000
if (aconnector->base.force == DRM_FORCE_UNSPECIFIED)
39014001
drm_kms_helper_connector_hotplug_event(connector);
4002+
} else if (debounce_required) {
4003+
/*
4004+
* HDMI disconnect detected - schedule delayed work instead of
4005+
* processing immediately. This allows us to coalesce spurious
4006+
* HDMI signals from physical unplugs.
4007+
*/
4008+
drm_dbg_kms(dev, "HDMI HPD: Disconnect detected, scheduling debounce work (%u ms)\n",
4009+
aconnector->hdmi_hpd_debounce_delay_ms);
4010+
4011+
/* Cache the current sink for later comparison */
4012+
if (aconnector->hdmi_prev_sink)
4013+
dc_sink_release(aconnector->hdmi_prev_sink);
4014+
aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink;
4015+
if (aconnector->hdmi_prev_sink)
4016+
dc_sink_retain(aconnector->hdmi_prev_sink);
4017+
4018+
/* Schedule delayed detection. */
4019+
if (mod_delayed_work(system_wq,
4020+
&aconnector->hdmi_hpd_debounce_work,
4021+
msecs_to_jiffies(aconnector->hdmi_hpd_debounce_delay_ms)))
4022+
drm_dbg_kms(dev, "HDMI HPD: Re-scheduled debounce work\n");
4023+
39024024
} else {
4025+
4026+
/* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */
4027+
if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work))
4028+
return;
4029+
39034030
scoped_guard(mutex, &adev->dm.dc_lock) {
39044031
dc_exit_ips_for_hw_access(dc);
39054032
ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD);
@@ -7388,6 +7515,13 @@ static void amdgpu_dm_connector_destroy(struct drm_connector *connector)
73887515
if (aconnector->mst_mgr.dev)
73897516
drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr);
73907517

7518+
/* Cancel and flush any pending HDMI HPD debounce work */
7519+
cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work);
7520+
if (aconnector->hdmi_prev_sink) {
7521+
dc_sink_release(aconnector->hdmi_prev_sink);
7522+
aconnector->hdmi_prev_sink = NULL;
7523+
}
7524+
73917525
if (aconnector->bl_idx != -1) {
73927526
backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]);
73937527
dm->backlight_dev[aconnector->bl_idx] = NULL;
@@ -8549,6 +8683,10 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
85498683
mutex_init(&aconnector->hpd_lock);
85508684
mutex_init(&aconnector->handle_mst_msg_ready);
85518685

8686+
aconnector->hdmi_hpd_debounce_delay_ms = AMDGPU_DM_HDMI_HPD_DEBOUNCE_MS;
8687+
INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, hdmi_hpd_debounce_work);
8688+
aconnector->hdmi_prev_sink = NULL;
8689+
85528690
/*
85538691
* configure support HPD hot plug connector_>polled default value is 0
85548692
* which means HPD hot plug not supported

drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959

6060
#define AMDGPU_HDR_MULT_DEFAULT (0x100000000LL)
6161

62+
#define AMDGPU_DM_HDMI_HPD_DEBOUNCE_MS 1500
6263
/*
6364
#include "include/amdgpu_dal_power_if.h"
6465
#include "amdgpu_dm_irq.h"
@@ -819,6 +820,11 @@ struct amdgpu_dm_connector {
819820
bool pack_sdp_v1_3;
820821
enum adaptive_sync_type as_type;
821822
struct amdgpu_hdmi_vsdb_info vsdb_info;
823+
824+
/* HDMI HPD debounce support */
825+
unsigned int hdmi_hpd_debounce_delay_ms;
826+
struct delayed_work hdmi_hpd_debounce_work;
827+
struct dc_sink *hdmi_prev_sink;
822828
};
823829

824830
static inline void amdgpu_dm_set_mst_status(uint8_t *status,

0 commit comments

Comments
 (0)