From 0dfab8fbd3223747e2a7948296606e661899630b Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 26 May 2009 00:52:20 +0200 Subject: [PATCH] ath5k: added initial RFKILL support This patch introduces initial RFKILL support for the ath5k driver. All RFKILL related code is separated into newly created rfkill.c. Changes to existing code are minimal: - added a new data structure ath5k_rfkill to the ath5k_softc structure - new function ath5k_eeprom_read_rfkill() for retrieving RFKILL related parameters from EEPROM - inserted calls to init/deinit routines for both the SW and HW part of RFKILL support - ath5k_intr() has been extended to handle AR5K_INT_GPIO interrupts --- drivers/net/wireless/ath/ath5k/Kconfig | 9 + drivers/net/wireless/ath/ath5k/Makefile | 1 + drivers/net/wireless/ath/ath5k/ath5k.h | 22 +++ drivers/net/wireless/ath/ath5k/base.c | 20 ++- drivers/net/wireless/ath/ath5k/base.h | 26 +++ drivers/net/wireless/ath/ath5k/eeprom.c | 16 ++ drivers/net/wireless/ath/ath5k/reset.c | 17 -- drivers/net/wireless/ath/ath5k/rfkill.c | 268 +++++++++++++++++++++++++++++++ 8 files changed, 358 insertions(+), 21 deletions(-) create mode 100644 drivers/net/wireless/ath/ath5k/rfkill.c diff --git a/drivers/net/wireless/ath/ath5k/Kconfig b/drivers/net/wireless/ath/ath5k/Kconfig index 509b6f9..618a879 100644 --- a/drivers/net/wireless/ath/ath5k/Kconfig +++ b/drivers/net/wireless/ath/ath5k/Kconfig @@ -39,3 +39,12 @@ config ATH5K_DEBUG modprobe ath5k debug=0x00000400 +config ATH5K_RFKILL + bool "Atheros 5xxx rfkill support" + select RFKILL + depends on ATH5K + default y + ---help--- + Include support for enabling/disabling WiFi via rfkill switch + with Atheros 5xxx cards + diff --git a/drivers/net/wireless/ath/ath5k/Makefile b/drivers/net/wireless/ath/ath5k/Makefile index 84a74c5..f1e281c 100644 --- a/drivers/net/wireless/ath/ath5k/Makefile +++ b/drivers/net/wireless/ath/ath5k/Makefile @@ -12,4 +12,5 @@ ath5k-y += attach.o ath5k-y += base.o ath5k-y += led.o ath5k-$(CONFIG_ATH5K_DEBUG) += debug.o +ath5k-$(CONFIG_ATH5K_RFKILL) += rfkill.o obj-$(CONFIG_ATH5K) += ath5k.o diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h index 8137182..0dadcbb 100644 --- a/drivers/net/wireless/ath/ath5k/ath5k.h +++ b/drivers/net/wireless/ath/ath5k/ath5k.h @@ -1162,6 +1162,10 @@ extern void ath5k_led_enable(struct ath5k_softc *sc); extern void ath5k_led_off(struct ath5k_softc *sc); extern void ath5k_unregister_leds(struct ath5k_softc *sc); +/* Start/Stop Functions */ +extern int ath5k_init(struct ath5k_softc *sc); +extern int ath5k_stop_hw(struct ath5k_softc *sc); + /* Reset Functions */ extern int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, int flags, bool initial); extern int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, struct ieee80211_channel *channel, bool change_channel); @@ -1190,6 +1194,7 @@ extern void ath5k_hw_update_mib_counters(struct ath5k_hw *ah, struct ieee80211_l extern int ath5k_eeprom_init(struct ath5k_hw *ah); extern void ath5k_eeprom_detach(struct ath5k_hw *ah); extern int ath5k_eeprom_read_mac(struct ath5k_hw *ah, u8 *mac); +extern void ath5k_eeprom_read_rfkill(struct ath5k_hw *ah, u16 *gpio, u8 *polarity); extern bool ath5k_eeprom_is_hb63(struct ath5k_hw *ah); /* Protocol Control Unit Functions */ @@ -1256,6 +1261,23 @@ extern u32 ath5k_hw_get_gpio(struct ath5k_hw *ah, u32 gpio); extern int ath5k_hw_set_gpio(struct ath5k_hw *ah, u32 gpio, u32 val); extern void ath5k_hw_set_gpio_intr(struct ath5k_hw *ah, unsigned int gpio, u32 interrupt_level); +/* rfkill Functions */ +#ifdef CONFIG_ATH5K_RFKILL +extern int ath5k_rfkill_init(struct ath5k_softc *sc); +extern void ath5k_rfkill_deinit(struct ath5k_softc *sc); +extern void ath5k_rfkill_hw_start(struct ath5k_hw *ah); +extern void ath5k_rfkill_hw_stop(struct ath5k_hw *ah); +#else +static inline int ath5k_rfkill_init(struct ath5k_softc *sc) +{ + return 0; +} + +static inline void ath5k_rfkill_deinit(struct ath5k_softc *sc) {} +static inline void ath5k_rfkill_hw_start(struct ath5k_hw *ah) {} +static inline void ath5k_rfkill_hw_stop(struct ath5k_hw *ah) {} +#endif + /* Misc functions */ int ath5k_hw_set_capabilities(struct ath5k_hw *ah); extern int ath5k_hw_get_capability(struct ath5k_hw *ah, enum ath5k_capability_type cap_type, u32 capability, u32 *result); diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c index fb51937..aa9446e 100644 --- a/drivers/net/wireless/ath/ath5k/base.c +++ b/drivers/net/wireless/ath/ath5k/base.c @@ -363,9 +363,7 @@ static inline u64 ath5k_extend_tsf(struct ath5k_hw *ah, u32 rstamp) } /* Interrupt handling */ -static int ath5k_init(struct ath5k_softc *sc); static int ath5k_stop_locked(struct ath5k_softc *sc); -static int ath5k_stop_hw(struct ath5k_softc *sc); static irqreturn_t ath5k_intr(int irq, void *dev_id); static void ath5k_tasklet_reset(unsigned long data); @@ -822,6 +820,9 @@ ath5k_attach(struct pci_dev *pdev, struct ieee80211_hw *hw) ath5k_init_leds(sc); + /* Initialize rfkill subsystem */ + ath5k_rfkill_init(sc); + return 0; err_queues: ath5k_txq_release(sc); @@ -856,6 +857,7 @@ ath5k_detach(struct pci_dev *pdev, struct ieee80211_hw *hw) ath5k_txq_release(sc); ath5k_hw_release_tx_queue(sc->ah, sc->bhalq); ath5k_unregister_leds(sc); + ath5k_rfkill_deinit(sc); /* * NB: can't reclaim these until after ieee80211_ifdetach @@ -2321,7 +2323,7 @@ static void ath5k_tasklet_beacon(unsigned long data) * Interrupt handling * \********************/ -static int +int ath5k_init(struct ath5k_softc *sc) { struct ath5k_hw *ah = sc->ah; @@ -2353,6 +2355,8 @@ ath5k_init(struct ath5k_softc *sc) if (ret) goto done; + ath5k_rfkill_hw_start(ah); + /* * Reset the key cache since some parts do not reset the * contents on initial power up or resume from suspend. @@ -2419,7 +2423,7 @@ ath5k_stop_locked(struct ath5k_softc *sc) * if another thread does a system call and the thread doing the * stop is preempted). */ -static int +int ath5k_stop_hw(struct ath5k_softc *sc) { int ret; @@ -2461,6 +2465,8 @@ ath5k_stop_hw(struct ath5k_softc *sc) tasklet_kill(&sc->restq); tasklet_kill(&sc->beacontq); + ath5k_rfkill_hw_stop(sc->ah); + return ret; } @@ -2519,6 +2525,12 @@ ath5k_intr(int irq, void *dev_id) */ ath5k_hw_update_mib_counters(ah, &sc->ll_stats); } +#ifdef CONFIG_ATH5K_RFKILL + if (status & AR5K_INT_GPIO) + { + tasklet_schedule(&sc->rf_kill.toggleq); + } +#endif } } while (ath5k_hw_is_intr_pending(ah) && --counter > 0); diff --git a/drivers/net/wireless/ath/ath5k/base.h b/drivers/net/wireless/ath/ath5k/base.h index 852b2c1..88c6929 100644 --- a/drivers/net/wireless/ath/ath5k/base.h +++ b/drivers/net/wireless/ath/ath5k/base.h @@ -46,6 +46,7 @@ #include #include #include +#include #include "ath5k.h" #include "debug.h" @@ -91,6 +92,27 @@ struct ath5k_led struct led_classdev led_dev; /* led classdev */ }; +/* Rfkill */ +#define ATH5K_RFKILL_FLAG_HW_STARTED BIT(0) +#define ATH5K_RFKILL_FLAG_SW_BLOCKED BIT(1) +#define ATH5K_RFKILL_FLAG_HW_BLOCKED BIT(2) + +struct ath5k_rfkill { + /* GPIO PIN for rfkill */ + u16 gpio; + /* polarity of rfkill GPIO PIN */ + u8 polarity; + /* The RFKILL subsystem data structure */ + struct rfkill *rfkill; + /* The unique name of this rfkill switch */ + char rfkill_name[32]; + /* Flags indicating SW RFKILL status */ + u32 flags; + /* RFKILL toggle tasklet */ + struct tasklet_struct toggleq; + /* mutex for RFKILL related operations */ + struct mutex lock; +}; #if CHAN_DEBUG #define ATH_CHAN_MAX (26+26+26+200+200) @@ -167,6 +189,10 @@ struct ath5k_softc { struct tasklet_struct txtq; /* tx intr tasklet */ struct ath5k_led tx_led; /* tx led */ +#ifdef CONFIG_ATH5K_RFKILL + struct ath5k_rfkill rf_kill; +#endif + spinlock_t block; /* protects beacon */ struct tasklet_struct beacontq; /* beacon intr tasklet */ struct ath5k_buf *bbuf; /* beacon buffer */ diff --git a/drivers/net/wireless/ath/ath5k/eeprom.c b/drivers/net/wireless/ath/ath5k/eeprom.c index c56b494..ff00502 100644 --- a/drivers/net/wireless/ath/ath5k/eeprom.c +++ b/drivers/net/wireless/ath/ath5k/eeprom.c @@ -1800,3 +1800,19 @@ int ath5k_eeprom_read_mac(struct ath5k_hw *ah, u8 *mac) return 0; } + + +/* + * Read RFKILL GPIO configuration from eeprom + */ +void ath5k_eeprom_read_rfkill(struct ath5k_hw *ah, u16 *gpio, u8 *polarity) +{ + u16 data; + + ath5k_hw_eeprom_read(ah, AR5K_EEPROM_RFKILL_GPIO_SEL, &data); + *gpio = AR5K_REG_MS(data, AR5K_EEPROM_RFKILL_GPIO_SEL); + + ath5k_hw_eeprom_read(ah, AR5K_EEPROM_RFKILL_POLARITY, &data); + *polarity = AR5K_REG_MS(data, AR5K_EEPROM_RFKILL_POLARITY); +} + diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c index bd1940e..8077f0a 100644 --- a/drivers/net/wireless/ath/ath5k/reset.c +++ b/drivers/net/wireless/ath/ath5k/reset.c @@ -1305,23 +1305,6 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, if (ah->ah_version != AR5K_AR5210) ath5k_hw_set_imr(ah, ah->ah_imr); - /* - * Setup RFKill interrupt if rfkill flag is set on eeprom. - * TODO: Use gpio pin and polarity infos from eeprom - * TODO: Handle this in ath5k_intr because it'll result - * a nasty interrupt storm. - */ -#if 0 - if (AR5K_EEPROM_HDR_RFKILL(ah->ah_capabilities.cap_eeprom.ee_header)) { - ath5k_hw_set_gpio_input(ah, 0); - ah->ah_gpio[0] = ath5k_hw_get_gpio(ah, 0); - if (ah->ah_gpio[0] == 0) - ath5k_hw_set_gpio_intr(ah, 0, 1); - else - ath5k_hw_set_gpio_intr(ah, 0, 0); - } -#endif - /* Enable 32KHz clock function for AR5212+ chips * Set clocks to 32KHz operation and use an * external 32KHz crystal when sleeping if one diff --git a/drivers/net/wireless/ath/ath5k/rfkill.c b/drivers/net/wireless/ath/ath5k/rfkill.c new file mode 100644 index 0000000..2ae6961 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/rfkill.c @@ -0,0 +1,268 @@ +/* + * RFKILL support for ath5k + * + * Copyright (c) 2009 Tobias Doerffel + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +#include "base.h" + + +static inline void ath5k_rfkill_disable(struct ath5k_softc *sc) +{ + ATH5K_DBG(sc, ATH5K_DEBUG_ANY, "rfkill disable (gpio:%d polarity:%d)\n", + sc->rf_kill.gpio, sc->rf_kill.polarity); + ath5k_hw_set_gpio_output(sc->ah, sc->rf_kill.gpio); + ath5k_hw_set_gpio(sc->ah, sc->rf_kill.gpio, !sc->rf_kill.polarity); +} + + +static inline void ath5k_rfkill_enable(struct ath5k_softc *sc) +{ + ATH5K_DBG(sc, ATH5K_DEBUG_ANY, "rfkill enable (gpio:%d polarity:%d)\n", + sc->rf_kill.gpio, sc->rf_kill.polarity); + ath5k_hw_set_gpio_output(sc->ah, sc->rf_kill.gpio); + ath5k_hw_set_gpio(sc->ah, sc->rf_kill.gpio, sc->rf_kill.polarity); +} + +static inline void ath5k_rfkill_set_intr(struct ath5k_softc *sc, bool enable) +{ + struct ath5k_hw *ah = sc->ah; + ath5k_hw_set_gpio_input(ah, sc->rf_kill.gpio); + ah->ah_gpio[0] = ath5k_hw_get_gpio(ah, sc->rf_kill.gpio); + ath5k_hw_set_gpio_intr(ah, sc->rf_kill.gpio, enable ? + !!ah->ah_gpio[0] : !ah->ah_gpio[0]); +} + +static void +ath5k_radio_enable(struct ath5k_softc *sc) +{ + ath5k_init(sc); + + ieee80211_wake_queues(sc->hw); +} + + +static void +ath5k_radio_disable(struct ath5k_softc *sc) +{ + ieee80211_stop_queues(sc->hw); + + /* shutdown hardware */ + ath5k_stop_hw(sc); +} + + +static bool +ath5k_is_rfkill_set(struct ath5k_softc *sc) +{ + /* configuring GPIO for input for some reason disables rfkill */ + /*ath5k_hw_set_gpio_input(sc->ah, sc->rf_kill.gpio);*/ + return ath5k_hw_get_gpio(sc->ah, sc->rf_kill.gpio) == + sc->rf_kill.polarity; +} + + +static void +ath5k_tasklet_rfkill_toggle(unsigned long data) +{ + struct ath5k_softc *sc = (void *)data; + bool radio_on; + + mutex_lock(&sc->rf_kill.lock); + + radio_on = !ath5k_is_rfkill_set(sc); + + /* + * enable/disable radio only when there is a + * state change in RF switch + */ + if (radio_on == !!(sc->rf_kill.flags & ATH5K_RFKILL_FLAG_HW_BLOCKED)) { + enum rfkill_state state; + + if (sc->rf_kill.flags & ATH5K_RFKILL_FLAG_SW_BLOCKED) { + state = radio_on ? RFKILL_STATE_SOFT_BLOCKED + : RFKILL_STATE_HARD_BLOCKED; + } else if (radio_on) { + ath5k_radio_enable(sc); + state = RFKILL_STATE_UNBLOCKED; + } else { + ath5k_radio_disable(sc); + state = RFKILL_STATE_HARD_BLOCKED; + } + + if (state == RFKILL_STATE_HARD_BLOCKED) + sc->rf_kill.flags |= ATH5K_RFKILL_FLAG_HW_BLOCKED; + else + sc->rf_kill.flags &= ~ATH5K_RFKILL_FLAG_HW_BLOCKED; + + rfkill_force_state(sc->rf_kill.rfkill, state); + } + mutex_unlock(&sc->rf_kill.lock); + +} + + +/* s/w rfkill handler */ +static int +ath5k_rfkill_soft_toggle(void *data, enum rfkill_state state) +{ + struct ath5k_softc *sc = data; + mutex_lock(&sc->rf_kill.lock); + /* RFKILL framework might call this toggle callback even if HW is + not started (i.e. interface not up) - depending on the current + SW RFKILL state this could cause calling ath5k_radio_disable() + leading to kernel panic due to un-initialized HW state */ + if (!(sc->rf_kill.flags & ATH5K_RFKILL_FLAG_HW_STARTED)) { + mutex_unlock(&sc->rf_kill.lock); + return -EINVAL; + } + + switch (state) { + case RFKILL_STATE_SOFT_BLOCKED: + if (!(sc->rf_kill.flags & (ATH5K_RFKILL_FLAG_HW_BLOCKED | + ATH5K_RFKILL_FLAG_SW_BLOCKED))) + ath5k_radio_disable(sc); + sc->rf_kill.flags |= ATH5K_RFKILL_FLAG_SW_BLOCKED; + mutex_unlock(&sc->rf_kill.lock); + return 0; + case RFKILL_STATE_UNBLOCKED: + if ((sc->rf_kill.flags & ATH5K_RFKILL_FLAG_SW_BLOCKED)) { + sc->rf_kill.flags &= ~ATH5K_RFKILL_FLAG_SW_BLOCKED; + if (sc->rf_kill.flags & ATH5K_RFKILL_FLAG_HW_BLOCKED) { + ATH5K_ERR(sc, "Can't turn on the" + "radio as it is disabled by h/w\n"); + mutex_unlock(&sc->rf_kill.lock); + return -EBUSY; + } + ath5k_radio_enable(sc); + } + mutex_unlock(&sc->rf_kill.lock); + return 0; + default: + break; + } + mutex_unlock(&sc->rf_kill.lock); + return -EINVAL; +} + + +/* Init s/w rfkill */ +int +ath5k_rfkill_init(struct ath5k_softc *sc) +{ + int err; + + mutex_init(&sc->rf_kill.lock); + + sc->rf_kill.rfkill = rfkill_allocate(wiphy_dev(sc->hw->wiphy), + RFKILL_TYPE_WLAN); + if (!sc->rf_kill.rfkill) { + ATH5K_ERR(sc, "Failed to allocate rfkill\n"); + return -ENOMEM; + } + + snprintf(sc->rf_kill.rfkill_name, sizeof(sc->rf_kill.rfkill_name), + "ath5k-%s:rfkill", wiphy_name(sc->hw->wiphy)); + sc->rf_kill.rfkill->name = sc->rf_kill.rfkill_name; + sc->rf_kill.rfkill->data = sc; + sc->rf_kill.rfkill->toggle_radio = ath5k_rfkill_soft_toggle; + sc->rf_kill.rfkill->state = RFKILL_STATE_UNBLOCKED; + + err = rfkill_register(sc->rf_kill.rfkill); + if (err) { + sc->rf_kill.rfkill = NULL; + return -1; + } + + return 0; +} + + +/* Deinitialize s/w rfkill */ +void +ath5k_rfkill_deinit(struct ath5k_softc *sc) +{ + if (sc->rf_kill.rfkill != NULL) { + rfkill_unregister(sc->rf_kill.rfkill); + sc->rf_kill.rfkill = NULL; + } +} + + +void +ath5k_rfkill_hw_start(struct ath5k_hw *ah) +{ + struct ath5k_softc *sc = ah->ah_sc; + + if ((sc->rf_kill.flags & ATH5K_RFKILL_FLAG_HW_STARTED) || + (sc->rf_kill.flags & ATH5K_RFKILL_FLAG_SW_BLOCKED)) + return; + + /* read rfkill GPIO configuration from EEPROM */ + ath5k_eeprom_read_rfkill(ah, &sc->rf_kill.gpio, &sc->rf_kill.polarity); + + tasklet_init(&sc->rf_kill.toggleq, ath5k_tasklet_rfkill_toggle, + (unsigned long)sc); + + if (!(sc->rf_kill.flags & ATH5K_RFKILL_FLAG_HW_BLOCKED)) + ath5k_rfkill_disable(sc); + + /* enable interrupt for rfkill switch */ + if (AR5K_EEPROM_HDR_RFKILL(ah->ah_capabilities.cap_eeprom.ee_header)) { + ath5k_rfkill_set_intr(sc, true); + } + + sc->rf_kill.flags |= ATH5K_RFKILL_FLAG_HW_STARTED; +} + + +void +ath5k_rfkill_hw_stop(struct ath5k_hw *ah) +{ + struct ath5k_softc *sc = ah->ah_sc; + + if (sc->rf_kill.flags & ATH5K_RFKILL_FLAG_HW_STARTED) { + /* disable interrupt for rfkill switch */ + if (AR5K_EEPROM_HDR_RFKILL( + ah->ah_capabilities.cap_eeprom.ee_header)) { + ath5k_rfkill_set_intr(sc, false); + } + + tasklet_kill(&sc->rf_kill.toggleq); + + /* enable RFKILL when stopping HW so Wifi LED is turned off */ + ath5k_rfkill_enable(sc); + + sc->rf_kill.flags &= ~ATH5K_RFKILL_FLAG_HW_STARTED; + } +} + -- 1.6.3.1