public inbox for linux-sound@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] ALSA: hda/realtek: Fix speaker pop on Star Labs StarFighter
@ 2026-02-17 22:14 Sean Rhodes
  2026-02-18  7:28 ` Takashi Iwai
  0 siblings, 1 reply; 2+ messages in thread
From: Sean Rhodes @ 2026-02-17 22:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: tiwai, chris.chiu, edip, kailang, linux-sound, perex,
	david.henningsson, sbinding

From 278f2646b0942aa2c57f2da1839b6e18adb38ed9 Mon Sep 17 00:00:00 2001
From: Sean Rhodes <sean@starlabs.systems>
Date: Tue, 17 Feb 2026 11:53:41 +0000
Subject: [PATCH] ALSA: hda/realtek: Fix speaker pop on Star Labs StarFighter
To: tiwai@suse.com
Cc: perex@perex.cz,
    sbinding@opensource.cirrus.com,
    kailang@realtek.com,
    chris.chiu@canonical.com,
    edip@medip.dev,
    david.henningsson@canonical.com,
    linux-sound@vger.kernel.org,
    linux-kernel@vger.kernel.org

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

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] ALSA: hda/realtek: Fix speaker pop on Star Labs StarFighter
  2026-02-17 22:14 [PATCH] ALSA: hda/realtek: Fix speaker pop on Star Labs StarFighter Sean Rhodes
@ 2026-02-18  7:28 ` Takashi Iwai
  0 siblings, 0 replies; 2+ messages in thread
From: Takashi Iwai @ 2026-02-18  7:28 UTC (permalink / raw)
  To: Sean Rhodes
  Cc: linux-kernel, tiwai, chris.chiu, edip, kailang, linux-sound,
	perex, david.henningsson, sbinding

On Tue, 17 Feb 2026 23:14:24 +0100,
Sean Rhodes wrote:
> 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.

Doesn't just turn off shutup (e.g. applying ALC269_FIXUP_NO_SHUTUP
quirk) work enough in your case?  Or just muting without touching
EAPD?

If only mute is needed, you can apply a simpler fix like below.


thanks,

Takashi

-- 8< --

--- a/sound/hda/codecs/realtek/alc269.c
+++ b/sound/hda/codecs/realtek/alc269.c
@@ -3609,6 +3609,22 @@ static void alc245_fixup_hp_zbook_firefly_g12a(struct hda_codec *codec,
 	alc285_fixup_hp_coef_micmute_led(codec, fix, action);
 }
 
+static void starlabs_starfighter_shutup(struct hda_codec *codec)
+{
+	if (snd_hda_gen_shutup_speakers(codec))
+		msleep(30);
+}
+
+static void alc233_fixup_starlabs_starfighter(struct hda_codec *codec,
+					      const struct hda_fixup *fix,
+					      int action)
+{
+	struct alc_spec *spec = codec->spec;
+
+	if (action == HDA_FIXUP_ACT_PRE_PROBE)
+		spec->shutup = starlabs_starfighter_shutup;
+}
+
 /*
  * ALC287 PCM hooks
  */
@@ -4056,6 +4072,7 @@ enum {
 	ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO,
 	ALC233_FIXUP_LENOVO_GPIO2_MIC_HOTKEY,
 	ALC245_FIXUP_BASS_HP_DAC,
+	ALC233_FIXUP_STARLABS_STARFIGHTER,
 };
 
 /* A special fixup for Lenovo C940 and Yoga Duet 7;
@@ -6576,6 +6593,10 @@ static const struct hda_fixup alc269_fixups[] = {
 		/* Borrow the DAC routing selected for those Thinkpads */
 		.v.func = alc285_fixup_thinkpad_x1_gen7,
 	},
+	[ALC233_FIXUP_STARLABS_STARFIGHTER] = {
+		.type = HDA_FIXUP_FUNC,
+		.v.func = alc233_fixup_starlabs_starfighter,
+	},
 };
 
 static const struct hda_quirk alc269_fixup_tbl[] = {
@@ -7651,6 +7672,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_STARFIGHTER),
 	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),

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-02-18  7:28 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-17 22:14 [PATCH] ALSA: hda/realtek: Fix speaker pop on Star Labs StarFighter Sean Rhodes
2026-02-18  7:28 ` Takashi Iwai

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox