diff mbox series

ASoC: cs42l43: Move shutter IRQ handling into a worker thread

Message ID 20240729155932.3054632-1-ckeepax@opensource.cirrus.com (mailing list archive)
State New, archived
Headers show
Series ASoC: cs42l43: Move shutter IRQ handling into a worker thread | expand

Commit Message

Charles Keepax July 29, 2024, 3:59 p.m. UTC
The microphone/speaker privacy shutter ALSA control handlers need to
call pm_runtime_resume, since the hardware needs to be powered up to
check the hardware state of the shutter. The IRQ handler for the
shutters also needs to notify the ALSA control to inform user-space
the shutters updated. However this leads to a mutex inversion, between
the sdw_dev_lock and the controls_rwsem.

To avoid this mutex inversion add a work item for handling the shutter
IRQ and move the notifies into that.

Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---
 sound/soc/codecs/cs42l43.c | 43 +++++++++++++++++++++++++++-----------
 sound/soc/codecs/cs42l43.h |  3 +++
 2 files changed, 34 insertions(+), 12 deletions(-)

Comments

Takashi Iwai July 29, 2024, 4:18 p.m. UTC | #1
On Mon, 29 Jul 2024 17:59:32 +0200,
Charles Keepax wrote:
> 
> The microphone/speaker privacy shutter ALSA control handlers need to
> call pm_runtime_resume, since the hardware needs to be powered up to
> check the hardware state of the shutter. The IRQ handler for the
> shutters also needs to notify the ALSA control to inform user-space
> the shutters updated. However this leads to a mutex inversion, between
> the sdw_dev_lock and the controls_rwsem.

That's bad, how does the mutex inversion look like?  Generally
speaking, a call of snd_ctl_notify() from an irq handler is a very
standard procedure, and it should work without too much workaround.


thanks,

Takashi

> 
> To avoid this mutex inversion add a work item for handling the shutter
> IRQ and move the notifies into that.
> 
> Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
> ---
>  sound/soc/codecs/cs42l43.c | 43 +++++++++++++++++++++++++++-----------
>  sound/soc/codecs/cs42l43.h |  3 +++
>  2 files changed, 34 insertions(+), 12 deletions(-)
> 
> diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c
> index 92674314227c4..74cb396ec0576 100644
> --- a/sound/soc/codecs/cs42l43.c
> +++ b/sound/soc/codecs/cs42l43.c
> @@ -249,9 +249,10 @@ CS42L43_IRQ_COMPLETE(spkr_startup)
>  CS42L43_IRQ_COMPLETE(spkl_startup)
>  CS42L43_IRQ_COMPLETE(load_detect)
>  
> -static irqreturn_t cs42l43_mic_shutter(int irq, void *data)
> +static void cs42l43_mic_shutter_work(struct work_struct *work)
>  {
> -	struct cs42l43_codec *priv = data;
> +	struct cs42l43_codec *priv = container_of(work, struct cs42l43_codec,
> +						  mic_shutter_work);
>  	static const char * const controls[] = {
>  		"Decimator 1 Switch",
>  		"Decimator 2 Switch",
> @@ -263,32 +264,47 @@ static irqreturn_t cs42l43_mic_shutter(int irq, void *data)
>  	dev_dbg(priv->dev, "Microphone shutter changed\n");
>  
>  	if (!priv->component)
> -		return IRQ_NONE;
> +		return;
>  
>  	for (i = 0; i < ARRAY_SIZE(controls); i++) {
> -		ret = snd_soc_component_notify_control(priv->component,
> -						       controls[i]);
> +		ret = snd_soc_component_notify_control(priv->component, controls[i]);
>  		if (ret)
> -			return IRQ_NONE;
> +			dev_err(priv->dev, "Failed to notify control %s: %d\n",
> +				controls[i], ret);
>  	}
> +}
> +
> +static irqreturn_t cs42l43_mic_shutter(int irq, void *data)
> +{
> +	struct cs42l43_codec *priv = data;
> +
> +	queue_work(system_wq, &priv->mic_shutter_work);
>  
>  	return IRQ_HANDLED;
>  }
>  
> -static irqreturn_t cs42l43_spk_shutter(int irq, void *data)
> +static void cs42l43_spk_shutter_work(struct work_struct *work)
>  {
> -	struct cs42l43_codec *priv = data;
> +	struct cs42l43_codec *priv = container_of(work, struct cs42l43_codec,
> +						  mic_shutter_work);
> +	const char * const control = "Speaker Digital Switch";
>  	int ret;
>  
>  	dev_dbg(priv->dev, "Speaker shutter changed\n");
>  
>  	if (!priv->component)
> -		return IRQ_NONE;
> +		return;
>  
> -	ret = snd_soc_component_notify_control(priv->component,
> -					       "Speaker Digital Switch");
> +	ret = snd_soc_component_notify_control(priv->component, control);
>  	if (ret)
> -		return IRQ_NONE;
> +		dev_err(priv->dev, "Failed to notify control %s: %d\n", control, ret);
> +}
> +
> +static irqreturn_t cs42l43_spk_shutter(int irq, void *data)
> +{
> +	struct cs42l43_codec *priv = data;
> +
> +	queue_work(system_wq, &priv->spk_shutter_work);
>  
>  	return IRQ_HANDLED;
>  }
> @@ -2280,6 +2296,9 @@ static int cs42l43_codec_probe(struct platform_device *pdev)
>  	INIT_WORK(&priv->button_release_work, cs42l43_button_release_work);
>  	INIT_WORK(&priv->hp_ilimit_work, cs42l43_hp_ilimit_work);
>  
> +	INIT_WORK(&priv->mic_shutter_work, cs42l43_mic_shutter_work);
> +	INIT_WORK(&priv->spk_shutter_work, cs42l43_spk_shutter_work);
> +
>  	pm_runtime_set_autosuspend_delay(priv->dev, 100);
>  	pm_runtime_use_autosuspend(priv->dev);
>  	pm_runtime_set_active(priv->dev);
> diff --git a/sound/soc/codecs/cs42l43.h b/sound/soc/codecs/cs42l43.h
> index 9924c13e1eb53..5592aab5c8042 100644
> --- a/sound/soc/codecs/cs42l43.h
> +++ b/sound/soc/codecs/cs42l43.h
> @@ -100,6 +100,9 @@ struct cs42l43_codec {
>  	struct delayed_work hp_ilimit_clear_work;
>  	bool hp_ilimited;
>  	int hp_ilimit_count;
> +
> +	struct work_struct mic_shutter_work;
> +	struct work_struct spk_shutter_work;
>  };
>  
>  #if IS_REACHABLE(CONFIG_SND_SOC_CS42L43_SDW)
> -- 
> 2.39.2
> 
>
Charles Keepax July 29, 2024, 4:44 p.m. UTC | #2
On Mon, Jul 29, 2024 at 06:18:50PM +0200, Takashi Iwai wrote:
> On Mon, 29 Jul 2024 17:59:32 +0200,
> Charles Keepax wrote:
> > 
> > The microphone/speaker privacy shutter ALSA control handlers need to
> > call pm_runtime_resume, since the hardware needs to be powered up to
> > check the hardware state of the shutter. The IRQ handler for the
> > shutters also needs to notify the ALSA control to inform user-space
> > the shutters updated. However this leads to a mutex inversion, between
> > the sdw_dev_lock and the controls_rwsem.
> 
> That's bad, how does the mutex inversion look like?  Generally
> speaking, a call of snd_ctl_notify() from an irq handler is a very
> standard procedure, and it should work without too much workaround.
> 

SoundWire IRQs are called under the sdw_dev_lock, and then in the
IRQ handler we call snd_ctl_notify which takes controls_rwsem. On
the other side, in the ALSA control handler which is obviously
called under the controls_rwsem, we do a pm_runtime_resume, which
causes SoundWire stuff to happen that takes the sdw_dev_lock.

Thanks,
Charles
Takashi Iwai July 29, 2024, 7:30 p.m. UTC | #3
On Mon, 29 Jul 2024 18:44:59 +0200,
Charles Keepax wrote:
> 
> On Mon, Jul 29, 2024 at 06:18:50PM +0200, Takashi Iwai wrote:
> > On Mon, 29 Jul 2024 17:59:32 +0200,
> > Charles Keepax wrote:
> > > 
> > > The microphone/speaker privacy shutter ALSA control handlers need to
> > > call pm_runtime_resume, since the hardware needs to be powered up to
> > > check the hardware state of the shutter. The IRQ handler for the
> > > shutters also needs to notify the ALSA control to inform user-space
> > > the shutters updated. However this leads to a mutex inversion, between
> > > the sdw_dev_lock and the controls_rwsem.
> > 
> > That's bad, how does the mutex inversion look like?  Generally
> > speaking, a call of snd_ctl_notify() from an irq handler is a very
> > standard procedure, and it should work without too much workaround.
> > 
> 
> SoundWire IRQs are called under the sdw_dev_lock, and then in the
> IRQ handler we call snd_ctl_notify which takes controls_rwsem.

snd_ctl_notify() doesn't take rwsem but ctl_files_rwlock.
And rwlock isn't taken except for two cases, at a list traverse at
enumerating from user-space and at the device disconnection, so it
shouldn't race.  Anything missing there...?


thanks,

Takashi

> On
> the other side, in the ALSA control handler which is obviously
> called under the controls_rwsem, we do a pm_runtime_resume, which
> causes SoundWire stuff to happen that takes the sdw_dev_lock.
> 
> Thanks,
> Charles
Charles Keepax July 30, 2024, 8:43 a.m. UTC | #4
On Mon, Jul 29, 2024 at 09:30:00PM +0200, Takashi Iwai wrote:
> On Mon, 29 Jul 2024 18:44:59 +0200,
> Charles Keepax wrote:
> > 
> > On Mon, Jul 29, 2024 at 06:18:50PM +0200, Takashi Iwai wrote:
> > > On Mon, 29 Jul 2024 17:59:32 +0200,
> > > Charles Keepax wrote:
> > > > 
> > > > The microphone/speaker privacy shutter ALSA control handlers need to
> > > > call pm_runtime_resume, since the hardware needs to be powered up to
> > > > check the hardware state of the shutter. The IRQ handler for the
> > > > shutters also needs to notify the ALSA control to inform user-space
> > > > the shutters updated. However this leads to a mutex inversion, between
> > > > the sdw_dev_lock and the controls_rwsem.
> > > 
> > > That's bad, how does the mutex inversion look like?  Generally
> > > speaking, a call of snd_ctl_notify() from an irq handler is a very
> > > standard procedure, and it should work without too much workaround.
> > > 
> > 
> > SoundWire IRQs are called under the sdw_dev_lock, and then in the
> > IRQ handler we call snd_ctl_notify which takes controls_rwsem.
> 
> snd_ctl_notify() doesn't take rwsem but ctl_files_rwlock.
> And rwlock isn't taken except for two cases, at a list traverse at
> enumerating from user-space and at the device disconnection, so it
> shouldn't race.  Anything missing there...?
> 

The trouble here isn't the snd_ctl_notify, the handler uses
snd_soc_component_notify_control which also does a
snd_soc_card_get_kcontrol, which eventually calls snd_ctl_find_id
which does take the controls_rwsem.

Thanks,
Charles
Takashi Iwai July 30, 2024, 12:33 p.m. UTC | #5
On Tue, 30 Jul 2024 10:43:01 +0200,
Charles Keepax wrote:
> 
> On Mon, Jul 29, 2024 at 09:30:00PM +0200, Takashi Iwai wrote:
> > On Mon, 29 Jul 2024 18:44:59 +0200,
> > Charles Keepax wrote:
> > > 
> > > On Mon, Jul 29, 2024 at 06:18:50PM +0200, Takashi Iwai wrote:
> > > > On Mon, 29 Jul 2024 17:59:32 +0200,
> > > > Charles Keepax wrote:
> > > > > 
> > > > > The microphone/speaker privacy shutter ALSA control handlers need to
> > > > > call pm_runtime_resume, since the hardware needs to be powered up to
> > > > > check the hardware state of the shutter. The IRQ handler for the
> > > > > shutters also needs to notify the ALSA control to inform user-space
> > > > > the shutters updated. However this leads to a mutex inversion, between
> > > > > the sdw_dev_lock and the controls_rwsem.
> > > > 
> > > > That's bad, how does the mutex inversion look like?  Generally
> > > > speaking, a call of snd_ctl_notify() from an irq handler is a very
> > > > standard procedure, and it should work without too much workaround.
> > > > 
> > > 
> > > SoundWire IRQs are called under the sdw_dev_lock, and then in the
> > > IRQ handler we call snd_ctl_notify which takes controls_rwsem.
> > 
> > snd_ctl_notify() doesn't take rwsem but ctl_files_rwlock.
> > And rwlock isn't taken except for two cases, at a list traverse at
> > enumerating from user-space and at the device disconnection, so it
> > shouldn't race.  Anything missing there...?
> > 
> 
> The trouble here isn't the snd_ctl_notify, the handler uses
> snd_soc_component_notify_control which also does a
> snd_soc_card_get_kcontrol, which eventually calls snd_ctl_find_id
> which does take the controls_rwsem.

OK, now it's clearer.  I think we can change snd_ctl_find_*() rather
to take rwlock instead of rwsem.  It's only a quick look-up, hence
rwlock can work well.

Totally untested patch set is below.


Takashi
Jaroslav Kysela July 30, 2024, 2:13 p.m. UTC | #6
On 30. 07. 24 14:33, Takashi Iwai wrote:
> On Tue, 30 Jul 2024 10:43:01 +0200,
> Charles Keepax wrote:
>>
>> On Mon, Jul 29, 2024 at 09:30:00PM +0200, Takashi Iwai wrote:
>>> On Mon, 29 Jul 2024 18:44:59 +0200,
>>> Charles Keepax wrote:
>>>>
>>>> On Mon, Jul 29, 2024 at 06:18:50PM +0200, Takashi Iwai wrote:
>>>>> On Mon, 29 Jul 2024 17:59:32 +0200,
>>>>> Charles Keepax wrote:
>>>>>>
>>>>>> The microphone/speaker privacy shutter ALSA control handlers need to
>>>>>> call pm_runtime_resume, since the hardware needs to be powered up to
>>>>>> check the hardware state of the shutter. The IRQ handler for the
>>>>>> shutters also needs to notify the ALSA control to inform user-space
>>>>>> the shutters updated. However this leads to a mutex inversion, between
>>>>>> the sdw_dev_lock and the controls_rwsem.
>>>>>
>>>>> That's bad, how does the mutex inversion look like?  Generally
>>>>> speaking, a call of snd_ctl_notify() from an irq handler is a very
>>>>> standard procedure, and it should work without too much workaround.
>>>>>
>>>>
>>>> SoundWire IRQs are called under the sdw_dev_lock, and then in the
>>>> IRQ handler we call snd_ctl_notify which takes controls_rwsem.
>>>
>>> snd_ctl_notify() doesn't take rwsem but ctl_files_rwlock.
>>> And rwlock isn't taken except for two cases, at a list traverse at
>>> enumerating from user-space and at the device disconnection, so it
>>> shouldn't race.  Anything missing there...?
>>>
>>
>> The trouble here isn't the snd_ctl_notify, the handler uses
>> snd_soc_component_notify_control which also does a
>> snd_soc_card_get_kcontrol, which eventually calls snd_ctl_find_id
>> which does take the controls_rwsem.
> 
> OK, now it's clearer.  I think we can change snd_ctl_find_*() rather
> to take rwlock instead of rwsem.  It's only a quick look-up, hence
> rwlock can work well.
> 
> Totally untested patch set is below.

But why the interrupt routine does not use the cached kcontrol pointer? It 
looks like a bad driver design when you need to do such name (string based) 
lookups from the interrupt routine.

					Jaroslav
Takashi Iwai July 30, 2024, 2:27 p.m. UTC | #7
On Tue, 30 Jul 2024 16:13:33 +0200,
Jaroslav Kysela wrote:
> 
> On 30. 07. 24 14:33, Takashi Iwai wrote:
> > On Tue, 30 Jul 2024 10:43:01 +0200,
> > Charles Keepax wrote:
> >> 
> >> On Mon, Jul 29, 2024 at 09:30:00PM +0200, Takashi Iwai wrote:
> >>> On Mon, 29 Jul 2024 18:44:59 +0200,
> >>> Charles Keepax wrote:
> >>>> 
> >>>> On Mon, Jul 29, 2024 at 06:18:50PM +0200, Takashi Iwai wrote:
> >>>>> On Mon, 29 Jul 2024 17:59:32 +0200,
> >>>>> Charles Keepax wrote:
> >>>>>> 
> >>>>>> The microphone/speaker privacy shutter ALSA control handlers need to
> >>>>>> call pm_runtime_resume, since the hardware needs to be powered up to
> >>>>>> check the hardware state of the shutter. The IRQ handler for the
> >>>>>> shutters also needs to notify the ALSA control to inform user-space
> >>>>>> the shutters updated. However this leads to a mutex inversion, between
> >>>>>> the sdw_dev_lock and the controls_rwsem.
> >>>>> 
> >>>>> That's bad, how does the mutex inversion look like?  Generally
> >>>>> speaking, a call of snd_ctl_notify() from an irq handler is a very
> >>>>> standard procedure, and it should work without too much workaround.
> >>>>> 
> >>>> 
> >>>> SoundWire IRQs are called under the sdw_dev_lock, and then in the
> >>>> IRQ handler we call snd_ctl_notify which takes controls_rwsem.
> >>> 
> >>> snd_ctl_notify() doesn't take rwsem but ctl_files_rwlock.
> >>> And rwlock isn't taken except for two cases, at a list traverse at
> >>> enumerating from user-space and at the device disconnection, so it
> >>> shouldn't race.  Anything missing there...?
> >>> 
> >> 
> >> The trouble here isn't the snd_ctl_notify, the handler uses
> >> snd_soc_component_notify_control which also does a
> >> snd_soc_card_get_kcontrol, which eventually calls snd_ctl_find_id
> >> which does take the controls_rwsem.
> > 
> > OK, now it's clearer.  I think we can change snd_ctl_find_*() rather
> > to take rwlock instead of rwsem.  It's only a quick look-up, hence
> > rwlock can work well.
> > 
> > Totally untested patch set is below.
> 
> But why the interrupt routine does not use the cached kcontrol
> pointer? It looks like a bad driver design when you need to do such
> name (string based) lookups from the interrupt routine.

Yeah, it'd be even better :)

Though, the conversion to rwlock might be still worth; it's a good
cleanup to drop the *_unlocked() versions that can be messy.


Takashi
Mark Brown July 30, 2024, 2:44 p.m. UTC | #8
On Tue, Jul 30, 2024 at 04:13:33PM +0200, Jaroslav Kysela wrote:
> On 30. 07. 24 14:33, Takashi Iwai wrote:

> > OK, now it's clearer.  I think we can change snd_ctl_find_*() rather
> > to take rwlock instead of rwsem.  It's only a quick look-up, hence
> > rwlock can work well.

> > Totally untested patch set is below.

> But why the interrupt routine does not use the cached kcontrol pointer? It
> looks like a bad driver design when you need to do such name (string based)
> lookups from the interrupt routine.

With some of these slower buses it's not immediately obvious that it's
worth the bother of caching - the overhead of doing the lookup is
negligable in the overall context of handling the interrupt.
Jaroslav Kysela July 30, 2024, 9:47 p.m. UTC | #9
On 30. 07. 24 16:44, Mark Brown wrote:
> On Tue, Jul 30, 2024 at 04:13:33PM +0200, Jaroslav Kysela wrote:
>> On 30. 07. 24 14:33, Takashi Iwai wrote:
> 
>>> OK, now it's clearer.  I think we can change snd_ctl_find_*() rather
>>> to take rwlock instead of rwsem.  It's only a quick look-up, hence
>>> rwlock can work well.
> 
>>> Totally untested patch set is below.
> 
>> But why the interrupt routine does not use the cached kcontrol pointer? It
>> looks like a bad driver design when you need to do such name (string based)
>> lookups from the interrupt routine.
> 
> With some of these slower buses it's not immediately obvious that it's
> worth the bother of caching - the overhead of doing the lookup is
> negligable in the overall context of handling the interrupt.

I don't buy that argument. We should always try to write an optimal code. 
Every CPU tick counts.

				Jaroslav
Mark Brown July 30, 2024, 11:20 p.m. UTC | #10
On Tue, Jul 30, 2024 at 11:47:48PM +0200, Jaroslav Kysela wrote:
> On 30. 07. 24 16:44, Mark Brown wrote:

> > With some of these slower buses it's not immediately obvious that it's
> > worth the bother of caching - the overhead of doing the lookup is
> > negligable in the overall context of handling the interrupt.

> I don't buy that argument. We should always try to write an optimal code.
> Every CPU tick counts.

So do the bytes of memory you'd use caching the pointers!
Takashi Iwai July 31, 2024, 6:30 a.m. UTC | #11
On Wed, 31 Jul 2024 01:20:40 +0200,
Mark Brown wrote:
> 
> On Tue, Jul 30, 2024 at 11:47:48PM +0200, Jaroslav Kysela wrote:
> > On 30. 07. 24 16:44, Mark Brown wrote:
> 
> > > With some of these slower buses it's not immediately obvious that it's
> > > worth the bother of caching - the overhead of doing the lookup is
> > > negligable in the overall context of handling the interrupt.
> 
> > I don't buy that argument. We should always try to write an optimal code.
> > Every CPU tick counts.
> 
> So do the bytes of memory you'd use caching the pointers!

Adding two more works would be even more wastes, that's the beginning
of the discussion :)

The problem is the order of ASoC component initialization, though;
AFAIUC, the kcontrols aren't instantiated at the point of component
probe, hence you can't get kcontrol objects there.


Takashi
Charles Keepax July 31, 2024, 8:49 a.m. UTC | #12
On Wed, Jul 31, 2024 at 08:30:15AM +0200, Takashi Iwai wrote:
> On Wed, 31 Jul 2024 01:20:40 +0200,
> Mark Brown wrote:
> > 
> > On Tue, Jul 30, 2024 at 11:47:48PM +0200, Jaroslav Kysela wrote:
> > > On 30. 07. 24 16:44, Mark Brown wrote:
> > 
> > > > With some of these slower buses it's not immediately obvious that it's
> > > > worth the bother of caching - the overhead of doing the lookup is
> > > > negligable in the overall context of handling the interrupt.
> > 
> > > I don't buy that argument. We should always try to write an optimal code.
> > > Every CPU tick counts.
> > 
> > So do the bytes of memory you'd use caching the pointers!
> 
> Adding two more works would be even more wastes, that's the beginning
> of the discussion :)
> 
> The problem is the order of ASoC component initialization, though;
> AFAIUC, the kcontrols aren't instantiated at the point of component
> probe, hence you can't get kcontrol objects there.
> 

I think there probably should be some way to cache the kcontrols,
I will have a look at implementing that. In general I chose the
delayed work as caching didn't obviously seem worth it as Mark notes,
and also the sdw_dev_lock tends to cause a lot of these inversions
and the delayed work is a very safe solution, whereas caching makes
me slightly nervous we will find another inversion at some point.

In general I think that is the actual problem here, the sdw_dev_lock
wraps too much stuff and frequently causes these inversions. Although
fixing that may not be possible and certainly requires a lot of
thinking.

Thanks,
Charles
diff mbox series

Patch

diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c
index 92674314227c4..74cb396ec0576 100644
--- a/sound/soc/codecs/cs42l43.c
+++ b/sound/soc/codecs/cs42l43.c
@@ -249,9 +249,10 @@  CS42L43_IRQ_COMPLETE(spkr_startup)
 CS42L43_IRQ_COMPLETE(spkl_startup)
 CS42L43_IRQ_COMPLETE(load_detect)
 
-static irqreturn_t cs42l43_mic_shutter(int irq, void *data)
+static void cs42l43_mic_shutter_work(struct work_struct *work)
 {
-	struct cs42l43_codec *priv = data;
+	struct cs42l43_codec *priv = container_of(work, struct cs42l43_codec,
+						  mic_shutter_work);
 	static const char * const controls[] = {
 		"Decimator 1 Switch",
 		"Decimator 2 Switch",
@@ -263,32 +264,47 @@  static irqreturn_t cs42l43_mic_shutter(int irq, void *data)
 	dev_dbg(priv->dev, "Microphone shutter changed\n");
 
 	if (!priv->component)
-		return IRQ_NONE;
+		return;
 
 	for (i = 0; i < ARRAY_SIZE(controls); i++) {
-		ret = snd_soc_component_notify_control(priv->component,
-						       controls[i]);
+		ret = snd_soc_component_notify_control(priv->component, controls[i]);
 		if (ret)
-			return IRQ_NONE;
+			dev_err(priv->dev, "Failed to notify control %s: %d\n",
+				controls[i], ret);
 	}
+}
+
+static irqreturn_t cs42l43_mic_shutter(int irq, void *data)
+{
+	struct cs42l43_codec *priv = data;
+
+	queue_work(system_wq, &priv->mic_shutter_work);
 
 	return IRQ_HANDLED;
 }
 
-static irqreturn_t cs42l43_spk_shutter(int irq, void *data)
+static void cs42l43_spk_shutter_work(struct work_struct *work)
 {
-	struct cs42l43_codec *priv = data;
+	struct cs42l43_codec *priv = container_of(work, struct cs42l43_codec,
+						  mic_shutter_work);
+	const char * const control = "Speaker Digital Switch";
 	int ret;
 
 	dev_dbg(priv->dev, "Speaker shutter changed\n");
 
 	if (!priv->component)
-		return IRQ_NONE;
+		return;
 
-	ret = snd_soc_component_notify_control(priv->component,
-					       "Speaker Digital Switch");
+	ret = snd_soc_component_notify_control(priv->component, control);
 	if (ret)
-		return IRQ_NONE;
+		dev_err(priv->dev, "Failed to notify control %s: %d\n", control, ret);
+}
+
+static irqreturn_t cs42l43_spk_shutter(int irq, void *data)
+{
+	struct cs42l43_codec *priv = data;
+
+	queue_work(system_wq, &priv->spk_shutter_work);
 
 	return IRQ_HANDLED;
 }
@@ -2280,6 +2296,9 @@  static int cs42l43_codec_probe(struct platform_device *pdev)
 	INIT_WORK(&priv->button_release_work, cs42l43_button_release_work);
 	INIT_WORK(&priv->hp_ilimit_work, cs42l43_hp_ilimit_work);
 
+	INIT_WORK(&priv->mic_shutter_work, cs42l43_mic_shutter_work);
+	INIT_WORK(&priv->spk_shutter_work, cs42l43_spk_shutter_work);
+
 	pm_runtime_set_autosuspend_delay(priv->dev, 100);
 	pm_runtime_use_autosuspend(priv->dev);
 	pm_runtime_set_active(priv->dev);
diff --git a/sound/soc/codecs/cs42l43.h b/sound/soc/codecs/cs42l43.h
index 9924c13e1eb53..5592aab5c8042 100644
--- a/sound/soc/codecs/cs42l43.h
+++ b/sound/soc/codecs/cs42l43.h
@@ -100,6 +100,9 @@  struct cs42l43_codec {
 	struct delayed_work hp_ilimit_clear_work;
 	bool hp_ilimited;
 	int hp_ilimit_count;
+
+	struct work_struct mic_shutter_work;
+	struct work_struct spk_shutter_work;
 };
 
 #if IS_REACHABLE(CONFIG_SND_SOC_CS42L43_SDW)