]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - sound/pci/hda/patch_via.c
ALSA: hda - Implement dynamic loopback control for VIA codecs
[mv-sheeva.git] / sound / pci / hda / patch_via.c
index 5232abc341f8131735e42eea4b6f540981ddcc13..76c688409cd8d5d901c94fb6308f70056e6264bd 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;