[PATCH] ALSA: hda/realtek: Fix speaker pop on Star Labs StarFighter

From: Sean Rhodes

Date: Tue Feb 17 2026 - 06:53:41 EST


On Star Labs StarFighter (Realtek ALC233/235), the internal speakers can
emit an audible pop when resuming from runtime suspend.

The speaker amplifier is controlled via EAPD. The generic Realtek shutup
path can toggle EAPD/pin widget control while the amp is still unmuted,
causing the pop.

Mute the speaker output before toggling EAPD on suspend/resume and restore
the previous mute state after resume. Also toggle EAPD for the auxiliary
pin (0x14) so runtime PM can fully power down the codec instead of being
held in D0.

On this machine the internal speaker pin is exposed as a line-out pin with
type speaker, so use the line-out pin as the target for mute/EAPD when
no dedicated speaker pin is present.

Test results: fixes the runtime PM suspend/resume speaker pop. Pops that can
occur on cold boot (G3 exit) and around display manager start/shutdown are not
addressed.

Tested-by: Sean Rhodes <sean@starlabs.systems>
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
sound/hda/codecs/realtek/alc269.c | 127 +++++++++++++++++++++++++++++
sound/hda/codecs/realtek/realtek.h | 2 +
2 files changed, 129 insertions(+)

diff --git a/sound/hda/codecs/realtek/alc269.c
b/sound/hda/codecs/realtek/alc269.c
index b66965a52107..4fec84b1bf5c 100644
--- a/sound/hda/codecs/realtek/alc269.c
+++ b/sound/hda/codecs/realtek/alc269.c
@@ -1017,6 +1017,126 @@ static int alc269_resume(struct hda_codec *codec)
return 0;
}

+#define STARLABS_STARFIGHTER_AUX_EAPD_PIN 0x14
+#define STARLABS_STARFIGHTER_EAPD_DELAY_MS 30
+#define STARLABS_STARFIGHTER_RESUME_DELAY_MS 20
+
+static hda_nid_t starlabs_starfighter_get_spk_pin(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ const struct auto_pin_cfg *cfg = &spec->gen.autocfg;
+ const hda_nid_t *pins = auto_cfg_speaker_pins(cfg);
+
+ if (auto_cfg_speaker_outs(cfg) > 0 && pins[0])
+ return pins[0];
+
+ if (cfg->line_out_type == AC_JACK_SPEAKER && cfg->line_out_pins[0])
+ return cfg->line_out_pins[0];
+
+ return 0;
+}
+
+static void starlabs_starfighter_set_mute(struct hda_codec *codec,
+ hda_nid_t nid, bool mute)
+{
+ snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+ mute ? HDA_AMP_MUTE : 0);
+ /* sync after issuing the verbs */
+ snd_hda_codec_amp_read(codec, nid, 0, HDA_OUTPUT, 0);
+ snd_hda_codec_amp_read(codec, nid, 1, HDA_OUTPUT, 0);
+}
+
+static void starlabs_starfighter_set_eapd(struct hda_codec *codec,
+ hda_nid_t nid, bool enable)
+{
+ if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD))
+ return;
+
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_EAPD_BTLENABLE,
+ enable ? 0x02 : 0x00);
+ /* sync after issuing the verb */
+ snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_EAPD_BTLENABLE, 0);
+}
+
+static void starlabs_starfighter_shutup(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ hda_nid_t spk_pin = starlabs_starfighter_get_spk_pin(codec);
+ unsigned int amp_l, amp_r;
+
+ if (!spk_pin)
+ return;
+
+ amp_l = snd_hda_codec_amp_read(codec, spk_pin, 0, HDA_OUTPUT, 0);
+ amp_r = snd_hda_codec_amp_read(codec, spk_pin, 1, HDA_OUTPUT, 0);
+ spec->speaker_pop_spk_was_muted =
+ !!((amp_l & HDA_AMP_MUTE) && (amp_r & HDA_AMP_MUTE));
+ spec->speaker_pop_resume_pending = 1;
+
+ /*
+ * Star Labs StarFighter: the internal speaker amplifier is controlled via
+ * EAPD. Mute before toggling EAPD on suspend/resume to avoid speaker pops.
+ */
+ starlabs_starfighter_set_mute(codec, spk_pin, true);
+ msleep(STARLABS_STARFIGHTER_EAPD_DELAY_MS);
+
+ starlabs_starfighter_set_eapd(codec, spk_pin, false);
+ if (spk_pin != STARLABS_STARFIGHTER_AUX_EAPD_PIN)
+ starlabs_starfighter_set_eapd(codec,
+ STARLABS_STARFIGHTER_AUX_EAPD_PIN,
+ false);
+ msleep(STARLABS_STARFIGHTER_EAPD_DELAY_MS);
+}
+
+static unsigned int starlabs_starfighter_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state)
+{
+ unsigned int state;
+
+ state = snd_hda_gen_path_power_filter(codec, nid, power_state);
+ return snd_hda_codec_eapd_power_filter(codec, nid, state);
+}
+
+static void alc233_fixup_starlabs_speaker_pop(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ codec->power_save_node = 1;
+ codec->power_filter = starlabs_starfighter_power_filter;
+ spec->shutup = starlabs_starfighter_shutup;
+ break;
+ case HDA_FIXUP_ACT_INIT: {
+ hda_nid_t spk_pin;
+
+ if (!spec->speaker_pop_resume_pending)
+ break;
+
+ spec->speaker_pop_resume_pending = 0;
+ spk_pin = starlabs_starfighter_get_spk_pin(codec);
+ if (!spk_pin)
+ break;
+
+ starlabs_starfighter_set_mute(codec, spk_pin, true);
+ starlabs_starfighter_set_eapd(codec, spk_pin, true);
+ if (spk_pin != STARLABS_STARFIGHTER_AUX_EAPD_PIN)
+ starlabs_starfighter_set_eapd(codec,
+ STARLABS_STARFIGHTER_AUX_EAPD_PIN,
+ true);
+ msleep(STARLABS_STARFIGHTER_EAPD_DELAY_MS);
+ msleep(STARLABS_STARFIGHTER_RESUME_DELAY_MS);
+
+ if (!spec->speaker_pop_spk_was_muted)
+ starlabs_starfighter_set_mute(codec, spk_pin, false);
+ break;
+ }
+ }
+}
+
static void alc269_fixup_pincfg_no_hp_to_lineout(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
@@ -3802,6 +3922,7 @@ enum {
ALC245_FIXUP_CLEVO_NOISY_MIC,
ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE,
ALC233_FIXUP_MEDION_MTL_SPK,
+ ALC233_FIXUP_STARLABS_SPEAKER_POP,
ALC294_FIXUP_BASS_SPEAKER_15,
ALC283_FIXUP_DELL_HP_RESUME,
ALC294_FIXUP_ASUS_CS35L41_SPI_2,
@@ -6238,6 +6359,10 @@ static const struct hda_fixup alc269_fixups[] = {
{ }
},
},
+ [ALC233_FIXUP_STARLABS_SPEAKER_POP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc233_fixup_starlabs_speaker_pop,
+ },
[ALC294_FIXUP_BASS_SPEAKER_15] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc294_fixup_bass_speaker_15,
@@ -7370,6 +7495,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX),
SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME),
SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK),
+ SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter",
ALC233_FIXUP_STARLABS_SPEAKER_POP),
SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC),
SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged",
ALC256_FIXUP_INTEL_NUC8_RUGGED),
SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10),
@@ -7466,6 +7592,7 @@ static const struct hda_model_fixup
alc269_fixup_models[] = {
{.id = ALC298_FIXUP_TPT470_DOCK_FIX, .name = "tpt470-dock-fix"},
{.id = ALC298_FIXUP_TPT470_DOCK, .name = "tpt470-dock"},
{.id = ALC233_FIXUP_LENOVO_MULTI_CODECS, .name = "dual-codecs"},
+ {.id = ALC233_FIXUP_STARLABS_SPEAKER_POP, .name = "starlabs-starfighter"},
{.id = ALC700_FIXUP_INTEL_REFERENCE, .name = "alc700-ref"},
{.id = ALC269_FIXUP_SONY_VAIO, .name = "vaio"},
{.id = ALC269_FIXUP_DELL_M101Z, .name = "dell-m101z"},
diff --git a/sound/hda/codecs/realtek/realtek.h
b/sound/hda/codecs/realtek/realtek.h
index b2a919904c4c..293fc0f3373f 100644
--- a/sound/hda/codecs/realtek/realtek.h
+++ b/sound/hda/codecs/realtek/realtek.h
@@ -119,6 +119,8 @@ struct alc_spec {
unsigned int has_hs_key:1;
unsigned int no_internal_mic_pin:1;
unsigned int en_3kpull_low:1;
+ unsigned int speaker_pop_resume_pending:1;
+ unsigned int speaker_pop_spk_was_muted:1;
int num_speaker_amps;

/* for PLL fix */
--
2.51.0