Re: [PATCH] ALSA: hda/hdmi: stop KAE before multichannel infoframe setup

Péter Ujfalusi <[email protected]>
Newsgroups org.kernel.vger.linux-sound,org.kernel.vger.stable
Message-ID <[email protected]>

On 23/07/2026 01:03, [email protected] wrote:
> On Intel Meteor Lake and other ADLP-class display codecs, HDA Keep-Alive
> (KAE) silent stream stays active while idle and transmits stereo silence.
> PCM prepare programmed converter channel count and the DP/HDMI audio
> infoframe before KAE was stopped, then re-enabled KAE during real
> playback. Reconfiguring an active keep-alive stream to 5.1/7.1 can hang
> the display audio codec until reboot. Stereo playback is unaffected.
> 
> Stop KAE in a new pre_prepare hook before infoframe/channel-count setup,
> do not re-enable KAE while real PCM is running, and restore the stereo
> silent stream from PCM close when the sink is still present.

I think this has been fixed by
https://lore.kernel.org/linux-sound/[email protected]/T/#u

> 
> Signed-off-by: Alexey Cluster <[email protected]>
> ---
> 
> Hardware / test notes:
> - Laptop: HP OMEN Transcend Gaming Laptop 14-fb0xxx
>   (marketing: OMEN Transcend Laptop 14-fb0065TX, SKU 9T0M1PA#AB2)
> - CPU/iGPU: Meteor Lake-P, PCI 8086:7d55 (Arc Graphics)
> - Audio controller: Meteor Lake-P HD Audio, PCI 8086:7e28
> - Stack: sof-soundwire + snd-hda-codec-intelhdmi
> - Display codec: Intel Meteor Lake HDMI, vendor_id 0x8086281d
> - Path: USB-C DisplayPort -> AVR "CZ350CK" (ELD speakers 0x4f, LPCM up to 8ch)
> - CONFIG_SND_HDA_INTEL_HDMI_SILENT_STREAM=y / enable_silent_stream=Y
> 
> Before this change, switching the sink from 2.0 to 5.1 (UCM "HiFi 5+1" /
> speaker-test -c6 on hw:sofsoundwire,5) could hang display audio until reboot.
> Stereo continued to work. After the change, cold 6ch open and 2ch<->6ch
> transitions complete with ca=0x0b and no hang.
> 
>  sound/hda/codecs/hdmi/hdmi.c       | 40 ++++++++++++++++++++++++----
>  sound/hda/codecs/hdmi/hdmi_local.h |  9 +++++++
>  sound/hda/codecs/hdmi/intelhdmi.c  | 42 ++++++++++++++++++++++++++----
>  3 files changed, 81 insertions(+), 10 deletions(-)
> 
> diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c
> index 0b6816018a42..e2a77877358a 100644
> --- a/sound/hda/codecs/hdmi/hdmi.c
> +++ b/sound/hda/codecs/hdmi/hdmi.c
> @@ -557,6 +557,11 @@ void snd_hda_hdmi_setup_audio_infoframe(struct hda_codec *codec,
>  
>  	active_channels = snd_hdac_get_active_channels(ca);
>  
> +	codec_dbg(codec,
> +		  "HDMI: infoframe pin-NID=0x%x channels=%d active=%d ca=0x%02x conn=%d spk_alloc=0x%x non_pcm=%d\n",
> +		  pin_nid, channels, active_channels, ca, eld->info.conn_type,
> +		  eld->info.spk_alloc, non_pcm);
> +
>  	chmap->ops.set_channel_count(&codec->core, per_pin->cvt_nid,
>  						active_channels);
>  
> @@ -1683,6 +1688,13 @@ int snd_hda_hdmi_generic_pcm_prepare(struct hda_pcm_stream *hinfo,
>  		snd_hdac_sync_audio_rate(&codec->core, per_pin->pin_nid,
>  					 per_pin->dev_id, runtime->rate);
>  
> +	/*
> +	 * Stop Intel KAE (and similar) before channel-count / infoframe are
> +	 * programmed. Must not sleep under per_pin->lock.
> +	 */
> +	if (spec->ops.pre_prepare)
> +		spec->ops.pre_prepare(codec, per_pin, runtime->channels);
> +
>  	non_pcm = check_non_pcm_per_cvt(codec, cvt_nid);
>  	scoped_guard(mutex, &per_pin->lock) {
>  		per_pin->channels = substream->runtime->channels;
> @@ -1732,6 +1744,7 @@ static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
>  	struct hdmi_spec_per_cvt *per_cvt;
>  	struct hdmi_spec_per_pin *per_pin;
>  	int pinctl;
> +	bool restore_silent = false;
>  
>  	guard(mutex)(&spec->pcm_lock);
>  	if (hinfo->nid) {
> @@ -1769,12 +1782,29 @@ static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
>  					    pinctl & ~PIN_OUT);
>  		}
>  
> -		guard(mutex)(&per_pin->lock);
> -		per_pin->chmap_set = false;
> -		memset(per_pin->chmap, 0, sizeof(per_pin->chmap));
> +		scoped_guard(mutex, &per_pin->lock) {
> +			per_pin->chmap_set = false;
> +			memset(per_pin->chmap, 0, sizeof(per_pin->chmap));
> +
> +			per_pin->setup = false;
> +			per_pin->channels = 0;
> +			/*
> +			 * KAE keep-alive is stopped for the duration of real
> +			 * PCM; restore stereo silent stream after close. The
> +			 * older I915 silent-stream mode keeps its own power
> +			 * refs and must not be re-armed here.
> +			 */
> +			restore_silent = per_pin->silent_stream &&
> +					 per_pin->sink_eld.monitor_present &&
> +					 spec->silent_stream_type == SILENT_STREAM_KAE;
> +		}
>  
> -		per_pin->setup = false;
> -		per_pin->channels = 0;
> +		if (restore_silent && spec->ops.silent_stream) {
> +			codec_dbg(codec,
> +				  "HDMI: restore silent stream after PCM close pin-NID=0x%x\n",
> +				  per_pin->pin_nid);
> +			spec->ops.silent_stream(codec, per_pin, true);
> +		}
>  	}
>  
>  	return 0;
> diff --git a/sound/hda/codecs/hdmi/hdmi_local.h b/sound/hda/codecs/hdmi/hdmi_local.h
> index 548241ad3fa9..20e7536efef0 100644
> --- a/sound/hda/codecs/hdmi/hdmi_local.h
> +++ b/sound/hda/codecs/hdmi/hdmi_local.h
> @@ -74,6 +74,15 @@ struct hdmi_ops {
>  			    hda_nid_t pin_nid, int dev_id, u32 stream_tag,
>  			    int format);
>  
> +	/*
> +	 * Optional: called before channel-count / infoframe programming in
> +	 * PCM prepare. Used by Intel KAE silent-stream to stop keep-alive
> +	 * before the converter is reconfigured for real playback.
> +	 */
> +	void (*pre_prepare)(struct hda_codec *codec,
> +			    struct hdmi_spec_per_pin *per_pin,
> +			    unsigned int channels);
> +
>  	void (*pin_cvt_fixup)(struct hda_codec *codec,
>  			      struct hdmi_spec_per_pin *per_pin,
>  			      hda_nid_t cvt_nid);
> diff --git a/sound/hda/codecs/hdmi/intelhdmi.c b/sound/hda/codecs/hdmi/intelhdmi.c
> index 6a7882544ab7..60ed2ffb182d 100644
> --- a/sound/hda/codecs/hdmi/intelhdmi.c
> +++ b/sound/hda/codecs/hdmi/intelhdmi.c
> @@ -418,6 +418,32 @@ static void intel_not_share_assigned_cvt_nid(struct hda_codec *codec,
>  		intel_not_share_assigned_cvt(codec, pin_nid, dev_id, mux_idx);
>  }
>  
> +/*
> + * Stop KAE before channel-count / infoframe are programmed. Reconfiguring an
> + * active keep-alive stream (stereo silence) to multichannel can hang Intel
> + * display codecs (MTL and earlier ADLP KAE). KAE is restored from PCM close.
> + */
> +static void i915_hsw_pre_prepare(struct hda_codec *codec,
> +				 struct hdmi_spec_per_pin *per_pin,
> +				 unsigned int channels)
> +{
> +	struct hdmi_spec *spec = codec->spec;
> +
> +	if (spec->silent_stream_type != SILENT_STREAM_KAE || !per_pin)
> +		return;
> +	if (!per_pin->silent_stream)
> +		return;
> +
> +	codec_dbg(codec,
> +		  "HDMI: KAE stop before prepare pin-NID=0x%x cvt-NID=0x%x channels=%u->%u conn=%d\n",
> +		  per_pin->pin_nid, per_pin->cvt_nid, per_pin->channels,
> +		  channels, per_pin->sink_eld.info.conn_type);
> +
> +	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,
> @@ -435,7 +461,13 @@ static int i915_hsw_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
>  
>  	haswell_verify_D0(codec, cvt_nid, pin_nid);
>  
> -	if (spec->silent_stream_type == SILENT_STREAM_KAE && per_pin && per_pin->silent_stream) {
> +	/*
> +	 * KAE should already be cleared in pre_prepare; keep a safety stop
> +	 * here for callers that only go through setup_stream. Do not re-enable
> +	 * KAE while real PCM is active — that races with multichannel formats.
> +	 */
> +	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);
> @@ -444,10 +476,9 @@ static int i915_hsw_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
>  	res = snd_hda_hdmi_setup_stream(codec, cvt_nid, pin_nid, dev_id,
>  					stream_tag, format);
>  
> -	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);
> -	}
> +	codec_dbg(codec,
> +		  "HDMI: setup_stream pin-NID=0x%x cvt-NID=0x%x tag=%u fmt=0x%x res=%d\n",
> +		  pin_nid, cvt_nid, stream_tag, format, res);
>  
>  	return res;
>  }
> @@ -608,6 +639,7 @@ static int intel_hsw_common_init(struct hda_codec *codec, hda_nid_t vendor_nid,
>  	codec->auto_runtime_pm = 1;
>  
>  	spec->ops.setup_stream = i915_hsw_setup_stream;
> +	spec->ops.pre_prepare = i915_hsw_pre_prepare;
>  	spec->ops.pin_cvt_fixup = i915_pin_cvt_fixup;
>  	spec->ops.silent_stream = i915_set_silent_stream;
>  

-- 
Péter
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.