ALSA: hda: HDMI: Support codecs with fewer cvts than pins
authorStephen Warren <swarren@nvidia.com>
Wed, 1 Jun 2011 17:14:21 +0000 (11:14 -0600)
committerTakashi Iwai <tiwai@suse.de>
Mon, 6 Jun 2011 10:58:14 +0000 (12:58 +0200)
The general concept of this change is to create a PCM device for each
pin widget instead of each converter widget. Whenever a PCM is opened,
a converter is dynamically selected to drive that pin based on those
available for muxing into the pin.

The one thing this model doesn't support is a single PCM/converter
sending audio to multiple pin widgets at once.

Note that this means that a struct hda_pcm_stream's nid variable is
set to 0 except between a stream's open and cleanup calls. The dynamic
de-assignment of converters to PCMs occurs within cleanup, not close,
in order for it to co-incide with when controller stream IDs are
cleaned up from converters.

While the PCM for a pin is not open, the pin is disabled (its widget
control's PIN_OUT bit is cleared) so that if the currently routed
converter is used to drive a different PCM/pin, that audio does not
leak out over a disabled pin.

We use the recently added SPDIF virtualization feature in order to
create SPDIF controls for each pin widget instead of each converter
widget, so that state is specific to a PCM.

In order to support this, a number of more mechanical changes are made:

* s/nid/pin_nid/ or s/nid/cvt_nid/ in many places in order to make it
  clear exactly what the code is dealing with.

* We now have per_pin and per_cvt arrays in hdmi_spec to store relevant
  data. In particular, we store a converter's capabilities in the per_cvt
  entry, rather than relying on a combination of codec_pcm_pars and
  the struct hda_pcm_stream.

* ELD-related workarounds were removed from hdmi_channel_allocation
  into hdmi_instrinsic in order to simplifiy infoframe calculations and
  remove HW dependencies.

* Various functions only apply to a single pin, since there is now
  only 1 pin per PCM. For example, hdmi_setup_infoframe,
  hdmi_setup_stream.

* hdmi_add_pin and hdmi_add_cvt are more oriented at pure codec parsing
  and data retrieval, rather than determining which pins/converters
  are to be used for creating PCMs.

This is quite a large change; it may be appropriate to simply read the
result of the patch rather than the diffs. Some small parts of the change
might be separable into different patches, but I think the bulk of the
change will probably always be one large patch. Hopefully the change
isn't too opaque!

This has been tested on:

* NVIDIA GeForce 400 series discrete graphics card. This model has the
  classical 1:1:1 codec:converter:pcm widget model. Tested stereo PCM
  audio to a PC monitor that supports audio.

* NVIDIA GeForce 520 discrete graphics card. This model is the new
  1 codec n converters m pins m>n model. Tested stereo PCM audio to a
  PC monitor that supports audio.

* NVIDIA GeForce 400 series laptop graphics chip. This model has the
  classical 1:1:1 codec:converter:pcm widget model. Tested stereo PCM,
  multi-channel PCM, and AC3 pass-through to an AV receiver.

* Intel Ibex Peak laptop. This model is the new 1 codec n converters m
  pins m>n model. Tested stereo PCM, multi-channel PCM, and AC3 pass-
  through to an AV receiver.

Note that I'm not familiar at all with AC3 pass-through. Hence, I may
not have covered all possible mechanisms that are applicable here. I do
know that my receiver definitely received AC3, not decoded PCM. I tested
with mplayer's "-afm hwac3" and/or "-af lavcac3enc" options, and alsa a
WAV file that I believe has AC3 content rather than PCM.

I also tested:
* Play a stream
* Mute while playing
* Stop stream
* Play some other streams to re-assign the converter to a different
  pin, PCM, set of SPDIF controls, ... hence hopefully triggering
  cleanup for the original PCM.
* Unmute original stream while not playing
* Play a stream on the original pin/PCM.

This was to test SPDIF control virtualization.

Signed-off-by: Stephen Warren <swarren@nvidia.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/hda_codec.c
sound/pci/hda/hda_codec.h
sound/pci/hda/patch_hdmi.c

index c63a06703de319d43302a77e12e7de9b5944b45b..ce418c805a1a0b0591d92cabdfbe21fbcb401f4e 100644 (file)
@@ -3412,7 +3412,7 @@ static unsigned int query_stream_param(struct hda_codec *codec, hda_nid_t nid)
  *
  * Returns 0 if successful, otherwise a negative error code.
  */
-static int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
+int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
                                u32 *ratesp, u64 *formatsp, unsigned int *bpsp)
 {
        unsigned int i, val, wcaps;
@@ -3504,6 +3504,7 @@ static int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
 
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_query_supported_pcm);
 
 /**
  * snd_hda_is_supported_format - Check the validity of the format
index 96c35cab57bf3de6cfd3241632f9bfc44084eca8..070efac7e20710fb94a2424b277538ff1c04eb2e 100644 (file)
@@ -903,6 +903,8 @@ int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid,
                          hda_nid_t *start_id);
 int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
                            hda_nid_t *conn_list, int max_conns);
+int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
+                               u32 *ratesp, u64 *formatsp, unsigned int *bpsp);
 
 struct hda_verb {
        hda_nid_t nid;
index 338546531c1783bc79d85be18c388dfc0dce7d6b..19cb72db9c38df1261f3ded1fe1c0471bd906aef 100644 (file)
@@ -43,7 +43,7 @@ MODULE_PARM_DESC(static_hdmi_pcm, "Don't restrict PCM parameters per ELD info");
 
 /*
  * The HDMI/DisplayPort configuration can be highly dynamic. A graphics device
- * could support two independent pipes, each of them can be connected to one or
+ * could support N independent pipes, each of them can be connected to one or
  * more ports (DVI, HDMI or DisplayPort).
  *
  * The HDA correspondence of pipes/ports are converter/pin nodes.
@@ -51,30 +51,33 @@ MODULE_PARM_DESC(static_hdmi_pcm, "Don't restrict PCM parameters per ELD info");
 #define MAX_HDMI_CVTS  4
 #define MAX_HDMI_PINS  4
 
-struct hdmi_spec {
-       int num_cvts;
-       int num_pins;
-       hda_nid_t cvt[MAX_HDMI_CVTS+1];  /* audio sources */
-       hda_nid_t pin[MAX_HDMI_PINS+1];  /* audio sinks */
+struct hdmi_spec_per_cvt {
+       hda_nid_t cvt_nid;
+       int assigned;
+       unsigned int channels_min;
+       unsigned int channels_max;
+       u32 rates;
+       u64 formats;
+       unsigned int maxbps;
+};
 
-       /*
-        * source connection for each pin
-        */
-       hda_nid_t pin_cvt[MAX_HDMI_PINS+1];
+struct hdmi_spec_per_pin {
+       hda_nid_t pin_nid;
+       int num_mux_nids;
+       hda_nid_t mux_nids[HDA_MAX_CONNECTIONS];
+       struct hdmi_eld sink_eld;
+};
 
-       /*
-        * HDMI sink attached to each pin
-        */
-       struct hdmi_eld sink_eld[MAX_HDMI_PINS];
+struct hdmi_spec {
+       int num_cvts;
+       struct hdmi_spec_per_cvt cvts[MAX_HDMI_CVTS];
 
-       /*
-        * export one pcm per pipe
-        */
-       struct hda_pcm  pcm_rec[MAX_HDMI_CVTS];
-       struct hda_pcm_stream codec_pcm_pars[MAX_HDMI_CVTS];
+       int num_pins;
+       struct hdmi_spec_per_pin pins[MAX_HDMI_PINS];
+       struct hda_pcm pcm_rec[MAX_HDMI_PINS];
 
        /*
-        * ati/nvhdmi specific
+        * Non-generic ATI/NVIDIA specific
         */
        struct hda_multi_out multiout;
        const struct hda_pcm_stream *pcm_playback;
@@ -284,15 +287,40 @@ static struct cea_channel_speaker_allocation channel_allocations[] = {
  * HDMI routines
  */
 
-static int hda_node_index(hda_nid_t *nids, hda_nid_t nid)
+static int pin_nid_to_pin_index(struct hdmi_spec *spec, hda_nid_t pin_nid)
 {
-       int i;
+       int pin_idx;
 
-       for (i = 0; nids[i]; i++)
-               if (nids[i] == nid)
-                       return i;
+       for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++)
+               if (spec->pins[pin_idx].pin_nid == pin_nid)
+                       return pin_idx;
 
-       snd_printk(KERN_WARNING "HDMI: nid %d not registered\n", nid);
+       snd_printk(KERN_WARNING "HDMI: pin nid %d not registered\n", pin_nid);
+       return -EINVAL;
+}
+
+static int hinfo_to_pin_index(struct hdmi_spec *spec,
+                             struct hda_pcm_stream *hinfo)
+{
+       int pin_idx;
+
+       for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++)
+               if (&spec->pcm_rec[pin_idx].stream[0] == hinfo)
+                       return pin_idx;
+
+       snd_printk(KERN_WARNING "HDMI: hinfo %p not registered\n", hinfo);
+       return -EINVAL;
+}
+
+static int cvt_nid_to_cvt_index(struct hdmi_spec *spec, hda_nid_t cvt_nid)
+{
+       int cvt_idx;
+
+       for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++)
+               if (spec->cvts[cvt_idx].cvt_nid == cvt_nid)
+                       return cvt_idx;
+
+       snd_printk(KERN_WARNING "HDMI: cvt nid %d not registered\n", cvt_nid);
        return -EINVAL;
 }
 
@@ -326,28 +354,28 @@ static void hdmi_write_dip_byte(struct hda_codec *codec, hda_nid_t pin_nid,
        snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_DATA, val);
 }
 
-static void hdmi_enable_output(struct hda_codec *codec, hda_nid_t pin_nid)
+static void hdmi_init_pin(struct hda_codec *codec, hda_nid_t pin_nid)
 {
        /* Unmute */
        if (get_wcaps(codec, pin_nid) & AC_WCAP_OUT_AMP)
                snd_hda_codec_write(codec, pin_nid, 0,
                                AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE);
-       /* Enable pin out */
+       /* Disable pin out until stream is active*/
        snd_hda_codec_write(codec, pin_nid, 0,
-                           AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+                           AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
 }
 
-static int hdmi_get_channel_count(struct hda_codec *codec, hda_nid_t nid)
+static int hdmi_get_channel_count(struct hda_codec *codec, hda_nid_t cvt_nid)
 {
-       return 1 + snd_hda_codec_read(codec, nid, 0,
+       return 1 + snd_hda_codec_read(codec, cvt_nid, 0,
                                        AC_VERB_GET_CVT_CHAN_COUNT, 0);
 }
 
 static void hdmi_set_channel_count(struct hda_codec *codec,
-                                  hda_nid_t nid, int chs)
+                                  hda_nid_t cvt_nid, int chs)
 {
-       if (chs != hdmi_get_channel_count(codec, nid))
-               snd_hda_codec_write(codec, nid, 0,
+       if (chs != hdmi_get_channel_count(codec, cvt_nid))
+               snd_hda_codec_write(codec, cvt_nid, 0,
                                    AC_VERB_SET_CVT_CHAN_COUNT, chs - 1);
 }
 
@@ -384,11 +412,8 @@ static void init_channel_allocations(void)
  *
  * TODO: it could select the wrong CA from multiple candidates.
 */
-static int hdmi_channel_allocation(struct hda_codec *codec, hda_nid_t nid,
-                                  int channels)
+static int hdmi_channel_allocation(struct hdmi_eld *eld, int channels)
 {
-       struct hdmi_spec *spec = codec->spec;
-       struct hdmi_eld *eld;
        int i;
        int ca = 0;
        int spk_mask = 0;
@@ -400,19 +425,6 @@ static int hdmi_channel_allocation(struct hda_codec *codec, hda_nid_t nid,
        if (channels <= 2)
                return 0;
 
-       i = hda_node_index(spec->pin_cvt, nid);
-       if (i < 0)
-               return 0;
-       eld = &spec->sink_eld[i];
-
-       /*
-        * HDMI sink's ELD info cannot always be retrieved for now, e.g.
-        * in console or for audio devices. Assume the highest speakers
-        * configuration, to _not_ prohibit multi-channel audio playback.
-        */
-       if (!eld->spk_alloc)
-               eld->spk_alloc = 0xffff;
-
        /*
         * expand ELD's speaker allocation mask
         *
@@ -608,67 +620,63 @@ static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid,
        return true;
 }
 
-static void hdmi_setup_audio_infoframe(struct hda_codec *codec, hda_nid_t nid,
+static void hdmi_setup_audio_infoframe(struct hda_codec *codec, int pin_idx,
                                        struct snd_pcm_substream *substream)
 {
        struct hdmi_spec *spec = codec->spec;
-       hda_nid_t pin_nid;
+       struct hdmi_spec_per_pin *per_pin = &spec->pins[pin_idx];
+       hda_nid_t pin_nid = per_pin->pin_nid;
        int channels = substream->runtime->channels;
+       struct hdmi_eld *eld;
        int ca;
-       int i;
        union audio_infoframe ai;
 
-       ca = hdmi_channel_allocation(codec, nid, channels);
-
-       for (i = 0; i < spec->num_pins; i++) {
-               if (spec->pin_cvt[i] != nid)
-                       continue;
-               if (!spec->sink_eld[i].monitor_present)
-                       continue;
+       eld = &spec->pins[pin_idx].sink_eld;
+       if (!eld->monitor_present)
+               return;
 
-               pin_nid = spec->pin[i];
-
-               memset(&ai, 0, sizeof(ai));
-               if (spec->sink_eld[i].conn_type == 0) { /* HDMI */
-                       struct hdmi_audio_infoframe *hdmi_ai = &ai.hdmi;
-
-                       hdmi_ai->type           = 0x84;
-                       hdmi_ai->ver            = 0x01;
-                       hdmi_ai->len            = 0x0a;
-                       hdmi_ai->CC02_CT47      = channels - 1;
-                       hdmi_ai->CA             = ca;
-                       hdmi_checksum_audio_infoframe(hdmi_ai);
-               } else if (spec->sink_eld[i].conn_type == 1) { /* DisplayPort */
-                       struct dp_audio_infoframe *dp_ai = &ai.dp;
-
-                       dp_ai->type             = 0x84;
-                       dp_ai->len              = 0x1b;
-                       dp_ai->ver              = 0x11 << 2;
-                       dp_ai->CC02_CT47        = channels - 1;
-                       dp_ai->CA               = ca;
-               } else {
-                       snd_printd("HDMI: unknown connection type at pin %d\n",
-                                  pin_nid);
-                       continue;
-               }
+       ca = hdmi_channel_allocation(eld, channels);
+
+       memset(&ai, 0, sizeof(ai));
+       if (eld->conn_type == 0) { /* HDMI */
+               struct hdmi_audio_infoframe *hdmi_ai = &ai.hdmi;
+
+               hdmi_ai->type           = 0x84;
+               hdmi_ai->ver            = 0x01;
+               hdmi_ai->len            = 0x0a;
+               hdmi_ai->CC02_CT47      = channels - 1;
+               hdmi_ai->CA             = ca;
+               hdmi_checksum_audio_infoframe(hdmi_ai);
+       } else if (eld->conn_type == 1) { /* DisplayPort */
+               struct dp_audio_infoframe *dp_ai = &ai.dp;
+
+               dp_ai->type             = 0x84;
+               dp_ai->len              = 0x1b;
+               dp_ai->ver              = 0x11 << 2;
+               dp_ai->CC02_CT47        = channels - 1;
+               dp_ai->CA               = ca;
+       } else {
+               snd_printd("HDMI: unknown connection type at pin %d\n",
+                           pin_nid);
+               return;
+       }
 
-               /*
-                * sizeof(ai) is used instead of sizeof(*hdmi_ai) or
-                * sizeof(*dp_ai) to avoid partial match/update problems when
-                * the user switches between HDMI/DP monitors.
-                */
-               if (!hdmi_infoframe_uptodate(codec, pin_nid, ai.bytes,
-                                            sizeof(ai))) {
-                       snd_printdd("hdmi_setup_audio_infoframe: "
-                                   "cvt=%d pin=%d channels=%d\n",
-                                   nid, pin_nid,
-                                   channels);
-                       hdmi_setup_channel_mapping(codec, pin_nid, ca);
-                       hdmi_stop_infoframe_trans(codec, pin_nid);
-                       hdmi_fill_audio_infoframe(codec, pin_nid,
-                                                 ai.bytes, sizeof(ai));
-                       hdmi_start_infoframe_trans(codec, pin_nid);
-               }
+       /*
+        * sizeof(ai) is used instead of sizeof(*hdmi_ai) or
+        * sizeof(*dp_ai) to avoid partial match/update problems when
+        * the user switches between HDMI/DP monitors.
+        */
+       if (!hdmi_infoframe_uptodate(codec, pin_nid, ai.bytes,
+                                       sizeof(ai))) {
+               snd_printdd("hdmi_setup_audio_infoframe: "
+                           "pin=%d channels=%d\n",
+                           pin_nid,
+                           channels);
+               hdmi_setup_channel_mapping(codec, pin_nid, ca);
+               hdmi_stop_infoframe_trans(codec, pin_nid);
+               hdmi_fill_audio_infoframe(codec, pin_nid,
+                                           ai.bytes, sizeof(ai));
+               hdmi_start_infoframe_trans(codec, pin_nid);
        }
 }
 
@@ -686,17 +694,27 @@ static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res)
        int pin_nid = res >> AC_UNSOL_RES_TAG_SHIFT;
        int pd = !!(res & AC_UNSOL_RES_PD);
        int eldv = !!(res & AC_UNSOL_RES_ELDV);
-       int index;
+       int pin_idx;
+       struct hdmi_eld *eld;
 
        printk(KERN_INFO
-               "HDMI hot plug event: Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
-               pin_nid, pd, eldv);
+               "HDMI hot plug event: Codec=%d Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
+               codec->addr, pin_nid, pd, eldv);
 
-       index = hda_node_index(spec->pin, pin_nid);
-       if (index < 0)
+       pin_idx = pin_nid_to_pin_index(spec, pin_nid);
+       if (pin_idx < 0)
                return;
+       eld = &spec->pins[pin_idx].sink_eld;
 
-       hdmi_present_sense(codec, pin_nid, &spec->sink_eld[index]);
+       hdmi_present_sense(codec, pin_nid, eld);
+
+       /*
+        * HDMI sink's ELD info cannot always be retrieved for now, e.g.
+        * in console or for audio devices. Assume the highest speakers
+        * configuration, to _not_ prohibit multi-channel audio playback.
+        */
+       if (!eld->spk_alloc)
+               eld->spk_alloc = 0xffff;
 }
 
 static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res)
@@ -707,7 +725,8 @@ static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res)
        int cp_ready = !!(res & AC_UNSOL_RES_CP_READY);
 
        printk(KERN_INFO
-               "HDMI CP event: PIN=%d SUBTAG=0x%x CP_STATE=%d CP_READY=%d\n",
+               "HDMI CP event: CODEC=%d PIN=%d SUBTAG=0x%x CP_STATE=%d CP_READY=%d\n",
+               codec->addr,
                tag,
                subtag,
                cp_state,
@@ -727,7 +746,7 @@ static void hdmi_unsol_event(struct hda_codec *codec, unsigned int res)
        int tag = res >> AC_UNSOL_RES_TAG_SHIFT;
        int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT;
 
-       if (hda_node_index(spec->pin, tag) < 0) {
+       if (pin_nid_to_pin_index(spec, tag) < 0) {
                snd_printd(KERN_INFO "Unexpected HDMI event tag 0x%x\n", tag);
                return;
        }
@@ -746,21 +765,14 @@ static void hdmi_unsol_event(struct hda_codec *codec, unsigned int res)
 #define is_hbr_format(format) \
        ((format & AC_FMT_TYPE_NON_PCM) && (format & AC_FMT_CHAN_MASK) == 7)
 
-static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid,
-                             u32 stream_tag, int format)
+static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
+                             hda_nid_t pin_nid, u32 stream_tag, int format)
 {
-       struct hdmi_spec *spec = codec->spec;
        int pinctl;
        int new_pinctl = 0;
-       int i;
 
-       for (i = 0; i < spec->num_pins; i++) {
-               if (spec->pin_cvt[i] != nid)
-                       continue;
-               if (!(snd_hda_query_pin_caps(codec, spec->pin[i]) & AC_PINCAP_HBR))
-                       continue;
-
-               pinctl = snd_hda_codec_read(codec, spec->pin[i], 0,
+       if (snd_hda_query_pin_caps(codec, pin_nid) & AC_PINCAP_HBR) {
+               pinctl = snd_hda_codec_read(codec, pin_nid, 0,
                                            AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
 
                new_pinctl = pinctl & ~AC_PINCTL_EPT;
@@ -771,22 +783,22 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid,
 
                snd_printdd("hdmi_setup_stream: "
                            "NID=0x%x, %spinctl=0x%x\n",
-                           spec->pin[i],
+                           pin_nid,
                            pinctl == new_pinctl ? "" : "new-",
                            new_pinctl);
 
                if (pinctl != new_pinctl)
-                       snd_hda_codec_write(codec, spec->pin[i], 0,
+                       snd_hda_codec_write(codec, pin_nid, 0,
                                            AC_VERB_SET_PIN_WIDGET_CONTROL,
                                            new_pinctl);
-       }
 
+       }
        if (is_hbr_format(format) && !new_pinctl) {
                snd_printdd("hdmi_setup_stream: HBR is not supported\n");
                return -EINVAL;
        }
 
-       snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format);
+       snd_hda_codec_setup_stream(codec, cvt_nid, stream_tag, 0, format);
        return 0;
 }
 
@@ -798,31 +810,62 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
                         struct snd_pcm_substream *substream)
 {
        struct hdmi_spec *spec = codec->spec;
-       struct hdmi_eld *eld;
-       struct hda_pcm_stream *codec_pars;
        struct snd_pcm_runtime *runtime = substream->runtime;
-       unsigned int idx;
+       int pin_idx, cvt_idx, mux_idx = 0;
+       struct hdmi_spec_per_pin *per_pin;
+       struct hdmi_eld *eld;
+       struct hdmi_spec_per_cvt *per_cvt = NULL;
+       int pinctl;
 
-       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))
+       /* Validate hinfo */
+       pin_idx = hinfo_to_pin_index(spec, hinfo);
+       if (snd_BUG_ON(pin_idx < 0))
                return -EINVAL;
+       per_pin = &spec->pins[pin_idx];
+       eld = &per_pin->sink_eld;
+
+       /* Dynamically assign converter to stream */
+       for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
+               per_cvt = &spec->cvts[cvt_idx];
 
-       /* save the PCM info the codec provides */
-       codec_pars = &spec->codec_pcm_pars[idx];
-       if (!codec_pars->rates)
-               *codec_pars = *hinfo;
+               /* Must not already be assigned */
+               if (per_cvt->assigned)
+                       continue;
+               /* Must be in pin's mux's list of converters */
+               for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++)
+                       if (per_pin->mux_nids[mux_idx] == per_cvt->cvt_nid)
+                               break;
+               /* Not in mux list */
+               if (mux_idx == per_pin->num_mux_nids)
+                       continue;
+               break;
+       }
+       /* No free converters */
+       if (cvt_idx == spec->num_cvts)
+               return -ENODEV;
+
+       /* Claim converter */
+       per_cvt->assigned = 1;
+       hinfo->nid = per_cvt->cvt_nid;
+
+       snd_hda_codec_write(codec, per_pin->pin_nid, 0,
+                           AC_VERB_SET_CONNECT_SEL,
+                           mux_idx);
+       pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0,
+                                   AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+       snd_hda_codec_write(codec, per_pin->pin_nid, 0,
+                           AC_VERB_SET_PIN_WIDGET_CONTROL,
+                           pinctl | PIN_OUT);
+       snd_hda_spdif_ctls_assign(codec, pin_idx, per_cvt->cvt_nid);
 
        /* Initially set the converter's capabilities */
-       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;
+       hinfo->channels_min = per_cvt->channels_min;
+       hinfo->channels_max = per_cvt->channels_max;
+       hinfo->rates = per_cvt->rates;
+       hinfo->formats = per_cvt->formats;
+       hinfo->maxbps = per_cvt->maxbps;
 
-       eld = &spec->sink_eld[idx];
+       /* Restrict capabilities by ELD if this isn't disabled */
        if (!static_hdmi_pcm && eld->eld_valid) {
                snd_hdmi_eld_update_pcm_info(eld, hinfo);
                if (hinfo->channels_min > hinfo->channels_max ||
@@ -844,12 +887,11 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
 /*
  * HDA/HDMI auto parsing
  */
-static int hdmi_read_pin_conn(struct hda_codec *codec, hda_nid_t pin_nid)
+static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
 {
        struct hdmi_spec *spec = codec->spec;
-       hda_nid_t conn_list[HDA_MAX_CONNECTIONS];
-       int conn_len, curr;
-       int index;
+       struct hdmi_spec_per_pin *per_pin = &spec->pins[pin_idx];
+       hda_nid_t pin_nid = per_pin->pin_nid;
 
        if (!(get_wcaps(codec, pin_nid) & AC_WCAP_CONN_LIST)) {
                snd_printk(KERN_WARNING
@@ -859,19 +901,9 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, hda_nid_t pin_nid)
                return -EINVAL;
        }
 
-       conn_len = snd_hda_get_connections(codec, pin_nid, conn_list,
-                                          HDA_MAX_CONNECTIONS);
-       if (conn_len > 1)
-               curr = snd_hda_codec_read(codec, pin_nid, 0,
-                                         AC_VERB_GET_CONNECT_SEL, 0);
-       else
-               curr = 0;
-
-       index = hda_node_index(spec->pin, pin_nid);
-       if (index < 0)
-               return -EINVAL;
-
-       spec->pin_cvt[index] = conn_list[curr];
+       per_pin->num_mux_nids = snd_hda_get_connections(codec, pin_nid,
+                                                       per_pin->mux_nids,
+                                                       HDA_MAX_CONNECTIONS);
 
        return 0;
 }
@@ -898,8 +930,8 @@ static void hdmi_present_sense(struct hda_codec *codec, hda_nid_t pin_nid,
                eld->eld_valid  = 0;
 
        printk(KERN_INFO
-               "HDMI status: Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
-               pin_nid, eld->monitor_present, eld->eld_valid);
+               "HDMI status: Codec=%d Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
+               codec->addr, pin_nid, eld->monitor_present, eld->eld_valid);
 
        if (eld->eld_valid)
                if (!snd_hdmi_get_eld(eld, codec, pin_nid))
@@ -911,47 +943,75 @@ static void hdmi_present_sense(struct hda_codec *codec, hda_nid_t pin_nid,
 static int hdmi_add_pin(struct hda_codec *codec, hda_nid_t pin_nid)
 {
        struct hdmi_spec *spec = codec->spec;
+       unsigned int caps, config;
+       int pin_idx;
+       struct hdmi_spec_per_pin *per_pin;
+       struct hdmi_eld *eld;
        int err;
 
-       if (spec->num_pins >= MAX_HDMI_PINS) {
-               snd_printk(KERN_WARNING
-                          "HDMI: no space for pin %d\n", pin_nid);
+       caps = snd_hda_param_read(codec, pin_nid, AC_PAR_PIN_CAP);
+       if (!(caps & (AC_PINCAP_HDMI | AC_PINCAP_DP)))
+               return 0;
+
+       config = snd_hda_codec_read(codec, pin_nid, 0,
+                               AC_VERB_GET_CONFIG_DEFAULT, 0);
+       if (get_defcfg_connect(config) == AC_JACK_PORT_NONE)
+               return 0;
+
+       if (snd_BUG_ON(spec->num_pins >= MAX_HDMI_PINS))
                return -E2BIG;
-       }
+
+       pin_idx = spec->num_pins;
+       per_pin = &spec->pins[pin_idx];
+       eld = &per_pin->sink_eld;
+
+       per_pin->pin_nid = pin_nid;
 
        err = snd_hda_input_jack_add(codec, pin_nid,
                                     SND_JACK_VIDEOOUT, NULL);
        if (err < 0)
                return err;
 
-       hdmi_present_sense(codec, pin_nid, &spec->sink_eld[spec->num_pins]);
+       err = hdmi_read_pin_conn(codec, pin_idx);
+       if (err < 0)
+               return err;
 
-       spec->pin[spec->num_pins] = pin_nid;
        spec->num_pins++;
 
-       return hdmi_read_pin_conn(codec, pin_nid);
+       hdmi_present_sense(codec, pin_nid, eld);
+
+       return 0;
 }
 
-static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t nid)
+static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t cvt_nid)
 {
-       int i, found_pin = 0;
        struct hdmi_spec *spec = codec->spec;
-
-       for (i = 0; i < spec->num_pins; i++)
-               if (nid == spec->pin_cvt[i]) {
-                       found_pin = 1;
-                       break;
-               }
-
-       if (!found_pin) {
-               snd_printdd("HDMI: Skipping node %d (no connection)\n", nid);
-               return -EINVAL;
-       }
+       int cvt_idx;
+       struct hdmi_spec_per_cvt *per_cvt;
+       unsigned int chans;
+       int err;
 
        if (snd_BUG_ON(spec->num_cvts >= MAX_HDMI_CVTS))
                return -E2BIG;
 
-       spec->cvt[spec->num_cvts] = nid;
+       chans = get_wcaps(codec, cvt_nid);
+       chans = get_wcaps_channels(chans);
+
+       cvt_idx = spec->num_cvts;
+       per_cvt = &spec->cvts[cvt_idx];
+
+       per_cvt->cvt_nid = cvt_nid;
+       per_cvt->channels_min = 2;
+       if (chans <= 16)
+               per_cvt->channels_max = chans;
+
+       err = snd_hda_query_supported_pcm(codec, cvt_nid,
+                                         &per_cvt->rates,
+                                         &per_cvt->formats,
+                                         &per_cvt->maxbps);
+       if (err < 0)
+               return err;
+
        spec->num_cvts++;
 
        return 0;
@@ -961,8 +1021,6 @@ static int hdmi_parse_codec(struct hda_codec *codec)
 {
        hda_nid_t nid;
        int i, nodes;
-       int num_tmp_cvts = 0;
-       hda_nid_t tmp_cvt[MAX_HDMI_CVTS];
 
        nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
        if (!nid || nodes < 0) {
@@ -973,7 +1031,6 @@ static int hdmi_parse_codec(struct hda_codec *codec)
        for (i = 0; i < nodes; i++, nid++) {
                unsigned int caps;
                unsigned int type;
-               unsigned int config;
 
                caps = snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
                type = get_wcaps_type(caps);
@@ -983,32 +1040,14 @@ static int hdmi_parse_codec(struct hda_codec *codec)
 
                switch (type) {
                case AC_WID_AUD_OUT:
-                       if (num_tmp_cvts >= MAX_HDMI_CVTS) {
-                               snd_printk(KERN_WARNING
-                                          "HDMI: no space for converter %d\n", nid);
-                               continue;
-                       }
-                       tmp_cvt[num_tmp_cvts] = nid;
-                       num_tmp_cvts++;
+                       hdmi_add_cvt(codec, nid);
                        break;
                case AC_WID_PIN:
-                       caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
-                       if (!(caps & (AC_PINCAP_HDMI | AC_PINCAP_DP)))
-                               continue;
-
-                       config = snd_hda_codec_read(codec, nid, 0,
-                                            AC_VERB_GET_CONFIG_DEFAULT, 0);
-                       if (get_defcfg_connect(config) == AC_JACK_PORT_NONE)
-                               continue;
-
                        hdmi_add_pin(codec, nid);
                        break;
                }
        }
 
-       for (i = 0; i < num_tmp_cvts; i++)
-               hdmi_add_cvt(codec, tmp_cvt[i]);
-
        /*
         * G45/IbexPeak don't support EPSS: the unsolicited pin hot plug event
         * can be lost and presence sense verb will become inaccurate if the
@@ -1025,7 +1064,7 @@ static int hdmi_parse_codec(struct hda_codec *codec)
 
 /*
  */
-static char *generic_hdmi_pcm_names[MAX_HDMI_CVTS] = {
+static char *generic_hdmi_pcm_names[MAX_HDMI_PINS] = {
        "HDMI 0",
        "HDMI 1",
        "HDMI 2",
@@ -1042,51 +1081,84 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
                                           unsigned int format,
                                           struct snd_pcm_substream *substream)
 {
-       hdmi_set_channel_count(codec, hinfo->nid,
-                              substream->runtime->channels);
+       hda_nid_t cvt_nid = hinfo->nid;
+       struct hdmi_spec *spec = codec->spec;
+       int pin_idx = hinfo_to_pin_index(spec, hinfo);
+       hda_nid_t pin_nid = spec->pins[pin_idx].pin_nid;
+
+       hdmi_set_channel_count(codec, cvt_nid, substream->runtime->channels);
 
-       hdmi_setup_audio_infoframe(codec, hinfo->nid, substream);
+       hdmi_setup_audio_infoframe(codec, pin_idx, substream);
 
-       return hdmi_setup_stream(codec, hinfo->nid, stream_tag, format);
+       return hdmi_setup_stream(codec, cvt_nid, pin_nid, stream_tag, format);
 }
 
-static const struct hda_pcm_stream generic_hdmi_pcm_playback = {
-       .substreams = 1,
-       .channels_min = 2,
-       .ops = {
-               .open = hdmi_pcm_open,
-               .prepare = generic_hdmi_playback_pcm_prepare,
-       },
+static int generic_hdmi_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+                                            struct hda_codec *codec,
+                                            struct snd_pcm_substream *substream)
+{
+       struct hdmi_spec *spec = codec->spec;
+       int cvt_idx, pin_idx;
+       struct hdmi_spec_per_cvt *per_cvt;
+       struct hdmi_spec_per_pin *per_pin;
+       int pinctl;
+
+       snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+
+       if (hinfo->nid) {
+               cvt_idx = cvt_nid_to_cvt_index(spec, hinfo->nid);
+               if (snd_BUG_ON(cvt_idx < 0))
+                       return -EINVAL;
+               per_cvt = &spec->cvts[cvt_idx];
+
+               snd_BUG_ON(!per_cvt->assigned);
+               per_cvt->assigned = 0;
+               hinfo->nid = 0;
+
+               pin_idx = hinfo_to_pin_index(spec, hinfo);
+               if (snd_BUG_ON(pin_idx < 0))
+                       return -EINVAL;
+               per_pin = &spec->pins[pin_idx];
+
+               pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0,
+                                           AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+               snd_hda_codec_write(codec, per_pin->pin_nid, 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL,
+                                   pinctl & ~PIN_OUT);
+               snd_hda_spdif_ctls_unassign(codec, pin_idx);
+       }
+
+       return 0;
+}
+
+static const struct hda_pcm_ops generic_ops = {
+       .open = hdmi_pcm_open,
+       .prepare = generic_hdmi_playback_pcm_prepare,
+       .cleanup = generic_hdmi_playback_pcm_cleanup,
 };
 
 static int generic_hdmi_build_pcms(struct hda_codec *codec)
 {
        struct hdmi_spec *spec = codec->spec;
-       struct hda_pcm *info = spec->pcm_rec;
-       int i;
+       int pin_idx;
 
-       codec->num_pcms = spec->num_cvts;
-       codec->pcm_info = info;
-
-       for (i = 0; i < codec->num_pcms; i++, info++) {
-               unsigned int chans;
+       for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+               struct hda_pcm *info;
                struct hda_pcm_stream *pstr;
 
-               chans = get_wcaps(codec, spec->cvt[i]);
-               chans = get_wcaps_channels(chans);
-
-               info->name = generic_hdmi_pcm_names[i];
+               info = &spec->pcm_rec[pin_idx];
+               info->name = generic_hdmi_pcm_names[pin_idx];
                info->pcm_type = HDA_PCM_TYPE_HDMI;
+
                pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK];
-               if (spec->pcm_playback)
-                       *pstr = *spec->pcm_playback;
-               else
-                       *pstr = generic_hdmi_pcm_playback;
-               pstr->nid = spec->cvt[i];
-               if (pstr->channels_max <= 2 && chans && chans <= 16)
-                       pstr->channels_max = chans;
+               pstr->substreams = 1;
+               pstr->ops = generic_ops;
+               /* other pstr fields are set in open */
        }
 
+       codec->num_pcms = spec->num_pins;
+       codec->pcm_info = spec->pcm_rec;
+
        return 0;
 }
 
@@ -1094,13 +1166,16 @@ static int generic_hdmi_build_controls(struct hda_codec *codec)
 {
        struct hdmi_spec *spec = codec->spec;
        int err;
-       int i;
+       int pin_idx;
 
-       for (i = 0; i < codec->num_pcms; i++) {
-               err = snd_hda_create_spdif_out_ctls(codec, spec->cvt[i],
-                                                   spec->cvt[i]);
+       for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+               struct hdmi_spec_per_pin *per_pin = &spec->pins[pin_idx];
+               err = snd_hda_create_spdif_out_ctls(codec,
+                                                   per_pin->pin_nid,
+                                                   per_pin->mux_nids[0]);
                if (err < 0)
                        return err;
+               snd_hda_spdif_ctls_unassign(codec, pin_idx);
        }
 
        return 0;
@@ -1109,13 +1184,19 @@ static int generic_hdmi_build_controls(struct hda_codec *codec)
 static int generic_hdmi_init(struct hda_codec *codec)
 {
        struct hdmi_spec *spec = codec->spec;
-       int i;
+       int pin_idx;
+
+       for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+               struct hdmi_spec_per_pin *per_pin = &spec->pins[pin_idx];
+               hda_nid_t pin_nid = per_pin->pin_nid;
+               struct hdmi_eld *eld = &per_pin->sink_eld;
 
-       for (i = 0; spec->pin[i]; i++) {
-               hdmi_enable_output(codec, spec->pin[i]);
-               snd_hda_codec_write(codec, spec->pin[i], 0,
+               hdmi_init_pin(codec, pin_nid);
+               snd_hda_codec_write(codec, pin_nid, 0,
                                    AC_VERB_SET_UNSOLICITED_ENABLE,
-                                   AC_USRSP_EN | spec->pin[i]);
+                                   AC_USRSP_EN | pin_nid);
+
+               snd_hda_eld_proc_new(codec, eld, pin_idx);
        }
        return 0;
 }
@@ -1123,10 +1204,14 @@ static int generic_hdmi_init(struct hda_codec *codec)
 static void generic_hdmi_free(struct hda_codec *codec)
 {
        struct hdmi_spec *spec = codec->spec;
-       int i;
+       int pin_idx;
+
+       for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+               struct hdmi_spec_per_pin *per_pin = &spec->pins[pin_idx];
+               struct hdmi_eld *eld = &per_pin->sink_eld;
 
-       for (i = 0; i < spec->num_pins; i++)
-               snd_hda_eld_proc_free(codec, &spec->sink_eld[i]);
+               snd_hda_eld_proc_free(codec, eld);
+       }
        snd_hda_input_jack_free(codec);
 
        kfree(spec);
@@ -1143,7 +1228,6 @@ static const struct hda_codec_ops generic_hdmi_patch_ops = {
 static int patch_generic_hdmi(struct hda_codec *codec)
 {
        struct hdmi_spec *spec;
-       int i;
 
        spec = kzalloc(sizeof(*spec), GFP_KERNEL);
        if (spec == NULL)
@@ -1157,9 +1241,6 @@ static int patch_generic_hdmi(struct hda_codec *codec)
        }
        codec->patch_ops = generic_hdmi_patch_ops;
 
-       for (i = 0; i < spec->num_pins; i++)
-               snd_hda_eld_proc_new(codec, &spec->sink_eld[i], i);
-
        init_channel_allocations();
 
        return 0;
@@ -1182,7 +1263,7 @@ static int simple_playback_build_pcms(struct hda_codec *codec)
                unsigned int chans;
                struct hda_pcm_stream *pstr;
 
-               chans = get_wcaps(codec, spec->cvt[i]);
+               chans = get_wcaps(codec, spec->cvts[i].cvt_nid);
                chans = get_wcaps_channels(chans);
 
                info->name = generic_hdmi_pcm_names[i];
@@ -1190,7 +1271,7 @@ static int simple_playback_build_pcms(struct hda_codec *codec)
                pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK];
                snd_BUG_ON(!spec->pcm_playback);
                *pstr = *spec->pcm_playback;
-               pstr->nid = spec->cvt[i];
+               pstr->nid = spec->cvts[i].cvt_nid;
                if (pstr->channels_max <= 2 && chans && chans <= 16)
                        pstr->channels_max = chans;
        }
@@ -1206,8 +1287,8 @@ static int simple_playback_build_controls(struct hda_codec *codec)
 
        for (i = 0; i < codec->num_pcms; i++) {
                err = snd_hda_create_spdif_out_ctls(codec,
-                                                   spec->cvt[i],
-                                                   spec->cvt[i]);
+                                                   spec->cvts[i].cvt_nid,
+                                                   spec->cvts[i].cvt_nid);
                if (err < 0)
                        return err;
        }
@@ -1414,7 +1495,7 @@ static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
        int i;
        struct hdmi_spec *spec = codec->spec;
        struct hda_spdif_out *spdif =
-               snd_hda_spdif_out_of_nid(codec, spec->cvt[0]);
+               snd_hda_spdif_out_of_nid(codec, spec->cvts[0].cvt_nid);
 
        mutex_lock(&codec->spdif_mutex);
 
@@ -1561,7 +1642,7 @@ static int patch_nvhdmi_2ch(struct hda_codec *codec)
        spec->multiout.max_channels = 2;
        spec->multiout.dig_out_nid = nvhdmi_master_con_nid_7x;
        spec->num_cvts = 1;
-       spec->cvt[0] = nvhdmi_master_con_nid_7x;
+       spec->cvts[0].cvt_nid = nvhdmi_master_con_nid_7x;
        spec->pcm_playback = &nvhdmi_pcm_playback_2ch;
 
        codec->patch_ops = nvhdmi_patch_ops_2ch;
@@ -1612,11 +1693,11 @@ static int atihdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
                                          substream);
        if (err < 0)
                return err;
-       snd_hda_codec_write(codec, spec->cvt[0], 0, AC_VERB_SET_CVT_CHAN_COUNT,
-                           chans - 1);
+       snd_hda_codec_write(codec, spec->cvts[0].cvt_nid, 0,
+                           AC_VERB_SET_CVT_CHAN_COUNT, chans - 1);
        /* FIXME: XXX */
        for (i = 0; i < chans; i++) {
-               snd_hda_codec_write(codec, spec->cvt[0], 0,
+               snd_hda_codec_write(codec, spec->cvts[0].cvt_nid, 0,
                                    AC_VERB_SET_HDMI_CHAN_SLOT,
                                    (i << 4) | i);
        }
@@ -1647,8 +1728,8 @@ static int atihdmi_init(struct hda_codec *codec)
 
        snd_hda_sequence_write(codec, atihdmi_basic_init);
        /* SI codec requires to unmute the pin */
-       if (get_wcaps(codec, spec->pin[0]) & AC_WCAP_OUT_AMP)
-               snd_hda_codec_write(codec, spec->pin[0], 0,
+       if (get_wcaps(codec, spec->pins[0].pin_nid) & AC_WCAP_OUT_AMP)
+               snd_hda_codec_write(codec, spec->pins[0].pin_nid, 0,
                                    AC_VERB_SET_AMP_GAIN_MUTE,
                                    AMP_OUT_UNMUTE);
        return 0;
@@ -1676,8 +1757,8 @@ static int patch_atihdmi(struct hda_codec *codec)
        spec->multiout.max_channels = 2;
        spec->multiout.dig_out_nid = ATIHDMI_CVT_NID;
        spec->num_cvts = 1;
-       spec->cvt[0] = ATIHDMI_CVT_NID;
-       spec->pin[0] = ATIHDMI_PIN_NID;
+       spec->cvts[0].cvt_nid = ATIHDMI_CVT_NID;
+       spec->pins[0].pin_nid = ATIHDMI_PIN_NID;
        spec->pcm_playback = &atihdmi_pcm_digital_playback;
 
        codec->patch_ops = atihdmi_patch_ops;