aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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)