aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormerge <null@invalid>2009-01-16 11:23:28 +0000
committerAndy Green <agreen@pads.home.warmcat.com>2009-01-16 11:23:28 +0000
commit4ff379a06a70e17997e196c9c393bc7c8648e42a (patch)
treed1b0ef0ecc612e860fe59bedf0f33bd06eae409b
parent6fde2330ccdf7a6817b664702c4c5e4c7e61a054 (diff)
MERGE-via-pending-tracking-hist-MERGE-via-stable-tracking-rfkill-support-for-the-ar6000-1232104308
pending-tracking-hist top was MERGE-via-stable-tracking-rfkill-support-for-the-ar6000-1232104308 / 09faeb511e9e6872c79b4d1ea2f3b5fefc16f3a1 ... parent commitmessage: From: merge <null@invalid> MERGE-via-stable-tracking-hist-rfkill-support-for-the-ar6000- stable-tracking-hist top was rfkill-support-for-the-ar6000- / e82f8ffa04ebcb05506c8e22268371c3c3b03173 ... parent commitmessage: From: Werner Almesberger <werner@openmoko.org> rfkill support for the AR6000 driver This patch adds rfkill support to the AR6000 driver. The driver does not directly implement an rfkill device but uses the help of a special platform device, such that the latter can retain rfkill state when the AR6k driver is removed (e.g., because of suspend). If an attempt is made to bring the driver up (module load, bind, or resume) while rfkill is blocking, only the driver's data structures are initialized, but function activation and most of the rest of the setup is deferred until the rfkill block is removed. Signed-off-by: Werner Almesberger <werner@openmoko.org>
-rw-r--r--arch/arm/mach-s3c2410/include/mach/gta02-pm-wlan.h9
-rw-r--r--arch/arm/plat-s3c24xx/gta02_pm_wlan.c126
-rw-r--r--drivers/ar6000/hif/hif2.c144
3 files changed, 254 insertions, 25 deletions
diff --git a/arch/arm/mach-s3c2410/include/mach/gta02-pm-wlan.h b/arch/arm/mach-s3c2410/include/mach/gta02-pm-wlan.h
index 8e4ccf241fc..5a224fe036b 100644
--- a/arch/arm/mach-s3c2410/include/mach/gta02-pm-wlan.h
+++ b/arch/arm/mach-s3c2410/include/mach/gta02-pm-wlan.h
@@ -1 +1,10 @@
+#ifndef __MACH_GTA02_PM_WLAN_H
+#define __MACH_GTA02_PM_WLAN_H
+
void gta02_wlan_power(int on);
+int gta02_wlan_query_rfkill_lock(void);
+void gta02_wlan_query_rfkill_unlock(void);
+void gta02_wlan_set_rfkill_cb(int (*cb)(void *user, int on), void *user);
+void gta02_wlan_clear_rfkill_cb(void);
+
+#endif /* __MACH_GTA02_PM_WLAN_H */
diff --git a/arch/arm/plat-s3c24xx/gta02_pm_wlan.c b/arch/arm/plat-s3c24xx/gta02_pm_wlan.c
index 3a0d24079da..419b3f77403 100644
--- a/arch/arm/plat-s3c24xx/gta02_pm_wlan.c
+++ b/arch/arm/plat-s3c24xx/gta02_pm_wlan.c
@@ -1,7 +1,7 @@
/*
* GTA02 WLAN power management
*
- * (C) 2008 by Openmoko Inc.
+ * (C) 2008, 2009 by Openmoko Inc.
* Author: Andy Green <andy@openmoko.com>
* All rights reserved.
*
@@ -27,6 +27,10 @@
#include <mach/regs-gpioj.h>
#include <linux/delay.h>
+#include <linux/rfkill.h>
+
+
+/* ----- Module hardware reset ("power") ----------------------------------- */
static void __gta02_wlan_power(int on)
@@ -109,10 +113,94 @@ static struct attribute_group gta02_wlan_attr_group = {
.attrs = gta02_wlan_sysfs_entries,
};
+
+/* ----- rfkill ------------------------------------------------------------ */
+
+/*
+ * S3C MCI handles suspend/resume through device removal/insertion. In order to
+ * preserve rfkill state, as required in clause 7 of section 3.1 in rfkill.txt,
+ * we therefore need to maintain rfkill state outside the driver.
+ *
+ * This platform driver is as good a place as any other.
+ */
+
+static int (*gta02_wlan_rfkill_cb)(void *user, int on);
+static void *gta02_wlan_rfkill_user;
+static DEFINE_MUTEX(gta02_wlan_rfkill_lock);
+static int gta02_wlan_rfkill_on;
+
+
+/*
+ * gta02_wlan_query_rfkill_lock is used to obtain the rfkill state before the
+ * driver is ready to process rfkill callbacks. To prevent the state from
+ * changing until the driver has completed its initialization, we grab and hold
+ * the rfkill lock.
+ *
+ * A call to gta02_wlan_query_rfkill_lock must be followed by either
+ * - a call to gta02_wlan_set_rfkill_cb, to complete the setup, or
+ * - a call to gta02_wlan_query_rfkill_unlock to abort the setup process.
+ */
+
+int gta02_wlan_query_rfkill_lock(void)
+{
+ mutex_lock(&gta02_wlan_rfkill_lock);
+ return gta02_wlan_rfkill_on;
+}
+EXPORT_SYMBOL_GPL(gta02_wlan_query_rfkill_lock);
+
+void gta02_wlan_query_rfkill_unlock(void)
+{
+ mutex_unlock(&gta02_wlan_rfkill_lock);
+}
+EXPORT_SYMBOL_GPL(gta02_wlan_query_rfkill_unlock);
+
+
+void gta02_wlan_set_rfkill_cb(int (*cb)(void *user, int on), void *user)
+{
+ BUG_ON(!mutex_is_locked(&gta02_wlan_rfkill_lock));
+ BUG_ON(gta02_wlan_rfkill_cb);
+ gta02_wlan_rfkill_cb = cb;
+ gta02_wlan_rfkill_user = user;
+ mutex_unlock(&gta02_wlan_rfkill_lock);
+}
+EXPORT_SYMBOL_GPL(gta02_wlan_set_rfkill_cb);
+
+void gta02_wlan_clear_rfkill_cb(void)
+{
+ mutex_lock(&gta02_wlan_rfkill_lock);
+ BUG_ON(!gta02_wlan_rfkill_cb);
+ gta02_wlan_rfkill_cb = NULL;
+ mutex_unlock(&gta02_wlan_rfkill_lock);
+}
+EXPORT_SYMBOL_GPL(gta02_wlan_clear_rfkill_cb);
+
+static int gta02_wlan_toggle_radio(void *data, enum rfkill_state state)
+{
+ struct device *dev = data;
+ int on = state == RFKILL_STATE_UNBLOCKED;
+ int res = 0;
+
+ dev_dbg(dev, "gta02_wlan_toggle_radio: state %d (%p)\n",
+ state, gta02_wlan_rfkill_cb);
+ mutex_lock(&gta02_wlan_rfkill_lock);
+ if (gta02_wlan_rfkill_cb)
+ res = gta02_wlan_rfkill_cb(gta02_wlan_rfkill_user, on);
+ if (!res)
+ gta02_wlan_rfkill_on = on;
+ mutex_unlock(&gta02_wlan_rfkill_lock);
+ return res;
+}
+
+
+/* ----- Initialization/removal -------------------------------------------- */
+
+
static int __init gta02_wlan_probe(struct platform_device *pdev)
{
/* default-on for now */
const int default_state = 1;
+ struct rfkill *rfkill;
+ int error;
if (!machine_is_neo1973_gta02())
return -EINVAL;
@@ -121,13 +209,45 @@ static int __init gta02_wlan_probe(struct platform_device *pdev)
s3c2410_gpio_cfgpin(GTA02_CHIP_PWD, S3C2410_GPIO_OUTPUT);
s3c2410_gpio_cfgpin(GTA02_GPIO_nWLAN_RESET, S3C2410_GPIO_OUTPUT);
- gta02_wlan_power(default_state);
+ gta02_wlan_power(1);
+
+ rfkill = rfkill_allocate(&pdev->dev, RFKILL_TYPE_WLAN);
+ rfkill->name = "ar6000";
+ rfkill->data = &pdev->dev;
+ rfkill->state = default_state ? RFKILL_STATE_ON : RFKILL_STATE_OFF;
+ /*
+ * If the WLAN driver somehow managed to get activated before we're
+ * ready, the driver is now in an unknown state, which isn't something
+ * we're prepared to handle. This can't happen, so just fail hard.
+ */
+ BUG_ON(gta02_wlan_rfkill_cb);
+ gta02_wlan_rfkill_on = default_state;
+ rfkill->toggle_radio = gta02_wlan_toggle_radio;
+
+ error = rfkill_register(rfkill);
+ if (error) {
+ rfkill_free(rfkill);
+ return error;
+ }
+
+ error = sysfs_create_group(&pdev->dev.kobj, &gta02_wlan_attr_group);
+ if (error) {
+ rfkill_free(rfkill);
+ return error;
+ }
+
+ dev_set_drvdata(&pdev->dev, rfkill);
- return sysfs_create_group(&pdev->dev.kobj, &gta02_wlan_attr_group);
+ return 0;
}
static int gta02_wlan_remove(struct platform_device *pdev)
{
+ struct rfkill *rfkill = dev_get_drvdata(&pdev->dev);
+
+ rfkill_unregister(rfkill);
+ rfkill_free(rfkill);
+
sysfs_remove_group(&pdev->dev.kobj, &gta02_wlan_attr_group);
return 0;
diff --git a/drivers/ar6000/hif/hif2.c b/drivers/ar6000/hif/hif2.c
index 2b1c5942003..571833f2c79 100644
--- a/drivers/ar6000/hif/hif2.c
+++ b/drivers/ar6000/hif/hif2.c
@@ -1,7 +1,7 @@
/*
* hif2.c - HIF layer re-implementation for the Linux SDIO stack
*
- * Copyright (C) 2008 by OpenMoko, Inc.
+ * Copyright (C) 2008, 2009 by OpenMoko, Inc.
* Written by Werner Almesberger <werner@openmoko.org>
* All Rights Reserved
*
@@ -21,11 +21,13 @@
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
+#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sdio_ids.h>
#include <asm/gpio.h>
+#include <mach/gta02-pm-wlan.h>
#include "athdefs.h"
#include "a_types.h"
@@ -91,6 +93,17 @@ struct hif_device {
spinlock_t queue_lock;
struct task_struct *io_task;
wait_queue_head_t wait;
+
+ /*
+ * activate_lock protects "active" and the activation/deactivation
+ * process itself.
+ *
+ * Relation to other locks: The SDIO function can be claimed while
+ * activate_lock is being held, but trying to acquire activate_lock
+ * while having ownership of the SDIO function could cause a deadlock.
+ */
+ int active;
+ struct mutex activate_lock;
};
struct hif_request {
@@ -454,25 +467,17 @@ A_STATUS HIFConfigureDevice(HIF_DEVICE *hif,
/* ----- Device probe and removal (Linux side) ----------------------------- */
-static int sdio_ar6000_probe(struct sdio_func *func,
- const struct sdio_device_id *id)
+static int ar6000_do_activate(struct hif_device *hif)
{
+ struct sdio_func *func = hif->func;
struct device *dev = &func->dev;
- struct hif_device *hif;
int ret;
- dev_dbg(dev, "sdio_ar6000_probe\n");
- BUG_ON(!htcCallbacks.deviceInsertedHandler);
+ dev_dbg(dev, "ar6000_do_activate\n");
- hif = kzalloc(sizeof(*hif), GFP_KERNEL);
- if (!hif)
- return -ENOMEM;
-
- sdio_set_drvdata(func, hif);
sdio_claim_host(func);
sdio_enable_func(func);
- hif->func = func;
INIT_LIST_HEAD(&hif->queue);
init_waitqueue_head(&hif->wait);
spin_lock_init(&hif->queue_lock);
@@ -525,7 +530,6 @@ out_got_irq:
sdio_release_irq(func);
out_enabled:
- sdio_set_drvdata(func, NULL);
sdio_disable_func(func);
sdio_release_host(func);
@@ -533,13 +537,16 @@ out_enabled:
}
-static void sdio_ar6000_remove(struct sdio_func *func)
+static void ar6000_do_deactivate(struct hif_device *hif)
{
+ struct sdio_func *func = hif->func;
struct device *dev = &func->dev;
- HIF_DEVICE *hif = sdio_get_drvdata(func);
int ret;
- dev_dbg(dev, "sdio_ar6000_remove\n");
+ dev_dbg(dev, "ar6000_do_deactivate\n");
+ if (!hif->active)
+ return;
+
if (mutex_trylock(&shutdown_lock)) {
/*
* Funny, Atheros' HIF does this call, but this just puts us in
@@ -560,9 +567,93 @@ static void sdio_ar6000_remove(struct sdio_func *func)
dev_err(dev, "kthread_stop (ar6000_io): %d\n", ret);
sdio_claim_host(func);
sdio_release_irq(func);
- sdio_set_drvdata(func, NULL);
sdio_disable_func(func);
sdio_release_host(func);
+}
+
+
+static int ar6000_activate(struct hif_device *hif)
+{
+ int ret = 0;
+
+ dev_dbg(&hif->func->dev, "ar6000_activate\n");
+ mutex_lock(&hif->activate_lock);
+ if (!hif->active) {
+ ret = ar6000_do_activate(hif);
+ hif->active = 1;
+ }
+ mutex_unlock(&hif->activate_lock);
+ return ret;
+}
+
+
+static void ar6000_deactivate(struct hif_device *hif)
+{
+ dev_dbg(&hif->func->dev, "ar6000_deactivate\n");
+ mutex_lock(&hif->activate_lock);
+ if (hif->active) {
+ ar6000_do_deactivate(hif);
+ hif->active = 0;
+ }
+ mutex_unlock(&hif->activate_lock);
+}
+
+
+static int ar6000_rfkill_cb(void *data, int on)
+{
+ struct hif_device *hif = data;
+ struct sdio_func *func = hif->func;
+ struct device *dev = &func->dev;
+
+ dev_dbg(dev, "ar6000_rfkill_cb: on %d\n", on);
+ if (on)
+ return ar6000_activate(hif);
+ ar6000_deactivate(hif);
+ return 0;
+}
+
+
+static int sdio_ar6000_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ struct device *dev = &func->dev;
+ struct hif_device *hif;
+ int ret = 0;
+
+ dev_dbg(dev, "sdio_ar6000_probe\n");
+ BUG_ON(!htcCallbacks.deviceInsertedHandler);
+
+ hif = kzalloc(sizeof(*hif), GFP_KERNEL);
+ if (!hif)
+ return -ENOMEM;
+
+ sdio_set_drvdata(func, hif);
+ hif->func = func;
+ mutex_init(&hif->activate_lock);
+ hif->active = 0;
+
+ if (gta02_wlan_query_rfkill_lock())
+ ret = ar6000_activate(hif);
+ if (!ret) {
+ gta02_wlan_set_rfkill_cb(ar6000_rfkill_cb, hif);
+ return 0;
+ }
+ gta02_wlan_query_rfkill_unlock();
+ sdio_set_drvdata(func, NULL);
+ kfree(hif);
+ return ret;
+}
+
+
+static void sdio_ar6000_remove(struct sdio_func *func)
+{
+ struct device *dev = &func->dev;
+ HIF_DEVICE *hif = sdio_get_drvdata(func);
+
+ dev_dbg(dev, "sdio_ar6000_remove\n");
+ gta02_wlan_clear_rfkill_cb();
+ ar6000_deactivate(hif);
+ sdio_set_drvdata(func, NULL);
kfree(hif);
}
@@ -615,12 +706,13 @@ int HIFInit(HTC_CALLBACKS *callbacks)
/*
- * We have three possible call chains here:
+ * We have four possible call chains here:
*
* System shutdown/reboot:
*
* kernel_restart_prepare ...> device_shutdown ... > s3cmci_shutdown ->
* mmc_remove_host ..> sdio_bus_remove -> sdio_ar6000_remove ->
+ * ar6000_deactivate -> ar6000_do_deactivate ->
* deviceRemovedHandler (HTCTargetRemovedHandler) -> HIFShutDownDevice
*
* This is roughly the same sequence as suspend, described below.
@@ -629,23 +721,31 @@ int HIFInit(HTC_CALLBACKS *callbacks)
*
* sys_delete_module -> ar6000_cleanup_module -> HTCShutDown ->
* HIFShutDownDevice -> sdio_unregister_driver ...> sdio_bus_remove ->
- * sdio_ar6000_remove
+ * sdio_ar6000_remove -> ar6000_deactivate -> ar6000_do_deactivate
*
* In this case, HIFShutDownDevice must call sdio_unregister_driver to
- * notify the driver about its removal. sdio_ar6000_remove must not call
+ * notify the driver about its removal. ar6000_do_deactivate must not call
* deviceRemovedHandler, because that would loop back into HIFShutDownDevice.
*
* Suspend:
*
* device_suspend ...> s3cmci_suspend ...> sdio_bus_remove ->
- * sdio_ar6000_remove -> deviceRemovedHandler (HTCTargetRemovedHandler) ->
- * HIFShutDownDevice
+ * sdio_ar6000_remove -> ar6000_deactivate -> ar6000_do_deactivate ->
+ * deviceRemovedHandler (HTCTargetRemovedHandler) -> HIFShutDownDevice
*
* We must call deviceRemovedHandler to inform the ar6k stack that the device
* has been removed. Since HTCTargetRemovedHandler calls back into
* HIFShutDownDevice, we must also prevent the call to
* sdio_unregister_driver, or we'd end up recursing into the SDIO stack,
* eventually deadlocking somewhere.
+ *
+ * rfkill:
+ *
+ * rfkill_state_store -> rfkill_toggle_radio -> gta02_wlan_toggle_radio ->
+ * ar6000_rfkill_cb -> ar6000_deactivate -> ar6000_do_deactivate ->
+ * deviceRemovedHandler (HTCTargetRemovedHandler) -> HIFShutDownDevice
+ *
+ * This is similar to suspend - only the entry point changes.
*/
void HIFShutDownDevice(HIF_DEVICE *hif)