From a045d12f94ca334f9835e859d0509ece5cbe0c2b Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 20 Aug 2026 11:26:30 +0300 Subject: [PATCH 1/2] ALSA: hda/hdmi: Add option to narrow the PCM with hw_constraints hdmi_pcm_open() reports what the converter and the sink's ELD allow by updating substream->runtime->hw. That works for a codec driven by the HDA controller, but not for one exposed by a DSP driver as an ASoC dynamic PCM backend: the ASoC core sets up the runtime->hw of the frontend from topology after the backends have been opened, overwriting everything the codec placed there. Userspace then negotiates against topology and only finds out at hw_params time, or not at all, that the sink cannot do what it asked for. The hw_constraints are not overwritten, and the final runtime->hw is merged into them by snd_pcm_hw_constraints_complete(), so a constraint added at open time composes with topology instead of being lost. Repeat the narrowing there for a codec which asks for it with the new constrain_pcms flag, which no codec sets yet. The ELD is applied with snd_pcm_hw_constraint_eld(), which keeps the correlation between the rates and the channel count of each SAD, so it is a finer restriction than struct hda_pcm_stream can express. The rates a converter supports are a mask, which no constraint can take, so those are left to the caps the PCM was created with. Signed-off-by: Peter Ujfalusi --- include/sound/hda_codec.h | 3 +++ sound/hda/codecs/hdmi/hdmi.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 1d05f991f2ce7e..b95b59c4e7887c 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -260,6 +260,9 @@ struct hda_codec { unsigned int no_stream_clean_at_suspend:1; /* do not clean streams at suspend */ unsigned int ctl_dev_id:1; /* old control element id build behaviour */ unsigned int eld_jack_detect:1; /* Machine jack-detection by ELD */ + unsigned int constrain_pcms:1; /* narrow the PCMs with hw_constraints + * because runtime->hw is not honoured + */ unsigned long power_on_acct; unsigned long power_off_acct; diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index 1e7a05c8773311..da26eb51d232d4 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -830,6 +830,32 @@ static void pin_cvt_fixup(struct hda_codec *codec, spec->ops.pin_cvt_fixup(codec, per_pin, cvt_nid); } +/* + * Repeat the narrowing of the PCM as hw_constraints for a codec which asked for + * it. The runtime->hw of such a codec is overwritten after this open returns, + * losing everything set there, while nothing touches the constraints. A DSP + * driver exposing the PCM as an ASoC dynamic PCM backend is the case that needs + * it, the frontend being what userspace negotiates against there. + */ +static void hdmi_pcm_add_constraints(struct hda_pcm_stream *hinfo, + struct snd_pcm_substream *substream, + struct hdmi_eld *eld) +{ + snd_pcm_hw_constraint_mask64(substream->runtime, + SNDRV_PCM_HW_PARAM_FORMAT, hinfo->formats); + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + hinfo->channels_min, hinfo->channels_max); + /* + * The rates a converter supports are a mask, which no constraint can + * take, so they are left to the caps the PCM was created with. The ELD + * rates are a finer restriction than @hinfo can express anyway, being + * correlated with the channel count. + */ + if (eld) + snd_pcm_hw_constraint_eld(substream->runtime, eld->eld_buffer); +} + /* called in hdmi_pcm_open when no pin is assigned to the PCM */ static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo, struct hda_codec *codec, @@ -871,6 +897,9 @@ static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo, runtime->hw.formats = hinfo->formats; runtime->hw.rates = hinfo->rates; + if (codec->constrain_pcms) + hdmi_pcm_add_constraints(hinfo, substream, NULL); + snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2); return 0; @@ -957,6 +986,12 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo, runtime->hw.formats = hinfo->formats; runtime->hw.rates = hinfo->rates; + if (codec->constrain_pcms) { + if (static_hdmi_pcm || !eld->eld_valid) + eld = NULL; + hdmi_pcm_add_constraints(hinfo, substream, eld); + } + snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2); return 0; From b09826f1b78ecbf1996b1e142c2580508e1c2bc6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 20 Aug 2026 11:27:11 +0300 Subject: [PATCH 2/2] ASoC: SOF: Intel: hda-codec: Narrow the codec PCMs with hw_constraints Every HDA codec probed here is exposed as an ASoC dynamic PCM backend, so the runtime->hw a codec sets up at open time is overwritten by the core with what topology says about the frontend, and the codec's own view of what the hardware can do is lost. Set constrain_pcms so that the codec adds it as hw_constraints as well, which survive and are merged with the topology capabilities. For HDMI this means the sink's ELD finally reaches the frontend that userspace negotiates against, instead of the mismatch showing up as a failing hw_params. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/intel/hda-codec.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c index fd371850b0d691..b972142a25c197 100644 --- a/sound/soc/sof/intel/hda-codec.c +++ b/sound/soc/sof/intel/hda-codec.c @@ -181,6 +181,15 @@ static int hda_codec_probe(struct snd_sof_dev *sdev, int address) hda_priv->dev_index = address; dev_set_drvdata(&codec->core.dev, hda_priv); + /* + * Every codec here is exposed as a dynamic PCM backend, and the ASoC + * core overwrites the runtime->hw of those with what topology says, + * after the codec has set it up. Ask for hw_constraints instead, which + * survive, so that a restriction only the codec knows about reaches the + * frontend that userspace negotiates against. + */ + codec->constrain_pcms = 1; + if ((resp & 0xFFFF0000) == IDISP_VID_INTEL) { if (!hbus->core.audio_component) { dev_dbg(sdev->dev,