Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc261738 authored by Takashi Iwai's avatar Takashi Iwai
Browse files

ALSA: hda - Treat stereo-to-mono mix properly



The commit [ef403edb: ALSA: hda - Don't access stereo amps for
mono channel widgets] fixed the handling of mono widgets in general,
but it still misses an exceptional case: namely, a mono mixer widget
taking a single stereo input.  In this case, it has stereo volumes
although it's a mono widget, and thus we have to take care of both
left and right input channels, as stated in HD-audio spec ("7.1.3
Widget Interconnection Rules").

This patch covers this missing piece by adding proper checks of stereo
amps in both the generic parser and the proc output codes.

Reported-by: default avatarRaymond Yau <superquad.vortex2@gmail.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 06e5801b
Loading
Loading
Loading
Loading
+19 −2
Original line number Diff line number Diff line
@@ -687,13 +687,30 @@ static int get_amp_val_to_activate(struct hda_codec *codec, hda_nid_t nid,
	return val;
}

/* is this a stereo widget or a stereo-to-mono mix? */
static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid, int dir)
{
	unsigned int wcaps = get_wcaps(codec, nid);
	hda_nid_t conn;

	if (wcaps & AC_WCAP_STEREO)
		return true;
	if (dir != HDA_INPUT || get_wcaps_type(wcaps) != AC_WID_AUD_MIX)
		return false;
	if (snd_hda_get_num_conns(codec, nid) != 1)
		return false;
	if (snd_hda_get_connections(codec, nid, &conn, 1) < 0)
		return false;
	return !!(get_wcaps(codec, conn) & AC_WCAP_STEREO);
}

/* initialize the amp value (only at the first time) */
static void init_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx)
{
	unsigned int caps = query_amp_caps(codec, nid, dir);
	int val = get_amp_val_to_activate(codec, nid, dir, caps, false);

	if (get_wcaps(codec, nid) & AC_WCAP_STEREO)
	if (is_stereo_amps(codec, nid, dir))
		snd_hda_codec_amp_init_stereo(codec, nid, dir, idx, 0xff, val);
	else
		snd_hda_codec_amp_init(codec, nid, 0, dir, idx, 0xff, val);
@@ -703,7 +720,7 @@ static void init_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx)
static int update_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx,
		      unsigned int mask, unsigned int val)
{
	if (get_wcaps(codec, nid) & AC_WCAP_STEREO)
	if (is_stereo_amps(codec, nid, dir))
		return snd_hda_codec_amp_stereo(codec, nid, dir, idx,
						mask, val);
	else
+30 −8
Original line number Diff line number Diff line
@@ -134,13 +134,38 @@ static void print_amp_caps(struct snd_info_buffer *buffer,
		    (caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT);
}

/* is this a stereo widget or a stereo-to-mono mix? */
static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid,
			   int dir, unsigned int wcaps, int indices)
{
	hda_nid_t conn;

	if (wcaps & AC_WCAP_STEREO)
		return true;
	/* check for a stereo-to-mono mix; it must be:
	 * only a single connection, only for input, and only a mixer widget
	 */
	if (indices != 1 || dir != HDA_INPUT ||
	    get_wcaps_type(wcaps) != AC_WID_AUD_MIX)
		return false;

	if (snd_hda_get_raw_connections(codec, nid, &conn, 1) < 0)
		return false;
	/* the connection source is a stereo? */
	wcaps = snd_hda_param_read(codec, conn, AC_PAR_AUDIO_WIDGET_CAP);
	return !!(wcaps & AC_WCAP_STEREO);
}

static void print_amp_vals(struct snd_info_buffer *buffer,
			   struct hda_codec *codec, hda_nid_t nid,
			   int dir, int stereo, int indices)
			   int dir, unsigned int wcaps, int indices)
{
	unsigned int val;
	bool stereo;
	int i;

	stereo = is_stereo_amps(codec, nid, dir, wcaps, indices);

	dir = dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
	for (i = 0; i < indices; i++) {
		snd_iprintf(buffer, " [");
@@ -757,12 +782,10 @@ static void print_codec_info(struct snd_info_entry *entry,
			    (codec->single_adc_amp &&
			     wid_type == AC_WID_AUD_IN))
				print_amp_vals(buffer, codec, nid, HDA_INPUT,
					       wid_caps & AC_WCAP_STEREO,
					       1);
					       wid_caps, 1);
			else
				print_amp_vals(buffer, codec, nid, HDA_INPUT,
					       wid_caps & AC_WCAP_STEREO,
					       conn_len);
					       wid_caps, conn_len);
		}
		if (wid_caps & AC_WCAP_OUT_AMP) {
			snd_iprintf(buffer, "  Amp-Out caps: ");
@@ -771,11 +794,10 @@ static void print_codec_info(struct snd_info_entry *entry,
			if (wid_type == AC_WID_PIN &&
			    codec->pin_amp_workaround)
				print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
					       wid_caps & AC_WCAP_STEREO,
					       conn_len);
					       wid_caps, conn_len);
			else
				print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
					       wid_caps & AC_WCAP_STEREO, 1);
					       wid_caps, 1);
		}

		switch (wid_type) {