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; 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,