From 09a9ad69a5467fbda3fd358d2be155c22aa416e4 Mon Sep 17 00:00:00 2001
From: Takashi Iwai <tiwai@suse.de>
Date: Tue, 21 Jun 2011 15:57:44 +0200
Subject: [PATCH] ALSA: hda - VT1708 independent HP routing fix

The codecs like VT1708 needs more complicated routing using the mixer
widget rather than the simple selector widgets.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 sound/pci/hda/patch_via.c | 222 +++++++++++++++++++++-----------------
 1 file changed, 122 insertions(+), 100 deletions(-)

diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c
index 5b907b356951..bceb6b2364fe 100644
--- a/sound/pci/hda/patch_via.c
+++ b/sound/pci/hda/patch_via.c
@@ -83,10 +83,20 @@ enum VIA_HDA_CODEC {
 
 #define MAX_NID_PATH_DEPTH	5
 
+/* output-path: DAC -> ... -> pin
+ * idx[] contains the source index number of the next widget;
+ * e.g. idx[0] is the index of the DAC selected by path[1] widget
+ * multi[] indicates whether it's a selector widget with multi-connectors
+ * (i.e. the connection selection is mandatory)
+ * vol_ctl and mute_ctl contains the NIDs for the assigned mixers
+ */
 struct nid_path {
 	int depth;
 	hda_nid_t path[MAX_NID_PATH_DEPTH];
-	short idx[MAX_NID_PATH_DEPTH];
+	unsigned char idx[MAX_NID_PATH_DEPTH];
+	unsigned char multi[MAX_NID_PATH_DEPTH];
+	unsigned int vol_ctl;
+	unsigned int mute_ctl;
 };
 
 struct via_spec {
@@ -422,43 +432,39 @@ static bool check_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
 	return false;
 }
 
-#define have_vol_or_mute(codec, nid, dir) \
-	check_amp_caps(codec, nid, dir, AC_AMPCAP_NUM_STEPS | AC_AMPCAP_MUTE)
+#define have_mute(codec, nid, dir) \
+	check_amp_caps(codec, nid, dir, AC_AMPCAP_MUTE)
 
-/* unmute input amp and select the specificed source */
-static void unmute_and_select(struct hda_codec *codec, hda_nid_t nid,
-			      hda_nid_t src, hda_nid_t mix)
+/* enable/disable the output-route */
+static void activate_output_path(struct hda_codec *codec, struct nid_path *path,
+				 bool enable, bool force)
 {
-	int idx, num_conns;
-
-	idx = __get_connection_index(codec, nid, src, &num_conns);
-	if (idx < 0)
-		return;
-
-	/* select the route explicitly when multiple connections exist */
-	if (num_conns > 1 &&
-	    get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_MIX)
-		snd_hda_codec_write(codec, nid, 0,
-				    AC_VERB_SET_CONNECT_SEL, idx);
-
-	/* unmute if the input amp is present */
-	if (have_vol_or_mute(codec, nid, HDA_INPUT))
-		snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
-				    AMP_IN_UNMUTE(idx));
-
-	/* unmute the src output */
-	if (have_vol_or_mute(codec, src, HDA_OUTPUT))
-		snd_hda_codec_write(codec, src, 0, AC_VERB_SET_AMP_GAIN_MUTE,
-				    AMP_OUT_UNMUTE);
-
-	/* unmute AA-path if present */
-	if (!mix || mix == src)
-		return;
-	idx = __get_connection_index(codec, nid, mix, NULL);
-	if (idx >= 0 && have_vol_or_mute(codec, nid, HDA_INPUT))
-		snd_hda_codec_write(codec, nid, 0,
-				    AC_VERB_SET_AMP_GAIN_MUTE,
-				    AMP_IN_UNMUTE(idx));
+	int i;
+	for (i = 0; i < path->depth; i++) {
+		hda_nid_t src, dst;
+		int idx = path->idx[i];
+		src = path->path[i];			
+		if (i < path->depth - 1)
+			dst = path->path[i + 1];
+		else
+			dst = 0;
+		if (enable && path->multi[i])
+			snd_hda_codec_write(codec, dst, 0,
+					    AC_VERB_SET_CONNECT_SEL, idx);
+		if (have_mute(codec, dst, HDA_INPUT)) {
+			int val = enable ? AMP_IN_UNMUTE(idx) :
+				AMP_IN_MUTE(idx);
+			snd_hda_codec_write(codec, dst, 0,
+					    AC_VERB_SET_AMP_GAIN_MUTE, val);
+		}
+		if (!force && (src == path->vol_ctl || src == path->mute_ctl))
+			continue;
+		if (have_mute(codec, src, HDA_OUTPUT)) {
+			int val = enable ? AMP_OUT_UNMUTE : AMP_OUT_MUTE;
+			snd_hda_codec_write(codec, src, 0,
+					    AC_VERB_SET_AMP_GAIN_MUTE, val);
+		}
+	}
 }
 
 /* set the given pin as output */
@@ -474,16 +480,18 @@ static void init_output_pin(struct hda_codec *codec, hda_nid_t pin,
 				    AC_VERB_SET_EAPD_BTLENABLE, 0x02);
 }
 
-static void via_auto_init_output(struct hda_codec *codec, hda_nid_t pin,
-				 int pin_type, struct nid_path *path)
+static void via_auto_init_output(struct hda_codec *codec,
+				 struct nid_path *path, int pin_type,
+				 bool force)
 {
 	struct via_spec *spec = codec->spec;
 	unsigned int caps;
-	hda_nid_t nid;
-	int i;
+	hda_nid_t pin, nid;
+	int i, idx;
 
-	if (!pin)
+	if (!path->depth)
 		return;
+	pin = path->path[path->depth - 1];
 
 	init_output_pin(codec, pin, pin_type);
 	caps = query_amp_caps(codec, pin, HDA_OUTPUT);
@@ -494,34 +502,48 @@ static void via_auto_init_output(struct hda_codec *codec, hda_nid_t pin,
 				    AMP_OUT_MUTE | val);
 	}
 
-	/* initialize the output path */
+	activate_output_path(codec, path, true, force);
+
+	/* initialize the AA-path */
+	if (!spec->aa_mix_nid)
+		return;
 	for (i = path->depth - 1; i > 0; i--) {
-		nid = path->path[i - 1];
-		unmute_and_select(codec, path->path[i], nid, spec->aa_mix_nid);
+		nid = path->path[i];
+		idx = get_connection_index(codec, nid, spec->aa_mix_nid);
+		if (idx >= 0) {
+			if (have_mute(codec, nid, HDA_INPUT))
+				snd_hda_codec_write(codec, nid, 0,
+						    AC_VERB_SET_AMP_GAIN_MUTE,
+						    AMP_IN_UNMUTE(idx));
+			break;
+		}
 	}
 }
 
-
 static void via_auto_init_multi_out(struct hda_codec *codec)
 {
 	struct via_spec *spec = codec->spec;
 	int i;
 
 	for (i = 0; i < spec->autocfg.line_outs + spec->smart51_nums; i++)
-		via_auto_init_output(codec, spec->autocfg.line_out_pins[i],
-				     PIN_OUT, &spec->out_path[i]);
+		via_auto_init_output(codec, &spec->out_path[i], PIN_OUT, true);
 }
 
 static void via_auto_init_hp_out(struct hda_codec *codec)
 {
 	struct via_spec *spec = codec->spec;
 
-	if (spec->hp_dac_nid)
-		via_auto_init_output(codec, spec->autocfg.hp_pins[0], PIN_HP,
-				     &spec->hp_path);
-	else
-		via_auto_init_output(codec, spec->autocfg.hp_pins[0], PIN_HP,
-				     &spec->hp_dep_path);
+	if (!spec->hp_dac_nid) {
+		via_auto_init_output(codec, &spec->hp_dep_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);
+	} else {
+		activate_output_path(codec, &spec->hp_path, false, false);
+		via_auto_init_output(codec, &spec->hp_dep_path, PIN_HP, true);
+	}
 }
 
 static void via_auto_init_speaker_out(struct hda_codec *codec)
@@ -529,8 +551,7 @@ static void via_auto_init_speaker_out(struct hda_codec *codec)
 	struct via_spec *spec = codec->spec;
 
 	if (spec->autocfg.speaker_outs)
-		via_auto_init_output(codec, spec->autocfg.speaker_pins[0],
-				     PIN_OUT, &spec->speaker_path);
+		via_auto_init_output(codec, &spec->speaker_path, PIN_OUT, true);
 }
 
 static bool is_smart51_pins(struct hda_codec *codec, hda_nid_t pin);
@@ -738,27 +759,14 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
 {
 	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
 	struct via_spec *spec = codec->spec;
-	hda_nid_t nid, src;
-	int i, idx, num_conns;
-	struct nid_path *path;
 
 	spec->hp_independent_mode = !!ucontrol->value.enumerated.item[0];
-	if (spec->hp_independent_mode)
-		path = &spec->hp_path;
-	else
-		path = &spec->hp_dep_path;
-
-	/* re-route the output path */
-	for (i = path->depth - 1; i > 0; i--) {
-		nid = path->path[i];
-		src = path->path[i - 1];
-		idx = __get_connection_index(codec, nid, src, &num_conns);
-		if (idx < 0)
-			continue;
-		if (num_conns > 1 &&
-		    get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_MIX)
-			snd_hda_codec_write(codec, nid, 0,
-					    AC_VERB_SET_CONNECT_SEL, idx);
+	if (spec->hp_independent_mode) {
+		activate_output_path(codec, &spec->hp_dep_path, 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);
 	}
 
 	/* update jack power state */
@@ -1577,12 +1585,8 @@ static bool __parse_output_path(struct hda_codec *codec, hda_nid_t nid,
 	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])) {
-			path->path[0] = conn[i];
-			path->idx[0] = i;
-			path->depth = 1;
-			return true;
-		}
+		if (conn[i] == target_dac || is_empty_dac(codec, conn[i]))
+			goto found;
 	}
 	if (depth >= MAX_NID_PATH_DEPTH)
 		return false;
@@ -1593,14 +1597,18 @@ static bool __parse_output_path(struct hda_codec *codec, hda_nid_t nid,
 		    (wid_type != -1 && type != wid_type))
 			continue;
 		if (__parse_output_path(codec, conn[i], target_dac,
-				      path, depth + 1, AC_WID_AUD_SEL)) {
-			path->path[path->depth] = conn[i];
-			path->idx[path->depth] = i;
-			path->depth++;
-			return true;
-		}
+				      path, depth + 1, AC_WID_AUD_SEL))
+			goto found;
 	}
 	return false;
+
+ found:
+	path->path[path->depth] = conn[i];
+	path->idx[path->depth] = i;
+	if (nums > 1 && get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_MIX)
+		path->multi[path->depth] = 1;
+	path->depth++;
+	return true;
 }
 
 static bool parse_output_path(struct hda_codec *codec, hda_nid_t nid,
@@ -1634,18 +1642,16 @@ static int via_auto_fill_dac_nids(struct hda_codec *codec)
 }
 
 static int create_ch_ctls(struct hda_codec *codec, const char *pfx,
-			  hda_nid_t pin, hda_nid_t dac, int chs)
+			  int chs, bool check_dac, struct nid_path *path)
 {
 	struct via_spec *spec = codec->spec;
 	char name[32];
-	hda_nid_t nid, sel, conn[8];
-	int nums, err;
+	hda_nid_t dac, pin, sel, nid;
+	int err;
 
-	/* check selector widget connected to the pin */
-	sel = 0;
-	nums = snd_hda_get_connections(codec, pin, conn, ARRAY_SIZE(conn));
-	if (nums == 1 && conn[0] != pin)
-		sel = conn[0];
+	dac = check_dac ? path->path[0] : 0;
+	pin = path->path[path->depth - 1];
+	sel = path->depth > 1 ? path->path[1] : 0;
 
 	if (dac && check_amp_caps(codec, dac, HDA_OUTPUT, AC_AMPCAP_NUM_STEPS))
 		nid = dac;
@@ -1661,6 +1667,7 @@ static int create_ch_ctls(struct hda_codec *codec, const char *pfx,
 			      HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT));
 		if (err < 0)
 			return err;
+		path->vol_ctl = nid;
 	}
 
 	if (dac && check_amp_caps(codec, dac, HDA_OUTPUT, AC_AMPCAP_MUTE))
@@ -1677,6 +1684,7 @@ static int create_ch_ctls(struct hda_codec *codec, const char *pfx,
 			      HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT));
 		if (err < 0)
 			return err;
+		path->mute_ctl = nid;
 	}
 	return 0;
 }
@@ -1747,10 +1755,12 @@ static int via_auto_create_multi_out_ctls(struct hda_codec *codec)
 		if (!pin || !dac)
 			continue;
 		if (i == HDA_CLFE) {
-			err = create_ch_ctls(codec, "Center", pin, dac, 1);
+			err = create_ch_ctls(codec, "Center", 1, true,
+					     &spec->out_path[i]);
 			if (err < 0)
 				return err;
-			err = create_ch_ctls(codec, "LFE", pin, dac, 2);
+			err = create_ch_ctls(codec, "LFE", 2, true,
+					     &spec->out_path[i]);
 			if (err < 0)
 				return err;
 		} else {
@@ -1758,7 +1768,8 @@ 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, pin, dac, 3);
+			err = create_ch_ctls(codec, pfx, 3, true,
+					     &spec->out_path[i]);
 			if (err < 0)
 				return err;
 		}
@@ -1790,6 +1801,7 @@ static int via_auto_create_multi_out_ctls(struct hda_codec *codec)
 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;
 	int err;
 
 	if (!pin)
@@ -1803,9 +1815,17 @@ static int via_auto_create_hp_ctls(struct hda_codec *codec, hda_nid_t pin)
 	    !spec->hp_dac_nid)
 		return 0;
 
-	err = create_ch_ctls(codec, "Headphone", pin, spec->hp_dac_nid, 3);
+	if (spec->hp_dac_nid)
+		path = &spec->hp_path;
+	else
+		path = &spec->hp_dep_path;
+	err = create_ch_ctls(codec, "Headphone", 3, false, 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;
+	}
 
 	return 0;
 }
@@ -1822,11 +1842,13 @@ static int via_auto_create_speaker_ctls(struct hda_codec *codec)
 	if (parse_output_path(codec, pin, 0, &spec->speaker_path)) {
 		dac = spec->speaker_path.path[0];
 		spec->multiout.extra_out_nid[0] = dac;
-		return create_ch_ctls(codec, "Speaker", pin, dac, 3);
+		return create_ch_ctls(codec, "Speaker", 3, true,
+				      &spec->speaker_path);
 	}
 	if (parse_output_path(codec, pin, spec->multiout.dac_nids[HDA_FRONT],
 			      &spec->speaker_path))
-		return create_ch_ctls(codec, "Speaker", pin, 0, 3);
+		return create_ch_ctls(codec, "Speaker", 3, false,
+				      &spec->speaker_path);
 
 	return 0;
 }
-- 
GitLab