[PATCH] ALSA: hda/realtek - Add control fixup for Lenovo Thinkpad X1 Carbon 7th

Jaroslav Kysela perex at perex.cz
Tue Sep 1 15:52:09 CEST 2020


Dne 29. 08. 20 v 13:27 Benjamin Poirier napsal(a):
> As a result of commit d2cd795c4ece ("ALSA: hda - fixup for the bass speaker
> on Lenovo Carbon X1 7th gen"), the sound output level on my machine, an X1
> Carbon 7th gen, was reduced to ~65% of its previous level when playing
> certain sounds. [1]
> 
> Internally, this laptop model has three outputs (PCM-OUT1, connection 0x02;
> PCM-OUT2, connection 0x03; SP-OUT PCM, connection 0x06) which can be routed
> to two sets of stereo speakers: Front (tweeters, node 0x14) and Bass
> (woofers, node 0x17, aka Rear in some contexts) and one headphone output
> (node 0x21). The tweeters are noticeably less powerful than the woofers.
> [2]
> 
> Before commit d2cd795c4ece, the bass speakers were connected to SP-OUT PCM.
> SP-OUT PCM is meant for s/pdif output and does not have volume control.
> This connection made volume control commonly ineffective (using the Master
> slider in alsa or pulseaudio apparently had little effect or alternated
> between mute or max with nothing in between).
> 
> commit d2cd795c4ece added quirk ALC285_FIXUP_SPEAKER2_TO_DAC1 which
> resulted in assigning both sets of speakers to PCM-OUT1, bringing
> the two sets of speakers under one effective volume control but also
> lowering the output volume noticeably.
> 
> Fix this by connecting PCM-OUT1 to Front speakers and PCM-OUT2 to Rear
> speakers. The max output volume is restored to what it was before commit
> d2cd795c4ece. This is done by setting the connection of node 0x17 to 0x03.
> 
> However, when we do this, the HDA auto config automatically changes the
> connection of node 0x21 to 0x02. This output, meant for the front speakers,
> has some "secret" equalizer which changes the output volume according to
> the level of what's being played, after some delay[3]. This is undesirable
> with headphones. Therefore, this patch manually limits the connection of
> node 0x21 to 0x03.
> 
> Even though there are three speaker/line outputs (nodes 0x14, 0x17, 0x21),
> there are only two amps for volume control (nodes 0x02, 0x03). When setting
> node 0x17 to connection 0x03 and node 0x21 to connection 0x03, amp 0x03 is
> shared by the Woofer and Headphone outputs. Therefore, they cannot have
> independent volume control. This patch uses the Speaker control element to
> regulate nodes 0x02 (acts on tweeters) and 0x03 (acts on woofers and
> headphones). A virtual control element is created for the Headphone mixer
> which mirrors the Speaker mixer. These changes are only for volume
> controls; there are 3 hardware mute controls.
> 
> The current fixup chain applied to this machine is
> ALC285_FIXUP_THINKPAD_HEADSET_JACK
> ALC285_FIXUP_SPEAKER2_TO_DAC1
> ALC269_FIXUP_THINKPAD_ACPI
> ALC269_FIXUP_SKU_IGNORE
> 
> The fixup added by this patch is an alternative to
> ALC285_FIXUP_SPEAKER2_TO_DAC1 which must be selected explicitly using the
> module parameter
> 	snd_sof_intel_hda_common.hda_model=alc285-tpx1-dual-speakers
> when using the new SOF driver architecture, or
> 	snd_hda_intel.model=alc285-tpx1-dual-speakers
> when using the snd_hda_intel driver in legacy mode (see the
> snd_intel_dspcfg.dsp_driver module parameter).
> 
> In order to be able to apply ALC285_FIXUP_THINKPAD_HEADSET_JACK without
> changing current behavior, a new variant of that fixup is introduced. When
> using the model added by this patch, the fixup chain is:
> ALC285_FIXUP_TPX1_DUAL_SPEAKERS
> ALC285_FIXUP_THINKPAD_HEADSET_JACK_ACPI
> ALC269_FIXUP_THINKPAD_ACPI
> ALC269_FIXUP_SKU_IGNORE
> 
> [1] https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3214171
> [2] https://bugzilla.kernel.org/show_bug.cgi?id=207407#c10
> [3] https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3276276
> 
> Fixes: d2cd795c4ece ("ALSA: hda - fixup for the bass speaker on Lenovo Carbon X1 7th gen")
> Link: https://lore.kernel.org/alsa-devel/20200210025249.GA2700@f3/
> Cc: Jaroslav Kysela <perex at perex.cz>
> Cc: Kailang Yang <kailang at realtek.com>
> Tested-by: Even Brenden <evenbrenden at gmail.com>
> Tested-by: Vincent Bernat <vincent at bernat.ch>
> Signed-off-by: Benjamin Poirier <benjamin.poirier at gmail.com>
> ---
> 
> This is an updated version of the patchset (now a single patch) to fix the
> low output volume on the Lenovo ThinkPad X1 Carbon 7th gen. With this
> patch, the volume output with the Linux driver matches the one with the
> Windows driver. Volume control is effective and headphone output is
> unaffected.
> 
> Multiple users of this machine have reported a low output volume [1] when
> using a kernel with commit d2cd795c4ece ("ALSA: hda - fixup for the bass
> speaker on Lenovo Carbon X1 7th gen"). Many have tried these patches or a
> similar workaround using `hda-verb` and prefer this configuration[2].
> 
> [1]
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3227516
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3240090
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3296637
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3304699
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3343871
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3349803
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3361380
> ...
> 
> [2]
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3343252
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3344118
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3345062
> https://gist.github.com/hamidzr/dd81e429dc86f4327ded7a2030e7d7d9#gistcomment-3345107
> ...
> 
> Changes:
> 
> v1
> ==
> https://lore.kernel.org/alsa-devel/20200211055651.4405-1-benjamin.poirier@gmail.com/
> 
> v2
> ==
> https://lore.kernel.org/alsa-devel/20200703080005.8942-1-benjamin.poirier@gmail.com/
> * patch 1
> Shortened log
> 
> * patch 2
> Change log to replace experimental observations by information from diagram
> posted on kernel.org bugzilla.
> Select exactly the desired connection for node 0x17 (bass speakers) instead of
> reusing alc295_fixup_disable_dac3().
> Control node 0x21 (headphones) connection to avoid the volume "wobble"
> effect described in the log.
> Include SET_CONNECT_SEL command in fixup since it is not done by the hda
> core.
> Rename one control to reflect its new function and to use as a condition in
> the ucm config.
> 
> v3
> ==
> * Remove patch 1 which was merged separately as commit 9774dc218bb6
> ("ALSA: hda/realtek - Fix Lenovo Thinkpad X1 Carbon 7th quirk
> subdevice id")
> * Make fixup activation manual using "alc285-tpx1-dual-speakers" model
> instead of automatic based on codec id. Suggested by Jaroslav.
> * Control both tweeter and woofer volume from "Speaker Playback Volume"
> control. This way a separate ucm change is not needed, unlike in v2 of
> the patchset. Suggested by Jaroslav.

Thank you for your work.

I tested this patch and it looks good on my test X1. The sound is really more
strong. I would make this model as default for X1 7th/8th.

>  sound/pci/hda/patch_realtek.c | 355 +++++++++++++++++++++++++++++++++-
>  1 file changed, 351 insertions(+), 4 deletions(-)
> 
> diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
> index a1fa983d2a94..8012c4aaf282 100644
> --- a/sound/pci/hda/patch_realtek.c
> +++ b/sound/pci/hda/patch_realtek.c
> @@ -5821,6 +5821,338 @@ static void alc285_fixup_speaker2_to_dac1(struct hda_codec *codec,
>  	}
>  }
>  
> +static struct snd_kcontrol *find_ctl_verbose(struct hda_codec *codec,
> +					     const char *name)
> +{
> +	struct snd_kcontrol *kctl;
> +
> +	kctl = snd_hda_find_mixer_ctl(codec, name);
> +	if (!kctl)
> +		codec_warn(codec, "Did not find control \"%s\"\n", name);
> +	return kctl;
> +}

Perhaps, it's too much verbose. I would use codec_dbg() instead.

> +
> +struct tpx1_dual_speaker {
> +	struct hda_codec *codec;
> +	struct snd_kcontrol underlying, hp_vol;
> +};
> +
> +static int tpx1_dual_speaker_vol_info(struct snd_kcontrol *kcontrol,
> +				      struct snd_ctl_elem_info *uinfo)
> +{
> +	struct tpx1_dual_speaker *speaker_priv = snd_kcontrol_chip(kcontrol);
> +
> +	return speaker_priv->underlying.info(&speaker_priv->underlying, uinfo);
> +}
> +
> +static int tpx1_dual_speaker_vol_get(struct snd_kcontrol *kcontrol,
> +				     struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct tpx1_dual_speaker *speaker_priv = snd_kcontrol_chip(kcontrol);
> +
> +	return speaker_priv->underlying.get(&speaker_priv->underlying,
> +					    ucontrol);

I think that the checkpatch limit is 100 characters per line now. It might
make sense to change those lines in this patch. It would be more readable.

> +}
> +
> +static int tpx1_dual_speaker_vol_put(struct snd_kcontrol *kcontrol,
> +				     struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct tpx1_dual_speaker *speaker_priv = snd_kcontrol_chip(kcontrol);
> +	int err;
> +
> +	/* Control tweeter volume */
> +	err = speaker_priv->underlying.put(&speaker_priv->underlying,
> +					   ucontrol);
> +	if (err < 0)
> +		return err;
> +
> +	/* Control woofer volume (shared with headphone) */
> +	err = speaker_priv->hp_vol.put(&speaker_priv->hp_vol, ucontrol);
> +	if (err < 0)
> +		return err;
> +
> +	snd_ctl_notify(speaker_priv->codec->card, SNDRV_CTL_EVENT_MASK_VALUE,
> +		       &speaker_priv->hp_vol.id);
> +	return err;
> +}
> +
> +static int tpx1_dual_speaker_vol_tlv(struct snd_kcontrol *kcontrol,
> +				     int op_flag, unsigned int size,
> +				     unsigned int __user *tlv)
> +{
> +	struct tpx1_dual_speaker *speaker_priv = snd_kcontrol_chip(kcontrol);
> +
> +	return speaker_priv->underlying.tlv.c(&speaker_priv->underlying,
> +					      op_flag, size, tlv);
> +}
> +
> +static void tpx1_dual_speaker_vol_free(struct snd_kcontrol *kcontrol)
> +{
> +	struct tpx1_dual_speaker *speaker_priv = snd_kcontrol_chip(kcontrol);
> +
> +	if (speaker_priv->underlying.private_free)
> +		speaker_priv->underlying.private_free(
> +			&speaker_priv->underlying);
> +	kfree(speaker_priv);
> +}
> +
> +static int tpx1_dual_override_speaker_vol(struct hda_codec *codec,
> +					  struct snd_kcontrol *speaker_vol,
> +					  struct snd_kcontrol *hp_vol)
> +{
> +	struct tpx1_dual_speaker *speaker_priv;
> +
> +	speaker_priv = kmalloc(sizeof(struct tpx1_dual_speaker), GFP_KERNEL);
> +	if (!speaker_priv)
> +		return -ENOMEM;
> +	speaker_priv->codec = codec;
> +	memcpy(&speaker_priv->underlying, speaker_vol,
> +	       sizeof(struct snd_kcontrol));
> +	memcpy(&speaker_priv->hp_vol, hp_vol, sizeof(struct snd_kcontrol));

This is a bit clumsy part. It would be probably nice to have a helper in the
upper control code to clone the original control safely. Takashi?

> +
> +	speaker_vol->info = &tpx1_dual_speaker_vol_info;
> +	speaker_vol->get = &tpx1_dual_speaker_vol_get;
> +	speaker_vol->put = &tpx1_dual_speaker_vol_put;
> +	speaker_vol->tlv.c = &tpx1_dual_speaker_vol_tlv;
> +	speaker_vol->private_data = speaker_priv;
> +	speaker_vol->private_free = &tpx1_dual_speaker_vol_free;
> +
> +	return 0;
> +}
> +
> +struct tpx1_dual_hp {
> +	struct hda_codec *codec;
> +	struct snd_kcontrol underlying, *speaker_vol;
> +};
> +
> +static int tpx1_dual_hp_vol_info(struct snd_kcontrol *kcontrol,
> +				 struct snd_ctl_elem_info *uinfo)
> +{
> +	struct tpx1_dual_hp *hp_priv = snd_kcontrol_chip(kcontrol);
> +
> +	return hp_priv->underlying.info(&hp_priv->underlying, uinfo);
> +}
> +
> +static int tpx1_dual_hp_vol_get(struct snd_kcontrol *kcontrol,
> +				struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct tpx1_dual_hp *hp_priv = snd_kcontrol_chip(kcontrol);
> +
> +	return hp_priv->speaker_vol->get(hp_priv->speaker_vol, ucontrol);
> +}
> +
> +static int tpx1_dual_hp_vol_put(struct snd_kcontrol *kcontrol,
> +				struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct tpx1_dual_hp *hp_priv = snd_kcontrol_chip(kcontrol);
> +	int err;
> +
> +	err = hp_priv->speaker_vol->put(hp_priv->speaker_vol, ucontrol);
> +	if (err < 0)
> +		return err;
> +	snd_ctl_notify(hp_priv->codec->card, SNDRV_CTL_EVENT_MASK_VALUE,
> +		       &hp_priv->speaker_vol->id);
> +
> +	return 0;
> +}
> +
> +static int tpx1_dual_hp_vol_tlv(struct snd_kcontrol *kcontrol,
> +				int op_flag, unsigned int size,
> +				unsigned int __user *tlv)
> +{
> +	struct tpx1_dual_hp *hp_priv = snd_kcontrol_chip(kcontrol);
> +
> +	return hp_priv->underlying.tlv.c(&hp_priv->underlying, op_flag, size,
> +					 tlv);
> +}
> +
> +static void tpx1_dual_hp_vol_free(struct snd_kcontrol *kcontrol)
> +{
> +	struct tpx1_dual_hp *hp_priv = snd_kcontrol_chip(kcontrol);
> +
> +	if (hp_priv->underlying.private_free)
> +		hp_priv->underlying.private_free(&hp_priv->underlying);
> +	kfree(hp_priv);
> +}
> +
> +static int tpx1_dual_override_hp_vol(struct hda_codec *codec,
> +				     struct snd_kcontrol *speaker_vol,
> +				     struct snd_kcontrol *hp_vol)
> +{
> +	struct tpx1_dual_hp *hp_priv;
> +
> +	hp_priv = kmalloc(sizeof(struct tpx1_dual_hp), GFP_KERNEL);
> +	if (!hp_priv)
> +		return -ENOMEM;
> +	hp_priv->codec = codec;
> +	memcpy(&hp_priv->underlying, hp_vol, sizeof(struct snd_kcontrol));
> +	hp_priv->speaker_vol = speaker_vol;
> +
> +	hp_vol->info = &tpx1_dual_hp_vol_info;
> +	hp_vol->get = &tpx1_dual_hp_vol_get;
> +	hp_vol->put = &tpx1_dual_hp_vol_put;
> +	hp_vol->tlv.c = &tpx1_dual_hp_vol_tlv;
> +	hp_vol->private_data = hp_priv;
> +	hp_vol->private_free = &tpx1_dual_hp_vol_free;
> +
> +	return 0;
> +}
> +
> +/* We cannot use snd_hda_add_vmaster() because it expects the controls to be
> + * HDA (see init_follower_0dB()). This is not the case here.
> + */
> +static int tpx1_dual_create_main_volume_ctl(struct hda_codec *codec)
> +{
> +	struct snd_kcontrol *main_vol, *speaker_vol;
> +	struct snd_ctl_elem_value *ucontrol = NULL;
> +	struct hda_gen_spec *spec = codec->spec;
> +	struct snd_ctl_elem_info *uinfo;
> +	int ch, err;
> +
> +	speaker_vol = find_ctl_verbose(codec, "Speaker Playback Volume");
> +	if (!speaker_vol)
> +		return -ENOENT;
> +
> +	main_vol = snd_ctl_make_virtual_master("Master Playback Volume",
> +					       spec->vmaster_tlv);
> +	if (!main_vol)
> +		return -ENOMEM;
> +	err = snd_hda_ctl_add(codec, 0, main_vol);
> +	if (err < 0)
> +		return err;
> +	err = snd_ctl_add_follower(main_vol, speaker_vol);
> +	if (err < 0)
> +		return err;
> +
> +	/* Initialize values */
> +	uinfo = kmalloc(sizeof(struct snd_ctl_elem_info), GFP_KERNEL);
> +	if (!uinfo)
> +		return -ENOMEM;
> +
> +	uinfo->id = speaker_vol->id;
> +	err = speaker_vol->info(speaker_vol, uinfo);
> +	if (err)
> +		goto out;
> +
> +	ucontrol = kmalloc(sizeof(struct snd_ctl_elem_value), GFP_KERNEL);
> +	if (!ucontrol) {
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	ucontrol->id = speaker_vol->id;
> +	for (ch = 0; ch < uinfo->count; ch++) {
> +		ucontrol->value.integer.value[ch] =
> +			uinfo->value.integer.max;
> +	}
> +	err = speaker_vol->put(speaker_vol, ucontrol);
> +	if (err < 0)
> +		goto out;
> +	else
> +		err = 0;
> +
> +out:
> +	kfree(ucontrol);
> +	kfree(uinfo);
> +	return err;
> +}
> +
> +static void alc285_fixup_tpx1_dual_speakers(struct hda_codec *codec,
> +					    const struct hda_fixup *fix,
> +					    int action)
> +{
> +	struct alc_spec *spec = codec->spec;
> +
> +	if (action == HDA_FIXUP_ACT_PRE_PROBE) {
> +		static const hda_nid_t conn[] = { 0x03 };
> +
> +		/* For NID 0x17 (bass speakers), the connection list is {0x02,
> +		 * 0x03, 0x06}. Disable SP-OUT PCM (0x06) selection since it
> +		 * has no volume control, disable PCM1 (0x02) selection since
> +		 * it is for front speakers. This leaves PCM2 (0x03).
> +		 */
> +		snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn),
> +					   conn);
> +		/* For NID 0x21 (headphone out), the connection list is {0x02,
> +		 * 0x03}. Disable LOUT1 (0x02) selection since its volume
> +		 * fluctuates according to input level. This leaves LOUT2
> +		 * (0x03).
> +		 */
> +		snd_hda_override_conn_list(codec, 0x21, ARRAY_SIZE(conn),
> +					   conn);
> +
> +		/* "Master" controls will be created manually after
> +		 * subordinate controls have been changed in the
> +		 * HDA_FIXUP_ACT_BUILD phase.
> +		 */
> +		spec->gen.suppress_vmaster = 1;
> +	} else if (action == HDA_FIXUP_ACT_INIT) {
> +		/* Because the overridden connection lists contain only a
> +		 * single node, __parse_nid_path() does not label the output
> +		 * as "multi". This leads snd_hda_activate_path() to skip the
> +		 * AC_VERB_SET_CONNECT_SEL even though it might be needed. Do
> +		 * it here instead.
> +		 * Note that when doing AC_VERB_SET_CONNECT_SEL, the
> +		 * connection is specified by index instead of nid.
> +		 */
> +		snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL,
> +				    0x1);
> +		snd_hda_codec_write(codec, 0x21, 0, AC_VERB_SET_CONNECT_SEL,
> +				    0x1);
> +	} else if (action == HDA_FIXUP_ACT_BUILD) {
> +		struct snd_kcontrol *speaker_vol, *hp_vol;
> +		int err;
> +
> +		speaker_vol = find_ctl_verbose(codec, "Speaker Playback Volume");
> +		if (!speaker_vol)
> +			return;
> +
> +		hp_vol = find_ctl_verbose(codec, "Headphone Playback Volume");
> +		if (!hp_vol)
> +			return;
> +
> +		err = tpx1_dual_override_speaker_vol(codec, speaker_vol, hp_vol);
> +		if (err) {
> +			codec_warn(codec, "Failed to override speaker volume control, err %d\n",
> +				   err);

Also codec_dbg()...

> +			return;
> +		}
> +
> +		err = tpx1_dual_override_hp_vol(codec, speaker_vol, hp_vol);
> +		if (err) {
> +			codec_warn(codec, "Failed to override headphone volume control, err %d\n",
> +				   err);
> +			return;
> +		}
> +
> +		err = tpx1_dual_create_main_volume_ctl(codec);
> +		if (err) {
> +			codec_warn(codec, "Failed to create main volume control, err %d\n",
> +				   err);
> +			return;
> +		}
> +
> +		err = __snd_hda_add_vmaster(
> +			codec, "Master Playback Switch", NULL,
> +			((const char * const[]){
> +				"Speaker", "Bass Speaker", "Headphone", NULL
> +			}), "Playback Switch", true,

Declare array separately to make code more readable.

> +			&spec->gen.vmaster_mute.sw_kctl);
> +		if (err) {
> +			codec_warn(codec, "Failed to create main mute control, err %d\n",
> +				   err);
> +			return;
> +		}
> +
> +		if (spec->gen.vmaster_mute.hook) {
> +			snd_hda_add_vmaster_hook(codec,
> +						 &spec->gen.vmaster_mute,
> +						 spec->gen.vmaster_mute_enum);
> +			snd_hda_sync_vmaster_hook(&spec->gen.vmaster_mute);
> +		}
> +	}
> +}
> +
>  /* Hook to update amp GPIO4 for automute */
>  static void alc280_hp_gpio4_automute_hook(struct hda_codec *codec,
>  					  struct hda_jack_callback *jack)
> @@ -6083,6 +6415,7 @@ enum {
>  	ALC225_FIXUP_DISABLE_MIC_VREF,
>  	ALC225_FIXUP_DELL1_MIC_NO_PRESENCE,
>  	ALC295_FIXUP_DISABLE_DAC3,
> +	ALC285_FIXUP_TPX1_DUAL_SPEAKERS,
>  	ALC285_FIXUP_SPEAKER2_TO_DAC1,
>  	ALC280_FIXUP_HP_HEADSET_MIC,
>  	ALC221_FIXUP_HP_FRONT_MIC,
> @@ -6135,7 +6468,8 @@ enum {
>  	ALC289_FIXUP_DUAL_SPK,
>  	ALC294_FIXUP_SPK2_TO_DAC1,
>  	ALC294_FIXUP_ASUS_DUAL_SPK,
> -	ALC285_FIXUP_THINKPAD_HEADSET_JACK,
> +	ALC285_FIXUP_THINKPAD_HEADSET_JACK_SPK,
> +	ALC285_FIXUP_THINKPAD_HEADSET_JACK_ACPI,
>  	ALC294_FIXUP_ASUS_HPE,
>  	ALC294_FIXUP_ASUS_COEF_1B,
>  	ALC285_FIXUP_HP_GPIO_LED,
> @@ -6905,6 +7239,12 @@ static const struct hda_fixup alc269_fixups[] = {
>  		.type = HDA_FIXUP_FUNC,
>  		.v.func = alc295_fixup_disable_dac3,
>  	},
> +	[ALC285_FIXUP_TPX1_DUAL_SPEAKERS] = {
> +		.type = HDA_FIXUP_FUNC,
> +		.v.func = alc285_fixup_tpx1_dual_speakers,
> +		.chained = true,
> +		.chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK_ACPI
> +	},
>  	[ALC285_FIXUP_SPEAKER2_TO_DAC1] = {
>  		.type = HDA_FIXUP_FUNC,
>  		.v.func = alc285_fixup_speaker2_to_dac1,
> @@ -7280,12 +7620,18 @@ static const struct hda_fixup alc269_fixups[] = {
>  		.chained = true,
>  		.chain_id = ALC294_FIXUP_SPK2_TO_DAC1
>  	},
> -	[ALC285_FIXUP_THINKPAD_HEADSET_JACK] = {
> +	[ALC285_FIXUP_THINKPAD_HEADSET_JACK_SPK] = {
>  		.type = HDA_FIXUP_FUNC,
>  		.v.func = alc_fixup_headset_jack,
>  		.chained = true,
>  		.chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1
>  	},
> +	[ALC285_FIXUP_THINKPAD_HEADSET_JACK_ACPI] = {
> +		.type = HDA_FIXUP_FUNC,
> +		.v.func = alc_fixup_headset_jack,
> +		.chained = true,
> +		.chain_id = ALC269_FIXUP_THINKPAD_ACPI
> +	},
>  	[ALC294_FIXUP_ASUS_HPE] = {
>  		.type = HDA_FIXUP_VERBS,
>  		.v.verbs = (const struct hda_verb[]) {
> @@ -7741,8 +8087,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
>  	SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
>  	SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
>  	SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
> -	SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK),
> -	SND_PCI_QUIRK(0x17aa, 0x22be, "Thinkpad X1 Carbon 8th", ALC285_FIXUP_THINKPAD_HEADSET_JACK),
> +	SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK_SPK),
> +	SND_PCI_QUIRK(0x17aa, 0x22be, "Thinkpad X1 Carbon 8th", ALC285_FIXUP_THINKPAD_HEADSET_JACK_SPK),
>  	SND_PCI_QUIRK(0x17aa, 0x30bb, "ThinkCentre AIO", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY),
>  	SND_PCI_QUIRK(0x17aa, 0x30e2, "ThinkCentre AIO", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY),
>  	SND_PCI_QUIRK(0x17aa, 0x310c, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION),
> @@ -7934,6 +8280,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = {
>  	{.id = ALC255_FIXUP_DELL_SPK_NOISE, .name = "dell-spk-noise"},
>  	{.id = ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc225-dell1"},
>  	{.id = ALC295_FIXUP_DISABLE_DAC3, .name = "alc295-disable-dac3"},
> +	{.id = ALC285_FIXUP_TPX1_DUAL_SPEAKERS, .name = "alc285-tpx1-dual-speakers"},
>  	{.id = ALC285_FIXUP_SPEAKER2_TO_DAC1, .name = "alc285-speaker2-to-dac1"},
>  	{.id = ALC280_FIXUP_HP_HEADSET_MIC, .name = "alc280-hp-headset"},
>  	{.id = ALC221_FIXUP_HP_FRONT_MIC, .name = "alc221-hp-mic"},
> 


-- 
Jaroslav Kysela <perex at perex.cz>
Linux Sound Maintainer; ALSA Project; Red Hat, Inc.


More information about the Alsa-devel mailing list