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;
+}
+
+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);
+}
+
+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));
+
+	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);
+			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,
+			&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"},
