/*
  * 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.
 #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;
  * 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;
 }
 
        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);
 }
 
  *
  * 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;
        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
         *
        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);
        }
 }
 
        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)
        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,
        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;
        }
 #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;
 
                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;
 }
 
                         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 ||
 /*
  * 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
                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;
 }
                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))
 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;
 {
        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) {
        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);
 
                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
 
 /*
  */
-static char *generic_hdmi_pcm_names[MAX_HDMI_CVTS] = {
+static char *generic_hdmi_pcm_names[MAX_HDMI_PINS] = {
        "HDMI 0",
        "HDMI 1",
        "HDMI 2",
                                           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;
 }
 
 {
        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;
 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;
 }
 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);
 static int patch_generic_hdmi(struct hda_codec *codec)
 {
        struct hdmi_spec *spec;
-       int i;
 
        spec = kzalloc(sizeof(*spec), GFP_KERNEL);
        if (spec == NULL)
        }
        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;
                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];
                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;
        }
 
        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;
        }
        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);
 
        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;
                                          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);
        }
 
        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;
        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;