Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 c918e75e1ed95be76f8e3156a411188f650fe03f)

authored by

Ivan Lipski and committed by
Alex Deucher
c97da478 8612badc

+144
+138
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
··· 3859 3859 update_subconnector_property(aconnector); 3860 3860 } 3861 3861 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 + 3862 3953 static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector) 3863 3954 { 3864 3955 struct drm_connector *connector = &aconnector->base; ··· 3959 3868 struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state); 3960 3869 struct dc *dc = aconnector->dc_link->ctx->dc; 3961 3870 bool ret = false; 3871 + bool debounce_required = false; 3962 3872 3963 3873 if (adev->dm.disable_hpd_irq) 3964 3874 return; ··· 3982 3890 if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type)) 3983 3891 drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); 3984 3892 3893 + /* 3894 + * Check for HDMI disconnect with debounce enabled. 3895 + */ 3896 + debounce_required = (aconnector->hdmi_hpd_debounce_delay_ms > 0 && 3897 + dc_is_hdmi_signal(aconnector->dc_link->connector_signal) && 3898 + new_connection_type == dc_connection_none && 3899 + aconnector->dc_link->local_sink != NULL); 3900 + 3985 3901 if (aconnector->base.force && new_connection_type == dc_connection_none) { 3986 3902 emulated_link_detect(aconnector->dc_link); 3987 3903 ··· 3999 3899 4000 3900 if (aconnector->base.force == DRM_FORCE_UNSPECIFIED) 4001 3901 drm_kms_helper_connector_hotplug_event(connector); 3902 + } else if (debounce_required) { 3903 + /* 3904 + * HDMI disconnect detected - schedule delayed work instead of 3905 + * processing immediately. This allows us to coalesce spurious 3906 + * HDMI signals from physical unplugs. 3907 + */ 3908 + drm_dbg_kms(dev, "HDMI HPD: Disconnect detected, scheduling debounce work (%u ms)\n", 3909 + aconnector->hdmi_hpd_debounce_delay_ms); 3910 + 3911 + /* Cache the current sink for later comparison */ 3912 + if (aconnector->hdmi_prev_sink) 3913 + dc_sink_release(aconnector->hdmi_prev_sink); 3914 + aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink; 3915 + if (aconnector->hdmi_prev_sink) 3916 + dc_sink_retain(aconnector->hdmi_prev_sink); 3917 + 3918 + /* Schedule delayed detection. */ 3919 + if (mod_delayed_work(system_wq, 3920 + &aconnector->hdmi_hpd_debounce_work, 3921 + msecs_to_jiffies(aconnector->hdmi_hpd_debounce_delay_ms))) 3922 + drm_dbg_kms(dev, "HDMI HPD: Re-scheduled debounce work\n"); 3923 + 4002 3924 } else { 3925 + 3926 + /* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */ 3927 + if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work)) 3928 + return; 3929 + 4003 3930 scoped_guard(mutex, &adev->dm.dc_lock) { 4004 3931 dc_exit_ips_for_hw_access(dc); 4005 3932 ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD); ··· 7515 7388 if (aconnector->mst_mgr.dev) 7516 7389 drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr); 7517 7390 7391 + /* Cancel and flush any pending HDMI HPD debounce work */ 7392 + cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work); 7393 + if (aconnector->hdmi_prev_sink) { 7394 + dc_sink_release(aconnector->hdmi_prev_sink); 7395 + aconnector->hdmi_prev_sink = NULL; 7396 + } 7397 + 7518 7398 if (aconnector->bl_idx != -1) { 7519 7399 backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]); 7520 7400 dm->backlight_dev[aconnector->bl_idx] = NULL; ··· 8682 8548 memset(&aconnector->vsdb_info, 0, sizeof(aconnector->vsdb_info)); 8683 8549 mutex_init(&aconnector->hpd_lock); 8684 8550 mutex_init(&aconnector->handle_mst_msg_ready); 8551 + 8552 + aconnector->hdmi_hpd_debounce_delay_ms = AMDGPU_DM_HDMI_HPD_DEBOUNCE_MS; 8553 + INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, hdmi_hpd_debounce_work); 8554 + aconnector->hdmi_prev_sink = NULL; 8685 8555 8686 8556 /* 8687 8557 * configure support HPD hot plug connector_>polled default value is 0
+6
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
··· 59 59 60 60 #define AMDGPU_HDR_MULT_DEFAULT (0x100000000LL) 61 61 62 + #define AMDGPU_DM_HDMI_HPD_DEBOUNCE_MS 1500 62 63 /* 63 64 #include "include/amdgpu_dal_power_if.h" 64 65 #include "amdgpu_dm_irq.h" ··· 820 819 bool pack_sdp_v1_3; 821 820 enum adaptive_sync_type as_type; 822 821 struct amdgpu_hdmi_vsdb_info vsdb_info; 822 + 823 + /* HDMI HPD debounce support */ 824 + unsigned int hdmi_hpd_debounce_delay_ms; 825 + struct delayed_work hdmi_hpd_debounce_work; 826 + struct dc_sink *hdmi_prev_sink; 823 827 }; 824 828 825 829 static inline void amdgpu_dm_set_mst_status(uint8_t *status,