[PATCH v6] ASoC: wm8962: stop microphone work before component removal

From: Myeonghun Pak

Date: Tue Nov 10 2026 - 00:45:10 EST


The I2C-owned IRQ outlives the codec component. Cancelling mic_work during
component removal leaves its producer running, so another microphone event
can queue work for the old component and jack.

Mask microphone sources, synchronize the IRQ and drain mic_work before
changing the jack pointer, regardless of whether a jack was registered.
Disable microphone detection during cleanup only while holding an active
runtime-PM reference, because its control register is volatile. Keep
unrelated IRQ events available.

Run this cleanup unconditionally from codec removal and drop Tegra's late
card remove callback, which runs after component and PCM runtime teardown.

Fixes: 0512615db6db ("ASoC: wm8962: Convert interrupt handler to direct regmap usage")
Co-developed-by: Ijae Kim <ae878000@xxxxxxxxx>
Signed-off-by: Ijae Kim <ae878000@xxxxxxxxx>
Signed-off-by: Myeonghun Pak <mhun512@xxxxxxxxx>
---
No hardware testing.

Changes in v6:
- Mask microphone IRQs and drain the IRQ/work path even with no old jack.
- Run codec cleanup unconditionally from component removal.
- Retain the active runtime-PM guard and Tegra callback removal.

sound/soc/codecs/wm8962.c | 34 +++++++++++++++++++---------------
sound/soc/tegra/tegra_wm8962.c | 13 -------------
2 files changed, 19 insertions(+), 28 deletions(-)

diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c
--- a/sound/soc/codecs/wm8962.c
+++ b/sound/soc/codecs/wm8962.c
@@ -3248,21 +3248,27 @@
{
struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
- int irq_mask, enable;
+ int mic_irq_mask = WM8962_MICD_EINT | WM8962_MICSCD_EINT;
+
+ snd_soc_component_update_bits(component, WM8962_INTERRUPT_STATUS_2_MASK,
+ mic_irq_mask, mic_irq_mask);
+ if (pm_runtime_get_if_active(component->dev) > 0) {
+ snd_soc_component_update_bits(component, WM8962_ADDITIONAL_CONTROL_4,
+ WM8962_MICDET_ENA, 0);
+ pm_runtime_put(component->dev);
+ }
+ if (wm8962->irq)
+ synchronize_irq(wm8962->irq);
+ cancel_delayed_work_sync(&wm8962->mic_work);

wm8962->jack = jack;
if (jack) {
- irq_mask = 0;
- enable = WM8962_MICDET_ENA;
- } else {
- irq_mask = WM8962_MICD_EINT | WM8962_MICSCD_EINT;
- enable = 0;
- }
-
- snd_soc_component_update_bits(component, WM8962_INTERRUPT_STATUS_2_MASK,
- WM8962_MICD_EINT | WM8962_MICSCD_EINT, irq_mask);
- snd_soc_component_update_bits(component, WM8962_ADDITIONAL_CONTROL_4,
- WM8962_MICDET_ENA, enable);
+ snd_soc_component_update_bits(component,
+ WM8962_INTERRUPT_STATUS_2_MASK,
+ mic_irq_mask, 0);
+ snd_soc_component_update_bits(component, WM8962_ADDITIONAL_CONTROL_4,
+ WM8962_MICDET_ENA, WM8962_MICDET_ENA);
+ }

/* Send an initial empty report */
snd_soc_jack_report(wm8962->jack, 0,
@@ -3592,9 +3598,7 @@

static void wm8962_remove(struct snd_soc_component *component)
{
- struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component);
-
- cancel_delayed_work_sync(&wm8962->mic_work);
+ wm8962_mic_detect(component, NULL);

wm8962_free_gpio(component);
wm8962_free_beep(component);
diff --git a/sound/soc/tegra/tegra_wm8962.c b/sound/soc/tegra/tegra_wm8962.c
--- a/sound/soc/tegra/tegra_wm8962.c
+++ b/sound/soc/tegra/tegra_wm8962.c
@@ -97,18 +97,6 @@
return 0;
}

-static int tegra_wm8962_remove(struct snd_soc_card *card)
-{
- struct snd_soc_dai_link *link = &card->dai_link[0];
- struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, link);
- struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
- struct snd_soc_component *component = codec_dai->component;
-
- snd_soc_component_set_jack(component, NULL, NULL);
-
- return 0;
-}
-
SND_SOC_DAILINK_DEFS(wm8962_hifi,
DAILINK_COMP_ARRAY(COMP_EMPTY()),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8962")),
@@ -129,7 +117,6 @@
.owner = THIS_MODULE,
.dai_link = &tegra_wm8962_dai,
.num_links = 1,
- .remove = tegra_wm8962_remove,
.fully_routed = true,
};