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

Reply via email to