[PATCH] ASoC: codecs: wsa881x: Fix shared powerdown GPIO handling
From: Marek Szyprowski
Date: Thu Oct 22 2026 - 05:20:14 EST
On Qualcomm RB5 both WSA8815 speaker amplifiers share a single powerdown
GPIO (TLMM 130), which is handled by the shared GPIO proxy. Since commit
b30973e8c392 ("gpio: shared: make the voting mechanism adaptable") the
proxy treats the value set by the first user on request as the default
and lets any single user vote for the opposite value.
The driver requests the GPIO de-asserted (GPIOD_OUT_LOW), so powering the
amplifier up became the default and asserting powerdown became the
winning vote. Once both amplifiers are runtime suspended, resuming them
deadlocks: the first one releases its powerdown vote and waits for
SoundWire enumeration, but the line stays asserted, because the second
amplifier is still suspended and votes for powerdown. The wait times out
and playback fails:
wsa881x-codec sdw:1:0:0217:2110:00:3: Initialization not complete
wsa881x-codec sdw:1:0:0217:2110:00:3: ASoC error (-110): at snd_soc_pcm_component_pm_runtime_get() on sdw:1:0:0217:2110:00:3
Request the GPIO asserted, so that powerdown is the default state and
the line is de-asserted as long as any amplifier needs to be powered,
then de-assert it to power up the amplifier as before.
Also use gpiod_set_value_cansleep() instead of gpiod_direction_output()
in the runtime PM callbacks. The line is already configured as an output
and the proxy resets its default value when the direction is set while
it has only a single user, which can happen if the other amplifiers have
not been probed yet.
Fixes: bfa336cee332 ("ASoC: wsa881x: Move custom workaround to gpiolib-of")
Fixes: b30973e8c392 ("gpio: shared: make the voting mechanism adaptable")
Signed-off-by: Marek Szyprowski <m.szyprowski@xxxxxxxxxxx>
---
The patch has been prepared with LLM assistance and tested on the real
Qualcomm RB5 board.
Best regards
Marek Szyprowski, PhD
Samsung R&D Institute Poland
---
sound/soc/codecs/wsa881x.c | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/sound/soc/codecs/wsa881x.c b/sound/soc/codecs/wsa881x.c
index 47ac3e4985a5..4f3e6bc29c7e 100644
--- a/sound/soc/codecs/wsa881x.c
+++ b/sound/soc/codecs/wsa881x.c
@@ -1115,11 +1115,19 @@ static int wsa881x_probe(struct sdw_slave *pdev,
if (!wsa881x)
return -ENOMEM;
- wsa881x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_LOW);
+ /*
+ * The powerdown GPIO may be shared between several amplifiers. Request
+ * it asserted, so the shared GPIO proxy treats powerdown as the default
+ * state and keeps the line de-asserted as long as any amplifier needs
+ * it, then power up this amplifier.
+ */
+ wsa881x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
if (IS_ERR(wsa881x->sd_n))
return dev_err_probe(dev, PTR_ERR(wsa881x->sd_n),
"Shutdown Control GPIO not found\n");
+ gpiod_set_value_cansleep(wsa881x->sd_n, 0);
+
dev_set_drvdata(dev, wsa881x);
wsa881x->slave = pdev;
wsa881x->dev = dev;
@@ -1154,7 +1162,7 @@ static int wsa881x_runtime_suspend(struct device *dev)
struct regmap *regmap = dev_get_regmap(dev, NULL);
struct wsa881x_priv *wsa881x = dev_get_drvdata(dev);
- gpiod_direction_output(wsa881x->sd_n, 1);
+ gpiod_set_value_cansleep(wsa881x->sd_n, 1);
regcache_cache_only(regmap, true);
regcache_mark_dirty(regmap);
@@ -1169,11 +1177,11 @@ static int wsa881x_runtime_resume(struct device *dev)
struct wsa881x_priv *wsa881x = dev_get_drvdata(dev);
int ret;
- gpiod_direction_output(wsa881x->sd_n, 0);
+ gpiod_set_value_cansleep(wsa881x->sd_n, 0);
ret = sdw_slave_wait_for_init(slave, WSA881X_PROBE_TIMEOUT);
if (ret) {
- gpiod_direction_output(wsa881x->sd_n, 1);
+ gpiod_set_value_cansleep(wsa881x->sd_n, 1);
return ret;
}
@@ -1182,7 +1190,7 @@ static int wsa881x_runtime_resume(struct device *dev)
if (ret) {
regcache_cache_only(regmap, true);
regcache_mark_dirty(regmap);
- gpiod_direction_output(wsa881x->sd_n, 1);
+ gpiod_set_value_cansleep(wsa881x->sd_n, 1);
return ret;
}
--
2.43.0