aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/s3c24xx/files-2.6.30/drivers/ar6000/hif/hif2.c
diff options
context:
space:
mode:
authorblogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73>2012-10-05 10:12:53 +0000
committerblogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73>2012-10-05 10:12:53 +0000
commit5c105d9f3fd086aff195d3849dcf847d6b0bd927 (patch)
tree1229a11f725bfa58aa7c57a76898553bb5f6654a /target/linux/s3c24xx/files-2.6.30/drivers/ar6000/hif/hif2.c
downloadopenwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.tar.gz
openwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.zip
branch Attitude Adjustment
git-svn-id: svn://svn.openwrt.org/openwrt/branches/attitude_adjustment@33625 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/s3c24xx/files-2.6.30/drivers/ar6000/hif/hif2.c')
-rw-r--r--target/linux/s3c24xx/files-2.6.30/drivers/ar6000/hif/hif2.c768
1 files changed, 768 insertions, 0 deletions
diff --git a/target/linux/s3c24xx/files-2.6.30/drivers/ar6000/hif/hif2.c b/target/linux/s3c24xx/files-2.6.30/drivers/ar6000/hif/hif2.c
new file mode 100644
index 000000000..386d96e66
--- /dev/null
+++ b/target/linux/s3c24xx/files-2.6.30/drivers/ar6000/hif/hif2.c
@@ -0,0 +1,768 @@
+/*
+ * hif2.c - HIF layer re-implementation for the Linux SDIO stack
+ *
+ * Copyright (C) 2008, 2009 by OpenMoko, Inc.
+ * Written by Werner Almesberger <werner@openmoko.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * Based on:
+ *
+ * @abstract: HIF layer reference implementation for Atheros SDIO stack
+ * @notice: Copyright (c) 2004-2006 Atheros Communications Inc.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#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 "athdefs.h"
+#include "a_types.h"
+#include "hif.h"
+
+
+/* @@@ Hack - this wants cleaning up */
+
+#ifdef CONFIG_MACH_NEO1973_GTA02
+
+#include <mach/gta02-pm-wlan.h>
+
+#else /* CONFIG_MACH_NEO1973_GTA02 */
+
+#define gta02_wlan_query_rfkill_lock() 1
+#define gta02_wlan_set_rfkill_cb(cb, hif) ((void) cb)
+#define gta02_wlan_query_rfkill_unlock()
+#define gta02_wlan_clear_rfkill_cb()
+
+#endif /* !CONFIG_MACH_NEO1973_GTA02 */
+
+
+/*
+ * KNOWN BUGS:
+ *
+ * - HIF_DEVICE_IRQ_ASYNC_SYNC doesn't work yet (gets MMC errors)
+ * - latency can reach hundreds of ms, probably because of scheduling delays
+ * - packets go through about three queues before finally hitting the network
+ */
+
+/*
+ * Differences from Atheros' HIFs:
+ *
+ * - synchronous and asynchronous requests may get reordered with respect to
+ * each other, e.g., if HIFReadWrite returns for an asynchronous request and
+ * then HIFReadWrite is called for a synchronous request, the synchronous
+ * request may be executed before the asynchronous request.
+ *
+ * - request queue locking seems unnecessarily complex in the Atheros HIFs.
+ *
+ * - Atheros mask interrupts by calling sdio_claim_irq/sdio_release_irq, which
+ * can cause quite a bit of overhead. This HIF has its own light-weight
+ * interrupt masking.
+ *
+ * - Atheros call deviceInsertedHandler from a thread spawned off the probe or
+ * device insertion function. The original explanation for the Atheros SDIO
+ * stack said that this is done because a delay is needed to let the chip
+ * complete initialization. There is indeed a one second delay in the thread.
+ *
+ * The Atheros Linux SDIO HIF removes the delay and only retains the thread.
+ * Experimentally removing the thread didn't show any conflicts, so let's get
+ * rid of it for good.
+ *
+ * - The Atheros SDIO stack with Samuel's driver sets SDIO_CCCR_POWER in
+ * SDIO_POWER_EMPC. Atheros' Linux SDIO code apparently doesn't. We don't
+ * either, and this seems to work fine.
+ * @@@ Need to check this with Atheros.
+ */
+
+
+#define MBOXES 4
+
+#define HIF_MBOX_BLOCK_SIZE 128
+#define HIF_MBOX_BASE_ADDR 0x800
+#define HIF_MBOX_WIDTH 0x800
+#define HIF_MBOX_START_ADDR(mbox) \
+ (HIF_MBOX_BASE_ADDR+(mbox)*HIF_MBOX_WIDTH)
+
+
+struct hif_device {
+ void *htc_handle;
+ struct sdio_func *func;
+
+ /*
+ * @@@ our sweet little bit of bogosity - the mechanism that lets us
+ * use the SDIO stack from softirqs. This really wants to use skbs.
+ */
+ struct list_head queue;
+ 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 {
+ struct list_head list;
+ struct sdio_func *func;
+ int (*read)(struct sdio_func *func,
+ void *dst, unsigned int addr, int count);
+ int (*write)(struct sdio_func *func,
+ unsigned int addr, void *src, int count);
+ void *buf;
+ unsigned long addr;
+ int len;
+ A_STATUS (*completion)(void *context, A_STATUS status);
+ void *context;
+};
+
+
+static HTC_CALLBACKS htcCallbacks;
+
+/*
+ * shutdown_lock prevents recursion through HIFShutDownDevice
+ */
+static DEFINE_MUTEX(shutdown_lock);
+
+
+/* ----- Request processing ------------------------------------------------ */
+
+
+static A_STATUS process_request(struct hif_request *req)
+{
+ int ret;
+ A_STATUS status;
+
+ dev_dbg(&req->func->dev, "process_request(req %p)\n", req);
+ sdio_claim_host(req->func);
+ if (req->read) {
+ ret = req->read(req->func, req->buf, req->addr, req->len);
+ } else {
+ ret = req->write(req->func, req->addr, req->buf, req->len);
+ }
+ sdio_release_host(req->func);
+ status = ret ? A_ERROR : A_OK;
+ if (req->completion)
+ req->completion(req->context, status);
+ kfree(req);
+ return status;
+}
+
+
+static void enqueue_request(struct hif_device *hif, struct hif_request *req)
+{
+ unsigned long flags;
+
+ dev_dbg(&req->func->dev, "enqueue_request(req %p)\n", req);
+ spin_lock_irqsave(&hif->queue_lock, flags);
+ list_add_tail(&req->list, &hif->queue);
+ spin_unlock_irqrestore(&hif->queue_lock, flags);
+ wake_up(&hif->wait);
+}
+
+
+static struct hif_request *dequeue_request(struct hif_device *hif)
+{
+ struct hif_request *req;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hif->queue_lock, flags);
+ if (list_empty(&hif->queue))
+ req = NULL;
+ else {
+ req = list_first_entry(&hif->queue,
+ struct hif_request, list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&hif->queue_lock, flags);
+ return req;
+}
+
+
+static void wait_queue_empty(struct hif_device *hif)
+{
+ unsigned long flags;
+ int empty;
+
+ while (1) {
+ spin_lock_irqsave(&hif->queue_lock, flags);
+ empty = list_empty(&hif->queue);
+ spin_unlock_irqrestore(&hif->queue_lock, flags);
+ if (empty)
+ break;
+ else
+ yield();
+ }
+}
+
+
+static int io(void *data)
+{
+ struct hif_device *hif = data;
+ struct sched_param param = { .sched_priority = 2 };
+ /* one priority level slower than ksdioirqd (which is at 1) */
+ DEFINE_WAIT(wait);
+ struct hif_request *req;
+
+ sched_setscheduler(current, SCHED_FIFO, &param);
+
+ while (1) {
+ while (1) {
+ /*
+ * Since we never use signals here, one might think
+ * that this ought to be TASK_UNINTERRUPTIBLE. However,
+ * such a task would increase the load average and,
+ * worse, it would trigger the softlockup check.
+ */
+ prepare_to_wait(&hif->wait, &wait, TASK_INTERRUPTIBLE);
+ if (kthread_should_stop()) {
+ finish_wait(&hif->wait, &wait);
+ return 0;
+ }
+ req = dequeue_request(hif);
+ if (req)
+ break;
+ schedule();
+ }
+ finish_wait(&hif->wait, &wait);
+
+ (void) process_request(req);
+ }
+ return 0;
+}
+
+
+A_STATUS HIFReadWrite(HIF_DEVICE *hif, A_UINT32 address, A_UCHAR *buffer,
+ A_UINT32 length, A_UINT32 request, void *context)
+{
+ struct device *dev = HIFGetOSDevice(hif);
+ struct hif_request *req;
+
+ dev_dbg(dev, "HIFReadWrite(device %p, address 0x%x, buffer %p, "
+ "length %d, request 0x%x, context %p)\n",
+ hif, address, buffer, length, request, context);
+
+ BUG_ON(!(request & (HIF_SYNCHRONOUS | HIF_ASYNCHRONOUS)));
+ BUG_ON(!(request & (HIF_BYTE_BASIS | HIF_BLOCK_BASIS)));
+ BUG_ON(!(request & (HIF_READ | HIF_WRITE)));
+ BUG_ON(!(request & HIF_EXTENDED_IO));
+
+ if (address >= HIF_MBOX_START_ADDR(0) &&
+ address < HIF_MBOX_START_ADDR(MBOXES+1)) {
+ BUG_ON(length > HIF_MBOX_WIDTH);
+ /* Adjust the address so that the last byte falls on the EOM
+ address. */
+ address += HIF_MBOX_WIDTH-length;
+ }
+
+ req = kzalloc(sizeof(*req), GFP_ATOMIC);
+ if (!req) {
+ if (request & HIF_ASYNCHRONOUS)
+ htcCallbacks.rwCompletionHandler(context, A_ERROR);
+ return A_ERROR;
+ }
+
+ req->func = hif->func;
+ req->addr = address;
+ req->buf = buffer;
+ req->len = length;
+
+ if (request & HIF_READ) {
+ if (request & HIF_FIXED_ADDRESS)
+ req->read = sdio_readsb;
+ else
+ req->read = sdio_memcpy_fromio;
+ } else {
+ if (request & HIF_FIXED_ADDRESS)
+ req->write = sdio_writesb;
+ else
+ req->write = sdio_memcpy_toio;
+ }
+
+ if (!(request & HIF_ASYNCHRONOUS))
+ return process_request(req);
+
+ req->completion = htcCallbacks.rwCompletionHandler;
+ req->context = context;
+ enqueue_request(hif, req);
+
+ return A_OK;
+}
+
+
+/* ----- Interrupt handling ------------------------------------------------ */
+
+/*
+ * Volatile ought to be good enough to make gcc do the right thing on S3C24xx.
+ * No need to use atomic or put barriers, keeping the code more readable.
+ *
+ * Warning: this story changes if going SMP/SMT.
+ */
+
+static volatile int masked = 1;
+static volatile int pending;
+static volatile int in_interrupt;
+
+
+static void ar6000_do_irq(struct sdio_func *func)
+{
+ HIF_DEVICE *hif = sdio_get_drvdata(func);
+ struct device *dev = HIFGetOSDevice(hif);
+ A_STATUS status;
+
+ dev_dbg(dev, "ar6000_do_irq -> %p\n", htcCallbacks.dsrHandler);
+
+ status = htcCallbacks.dsrHandler(hif->htc_handle);
+ BUG_ON(status != A_OK);
+}
+
+
+static void sdio_ar6000_irq(struct sdio_func *func)
+{
+ HIF_DEVICE *hif = sdio_get_drvdata(func);
+ struct device *dev = HIFGetOSDevice(hif);
+
+ dev_dbg(dev, "sdio_ar6000_irq\n");
+
+ in_interrupt = 1;
+ if (masked) {
+ in_interrupt = 0;
+ pending++;
+ return;
+ }
+ /*
+ * @@@ This is ugly. If we don't drop the lock, we'll deadlock when
+ * the handler tries to do SDIO. So there are four choices:
+ *
+ * 1) Break the call chain by calling the callback from a workqueue.
+ * Ugh.
+ * 2) Make process_request aware that we already have the lock.
+ * 3) Drop the lock. Which is ugly but should be safe as long as we're
+ * making sure the device doesn't go away.
+ * 4) Change the AR6k driver such that it only issues asynchronous
+ * quests when called from an interrupt.
+ *
+ * Solution 2) is probably the best for now. Will try it later.
+ */
+ sdio_release_host(func);
+ ar6000_do_irq(func);
+ sdio_claim_host(func);
+ in_interrupt = 0;
+}
+
+
+void HIFAckInterrupt(HIF_DEVICE *hif)
+{
+ struct device *dev = HIFGetOSDevice(hif);
+
+ dev_dbg(dev, "HIFAckInterrupt\n");
+ /* do nothing */
+}
+
+
+void HIFUnMaskInterrupt(HIF_DEVICE *hif)
+{
+ struct device *dev = HIFGetOSDevice(hif);
+
+ dev_dbg(dev, "HIFUnMaskInterrupt\n");
+ do {
+ masked = 1;
+ if (pending) {
+ pending = 0;
+ ar6000_do_irq(hif->func);
+ /* We may take an interrupt before unmasking and thus
+ get it pending. In this case, we just loop back. */
+ }
+ masked = 0;
+ }
+ while (pending);
+}
+
+
+void HIFMaskInterrupt(HIF_DEVICE *hif)
+{
+ struct device *dev = HIFGetOSDevice(hif);
+
+ dev_dbg(dev, "HIFMaskInterrupt\n");
+ /*
+ * Since sdio_ar6000_irq can also be called from a process context, we
+ * may conceivably end up racing with it. Thus, we need to wait until
+ * we can be sure that no concurrent interrupt processing is going on
+ * before we return.
+ *
+ * Note: this may be a bit on the paranoid side - the callers may
+ * actually be nice enough to disable scheduling. Check later.
+ */
+ masked = 1;
+ while (in_interrupt)
+ yield();
+}
+
+
+/* ----- HIF API glue functions -------------------------------------------- */
+
+
+struct device *HIFGetOSDevice(HIF_DEVICE *hif)
+{
+ return &hif->func->dev;
+}
+
+
+void HIFSetHandle(void *hif_handle, void *handle)
+{
+ HIF_DEVICE *hif = (HIF_DEVICE *) hif_handle;
+
+ hif->htc_handle = handle;
+}
+
+
+/* ----- Device configuration (HIF side) ----------------------------------- */
+
+
+A_STATUS HIFConfigureDevice(HIF_DEVICE *hif,
+ HIF_DEVICE_CONFIG_OPCODE opcode, void *config, A_UINT32 configLen)
+{
+ struct device *dev = HIFGetOSDevice(hif);
+ HIF_DEVICE_IRQ_PROCESSING_MODE *ipm_cfg = config;
+ A_UINT32 *mbs_cfg = config;
+ int i;
+
+ dev_dbg(dev, "HIFConfigureDevice\n");
+
+ switch (opcode) {
+ case HIF_DEVICE_GET_MBOX_BLOCK_SIZE:
+ for (i = 0; i != MBOXES; i++)
+ mbs_cfg[i] = HIF_MBOX_BLOCK_SIZE;
+ break;
+ case HIF_DEVICE_GET_MBOX_ADDR:
+ for (i = 0; i != MBOXES; i++)
+ mbs_cfg[i] = HIF_MBOX_START_ADDR(i);
+ break;
+ case HIF_DEVICE_GET_IRQ_PROC_MODE:
+ *ipm_cfg = HIF_DEVICE_IRQ_SYNC_ONLY;
+// *ipm_cfg = HIF_DEVICE_IRQ_ASYNC_SYNC;
+ break;
+ default:
+ return A_ERROR;
+ }
+ return A_OK;
+}
+
+
+/* ----- Device probe and removal (Linux side) ----------------------------- */
+
+
+static int ar6000_do_activate(struct hif_device *hif)
+{
+ struct sdio_func *func = hif->func;
+ struct device *dev = &func->dev;
+ int ret;
+
+ dev_dbg(dev, "ar6000_do_activate\n");
+
+ sdio_claim_host(func);
+ sdio_enable_func(func);
+
+ INIT_LIST_HEAD(&hif->queue);
+ init_waitqueue_head(&hif->wait);
+ spin_lock_init(&hif->queue_lock);
+
+ ret = sdio_set_block_size(func, HIF_MBOX_BLOCK_SIZE);
+ if (ret < 0) {
+ dev_err(dev, "sdio_set_block_size returns %d\n", ret);
+ goto out_enabled;
+ }
+ ret = sdio_claim_irq(func, sdio_ar6000_irq);
+ if (ret) {
+ dev_err(dev, "sdio_claim_irq returns %d\n", ret);
+ goto out_enabled;
+ }
+ /* Set SDIO_BUS_CD_DISABLE in SDIO_CCCR_IF ? */
+#if 0
+ sdio_f0_writeb(func, SDIO_CCCR_CAP_E4MI, SDIO_CCCR_CAPS, &ret);
+ if (ret) {
+ dev_err(dev, "sdio_f0_writeb(SDIO_CCCR_CAPS) returns %d\n",
+ ret);
+ goto out_got_irq;
+ }
+#else
+ if (0) /* avoid warning */
+ goto out_got_irq;
+#endif
+
+ sdio_release_host(func);
+
+ hif->io_task = kthread_run(io, hif, "ar6000_io");
+ ret = IS_ERR(hif->io_task);
+ if (ret) {
+ dev_err(dev, "kthread_run(ar6000_io): %d\n", ret);
+ goto out_func_ready;
+ }
+
+ ret = htcCallbacks.deviceInsertedHandler(hif);
+ if (ret == A_OK)
+ return 0;
+
+ dev_err(dev, "deviceInsertedHandler: %d\n", ret);
+
+ ret = kthread_stop(hif->io_task);
+ if (ret)
+ dev_err(dev, "kthread_stop (ar6000_io): %d\n", ret);
+
+out_func_ready:
+ sdio_claim_host(func);
+
+out_got_irq:
+ sdio_release_irq(func);
+
+out_enabled:
+ sdio_disable_func(func);
+ sdio_release_host(func);
+
+ return ret;
+}
+
+
+static void ar6000_do_deactivate(struct hif_device *hif)
+{
+ struct sdio_func *func = hif->func;
+ struct device *dev = &func->dev;
+ int ret;
+
+ 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
+ * a recursion through HTCShutDown/HIFShutDown if unloading the
+ * module.
+ *
+ * However, we need it for suspend/resume. See the comment at
+ * HIFShutDown, below.
+ */
+ ret = htcCallbacks.deviceRemovedHandler(hif->htc_handle, A_OK);
+ if (ret != A_OK)
+ dev_err(dev, "deviceRemovedHandler: %d\n", ret);
+ mutex_unlock(&shutdown_lock);
+ }
+ wait_queue_empty(hif);
+ ret = kthread_stop(hif->io_task);
+ if (ret)
+ dev_err(dev, "kthread_stop (ar6000_io): %d\n", ret);
+ sdio_claim_host(func);
+ sdio_release_irq(func);
+ 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);
+ if (ret) {
+ printk(KERN_ERR "%s: Failed to activate %d\n",
+ __func__, ret);
+ goto out;
+ }
+ hif->active = 1;
+ }
+out:
+ 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);
+}
+
+
+/* ----- Device registration/unregistration (called by HIF) ---------------- */
+
+
+#define ATHEROS_SDIO_DEVICE(id, offset) \
+ SDIO_DEVICE(SDIO_VENDOR_ID_ATHEROS, SDIO_DEVICE_ID_ATHEROS_##id | (offset))
+
+static const struct sdio_device_id sdio_ar6000_ids[] = {
+ { ATHEROS_SDIO_DEVICE(AR6002, 0) },
+ { ATHEROS_SDIO_DEVICE(AR6002, 0x1) },
+ { ATHEROS_SDIO_DEVICE(AR6001, 0x8) },
+ { ATHEROS_SDIO_DEVICE(AR6001, 0x9) },
+ { ATHEROS_SDIO_DEVICE(AR6001, 0xa) },
+ { ATHEROS_SDIO_DEVICE(AR6001, 0xb) },
+ { /* end: all zeroes */ },
+};
+
+MODULE_DEVICE_TABLE(sdio, sdio_ar6000_ids);
+
+
+static struct sdio_driver sdio_ar6000_driver = {
+ .probe = sdio_ar6000_probe,
+ .remove = sdio_ar6000_remove,
+ .name = "sdio_ar6000",
+ .id_table = sdio_ar6000_ids,
+};
+
+
+int HIFInit(HTC_CALLBACKS *callbacks)
+{
+ int ret;
+
+ BUG_ON(!callbacks);
+
+ printk(KERN_DEBUG "HIFInit\n");
+ htcCallbacks = *callbacks;
+
+ ret = sdio_register_driver(&sdio_ar6000_driver);
+ if (ret) {
+ printk(KERN_ERR
+ "sdio_register_driver(sdio_ar6000_driver): %d\n", ret);
+ return A_ERROR;
+ }
+
+ return 0;
+}
+
+
+/*
+ * 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.
+ *
+ * Module removal:
+ *
+ * sys_delete_module -> ar6000_cleanup_module -> HTCShutDown ->
+ * HIFShutDownDevice -> sdio_unregister_driver ...> sdio_bus_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. 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 -> 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)
+{
+ /* Beware, HTCShutDown calls us with hif == NULL ! */
+ if (mutex_trylock(&shutdown_lock)) {
+ sdio_unregister_driver(&sdio_ar6000_driver);
+ mutex_unlock(&shutdown_lock);
+ }
+}