From c80f86ed857e21dcdd389559d3e62bf53ff39f9a Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Thu, 22 Oct 2026 11:09:22 +0200 Subject: [PATCH] ASoC: codecs: wsa881x: Fix shared powerdown GPIO handling 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 Link: https://patch.msgid.link/20261022090923.769464-1-m.szyprowski@samsung.com Signed-off-by: Mark Brown --- 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; }