]> err.no Git - linux-2.6/blobdiff - sound/pci/hda/hda_codec.c
[ALSA] hda-intel - correct a bug in detection of rate supported
[linux-2.6] / sound / pci / hda / hda_codec.c
index 56941031ab0d26dcf4c1f7ab2ee50b78ecb01744..e067a14a2d9e5f914b415e0de9ccb37e39aadf44 100644 (file)
@@ -49,6 +49,7 @@ struct hda_vendor_id {
 /* codec vendor labels */
 static struct hda_vendor_id hda_vendor_ids[] = {
        { 0x10ec, "Realtek" },
+       { 0x11d4, "Analog Devices" },
        { 0x13f6, "C-Media" },
        { 0x434d, "C-Media" },
        { 0x8384, "SigmaTel" },
@@ -431,22 +432,26 @@ void snd_hda_get_codec_name(struct hda_codec *codec,
 }
 
 /*
- * look for an AFG node
- *
- * return 0 if not found
+ * look for an AFG and MFG nodes
  */
-static int look_for_afg_node(struct hda_codec *codec)
+static void setup_fg_nodes(struct hda_codec *codec)
 {
        int i, total_nodes;
        hda_nid_t nid;
 
        total_nodes = snd_hda_get_sub_nodes(codec, AC_NODE_ROOT, &nid);
        for (i = 0; i < total_nodes; i++, nid++) {
-               if ((snd_hda_param_read(codec, nid, AC_PAR_FUNCTION_TYPE) & 0xff) ==
-                   AC_GRP_AUDIO_FUNCTION)
-                       return nid;
+               switch((snd_hda_param_read(codec, nid, AC_PAR_FUNCTION_TYPE) & 0xff)) {
+               case AC_GRP_AUDIO_FUNCTION:
+                       codec->afg = nid;
+                       break;
+               case AC_GRP_MODEM_FUNCTION:
+                       codec->mfg = nid;
+                       break;
+               default:
+                       break;
+               }
        }
-       return 0;
 }
 
 /*
@@ -506,10 +511,9 @@ int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
        codec->subsystem_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_SUBSYSTEM_ID);
        codec->revision_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_REV_ID);
 
-       /* FIXME: support for multiple AFGs? */
-       codec->afg = look_for_afg_node(codec);
-       if (! codec->afg) {
-               snd_printdd("hda_codec: no AFG node found\n");
+       setup_fg_nodes(codec);
+       if (! codec->afg && ! codec->mfg) {
+               snd_printdd("hda_codec: no AFG or MFG node found\n");
                snd_hda_codec_free(codec);
                return -ENODEV;
        }
@@ -565,9 +569,10 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, u32 stre
  * amp access functions
  */
 
-#define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + (idx) * 32 + (dir) * 64)
+/* FIXME: more better hash key? */
+#define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + ((idx) << 16) + ((dir) << 24))
 #define INFO_AMP_CAPS  (1<<0)
-#define INFO_AMP_VOL   (1<<1)
+#define INFO_AMP_VOL(ch)       (1 << (1 + (ch)))
 
 /* initialize the hash table */
 static void init_amp_hash(struct hda_codec *codec)
@@ -626,28 +631,29 @@ static u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
 
 /*
  * read the current volume to info
- * if the cache exists, read from the cache.
+ * if the cache exists, read the cache value.
  */
-static void get_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
+static unsigned int get_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
                         hda_nid_t nid, int ch, int direction, int index)
 {
        u32 val, parm;
 
-       if (info->status & (INFO_AMP_VOL << ch))
-               return;
+       if (info->status & INFO_AMP_VOL(ch))
+               return info->vol[ch];
 
        parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT;
        parm |= direction == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
        parm |= index;
        val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_AMP_GAIN_MUTE, parm);
        info->vol[ch] = val & 0xff;
-       info->status |= INFO_AMP_VOL << ch;
+       info->status |= INFO_AMP_VOL(ch);
+       return info->vol[ch];
 }
 
 /*
- * write the current volume in info to the h/w
+ * write the current volume in info to the h/w and update the cache
  */
-static void put_vol_mute(struct hda_codec *codec,
+static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
                         hda_nid_t nid, int ch, int direction, int index, int val)
 {
        u32 parm;
@@ -657,30 +663,34 @@ static void put_vol_mute(struct hda_codec *codec,
        parm |= index << AC_AMP_SET_INDEX_SHIFT;
        parm |= val;
        snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm);
+       info->vol[ch] = val;
 }
 
 /*
- * read/write AMP value.  The volume is between 0 to 0x7f, 0x80 = mute bit.
+ * read AMP value.  The volume is between 0 to 0x7f, 0x80 = mute bit.
  */
 static int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int index)
 {
        struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index));
        if (! info)
                return 0;
-       get_vol_mute(codec, info, nid, ch, direction, index);
-       return info->vol[ch];
+       return get_vol_mute(codec, info, nid, ch, direction, index);
 }
 
-static int snd_hda_codec_amp_write(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int idx, int val)
+/*
+ * update the AMP value, mask = bit mask to set, val = the value
+ */
+static int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int idx, int mask, int val)
 {
        struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx));
+
        if (! info)
                return 0;
-       get_vol_mute(codec, info, nid, ch, direction, idx);
+       val &= mask;
+       val |= get_vol_mute(codec, info, nid, ch, direction, idx) & ~mask;
        if (info->vol[ch] == val && ! codec->in_resume)
                return 0;
-       put_vol_mute(codec, nid, ch, direction, idx, val);
-       info->vol[ch] = val;
+       put_vol_mute(codec, info, nid, ch, direction, idx, val);
        return 1;
 }
 
@@ -739,21 +749,17 @@ int snd_hda_mixer_amp_volume_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t
        int chs = get_amp_channels(kcontrol);
        int dir = get_amp_direction(kcontrol);
        int idx = get_amp_index(kcontrol);
-       int val;
        long *valp = ucontrol->value.integer.value;
        int change = 0;
 
        if (chs & 1) {
-               val = *valp & 0x7f;
-               val |= snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x80;
-               change = snd_hda_codec_amp_write(codec, nid, 0, dir, idx, val);
+               change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
+                                                 0x7f, *valp);
                valp++;
        }
-       if (chs & 2) {
-               val = *valp & 0x7f;
-               val |= snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x80;
-               change |= snd_hda_codec_amp_write(codec, nid, 1, dir, idx, val);
-       }
+       if (chs & 2)
+               change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
+                                                  0x7f, *valp);
        return change;
 }
 
@@ -792,21 +798,18 @@ int snd_hda_mixer_amp_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t
        int chs = get_amp_channels(kcontrol);
        int dir = get_amp_direction(kcontrol);
        int idx = get_amp_index(kcontrol);
-       int val;
        long *valp = ucontrol->value.integer.value;
        int change = 0;
 
        if (chs & 1) {
-               val = snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x7f;
-               val |= *valp ? 0 : 0x80;
-               change = snd_hda_codec_amp_write(codec, nid, 0, dir, idx, val);
+               change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
+                                                 0x80, *valp ? 0 : 0x80);
                valp++;
        }
-       if (chs & 2) {
-               val = snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x7f;
-               val |= *valp ? 0 : 0x80;
-               change = snd_hda_codec_amp_write(codec, nid, 1, dir, idx, val);
-       }
+       if (chs & 2)
+               change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
+                                                  0x80, *valp ? 0 : 0x80);
+       
        return change;
 }
 
@@ -1162,6 +1165,8 @@ int snd_hda_build_controls(struct hda_bus *bus)
  */
 static unsigned int rate_bits[][3] = {
        /* rate in Hz, ALSA rate bitmask, HDA format value */
+
+       /* autodetected value used in snd_hda_query_supported_pcm */
        { 8000, SNDRV_PCM_RATE_8000, 0x0500 }, /* 1/6 x 48 */
        { 11025, SNDRV_PCM_RATE_11025, 0x4300 }, /* 1/4 x 44 */
        { 16000, SNDRV_PCM_RATE_16000, 0x0200 }, /* 1/3 x 48 */
@@ -1173,6 +1178,9 @@ static unsigned int rate_bits[][3] = {
        { 96000, SNDRV_PCM_RATE_96000, 0x0800 }, /* 2 x 48 */
        { 176400, SNDRV_PCM_RATE_176400, 0x5800 },/* 4 x 44 */
        { 192000, SNDRV_PCM_RATE_192000, 0x1800 }, /* 4 x 48 */
+
+       /* not autodetected value */
+       { 9600, SNDRV_PCM_RATE_KNOT, 0x0400 }, /* 1/5 x 48 */
        { 0 }
 };
 
@@ -1525,9 +1533,9 @@ int snd_hda_build_pcms(struct hda_bus *bus)
  *
  * If no entries are matching, the function returns a negative value.
  */
-int snd_hda_check_board_config(struct hda_codec *codec, struct hda_board_config *tbl)
+int snd_hda_check_board_config(struct hda_codec *codec, const struct hda_board_config *tbl)
 {
-       struct hda_board_config *c;
+       const struct hda_board_config *c;
 
        if (codec->bus->modelname) {
                for (c = tbl; c->modelname || c->pci_subvendor; c++) {
@@ -1545,8 +1553,12 @@ int snd_hda_check_board_config(struct hda_codec *codec, struct hda_board_config
                pci_read_config_word(codec->bus->pci, PCI_SUBSYSTEM_ID, &subsystem_device);
                for (c = tbl; c->modelname || c->pci_subvendor; c++) {
                        if (c->pci_subvendor == subsystem_vendor &&
-                           c->pci_subdevice == subsystem_device)
+                           (! c->pci_subdevice /* all match */||
+                            (c->pci_subdevice == subsystem_device))) {
+                               snd_printdd(KERN_INFO "hda_codec: PCI %x:%x, codec config %d is selected\n",
+                                           subsystem_vendor, subsystem_device, c->config);
                                return c->config;
+                       }
                }
        }
        return -1;
@@ -1687,11 +1699,12 @@ int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, struct hda_multi_o
                snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag, 0, format);
        /* surrounds */
        for (i = 1; i < mout->num_dacs; i++) {
-               if (i == HDA_REAR && chs == 2) /* copy front to rear */
-                       snd_hda_codec_setup_stream(codec, nids[i], stream_tag, 0, format);
-               else if (chs >= (i + 1) * 2) /* independent out */
+               if (chs >= (i + 1) * 2) /* independent out */
                        snd_hda_codec_setup_stream(codec, nids[i], stream_tag, i * 2,
                                                   format);
+               else /* copy front */
+                       snd_hda_codec_setup_stream(codec, nids[i], stream_tag, 0,
+                                                  format);
        }
        return 0;
 }
@@ -1717,6 +1730,119 @@ int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, struct hda_multi_o
        return 0;
 }
 
+/*
+ * Helper for automatic ping configuration
+ */
+/* parse all pin widgets and store the useful pin nids to cfg */
+int snd_hda_parse_pin_def_config(struct hda_codec *codec, struct auto_pin_cfg *cfg)
+{
+       hda_nid_t nid, nid_start;
+       int i, j, nodes;
+       short seq, sequences[4], assoc_line_out;
+
+       memset(cfg, 0, sizeof(*cfg));
+
+       memset(sequences, 0, sizeof(sequences));
+       assoc_line_out = 0;
+
+       nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid_start);
+       for (nid = nid_start; nid < nodes + nid_start; nid++) {
+               unsigned int wid_caps = snd_hda_param_read(codec, nid,
+                                                          AC_PAR_AUDIO_WIDGET_CAP);
+               unsigned int wid_type = (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+               unsigned int def_conf;
+               short assoc, loc;
+
+               /* read all default configuration for pin complex */
+               if (wid_type != AC_WID_PIN)
+                       continue;
+               def_conf = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
+               if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE)
+                       continue;
+               loc = get_defcfg_location(def_conf);
+               switch (get_defcfg_device(def_conf)) {
+               case AC_JACK_LINE_OUT:
+               case AC_JACK_SPEAKER:
+                       seq = get_defcfg_sequence(def_conf);
+                       assoc = get_defcfg_association(def_conf);
+                       if (! assoc)
+                               continue;
+                       if (! assoc_line_out)
+                               assoc_line_out = assoc;
+                       else if (assoc_line_out != assoc)
+                               continue;
+                       if (cfg->line_outs >= ARRAY_SIZE(cfg->line_out_pins))
+                               continue;
+                       cfg->line_out_pins[cfg->line_outs] = nid;
+                       sequences[cfg->line_outs] = seq;
+                       cfg->line_outs++;
+                       break;
+               case AC_JACK_HP_OUT:
+                       cfg->hp_pin = nid;
+                       break;
+               case AC_JACK_MIC_IN:
+                       if (loc == AC_JACK_LOC_FRONT)
+                               cfg->input_pins[AUTO_PIN_FRONT_MIC] = nid;
+                       else
+                               cfg->input_pins[AUTO_PIN_MIC] = nid;
+                       break;
+               case AC_JACK_LINE_IN:
+                       if (loc == AC_JACK_LOC_FRONT)
+                               cfg->input_pins[AUTO_PIN_FRONT_LINE] = nid;
+                       else
+                               cfg->input_pins[AUTO_PIN_LINE] = nid;
+                       break;
+               case AC_JACK_CD:
+                       cfg->input_pins[AUTO_PIN_CD] = nid;
+                       break;
+               case AC_JACK_AUX:
+                       cfg->input_pins[AUTO_PIN_AUX] = nid;
+                       break;
+               case AC_JACK_SPDIF_OUT:
+                       cfg->dig_out_pin = nid;
+                       break;
+               case AC_JACK_SPDIF_IN:
+                       cfg->dig_in_pin = nid;
+                       break;
+               }
+       }
+
+       /* sort by sequence */
+       for (i = 0; i < cfg->line_outs; i++)
+               for (j = i + 1; j < cfg->line_outs; j++)
+                       if (sequences[i] > sequences[j]) {
+                               seq = sequences[i];
+                               sequences[i] = sequences[j];
+                               sequences[j] = seq;
+                               nid = cfg->line_out_pins[i];
+                               cfg->line_out_pins[i] = cfg->line_out_pins[j];
+                               cfg->line_out_pins[j] = nid;
+                       }
+
+       /* Reorder the surround channels
+        * ALSA sequence is front/surr/clfe/side
+        * HDA sequence is:
+        *    4-ch: front/surr  =>  OK as it is
+        *    6-ch: front/clfe/surr
+        *    8-ch: front/clfe/side/surr
+        */
+       switch (cfg->line_outs) {
+       case 3:
+               nid = cfg->line_out_pins[1];
+               cfg->line_out_pins[1] = cfg->line_out_pins[2];
+               cfg->line_out_pins[2] = nid;
+               break;
+       case 4:
+               nid = cfg->line_out_pins[1];
+               cfg->line_out_pins[1] = cfg->line_out_pins[3];
+               cfg->line_out_pins[3] = cfg->line_out_pins[2];
+               cfg->line_out_pins[2] = nid;
+               break;
+       }
+
+       return 0;
+}
+
 #ifdef CONFIG_PM
 /*
  * power management