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:
Takashi Iwai 2010-08-13 08:45:23 +02:00
parent 8a345a042a
commit bbbe33900d
5 changed files with 95 additions and 3 deletions

View File

@ -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 */

View File

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

View File

@ -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
*/ */

View File

@ -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,
}, },

View File

@ -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,
}, },