]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - sound/pci/hda/patch_hdmi.c
ALSA: hda - hide HDMI/ELD printks unless snd.debug=2
[linux-2.6.git] / sound / pci / hda / patch_hdmi.c
index a5876773672749d05ac9672b9ba59b8acb0fc903..3e35734f1d7a7950275462dbd4b22b4bc7402847 100644 (file)
 #include <linux/init.h>
 #include <linux/delay.h>
 #include <linux/slab.h>
-#include <linux/moduleparam.h>
+#include <linux/module.h>
 #include <sound/core.h>
+#include <sound/jack.h>
+
+#ifdef CONFIG_SND_HDA_PLATFORM_NVIDIA_TEGRA
+#include <mach/hdmi-audio.h>
+#endif
+
 #include "hda_codec.h"
 #include "hda_local.h"
+#include "hda_jack.h"
 
 static bool static_hdmi_pcm;
 module_param(static_hdmi_pcm, bool, 0644);
@@ -42,45 +49,48 @@ 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.
  */
-#define MAX_HDMI_CVTS  3
-#define MAX_HDMI_PINS  3
+#define MAX_HDMI_CVTS  8
+#define MAX_HDMI_PINS  8
+
+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;
+};
 
-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_pin {
+       hda_nid_t pin_nid;
+       int num_mux_nids;
+       hda_nid_t mux_nids[HDA_MAX_CONNECTIONS];
 
-       /*
-        * source connection for each pin
-        */
-       hda_nid_t pin_cvt[MAX_HDMI_PINS+1];
+       struct hda_codec *codec;
+       struct hdmi_eld sink_eld;
+       struct delayed_work work;
+       int repoll_count;
+};
 
-       /*
-        * 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;
-       struct hda_pcm_stream *pcm_playback;
-
-       /* misc flags */
-       /* PD bit indicates only the update, not the current state */
-       unsigned int old_pin_detect:1;
+       const struct hda_pcm_stream *pcm_playback;
 };
 
 
@@ -110,6 +120,12 @@ struct dp_audio_infoframe {
        u8 LFEPBL01_LSV36_DM_INH7;
 };
 
+union audio_infoframe {
+       struct hdmi_audio_infoframe hdmi;
+       struct dp_audio_infoframe dp;
+       u8 bytes[0];
+};
+
 /*
  * CEA speaker placement:
  *
@@ -281,23 +297,101 @@ 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 void hdmi_get_show_eld(struct hda_codec *codec, hda_nid_t pin_nid,
-                             struct hdmi_eld *eld)
+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;
+}
+
+static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_info *uinfo)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct hdmi_spec *spec;
+       int pin_idx;
+
+       spec = codec->spec;
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+
+       pin_idx = kcontrol->private_value;
+       uinfo->count = spec->pins[pin_idx].sink_eld.eld_size;
+
+       return 0;
+}
+
+static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_value *ucontrol)
 {
-       if (!snd_hdmi_get_eld(eld, codec, pin_nid))
-               snd_hdmi_show_eld(eld);
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct hdmi_spec *spec;
+       int pin_idx;
+
+       spec = codec->spec;
+       pin_idx = kcontrol->private_value;
+
+       memcpy(ucontrol->value.bytes.data,
+               spec->pins[pin_idx].sink_eld.eld_buffer, ELD_MAX_SIZE);
+
+       return 0;
+}
+
+static struct snd_kcontrol_new eld_bytes_ctl = {
+       .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+       .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+       .name = "ELD",
+       .info = hdmi_eld_ctl_info,
+       .get = hdmi_eld_ctl_get,
+};
+
+static int hdmi_create_eld_ctl(struct hda_codec *codec, int pin_idx,
+                       int device)
+{
+       struct snd_kcontrol *kctl;
+       struct hdmi_spec *spec = codec->spec;
+       int err;
+
+       kctl = snd_ctl_new1(&eld_bytes_ctl, codec);
+       if (!kctl)
+               return -ENOMEM;
+       kctl->private_value = pin_idx;
+       kctl->id.device = device;
+
+       err = snd_hda_ctl_add(codec, spec->pins[pin_idx].pin_nid, kctl);
+       if (err < 0)
+               return err;
+
+       return 0;
 }
 
 #ifdef BE_PARANOID
@@ -330,28 +424,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);
 }
 
@@ -388,11 +482,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;
@@ -404,19 +495,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
         *
@@ -612,69 +690,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;
-       u8 ai[max(sizeof(struct hdmi_audio_infoframe),
-                 sizeof(struct dp_audio_infoframe))];
-
-       ca = hdmi_channel_allocation(codec, nid, channels);
+       union audio_infoframe ai;
 
-       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;
-
-                       hdmi_ai = (struct hdmi_audio_infoframe *)ai;
-                       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;
-
-                       dp_ai = (struct dp_audio_infoframe *)ai;
-                       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, 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, 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);
        }
 }
 
@@ -683,39 +755,33 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, hda_nid_t nid,
  * Unsolicited events
  */
 
-static void hdmi_present_sense(struct hda_codec *codec, hda_nid_t pin_nid,
-                              struct hdmi_eld *eld);
+static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll);
 
 static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res)
 {
        struct hdmi_spec *spec = codec->spec;
        int tag = res >> AC_UNSOL_RES_TAG_SHIFT;
-       int pind = !!(res & AC_UNSOL_RES_PD);
-       int eldv = !!(res & AC_UNSOL_RES_ELDV);
-       int index;
-
-       printk(KERN_INFO
-               "HDMI hot plug event: Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
-               tag, pind, eldv);
+       int pin_nid;
+       int pin_idx;
+       struct hda_jack_tbl *jack;
 
-       index = hda_node_index(spec->pin, tag);
-       if (index < 0)
+       jack = snd_hda_jack_tbl_get_from_tag(codec, tag);
+       if (!jack)
                return;
+       pin_nid = jack->nid;
+       jack->jack_dirty = 1;
 
-       if (spec->old_pin_detect) {
-               if (pind)
-                       hdmi_present_sense(codec, tag, &spec->sink_eld[index]);
-               pind = spec->sink_eld[index].monitor_present;
-       }
+       _snd_printd(SND_PR_VERBOSE,
+               "HDMI hot plug event: Codec=%d Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
+               codec->addr, pin_nid,
+               !!(res & AC_UNSOL_RES_PD), !!(res & AC_UNSOL_RES_ELDV));
 
-       spec->sink_eld[index].monitor_present = pind;
-       spec->sink_eld[index].eld_valid = eldv;
+       pin_idx = pin_nid_to_pin_index(spec, pin_nid);
+       if (pin_idx < 0)
+               return;
 
-       if (pind && eldv) {
-               hdmi_get_show_eld(codec, spec->pin[index],
-                                 &spec->sink_eld[index]);
-               /* TODO: do real things about ELD */
-       }
+       hdmi_present_sense(&spec->pins[pin_idx], 1);
+       snd_hda_jack_report_sync(codec);
 }
 
 static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res)
@@ -726,7 +792,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,
@@ -742,11 +809,10 @@ static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res)
 
 static void hdmi_unsol_event(struct hda_codec *codec, unsigned int res)
 {
-       struct hdmi_spec *spec = codec->spec;
        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 (!snd_hda_jack_tbl_get_from_tag(codec, tag)) {
                snd_printd(KERN_INFO "Unexpected HDMI event tag 0x%x\n", tag);
                return;
        }
@@ -765,21 +831,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;
@@ -790,22 +849,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;
 }
 
@@ -817,37 +876,85 @@ 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;
+
+#ifdef CONFIG_SND_HDA_PLATFORM_NVIDIA_TEGRA
+       if ((codec->preset->id == 0x10de0020) &&
+           (!eld->monitor_present || !eld->lpcm_sad_ready)) {
+               if (!eld->monitor_present) {
+                       if (tegra_hdmi_setup_hda_presence() < 0) {
+                               snd_printk(KERN_WARNING
+                                          "HDMI: No HDMI device connected\n");
+                               return -ENODEV;
+                       }
+               }
+               if (!eld->lpcm_sad_ready)
+                       return -ENODEV;
+       }
+#endif
 
-       /* save the PCM info the codec provides */
-       codec_pars = &spec->codec_pcm_pars[idx];
-       if (!codec_pars->rates)
-               *codec_pars = *hinfo;
+       /* Dynamically assign converter to stream */
+       for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
+               per_cvt = &spec->cvts[cvt_idx];
 
-       eld = &spec->sink_eld[idx];
-       if (!static_hdmi_pcm && eld->eld_valid && eld->sad_count > 0) {
-               hdmi_eld_update_pcm_info(eld, hinfo, codec_pars);
+               /* 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 = 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;
+
+       /* Restrict capabilities by ELD if this isn't disabled */
+       if (!static_hdmi_pcm && (eld->eld_valid || eld->lpcm_sad_ready)) {
+               snd_hdmi_eld_update_pcm_info(eld, hinfo);
                if (hinfo->channels_min > hinfo->channels_max ||
                    !hinfo->rates || !hinfo->formats)
                        return -ENODEV;
-       } else {
-               /* fallback to the codec default */
-               hinfo->channels_max = codec_pars->channels_max;
-               hinfo->rates = codec_pars->rates;
-               hinfo->formats = codec_pars->formats;
-               hinfo->maxbps = codec_pars->maxbps;
        }
-       /* store the updated parameters */
+
+       /* Store the updated parameters */
        runtime->hw.channels_min = hinfo->channels_min;
        runtime->hw.channels_max = hinfo->channels_max;
        runtime->hw.formats = hinfo->formats;
@@ -861,12 +968,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
@@ -876,73 +982,124 @@ 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;
 }
 
-static void hdmi_present_sense(struct hda_codec *codec, hda_nid_t pin_nid,
-                              struct hdmi_eld *eld)
+static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
 {
+       struct hda_codec *codec = per_pin->codec;
+       struct hdmi_eld *eld = &per_pin->sink_eld;
+       hda_nid_t pin_nid = per_pin->pin_nid;
+       /*
+        * Always execute a GetPinSense verb here, even when called from
+        * hdmi_intrinsic_event; for some NVIDIA HW, the unsolicited
+        * response's PD bit is not the real PD value, but indicates that
+        * the real PD value changed. An older version of the HD-audio
+        * specification worked this way. Hence, we just ignore the data in
+        * the unsolicited response to avoid custom WARs.
+        */
        int present = snd_hda_pin_sense(codec, pin_nid);
+       bool eld_valid = false;
+
+       memset(eld, 0, offsetof(struct hdmi_eld, eld_buffer));
 
        eld->monitor_present    = !!(present & AC_PINSENSE_PRESENCE);
-       eld->eld_valid          = !!(present & AC_PINSENSE_ELDV);
+       if (eld->monitor_present)
+               eld_valid       = !!(present & AC_PINSENSE_ELDV);
+
+       _snd_printd(SND_PR_VERBOSE,
+               "HDMI status: Codec=%d Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
+               codec->addr, pin_nid, eld->monitor_present, eld_valid);
+
+       if (eld_valid) {
+               if (!snd_hdmi_get_eld(eld, codec, pin_nid))
+                       snd_hdmi_show_eld(eld);
+               else if (repoll) {
+                       queue_delayed_work(codec->bus->workq,
+                                          &per_pin->work,
+                                          msecs_to_jiffies(300));
+               }
+       }
+}
 
-       if (present & AC_PINSENSE_ELDV)
-               hdmi_get_show_eld(codec, pin_nid, eld);
+static void hdmi_repoll_eld(struct work_struct *work)
+{
+       struct hdmi_spec_per_pin *per_pin =
+       container_of(to_delayed_work(work), struct hdmi_spec_per_pin, work);
+
+       if (per_pin->repoll_count++ > 6)
+               per_pin->repoll_count = 0;
+
+       hdmi_present_sense(per_pin, per_pin->repoll_count);
 }
 
 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;
+       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;
-       }
 
-       hdmi_present_sense(codec, pin_nid, &spec->sink_eld[spec->num_pins]);
+       pin_idx = spec->num_pins;
+       per_pin = &spec->pins[pin_idx];
+
+       per_pin->pin_nid = pin_nid;
+
+       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);
+       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;
@@ -952,8 +1109,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) {
@@ -964,7 +1119,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);
@@ -974,40 +1128,22 @@ 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
         * HDA link is powered off at hot plug or hw initialization time.
         */
 #ifdef CONFIG_SND_HDA_POWER_SAVE
-       if (!(snd_hda_param_read(codec, codec->afg, AC_PAR_POWER_STATE) &
-             AC_PWRST_EPSS))
+       if ((!(snd_hda_param_read(codec, codec->afg, AC_PAR_POWER_STATE) &
+             AC_PWRST_EPSS)) && (codec->preset->id != 0x10de0020))
                codec->bus->power_keep_link_on = 1;
 #endif
 
@@ -1016,11 +1152,12 @@ static int hdmi_parse_codec(struct hda_codec *codec)
 
 /*
  */
-static char *generic_hdmi_pcm_names[MAX_HDMI_CVTS] = {
-       "HDMI 0",
-       "HDMI 1",
-       "HDMI 2",
-};
+static char *get_hdmi_pcm_name(int idx)
+{
+       static char names[MAX_HDMI_PINS][8];
+       sprintf(&names[idx][0], "HDMI %d", idx);
+       return &names[idx][0];
+}
 
 /*
  * HDMI callbacks
@@ -1032,64 +1169,144 @@ 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;
+
+#if defined(CONFIG_SND_HDA_PLATFORM_NVIDIA_TEGRA) && defined(CONFIG_TEGRA_DC)
+       if (codec->preset->id == 0x10de0020) {
+               int err = 0;
+               /* Set hdmi:audio freq and source selection*/
+               err = tegra_hdmi_setup_audio_freq_source(
+                                       substream->runtime->rate, HDA);
+               if ( err < 0 ) {
+                       snd_printk(KERN_ERR
+                               "Unable to set hdmi audio freq to %d \n",
+                                               substream->runtime->rate);
+                       return err;
+               }
+       }
+#endif
+
+       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 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;
-
-       codec->num_pcms = spec->num_cvts;
-       codec->pcm_info = info;
+       int pin_idx;
 
-       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 = get_hdmi_pcm_name(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;
 }
 
+static int generic_hdmi_build_jack(struct hda_codec *codec, int pin_idx)
+{
+       char hdmi_str[32] = "HDMI/DP";
+       struct hdmi_spec *spec = codec->spec;
+       struct hdmi_spec_per_pin *per_pin = &spec->pins[pin_idx];
+       int pcmdev = spec->pcm_rec[pin_idx].device;
+
+       if (pcmdev > 0)
+               sprintf(hdmi_str + strlen(hdmi_str), ",pcm=%d", pcmdev);
+
+       return snd_hda_jack_add_kctl(codec, per_pin->pin_nid, hdmi_str, 0);
+}
+
 static int generic_hdmi_build_controls(struct hda_codec *codec)
 {
        struct hdmi_spec *spec = codec->spec;
        int err;
-       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];
+
+               err = generic_hdmi_build_jack(codec, pin_idx);
+               if (err < 0)
+                       return err;
+
+               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);
+
+               /* add control for ELD Bytes */
+               err = hdmi_create_eld_ctl(codec,
+                                       pin_idx,
+                                       spec->pcm_rec[pin_idx].device);
 
-       for (i = 0; i < codec->num_pcms; i++) {
-               err = snd_hda_create_spdif_out_ctls(codec, spec->cvt[i]);
                if (err < 0)
                        return err;
+
+               hdmi_present_sense(per_pin, 0);
        }
 
        return 0;
@@ -1098,29 +1315,50 @@ 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 (i = 0; spec->pin[i]; i++) {
-               hdmi_enable_output(codec, spec->pin[i]);
-               snd_hda_codec_write(codec, spec->pin[i], 0,
-                                   AC_VERB_SET_UNSOLICITED_ENABLE,
-                                   AC_USRSP_EN | spec->pin[i]);
+       switch (codec->preset->id) {
+       case 0x10de0020:
+               snd_hda_codec_write(codec, 4, 0,
+                                   AC_VERB_SET_DIGI_CONVERT_1, 0x11);
+       default:
+               break;
        }
+
+       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;
+
+               hdmi_init_pin(codec, pin_nid);
+               snd_hda_jack_detect_enable(codec, pin_nid, pin_nid);
+
+               per_pin->codec = codec;
+               INIT_DELAYED_WORK(&per_pin->work, hdmi_repoll_eld);
+               snd_hda_eld_proc_new(codec, eld, pin_idx);
+       }
+       snd_hda_jack_report_sync(codec);
        return 0;
 }
 
 static void generic_hdmi_free(struct hda_codec *codec)
 {
        struct hdmi_spec *spec = codec->spec;
-       int i;
+       int pin_idx;
 
-       for (i = 0; i < spec->num_pins; i++)
-               snd_hda_eld_proc_free(codec, &spec->sink_eld[i]);
+       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;
 
+               cancel_delayed_work(&per_pin->work);
+               snd_hda_eld_proc_free(codec, eld);
+       }
+
+       flush_workqueue(codec->bus->workq);
        kfree(spec);
 }
 
-static struct hda_codec_ops generic_hdmi_patch_ops = {
+static const struct hda_codec_ops generic_hdmi_patch_ops = {
        .init                   = generic_hdmi_init,
        .free                   = generic_hdmi_free,
        .build_pcms             = generic_hdmi_build_pcms,
@@ -1131,7 +1369,6 @@ static 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)
@@ -1145,14 +1382,68 @@ 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;
 }
 
+/*
+ * Shared non-generic implementations
+ */
+
+static int simple_playback_build_pcms(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec = codec->spec;
+       struct hda_pcm *info = spec->pcm_rec;
+       int i;
+
+       codec->num_pcms = spec->num_cvts;
+       codec->pcm_info = info;
+
+       for (i = 0; i < codec->num_pcms; i++, info++) {
+               unsigned int chans;
+               struct hda_pcm_stream *pstr;
+
+               chans = get_wcaps(codec, spec->cvts[i].cvt_nid);
+               chans = get_wcaps_channels(chans);
+
+               info->name = get_hdmi_pcm_name(i);
+               info->pcm_type = HDA_PCM_TYPE_HDMI;
+               pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK];
+               snd_BUG_ON(!spec->pcm_playback);
+               *pstr = *spec->pcm_playback;
+               pstr->nid = spec->cvts[i].cvt_nid;
+               if (pstr->channels_max <= 2 && chans && chans <= 16)
+                       pstr->channels_max = chans;
+       }
+
+       return 0;
+}
+
+static int simple_playback_build_controls(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec = codec->spec;
+       int err;
+       int i;
+
+       for (i = 0; i < codec->num_pcms; i++) {
+               err = snd_hda_create_spdif_out_ctls(codec,
+                                                   spec->cvts[i].cvt_nid,
+                                                   spec->cvts[i].cvt_nid);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
+static void simple_playback_free(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec = codec->spec;
+
+       kfree(spec);
+}
+
 /*
  * Nvidia specific implementations
  */
@@ -1165,12 +1456,12 @@ static int patch_generic_hdmi(struct hda_codec *codec)
 #define nvhdmi_master_con_nid_7x       0x04
 #define nvhdmi_master_pin_nid_7x       0x05
 
-static hda_nid_t nvhdmi_con_nids_7x[4] = {
+static const hda_nid_t nvhdmi_con_nids_7x[4] = {
        /*front, rear, clfe, rear_surr */
        0x6, 0x8, 0xa, 0xc,
 };
 
-static struct hda_verb nvhdmi_basic_init_7x[] = {
+static const struct hda_verb nvhdmi_basic_init_7x[] = {
        /* set audio protect on */
        { 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1},
        /* enable digital output on pin widget */
@@ -1276,6 +1567,39 @@ static int simple_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
                                             stream_tag, format, substream);
 }
 
+static void nvhdmi_8ch_7x_set_info_frame_parameters(struct hda_codec *codec,
+                                                   int channels)
+{
+       unsigned int chanmask;
+       int chan = channels ? (channels - 1) : 1;
+
+       switch (channels) {
+       default:
+       case 0:
+       case 2:
+               chanmask = 0x00;
+               break;
+       case 4:
+               chanmask = 0x08;
+               break;
+       case 6:
+               chanmask = 0x0b;
+               break;
+       case 8:
+               chanmask = 0x13;
+               break;
+       }
+
+       /* Set the audio infoframe channel allocation and checksum fields.  The
+        * channel count is computed implicitly by the hardware. */
+       snd_hda_codec_write(codec, 0x1, 0,
+                       Nv_VERB_SET_Channel_Allocation, chanmask);
+
+       snd_hda_codec_write(codec, 0x1, 0,
+                       Nv_VERB_SET_Info_Frame_Checksum,
+                       (0x71 - chan - chanmask));
+}
+
 static int nvhdmi_8ch_7x_pcm_close(struct hda_pcm_stream *hinfo,
                                   struct hda_codec *codec,
                                   struct snd_pcm_substream *substream)
@@ -1294,6 +1618,10 @@ static int nvhdmi_8ch_7x_pcm_close(struct hda_pcm_stream *hinfo,
                                AC_VERB_SET_STREAM_FORMAT, 0);
        }
 
+       /* The audio hardware sends a channel count of 0x7 (8ch) when all the
+        * streams are disabled. */
+       nvhdmi_8ch_7x_set_info_frame_parameters(codec, 8);
+
        return snd_hda_multi_out_dig_close(codec, &spec->multiout);
 }
 
@@ -1304,44 +1632,25 @@ static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
                                     struct snd_pcm_substream *substream)
 {
        int chs;
-       unsigned int dataDCC1, dataDCC2, chan, chanmask, channel_id;
+       unsigned int dataDCC2, channel_id;
        int i;
+       struct hdmi_spec *spec = codec->spec;
+       struct hda_spdif_out *spdif =
+               snd_hda_spdif_out_of_nid(codec, spec->cvts[0].cvt_nid);
 
        mutex_lock(&codec->spdif_mutex);
 
        chs = substream->runtime->channels;
-       chan = chs ? (chs - 1) : 1;
 
-       switch (chs) {
-       default:
-       case 0:
-       case 2:
-               chanmask = 0x00;
-               break;
-       case 4:
-               chanmask = 0x08;
-               break;
-       case 6:
-               chanmask = 0x0b;
-               break;
-       case 8:
-               chanmask = 0x13;
-               break;
-       }
-       dataDCC1 = AC_DIG1_ENABLE | AC_DIG1_COPYRIGHT;
        dataDCC2 = 0x2;
 
-       /* set the Audio InforFrame Channel Allocation */
-       snd_hda_codec_write(codec, 0x1, 0,
-                       Nv_VERB_SET_Channel_Allocation, chanmask);
-
        /* turn off SPDIF once; otherwise the IEC958 bits won't be updated */
-       if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE))
+       if (codec->spdif_status_reset && (spdif->ctls & AC_DIG1_ENABLE))
                snd_hda_codec_write(codec,
                                nvhdmi_master_con_nid_7x,
                                0,
                                AC_VERB_SET_DIGI_CONVERT_1,
-                               codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff);
+                               spdif->ctls & ~AC_DIG1_ENABLE & 0xff);
 
        /* set the stream id */
        snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0,
@@ -1353,12 +1662,12 @@ static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
 
        /* turn on again (if needed) */
        /* enable and set the channel status audio/data flag */
-       if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE)) {
+       if (codec->spdif_status_reset && (spdif->ctls & AC_DIG1_ENABLE)) {
                snd_hda_codec_write(codec,
                                nvhdmi_master_con_nid_7x,
                                0,
                                AC_VERB_SET_DIGI_CONVERT_1,
-                               codec->spdif_ctls & 0xff);
+                               spdif->ctls & 0xff);
                snd_hda_codec_write(codec,
                                nvhdmi_master_con_nid_7x,
                                0,
@@ -1375,12 +1684,12 @@ static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
                 *otherwise the IEC958 bits won't be updated
                 */
                if (codec->spdif_status_reset &&
-               (codec->spdif_ctls & AC_DIG1_ENABLE))
+               (spdif->ctls & AC_DIG1_ENABLE))
                        snd_hda_codec_write(codec,
                                nvhdmi_con_nids_7x[i],
                                0,
                                AC_VERB_SET_DIGI_CONVERT_1,
-                               codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff);
+                               spdif->ctls & ~AC_DIG1_ENABLE & 0xff);
                /* set the stream id */
                snd_hda_codec_write(codec,
                                nvhdmi_con_nids_7x[i],
@@ -1396,12 +1705,12 @@ static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
                /* turn on again (if needed) */
                /* enable and set the channel status audio/data flag */
                if (codec->spdif_status_reset &&
-               (codec->spdif_ctls & AC_DIG1_ENABLE)) {
+               (spdif->ctls & AC_DIG1_ENABLE)) {
                        snd_hda_codec_write(codec,
                                        nvhdmi_con_nids_7x[i],
                                        0,
                                        AC_VERB_SET_DIGI_CONVERT_1,
-                                       codec->spdif_ctls & 0xff);
+                                       spdif->ctls & 0xff);
                        snd_hda_codec_write(codec,
                                        nvhdmi_con_nids_7x[i],
                                        0,
@@ -1409,16 +1718,13 @@ static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
                }
        }
 
-       /* set the Audio Info Frame Checksum */
-       snd_hda_codec_write(codec, 0x1, 0,
-                       Nv_VERB_SET_Info_Frame_Checksum,
-                       (0x71 - chan - chanmask));
+       nvhdmi_8ch_7x_set_info_frame_parameters(codec, chs);
 
        mutex_unlock(&codec->spdif_mutex);
        return 0;
 }
 
-static struct hda_pcm_stream nvhdmi_pcm_playback_8ch_7x = {
+static const struct hda_pcm_stream nvhdmi_pcm_playback_8ch_7x = {
        .substreams = 1,
        .channels_min = 2,
        .channels_max = 8,
@@ -1433,7 +1739,7 @@ static struct hda_pcm_stream nvhdmi_pcm_playback_8ch_7x = {
        },
 };
 
-static struct hda_pcm_stream nvhdmi_pcm_playback_2ch = {
+static const struct hda_pcm_stream nvhdmi_pcm_playback_2ch = {
        .substreams = 1,
        .channels_min = 2,
        .channels_max = 2,
@@ -1448,32 +1754,20 @@ static struct hda_pcm_stream nvhdmi_pcm_playback_2ch = {
        },
 };
 
-static struct hda_codec_ops nvhdmi_patch_ops_8ch_7x = {
-       .build_controls = generic_hdmi_build_controls,
-       .build_pcms = generic_hdmi_build_pcms,
+static const struct hda_codec_ops nvhdmi_patch_ops_8ch_7x = {
+       .build_controls = simple_playback_build_controls,
+       .build_pcms = simple_playback_build_pcms,
        .init = nvhdmi_7x_init,
-       .free = generic_hdmi_free,
+       .free = simple_playback_free,
 };
 
-static struct hda_codec_ops nvhdmi_patch_ops_2ch = {
-       .build_controls = generic_hdmi_build_controls,
-       .build_pcms = generic_hdmi_build_pcms,
+static const struct hda_codec_ops nvhdmi_patch_ops_2ch = {
+       .build_controls = simple_playback_build_controls,
+       .build_pcms = simple_playback_build_pcms,
        .init = nvhdmi_7x_init,
-       .free = generic_hdmi_free,
+       .free = simple_playback_free,
 };
 
-static int patch_nvhdmi_8ch_89(struct hda_codec *codec)
-{
-       struct hdmi_spec *spec;
-       int err = patch_generic_hdmi(codec);
-
-       if (err < 0)
-               return err;
-       spec = codec->spec;
-       spec->old_pin_detect = 1;
-       return 0;
-}
-
 static int patch_nvhdmi_2ch(struct hda_codec *codec)
 {
        struct hdmi_spec *spec;
@@ -1487,9 +1781,8 @@ static int patch_nvhdmi_2ch(struct hda_codec *codec)
        spec->multiout.num_dacs = 0;  /* no analog */
        spec->multiout.max_channels = 2;
        spec->multiout.dig_out_nid = nvhdmi_master_con_nid_7x;
-       spec->old_pin_detect = 1;
        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;
@@ -1508,6 +1801,11 @@ static int patch_nvhdmi_8ch_7x(struct hda_codec *codec)
        spec->multiout.max_channels = 8;
        spec->pcm_playback = &nvhdmi_pcm_playback_8ch_7x;
        codec->patch_ops = nvhdmi_patch_ops_8ch_7x;
+
+       /* Initialize the audio infoframe channel mask and checksum to something
+        * valid */
+       nvhdmi_8ch_7x_set_info_frame_parameters(codec, 8);
+
        return 0;
 }
 
@@ -1535,18 +1833,18 @@ 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);
        }
        return 0;
 }
 
-static struct hda_pcm_stream atihdmi_pcm_digital_playback = {
+static const struct hda_pcm_stream atihdmi_pcm_digital_playback = {
        .substreams = 1,
        .channels_min = 2,
        .channels_max = 2,
@@ -1558,7 +1856,7 @@ static struct hda_pcm_stream atihdmi_pcm_digital_playback = {
        },
 };
 
-static struct hda_verb atihdmi_basic_init[] = {
+static const struct hda_verb atihdmi_basic_init[] = {
        /* enable digital output on pin widget */
        { 0x03, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
        {} /* terminator */
@@ -1570,18 +1868,18 @@ 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;
 }
 
-static struct hda_codec_ops atihdmi_patch_ops = {
-       .build_controls = generic_hdmi_build_controls,
-       .build_pcms = generic_hdmi_build_pcms,
+static const struct hda_codec_ops atihdmi_patch_ops = {
+       .build_controls = simple_playback_build_controls,
+       .build_pcms = simple_playback_build_pcms,
        .init = atihdmi_init,
-       .free = generic_hdmi_free,
+       .free = simple_playback_free,
 };
 
 
@@ -1599,8 +1897,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;
@@ -1612,7 +1910,7 @@ static int patch_atihdmi(struct hda_codec *codec)
 /*
  * patch entries
  */
-static struct hda_codec_preset snd_hda_preset_hdmi[] = {
+static const struct hda_codec_preset snd_hda_preset_hdmi[] = {
 { .id = 0x1002793c, .name = "RS600 HDMI",      .patch = patch_atihdmi },
 { .id = 0x10027919, .name = "RS600 HDMI",      .patch = patch_atihdmi },
 { .id = 0x1002791a, .name = "RS690/780 HDMI",  .patch = patch_atihdmi },
@@ -1625,25 +1923,29 @@ static struct hda_codec_preset snd_hda_preset_hdmi[] = {
 { .id = 0x10de0005, .name = "MCP77/78 HDMI",   .patch = patch_nvhdmi_8ch_7x },
 { .id = 0x10de0006, .name = "MCP77/78 HDMI",   .patch = patch_nvhdmi_8ch_7x },
 { .id = 0x10de0007, .name = "MCP79/7A HDMI",   .patch = patch_nvhdmi_8ch_7x },
-{ .id = 0x10de000a, .name = "GPU 0a HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de000b, .name = "GPU 0b HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de000c, .name = "MCP89 HDMI",      .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de000d, .name = "GPU 0d HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0010, .name = "GPU 10 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0011, .name = "GPU 11 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0012, .name = "GPU 12 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0013, .name = "GPU 13 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0014, .name = "GPU 14 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0018, .name = "GPU 18 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0019, .name = "GPU 19 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de001a, .name = "GPU 1a HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de001b, .name = "GPU 1b HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de001c, .name = "GPU 1c HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0040, .name = "GPU 40 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0041, .name = "GPU 41 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0042, .name = "GPU 42 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0043, .name = "GPU 43 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
-{ .id = 0x10de0044, .name = "GPU 44 HDMI/DP",  .patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de000a, .name = "GPU 0a HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de000b, .name = "GPU 0b HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de000c, .name = "MCP89 HDMI",      .patch = patch_generic_hdmi },
+{ .id = 0x10de000d, .name = "GPU 0d HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0010, .name = "GPU 10 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0011, .name = "GPU 11 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0012, .name = "GPU 12 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0013, .name = "GPU 13 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0014, .name = "GPU 14 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0015, .name = "GPU 15 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0016, .name = "GPU 16 HDMI/DP",  .patch = patch_generic_hdmi },
+/* 17 is known to be absent */
+{ .id = 0x10de0018, .name = "GPU 18 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0019, .name = "GPU 19 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de001a, .name = "GPU 1a HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de001b, .name = "GPU 1b HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de001c, .name = "GPU 1c HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0020, .name = "Tegra30 HDMI",    .patch = patch_generic_hdmi },
+{ .id = 0x10de0040, .name = "GPU 40 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0041, .name = "GPU 41 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0042, .name = "GPU 42 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0043, .name = "GPU 43 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0044, .name = "GPU 44 HDMI/DP",  .patch = patch_generic_hdmi },
 { .id = 0x10de0067, .name = "MCP67 HDMI",      .patch = patch_nvhdmi_2ch },
 { .id = 0x10de8001, .name = "MCP73 HDMI",      .patch = patch_nvhdmi_2ch },
 { .id = 0x80860054, .name = "IbexPeak HDMI",   .patch = patch_generic_hdmi },
@@ -1652,6 +1954,8 @@ static struct hda_codec_preset snd_hda_preset_hdmi[] = {
 { .id = 0x80862803, .name = "Eaglelake HDMI",  .patch = patch_generic_hdmi },
 { .id = 0x80862804, .name = "IbexPeak HDMI",   .patch = patch_generic_hdmi },
 { .id = 0x80862805, .name = "CougarPoint HDMI",        .patch = patch_generic_hdmi },
+{ .id = 0x80862806, .name = "PantherPoint HDMI", .patch = patch_generic_hdmi },
+{ .id = 0x80862880, .name = "CedarTrail HDMI", .patch = patch_generic_hdmi },
 { .id = 0x808629fb, .name = "Crestline HDMI",  .patch = patch_generic_hdmi },
 {} /* terminator */
 };
@@ -1676,11 +1980,14 @@ MODULE_ALIAS("snd-hda-codec-id:10de0011");
 MODULE_ALIAS("snd-hda-codec-id:10de0012");
 MODULE_ALIAS("snd-hda-codec-id:10de0013");
 MODULE_ALIAS("snd-hda-codec-id:10de0014");
+MODULE_ALIAS("snd-hda-codec-id:10de0015");
+MODULE_ALIAS("snd-hda-codec-id:10de0016");
 MODULE_ALIAS("snd-hda-codec-id:10de0018");
 MODULE_ALIAS("snd-hda-codec-id:10de0019");
 MODULE_ALIAS("snd-hda-codec-id:10de001a");
 MODULE_ALIAS("snd-hda-codec-id:10de001b");
 MODULE_ALIAS("snd-hda-codec-id:10de001c");
+MODULE_ALIAS("snd-hda-codec-id:10de0020");
 MODULE_ALIAS("snd-hda-codec-id:10de0040");
 MODULE_ALIAS("snd-hda-codec-id:10de0041");
 MODULE_ALIAS("snd-hda-codec-id:10de0042");
@@ -1695,6 +2002,8 @@ MODULE_ALIAS("snd-hda-codec-id:80862802");
 MODULE_ALIAS("snd-hda-codec-id:80862803");
 MODULE_ALIAS("snd-hda-codec-id:80862804");
 MODULE_ALIAS("snd-hda-codec-id:80862805");
+MODULE_ALIAS("snd-hda-codec-id:80862806");
+MODULE_ALIAS("snd-hda-codec-id:80862880");
 MODULE_ALIAS("snd-hda-codec-id:808629fb");
 
 MODULE_LICENSE("GPL");