ALSA: hda - Implement dynamic loopback control for VIA codecs
authorTakashi Iwai <tiwai@suse.de>
Mon, 18 Jul 2011 10:49:25 +0000 (12:49 +0200)
committerTakashi Iwai <tiwai@suse.de>
Mon, 18 Jul 2011 14:47:33 +0000 (16:47 +0200)
This patch adds the dynamic control of analog-loopback for VIA codecs.

When the loopback is enabled, the inputs from line-ins and mics are
mixed with the front DAC, and sent to the front outputs.  The very same
input is routed to the headhpones and speakers in loopback mode.
However, since the loopback mix can't take other than the front DAC,
there is no longer individual volume controls for headphones and
speakers.  Once when the loopback control is off, these volumes take
effect.

Since the individual volumes are more desired in general use caess, the
loopback mode is set to off as default for now.

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

index 5232abc..76c6884 100644 (file)
@@ -130,13 +130,28 @@ struct via_spec {
        struct hda_multi_out multiout;
        hda_nid_t slave_dig_outs[2];
        hda_nid_t hp_dac_nid;
-       bool hp_indep_shared;   /* indep HP-DAC is shared with side ch */
+       hda_nid_t speaker_dac_nid;
+       int hp_indep_shared;    /* indep HP-DAC is shared with side ch */
        int num_active_streams;
-
+       int aamix_mode;         /* loopback is enabled for output-path? */
+
+       /* Output-paths:
+        * There are different output-paths depending on the setup.
+        * out_path, hp_path and speaker_path are primary paths.  If both
+        * direct DAC and aa-loopback routes are available, these contain
+        * the former paths.  Meanwhile *_mix_path contain the paths with
+        * loopback mixer.  (Since the loopback is only for front channel,
+        * no out_mix_path for surround channels.)
+        * The HP output has another path, hp_indep_path, which is used in
+        * the independent-HP mode.
+        */
        struct nid_path out_path[HDA_SIDE + 1];
+       struct nid_path out_mix_path;
        struct nid_path hp_path;
-       struct nid_path hp_dep_path;
+       struct nid_path hp_mix_path;
+       struct nid_path hp_indep_path;
        struct nid_path speaker_path;
+       struct nid_path speaker_mix_path;
 
        /* capture */
        unsigned int num_adc_nids;
@@ -437,50 +452,20 @@ static bool check_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
 #define have_mute(codec, nid, dir) \
        check_amp_caps(codec, nid, dir, AC_AMPCAP_MUTE)
 
-static bool is_node_in_path(struct nid_path *path, hda_nid_t nid)
-{
-       int i;
-       if (!nid)
-               return false;
-       for (i = 0; i < path->depth; i++) {
-               if (path->path[i] == nid)
-                       return true;
-       }
-       return false;
-}
-
 /* enable/disable the output-route mixers */
 static void activate_output_mix(struct hda_codec *codec, struct nid_path *path,
-                                hda_nid_t mix_nid, int aa_mix_idx, bool enable)
+                               hda_nid_t mix_nid, int idx, bool enable)
 {
        int i, num, val;
-       bool hp_path, front_path;
-       struct via_spec *spec = codec->spec;
 
        if (!path)
                return;
        num = snd_hda_get_conn_list(codec, mix_nid, NULL);
-       hp_path = is_node_in_path(path, spec->hp_dac_nid);
-       front_path = is_node_in_path(path, spec->multiout.dac_nids[0]);
-
        for (i = 0; i < num; i++) {
-               if (i == aa_mix_idx) {
-                       if (hp_path)
-                               val = enable ? AMP_IN_MUTE(i) :
-                               AMP_IN_UNMUTE(i);
-                       else if (front_path)
-                               val = AMP_IN_UNMUTE(i);
-                       else
-                               val = AMP_IN_MUTE(i);
-               } else {
-                       if (hp_path)
-                               val = enable ? AMP_IN_UNMUTE(i) :
-                               AMP_IN_MUTE(i);
-                       else if (front_path)
-                               val = AMP_IN_MUTE(i);
-                       else
-                               val = AMP_IN_UNMUTE(i);
-               }
+               if (i == idx)
+                       val = AMP_IN_UNMUTE(i);
+               else
+                       val = AMP_IN_MUTE(i);
                snd_hda_codec_write(codec, mix_nid, 0,
                                    AC_VERB_SET_AMP_GAIN_MUTE, val);
        }
@@ -490,9 +475,8 @@ static void activate_output_mix(struct hda_codec *codec, struct nid_path *path,
 static void activate_output_path(struct hda_codec *codec, struct nid_path *path,
                                 bool enable, bool force)
 {
-       int i, val;
        struct via_spec *spec = codec->spec;
-       hda_nid_t aa_mix_nid = spec->aa_mix_nid;
+       int i;
        for (i = 0; i < path->depth; i++) {
                hda_nid_t src, dst;
                int idx = path->idx[i];
@@ -504,25 +488,10 @@ static void activate_output_path(struct hda_codec *codec, struct nid_path *path,
                if (enable && path->multi[i])
                        snd_hda_codec_write(codec, dst, 0,
                                            AC_VERB_SET_CONNECT_SEL, idx);
-               if (!force
-                   && get_wcaps_type(get_wcaps(codec, src)) == AC_WID_AUD_OUT
-                   && get_wcaps_type(get_wcaps(codec, dst)) == AC_WID_AUD_MIX)
+               if (!force && (dst == spec->aa_mix_nid))
                        continue;
-               if (have_mute(codec, dst, HDA_INPUT)) {
-                       if (dst == aa_mix_nid) {
-                               val = enable ? AMP_IN_UNMUTE(idx) :
-                                       AMP_IN_MUTE(idx);
-                               snd_hda_codec_write(codec, dst, 0,
-                                       AC_VERB_SET_AMP_GAIN_MUTE, val);
-                       } else {
-                               idx = get_connection_index(codec, dst,
-                                                          aa_mix_nid);
-                               if (idx >= 0) {
-                                       activate_output_mix(codec, path,
-                                                           dst, idx, enable);
-                               }
-                       }
-               }
+               if (have_mute(codec, dst, HDA_INPUT))
+                       activate_output_mix(codec, path, dst, idx, enable);
                if (!force && (src == path->vol_ctl || src == path->mute_ctl))
                        continue;
                if (have_mute(codec, src, HDA_OUTPUT)) {
@@ -548,9 +517,8 @@ static void init_output_pin(struct hda_codec *codec, hda_nid_t pin,
 
 static void via_auto_init_output(struct hda_codec *codec,
                                 struct nid_path *path, int pin_type,
-                                bool with_aa_mix, bool force)
+                                bool force)
 {
-       struct via_spec *spec = codec->spec;
        unsigned int caps;
        hda_nid_t pin;
 
@@ -566,41 +534,45 @@ static void via_auto_init_output(struct hda_codec *codec,
                snd_hda_codec_write(codec, pin, 0, AC_VERB_SET_AMP_GAIN_MUTE,
                                    AMP_OUT_MUTE | val);
        }
-
-       /* initialize the AA-path */
-       if (!spec->aa_mix_nid)
-               return;
        activate_output_path(codec, path, true, force);
 }
 
 static void via_auto_init_multi_out(struct hda_codec *codec)
 {
        struct via_spec *spec = codec->spec;
+       struct nid_path *path;
        int i;
 
-       for (i = 0; i < spec->autocfg.line_outs + spec->smart51_nums; i++)
-               /* enable aa-mute only for the front channel */
-               via_auto_init_output(codec, &spec->out_path[i], PIN_OUT,
-                                    i == 0, true);
+       for (i = 0; i < spec->autocfg.line_outs + spec->smart51_nums; i++) {
+               path = &spec->out_path[i];
+               if (!i && spec->aamix_mode && spec->out_mix_path.depth)
+                       path = &spec->out_mix_path;
+               via_auto_init_output(codec, path, PIN_OUT, true);
+       }
 }
 
 static void via_auto_init_hp_out(struct hda_codec *codec)
 {
        struct via_spec *spec = codec->spec;
+       int shared = spec->hp_indep_shared;
 
-       if (!spec->hp_dac_nid) {
-               via_auto_init_output(codec, &spec->hp_dep_path, PIN_HP,
-                                    true, true);
+       if (!spec->hp_path.depth) {
+               via_auto_init_output(codec, &spec->hp_mix_path, PIN_HP, true);
                return;
        }
        if (spec->hp_independent_mode) {
-               activate_output_path(codec, &spec->hp_dep_path, false, false);
-               via_auto_init_output(codec, &spec->hp_path, PIN_HP,
-                                    true, true);
-       } else {
                activate_output_path(codec, &spec->hp_path, false, false);
-               via_auto_init_output(codec, &spec->hp_dep_path, PIN_HP,
-                                    true, true);
+               activate_output_path(codec, &spec->hp_mix_path, false, false);
+               if (shared)
+                       activate_output_path(codec, &spec->out_path[shared],
+                                            false, false);
+               via_auto_init_output(codec, &spec->hp_indep_path, PIN_HP, true);
+       } else if (spec->aamix_mode) {
+               activate_output_path(codec, &spec->hp_path, false, false);
+               via_auto_init_output(codec, &spec->hp_mix_path, PIN_HP, true);
+       } else {
+               activate_output_path(codec, &spec->hp_mix_path, false, false);
+               via_auto_init_output(codec, &spec->hp_path, PIN_HP, true);
        }
 }
 
@@ -608,9 +580,23 @@ static void via_auto_init_speaker_out(struct hda_codec *codec)
 {
        struct via_spec *spec = codec->spec;
 
-       if (spec->autocfg.speaker_outs)
+       if (!spec->autocfg.speaker_outs)
+               return;
+       if (!spec->speaker_path.depth) {
+               via_auto_init_output(codec, &spec->speaker_mix_path, PIN_OUT,
+                                    true);
+               return;
+       }
+       if (!spec->aamix_mode) {
+               activate_output_path(codec, &spec->speaker_mix_path,
+                                    false, false);
                via_auto_init_output(codec, &spec->speaker_path, PIN_OUT,
-                                    true, true);
+                                    true);
+       } else {
+               activate_output_path(codec, &spec->speaker_path, false, false);
+               via_auto_init_output(codec, &spec->speaker_mix_path, PIN_OUT,
+                                    true);
+       }
 }
 
 static bool is_smart51_pins(struct hda_codec *codec, hda_nid_t pin);
@@ -775,7 +761,7 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
 {
        struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
        struct via_spec *spec = codec->spec;
-       int cur;
+       int cur, shared;
 
        /* no independent-hp status change during PCM playback is running */
        if (spec->num_active_streams)
@@ -785,18 +771,19 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
        if (spec->hp_independent_mode == cur)
                return 0;
        spec->hp_independent_mode = cur;
+       shared = spec->hp_indep_shared;
        if (cur) {
-               activate_output_path(codec, &spec->hp_dep_path, false, false);
-               activate_output_path(codec, &spec->hp_path, true, false);
-               if (spec->hp_indep_shared)
-                       activate_output_path(codec, &spec->out_path[HDA_SIDE],
+               activate_output_path(codec, &spec->hp_mix_path, false, false);
+               if (shared)
+                       activate_output_path(codec, &spec->out_path[shared],
                                             false, false);
+               activate_output_path(codec, &spec->hp_path, true, false);
        } else {
                activate_output_path(codec, &spec->hp_path, false, false);
-               activate_output_path(codec, &spec->hp_dep_path, true, false);
-               if (spec->hp_indep_shared)
-                       activate_output_path(codec, &spec->out_path[HDA_SIDE],
+               if (shared)
+                       activate_output_path(codec, &spec->out_path[shared],
                                             true, false);
+               activate_output_path(codec, &spec->hp_mix_path, true, false);
        }
 
        /* update jack power state */
@@ -1671,29 +1658,38 @@ static bool is_empty_dac(struct hda_codec *codec, hda_nid_t dac)
 }
 
 static bool __parse_output_path(struct hda_codec *codec, hda_nid_t nid,
-                             hda_nid_t target_dac, struct nid_path *path,
-                             int depth, int wid_type)
+                               hda_nid_t target_dac, int with_aa_mix,
+                               struct nid_path *path, int depth)
 {
+       struct via_spec *spec = codec->spec;
        hda_nid_t conn[8];
        int i, nums;
 
+       if (nid == spec->aa_mix_nid) {
+               if (!with_aa_mix)
+                       return false;
+               with_aa_mix = 2; /* mark aa-mix is included */
+       }
+
        nums = snd_hda_get_connections(codec, nid, conn, ARRAY_SIZE(conn));
        for (i = 0; i < nums; i++) {
                if (get_wcaps_type(get_wcaps(codec, conn[i])) != AC_WID_AUD_OUT)
                        continue;
-               if (conn[i] == target_dac || is_empty_dac(codec, conn[i]))
-                       goto found;
+               if (conn[i] == target_dac || is_empty_dac(codec, conn[i])) {
+                       /* aa-mix is requested but not included? */
+                       if (!(spec->aa_mix_nid && with_aa_mix == 1))
+                               goto found;
+               }
        }
        if (depth >= MAX_NID_PATH_DEPTH)
                return false;
        for (i = 0; i < nums; i++) {
                unsigned int type;
                type = get_wcaps_type(get_wcaps(codec, conn[i]));
-               if (type == AC_WID_AUD_OUT ||
-                   (wid_type != -1 && type != wid_type))
+               if (type == AC_WID_AUD_OUT)
                        continue;
                if (__parse_output_path(codec, conn[i], target_dac,
-                                     path, depth + 1, AC_WID_AUD_SEL))
+                                       with_aa_mix, path, depth + 1))
                        goto found;
        }
        return false;
@@ -1708,11 +1704,15 @@ static bool __parse_output_path(struct hda_codec *codec, hda_nid_t nid,
 }
 
 static bool parse_output_path(struct hda_codec *codec, hda_nid_t nid,
-                             hda_nid_t target_dac, struct nid_path *path)
+                             hda_nid_t target_dac, int with_aa_mix,
+                             struct nid_path *path)
 {
-       if (__parse_output_path(codec, nid, target_dac, path, 1, -1)) {
+       if (__parse_output_path(codec, nid, target_dac, with_aa_mix, path, 1)) {
                path->path[path->depth] = nid;
                path->depth++;
+               snd_printdd("output-path: depth=%d, %02x/%02x/%02x/%02x/%02x\n",
+                           path->depth, path->path[0], path->path[1],
+                           path->path[2], path->path[3], path->path[4]);
                return true;
        }
        return false;
@@ -1728,14 +1728,24 @@ static int via_auto_fill_dac_nids(struct hda_codec *codec)
        spec->multiout.dac_nids = spec->private_dac_nids;
        dac_num = 0;
        for (i = 0; i < cfg->line_outs; i++) {
+               hda_nid_t dac = 0;
                nid = cfg->line_out_pins[i];
                if (!nid)
                        continue;
-               if (parse_output_path(codec, nid, 0, &spec->out_path[i])) {
-                       spec->private_dac_nids[i] = spec->out_path[i].path[0];
+               if (parse_output_path(codec, nid, 0, 0, &spec->out_path[i]))
+                       dac = spec->out_path[i].path[0];
+               if (!i && parse_output_path(codec, nid, dac, 1,
+                                           &spec->out_mix_path))
+                       dac = spec->out_mix_path.path[0];
+               if (dac) {
+                       spec->private_dac_nids[i] = dac;
                        dac_num++;
                }
        }
+       if (!spec->out_path[0].depth && spec->out_mix_path.depth) {
+               spec->out_path[0] = spec->out_mix_path;
+               spec->out_mix_path.depth = 0;
+       }
        spec->multiout.num_dacs = dac_num;
        return 0;
 }
@@ -1832,6 +1842,7 @@ static int via_auto_create_multi_out_ctls(struct hda_codec *codec)
 {
        struct via_spec *spec = codec->spec;
        struct auto_pin_cfg *cfg = &spec->autocfg;
+       struct nid_path *path;
        static const char * const chname[4] = {
                "Front", "Surround", "C/LFE", "Side"
        };
@@ -1857,13 +1868,12 @@ static int via_auto_create_multi_out_ctls(struct hda_codec *codec)
                dac = spec->multiout.dac_nids[i];
                if (!pin || !dac)
                        continue;
+               path = spec->out_path + i;
                if (i == HDA_CLFE) {
-                       err = create_ch_ctls(codec, "Center", 1, true,
-                                            &spec->out_path[i]);
+                       err = create_ch_ctls(codec, "Center", 1, true, path);
                        if (err < 0)
                                return err;
-                       err = create_ch_ctls(codec, "LFE", 2, true,
-                                            &spec->out_path[i]);
+                       err = create_ch_ctls(codec, "LFE", 2, true, path);
                        if (err < 0)
                                return err;
                } else {
@@ -1871,25 +1881,35 @@ static int via_auto_create_multi_out_ctls(struct hda_codec *codec)
                        if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT &&
                            cfg->line_outs == 1)
                                pfx = "Speaker";
-                       err = create_ch_ctls(codec, pfx, 3, true,
-                                            &spec->out_path[i]);
+                       err = create_ch_ctls(codec, pfx, 3, true, path);
                        if (err < 0)
                                return err;
                }
+               if (path != spec->out_path + i) {
+                       spec->out_path[i].vol_ctl = path->vol_ctl;
+                       spec->out_path[i].mute_ctl = path->mute_ctl;
+               }
+               if (path == spec->out_path && spec->out_mix_path.depth) {
+                       spec->out_mix_path.vol_ctl = path->vol_ctl;
+                       spec->out_mix_path.mute_ctl = path->mute_ctl;
+               }
        }
 
        idx = get_connection_index(codec, spec->aa_mix_nid,
                                   spec->multiout.dac_nids[0]);
        if (idx >= 0) {
                /* add control to mixer */
-               err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
-                                     "PCM Playback Volume",
+               const char *name;
+               name = spec->out_mix_path.depth ?
+                       "PCM Loopback Playback Volume" : "PCM Playback Volume";
+               err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
                                      HDA_COMPOSE_AMP_VAL(spec->aa_mix_nid, 3,
                                                          idx, HDA_INPUT));
                if (err < 0)
                        return err;
-               err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
-                                     "PCM Playback Switch",
+               name = spec->out_mix_path.depth ?
+                       "PCM Loopback Playback Switch" : "PCM Playback Switch";
+               err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
                                      HDA_COMPOSE_AMP_VAL(spec->aa_mix_nid, 3,
                                                          idx, HDA_INPUT));
                if (err < 0)
@@ -1906,70 +1926,167 @@ static int via_auto_create_hp_ctls(struct hda_codec *codec, hda_nid_t pin)
        struct via_spec *spec = codec->spec;
        struct nid_path *path;
        bool check_dac;
-       int err;
+       int i, err;
 
        if (!pin)
                return 0;
 
-       if (parse_output_path(codec, pin, 0, &spec->hp_path))
-               spec->hp_dac_nid = spec->hp_path.path[0];
-       else if (spec->multiout.dac_nids[HDA_SIDE] &&
-                parse_output_path(codec, pin,
-                                  spec->multiout.dac_nids[HDA_SIDE],
-                                  &spec->hp_path)) {
-               spec->hp_dac_nid = spec->hp_path.path[0];
-               spec->hp_indep_shared = true;
-       } else if (spec->multiout.dac_nids[HDA_CLFE] &&
-                parse_output_path(codec, pin,
-                                  spec->multiout.dac_nids[HDA_CLFE],
-                                  &spec->hp_path)) {
-               spec->hp_dac_nid = spec->hp_path.path[0];
-               spec->hp_indep_shared = true;
+       if (!parse_output_path(codec, pin, 0, 0, &spec->hp_indep_path)) {
+               for (i = HDA_SIDE; i >= HDA_CLFE; i--) {
+                       if (i < spec->multiout.num_dacs &&
+                           parse_output_path(codec, pin,
+                                             spec->multiout.dac_nids[i], 0,
+                                             &spec->hp_indep_path)) {
+                               spec->hp_indep_shared = i;
+                               break;
+                       }
+               }
        }
+       if (spec->hp_indep_path.depth) {
+               spec->hp_dac_nid = spec->hp_indep_path.path[0];
+               if (!spec->hp_indep_shared)
+                       spec->hp_path = spec->hp_indep_path;
+       }
+       /* optionally check front-path w/o AA-mix */
+       if (!spec->hp_path.depth)
+               parse_output_path(codec, pin,
+                                 spec->multiout.dac_nids[HDA_FRONT], 0,
+                                 &spec->hp_path);
 
        if (!parse_output_path(codec, pin, spec->multiout.dac_nids[HDA_FRONT],
-                              &spec->hp_dep_path) &&
-           !spec->hp_dac_nid)
+                              1, &spec->hp_mix_path) && !spec->hp_path.depth)
                return 0;
 
-       if (spec->hp_dac_nid && !spec->hp_indep_shared) {
+       if (spec->hp_path.depth) {
                path = &spec->hp_path;
                check_dac = true;
        } else {
-               path = &spec->hp_dep_path;
+               path = &spec->hp_mix_path;
                check_dac = false;
        }
        err = create_ch_ctls(codec, "Headphone", 3, check_dac, path);
        if (err < 0)
                return err;
-       if (spec->hp_dac_nid) {
-               spec->hp_dep_path.vol_ctl = spec->hp_path.vol_ctl;
-               spec->hp_dep_path.mute_ctl = spec->hp_path.mute_ctl;
+       if (check_dac) {
+               spec->hp_mix_path.vol_ctl = path->vol_ctl;
+               spec->hp_mix_path.mute_ctl = path->mute_ctl;
+       } else {
+               spec->hp_path.vol_ctl = path->vol_ctl;
+               spec->hp_path.mute_ctl = path->mute_ctl;
        }
-
        return 0;
 }
 
 static int via_auto_create_speaker_ctls(struct hda_codec *codec)
 {
        struct via_spec *spec = codec->spec;
+       struct nid_path *path;
+       bool check_dac;
        hda_nid_t pin, dac;
+       int err;
 
        pin = spec->autocfg.speaker_pins[0];
        if (!spec->autocfg.speaker_outs || !pin)
                return 0;
 
-       if (parse_output_path(codec, pin, 0, &spec->speaker_path)) {
+       if (parse_output_path(codec, pin, 0, 0, &spec->speaker_path))
                dac = spec->speaker_path.path[0];
-               spec->multiout.extra_out_nid[0] = dac;
-               return create_ch_ctls(codec, "Speaker", 3, true,
-                                     &spec->speaker_path);
+       if (!dac)
+               parse_output_path(codec, pin,
+                                 spec->multiout.dac_nids[HDA_FRONT], 0,
+                                 &spec->speaker_path);
+       if (!parse_output_path(codec, pin, spec->multiout.dac_nids[HDA_FRONT],
+                              1, &spec->speaker_mix_path) && !dac)
+               return 0;
+
+       /* no AA-path for front? */
+       if (!spec->out_mix_path.depth && spec->speaker_mix_path.depth)
+               dac = 0;
+
+       spec->speaker_dac_nid = dac;
+       spec->multiout.extra_out_nid[0] = dac;
+       if (dac) {
+               path = &spec->speaker_path;
+               check_dac = true;
+       } else {
+               path = &spec->speaker_mix_path;
+               check_dac = false;
+       }
+       err = create_ch_ctls(codec, "Speaker", 3, check_dac, path);
+       if (err < 0)
+               return err;
+       if (check_dac) {
+               spec->speaker_mix_path.vol_ctl = path->vol_ctl;
+               spec->speaker_mix_path.mute_ctl = path->mute_ctl;
+       } else {
+               spec->speaker_path.vol_ctl = path->vol_ctl;
+               spec->speaker_path.mute_ctl = path->mute_ctl;
        }
-       if (parse_output_path(codec, pin, spec->multiout.dac_nids[HDA_FRONT],
-                             &spec->speaker_path))
-               return create_ch_ctls(codec, "Speaker", 3, false,
-                                     &spec->speaker_path);
+       return 0;
+}
+
+#define via_aamix_ctl_info     via_pin_power_ctl_info
 
+static int via_aamix_ctl_get(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct via_spec *spec = codec->spec;
+       ucontrol->value.enumerated.item[0] = spec->aamix_mode;
+       return 0;
+}
+
+static void update_aamix_paths(struct hda_codec *codec, int do_mix,
+                              struct nid_path *nomix, struct nid_path *mix)
+{
+       if (do_mix) {
+               activate_output_path(codec, nomix, false, false);
+               activate_output_path(codec, mix, true, false);
+       } else {
+               activate_output_path(codec, mix, false, false);
+               activate_output_path(codec, nomix, true, false);
+       }
+}
+
+static int via_aamix_ctl_put(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct via_spec *spec = codec->spec;
+       unsigned int val = ucontrol->value.enumerated.item[0];
+
+       if (val == spec->aamix_mode)
+               return 0;
+       spec->aamix_mode = val;
+       /* update front path */
+       update_aamix_paths(codec, val, &spec->out_path[0], &spec->out_mix_path);
+       /* update HP path */
+       if (!spec->hp_independent_mode) {
+               update_aamix_paths(codec, val, &spec->hp_path,
+                                  &spec->hp_mix_path);
+       }
+       /* update speaker path */
+       update_aamix_paths(codec, val, &spec->speaker_path,
+                          &spec->speaker_mix_path);
+       return 1;
+}
+
+static const struct snd_kcontrol_new via_aamix_ctl_enum = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Loopback Mixing",
+       .info = via_aamix_ctl_info,
+       .get = via_aamix_ctl_get,
+       .put = via_aamix_ctl_put,
+};
+
+static int via_auto_create_loopback_switch(struct hda_codec *codec)
+{
+       struct via_spec *spec = codec->spec;
+
+       if (!spec->aa_mix_nid || !spec->out_mix_path.depth)
+               return 0; /* no loopback switching available */
+       if (!via_clone_control(spec, &via_aamix_ctl_enum))
+               return -ENOMEM;
        return 0;
 }
 
@@ -2438,6 +2555,9 @@ static int via_parse_auto_config(struct hda_codec *codec)
        if (err < 0)
                return err;
        err = via_auto_create_speaker_ctls(codec);
+       if (err < 0)
+               return err;
+       err = via_auto_create_loopback_switch(codec);
        if (err < 0)
                return err;
        err = via_auto_create_analog_input_ctls(codec);
@@ -2453,7 +2573,7 @@ static int via_parse_auto_config(struct hda_codec *codec)
                spec->mixers[spec->num_mixers++] = spec->kctls.list;
 
 
-       if (spec->hp_dac_nid && spec->hp_dep_path.depth) {
+       if (spec->hp_dac_nid && spec->hp_mix_path.depth) {
                err = via_hp_build(codec);
                if (err < 0)
                        return err;