mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-12-01 11:54:04 +02:00
769 lines
19 KiB
C
769 lines
19 KiB
C
|
/*
|
||
|
* 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, ¶m);
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|