--- /dev/null
+From stable+bounces-296948-greg=kroah.com@vger.kernel.org Thu Aug 6 19:13:46 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 6 Aug 2026 13:02:03 -0400
+Subject: ALSA: hda: codecs: hdmi: disable keep-alive before audio format change
+To: stable@vger.kernel.org
+Cc: Kai Vehmanen <kai.vehmanen@linux.intel.com>, Alexander Kaplan <alexander.kaplan@sms-medipool.de>, Takashi Iwai <tiwai@suse.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260806170203.1185340-1-sashal@kernel.org>
+
+From: Kai Vehmanen <kai.vehmanen@linux.intel.com>
+
+[ Upstream commit a3d6d3cedfe87bbd5a677d52b22ac20d28e59cf8 ]
+
+When a keep-alive (KAE) silent stream is active on an Intel HDMI/DP
+codec, opening a real PCM stream reprograms the converter format and the
+audio infoframe in snd_hda_hdmi_generic_pcm_prepare(). Part of that
+reprogramming - the converter channel count and the channel mapping in
+snd_hda_hdmi_setup_audio_infoframe() - is not safe to do while a
+keep-alive stream is active. This is most visible when switching to a
+multichannel PCM configuration, where the active channel count actually
+changes. In that case the newly opened PCM stream plays no sound.
+
+Add an optional hdmi_ops .prepare hook, called at the start of the
+PCM prepare sequence (before the format and infoframe are touched), and
+implement it for HSW+ to release keep-alive. Keep-alive is then
+re-enabled as before once the new stream has been set up, in the
+setup_stream op.
+
+Fixes: 15175a4f2bbb ("ALSA: hda/hdmi: add keep-alive support for ADL-P and DG2")
+Reported-by: Alexander Kaplan <alexander.kaplan@sms-medipool.de>
+Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/8412
+Tested-by: Alexander Kaplan <alexander.kaplan@sms-medipool.de>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
+Link: https://patch.msgid.link/20260715180610.1371243-1-kai.vehmanen@linux.intel.com
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+[ adapted three hunks from the post-6.12 split files (hdmi.c/hdmi_local.h/intelhdmi.c) back into the monolithic sound/pci/hda/patch_hdmi.c, with the prepare hook un-indented one level since 6.12 uses plain mutex_lock() instead of scoped_guard() ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/pci/hda/patch_hdmi.c | 48 +++++++++++++++++++++++++++++++++++++++------
+ 1 file changed, 42 insertions(+), 6 deletions(-)
+
+--- a/sound/pci/hda/patch_hdmi.c
++++ b/sound/pci/hda/patch_hdmi.c
+@@ -111,6 +111,15 @@ struct hdmi_ops {
+ hda_nid_t pin_nid, int dev_id, u32 stream_tag,
+ int format);
+
++ /*
++ * Optional hook invoked at the beginning of the PCM prepare
++ * sequence, before the audio infoframe and stream format are
++ * (re)programmed. Used to disable keep-alive / silent stream so
++ * that the format change is not done while keep-alive is active.
++ */
++ void (*prepare)(struct hda_codec *codec,
++ struct hdmi_spec_per_pin *per_pin);
++
+ void (*pin_cvt_fixup)(struct hda_codec *codec,
+ struct hdmi_spec_per_pin *per_pin,
+ hda_nid_t cvt_nid);
+@@ -2134,6 +2143,9 @@ static int generic_hdmi_playback_pcm_pre
+ per_pin->channels = substream->runtime->channels;
+ per_pin->setup = true;
+
++ if (spec->ops.prepare)
++ spec->ops.prepare(codec, per_pin);
++
+ if (get_wcaps(codec, cvt_nid) & AC_WCAP_STRIPE) {
+ stripe = snd_hdac_get_stream_stripe_ctl(&codec->bus->core,
+ substream);
+@@ -2905,6 +2917,28 @@ static void register_i915_notifier(struc
+ codec->relaxed_resume = 1;
+ }
+
++/*
++ * prepare ops override for HSW+
++ *
++ * Disable keep-alive before the converter format and audio infoframe are
++ * reprogrammed by the PCM prepare sequence. Changing the audio format (e.g.
++ * the channel count when switching to multichannel PCM) while a keep-alive
++ * stream is active is not safe, so release keep-alive here, early in the
++ * sequence. It is re-enabled once the new stream has been set up, in
++ * i915_hsw_setup_stream().
++ */
++static void i915_hsw_prepare(struct hda_codec *codec,
++ struct hdmi_spec_per_pin *per_pin)
++{
++ struct hdmi_spec *spec = codec->spec;
++
++ if (spec->silent_stream_type == SILENT_STREAM_KAE && per_pin->silent_stream) {
++ silent_stream_set_kae(codec, per_pin, false);
++ /* wait for pending transfers in codec to clear */
++ usleep_range(100, 200);
++ }
++}
++
+ /* setup_stream ops override for HSW+ */
+ static int i915_hsw_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
+ hda_nid_t pin_nid, int dev_id, u32 stream_tag,
+@@ -2922,15 +2956,16 @@ static int i915_hsw_setup_stream(struct
+
+ haswell_verify_D0(codec, cvt_nid, pin_nid);
+
+- if (spec->silent_stream_type == SILENT_STREAM_KAE && per_pin && per_pin->silent_stream) {
+- silent_stream_set_kae(codec, per_pin, false);
+- /* wait for pending transfers in codec to clear */
+- usleep_range(100, 200);
+- }
+-
+ res = hdmi_setup_stream(codec, cvt_nid, pin_nid, dev_id,
+ stream_tag, format);
+
++ /*
++ * Keep-alive was disabled in i915_hsw_prepare(), re-enable it now.
++ * The pin lookup above resolves to the same per_pin that prepare
++ * used (pin_nid comes from that per_pin), so this stays balanced; a
++ * NULL per_pin only occurs on a lookup failure that also implies no
++ * active keep-alive stream to restore.
++ */
+ if (spec->silent_stream_type == SILENT_STREAM_KAE && per_pin && per_pin->silent_stream) {
+ usleep_range(100, 200);
+ silent_stream_set_kae(codec, per_pin, true);
+@@ -3106,6 +3141,7 @@ static int intel_hsw_common_init(struct
+ codec->depop_delay = 0;
+ codec->auto_runtime_pm = 1;
+
++ spec->ops.prepare = i915_hsw_prepare;
+ spec->ops.setup_stream = i915_hsw_setup_stream;
+ spec->ops.pin_cvt_fixup = i915_pin_cvt_fixup;
+