[PATCH ath-next v2] wifi: ath9k: don't re-request the LED GPIO in ath9k_stop()
From: Rosen Penev
Date: Tue Oct 27 2026 - 23:41:17 EST
ath9k_deinit_device() frees the LED pin in ath_deinit_leds() before it
unregisters the hw. ath9k_stop() then switches the LED pin back to an
input with ath9k_hw_gpio_request_in(). For a SoC GPIO outside of the
WMAC, ath9k_hw_gpio_request() takes a new descriptor through
gpiod_get_index() at that point, and nothing releases it again. The
next probe fails with "request GPIO12 failed:-16" and every LED update
hits the WARN_ON() in ath9k_hw_set_gpio(). That request also runs
under sc->sc_pcu_lock, while gpiod_get_index() and
gpiod_set_consumer_name() may sleep.
The input direction was only added for symmetry with ath9k_start(); the
suspend/resume fix that brought it back only needed the output side.
On WMAC pins it also undoes the "LED off" level written just before it.
Drop it, so the pin simply stays an output driven to the off level.
ath9k_stop() still writes the LED pin, so free the pin after
ieee80211_unregister_hw() instead of in ath_deinit_leds(); otherwise
that write hits the WARN_ON() in ath9k_hw_set_gpio() on SoC GPIOs. This
also frees the pin when the LED class device could not be registered,
which leaked it before.
Seen on a TP-Link Archer C7 v2 (QCA9558, LED on SoC GPIO 12) when
reloading ath9k.
Fixes: e34f2ff40e03 ("ath9k: bring back direction setting in ath9k_{start_stop}")
Signed-off-by: Rosen Penev <rosenp@xxxxxxxxx>
---
v2: remove ath9k_hw_gpio_free in ath_deinit_leds
drivers/net/wireless/ath/ath9k/gpio.c | 2 --
drivers/net/wireless/ath/ath9k/init.c | 5 +++++
drivers/net/wireless/ath/ath9k/main.c | 4 +---
3 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/net/wireless/ath/ath9k/gpio.c b/drivers/net/wireless/ath/ath9k/gpio.c
index 2dbc7efdd637..d525d8ef9369 100644
--- a/drivers/net/wireless/ath/ath9k/gpio.c
+++ b/drivers/net/wireless/ath/ath9k/gpio.c
@@ -67,8 +67,6 @@ void ath_deinit_leds(struct ath_softc *sc)
ath_led_brightness(&sc->led_cdev, LED_OFF);
led_classdev_unregister(&sc->led_cdev);
-
- ath9k_hw_gpio_free(sc->sc_ah, sc->sc_ah->led_pin);
}
void ath_init_leds(struct ath_softc *sc)
diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c
index a7a11781d83d..d35460e352d9 100644
--- a/drivers/net/wireless/ath/ath9k/init.c
+++ b/drivers/net/wireless/ath/ath9k/init.c
@@ -1128,6 +1128,11 @@ void ath9k_deinit_device(struct ath_softc *sc)
ath9k_deinit_debug(sc);
ath9k_deinit_wow(hw);
ieee80211_unregister_hw(hw);
+
+ /* ath9k_stop() still drives the LED pin, so release it only now */
+ if (sc->sc_ah->led_pin >= 0)
+ ath9k_hw_gpio_free(sc->sc_ah, sc->sc_ah->led_pin);
+
ath_rx_cleanup(sc);
ath9k_deinit_softc(sc);
}
diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c
index 740a6fc7b067..07797cc89ee0 100644
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
@@ -942,11 +942,9 @@ static void ath9k_stop(struct ieee80211_hw *hw, bool suspend)
spin_lock_bh(&sc->sc_pcu_lock);
- if (ah->led_pin >= 0) {
+ if (ah->led_pin >= 0)
ath9k_hw_set_gpio(ah, ah->led_pin,
(ah->config.led_active_high) ? 0 : 1);
- ath9k_hw_gpio_request_in(ah, ah->led_pin, NULL);
- }
ath_prepare_reset(sc);
--
2.56.0