Skip to content

Instantly share code, notes, and snippets.

@john-tornblom
Created July 21, 2026 17:08
Show Gist options
  • Select an option

  • Save john-tornblom/0d5fcd4da4bce432a65123a5251934ec to your computer and use it in GitHub Desktop.

Select an option

Save john-tornblom/0d5fcd4da4bce432a65123a5251934ec to your computer and use it in GitHub Desktop.
--- a/drivers/ps5/hdmi.c 2026-07-16 16:29:55.397400541 +0200
+++ b/drivers/ps5/hdmi.c 2026-07-16 16:35:02.223150675 +0200
@@ -4,6 +4,7 @@
#include <linux/module.h>
#include <linux/ps5.h>
+#include <linux/workqueue.h>
#include <drm/drm_edid.h>
#define HDMI_IC_TYPE_FLAVA3 2
@@ -108,6 +109,16 @@
static u8 dp_polarity;
static u8 dp_ch_swap;
+/*
+ * Last output config successfully applied by the DRM/DC commit path. Cached
+ * here so it can be replayed on a hotplug reconnect, where DC performs no
+ * modeset (the internal APU->converter DP link never drops) and therefore
+ * never re-runs the converter bring-up itself.
+ */
+static struct drm_display_mode hdmi_cached_mode;
+static bool hdmi_cached_mode_valid;
+static int hdmi_cached_channels;
+
static void i2c_init(u8 code)
{
i2c_ctx.msg_cur = i2c_ctx.msg_buf;
@@ -1107,6 +1118,9 @@
void sceHdmiSetVideoConfig(const struct drm_display_mode *mode)
{
+ hdmi_cached_mode = *mode;
+ hdmi_cached_mode_valid = true;
+
if (hdmi_ic_type == HDMI_IC_TYPE_FLAVA3) {
setHdmiVideoConfigFlava3(mode);
} else {
@@ -1129,6 +1143,8 @@
void sceHdmiSetAudioConfig(int channels)
{
+ hdmi_cached_channels = channels;
+
if (hdmi_ic_type == HDMI_IC_TYPE_FLAVA3) {
sceHdmiSetAudioConfigFlava3(channels);
} else {
@@ -1227,6 +1243,38 @@
edid[0x7f] = -sum & 0xff;
}
+/*
+ * Replay the full converter bring-up for the last-applied mode.
+ *
+ * On an input-source swap or replug the sink disappears and comes back, so the
+ * converter's downstream HDMI transmitter is left cold. The internal
+ * APU->converter DP link never drops, so DC sees no change, performs no
+ * modeset, and never re-runs the init sequence from amdgpu_dm_commit_streams()/
+ * amdgpu_dm_atomic_commit_tail(). We reproduce that sequence here.
+ *
+ * Deferred to a workqueue: the sequence sleeps (icc_query() round-trips plus
+ * hundreds of ms of i2c_delay()), and the ICC notification callback may run in
+ * a context that cannot.
+ */
+static void hdmi_replay_work_fn(struct work_struct *work)
+{
+ if (!hdmi_cached_mode_valid) {
+ /*
+ * Nothing has been applied yet (e.g. the boot-time EDID
+ * notification, which arrives before the first DC commit). The
+ * normal DC path will perform the initial bring-up.
+ */
+ return;
+ }
+
+ sceHdmiInitVideoConfig();
+ sceHdmiSetVideoConfig(&hdmi_cached_mode);
+ sceHdmiDeviceSetVideoMute(0);
+ sceHdmiSetAudioConfig(hdmi_cached_channels);
+ sceHdmiSetAudioMute(0);
+}
+static DECLARE_WORK(hdmi_replay_work, hdmi_replay_work_fn);
+
void hdmi_notification_handler(struct icc_msg *msg)
{
if (msg->data[1] == 0x02) {
@@ -1234,6 +1282,14 @@
real_edid = drm_edid_alloc(&msg->data[4], *(u16 *)&msg->data[2]);
pr_info("got real edid\n");
sceHdmiOutputMode();
+
+ /*
+ * Sink (re)appeared. If this is a reconnect rather than
+ * the initial boot detection, the converter is cold and
+ * DC will not re-init it for us, so replay the last
+ * applied config to restore output.
+ */
+ schedule_work(&hdmi_replay_work);
}
}
EXPORT_SYMBOL(hdmi_notification_handler);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment