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;
#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);
}
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];
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)) {
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;
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);
}
}
{
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);
{
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)
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 */
}
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;
}
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;
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;
}
{
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"
};
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 {
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)
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;
}
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);
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;