ALSA: hda - Restrict PCM parameters per ELD information over HDMI
When a device is plugged over HDMI, it passes some information in ELD including the supported PCM parameters like formats, rates, channels. This patch adds the check to PCM open callback of HDMI streams so that only valid parameters the device supports are used. When no device is plugged, the parameters the codec supports are used; it's mostly all parameters the hardware can work. This is for apps that are started before device plugging and do probing (e.g. a sound daemon), so that at least, probing would work even before the device plugging. Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
parent
8a345a042a
commit
bbbe33900d
|
@ -596,4 +596,53 @@ void snd_hda_eld_proc_free(struct hda_codec *codec, struct hdmi_eld *eld)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_HDA(snd_hda_eld_proc_free);
|
EXPORT_SYMBOL_HDA(snd_hda_eld_proc_free);
|
||||||
|
|
||||||
|
/* update PCM info based on ELD */
|
||||||
|
void hdmi_eld_update_pcm_info(struct hdmi_eld *eld, struct hda_pcm_stream *pcm,
|
||||||
|
struct hda_pcm_stream *codec_pars)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
pcm->rates = 0;
|
||||||
|
pcm->formats = 0;
|
||||||
|
pcm->maxbps = 0;
|
||||||
|
pcm->channels_min = -1;
|
||||||
|
pcm->channels_max = 0;
|
||||||
|
for (i = 0; i < eld->sad_count; i++) {
|
||||||
|
struct cea_sad *a = &eld->sad[i];
|
||||||
|
pcm->rates |= a->rates;
|
||||||
|
if (a->channels < pcm->channels_min)
|
||||||
|
pcm->channels_min = a->channels;
|
||||||
|
if (a->channels > pcm->channels_max)
|
||||||
|
pcm->channels_max = a->channels;
|
||||||
|
if (a->format == AUDIO_CODING_TYPE_LPCM) {
|
||||||
|
if (a->sample_bits & AC_SUPPCM_BITS_16) {
|
||||||
|
pcm->formats |= SNDRV_PCM_FMTBIT_S16_LE;
|
||||||
|
if (pcm->maxbps < 16)
|
||||||
|
pcm->maxbps = 16;
|
||||||
|
}
|
||||||
|
if (a->sample_bits & AC_SUPPCM_BITS_20) {
|
||||||
|
pcm->formats |= SNDRV_PCM_FMTBIT_S32_LE;
|
||||||
|
if (pcm->maxbps < 20)
|
||||||
|
pcm->maxbps = 20;
|
||||||
|
}
|
||||||
|
if (a->sample_bits & AC_SUPPCM_BITS_24) {
|
||||||
|
pcm->formats |= SNDRV_PCM_FMTBIT_S32_LE;
|
||||||
|
if (pcm->maxbps < 24)
|
||||||
|
pcm->maxbps = 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!codec_pars)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* restrict the parameters by the values the codec provides */
|
||||||
|
pcm->rates &= codec_pars->rates;
|
||||||
|
pcm->formats &= codec_pars->formats;
|
||||||
|
pcm->channels_min = max(pcm->channels_min, codec_pars->channels_min);
|
||||||
|
pcm->channels_max = min(pcm->channels_max, codec_pars->channels_max);
|
||||||
|
pcm->maxbps = min(pcm->maxbps, codec_pars->maxbps);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_HDA(hdmi_eld_update_pcm_info);
|
||||||
|
|
||||||
#endif /* CONFIG_PROC_FS */
|
#endif /* CONFIG_PROC_FS */
|
||||||
|
|
|
@ -604,6 +604,8 @@ struct hdmi_eld {
|
||||||
int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid);
|
int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid);
|
||||||
int snd_hdmi_get_eld(struct hdmi_eld *, struct hda_codec *, hda_nid_t);
|
int snd_hdmi_get_eld(struct hdmi_eld *, struct hda_codec *, hda_nid_t);
|
||||||
void snd_hdmi_show_eld(struct hdmi_eld *eld);
|
void snd_hdmi_show_eld(struct hdmi_eld *eld);
|
||||||
|
void hdmi_eld_update_pcm_info(struct hdmi_eld *eld, struct hda_pcm_stream *pcm,
|
||||||
|
struct hda_pcm_stream *codec_pars);
|
||||||
|
|
||||||
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
||||||
int snd_hda_eld_proc_new(struct hda_codec *codec, struct hdmi_eld *eld,
|
int snd_hda_eld_proc_new(struct hda_codec *codec, struct hdmi_eld *eld,
|
||||||
|
|
|
@ -46,6 +46,7 @@ struct hdmi_spec {
|
||||||
* export one pcm per pipe
|
* export one pcm per pipe
|
||||||
*/
|
*/
|
||||||
struct hda_pcm pcm_rec[MAX_HDMI_CVTS];
|
struct hda_pcm pcm_rec[MAX_HDMI_CVTS];
|
||||||
|
struct hda_pcm_stream codec_pcm_pars[MAX_HDMI_CVTS];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* nvhdmi specific
|
* nvhdmi specific
|
||||||
|
@ -765,6 +766,47 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HDA PCM callbacks
|
||||||
|
*/
|
||||||
|
static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
|
||||||
|
struct hda_codec *codec,
|
||||||
|
struct snd_pcm_substream *substream)
|
||||||
|
{
|
||||||
|
struct hdmi_spec *spec = codec->spec;
|
||||||
|
struct hdmi_eld *eld;
|
||||||
|
struct hda_pcm_stream *codec_pars;
|
||||||
|
unsigned int idx;
|
||||||
|
|
||||||
|
for (idx = 0; idx < spec->num_cvts; idx++)
|
||||||
|
if (hinfo->nid == spec->cvt[idx])
|
||||||
|
break;
|
||||||
|
if (snd_BUG_ON(idx >= spec->num_cvts) ||
|
||||||
|
snd_BUG_ON(idx >= spec->num_pins))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* save the PCM info the codec provides */
|
||||||
|
codec_pars = &spec->codec_pcm_pars[idx];
|
||||||
|
if (!codec_pars->rates)
|
||||||
|
*codec_pars = *hinfo;
|
||||||
|
|
||||||
|
eld = &spec->sink_eld[idx];
|
||||||
|
if (eld->sad_count > 0) {
|
||||||
|
hdmi_eld_update_pcm_info(eld, hinfo, codec_pars);
|
||||||
|
if (hinfo->channels_min > hinfo->channels_max ||
|
||||||
|
!hinfo->rates || !hinfo->formats)
|
||||||
|
return -ENODEV;
|
||||||
|
} else {
|
||||||
|
/* fallback to the codec default */
|
||||||
|
hinfo->channels_min = codec_pars->channels_min;
|
||||||
|
hinfo->channels_max = codec_pars->channels_max;
|
||||||
|
hinfo->rates = codec_pars->rates;
|
||||||
|
hinfo->formats = codec_pars->formats;
|
||||||
|
hinfo->maxbps = codec_pars->maxbps;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* HDA/HDMI auto parsing
|
* HDA/HDMI auto parsing
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -80,6 +80,7 @@ static struct hda_pcm_stream intel_hdmi_pcm_playback = {
|
||||||
.substreams = 1,
|
.substreams = 1,
|
||||||
.channels_min = 2,
|
.channels_min = 2,
|
||||||
.ops = {
|
.ops = {
|
||||||
|
.open = hdmi_pcm_open,
|
||||||
.prepare = intel_hdmi_playback_pcm_prepare,
|
.prepare = intel_hdmi_playback_pcm_prepare,
|
||||||
.cleanup = intel_hdmi_playback_pcm_cleanup,
|
.cleanup = intel_hdmi_playback_pcm_cleanup,
|
||||||
},
|
},
|
||||||
|
|
|
@ -347,10 +347,8 @@ static int nvhdmi_dig_playback_pcm_prepare_2ch(struct hda_pcm_stream *hinfo,
|
||||||
static struct hda_pcm_stream nvhdmi_pcm_digital_playback_8ch_89 = {
|
static struct hda_pcm_stream nvhdmi_pcm_digital_playback_8ch_89 = {
|
||||||
.substreams = 1,
|
.substreams = 1,
|
||||||
.channels_min = 2,
|
.channels_min = 2,
|
||||||
.rates = SUPPORTED_RATES,
|
|
||||||
.maxbps = SUPPORTED_MAXBPS,
|
|
||||||
.formats = SUPPORTED_FORMATS,
|
|
||||||
.ops = {
|
.ops = {
|
||||||
|
.open = hdmi_pcm_open,
|
||||||
.prepare = nvhdmi_dig_playback_pcm_prepare_8ch_89,
|
.prepare = nvhdmi_dig_playback_pcm_prepare_8ch_89,
|
||||||
.cleanup = nvhdmi_playback_pcm_cleanup,
|
.cleanup = nvhdmi_playback_pcm_cleanup,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue