| From 4105a4c0678b2808fc8046b60321b4f1cc7dae75 Mon Sep 17 00:00:00 2001 |
| From: Runyu Xiao <runyu.xiao@seu.edu.cn> |
| Date: Fri, 12 Jun 2026 00:15:53 +0800 |
| Subject: ASoC: cs35l34: drain threaded IRQ before runtime suspend |
| |
| From: Runyu Xiao <runyu.xiao@seu.edu.cn> |
| |
| commit 4105a4c0678b2808fc8046b60321b4f1cc7dae75 upstream. |
| |
| cs35l34_runtime_suspend() currently switches the codec into |
| regcache_cache_only(true), asserts reset low, and powers the device off |
| without first quiescing the threaded IRQ registered by |
| devm_request_threaded_irq(). That leaves a window where |
| cs35l34_irq_thread() can still run after suspend has removed live |
| hardware access. |
| |
| A running system can reach this during runtime PM while the driver still |
| has critical fault IRQs unmasked. If the threaded handler runs in that |
| window, it reads volatile INT_STATUS_1..4 after cache_only has been |
| enabled, ignores the regmap_read() failures, and can still execute the |
| PROT_RELEASE_CTL release sequence or the BST fault power-down writes. |
| |
| Use disable_irq() before entering cache_only/reset-low/power-off so any |
| in-flight threaded handler is drained and no new IRQ thread can run |
| while the device is suspended. Re-enable the IRQ only after |
| runtime_resume() has restored live register access with regcache_sync(). |
| Since probe only logs request_threaded_irq() failures and keeps going, |
| track whether the IRQ was actually installed before disabling or |
| re-enabling it. |
| |
| Fixes: c1124c09e103 ("ASoC: cs35l34: Initial commit of the cs35l34 CODEC driver.") |
| Cc: stable@vger.kernel.org |
| Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn> |
| Link: https://patch.msgid.link/20260611161553.3378721-3-runyu.xiao@seu.edu.cn |
| Signed-off-by: Mark Brown <broonie@kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| sound/soc/codecs/cs35l34.c | 14 ++++++++++++-- |
| 1 file changed, 12 insertions(+), 2 deletions(-) |
| |
| --- a/sound/soc/codecs/cs35l34.c |
| +++ b/sound/soc/codecs/cs35l34.c |
| @@ -45,6 +45,7 @@ struct cs35l34_private { |
| int num_core_supplies; |
| int mclk_int; |
| bool tdm_mode; |
| + bool irq_requested; |
| struct gpio_desc *reset_gpio; /* Active-low reset GPIO */ |
| }; |
| |
| @@ -1053,10 +1054,12 @@ static int cs35l34_i2c_probe(struct i2c_ |
| } |
| |
| ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL, |
| - cs35l34_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_LOW, |
| - "cs35l34", cs35l34); |
| + cs35l34_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_LOW, |
| + "cs35l34", cs35l34); |
| if (ret != 0) |
| dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret); |
| + else |
| + cs35l34->irq_requested = true; |
| |
| cs35l34->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, |
| "reset", GPIOD_OUT_LOW); |
| @@ -1161,6 +1164,9 @@ static int __maybe_unused cs35l34_runtim |
| dev_err(dev, "Failed to restore register cache\n"); |
| goto err; |
| } |
| + |
| + if (cs35l34->irq_requested) |
| + enable_irq(to_i2c_client(dev)->irq); |
| return 0; |
| err: |
| regcache_cache_only(cs35l34->regmap, true); |
| @@ -1174,6 +1180,10 @@ static int __maybe_unused cs35l34_runtim |
| { |
| struct cs35l34_private *cs35l34 = dev_get_drvdata(dev); |
| |
| + /* Drain and block the threaded IRQ before cache_only/power-off. */ |
| + if (cs35l34->irq_requested) |
| + disable_irq(to_i2c_client(dev)->irq); |
| + |
| regcache_cache_only(cs35l34->regmap, true); |
| regcache_mark_dirty(cs35l34->regmap); |
| |