ALSA: hda - Add auto-parser support to cxt5051 / CX20561 Hermosa

Extend the existing auto-parser for CX2064x for cxt5051 codec.
Now the auto-parser supports ADC-switching for this codec.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c
index 19416e3..cdb9f49 100644
--- a/sound/pci/hda/patch_conexant.c
+++ b/sound/pci/hda/patch_conexant.c
@@ -89,6 +89,8 @@
 	unsigned int cur_adc_stream_tag;
 	unsigned int cur_adc_format;
 
+	const struct hda_pcm_stream *capture_stream;
+
 	/* capture source */
 	const struct hda_input_mux *input_mux;
 	const hda_nid_t *capsrc_nids;
@@ -106,6 +108,7 @@
 	/* dynamic controls, init_verbs and input_mux */
 	struct auto_pin_cfg autocfg;
 	struct hda_input_mux private_imux;
+	hda_nid_t imux_adcs[HDA_MAX_NUM_INPUTS];
 	hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
 	struct pin_dac_pair dac_info[8];
 	int dac_info_filled;
@@ -119,6 +122,8 @@
 	unsigned int hp_laptop:1;
 	unsigned int asus:1;
 
+	unsigned int adc_switching:1;
+
 	unsigned int ext_mic_present;
 	unsigned int recording;
 	void (*capture_prepare)(struct hda_codec *codec);
@@ -319,13 +324,19 @@
 		spec->multiout.max_channels;
 	info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
 		spec->multiout.dac_nids[0];
-	if (codec->vendor_id == 0x14f15051)
-		info->stream[SNDRV_PCM_STREAM_CAPTURE] =
-			cx5051_pcm_analog_capture;
-	else
-		info->stream[SNDRV_PCM_STREAM_CAPTURE] =
-			conexant_pcm_analog_capture;
-	info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_adc_nids;
+	if (spec->capture_stream)
+		info->stream[SNDRV_PCM_STREAM_CAPTURE] = *spec->capture_stream;
+	else {
+		if (codec->vendor_id == 0x14f15051)
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+				cx5051_pcm_analog_capture;
+		else {
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+				conexant_pcm_analog_capture;
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams =
+				spec->num_adc_nids;
+		}
+	}
 	info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
 
 	if (spec->multiout.dig_out_nid) {
@@ -564,6 +575,7 @@
 #define set_beep_amp(spec, nid, idx, dir) /* NOP */
 #endif
 
+static int patch_conexant_auto(struct hda_codec *codec);
 /*
  * EAPD control
  * the private value = nid | (invert << 8)
@@ -1906,6 +1918,7 @@
 	CXT5051_F700,       /* HP Compaq Presario F700 */
 	CXT5051_TOSHIBA,	/* Toshiba M300 & co */
 	CXT5051_IDEAPAD,	/* Lenovo IdeaPad Y430 */
+	CXT5051_AUTO,		/* auto-parser */
 	CXT5051_MODELS
 };
 
@@ -1917,6 +1930,7 @@
 	[CXT5051_F700]          = "hp-700",
 	[CXT5051_TOSHIBA]	= "toshiba",
 	[CXT5051_IDEAPAD]	= "ideapad",
+	[CXT5051_AUTO]		= "auto",
 };
 
 static const struct snd_pci_quirk cxt5051_cfg_tbl[] = {
@@ -1937,6 +1951,17 @@
 	struct conexant_spec *spec;
 	int board_config;
 
+	board_config = snd_hda_check_board_config(codec, CXT5051_MODELS,
+						  cxt5051_models,
+						  cxt5051_cfg_tbl);
+	if (board_config < 0)
+		board_config = CXT5051_AUTO;
+	if (board_config == CXT5051_AUTO) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		return patch_conexant_auto(codec);
+	}
+
 	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
 	if (!spec)
 		return -ENOMEM;
@@ -1967,9 +1992,6 @@
 
 	codec->patch_ops.unsol_event = cxt5051_hp_unsol_event;
 
-	board_config = snd_hda_check_board_config(codec, CXT5051_MODELS,
-						  cxt5051_models,
-						  cxt5051_cfg_tbl);
 	spec->auto_mic = AUTO_MIC_PORTB | AUTO_MIC_PORTC;
 	switch (board_config) {
 	case CXT5051_HP:
@@ -3195,6 +3217,44 @@
  * Automatic parser for CX20641 & co
  */
 
+static int cx_auto_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       unsigned int stream_tag,
+				       unsigned int format,
+				       struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	hda_nid_t adc = spec->imux_adcs[spec->cur_mux[0]];
+	if (spec->adc_switching) {
+		spec->cur_adc = adc;
+		spec->cur_adc_stream_tag = stream_tag;
+		spec->cur_adc_format = format;
+	}
+	snd_hda_codec_setup_stream(codec, adc, stream_tag, 0, format);
+	return 0;
+}
+
+static int cx_auto_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
+	spec->cur_adc = 0;
+	return 0;
+}
+
+static const struct hda_pcm_stream cx_auto_pcm_analog_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0, /* fill later */
+	.ops = {
+		.prepare = cx_auto_capture_pcm_prepare,
+		.cleanup = cx_auto_capture_pcm_cleanup
+	},
+};
+
 static const hda_nid_t cx_auto_adc_nids[] = { 0x14 };
 
 /* get the connection index of @nid in the widget @mux */
@@ -3211,6 +3271,15 @@
 	return -1;
 }
 
+static int has_multi_connection(struct hda_codec *codec, hda_nid_t mux)
+{
+	hda_nid_t conn[HDA_MAX_NUM_INPUTS];
+	int nums;
+
+	nums = snd_hda_get_connections(codec, mux, conn, ARRAY_SIZE(conn));
+	return nums > 1;
+}
+
 /* get an unassigned DAC from the given list.
  * Return the nid if found and reduce the DAC list, or return zero if
  * not found
@@ -3362,25 +3431,89 @@
 	cx_auto_turn_eapd(codec, cfg->speaker_outs, cfg->speaker_pins, !present);
 }
 
+static int cx_auto_mux_enum_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+
+	return snd_hda_input_mux_info(&spec->private_imux, uinfo);
+}
+
+static int cx_auto_mux_enum_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+
+	ucontrol->value.enumerated.item[0] = spec->cur_mux[0];
+	return 0;
+}
+
+static int cx_auto_mux_enum_update(struct hda_codec *codec,
+				   const struct hda_input_mux *imux,
+				   unsigned int idx)
+{
+	struct conexant_spec *spec = codec->spec;
+	hda_nid_t adc;
+
+	if (!imux->num_items)
+		return 0;
+	if (idx >= imux->num_items)
+		idx = imux->num_items - 1;
+	if (spec->cur_mux[0] == idx)
+		return 0;
+	adc = spec->imux_adcs[idx];
+	if (has_multi_connection(codec, adc))
+		snd_hda_codec_write(codec, adc, 0,
+				    AC_VERB_SET_CONNECT_SEL,
+				    imux->items[idx].index);
+	if (spec->cur_adc && spec->cur_adc != adc) {
+		/* stream is running, let's swap the current ADC */
+		__snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
+		spec->cur_adc = adc;
+		snd_hda_codec_setup_stream(codec, adc,
+					   spec->cur_adc_stream_tag, 0,
+					   spec->cur_adc_format);
+	}
+	spec->cur_mux[0] = idx;
+	return 1;
+}
+
+static int cx_auto_mux_enum_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+
+	return cx_auto_mux_enum_update(codec, &spec->private_imux,
+				       ucontrol->value.enumerated.item[0]);
+}
+
+static const struct snd_kcontrol_new cx_auto_capture_mixers[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = cx_auto_mux_enum_info,
+		.get = cx_auto_mux_enum_get,
+		.put = cx_auto_mux_enum_put
+	},
+	{}
+};
+
 /* automatic switch internal and external mic */
 static void cx_auto_automic(struct hda_codec *codec)
 {
 	struct conexant_spec *spec = codec->spec;
 	struct auto_pin_cfg *cfg = &spec->autocfg;
-	struct hda_input_mux *imux = &spec->private_imux;
 	int ext_idx = spec->auto_mic_ext;
 
 	if (!spec->auto_mic)
 		return;
-	if (snd_hda_jack_detect(codec, cfg->inputs[ext_idx].pin)) {
-		snd_hda_codec_write(codec, spec->adc_nids[0], 0,
-				    AC_VERB_SET_CONNECT_SEL,
-				    imux->items[ext_idx].index);
-	} else {
-		snd_hda_codec_write(codec, spec->adc_nids[0], 0,
-				    AC_VERB_SET_CONNECT_SEL,
-				    imux->items[!ext_idx].index);
-	}
+	if (snd_hda_jack_detect(codec, cfg->inputs[ext_idx].pin))
+		cx_auto_mux_enum_update(codec, &spec->private_imux, ext_idx);
+	else
+		cx_auto_mux_enum_update(codec, &spec->private_imux, !ext_idx);
 }
 
 static void cx_auto_unsol_event(struct hda_codec *codec, unsigned int res)
@@ -3442,22 +3575,33 @@
 	struct conexant_spec *spec = codec->spec;
 	struct auto_pin_cfg *cfg = &spec->autocfg;
 	struct hda_input_mux *imux;
-	int i;
+	int i, j;
 
 	imux = &spec->private_imux;
 	for (i = 0; i < cfg->num_inputs; i++) {
-		int idx = get_connection_index(codec, spec->adc_nids[0],
+		for (j = 0; j < spec->num_adc_nids; j++) {
+			hda_nid_t adc = spec->adc_nids[j];
+			int idx = get_connection_index(codec, adc,
 					       cfg->inputs[i].pin);
-		if (idx >= 0) {
-			const char *label;
-			label = hda_get_autocfg_input_label(codec, cfg, i);
-			snd_hda_add_imux_item(imux, label, idx, NULL);
+			if (idx >= 0) {
+				const char *label;
+				label = hda_get_autocfg_input_label(codec, cfg, i);
+				snd_hda_add_imux_item(imux, label, idx, NULL);
+				spec->imux_adcs[i] = adc;
+				break;
+			}
 		}
 	}
 	if (imux->num_items == 2 && cfg->num_inputs == 2)
 		cx_auto_check_auto_mic(codec);
-	if (imux->num_items > 1 && !spec->auto_mic)
-		spec->input_mux = imux;
+	if (imux->num_items > 1 && !spec->auto_mic) {
+		for (i = 1; i < imux->num_items; i++) {
+			if (spec->imux_adcs[i] != spec->imux_adcs[0]) {
+				spec->adc_switching = 1;
+				break;
+			}
+		}
+	}
 }
 
 /* get digital-input audio widget corresponding to the given pin */
@@ -3736,39 +3880,37 @@
 	return 0;
 }
 
+static int cx_auto_add_capture_volume(struct hda_codec *codec, hda_nid_t nid,
+				      const char *label, const char *pfx,
+				      int cidx)
+{
+	struct conexant_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->num_adc_nids; i++) {
+		hda_nid_t adc_nid = spec->adc_nids[i];
+		int idx = get_connection_index(codec, adc_nid, nid);
+		if (idx < 0)
+			continue;
+		return cx_auto_add_volume_idx(codec, label, pfx,
+					      cidx, adc_nid, HDA_INPUT, idx);
+	}
+	return 0;
+}
+
 static int cx_auto_build_input_controls(struct hda_codec *codec)
 {
 	struct conexant_spec *spec = codec->spec;
 	struct auto_pin_cfg *cfg = &spec->autocfg;
 	static const char *prev_label;
-	int i, err, cidx, conn_len;
-	hda_nid_t conn[HDA_MAX_CONNECTIONS];
-
-	int multi_adc_volume = 0; /* If the ADC nid has several input volumes */
-	int adc_nid = spec->adc_nids[0];
-
-	conn_len = snd_hda_get_connections(codec, adc_nid, conn,
-					   HDA_MAX_CONNECTIONS);
-	if (conn_len < 0)
-		return conn_len;
-
-	multi_adc_volume = cfg->num_inputs > 1 && conn_len > 1;
-	if (!multi_adc_volume) {
-		err = cx_auto_add_volume(codec, "Capture", "", 0, adc_nid,
-					 HDA_INPUT);
-		if (err < 0)
-			return err;
-	}
+	int i, err, cidx;
 
 	prev_label = NULL;
 	cidx = 0;
 	for (i = 0; i < cfg->num_inputs; i++) {
 		hda_nid_t nid = cfg->inputs[i].pin;
 		const char *label;
-		int j;
 		int pin_amp = get_wcaps(codec, nid) & AC_WCAP_IN_AMP;
-		if (!pin_amp && !multi_adc_volume)
-			continue;
 
 		label = hda_get_autocfg_input_label(codec, cfg, i);
 		if (label == prev_label)
@@ -3784,18 +3926,23 @@
 				return err;
 		}
 
-		if (!multi_adc_volume)
-			continue;
-		for (j = 0; j < conn_len; j++) {
-			if (conn[j] == nid) {
-				err = cx_auto_add_volume_idx(codec, label,
-				    " Capture", cidx, adc_nid, HDA_INPUT, j);
-				if (err < 0)
-					return err;
-				break;
-			}
+		if (cfg->num_inputs == 1) {
+			err = cx_auto_add_capture_volume(codec, nid,
+							 "Capture", "", cidx);
+		} else {
+			err = cx_auto_add_capture_volume(codec, nid,
+							 label, " Capture", cidx);
 		}
+		if (err < 0)
+			return err;
 	}
+
+	if (spec->private_imux.num_items > 1 && !spec->auto_mic) {
+		err = snd_hda_add_new_ctls(codec, cx_auto_capture_mixers);
+		if (err < 0)
+			return err;
+	}
+
 	return 0;
 }
 
@@ -3833,15 +3980,23 @@
 	if (!spec)
 		return -ENOMEM;
 	codec->spec = spec;
-	spec->adc_nids = cx_auto_adc_nids;
-	spec->num_adc_nids = ARRAY_SIZE(cx_auto_adc_nids);
-	spec->capsrc_nids = spec->adc_nids;
+	if (codec->vendor_id == 0x14f15051) {
+		codec->pin_amp_workaround = 1;
+		spec->adc_nids = cxt5051_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(cxt5051_adc_nids);
+		spec->capsrc_nids = spec->adc_nids;
+	} else {
+		spec->adc_nids = cx_auto_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(cx_auto_adc_nids);
+		spec->capsrc_nids = spec->adc_nids;
+	}
 	err = cx_auto_parse_auto_config(codec);
 	if (err < 0) {
 		kfree(codec->spec);
 		codec->spec = NULL;
 		return err;
 	}
+	spec->capture_stream = &cx_auto_pcm_analog_capture;
 	codec->patch_ops = cx_auto_patch_ops;
 	if (spec->beep_amp)
 		snd_hda_attach_beep_device(codec, spec->beep_amp);