ALSA: hda/realtek - Finer tuning of auto-parser with badness evaluation
authorTakashi Iwai <tiwai@suse.de>
Thu, 16 Feb 2012 15:45:59 +0000 (16:45 +0100)
committerTakashi Iwai <tiwai@suse.de>
Thu, 16 Feb 2012 17:08:39 +0000 (18:08 +0100)
This patch improves the Realtek auto-parser for assigning the DACs and
mixers in more suitable ways by evaluating the assignment with "badness"
calculations.

When assigning a DAC hinders the assignment of individual DACs for
other pins, some badness point is given.  Similarly, when it blocks the
assignment of unique mixer controls, another badness point is added.
Also, if no DAC, even shared DAC, can be assigned, more badness is
pointed.  Finally, comparing the accumulated badness, the best route is
chosen among several trials.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/patch_realtek.c

index a5697c3b30b8c2e3e56e6fd913b12be56d02e3f3..4746afa25db88283654913e672a6e30c9e4a117d 100644 (file)
@@ -2982,76 +2982,191 @@ static hda_nid_t get_dac_if_single(struct hda_codec *codec, hda_nid_t pin)
        return 0;
 }
 
-/* return 0 if no possible DAC is found, 1 if one or more found */
+/* mark up volume and mute control NIDs: used during badness parsing and
+ * at creating actual controls
+ */
+static inline unsigned int get_ctl_pos(unsigned int data)
+{
+       hda_nid_t nid = get_amp_nid_(data);
+       unsigned int dir;
+       if (snd_BUG_ON(nid >= MAX_VOL_NIDS))
+               return 0;
+       dir = get_amp_direction_(data);
+       return (nid << 1) | dir;
+}
+
+#define is_ctl_used(bits, data) \
+       test_bit(get_ctl_pos(data), bits)
+#define mark_ctl_usage(bits, data) \
+       set_bit(get_ctl_pos(data), bits)
+
+static void clear_vol_marks(struct hda_codec *codec)
+{
+       struct alc_spec *spec = codec->spec;
+       memset(spec->vol_ctls, 0, sizeof(spec->vol_ctls));
+       memset(spec->sw_ctls, 0, sizeof(spec->sw_ctls));
+}
+
+/* badness definition */
+enum {
+       /* No primary DAC is found for the main output */
+       BAD_NO_PRIMARY_DAC = 0x10000,
+       /* No DAC is found for the extra output */
+       BAD_NO_DAC = 0x4000,
+       /* No individual DAC for extra output */
+       BAD_NO_EXTRA_DAC = 0x1000,
+       /* No individual DAC for extra surrounds */
+       BAD_NO_EXTRA_SURR_DAC = 0x200,
+       /* Primary DAC shared with main surrounds */
+       BAD_SHARED_SURROUND = 0x100,
+       /* Volume widget is shared */
+       BAD_SHARED_VOL = 0x10,
+       /* Primary DAC shared with main CLFE */
+       BAD_SHARED_CLFE = 0x10,
+       /* Primary DAC shared with extra surrounds */
+       BAD_SHARED_EXTRA_SURROUND = 0x10,
+       /* No possible multi-ios */
+       BAD_MULTI_IO = 0x1,
+};
+
+static hda_nid_t alc_look_for_out_mute_nid(struct hda_codec *codec,
+                                          hda_nid_t pin, hda_nid_t dac);
+static hda_nid_t alc_look_for_out_vol_nid(struct hda_codec *codec,
+                                         hda_nid_t pin, hda_nid_t dac);
+
+static int eval_shared_vol_badness(struct hda_codec *codec, hda_nid_t pin,
+                                  hda_nid_t dac)
+{
+       struct alc_spec *spec = codec->spec;
+       hda_nid_t nid;
+       unsigned int val;
+       int badness = 0;
+
+       nid = alc_look_for_out_vol_nid(codec, pin, dac);
+       if (nid) {
+               val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+               if (is_ctl_used(spec->vol_ctls, nid))
+                       badness += BAD_SHARED_VOL;
+               else
+                       mark_ctl_usage(spec->vol_ctls, val);
+       } else
+               badness += BAD_SHARED_VOL;
+       nid = alc_look_for_out_mute_nid(codec, pin, dac);
+       if (nid) {
+               unsigned int wid_type = get_wcaps_type(get_wcaps(codec, nid));
+               if (wid_type == AC_WID_PIN || wid_type == AC_WID_AUD_OUT)
+                       val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+               else
+                       val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT);
+               if (is_ctl_used(spec->sw_ctls, val))
+                       badness += BAD_SHARED_VOL;
+               else
+                       mark_ctl_usage(spec->sw_ctls, val);
+       } else
+               badness += BAD_SHARED_VOL;
+       return badness;
+}
+
+/* try to assign DACs to extra pins and return the resultant badness */
 static int alc_auto_fill_extra_dacs(struct hda_codec *codec, int num_outs,
                                    const hda_nid_t *pins, hda_nid_t *dacs)
 {
+       struct alc_spec *spec = codec->spec;
        int i;
+       int badness = 0;
+       hda_nid_t dac;
 
        if (num_outs && !dacs[0]) {
-               dacs[0] = alc_auto_look_for_dac(codec, pins[0]);
-               if (!dacs[0])
-                       return 0;
+               dac = dacs[0] = alc_auto_look_for_dac(codec, pins[0]);
+               if (!dacs[0]) {
+                       dac = spec->private_dac_nids[0];
+                       if (!alc_auto_is_dac_reachable(codec, pins[0], dac))
+                               return BAD_NO_DAC;
+                       badness += BAD_NO_EXTRA_DAC;
+               }
+               badness += eval_shared_vol_badness(codec, pins[0], dac);
        }
 
        for (i = 1; i < num_outs; i++)
                dacs[i] = get_dac_if_single(codec, pins[i]);
        for (i = 1; i < num_outs; i++) {
-               if (!dacs[i])
-                       dacs[i] = alc_auto_look_for_dac(codec, pins[i]);
+               dac = dacs[i];
+               if (!dac)
+                       dac = dacs[i] = alc_auto_look_for_dac(codec, pins[i]);
+               if (!dac) {
+                       if (alc_auto_is_dac_reachable(codec, pins[i], dacs[0])) {
+                               dac = dacs[0];
+                               badness += BAD_SHARED_EXTRA_SURROUND;
+                       } else if (alc_auto_is_dac_reachable(codec, pins[i],
+                                       spec->private_dac_nids[0])) {
+                               dac = spec->private_dac_nids[0];
+                               badness += BAD_NO_EXTRA_SURR_DAC;
+                       } else
+                               badness += BAD_NO_DAC;
+               }
+               if (dac)
+                       badness += eval_shared_vol_badness(codec, pins[i], dac);
        }
-       return 1;
+       return badness;
 }
 
 static int alc_auto_fill_multi_ios(struct hda_codec *codec,
                                   unsigned int location, int offset);
-static hda_nid_t alc_look_for_out_vol_nid(struct hda_codec *codec,
-                                         hda_nid_t pin, hda_nid_t dac);
 
 /* fill in the dac_nids table from the parsed pin configuration */
-static int alc_auto_fill_dac_nids(struct hda_codec *codec)
+static int fill_and_eval_dacs(struct hda_codec *codec,
+                             bool fill_hardwired)
 {
        struct alc_spec *spec = codec->spec;
        struct auto_pin_cfg *cfg = &spec->autocfg;
        unsigned int location, defcfg;
-       int num_pins;
-       bool redone = false;
-       int i;
+       int i, err, badness;
 
- again:
        /* set num_dacs once to full for alc_auto_look_for_dac() */
        spec->multiout.num_dacs = cfg->line_outs;
-       spec->multiout.hp_out_nid[0] = 0;
-       spec->multiout.extra_out_nid[0] = 0;
-       memset(spec->private_dac_nids, 0, sizeof(spec->private_dac_nids));
        spec->multiout.dac_nids = spec->private_dac_nids;
+       memset(spec->private_dac_nids, 0, sizeof(spec->private_dac_nids));
+       memset(spec->multiout.hp_out_nid, 0, sizeof(spec->multiout.hp_out_nid));
+       memset(spec->multiout.extra_out_nid, 0, sizeof(spec->multiout.extra_out_nid));
        spec->multi_ios = 0;
+       clear_vol_marks(codec);
+       badness = 0;
 
        /* fill hard-wired DACs first */
-       if (!redone) {
+       if (fill_hardwired) {
                for (i = 0; i < cfg->line_outs; i++)
                        spec->private_dac_nids[i] =
                                get_dac_if_single(codec, cfg->line_out_pins[i]);
-               if (cfg->hp_outs)
-                       spec->multiout.hp_out_nid[0] =
-                               get_dac_if_single(codec, cfg->hp_pins[0]);
-               if (cfg->speaker_outs)
-                       spec->multiout.extra_out_nid[0] =
-                               get_dac_if_single(codec, cfg->speaker_pins[0]);
+               for (i = 0; i < cfg->hp_outs; i++)
+                       spec->multiout.hp_out_nid[i] =
+                               get_dac_if_single(codec, cfg->hp_pins[i]);
+               for (i = 0; i < cfg->speaker_outs; i++)
+                       spec->multiout.extra_out_nid[i] =
+                               get_dac_if_single(codec, cfg->speaker_pins[i]);
        }
 
        for (i = 0; i < cfg->line_outs; i++) {
                hda_nid_t pin = cfg->line_out_pins[i];
-               if (spec->private_dac_nids[i])
-                       continue;
-               spec->private_dac_nids[i] = alc_auto_look_for_dac(codec, pin);
-               if (!spec->private_dac_nids[i] && !redone) {
-                       /* if we can't find primary DACs, re-probe without
-                        * checking the hard-wired DACs
-                        */
-                       redone = true;
-                       goto again;
+               hda_nid_t dac;
+               if (!spec->private_dac_nids[i])
+                       spec->private_dac_nids[i] =
+                               alc_auto_look_for_dac(codec, pin);
+               dac = spec->private_dac_nids[i];
+               if (!dac) {
+                       if (!i)
+                               badness += BAD_NO_PRIMARY_DAC;
+                       else if (alc_auto_is_dac_reachable(codec, pin,
+                                       spec->private_dac_nids[0])) {
+                               if (i == 1)
+                                       badness += BAD_SHARED_SURROUND;
+                               else
+                                       badness += BAD_SHARED_CLFE;
+                               dac = spec->private_dac_nids[0];
+                       } else
+                               badness += BAD_NO_DAC;
                }
+               if (dac)
+                       badness += eval_shared_vol_badness(codec, pin, dac);
        }
 
        /* re-count num_dacs and squash invalid entries */
@@ -3071,26 +3186,114 @@ static int alc_auto_fill_dac_nids(struct hda_codec *codec)
                /* try to fill multi-io first */
                defcfg = snd_hda_codec_get_pincfg(codec, cfg->line_out_pins[0]);
                location = get_defcfg_location(defcfg);
-
-               num_pins = alc_auto_fill_multi_ios(codec, location, 0);
-               if (num_pins > 0) {
-                       spec->multi_ios = num_pins;
-                       spec->ext_channel_count = 2;
-                       spec->multiout.num_dacs = num_pins + 1;
-               }
+               err = alc_auto_fill_multi_ios(codec, location, 0);
+               if (err < 0)
+                       return err;
+               badness += err;
        }
 
-       if (cfg->line_out_type != AUTO_PIN_HP_OUT)
-               alc_auto_fill_extra_dacs(codec, cfg->hp_outs, cfg->hp_pins,
-                                spec->multiout.hp_out_nid);
+       if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
+               err = alc_auto_fill_extra_dacs(codec, cfg->hp_outs,
+                                              cfg->hp_pins,
+                                              spec->multiout.hp_out_nid);
+               if (err < 0)
+                       return err;
+               badness += err;
+       }
        if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
-               int err = alc_auto_fill_extra_dacs(codec, cfg->speaker_outs,
-                                       cfg->speaker_pins,
-                                       spec->multiout.extra_out_nid);
-               /* if no speaker volume is assigned, try again as the primary
-                * output
-                */
-               if (!err && cfg->speaker_outs > 0 &&
+               err = alc_auto_fill_extra_dacs(codec, cfg->speaker_outs,
+                                              cfg->speaker_pins,
+                                              spec->multiout.extra_out_nid);
+               if (err < 0)
+                       return err;
+               badness += err;
+       }
+       if (!spec->multi_ios &&
+           cfg->line_out_type == AUTO_PIN_SPEAKER_OUT &&
+           cfg->hp_outs) {
+               /* try multi-ios with HP + inputs */
+               defcfg = snd_hda_codec_get_pincfg(codec, cfg->hp_pins[0]);
+               location = get_defcfg_location(defcfg);
+               err = alc_auto_fill_multi_ios(codec, location, 1);
+               if (err < 0)
+                       return err;
+               badness += err;
+       }
+
+       return badness;
+}
+
+#define DEBUG_BADNESS
+
+#ifdef DEBUG_BADNESS
+#define debug_badness  snd_printdd
+#else
+#define debug_badness(...)
+#endif
+
+static void debug_show_configs(struct alc_spec *spec, struct auto_pin_cfg *cfg)
+{
+       debug_badness("multi_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
+                     cfg->line_out_pins[0], cfg->line_out_pins[1],
+                     cfg->line_out_pins[2], cfg->line_out_pins[2],
+                     spec->multiout.dac_nids[0],
+                     spec->multiout.dac_nids[1],
+                     spec->multiout.dac_nids[2],
+                     spec->multiout.dac_nids[3]);
+       debug_badness("hp_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
+                     cfg->hp_pins[0], cfg->hp_pins[1],
+                     cfg->hp_pins[2], cfg->hp_pins[2],
+                     spec->multiout.hp_out_nid[0],
+                     spec->multiout.hp_out_nid[1],
+                     spec->multiout.hp_out_nid[2],
+                     spec->multiout.hp_out_nid[3]);
+       debug_badness("spk_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
+                     cfg->speaker_pins[0], cfg->speaker_pins[1],
+                     cfg->speaker_pins[2], cfg->speaker_pins[3],
+                     spec->multiout.extra_out_nid[0],
+                     spec->multiout.extra_out_nid[1],
+                     spec->multiout.extra_out_nid[2],
+                     spec->multiout.extra_out_nid[3]);
+}
+
+static int alc_auto_fill_dac_nids(struct hda_codec *codec)
+{
+       struct alc_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       struct auto_pin_cfg *best_cfg;
+       int best_badness = INT_MAX;
+       int badness;
+       bool fill_hardwired = true;
+       bool best_wired = true;
+       bool hp_spk_swapped = false;
+
+       best_cfg = kmalloc(sizeof(*best_cfg), GFP_KERNEL);
+       if (!best_cfg)
+               return -ENOMEM;
+       *best_cfg = *cfg;
+
+       for (;;) {
+               badness = fill_and_eval_dacs(codec, fill_hardwired);
+               if (badness < 0)
+                       return badness;
+               debug_badness("==> lo_type=%d, wired=%d, badness=0x%x\n",
+                             cfg->line_out_type, fill_hardwired, badness);
+               debug_show_configs(spec, cfg);
+               if (badness < best_badness) {
+                       best_badness = badness;
+                       *best_cfg = *cfg;
+                       best_wired = fill_hardwired;
+               }
+               if (!badness)
+                       break;
+               if (fill_hardwired) {
+                       fill_hardwired = false;
+                       continue;
+               }
+               if (hp_spk_swapped)
+                       break;
+               hp_spk_swapped = true;
+               if (cfg->speaker_outs > 0 &&
                    cfg->line_out_type == AUTO_PIN_HP_OUT) {
                        cfg->hp_outs = cfg->line_outs;
                        memcpy(cfg->hp_pins, cfg->line_out_pins,
@@ -3101,48 +3304,45 @@ static int alc_auto_fill_dac_nids(struct hda_codec *codec)
                        cfg->speaker_outs = 0;
                        memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins));
                        cfg->line_out_type = AUTO_PIN_SPEAKER_OUT;
-                       redone = false;
-                       goto again;
-               }
+                       fill_hardwired = true;
+                       continue;
+               } 
+               if (cfg->hp_outs > 0 &&
+                   cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) {
+                       cfg->speaker_outs = cfg->line_outs;
+                       memcpy(cfg->speaker_pins, cfg->line_out_pins,
+                              sizeof(cfg->speaker_pins));
+                       cfg->line_outs = cfg->hp_outs;
+                       memcpy(cfg->line_out_pins, cfg->hp_pins,
+                              sizeof(cfg->hp_pins));
+                       cfg->hp_outs = 0;
+                       memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins));
+                       cfg->line_out_type = AUTO_PIN_HP_OUT;
+                       fill_hardwired = true;
+                       continue;
+               } 
+               break;
        }
 
-       if (!spec->multi_ios &&
-           cfg->line_out_type == AUTO_PIN_SPEAKER_OUT &&
-           cfg->hp_outs) {
-               /* try multi-ios with HP + inputs */
-               defcfg = snd_hda_codec_get_pincfg(codec, cfg->hp_pins[0]);
-               location = get_defcfg_location(defcfg);
-
-               num_pins = alc_auto_fill_multi_ios(codec, location, 1);
-               if (num_pins > 0) {
-                       spec->multi_ios = num_pins;
-                       spec->ext_channel_count = 2;
-                       spec->multiout.num_dacs = num_pins + 1;
-               }
+       if (badness) {
+               *cfg = *best_cfg;
+               fill_and_eval_dacs(codec, best_wired);
        }
+       debug_badness("==> Best config: lo_type=%d, wired=%d\n",
+                     cfg->line_out_type, best_wired);
+       debug_show_configs(spec, cfg);
 
        if (cfg->line_out_pins[0])
                spec->vmaster_nid =
                        alc_look_for_out_vol_nid(codec, cfg->line_out_pins[0],
                                                 spec->multiout.dac_nids[0]);
-       return 0;
-}
 
-static inline unsigned int get_ctl_pos(unsigned int data)
-{
-       hda_nid_t nid = get_amp_nid_(data);
-       unsigned int dir;
-       if (snd_BUG_ON(nid >= MAX_VOL_NIDS))
-               return 0;
-       dir = get_amp_direction_(data);
-       return (nid << 1) | dir;
+       /* clear the bitmap flags for creating controls */
+       clear_vol_marks(codec);
+       kfree(best_cfg);
+       return 0;
 }
 
-#define is_ctl_used(bits, data) \
-       test_bit(get_ctl_pos(data), bits)
-#define mark_ctl_usage(bits, data) \
-       set_bit(get_ctl_pos(data), bits)
-
 static int alc_auto_add_vol_ctl(struct hda_codec *codec,
                              const char *pfx, int cidx,
                              hda_nid_t nid, unsigned int chs)
@@ -3539,6 +3739,7 @@ static int alc_auto_fill_multi_ios(struct hda_codec *codec,
        struct auto_pin_cfg *cfg = &spec->autocfg;
        hda_nid_t prime_dac = spec->private_dac_nids[0];
        int type, i, dacs, num_pins = 0;
+       int badness = 0;
 
        dacs = spec->multiout.num_dacs;
        for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) {
@@ -3563,12 +3764,16 @@ static int alc_auto_fill_multi_ios(struct hda_codec *codec,
                        }
                        if (!dac)
                                dac = alc_auto_look_for_dac(codec, nid);
-                       if (!dac)
+                       if (!dac) {
+                               badness += BAD_MULTI_IO;
                                continue;
+                       }
                        spec->multi_io[num_pins].pin = nid;
                        spec->multi_io[num_pins].dac = dac;
                        num_pins++;
                        spec->private_dac_nids[spec->multiout.num_dacs++] = dac;
+                       if (num_pins >= 2)
+                               break;
                }
        }
        spec->multiout.num_dacs = dacs;
@@ -3577,9 +3782,13 @@ static int alc_auto_fill_multi_ios(struct hda_codec *codec,
                memset(spec->private_dac_nids + dacs, 0,
                       sizeof(hda_nid_t) * (AUTO_CFG_MAX_OUTS - dacs));
                spec->private_dac_nids[0] = prime_dac;
-               return 0;
+               return badness;
        }
-       return num_pins;
+
+       spec->multi_ios = num_pins;
+       spec->ext_channel_count = 2;
+       spec->multiout.num_dacs = num_pins + 1;
+       return 0;
 }
 
 static int alc_auto_ch_mode_info(struct snd_kcontrol *kcontrol,