[PATCH 8/8] ALSA: hda/realtek: enable AW88399 on Lenovo Legion Pro

From: Marco Giunta

Date: Fri Jul 17 2026 - 09:29:28 EST


Enable audio output through the AW88399 woofer amplifiers on Lenovo
Legion laptops by adding the necessary Realtek ALC287 fixups and
AW88399 per-model quirks.

Realtek fixups (alc269.c):

* ALC287_FIXUP_AW88399_I2C_2: registers the AW88399 as a two-instance
I2C companion codec using comp_generic_fixup, matching ACPI HID
"AWDZ8399".

* ALC287_FIXUP_LENOVO_LEGION_AW88399: forces DAC 0x02 for the bass
speaker pin 0x17, as the default DAC 0x06 lacks volume controls.
Also applies internal microphone boost calibration via
alc269_fixup_limit_int_mic_boost and disables unused pin 0x1d
to match the Windows driver's pin configuration.
Chained to ALC287_FIXUP_AW88399_I2C_2.

Per-model quirks (aw88399_hda.c):

* Channel swap: the I2C wiring on these Legion models is reversed
(0x34 is physically the right speaker, 0x35 is the left). The
quirk swaps the channel assignment to correct L/R audio.

* BSTS status bypass: the AW88399's boost-finished status bit (BSTS,
SYSST register bit 9) does not reliably assert on this hardware.
Register dumps during normal playback show both amplifiers
reporting BSTS=0 on both channels despite clean audio output.
The quirk sets the bsts_unreliable flag (introduced in patch 3)
so the startup status check skips the BSTS requirement
on these devices.

The R9000P ADR10 entries use HDA_CODEC_QUIRK and are placed before
the existing SND_PCI_QUIRK for 17aa:38bb (Yoga S780-14.5 Air) to
ensure the codec SSID match takes priority over the shared PCI SSID,
following the pattern established by e.g.
commit 0f3a822ae225 ("ALSA: hda/realtek: Fix quirk matching for Legion Pro 7"),
commit dd074f04e046 ("ALSA: hda/realtek: Fix Legion 7 16ITHG6 speaker amp binding").
All other entries also use HDA_CODEC_QUIRK for consistency.

Supported models (Lenovo vendor ID 0x17aa):

* 0x3906: Legion Pro 7i 16IAX10H / Y9000P IAX10 (Intel)
* 0x3907: Legion Pro 7i 16IAX10H / Y9000P IAX10 (Intel)
* 0x3927: Legion R9000P ADR10 (AMD)
* 0x3928: Legion R9000P ADR10 (AMD)
* 0x3938: Legion Pro 7 16AFR10H (AMD)
* 0x3939: Legion Pro 7 16AFR10H (AMD)

Tested-by: Nadim Kobeissi <nadim@symbolic.software>
Tested-by: Xia Yun'an <imitoy@xxxxxxxxxx>
Tested-by: Munzir Taha <munzirtaha@xxxxxxxxx>
Co-developed-by: Yakov Till <yakov.till@xxxxxxxxx>
Signed-off-by: Yakov Till <yakov.till@xxxxxxxxx>
Signed-off-by: Marco Giunta <marco_giunta@xxxxxxxxxx>
---
sound/hda/codecs/realtek/alc269.c | 50 ++++++++++++++++++++++
sound/hda/codecs/side-codecs/aw88399_hda.c | 42 ++++++++++++++++++
2 files changed, 92 insertions(+)

diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c
index b3562c821954..5aa30569595d 100644
--- a/sound/hda/codecs/realtek/alc269.c
+++ b/sound/hda/codecs/realtek/alc269.c
@@ -3262,6 +3262,34 @@ static void find_cirrus_companion_amps(struct hda_codec *cdc)
comp_generic_fixup(cdc, HDA_FIXUP_ACT_PRE_PROBE, bus, acpi_ids[i].hid, match, count);
}

+static void aw88399_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action)
+{
+ comp_generic_fixup(cdc, action, "i2c", "AWDZ8399", "-%s:00-aw88399-hda.%d", 2);
+}
+
+static void alc287_fixup_legion_16iax10h_aw88399(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x1d, 0x411111f0 }, /* unused bogus pin */
+ { }
+ };
+
+ /*
+ * Force DAC 0x02 for the bass speaker 0x17, as the default 0x06 lacks volume controls.
+ */
+ static const hda_nid_t conn[] = { 0x02 };
+
+ alc269_fixup_limit_int_mic_boost(codec, fix, action);
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ snd_hda_apply_pincfgs(codec, pincfgs);
+ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn);
+ break;
+ }
+}
+
static void cs35l41_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action)
{
comp_generic_fixup(cdc, action, "i2c", "CSC3551", "-%s:00-cs35l41-hda.%d", 2);
@@ -4216,6 +4244,8 @@ enum {
ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX,
ALC287_FIXUP_ACER_MICMUTE_LED,
ALC236_FIXUP_DELL_HP_POP_NOISE,
+ ALC287_FIXUP_AW88399_I2C_2,
+ ALC287_FIXUP_LENOVO_LEGION_AW88399,
};

/* A special fixup for Lenovo C940 and Yoga Duet 7;
@@ -6852,6 +6882,16 @@ static const struct hda_fixup alc269_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc285_fixup_invalidate_dacs,
},
+ [ALC287_FIXUP_AW88399_I2C_2] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = aw88399_fixup_i2c_two,
+ },
+ [ALC287_FIXUP_LENOVO_LEGION_AW88399] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc287_fixup_legion_16iax10h_aw88399,
+ .chained = true,
+ .chain_id = ALC287_FIXUP_AW88399_I2C_2,
+ },
};

static const struct hda_quirk alc269_fixup_tbl[] = {
@@ -7900,6 +7940,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x38b8, "Yoga S780-14.5 proX AMD YC Dual", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x38b9, "Yoga S780-14.5 proX AMD LX Dual", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x38ba, "Yoga S780-14.5 Air AMD quad YC", ALC287_FIXUP_TAS2781_I2C),
+ /* Legion R9000P ADR10 shares PCI SSID 17aa:38bb with Yoga S780-14.5 Air AMD quad AAC;
+ * use codec SSID to distinguish them
+ */
+ HDA_CODEC_QUIRK(0x17aa, 0x3927, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399),
+ HDA_CODEC_QUIRK(0x17aa, 0x3928, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399),
SND_PCI_QUIRK(0x17aa, 0x38bb, "Yoga S780-14.5 Air AMD quad AAC", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x38be, "Yoga S980-14.5 proX YC Dual", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x38bf, "Yoga S980-14.5 proX LX Dual", ALC287_FIXUP_TAS2781_I2C),
@@ -7923,6 +7968,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x38fc, "Lenovo Yoga Pro 7 15ASH11", ALC287_FIXUP_LENOVO_YOGA_PRO7),
SND_PCI_QUIRK(0x17aa, 0x38fd, "ThinkBook plus Gen5 Hybrid", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI),
+ HDA_CODEC_QUIRK(0x17aa, 0x3906, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399),
+ HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399),
SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN),
SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN),
SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN),
@@ -7932,6 +7979,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD),
SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD),
+ HDA_CODEC_QUIRK(0x17aa, 0x3938, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399),
+ HDA_CODEC_QUIRK(0x17aa, 0x3939, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399),
HDA_CODEC_QUIRK(0x17aa, 0x394c, "Lenovo Yoga Slim 7 14AGP11", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN),
SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC),
SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI),
@@ -8230,6 +8279,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = {
{.id = ALC2XX_FIXUP_HEADSET_MIC, .name = "alc2xx-fixup-headset-mic"},
{.id = ALC245_FIXUP_BASS_HP_DAC, .name = "alc245-fixup-bass-hp-dac"},
{.id = ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, .name = "alc256-honor-mrb-xxx-m1020-audio"},
+ {.id = ALC287_FIXUP_LENOVO_LEGION_AW88399, .name = "alc287-lenovo-legion-aw88399"},
{}
};
#define ALC225_STANDARD_PINS \
diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c
index 9f6f4cbcf641..934fe68129a0 100644
--- a/sound/hda/codecs/side-codecs/aw88399_hda.c
+++ b/sound/hda/codecs/side-codecs/aw88399_hda.c
@@ -140,12 +140,54 @@ static int aw88399_hda_init(struct aw88399_hda *aw88399)
return 0;
}

+static int aw88399_swap_channels(struct aw88399_hda *aw88399)
+{
+ /*
+ * Certain Lenovo Legion laptops have their
+ * I2C wiring reversed: 0x34 is physically the right speaker,
+ * 0x35 is the left. Swap channels to correct L/R assignment.
+ * This is a model-specific hardware wiring issue, not a driver bug.
+ */
+ aw88399->channel = 1 - aw88399->channel;
+ dev_dbg(aw88399->dev,
+ "Channel swap applied: index %d -> channel %d\n",
+ aw88399->index, aw88399->channel);
+ return 0;
+}
+
+static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399)
+{
+ /*
+ * BSTS (boost-finished) status bit does not reliably report on
+ * some hardware. On certain Lenovo Legion laptops, both amps
+ * report BSTS=0 (boost not finished) during normal playback
+ * despite clean audio output. Skip BSTS in the startup status
+ * check to avoid false init failures.
+ */
+ aw88399->bsts_unreliable = true;
+ dev_dbg(aw88399->dev, "BSTS status check disabled\n");
+ return 0;
+}
+
+static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399)
+{
+ aw88399_swap_channels(aw88399);
+ aw88399_skip_bsts_check(aw88399);
+ return 0;
+}
+
struct aw88399_prop_model {
const char *ssid;
int (*apply_prop)(struct aw88399_hda *aw88399);
};

static const struct aw88399_prop_model aw88399_prop_model_table[] = {
+ { "17AA3906", aw88399_apply_legion_quirks },
+ { "17AA3907", aw88399_apply_legion_quirks },
+ { "17AA3927", aw88399_apply_legion_quirks },
+ { "17AA3928", aw88399_apply_legion_quirks },
+ { "17AA3938", aw88399_apply_legion_quirks },
+ { "17AA3939", aw88399_apply_legion_quirks },
{ }
};

--
2.55.0