@@ -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"},