ALSA: hda - Restrict PCM parameters per ELD information over HDMI
authorTakashi Iwai <tiwai@suse.de>
Fri, 13 Aug 2010 06:45:23 +0000 (08:45 +0200)
committerTakashi Iwai <tiwai@suse.de>
Fri, 13 Aug 2010 06:45:23 +0000 (08:45 +0200)
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>
sound/pci/hda/hda_eld.c
sound/pci/hda/hda_local.h
sound/pci/hda/patch_hdmi.c
sound/pci/hda/patch_intelhdmi.c
sound/pci/hda/patch_nvhdmi.c

index d8da18a9e98b9051a05c15d426f964c29fcf9b21..803b298f741101065b5966f17a84cddf01028ef6 100644 (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);
 
+/* 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 */
index 7a97f126f6f7c3465db61d23b85b22d1b09e112c..28ab4aead48fd17db36098f2eb36b306f4a5d534 100644 (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(struct hdmi_eld *, struct hda_codec *, hda_nid_t);
 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
 int snd_hda_eld_proc_new(struct hda_codec *codec, struct hdmi_eld *eld,
index 522e0748ee99f841cf6458823bd8228013bbd83c..2bc0f07cf33fe4f67720fc3c9487a817b3e77823 100644 (file)
@@ -46,6 +46,7 @@ struct hdmi_spec {
         * export one pcm per pipe
         */
        struct hda_pcm  pcm_rec[MAX_HDMI_CVTS];
+       struct hda_pcm_stream codec_pcm_pars[MAX_HDMI_CVTS];
 
        /*
         * nvhdmi specific
@@ -765,6 +766,47 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid,
        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
  */
index 5972d5e7d01f5757fb77aa38ae05f62de180a00e..d382d3c81c0fc5bb0f320fcad17b96d67f6ef473 100644 (file)
@@ -80,6 +80,7 @@ static struct hda_pcm_stream intel_hdmi_pcm_playback = {
        .substreams = 1,
        .channels_min = 2,
        .ops = {
+               .open = hdmi_pcm_open,
                .prepare = intel_hdmi_playback_pcm_prepare,
                .cleanup = intel_hdmi_playback_pcm_cleanup,
        },
index 77e2b4028b9f032fd9abeafd8d8a471b2b7d6e96..f636870dc718d4445ed400924f3075b11b186159 100644 (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 = {
        .substreams = 1,
        .channels_min = 2,
-       .rates = SUPPORTED_RATES,
-       .maxbps = SUPPORTED_MAXBPS,
-       .formats = SUPPORTED_FORMATS,
        .ops = {
+               .open = hdmi_pcm_open,
                .prepare = nvhdmi_dig_playback_pcm_prepare_8ch_89,
                .cleanup = nvhdmi_playback_pcm_cleanup,
        },