Signed-off-by: Tobias Doerffel <tobias.doerf...@gmail.com> --- drivers/net/wireless/ath5k/Kconfig | 11 ++ drivers/net/wireless/ath5k/Makefile | 1 + drivers/net/wireless/ath5k/ath5k.h | 15 +++ drivers/net/wireless/ath5k/base.c | 34 ++++-- drivers/net/wireless/ath5k/base.h | 19 +++ drivers/net/wireless/ath5k/reset.c | 2 + drivers/net/wireless/ath5k/rfkill.c | 238 +++++++++++++++++++++++++++++++++++ 7 files changed, 309 insertions(+), 11 deletions(-) create mode 100644 drivers/net/wireless/ath5k/rfkill.c
diff --git a/drivers/net/wireless/ath5k/Kconfig b/drivers/net/wireless/ath5k/Kconfig index 75383a5..98b76bc 100644 --- a/drivers/net/wireless/ath5k/Kconfig +++ b/drivers/net/wireless/ath5k/Kconfig @@ -38,3 +38,14 @@ config ATH5K_DEBUG modprobe ath5k debug=0x00000400 +config ATH5K_RFKILL + bool "Atheros 5xxx rfkill support" + select RFKILL + select RFKILL_INPUT + select INPUT_POLLDEV + 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/ath5k/Makefile b/drivers/net/wireless/ath5k/Makefile index 719cfae..e1a469d 100644 --- a/drivers/net/wireless/ath5k/Makefile +++ b/drivers/net/wireless/ath5k/Makefile @@ -11,4 +11,5 @@ ath5k-y += reset.o ath5k-y += attach.o ath5k-y += base.o ath5k-$(CONFIG_ATH5K_DEBUG) += debug.o +ath5k-$(CONFIG_ATH5K_RFKILL) += rfkill.o obj-$(CONFIG_ATH5K) += ath5k.o diff --git a/drivers/net/wireless/ath5k/ath5k.h b/drivers/net/wireless/ath5k/ath5k.h index 183ffc8..bd0bbf6 100644 --- a/drivers/net/wireless/ath5k/ath5k.h +++ b/drivers/net/wireless/ath5k/ath5k.h @@ -1159,6 +1159,10 @@ struct ath5k_hw { extern struct ath5k_hw *ath5k_hw_attach(struct ath5k_softc *sc, u8 mac_version); extern void ath5k_hw_detach(struct ath5k_hw *ah); +/* Start/Stop Functions */ +extern int ath5k_init(struct ath5k_softc *sc, bool is_resume); +extern int ath5k_stop_hw(struct ath5k_softc *sc, bool is_suspend); + /* 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); @@ -1250,6 +1254,17 @@ 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); +/* LED Functions */ +extern void ath5k_led_enable(struct ath5k_softc *sc); +extern void ath5k_led_on(struct ath5k_softc *sc); +extern void ath5k_led_off(struct ath5k_softc *sc); + +#ifdef CONFIG_ATH5K_RFKILL +/* rfkill Functions */ +extern int ath5k_rfkill_init(struct ath5k_softc *sc); +extern void ath5k_rfkill_deinit(struct ath5k_softc *sc); +#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/ath5k/base.c b/drivers/net/wireless/ath5k/base.c index 0b56e0e..3ee48e4 100644 --- a/drivers/net/wireless/ath5k/base.c +++ b/drivers/net/wireless/ath5k/base.c @@ -347,17 +347,13 @@ static inline u64 ath5k_extend_tsf(struct ath5k_hw *ah, u32 rstamp) } /* Interrupt handling */ -static int ath5k_init(struct ath5k_softc *sc, bool is_resume); static int ath5k_stop_locked(struct ath5k_softc *sc); -static int ath5k_stop_hw(struct ath5k_softc *sc, bool is_suspend); static irqreturn_t ath5k_intr(int irq, void *dev_id); static void ath5k_tasklet_reset(unsigned long data); static void ath5k_calibrate(unsigned long data); /* LED functions */ static int ath5k_init_leds(struct ath5k_softc *sc); -static void ath5k_led_enable(struct ath5k_softc *sc); -static void ath5k_led_off(struct ath5k_softc *sc); static void ath5k_unregister_leds(struct ath5k_softc *sc); /* @@ -2204,7 +2200,7 @@ ath5k_beacon_config(struct ath5k_softc *sc) * Interrupt handling * \********************/ -static int +int ath5k_init(struct ath5k_softc *sc, bool is_resume) { struct ath5k_hw *ah = sc->ah; @@ -2310,7 +2306,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, bool is_suspend) { int ret; @@ -2496,7 +2492,7 @@ ath5k_calibrate(unsigned long data) * LED functions * \***************/ -static void +void ath5k_led_enable(struct ath5k_softc *sc) { if (test_bit(ATH_STAT_LEDSOFT, sc->status)) { @@ -2505,7 +2501,7 @@ ath5k_led_enable(struct ath5k_softc *sc) } } -static void +void ath5k_led_on(struct ath5k_softc *sc) { if (!test_bit(ATH_STAT_LEDSOFT, sc->status)) @@ -2513,7 +2509,7 @@ ath5k_led_on(struct ath5k_softc *sc) ath5k_hw_set_gpio(sc->ah, sc->led_pin, sc->led_on); } -static void +void ath5k_led_off(struct ath5k_softc *sc) { if (!test_bit(ATH_STAT_LEDSOFT, sc->status)) @@ -2771,12 +2767,28 @@ ath5k_reset_wake(struct ath5k_softc *sc) static int ath5k_start(struct ieee80211_hw *hw) { - return ath5k_init(hw->priv, false); + struct ath5k_softc *sc = hw->priv; + int err; + + err = ath5k_init(sc, false); + if (err) + return err; + +#ifdef CONFIG_ATH5K_RFKILL + /* Initialize rfkill subsystem */ + err = ath5k_rfkill_init(sc); +#endif + return err; + } static void ath5k_stop(struct ieee80211_hw *hw) { - ath5k_stop_hw(hw->priv, false); + struct ath5k_softc *sc = hw->priv; +#ifdef CONFIG_ATH5K_RFKILL + ath5k_rfkill_deinit(sc); +#endif + ath5k_stop_hw(sc, false); } static int ath5k_add_interface(struct ieee80211_hw *hw, diff --git a/drivers/net/wireless/ath5k/base.h b/drivers/net/wireless/ath5k/base.h index 47af801..2359cc2 100644 --- a/drivers/net/wireless/ath5k/base.h +++ b/drivers/net/wireless/ath5k/base.h @@ -46,6 +46,7 @@ #include <linux/wireless.h> #include <linux/if_ether.h> #include <linux/leds.h> +#include <linux/rfkill.h> #include "ath5k.h" #include "debug.h" @@ -92,6 +93,22 @@ struct ath5k_led struct led_classdev led_dev; /* led classdev */ }; +/* Rfkill */ +#define ATH5K_RFKILL_POLL_INTERVAL 2000 /* msecs */ +#define ATH_RFKILL_FLAG_REGISTERED BIT(0) +#define ATH_RFKILL_FLAG_SW_BLOCKED BIT(1) +#define ATH_RFKILL_FLAG_HW_BLOCKED BIT(2) + +struct ath5k_rfkill { + /* The RFKILL subsystem data structure */ + struct rfkill *rfkill; + /* The poll device for the RFKILL input button */ + struct input_polled_dev *poll_dev; + /* The unique name of this rfkill switch */ + char rfkill_name[32]; + /* Flags indicating SW RFKILL status */ + u32 flags; +}; #if CHAN_DEBUG #define ATH_CHAN_MAX (26+26+26+200+200) @@ -171,6 +188,8 @@ struct ath5k_softc { struct tasklet_struct txtq; /* tx intr tasklet */ struct ath5k_led tx_led; /* tx led */ + struct ath5k_rfkill rf_kill; + spinlock_t block; /* protects beacon */ struct ath5k_buf *bbuf; /* beacon buffer */ unsigned int bhalq, /* SW q for outgoing beacons */ diff --git a/drivers/net/wireless/ath5k/reset.c b/drivers/net/wireless/ath5k/reset.c index dc2d7d8..856f496 100644 --- a/drivers/net/wireless/ath5k/reset.c +++ b/drivers/net/wireless/ath5k/reset.c @@ -864,6 +864,7 @@ 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); +#ifdef CONFIG_ATH5K_RFKILL /* * Set RF kill flags if supported by the device (read from the EEPROM) * Disable gpio_intr for now since it results system hang. @@ -879,6 +880,7 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, ath5k_hw_set_gpio_intr(ah, 0, 0); } #endif +#endif /* * Set the 32MHz reference clock on 5212 phy clock sleep register diff --git a/drivers/net/wireless/ath5k/rfkill.c b/drivers/net/wireless/ath5k/rfkill.c new file mode 100644 index 0000000..b9b132b --- /dev/null +++ b/drivers/net/wireless/ath5k/rfkill.c @@ -0,0 +1,238 @@ +/* + * RFKILL support + * + * Copyright (c) 2009 Tobias Doerffel <tobias.doerf...@gmail.com> + * + * 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 <linux/pci.h> +#include <linux/input-polldev.h> + +#include "base.h" + + +static void +ath5k_radio_enable(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + int ret; + + ret = ath5k_hw_reset(ah, sc->opmode, sc->curchan, false); + if (ret) { + ATH5K_ERR(sc, "Unable to reset hardware (%d)\n", ret); + return; + } + + ath5k_init(sc, true); + + ath5k_led_enable(sc); + ath5k_led_on(sc); + + ieee80211_wake_queues(sc->hw); +} + +static void +ath5k_radio_disable(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + + ieee80211_stop_queues(sc->hw); + + /* Disable LED */ + ath5k_led_off(sc); + + /* shutdown hardware */ + ath5k_stop_hw(sc, true); + + ath5k_hw_set_power(ah, AR5K_PM_FULL_SLEEP, true, 0); +} + +static bool +ath5k_is_rfkill_set(struct ath5k_softc *sc) +{ + /* TODO */ + return false; +} + +/* h/w rfkill poll function */ +static void +ath5k_rfkill_poll(struct input_polled_dev *poll_dev) +{ + struct ath5k_softc *sc = poll_dev->private; + bool radio_on; + + 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 & ATH_RFKILL_FLAG_HW_BLOCKED)) { + enum rfkill_state state; + + if (sc->rf_kill.flags & ATH_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 |= ATH_RFKILL_FLAG_HW_BLOCKED; + else + sc->rf_kill.flags &= ~ATH_RFKILL_FLAG_HW_BLOCKED; + + rfkill_force_state(sc->rf_kill.rfkill, state); + input_report_key(poll_dev->input, KEY_WLAN, 1); + input_report_key(poll_dev->input, KEY_WLAN, 0); + } +} + +/* s/w rfkill handler */ +static int +ath5k_rfkill_soft_toggle(void *data, enum rfkill_state state) +{ + struct ath5k_softc *sc = data; + switch (state) { + case RFKILL_STATE_SOFT_BLOCKED: + if (!(sc->rf_kill.flags & (ATH_RFKILL_FLAG_HW_BLOCKED | + ATH_RFKILL_FLAG_SW_BLOCKED))) + ath5k_radio_disable(sc); + sc->rf_kill.flags |= ATH_RFKILL_FLAG_SW_BLOCKED; + return 0; + case RFKILL_STATE_UNBLOCKED: + if ((sc->rf_kill.flags & ATH_RFKILL_FLAG_SW_BLOCKED)) { + sc->rf_kill.flags &= ~ATH_RFKILL_FLAG_SW_BLOCKED; + if (sc->rf_kill.flags & ATH_RFKILL_FLAG_HW_BLOCKED) { + ATH5K_ERR(sc, "Can't turn on the" + "radio as it is disabled by h/w\n"); + return -EBUSY; + } + ath5k_radio_enable(sc); + } + return 0; + default: + return -EINVAL; + } +} + +/* Init s/w rfkill */ +int +ath5k_rfkill_init(struct ath5k_softc *sc) +{ + int err; + + 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; + sc->rf_kill.rfkill->user_claim_unsupported = 1; + + sc->rf_kill.poll_dev = input_allocate_polled_device(); + if (!sc->rf_kill.poll_dev) { + rfkill_free(sc->rf_kill.rfkill); + goto err_free_rfk; + } + + sc->rf_kill.poll_dev->private = sc; + sc->rf_kill.poll_dev->poll = ath5k_rfkill_poll; + sc->rf_kill.poll_dev->poll_interval = ATH5K_RFKILL_POLL_INTERVAL; + + sc->rf_kill.poll_dev->input->name = sc->rf_kill.rfkill->name; + sc->rf_kill.poll_dev->input->id.bustype = BUS_HOST; + sc->rf_kill.poll_dev->input->id.vendor = sc->pdev->vendor; + sc->rf_kill.poll_dev->input->evbit[0] = BIT(EV_KEY); + set_bit(KEY_WLAN, sc->rf_kill.poll_dev->input->keybit); + + + err = rfkill_register(sc->rf_kill.rfkill); + if (err) + goto err_free_polldev; + +#ifdef CONFIG_RFKILL_INPUT_MODULE + /* RF-kill isn't useful without the rfkill-input subsystem. + * Try to load the module. */ + err = request_module("rfkill-input"); + if (err) + ATH5K_ERR(sc, "Failed to load the rfkill-input module. " + "The built-in radio LED will not work.\n"); +#endif /* CONFIG_RFKILL_INPUT */ + + err = input_register_polled_device(sc->rf_kill.poll_dev); + if (err) + goto err_unreg_rfk; + + sc->rf_kill.flags |= ATH_RFKILL_FLAG_REGISTERED; + + return 0; + +err_unreg_rfk: + rfkill_unregister(sc->rf_kill.rfkill); + +err_free_polldev: + input_free_polled_device(sc->rf_kill.poll_dev); + sc->rf_kill.poll_dev = NULL; + +err_free_rfk: + sc->rf_kill.rfkill = NULL; + sc->rf_kill.poll_dev = NULL; + + return 0; +} + +/* Deinitialize rfkill */ +void +ath5k_rfkill_deinit(struct ath5k_softc *sc) +{ + if (sc->rf_kill.flags & ATH_RFKILL_FLAG_REGISTERED) { + input_unregister_polled_device(sc->rf_kill.poll_dev); + rfkill_unregister(sc->rf_kill.rfkill); + input_free_polled_device(sc->rf_kill.poll_dev); + sc->rf_kill.rfkill = NULL; + sc->rf_kill.poll_dev = NULL; + sc->rf_kill.flags &= ~ATH_RFKILL_FLAG_REGISTERED; + } +} + + -- 1.6.0.4 _______________________________________________ ath5k-devel mailing list ath5k-devel@lists.ath5k.org https://lists.ath5k.org/mailman/listinfo/ath5k-devel