aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/plat-s3c24xx
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 /arch/arm/plat-s3c24xx
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>
Diffstat (limited to 'arch/arm/plat-s3c24xx')
-rw-r--r--arch/arm/plat-s3c24xx/gta02_pm_wlan.c126
1 files changed, 123 insertions, 3 deletions
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;