]> git.karo-electronics.de Git - linux-beck.git/blobdiff - sound/pci/hda/patch_hdmi.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/roland...
[linux-beck.git] / sound / pci / hda / patch_hdmi.c
index 2bc0f07cf33fe4f67720fc3c9487a817b3e77823..31df7747990d0be6690a520321a0498c9caba349 100644 (file)
@@ -3,6 +3,9 @@
  *  patch_hdmi.c - routines for HDMI/DisplayPort codecs
  *
  *  Copyright(c) 2008-2010 Intel Corporation. All rights reserved.
+ *  Copyright (c) 2006 ATI Technologies Inc.
+ *  Copyright (c) 2008 NVIDIA Corp.  All rights reserved.
+ *  Copyright (c) 2008 Wei Ni <wni@nvidia.com>
  *
  *  Authors:
  *                     Wu Fengguang <wfg@linux.intel.com>
  *  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
 
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+/*
+ * 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
+ * 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
 
 struct hdmi_spec {
        int num_cvts;
@@ -49,10 +68,10 @@ struct hdmi_spec {
        struct hda_pcm_stream codec_pcm_pars[MAX_HDMI_CVTS];
 
        /*
-        * nvhdmi specific
+        * ati/nvhdmi specific
         */
        struct hda_multi_out multiout;
-       unsigned int codec_type;
+       struct hda_pcm_stream *pcm_playback;
 
        /* misc flags */
        /* PD bit indicates only the update, not the current state */
@@ -65,13 +84,25 @@ struct hdmi_audio_infoframe {
        u8 ver;  /* 0x01 */
        u8 len;  /* 0x0a */
 
-       u8 checksum;    /* PB0 */
+       u8 checksum;
+
        u8 CC02_CT47;   /* CC in bits 0:2, CT in 4:7 */
        u8 SS01_SF24;
        u8 CXT04;
        u8 CA;
        u8 LFEPBL01_LSV36_DM_INH7;
-       u8 reserved[5]; /* PB6 - PB10 */
+};
+
+struct dp_audio_infoframe {
+       u8 type; /* 0x84 */
+       u8 len;  /* 0x1b */
+       u8 ver;  /* 0x11 << 2 */
+
+       u8 CC02_CT47;   /* match with HDMI infoframe from this on */
+       u8 SS01_SF24;
+       u8 CXT04;
+       u8 CA;
+       u8 LFEPBL01_LSV36_DM_INH7;
 };
 
 /*
@@ -162,7 +193,7 @@ static int hdmi_channel_mapping[0x32][8] = {
        /* 4ch */
        [0x03] = { 0x00, 0x11, 0x23, 0x32, 0x44, 0xf5, 0xf6, 0xf7 },
        /* surround41 */
-       [0x09] = { 0x00, 0x11, 0x24, 0x34, 0x43, 0xf2, 0xf6, 0xf7 },
+       [0x09] = { 0x00, 0x11, 0x24, 0x35, 0x42, 0xf3, 0xf6, 0xf7 },
        /* surround50 */
        [0x0a] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0xf2, 0xf6, 0xf7 },
        /* surround51 */
@@ -175,7 +206,7 @@ static int hdmi_channel_mapping[0x32][8] = {
  * This is an ordered list!
  *
  * The preceding ones have better chances to be selected by
- * hdmi_setup_channel_allocation().
+ * hdmi_channel_allocation().
  */
 static struct cea_channel_speaker_allocation channel_allocations[] = {
 /*                       channel:   7     6    5    4    3     2    1    0  */
@@ -352,14 +383,14 @@ static void init_channel_allocations(void)
  *
  * TODO: it could select the wrong CA from multiple candidates.
 */
-static int hdmi_setup_channel_allocation(struct hda_codec *codec, hda_nid_t nid,
-                                        struct hdmi_audio_infoframe *ai)
+static int hdmi_channel_allocation(struct hda_codec *codec, hda_nid_t nid,
+                                  int channels)
 {
        struct hdmi_spec *spec = codec->spec;
        struct hdmi_eld *eld;
        int i;
+       int ca = 0;
        int spk_mask = 0;
-       int channels = 1 + (ai->CC02_CT47 & 0x7);
        char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
 
        /*
@@ -397,16 +428,16 @@ static int hdmi_setup_channel_allocation(struct hda_codec *codec, hda_nid_t nid,
                if (channels == channel_allocations[i].channels &&
                    (spk_mask & channel_allocations[i].spk_mask) ==
                                channel_allocations[i].spk_mask) {
-                       ai->CA = channel_allocations[i].ca_index;
+                       ca = channel_allocations[i].ca_index;
                        break;
                }
        }
 
        snd_print_channel_allocation(eld->spk_alloc, buf, sizeof(buf));
        snd_printdd("HDMI: select CA 0x%x for %d-channel allocation: %s\n",
-                   ai->CA, channels, buf);
+                   ca, channels, buf);
 
-       return ai->CA;
+       return ca;
 }
 
 static void hdmi_debug_channel_mapping(struct hda_codec *codec,
@@ -428,10 +459,9 @@ static void hdmi_debug_channel_mapping(struct hda_codec *codec,
 
 static void hdmi_setup_channel_mapping(struct hda_codec *codec,
                                       hda_nid_t pin_nid,
-                                      struct hdmi_audio_infoframe *ai)
+                                      int ca)
 {
        int i;
-       int ca = ai->CA;
        int err;
 
        if (hdmi_channel_mapping[ca][1] == 0) {
@@ -528,41 +558,37 @@ static void hdmi_clear_dip_buffers(struct hda_codec *codec, hda_nid_t pin_nid)
 #endif
 }
 
-static void hdmi_checksum_audio_infoframe(struct hdmi_audio_infoframe *ai)
+static void hdmi_checksum_audio_infoframe(struct hdmi_audio_infoframe *hdmi_ai)
 {
-       u8 *bytes = (u8 *)ai;
+       u8 *bytes = (u8 *)hdmi_ai;
        u8 sum = 0;
        int i;
 
-       ai->checksum = 0;
+       hdmi_ai->checksum = 0;
 
-       for (i = 0; i < sizeof(*ai); i++)
+       for (i = 0; i < sizeof(*hdmi_ai); i++)
                sum += bytes[i];
 
-       ai->checksum = -sum;
+       hdmi_ai->checksum = -sum;
 }
 
 static void hdmi_fill_audio_infoframe(struct hda_codec *codec,
                                      hda_nid_t pin_nid,
-                                     struct hdmi_audio_infoframe *ai)
+                                     u8 *dip, int size)
 {
-       u8 *bytes = (u8 *)ai;
        int i;
 
        hdmi_debug_dip_size(codec, pin_nid);
        hdmi_clear_dip_buffers(codec, pin_nid); /* be paranoid */
 
-       hdmi_checksum_audio_infoframe(ai);
-
        hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
-       for (i = 0; i < sizeof(*ai); i++)
-               hdmi_write_dip_byte(codec, pin_nid, bytes[i]);
+       for (i = 0; i < size; i++)
+               hdmi_write_dip_byte(codec, pin_nid, dip[i]);
 }
 
 static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid,
-                                   struct hdmi_audio_infoframe *ai)
+                                   u8 *dip, int size)
 {
-       u8 *bytes = (u8 *)ai;
        u8 val;
        int i;
 
@@ -571,10 +597,10 @@ static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid,
                return false;
 
        hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
-       for (i = 0; i < sizeof(*ai); i++) {
+       for (i = 0; i < size; i++) {
                val = snd_hda_codec_read(codec, pin_nid, 0,
                                         AC_VERB_GET_HDMI_DIP_DATA, 0);
-               if (val != bytes[i])
+               if (val != dip[i])
                        return false;
        }
 
@@ -586,15 +612,13 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, hda_nid_t nid,
 {
        struct hdmi_spec *spec = codec->spec;
        hda_nid_t pin_nid;
+       int channels = substream->runtime->channels;
+       int ca;
        int i;
-       struct hdmi_audio_infoframe ai = {
-               .type           = 0x84,
-               .ver            = 0x01,
-               .len            = 0x0a,
-               .CC02_CT47      = substream->runtime->channels - 1,
-       };
+       u8 ai[max(sizeof(struct hdmi_audio_infoframe),
+                 sizeof(struct dp_audio_infoframe))];
 
-       hdmi_setup_channel_allocation(codec, nid, &ai);
+       ca = hdmi_channel_allocation(codec, nid, channels);
 
        for (i = 0; i < spec->num_pins; i++) {
                if (spec->pin_cvt[i] != nid)
@@ -603,14 +627,45 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, hda_nid_t nid,
                        continue;
 
                pin_nid = spec->pin[i];
-               if (!hdmi_infoframe_uptodate(codec, pin_nid, &ai)) {
+
+               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_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;
+               } else {
+                       snd_printd("HDMI: unknown connection type at pin %d\n",
+                                  pin_nid);
+                       continue;
+               }
+
+               /*
+                * 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,
-                                   substream->runtime->channels);
-                       hdmi_setup_channel_mapping(codec, pin_nid, &ai);
+                                   channels);
+                       hdmi_setup_channel_mapping(codec, pin_nid, ca);
                        hdmi_stop_infoframe_trans(codec, pin_nid);
-                       hdmi_fill_audio_infoframe(codec, pin_nid, &ai);
+                       hdmi_fill_audio_infoframe(codec, pin_nid,
+                                                 ai, sizeof(ai));
                        hdmi_start_infoframe_trans(codec, pin_nid);
                }
        }
@@ -707,8 +762,6 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid,
                              u32 stream_tag, int format)
 {
        struct hdmi_spec *spec = codec->spec;
-       int tag;
-       int fmt;
        int pinctl;
        int new_pinctl = 0;
        int i;
@@ -745,24 +798,7 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid,
                return -EINVAL;
        }
 
-       tag = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0) >> 4;
-       fmt = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_STREAM_FORMAT, 0);
-
-       snd_printdd("hdmi_setup_stream: "
-                   "NID=0x%x, %sstream=0x%x, %sformat=0x%x\n",
-                   nid,
-                   tag == stream_tag ? "" : "new-",
-                   stream_tag,
-                   fmt == format ? "" : "new-",
-                   format);
-
-       if (tag != stream_tag)
-               snd_hda_codec_write(codec, nid, 0,
-                                   AC_VERB_SET_CHANNEL_STREAMID,
-                                   stream_tag << 4);
-       if (fmt != format)
-               snd_hda_codec_write(codec, nid, 0,
-                                   AC_VERB_SET_STREAM_FORMAT, format);
+       snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format);
        return 0;
 }
 
@@ -798,7 +834,6 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
                        return -ENODEV;
        } else {
                /* fallback to the codec default */
-               hinfo->channels_min = codec_pars->channels_min;
                hinfo->channels_max = codec_pars->channels_max;
                hinfo->rates = codec_pars->rates;
                hinfo->formats = codec_pars->formats;
@@ -810,7 +845,6 @@ 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)
 {
        struct hdmi_spec *spec = codec->spec;
@@ -941,3 +975,664 @@ static int hdmi_parse_codec(struct hda_codec *codec)
        return 0;
 }
 
+/*
+ */
+static char *generic_hdmi_pcm_names[MAX_HDMI_CVTS] = {
+       "HDMI 0",
+       "HDMI 1",
+       "HDMI 2",
+};
+
+/*
+ * HDMI callbacks
+ */
+
+static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+                                          struct hda_codec *codec,
+                                          unsigned int stream_tag,
+                                          unsigned int format,
+                                          struct snd_pcm_substream *substream)
+{
+       hdmi_set_channel_count(codec, hinfo->nid,
+                              substream->runtime->channels);
+
+       hdmi_setup_audio_infoframe(codec, hinfo->nid, substream);
+
+       return hdmi_setup_stream(codec, hinfo->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_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->cvt[i]);
+               chans = get_wcaps_channels(chans);
+
+               info->name = generic_hdmi_pcm_names[i];
+               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;
+       }
+
+       return 0;
+}
+
+static int generic_hdmi_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->cvt[i]);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
+static int generic_hdmi_init(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec = codec->spec;
+       int i;
+
+       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]);
+       }
+       return 0;
+}
+
+static void generic_hdmi_free(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec = codec->spec;
+       int i;
+
+       for (i = 0; i < spec->num_pins; i++)
+               snd_hda_eld_proc_free(codec, &spec->sink_eld[i]);
+
+       kfree(spec);
+}
+
+static struct hda_codec_ops generic_hdmi_patch_ops = {
+       .init                   = generic_hdmi_init,
+       .free                   = generic_hdmi_free,
+       .build_pcms             = generic_hdmi_build_pcms,
+       .build_controls         = generic_hdmi_build_controls,
+       .unsol_event            = hdmi_unsol_event,
+};
+
+static int patch_generic_hdmi(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec;
+       int i;
+
+       spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+       if (spec == NULL)
+               return -ENOMEM;
+
+       codec->spec = spec;
+       if (hdmi_parse_codec(codec) < 0) {
+               codec->spec = NULL;
+               kfree(spec);
+               return -EINVAL;
+       }
+       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;
+}
+
+/*
+ * Nvidia specific implementations
+ */
+
+#define Nv_VERB_SET_Channel_Allocation          0xF79
+#define Nv_VERB_SET_Info_Frame_Checksum         0xF7A
+#define Nv_VERB_SET_Audio_Protection_On         0xF98
+#define Nv_VERB_SET_Audio_Protection_Off        0xF99
+
+#define nvhdmi_master_con_nid_7x       0x04
+#define nvhdmi_master_pin_nid_7x       0x05
+
+static 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[] = {
+       /* set audio protect on */
+       { 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1},
+       /* enable digital output on pin widget */
+       { 0x5, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+       { 0x7, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+       { 0x9, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+       { 0xb, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+       { 0xd, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+       {} /* terminator */
+};
+
+#ifdef LIMITED_RATE_FMT_SUPPORT
+/* support only the safe format and rate */
+#define SUPPORTED_RATES                SNDRV_PCM_RATE_48000
+#define SUPPORTED_MAXBPS       16
+#define SUPPORTED_FORMATS      SNDRV_PCM_FMTBIT_S16_LE
+#else
+/* support all rates and formats */
+#define SUPPORTED_RATES \
+       (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+       SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\
+        SNDRV_PCM_RATE_192000)
+#define SUPPORTED_MAXBPS       24
+#define SUPPORTED_FORMATS \
+       (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
+#endif
+
+static int nvhdmi_7x_init(struct hda_codec *codec)
+{
+       snd_hda_sequence_write(codec, nvhdmi_basic_init_7x);
+       return 0;
+}
+
+static int simple_playback_pcm_open(struct hda_pcm_stream *hinfo,
+                                   struct hda_codec *codec,
+                                   struct snd_pcm_substream *substream)
+{
+       struct hdmi_spec *spec = codec->spec;
+       return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int simple_playback_pcm_close(struct hda_pcm_stream *hinfo,
+                                    struct hda_codec *codec,
+                                    struct snd_pcm_substream *substream)
+{
+       struct hdmi_spec *spec = codec->spec;
+       return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int simple_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+                                      struct hda_codec *codec,
+                                      unsigned int stream_tag,
+                                      unsigned int format,
+                                      struct snd_pcm_substream *substream)
+{
+       struct hdmi_spec *spec = codec->spec;
+       return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+                                            stream_tag, format, substream);
+}
+
+static int nvhdmi_8ch_7x_pcm_close(struct hda_pcm_stream *hinfo,
+                                  struct hda_codec *codec,
+                                  struct snd_pcm_substream *substream)
+{
+       struct hdmi_spec *spec = codec->spec;
+       int i;
+
+       snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x,
+                       0, AC_VERB_SET_CHANNEL_STREAMID, 0);
+       for (i = 0; i < 4; i++) {
+               /* set the stream id */
+               snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0,
+                               AC_VERB_SET_CHANNEL_STREAMID, 0);
+               /* set the stream format */
+               snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0,
+                               AC_VERB_SET_STREAM_FORMAT, 0);
+       }
+
+       return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
+                                    struct hda_codec *codec,
+                                    unsigned int stream_tag,
+                                    unsigned int format,
+                                    struct snd_pcm_substream *substream)
+{
+       int chs;
+       unsigned int dataDCC1, dataDCC2, chan, chanmask, channel_id;
+       int i;
+
+       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))
+               snd_hda_codec_write(codec,
+                               nvhdmi_master_con_nid_7x,
+                               0,
+                               AC_VERB_SET_DIGI_CONVERT_1,
+                               codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff);
+
+       /* set the stream id */
+       snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0,
+                       AC_VERB_SET_CHANNEL_STREAMID, (stream_tag << 4) | 0x0);
+
+       /* set the stream format */
+       snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0,
+                       AC_VERB_SET_STREAM_FORMAT, format);
+
+       /* 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)) {
+               snd_hda_codec_write(codec,
+                               nvhdmi_master_con_nid_7x,
+                               0,
+                               AC_VERB_SET_DIGI_CONVERT_1,
+                               codec->spdif_ctls & 0xff);
+               snd_hda_codec_write(codec,
+                               nvhdmi_master_con_nid_7x,
+                               0,
+                               AC_VERB_SET_DIGI_CONVERT_2, dataDCC2);
+       }
+
+       for (i = 0; i < 4; i++) {
+               if (chs == 2)
+                       channel_id = 0;
+               else
+                       channel_id = i * 2;
+
+               /* turn off SPDIF once;
+                *otherwise the IEC958 bits won't be updated
+                */
+               if (codec->spdif_status_reset &&
+               (codec->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);
+               /* set the stream id */
+               snd_hda_codec_write(codec,
+                               nvhdmi_con_nids_7x[i],
+                               0,
+                               AC_VERB_SET_CHANNEL_STREAMID,
+                               (stream_tag << 4) | channel_id);
+               /* set the stream format */
+               snd_hda_codec_write(codec,
+                               nvhdmi_con_nids_7x[i],
+                               0,
+                               AC_VERB_SET_STREAM_FORMAT,
+                               format);
+               /* 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)) {
+                       snd_hda_codec_write(codec,
+                                       nvhdmi_con_nids_7x[i],
+                                       0,
+                                       AC_VERB_SET_DIGI_CONVERT_1,
+                                       codec->spdif_ctls & 0xff);
+                       snd_hda_codec_write(codec,
+                                       nvhdmi_con_nids_7x[i],
+                                       0,
+                                       AC_VERB_SET_DIGI_CONVERT_2, dataDCC2);
+               }
+       }
+
+       /* set the Audio Info Frame Checksum */
+       snd_hda_codec_write(codec, 0x1, 0,
+                       Nv_VERB_SET_Info_Frame_Checksum,
+                       (0x71 - chan - chanmask));
+
+       mutex_unlock(&codec->spdif_mutex);
+       return 0;
+}
+
+static struct hda_pcm_stream nvhdmi_pcm_playback_8ch_7x = {
+       .substreams = 1,
+       .channels_min = 2,
+       .channels_max = 8,
+       .nid = nvhdmi_master_con_nid_7x,
+       .rates = SUPPORTED_RATES,
+       .maxbps = SUPPORTED_MAXBPS,
+       .formats = SUPPORTED_FORMATS,
+       .ops = {
+               .open = simple_playback_pcm_open,
+               .close = nvhdmi_8ch_7x_pcm_close,
+               .prepare = nvhdmi_8ch_7x_pcm_prepare
+       },
+};
+
+static struct hda_pcm_stream nvhdmi_pcm_playback_2ch = {
+       .substreams = 1,
+       .channels_min = 2,
+       .channels_max = 2,
+       .nid = nvhdmi_master_con_nid_7x,
+       .rates = SUPPORTED_RATES,
+       .maxbps = SUPPORTED_MAXBPS,
+       .formats = SUPPORTED_FORMATS,
+       .ops = {
+               .open = simple_playback_pcm_open,
+               .close = simple_playback_pcm_close,
+               .prepare = simple_playback_pcm_prepare
+       },
+};
+
+static struct hda_codec_ops nvhdmi_patch_ops_8ch_7x = {
+       .build_controls = generic_hdmi_build_controls,
+       .build_pcms = generic_hdmi_build_pcms,
+       .init = nvhdmi_7x_init,
+       .free = generic_hdmi_free,
+};
+
+static struct hda_codec_ops nvhdmi_patch_ops_2ch = {
+       .build_controls = generic_hdmi_build_controls,
+       .build_pcms = generic_hdmi_build_pcms,
+       .init = nvhdmi_7x_init,
+       .free = generic_hdmi_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;
+
+       spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+       if (spec == NULL)
+               return -ENOMEM;
+
+       codec->spec = spec;
+
+       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->pcm_playback = &nvhdmi_pcm_playback_2ch;
+
+       codec->patch_ops = nvhdmi_patch_ops_2ch;
+
+       return 0;
+}
+
+static int patch_nvhdmi_8ch_7x(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec;
+       int err = patch_nvhdmi_2ch(codec);
+
+       if (err < 0)
+               return err;
+       spec = codec->spec;
+       spec->multiout.max_channels = 8;
+       spec->pcm_playback = &nvhdmi_pcm_playback_8ch_7x;
+       codec->patch_ops = nvhdmi_patch_ops_8ch_7x;
+       return 0;
+}
+
+/*
+ * ATI-specific implementations
+ *
+ * FIXME: we may omit the whole this and use the generic code once after
+ * it's confirmed to work.
+ */
+
+#define ATIHDMI_CVT_NID                0x02    /* audio converter */
+#define ATIHDMI_PIN_NID                0x03    /* HDMI output pin */
+
+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)
+{
+       struct hdmi_spec *spec = codec->spec;
+       int chans = substream->runtime->channels;
+       int i, err;
+
+       err = simple_playback_pcm_prepare(hinfo, codec, stream_tag, format,
+                                         substream);
+       if (err < 0)
+               return err;
+       snd_hda_codec_write(codec, spec->cvt[0], 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,
+                                   AC_VERB_SET_HDMI_CHAN_SLOT,
+                                   (i << 4) | i);
+       }
+       return 0;
+}
+
+static struct hda_pcm_stream atihdmi_pcm_digital_playback = {
+       .substreams = 1,
+       .channels_min = 2,
+       .channels_max = 2,
+       .nid = ATIHDMI_CVT_NID,
+       .ops = {
+               .open = simple_playback_pcm_open,
+               .close = simple_playback_pcm_close,
+               .prepare = atihdmi_playback_pcm_prepare
+       },
+};
+
+static struct hda_verb atihdmi_basic_init[] = {
+       /* enable digital output on pin widget */
+       { 0x03, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+       {} /* terminator */
+};
+
+static int atihdmi_init(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec = codec->spec;
+
+       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,
+                                   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,
+       .init = atihdmi_init,
+       .free = generic_hdmi_free,
+};
+
+
+static int patch_atihdmi(struct hda_codec *codec)
+{
+       struct hdmi_spec *spec;
+
+       spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+       if (spec == NULL)
+               return -ENOMEM;
+
+       codec->spec = spec;
+
+       spec->multiout.num_dacs = 0;      /* no analog */
+       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->pcm_playback = &atihdmi_pcm_digital_playback;
+
+       codec->patch_ops = atihdmi_patch_ops;
+
+       return 0;
+}
+
+
+/*
+ * patch entries
+ */
+static 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_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 },
+{ .id = 0x10de0002, .name = "MCP77/78 HDMI",   .patch = patch_nvhdmi_8ch_7x },
+{ .id = 0x10de0003, .name = "MCP77/78 HDMI",   .patch = patch_nvhdmi_8ch_7x },
+{ .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 = 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 },
+{ .id = 0x80862801, .name = "Bearlake HDMI",   .patch = patch_generic_hdmi },
+{ .id = 0x80862802, .name = "Cantiga HDMI",    .patch = patch_generic_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 = 0x808629fb, .name = "Crestline HDMI",  .patch = patch_generic_hdmi },
+{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:1002793c");
+MODULE_ALIAS("snd-hda-codec-id:10027919");
+MODULE_ALIAS("snd-hda-codec-id:1002791a");
+MODULE_ALIAS("snd-hda-codec-id:1002aa01");
+MODULE_ALIAS("snd-hda-codec-id:10951390");
+MODULE_ALIAS("snd-hda-codec-id:10951392");
+MODULE_ALIAS("snd-hda-codec-id:10de0002");
+MODULE_ALIAS("snd-hda-codec-id:10de0003");
+MODULE_ALIAS("snd-hda-codec-id:10de0005");
+MODULE_ALIAS("snd-hda-codec-id:10de0006");
+MODULE_ALIAS("snd-hda-codec-id:10de0007");
+MODULE_ALIAS("snd-hda-codec-id:10de000a");
+MODULE_ALIAS("snd-hda-codec-id:10de000b");
+MODULE_ALIAS("snd-hda-codec-id:10de000c");
+MODULE_ALIAS("snd-hda-codec-id:10de000d");
+MODULE_ALIAS("snd-hda-codec-id:10de0010");
+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: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:10de0040");
+MODULE_ALIAS("snd-hda-codec-id:10de0041");
+MODULE_ALIAS("snd-hda-codec-id:10de0042");
+MODULE_ALIAS("snd-hda-codec-id:10de0043");
+MODULE_ALIAS("snd-hda-codec-id:10de0044");
+MODULE_ALIAS("snd-hda-codec-id:10de0067");
+MODULE_ALIAS("snd-hda-codec-id:10de8001");
+MODULE_ALIAS("snd-hda-codec-id:17e80047");
+MODULE_ALIAS("snd-hda-codec-id:80860054");
+MODULE_ALIAS("snd-hda-codec-id:80862801");
+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:808629fb");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HDMI HD-audio codec");
+MODULE_ALIAS("snd-hda-codec-intelhdmi");
+MODULE_ALIAS("snd-hda-codec-nvhdmi");
+MODULE_ALIAS("snd-hda-codec-atihdmi");
+
+static struct hda_codec_preset_list intel_list = {
+       .preset = snd_hda_preset_hdmi,
+       .owner = THIS_MODULE,
+};
+
+static int __init patch_hdmi_init(void)
+{
+       return snd_hda_add_codec_preset(&intel_list);
+}
+
+static void __exit patch_hdmi_exit(void)
+{
+       snd_hda_delete_codec_preset(&intel_list);
+}
+
+module_init(patch_hdmi_init)
+module_exit(patch_hdmi_exit)