ALSA: hda - Delay HDMI presence reports while waiting for ELD information
[sfrench/cifs-2.6.git] / sound / pci / hda / patch_hdmi.c
index 50173d412ac5c0d5fb7095b3e3004e2c63c319a0..ce412d1a15d064d5125c231f321ace6192093db0 100644 (file)
@@ -6,6 +6,7 @@
  *  Copyright (c) 2006 ATI Technologies Inc.
  *  Copyright (c) 2008 NVIDIA Corp.  All rights reserved.
  *  Copyright (c) 2008 Wei Ni <wni@nvidia.com>
+ *  Copyright (c) 2013 Anssi Hannula <anssi.hannula@iki.fi>
  *
  *  Authors:
  *                     Wu Fengguang <wfg@linux.intel.com>
@@ -45,6 +46,7 @@ module_param(static_hdmi_pcm, bool, 0644);
 MODULE_PARM_DESC(static_hdmi_pcm, "Don't restrict PCM parameters per ELD info");
 
 #define is_haswell(codec)  ((codec)->vendor_id == 0x80862807)
+#define is_valleyview(codec) ((codec)->vendor_id == 0x80862882)
 
 struct hdmi_spec_per_cvt {
        hda_nid_t cvt_nid;
@@ -63,9 +65,11 @@ struct hdmi_spec_per_pin {
        hda_nid_t pin_nid;
        int num_mux_nids;
        hda_nid_t mux_nids[HDA_MAX_CONNECTIONS];
+       hda_nid_t cvt_nid;
 
        struct hda_codec *codec;
        struct hdmi_eld sink_eld;
+       struct mutex lock;
        struct delayed_work work;
        struct snd_kcontrol *eld_ctl;
        int repoll_count;
@@ -75,6 +79,42 @@ struct hdmi_spec_per_pin {
        bool chmap_set;         /* channel-map override by ALSA API? */
        unsigned char chmap[8]; /* ALSA API channel-map */
        char pcm_name[8];       /* filled in build_pcm callbacks */
+#ifdef CONFIG_PROC_FS
+       struct snd_info_entry *proc_entry;
+#endif
+};
+
+struct cea_channel_speaker_allocation;
+
+/* operations used by generic code that can be overridden by patches */
+struct hdmi_ops {
+       int (*pin_get_eld)(struct hda_codec *codec, hda_nid_t pin_nid,
+                          unsigned char *buf, int *eld_size);
+
+       /* get and set channel assigned to each HDMI ASP (audio sample packet) slot */
+       int (*pin_get_slot_channel)(struct hda_codec *codec, hda_nid_t pin_nid,
+                                   int asp_slot);
+       int (*pin_set_slot_channel)(struct hda_codec *codec, hda_nid_t pin_nid,
+                                   int asp_slot, int channel);
+
+       void (*pin_setup_infoframe)(struct hda_codec *codec, hda_nid_t pin_nid,
+                                   int ca, int active_channels, int conn_type);
+
+       /* enable/disable HBR (HD passthrough) */
+       int (*pin_hbr_setup)(struct hda_codec *codec, hda_nid_t pin_nid, bool hbr);
+
+       int (*setup_stream)(struct hda_codec *codec, hda_nid_t cvt_nid,
+                           hda_nid_t pin_nid, u32 stream_tag, int format);
+
+       /* Helpers for producing the channel map TLVs. These can be overridden
+        * for devices that have non-standard mapping requirements. */
+       int (*chmap_cea_alloc_validate_get_type)(struct cea_channel_speaker_allocation *cap,
+                                                int channels);
+       void (*cea_alloc_to_tlv_chmap)(struct cea_channel_speaker_allocation *cap,
+                                      unsigned int *chmap, int channels);
+
+       /* check that the user-given chmap is supported */
+       int (*chmap_validate)(int ca, int channels, unsigned char *chmap);
 };
 
 struct hdmi_spec {
@@ -88,8 +128,9 @@ struct hdmi_spec {
        unsigned int channels_max; /* max over all cvts */
 
        struct hdmi_eld temp_eld;
+       struct hdmi_ops ops;
        /*
-        * Non-generic ATI/NVIDIA specific
+        * Non-generic VIA/NVIDIA specific
         */
        struct hda_multi_out multiout;
        struct hda_pcm_stream pcm_playback;
@@ -348,17 +389,19 @@ static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol,
 {
        struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
        struct hdmi_spec *spec = codec->spec;
+       struct hdmi_spec_per_pin *per_pin;
        struct hdmi_eld *eld;
        int pin_idx;
 
        uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
 
        pin_idx = kcontrol->private_value;
-       eld = &get_pin(spec, pin_idx)->sink_eld;
+       per_pin = get_pin(spec, pin_idx);
+       eld = &per_pin->sink_eld;
 
-       mutex_lock(&eld->lock);
+       mutex_lock(&per_pin->lock);
        uinfo->count = eld->eld_valid ? eld->eld_size : 0;
-       mutex_unlock(&eld->lock);
+       mutex_unlock(&per_pin->lock);
 
        return 0;
 }
@@ -368,15 +411,17 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,
 {
        struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
        struct hdmi_spec *spec = codec->spec;
+       struct hdmi_spec_per_pin *per_pin;
        struct hdmi_eld *eld;
        int pin_idx;
 
        pin_idx = kcontrol->private_value;
-       eld = &get_pin(spec, pin_idx)->sink_eld;
+       per_pin = get_pin(spec, pin_idx);
+       eld = &per_pin->sink_eld;
 
-       mutex_lock(&eld->lock);
+       mutex_lock(&per_pin->lock);
        if (eld->eld_size > ARRAY_SIZE(ucontrol->value.bytes.data)) {
-               mutex_unlock(&eld->lock);
+               mutex_unlock(&per_pin->lock);
                snd_BUG();
                return -EINVAL;
        }
@@ -386,7 +431,7 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,
        if (eld->eld_valid)
                memcpy(ucontrol->value.bytes.data, eld->eld_buffer,
                       eld->eld_size);
-       mutex_unlock(&eld->lock);
+       mutex_unlock(&per_pin->lock);
 
        return 0;
 }
@@ -477,6 +522,68 @@ static void hdmi_set_channel_count(struct hda_codec *codec,
                                    AC_VERB_SET_CVT_CHAN_COUNT, chs - 1);
 }
 
+/*
+ * ELD proc files
+ */
+
+#ifdef CONFIG_PROC_FS
+static void print_eld_info(struct snd_info_entry *entry,
+                          struct snd_info_buffer *buffer)
+{
+       struct hdmi_spec_per_pin *per_pin = entry->private_data;
+
+       mutex_lock(&per_pin->lock);
+       snd_hdmi_print_eld_info(&per_pin->sink_eld, buffer);
+       mutex_unlock(&per_pin->lock);
+}
+
+static void write_eld_info(struct snd_info_entry *entry,
+                          struct snd_info_buffer *buffer)
+{
+       struct hdmi_spec_per_pin *per_pin = entry->private_data;
+
+       mutex_lock(&per_pin->lock);
+       snd_hdmi_write_eld_info(&per_pin->sink_eld, buffer);
+       mutex_unlock(&per_pin->lock);
+}
+
+static int eld_proc_new(struct hdmi_spec_per_pin *per_pin, int index)
+{
+       char name[32];
+       struct hda_codec *codec = per_pin->codec;
+       struct snd_info_entry *entry;
+       int err;
+
+       snprintf(name, sizeof(name), "eld#%d.%d", codec->addr, index);
+       err = snd_card_proc_new(codec->bus->card, name, &entry);
+       if (err < 0)
+               return err;
+
+       snd_info_set_text_ops(entry, per_pin, print_eld_info);
+       entry->c.text.write = write_eld_info;
+       entry->mode |= S_IWUSR;
+       per_pin->proc_entry = entry;
+
+       return 0;
+}
+
+static void eld_proc_free(struct hdmi_spec_per_pin *per_pin)
+{
+       if (!per_pin->codec->bus->shutdown && per_pin->proc_entry) {
+               snd_device_free(per_pin->codec->bus->card, per_pin->proc_entry);
+               per_pin->proc_entry = NULL;
+       }
+}
+#else
+static inline int eld_proc_new(struct hdmi_spec_per_pin *per_pin,
+                              int index)
+{
+       return 0;
+}
+static inline void eld_proc_free(struct hdmi_spec_per_pin *per_pin)
+{
+}
+#endif
 
 /*
  * Channel mapping routines
@@ -577,74 +684,91 @@ static void hdmi_debug_channel_mapping(struct hda_codec *codec,
                                       hda_nid_t pin_nid)
 {
 #ifdef CONFIG_SND_DEBUG_VERBOSE
+       struct hdmi_spec *spec = codec->spec;
        int i;
-       int slot;
+       int channel;
 
        for (i = 0; i < 8; i++) {
-               slot = snd_hda_codec_read(codec, pin_nid, 0,
-                                               AC_VERB_GET_HDMI_CHAN_SLOT, i);
+               channel = spec->ops.pin_get_slot_channel(codec, pin_nid, i);
                printk(KERN_DEBUG "HDMI: ASP channel %d => slot %d\n",
-                                               slot >> 4, slot & 0xf);
+                                               channel, i);
        }
 #endif
 }
 
-
 static void hdmi_std_setup_channel_mapping(struct hda_codec *codec,
                                       hda_nid_t pin_nid,
                                       bool non_pcm,
                                       int ca)
 {
+       struct hdmi_spec *spec = codec->spec;
+       struct cea_channel_speaker_allocation *ch_alloc;
        int i;
        int err;
        int order;
        int non_pcm_mapping[8];
 
        order = get_channel_allocation_order(ca);
+       ch_alloc = &channel_allocations[order];
 
        if (hdmi_channel_mapping[ca][1] == 0) {
-               for (i = 0; i < channel_allocations[order].channels; i++)
-                       hdmi_channel_mapping[ca][i] = i | (i << 4);
-               for (; i < 8; i++)
-                       hdmi_channel_mapping[ca][i] = 0xf | (i << 4);
+               int hdmi_slot = 0;
+               /* fill actual channel mappings in ALSA channel (i) order */
+               for (i = 0; i < ch_alloc->channels; i++) {
+                       while (!ch_alloc->speakers[7 - hdmi_slot] && !WARN_ON(hdmi_slot >= 8))
+                               hdmi_slot++; /* skip zero slots */
+
+                       hdmi_channel_mapping[ca][i] = (i << 4) | hdmi_slot++;
+               }
+               /* fill the rest of the slots with ALSA channel 0xf */
+               for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++)
+                       if (!ch_alloc->speakers[7 - hdmi_slot])
+                               hdmi_channel_mapping[ca][i++] = (0xf << 4) | hdmi_slot;
        }
 
        if (non_pcm) {
-               for (i = 0; i < channel_allocations[order].channels; i++)
-                       non_pcm_mapping[i] = i | (i << 4);
+               for (i = 0; i < ch_alloc->channels; i++)
+                       non_pcm_mapping[i] = (i << 4) | i;
                for (; i < 8; i++)
-                       non_pcm_mapping[i] = 0xf | (i << 4);
+                       non_pcm_mapping[i] = (0xf << 4) | i;
        }
 
        for (i = 0; i < 8; i++) {
-               err = snd_hda_codec_write(codec, pin_nid, 0,
-                                         AC_VERB_SET_HDMI_CHAN_SLOT,
-                                         non_pcm ? non_pcm_mapping[i] : hdmi_channel_mapping[ca][i]);
+               int slotsetup = non_pcm ? non_pcm_mapping[i] : hdmi_channel_mapping[ca][i];
+               int hdmi_slot = slotsetup & 0x0f;
+               int channel = (slotsetup & 0xf0) >> 4;
+               err = spec->ops.pin_set_slot_channel(codec, pin_nid, hdmi_slot, channel);
                if (err) {
                        snd_printdd(KERN_NOTICE
                                    "HDMI: channel mapping failed\n");
                        break;
                }
        }
-
-       hdmi_debug_channel_mapping(codec, pin_nid);
 }
 
 struct channel_map_table {
        unsigned char map;              /* ALSA API channel map position */
-       unsigned char cea_slot;         /* CEA slot value */
        int spk_mask;                   /* speaker position bit mask */
 };
 
 static struct channel_map_table map_tables[] = {
-       { SNDRV_CHMAP_FL,       0x00,   FL },
-       { SNDRV_CHMAP_FR,       0x01,   FR },
-       { SNDRV_CHMAP_RL,       0x04,   RL },
-       { SNDRV_CHMAP_RR,       0x05,   RR },
-       { SNDRV_CHMAP_LFE,      0x02,   LFE },
-       { SNDRV_CHMAP_FC,       0x03,   FC },
-       { SNDRV_CHMAP_RLC,      0x06,   RLC },
-       { SNDRV_CHMAP_RRC,      0x07,   RRC },
+       { SNDRV_CHMAP_FL,       FL },
+       { SNDRV_CHMAP_FR,       FR },
+       { SNDRV_CHMAP_RL,       RL },
+       { SNDRV_CHMAP_RR,       RR },
+       { SNDRV_CHMAP_LFE,      LFE },
+       { SNDRV_CHMAP_FC,       FC },
+       { SNDRV_CHMAP_RLC,      RLC },
+       { SNDRV_CHMAP_RRC,      RRC },
+       { SNDRV_CHMAP_RC,       RC },
+       { SNDRV_CHMAP_FLC,      FLC },
+       { SNDRV_CHMAP_FRC,      FRC },
+       { SNDRV_CHMAP_FLH,      FLH },
+       { SNDRV_CHMAP_FRH,      FRH },
+       { SNDRV_CHMAP_FLW,      FLW },
+       { SNDRV_CHMAP_FRW,      FRW },
+       { SNDRV_CHMAP_TC,       TC },
+       { SNDRV_CHMAP_FCH,      FCH },
        {} /* terminator */
 };
 
@@ -660,25 +784,19 @@ static int to_spk_mask(unsigned char c)
 }
 
 /* from ALSA API channel position to CEA slot */
-static int to_cea_slot(unsigned char c)
+static int to_cea_slot(int ordered_ca, unsigned char pos)
 {
-       struct channel_map_table *t = map_tables;
-       for (; t->map; t++) {
-               if (t->map == c)
-                       return t->cea_slot;
-       }
-       return 0x0f;
-}
+       int mask = to_spk_mask(pos);
+       int i;
 
-/* from CEA slot to ALSA API channel position */
-static int from_cea_slot(unsigned char c)
-{
-       struct channel_map_table *t = map_tables;
-       for (; t->map; t++) {
-               if (t->cea_slot == c)
-                       return t->map;
+       if (mask) {
+               for (i = 0; i < 8; i++) {
+                       if (channel_allocations[ordered_ca].speakers[7 - i] == mask)
+                               return i;
+               }
        }
-       return 0;
+
+       return -1;
 }
 
 /* from speaker bit mask to ALSA API channel position */
@@ -692,6 +810,14 @@ static int spk_to_chmap(int spk)
        return 0;
 }
 
+/* from CEA slot to ALSA API channel position */
+static int from_cea_slot(int ordered_ca, unsigned char slot)
+{
+       int mask = channel_allocations[ordered_ca].speakers[7 - slot];
+
+       return spk_to_chmap(mask);
+}
+
 /* get the CA index corresponding to the given ALSA API channel map */
 static int hdmi_manual_channel_allocation(int chs, unsigned char *map)
 {
@@ -718,18 +844,29 @@ static int hdmi_manual_channel_allocation(int chs, unsigned char *map)
 /* set up the channel slots for the given ALSA API channel map */
 static int hdmi_manual_setup_channel_mapping(struct hda_codec *codec,
                                             hda_nid_t pin_nid,
-                                            int chs, unsigned char *map)
+                                            int chs, unsigned char *map,
+                                            int ca)
 {
-       int i;
-       for (i = 0; i < 8; i++) {
-               int val, err;
-               if (i < chs)
-                       val = to_cea_slot(map[i]);
-               else
-                       val = 0xf;
-               val |= (i << 4);
-               err = snd_hda_codec_write(codec, pin_nid, 0,
-                                         AC_VERB_SET_HDMI_CHAN_SLOT, val);
+       struct hdmi_spec *spec = codec->spec;
+       int ordered_ca = get_channel_allocation_order(ca);
+       int alsa_pos, hdmi_slot;
+       int assignments[8] = {[0 ... 7] = 0xf};
+
+       for (alsa_pos = 0; alsa_pos < chs; alsa_pos++) {
+
+               hdmi_slot = to_cea_slot(ordered_ca, map[alsa_pos]);
+
+               if (hdmi_slot < 0)
+                       continue; /* unassigned channel */
+
+               assignments[hdmi_slot] = alsa_pos;
+       }
+
+       for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++) {
+               int err;
+
+               err = spec->ops.pin_set_slot_channel(codec, pin_nid, hdmi_slot,
+                                                    assignments[hdmi_slot]);
                if (err)
                        return -EINVAL;
        }
@@ -740,9 +877,10 @@ static int hdmi_manual_setup_channel_mapping(struct hda_codec *codec,
 static void hdmi_setup_fake_chmap(unsigned char *map, int ca)
 {
        int i;
+       int ordered_ca = get_channel_allocation_order(ca);
        for (i = 0; i < 8; i++) {
-               if (i < channel_allocations[ca].channels)
-                       map[i] = from_cea_slot((hdmi_channel_mapping[ca][i] >> 4) & 0x0f);
+               if (i < channel_allocations[ordered_ca].channels)
+                       map[i] = from_cea_slot(ordered_ca, hdmi_channel_mapping[ca][i] & 0x0f);
                else
                        map[i] = 0;
        }
@@ -755,11 +893,29 @@ static void hdmi_setup_channel_mapping(struct hda_codec *codec,
 {
        if (!non_pcm && chmap_set) {
                hdmi_manual_setup_channel_mapping(codec, pin_nid,
-                                                 channels, map);
+                                                 channels, map, ca);
        } else {
                hdmi_std_setup_channel_mapping(codec, pin_nid, non_pcm, ca);
                hdmi_setup_fake_chmap(map, ca);
        }
+
+       hdmi_debug_channel_mapping(codec, pin_nid);
+}
+
+static int hdmi_pin_set_slot_channel(struct hda_codec *codec, hda_nid_t pin_nid,
+                                    int asp_slot, int channel)
+{
+       return snd_hda_codec_write(codec, pin_nid, 0,
+                                  AC_VERB_SET_HDMI_CHAN_SLOT,
+                                  (channel << 4) | asp_slot);
+}
+
+static int hdmi_pin_get_slot_channel(struct hda_codec *codec, hda_nid_t pin_nid,
+                                    int asp_slot)
+{
+       return (snd_hda_codec_read(codec, pin_nid, 0,
+                                  AC_VERB_GET_HDMI_CHAN_SLOT,
+                                  asp_slot) & 0xf0) >> 4;
 }
 
 /*
@@ -883,15 +1039,64 @@ static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid,
        return true;
 }
 
+static void hdmi_pin_setup_infoframe(struct hda_codec *codec,
+                                    hda_nid_t pin_nid,
+                                    int ca, int active_channels,
+                                    int conn_type)
+{
+       union audio_infoframe ai;
+
+       if (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      = active_channels - 1;
+               hdmi_ai->CA             = ca;
+               hdmi_checksum_audio_infoframe(hdmi_ai);
+       } else if (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        = active_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_pin_setup_infoframe: "
+                           "pin=%d channels=%d ca=0x%02x\n",
+                           pin_nid,
+                           active_channels, 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);
+       }
+}
+
 static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
                                       struct hdmi_spec_per_pin *per_pin,
                                       bool non_pcm)
 {
+       struct hdmi_spec *spec = codec->spec;
        hda_nid_t pin_nid = per_pin->pin_nid;
        int channels = per_pin->channels;
+       int active_channels;
        struct hdmi_eld *eld;
-       int ca;
-       union audio_infoframe ai;
+       int ca, ordered_ca;
 
        if (!channels)
                return;
@@ -912,29 +1117,10 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
        if (ca < 0)
                ca = 0;
 
-       memset(&ai, 0, sizeof(ai));
-       if (eld->info.conn_type == 0) { /* HDMI */
-               struct hdmi_audio_infoframe *hdmi_ai = &ai.hdmi;
+       ordered_ca = get_channel_allocation_order(ca);
+       active_channels = channel_allocations[ordered_ca].channels;
 
-               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->info.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;
-       }
+       hdmi_set_channel_count(codec, per_pin->cvt_nid, active_channels);
 
        /*
         * always configure channel mapping, it may have been changed by the
@@ -944,32 +1130,17 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
                                   channels, per_pin->chmap,
                                   per_pin->chmap_set);
 
-       /*
-        * 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_stop_infoframe_trans(codec, pin_nid);
-               hdmi_fill_audio_infoframe(codec, pin_nid,
-                                           ai.bytes, sizeof(ai));
-               hdmi_start_infoframe_trans(codec, pin_nid);
-       }
+       spec->ops.pin_setup_infoframe(codec, pin_nid, ca, active_channels,
+                                     eld->info.conn_type);
 
        per_pin->non_pcm = non_pcm;
 }
 
-
 /*
  * Unsolicited events
  */
 
-static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll);
+static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll);
 
 static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res)
 {
@@ -995,8 +1166,8 @@ static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res)
        if (pin_idx < 0)
                return;
 
-       hdmi_present_sense(get_pin(spec, pin_idx), 1);
-       snd_hda_jack_report_sync(codec);
+       if (hdmi_present_sense(get_pin(spec, pin_idx), 1))
+               snd_hda_jack_report_sync(codec);
 }
 
 static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res)
@@ -1067,26 +1238,22 @@ static void haswell_verify_D0(struct hda_codec *codec,
 #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 cvt_nid,
-                             hda_nid_t pin_nid, u32 stream_tag, int format)
+static int hdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid,
+                             bool hbr)
 {
-       int pinctl;
-       int new_pinctl = 0;
-
-       if (is_haswell(codec))
-               haswell_verify_D0(codec, cvt_nid, pin_nid);
+       int pinctl, new_pinctl;
 
        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;
-               if (is_hbr_format(format))
+               if (hbr)
                        new_pinctl |= AC_PINCTL_EPT_HBR;
                else
                        new_pinctl |= AC_PINCTL_EPT_NATIVE;
 
-               snd_printdd("hdmi_setup_stream: "
+               snd_printdd("hdmi_pin_hbr_setup: "
                            "NID=0x%x, %spinctl=0x%x\n",
                            pin_nid,
                            pinctl == new_pinctl ? "" : "new-",
@@ -1096,11 +1263,26 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
                        snd_hda_codec_write(codec, pin_nid, 0,
                                            AC_VERB_SET_PIN_WIDGET_CONTROL,
                                            new_pinctl);
+       } else if (hbr)
+               return -EINVAL;
 
-       }
-       if (is_hbr_format(format) && !new_pinctl) {
+       return 0;
+}
+
+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 err;
+
+       if (is_haswell(codec))
+               haswell_verify_D0(codec, cvt_nid, pin_nid);
+
+       err = spec->ops.pin_hbr_setup(codec, pin_nid, is_hbr_format(format));
+
+       if (err) {
                snd_printdd("hdmi_setup_stream: HBR is not supported\n");
-               return -EINVAL;
+               return err;
        }
 
        snd_hda_codec_setup_stream(codec, cvt_nid, stream_tag, 0, format);
@@ -1146,7 +1328,16 @@ static int hdmi_choose_cvt(struct hda_codec *codec,
        return 0;
 }
 
-static void haswell_config_cvts(struct hda_codec *codec,
+/* Intel HDMI workaround to fix audio routing issue:
+ * For some Intel display codecs, pins share the same connection list.
+ * So a conveter can be selected by multiple pins and playback on any of these
+ * pins will generate sound on the external display, because audio flows from
+ * the same converter to the display pipeline. Also muting one pin may make
+ * other pins have no sound output.
+ * So this function assures that an assigned converter for a pin is not selected
+ * by any other pins.
+ */
+static void intel_not_share_assigned_cvt(struct hda_codec *codec,
                        hda_nid_t pin_nid, int mux_idx)
 {
        struct hdmi_spec *spec = codec->spec;
@@ -1217,6 +1408,7 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
        per_cvt = get_cvt(spec, cvt_idx);
        /* Claim converter */
        per_cvt->assigned = 1;
+       per_pin->cvt_nid = per_cvt->cvt_nid;
        hinfo->nid = per_cvt->cvt_nid;
 
        snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0,
@@ -1224,8 +1416,8 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
                            mux_idx);
 
        /* configure unused pins to choose other converters */
-       if (is_haswell(codec))
-               haswell_config_cvts(codec, per_pin->pin_nid, mux_idx);
+       if (is_haswell(codec) || is_valleyview(codec))
+               intel_not_share_assigned_cvt(codec, per_pin->pin_nid, mux_idx);
 
        snd_hda_spdif_ctls_assign(codec, pin_idx, per_cvt->cvt_nid);
 
@@ -1283,7 +1475,7 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
        return 0;
 }
 
-static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
+static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
 {
        struct hda_codec *codec = per_pin->codec;
        struct hdmi_spec *spec = codec->spec;
@@ -1301,7 +1493,9 @@ static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
        int present = snd_hda_pin_sense(codec, pin_nid);
        bool update_eld = false;
        bool eld_changed = false;
+       bool ret;
 
+       mutex_lock(&per_pin->lock);
        pin_eld->monitor_present = !!(present & AC_PINSENSE_PRESENCE);
        if (pin_eld->monitor_present)
                eld->eld_valid  = !!(present & AC_PINSENSE_ELDV);
@@ -1313,7 +1507,7 @@ static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
                codec->addr, pin_nid, pin_eld->monitor_present, eld->eld_valid);
 
        if (eld->eld_valid) {
-               if (snd_hdmi_get_eld(codec, pin_nid, eld->eld_buffer,
+               if (spec->ops.pin_get_eld(codec, pin_nid, eld->eld_buffer,
                                                     &eld->eld_size) < 0)
                        eld->eld_valid = false;
                else {
@@ -1331,11 +1525,10 @@ static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
                        queue_delayed_work(codec->bus->workq,
                                           &per_pin->work,
                                           msecs_to_jiffies(300));
-                       return;
+                       goto unlock;
                }
        }
 
-       mutex_lock(&pin_eld->lock);
        if (pin_eld->eld_valid && !eld->eld_valid) {
                update_eld = true;
                eld_changed = true;
@@ -1352,20 +1545,27 @@ static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
                pin_eld->eld_size = eld->eld_size;
                pin_eld->info = eld->info;
 
-               /* Haswell-specific workaround: re-setup when the transcoder is
-                * changed during the stream playback
+               /*
+                * Re-setup pin and infoframe. This is needed e.g. when
+                * - sink is first plugged-in (infoframe is not set up if !monitor_present)
+                * - transcoder can change during stream playback on Haswell
                 */
-               if (is_haswell(codec) &&
-                   eld->eld_valid && !old_eld_valid && per_pin->setup)
+               if (eld->eld_valid && !old_eld_valid && per_pin->setup)
                        hdmi_setup_audio_infoframe(codec, per_pin,
                                                   per_pin->non_pcm);
        }
-       mutex_unlock(&pin_eld->lock);
 
        if (eld_changed)
                snd_ctl_notify(codec->bus->card,
                               SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO,
                               &per_pin->eld_ctl->id);
+ unlock:
+       if ((codec->vendor_id & 0xffff0000) == 0x10020000)
+               ret = true; /* AMD codecs create ELD by itself */
+       else
+               ret = !repoll || !pin_eld->monitor_present || pin_eld->eld_valid;
+       mutex_unlock(&per_pin->lock);
+       return ret;
 }
 
 static void hdmi_repoll_eld(struct work_struct *work)
@@ -1376,7 +1576,8 @@ static void hdmi_repoll_eld(struct work_struct *work)
        if (per_pin->repoll_count++ > 6)
                per_pin->repoll_count = 0;
 
-       hdmi_present_sense(per_pin, per_pin->repoll_count);
+       if (hdmi_present_sense(per_pin, per_pin->repoll_count))
+               snd_hda_jack_report_sync(per_pin->codec);
 }
 
 static void intel_haswell_fixup_connect_list(struct hda_codec *codec,
@@ -1536,14 +1737,14 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
        bool non_pcm;
 
        non_pcm = check_non_pcm_per_cvt(codec, cvt_nid);
+       mutex_lock(&per_pin->lock);
        per_pin->channels = substream->runtime->channels;
        per_pin->setup = true;
 
-       hdmi_set_channel_count(codec, cvt_nid, substream->runtime->channels);
-
        hdmi_setup_audio_infoframe(codec, per_pin, non_pcm);
+       mutex_unlock(&per_pin->lock);
 
-       return hdmi_setup_stream(codec, cvt_nid, pin_nid, stream_tag, format);
+       return spec->ops.setup_stream(codec, cvt_nid, pin_nid, stream_tag, format);
 }
 
 static int generic_hdmi_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
@@ -1579,11 +1780,14 @@ static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
                per_pin = get_pin(spec, pin_idx);
 
                snd_hda_spdif_ctls_unassign(codec, pin_idx);
+
+               mutex_lock(&per_pin->lock);
                per_pin->chmap_set = false;
                memset(per_pin->chmap, 0, sizeof(per_pin->chmap));
 
                per_pin->setup = false;
                per_pin->channels = 0;
+               mutex_unlock(&per_pin->lock);
        }
 
        return 0;
@@ -1612,14 +1816,40 @@ static int hdmi_chmap_ctl_info(struct snd_kcontrol *kcontrol,
        return 0;
 }
 
+static int hdmi_chmap_cea_alloc_validate_get_type(struct cea_channel_speaker_allocation *cap,
+                                                 int channels)
+{
+       /* If the speaker allocation matches the channel count, it is OK.*/
+       if (cap->channels != channels)
+               return -1;
+
+       /* all channels are remappable freely */
+       return SNDRV_CTL_TLVT_CHMAP_VAR;
+}
+
+static void hdmi_cea_alloc_to_tlv_chmap(struct cea_channel_speaker_allocation *cap,
+                                       unsigned int *chmap, int channels)
+{
+       int count = 0;
+       int c;
+
+       for (c = 7; c >= 0; c--) {
+               int spk = cap->speakers[c];
+               if (!spk)
+                       continue;
+
+               chmap[count++] = spk_to_chmap(spk);
+       }
+
+       WARN_ON(count != channels);
+}
+
 static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
                              unsigned int size, unsigned int __user *tlv)
 {
        struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
        struct hda_codec *codec = info->private_data;
        struct hdmi_spec *spec = codec->spec;
-       const unsigned int valid_mask =
-               FL | FR | RL | RR | LFE | FC | RLC | RRC;
        unsigned int __user *dst;
        int chs, count = 0;
 
@@ -1630,18 +1860,19 @@ static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
        size -= 8;
        dst = tlv + 2;
        for (chs = 2; chs <= spec->channels_max; chs++) {
-               int i, c;
+               int i;
                struct cea_channel_speaker_allocation *cap;
                cap = channel_allocations;
                for (i = 0; i < ARRAY_SIZE(channel_allocations); i++, cap++) {
                        int chs_bytes = chs * 4;
-                       if (cap->channels != chs)
-                               continue;
-                       if (cap->spk_mask & ~valid_mask)
+                       int type = spec->ops.chmap_cea_alloc_validate_get_type(cap, chs);
+                       unsigned int tlv_chmap[8];
+
+                       if (type < 0)
                                continue;
                        if (size < 8)
                                return -ENOMEM;
-                       if (put_user(SNDRV_CTL_TLVT_CHMAP_VAR, dst) ||
+                       if (put_user(type, dst) ||
                            put_user(chs_bytes, dst + 1))
                                return -EFAULT;
                        dst += 2;
@@ -1651,14 +1882,10 @@ static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
                                return -ENOMEM;
                        size -= chs_bytes;
                        count += chs_bytes;
-                       for (c = 7; c >= 0; c--) {
-                               int spk = cap->speakers[c];
-                               if (!spk)
-                                       continue;
-                               if (put_user(spk_to_chmap(spk), dst))
-                                       return -EFAULT;
-                               dst++;
-                       }
+                       spec->ops.cea_alloc_to_tlv_chmap(cap, tlv_chmap, chs);
+                       if (copy_to_user(dst, tlv_chmap, chs_bytes))
+                               return -EFAULT;
+                       dst += chs;
                }
        }
        if (put_user(count, tlv + 1))
@@ -1692,7 +1919,7 @@ static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
        unsigned int ctl_idx;
        struct snd_pcm_substream *substream;
        unsigned char chmap[8];
-       int i, ca, prepared = 0;
+       int i, err, ca, prepared = 0;
 
        ctl_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
        substream = snd_pcm_chmap_substream(info, ctl_idx);
@@ -1716,10 +1943,17 @@ static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
        ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap);
        if (ca < 0)
                return -EINVAL;
+       if (spec->ops.chmap_validate) {
+               err = spec->ops.chmap_validate(ca, ARRAY_SIZE(chmap), chmap);
+               if (err)
+                       return err;
+       }
+       mutex_lock(&per_pin->lock);
        per_pin->chmap_set = true;
        memcpy(per_pin->chmap, chmap, sizeof(chmap));
        if (prepared)
                hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm);
+       mutex_unlock(&per_pin->lock);
 
        return 0;
 }
@@ -1836,12 +2070,11 @@ static int generic_hdmi_init_per_pins(struct hda_codec *codec)
 
        for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
                struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
-               struct hdmi_eld *eld = &per_pin->sink_eld;
 
                per_pin->codec = codec;
-               mutex_init(&eld->lock);
+               mutex_init(&per_pin->lock);
                INIT_DELAYED_WORK(&per_pin->work, hdmi_repoll_eld);
-               snd_hda_eld_proc_new(codec, eld, pin_idx);
+               eld_proc_new(per_pin, pin_idx);
        }
        return 0;
 }
@@ -1882,10 +2115,9 @@ static void generic_hdmi_free(struct hda_codec *codec)
 
        for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
                struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
-               struct hdmi_eld *eld = &per_pin->sink_eld;
 
                cancel_delayed_work(&per_pin->work);
-               snd_hda_eld_proc_free(codec, eld);
+               eld_proc_free(per_pin);
        }
 
        flush_workqueue(codec->bus->workq);
@@ -1922,6 +2154,17 @@ static const struct hda_codec_ops generic_hdmi_patch_ops = {
 #endif
 };
 
+static const struct hdmi_ops generic_standard_hdmi_ops = {
+       .pin_get_eld                            = snd_hdmi_get_eld,
+       .pin_get_slot_channel                   = hdmi_pin_get_slot_channel,
+       .pin_set_slot_channel                   = hdmi_pin_set_slot_channel,
+       .pin_setup_infoframe                    = hdmi_pin_setup_infoframe,
+       .pin_hbr_setup                          = hdmi_pin_hbr_setup,
+       .setup_stream                           = hdmi_setup_stream,
+       .chmap_cea_alloc_validate_get_type      = hdmi_chmap_cea_alloc_validate_get_type,
+       .cea_alloc_to_tlv_chmap                 = hdmi_cea_alloc_to_tlv_chmap,
+};
+
 
 static void intel_haswell_fixup_connect_list(struct hda_codec *codec,
                                             hda_nid_t nid)
@@ -2004,6 +2247,7 @@ static int patch_generic_hdmi(struct hda_codec *codec)
        if (spec == NULL)
                return -ENOMEM;
 
+       spec->ops = generic_standard_hdmi_ops;
        codec->spec = spec;
        hdmi_array_init(spec, 4);
 
@@ -2559,49 +2803,398 @@ static int patch_nvhdmi_8ch_7x(struct hda_codec *codec)
 }
 
 /*
- * ATI-specific implementations
- *
- * FIXME: we may omit the whole this and use the generic code once after
- * it's confirmed to work.
+ * NVIDIA codecs ignore ASP mapping for 2ch - confirmed on:
+ * - 0x10de0015
+ * - 0x10de0040
  */
+static int nvhdmi_chmap_cea_alloc_validate_get_type(struct cea_channel_speaker_allocation *cap,
+                                                   int channels)
+{
+       if (cap->ca_index == 0x00 && channels == 2)
+               return SNDRV_CTL_TLVT_CHMAP_FIXED;
 
-#define ATIHDMI_CVT_NID                0x02    /* audio converter */
-#define ATIHDMI_PIN_NID                0x03    /* HDMI output pin */
+       return hdmi_chmap_cea_alloc_validate_get_type(cap, channels);
+}
 
-static int atihdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
-                                       struct hda_codec *codec,
-                                       unsigned int stream_tag,
-                                       unsigned int format,
-                                       struct snd_pcm_substream *substream)
+static int nvhdmi_chmap_validate(int ca, int chs, unsigned char *map)
+{
+       if (ca == 0x00 && (map[0] != SNDRV_CHMAP_FL || map[1] != SNDRV_CHMAP_FR))
+               return -EINVAL;
+
+       return 0;
+}
+
+static int patch_nvhdmi(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec;
+       int err;
+
+       err = patch_generic_hdmi(codec);
+       if (err)
+               return err;
+
+       spec = codec->spec;
+
+       spec->ops.chmap_cea_alloc_validate_get_type =
+               nvhdmi_chmap_cea_alloc_validate_get_type;
+       spec->ops.chmap_validate = nvhdmi_chmap_validate;
+
+       return 0;
+}
+
+/*
+ * ATI/AMD-specific implementations
+ */
+
+#define is_amdhdmi_rev3_or_later(codec) \
+       ((codec)->vendor_id == 0x1002aa01 && ((codec)->revision_id & 0xff00) >= 0x0300)
+#define has_amd_full_remap_support(codec) is_amdhdmi_rev3_or_later(codec)
+
+/* ATI/AMD specific HDA pin verbs, see the AMD HDA Verbs specification */
+#define ATI_VERB_SET_CHANNEL_ALLOCATION        0x771
+#define ATI_VERB_SET_DOWNMIX_INFO      0x772
+#define ATI_VERB_SET_MULTICHANNEL_01   0x777
+#define ATI_VERB_SET_MULTICHANNEL_23   0x778
+#define ATI_VERB_SET_MULTICHANNEL_45   0x779
+#define ATI_VERB_SET_MULTICHANNEL_67   0x77a
+#define ATI_VERB_SET_HBR_CONTROL       0x77c
+#define ATI_VERB_SET_MULTICHANNEL_1    0x785
+#define ATI_VERB_SET_MULTICHANNEL_3    0x786
+#define ATI_VERB_SET_MULTICHANNEL_5    0x787
+#define ATI_VERB_SET_MULTICHANNEL_7    0x788
+#define ATI_VERB_SET_MULTICHANNEL_MODE 0x789
+#define ATI_VERB_GET_CHANNEL_ALLOCATION        0xf71
+#define ATI_VERB_GET_DOWNMIX_INFO      0xf72
+#define ATI_VERB_GET_MULTICHANNEL_01   0xf77
+#define ATI_VERB_GET_MULTICHANNEL_23   0xf78
+#define ATI_VERB_GET_MULTICHANNEL_45   0xf79
+#define ATI_VERB_GET_MULTICHANNEL_67   0xf7a
+#define ATI_VERB_GET_HBR_CONTROL       0xf7c
+#define ATI_VERB_GET_MULTICHANNEL_1    0xf85
+#define ATI_VERB_GET_MULTICHANNEL_3    0xf86
+#define ATI_VERB_GET_MULTICHANNEL_5    0xf87
+#define ATI_VERB_GET_MULTICHANNEL_7    0xf88
+#define ATI_VERB_GET_MULTICHANNEL_MODE 0xf89
+
+/* AMD specific HDA cvt verbs */
+#define ATI_VERB_SET_RAMP_RATE         0x770
+#define ATI_VERB_GET_RAMP_RATE         0xf70
+
+#define ATI_OUT_ENABLE 0x1
+
+#define ATI_MULTICHANNEL_MODE_PAIRED   0
+#define ATI_MULTICHANNEL_MODE_SINGLE   1
+
+#define ATI_HBR_CAPABLE 0x01
+#define ATI_HBR_ENABLE 0x10
+
+static int atihdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid,
+                          unsigned char *buf, int *eld_size)
+{
+       /* call hda_eld.c ATI/AMD-specific function */
+       return snd_hdmi_get_eld_ati(codec, nid, buf, eld_size,
+                                   is_amdhdmi_rev3_or_later(codec));
+}
+
+static void atihdmi_pin_setup_infoframe(struct hda_codec *codec, hda_nid_t pin_nid, int ca,
+                                       int active_channels, int conn_type)
+{
+       snd_hda_codec_write(codec, pin_nid, 0, ATI_VERB_SET_CHANNEL_ALLOCATION, ca);
+}
+
+static int atihdmi_paired_swap_fc_lfe(int pos)
+{
+       /*
+        * ATI/AMD have automatic FC/LFE swap built-in
+        * when in pairwise mapping mode.
+        */
+
+       switch (pos) {
+               /* see channel_allocations[].speakers[] */
+               case 2: return 3;
+               case 3: return 2;
+               default: break;
+       }
+
+       return pos;
+}
+
+static int atihdmi_paired_chmap_validate(int ca, int chs, unsigned char *map)
+{
+       struct cea_channel_speaker_allocation *cap;
+       int i, j;
+
+       /* check that only channel pairs need to be remapped on old pre-rev3 ATI/AMD */
+
+       cap = &channel_allocations[get_channel_allocation_order(ca)];
+       for (i = 0; i < chs; ++i) {
+               int mask = to_spk_mask(map[i]);
+               bool ok = false;
+               bool companion_ok = false;
+
+               if (!mask)
+                       continue;
+
+               for (j = 0 + i % 2; j < 8; j += 2) {
+                       int chan_idx = 7 - atihdmi_paired_swap_fc_lfe(j);
+                       if (cap->speakers[chan_idx] == mask) {
+                               /* channel is in a supported position */
+                               ok = true;
+
+                               if (i % 2 == 0 && i + 1 < chs) {
+                                       /* even channel, check the odd companion */
+                                       int comp_chan_idx = 7 - atihdmi_paired_swap_fc_lfe(j + 1);
+                                       int comp_mask_req = to_spk_mask(map[i+1]);
+                                       int comp_mask_act = cap->speakers[comp_chan_idx];
+
+                                       if (comp_mask_req == comp_mask_act)
+                                               companion_ok = true;
+                                       else
+                                               return -EINVAL;
+                               }
+                               break;
+                       }
+               }
+
+               if (!ok)
+                       return -EINVAL;
+
+               if (companion_ok)
+                       i++; /* companion channel already checked */
+       }
+
+       return 0;
+}
+
+static int atihdmi_pin_set_slot_channel(struct hda_codec *codec, hda_nid_t pin_nid,
+                                       int hdmi_slot, int stream_channel)
+{
+       int verb;
+       int ati_channel_setup = 0;
+
+       if (hdmi_slot > 7)
+               return -EINVAL;
+
+       if (!has_amd_full_remap_support(codec)) {
+               hdmi_slot = atihdmi_paired_swap_fc_lfe(hdmi_slot);
+
+               /* In case this is an odd slot but without stream channel, do not
+                * disable the slot since the corresponding even slot could have a
+                * channel. In case neither have a channel, the slot pair will be
+                * disabled when this function is called for the even slot. */
+               if (hdmi_slot % 2 != 0 && stream_channel == 0xf)
+                       return 0;
+
+               hdmi_slot -= hdmi_slot % 2;
+
+               if (stream_channel != 0xf)
+                       stream_channel -= stream_channel % 2;
+       }
+
+       verb = ATI_VERB_SET_MULTICHANNEL_01 + hdmi_slot/2 + (hdmi_slot % 2) * 0x00e;
+
+       /* ati_channel_setup format: [7..4] = stream_channel_id, [1] = mute, [0] = enable */
+
+       if (stream_channel != 0xf)
+               ati_channel_setup = (stream_channel << 4) | ATI_OUT_ENABLE;
+
+       return snd_hda_codec_write(codec, pin_nid, 0, verb, ati_channel_setup);
+}
+
+static int atihdmi_pin_get_slot_channel(struct hda_codec *codec, hda_nid_t pin_nid,
+                                       int asp_slot)
+{
+       bool was_odd = false;
+       int ati_asp_slot = asp_slot;
+       int verb;
+       int ati_channel_setup;
+
+       if (asp_slot > 7)
+               return -EINVAL;
+
+       if (!has_amd_full_remap_support(codec)) {
+               ati_asp_slot = atihdmi_paired_swap_fc_lfe(asp_slot);
+               if (ati_asp_slot % 2 != 0) {
+                       ati_asp_slot -= 1;
+                       was_odd = true;
+               }
+       }
+
+       verb = ATI_VERB_GET_MULTICHANNEL_01 + ati_asp_slot/2 + (ati_asp_slot % 2) * 0x00e;
+
+       ati_channel_setup = snd_hda_codec_read(codec, pin_nid, 0, verb, 0);
+
+       if (!(ati_channel_setup & ATI_OUT_ENABLE))
+               return 0xf;
+
+       return ((ati_channel_setup & 0xf0) >> 4) + !!was_odd;
+}
+
+static int atihdmi_paired_chmap_cea_alloc_validate_get_type(struct cea_channel_speaker_allocation *cap,
+                                                           int channels)
+{
+       int c;
+
+       /*
+        * Pre-rev3 ATI/AMD codecs operate in a paired channel mode, so
+        * we need to take that into account (a single channel may take 2
+        * channel slots if we need to carry a silent channel next to it).
+        * On Rev3+ AMD codecs this function is not used.
+        */
+       int chanpairs = 0;
+
+       /* We only produce even-numbered channel count TLVs */
+       if ((channels % 2) != 0)
+               return -1;
+
+       for (c = 0; c < 7; c += 2) {
+               if (cap->speakers[c] || cap->speakers[c+1])
+                       chanpairs++;
+       }
+
+       if (chanpairs * 2 != channels)
+               return -1;
+
+       return SNDRV_CTL_TLVT_CHMAP_PAIRED;
+}
+
+static void atihdmi_paired_cea_alloc_to_tlv_chmap(struct cea_channel_speaker_allocation *cap,
+                                                 unsigned int *chmap, int channels)
+{
+       /* produce paired maps for pre-rev3 ATI/AMD codecs */
+       int count = 0;
+       int c;
+
+       for (c = 7; c >= 0; c--) {
+               int chan = 7 - atihdmi_paired_swap_fc_lfe(7 - c);
+               int spk = cap->speakers[chan];
+               if (!spk) {
+                       /* add N/A channel if the companion channel is occupied */
+                       if (cap->speakers[chan + (chan % 2 ? -1 : 1)])
+                               chmap[count++] = SNDRV_CHMAP_NA;
+
+                       continue;
+               }
+
+               chmap[count++] = spk_to_chmap(spk);
+       }
+
+       WARN_ON(count != channels);
+}
+
+static int atihdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid,
+                                bool hbr)
+{
+       int hbr_ctl, hbr_ctl_new;
+
+       hbr_ctl = snd_hda_codec_read(codec, pin_nid, 0, ATI_VERB_GET_HBR_CONTROL, 0);
+       if (hbr_ctl & ATI_HBR_CAPABLE) {
+               if (hbr)
+                       hbr_ctl_new = hbr_ctl | ATI_HBR_ENABLE;
+               else
+                       hbr_ctl_new = hbr_ctl & ~ATI_HBR_ENABLE;
+
+               snd_printdd("atihdmi_pin_hbr_setup: "
+                               "NID=0x%x, %shbr-ctl=0x%x\n",
+                               pin_nid,
+                               hbr_ctl == hbr_ctl_new ? "" : "new-",
+                               hbr_ctl_new);
+
+               if (hbr_ctl != hbr_ctl_new)
+                       snd_hda_codec_write(codec, pin_nid, 0,
+                                               ATI_VERB_SET_HBR_CONTROL,
+                                               hbr_ctl_new);
+
+       } else if (hbr)
+               return -EINVAL;
+
+       return 0;
+}
+
+static int atihdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
+                               hda_nid_t pin_nid, u32 stream_tag, int format)
+{
+
+       if (is_amdhdmi_rev3_or_later(codec)) {
+               int ramp_rate = 180; /* default as per AMD spec */
+               /* disable ramp-up/down for non-pcm as per AMD spec */
+               if (format & AC_FMT_TYPE_NON_PCM)
+                       ramp_rate = 0;
+
+               snd_hda_codec_write(codec, cvt_nid, 0, ATI_VERB_SET_RAMP_RATE, ramp_rate);
+       }
+
+       return hdmi_setup_stream(codec, cvt_nid, pin_nid, stream_tag, format);
+}
+
+
+static int atihdmi_init(struct hda_codec *codec)
 {
        struct hdmi_spec *spec = codec->spec;
-       struct hdmi_spec_per_cvt *per_cvt = get_cvt(spec, 0);
-       int chans = substream->runtime->channels;
-       int i, err;
+       int pin_idx, err;
 
-       err = simple_playback_pcm_prepare(hinfo, codec, stream_tag, format,
-                                         substream);
-       if (err < 0)
+       err = generic_hdmi_init(codec);
+
+       if (err)
                return err;
-       snd_hda_codec_write(codec, per_cvt->cvt_nid, 0,
-                           AC_VERB_SET_CVT_CHAN_COUNT, chans - 1);
-       /* FIXME: XXX */
-       for (i = 0; i < chans; i++) {
-               snd_hda_codec_write(codec, per_cvt->cvt_nid, 0,
-                                   AC_VERB_SET_HDMI_CHAN_SLOT,
-                                   (i << 4) | i);
+
+       for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+               struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+
+               /* make sure downmix information in infoframe is zero */
+               snd_hda_codec_write(codec, per_pin->pin_nid, 0, ATI_VERB_SET_DOWNMIX_INFO, 0);
+
+               /* enable channel-wise remap mode if supported */
+               if (has_amd_full_remap_support(codec))
+                       snd_hda_codec_write(codec, per_pin->pin_nid, 0,
+                                           ATI_VERB_SET_MULTICHANNEL_MODE,
+                                           ATI_MULTICHANNEL_MODE_SINGLE);
        }
+
        return 0;
 }
 
 static int patch_atihdmi(struct hda_codec *codec)
 {
        struct hdmi_spec *spec;
-       int err = patch_simple_hdmi(codec, ATIHDMI_CVT_NID, ATIHDMI_PIN_NID);
-       if (err < 0)
+       struct hdmi_spec_per_cvt *per_cvt;
+       int err, cvt_idx;
+
+       err = patch_generic_hdmi(codec);
+
+       if (err)
                return err;
+
+       codec->patch_ops.init = atihdmi_init;
+
        spec = codec->spec;
-       spec->pcm_playback.ops.prepare = atihdmi_playback_pcm_prepare;
+
+       spec->ops.pin_get_eld = atihdmi_pin_get_eld;
+       spec->ops.pin_get_slot_channel = atihdmi_pin_get_slot_channel;
+       spec->ops.pin_set_slot_channel = atihdmi_pin_set_slot_channel;
+       spec->ops.pin_setup_infoframe = atihdmi_pin_setup_infoframe;
+       spec->ops.pin_hbr_setup = atihdmi_pin_hbr_setup;
+       spec->ops.setup_stream = atihdmi_setup_stream;
+
+       if (!has_amd_full_remap_support(codec)) {
+               /* override to ATI/AMD-specific versions with pairwise mapping */
+               spec->ops.chmap_cea_alloc_validate_get_type =
+                       atihdmi_paired_chmap_cea_alloc_validate_get_type;
+               spec->ops.cea_alloc_to_tlv_chmap = atihdmi_paired_cea_alloc_to_tlv_chmap;
+               spec->ops.chmap_validate = atihdmi_paired_chmap_validate;
+       }
+
+       /* ATI/AMD converters do not advertise all of their capabilities */
+       for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
+               per_cvt = get_cvt(spec, cvt_idx);
+               per_cvt->channels_max = max(per_cvt->channels_max, 8u);
+               per_cvt->rates |= SUPPORTED_RATES;
+               per_cvt->formats |= SUPPORTED_FORMATS;
+               per_cvt->maxbps = max(per_cvt->maxbps, 24u);
+       }
+
+       spec->channels_max = max(spec->channels_max, 8u);
+
        return 0;
 }
 
@@ -2621,7 +3214,7 @@ 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 },
-{ .id = 0x1002aa01, .name = "R6xx HDMI",       .patch = patch_generic_hdmi },
+{ .id = 0x1002aa01, .name = "R6xx HDMI",       .patch = patch_atihdmi },
 { .id = 0x10951390, .name = "SiI1390 HDMI",    .patch = patch_generic_hdmi },
 { .id = 0x10951392, .name = "SiI1392 HDMI",    .patch = patch_generic_hdmi },
 { .id = 0x17e80047, .name = "Chrontel HDMI",   .patch = patch_generic_hdmi },
@@ -2630,30 +3223,30 @@ static const 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_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 },
+{ .id = 0x10de000a, .name = "GPU 0a HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de000b, .name = "GPU 0b HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de000c, .name = "MCP89 HDMI",      .patch = patch_nvhdmi },
+{ .id = 0x10de000d, .name = "GPU 0d HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0010, .name = "GPU 10 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0011, .name = "GPU 11 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0012, .name = "GPU 12 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0013, .name = "GPU 13 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0014, .name = "GPU 14 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0015, .name = "GPU 15 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0016, .name = "GPU 16 HDMI/DP",  .patch = patch_nvhdmi },
 /* 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 = 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 = 0x10de0051, .name = "GPU 51 HDMI/DP",  .patch = patch_generic_hdmi },
-{ .id = 0x10de0060, .name = "GPU 60 HDMI/DP",  .patch = patch_generic_hdmi },
+{ .id = 0x10de0018, .name = "GPU 18 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0019, .name = "GPU 19 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de001a, .name = "GPU 1a HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de001b, .name = "GPU 1b HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de001c, .name = "GPU 1c HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0040, .name = "GPU 40 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0041, .name = "GPU 41 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0042, .name = "GPU 42 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0043, .name = "GPU 43 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0044, .name = "GPU 44 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0051, .name = "GPU 51 HDMI/DP",  .patch = patch_nvhdmi },
+{ .id = 0x10de0060, .name = "GPU 60 HDMI/DP",  .patch = patch_nvhdmi },
 { .id = 0x10de0067, .name = "MCP67 HDMI",      .patch = patch_nvhdmi_2ch },
 { .id = 0x10de8001, .name = "MCP73 HDMI",      .patch = patch_nvhdmi_2ch },
 { .id = 0x11069f80, .name = "VX900 HDMI/DP",   .patch = patch_via_hdmi },
@@ -2669,6 +3262,7 @@ static const struct hda_codec_preset snd_hda_preset_hdmi[] = {
 { .id = 0x80862806, .name = "PantherPoint HDMI", .patch = patch_generic_hdmi },
 { .id = 0x80862807, .name = "Haswell HDMI",    .patch = patch_generic_hdmi },
 { .id = 0x80862880, .name = "CedarTrail HDMI", .patch = patch_generic_hdmi },
+{ .id = 0x80862882, .name = "Valleyview2 HDMI",        .patch = patch_generic_hdmi },
 { .id = 0x808629fb, .name = "Crestline HDMI",  .patch = patch_generic_hdmi },
 {} /* terminator */
 };
@@ -2723,6 +3317,7 @@ MODULE_ALIAS("snd-hda-codec-id:80862805");
 MODULE_ALIAS("snd-hda-codec-id:80862806");
 MODULE_ALIAS("snd-hda-codec-id:80862807");
 MODULE_ALIAS("snd-hda-codec-id:80862880");
+MODULE_ALIAS("snd-hda-codec-id:80862882");
 MODULE_ALIAS("snd-hda-codec-id:808629fb");
 
 MODULE_LICENSE("GPL");