2007-10-18 03:24:24 +07:00
|
|
|
/*
|
|
|
|
* linux/drivers/net/wireless/libertas/if_sdio.c
|
|
|
|
*
|
2008-06-28 17:52:45 +07:00
|
|
|
* Copyright 2007-2008 Pierre Ossman
|
2007-10-18 03:24:24 +07:00
|
|
|
*
|
|
|
|
* Inspired by if_cs.c, Copyright 2007 Holger Schurig
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
|
|
* your option) any later version.
|
|
|
|
*
|
|
|
|
* This hardware has more or less no CMD53 support, so all registers
|
|
|
|
* must be accessed using sdio_readb()/sdio_writeb().
|
|
|
|
*
|
|
|
|
* Transfers must be in one transaction or the firmware goes bonkers.
|
|
|
|
* This means that the transfer must either be small enough to do a
|
|
|
|
* byte based transfer or it must be padded to a multiple of the
|
|
|
|
* current block size.
|
|
|
|
*
|
|
|
|
* As SDIO is still new to the kernel, it is unfortunately common with
|
2007-12-15 10:53:41 +07:00
|
|
|
* bugs in the host controllers related to that. One such bug is that
|
2007-10-18 03:24:24 +07:00
|
|
|
* controllers cannot do transfers that aren't a multiple of 4 bytes.
|
|
|
|
* If you don't have time to fix the host controller driver, you can
|
|
|
|
* work around the problem by modifying if_sdio_host_to_card() and
|
|
|
|
* if_sdio_card_to_host() to pad the data.
|
|
|
|
*/
|
|
|
|
|
2011-05-03 06:49:14 +07:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2008-12-14 02:33:13 +07:00
|
|
|
#include <linux/kernel.h>
|
2007-10-18 03:24:24 +07:00
|
|
|
#include <linux/moduleparam.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2007-10-18 03:24:24 +07:00
|
|
|
#include <linux/firmware.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/mmc/card.h>
|
|
|
|
#include <linux/mmc/sdio_func.h>
|
|
|
|
#include <linux/mmc/sdio_ids.h>
|
2010-04-06 15:52:44 +07:00
|
|
|
#include <linux/mmc/sdio.h>
|
|
|
|
#include <linux/mmc/host.h>
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
#include "host.h"
|
|
|
|
#include "decl.h"
|
|
|
|
#include "defs.h"
|
|
|
|
#include "dev.h"
|
2009-06-02 08:04:36 +07:00
|
|
|
#include "cmd.h"
|
2007-10-18 03:24:24 +07:00
|
|
|
#include "if_sdio.h"
|
|
|
|
|
2009-06-02 08:04:36 +07:00
|
|
|
/* The if_sdio_remove() callback function is called when
|
|
|
|
* user removes this module from kernel space or ejects
|
|
|
|
* the card from the slot. The driver handles these 2 cases
|
|
|
|
* differently for SD8688 combo chip.
|
|
|
|
* If the user is removing the module, the FUNC_SHUTDOWN
|
|
|
|
* command for SD8688 is sent to the firmware.
|
|
|
|
* If the card is removed, there is no need to send this command.
|
|
|
|
*
|
|
|
|
* The variable 'user_rmmod' is used to distinguish these two
|
|
|
|
* scenarios. This flag is initialized as FALSE in case the card
|
|
|
|
* is removed, and will be set to TRUE for module removal when
|
|
|
|
* module_exit function is called.
|
|
|
|
*/
|
|
|
|
static u8 user_rmmod;
|
|
|
|
|
2007-11-16 06:05:47 +07:00
|
|
|
static char *lbs_helper_name = NULL;
|
|
|
|
module_param_named(helper_name, lbs_helper_name, charp, 0644);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
2007-11-16 06:05:47 +07:00
|
|
|
static char *lbs_fw_name = NULL;
|
|
|
|
module_param_named(fw_name, lbs_fw_name, charp, 0644);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
static const struct sdio_device_id if_sdio_ids[] = {
|
2009-04-07 05:50:56 +07:00
|
|
|
{ SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL,
|
|
|
|
SDIO_DEVICE_ID_MARVELL_LIBERTAS) },
|
|
|
|
{ SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL,
|
|
|
|
SDIO_DEVICE_ID_MARVELL_8688WLAN) },
|
|
|
|
{ /* end: all zeroes */ },
|
2007-10-18 03:24:24 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_DEVICE_TABLE(sdio, if_sdio_ids);
|
|
|
|
|
2010-08-08 09:15:52 +07:00
|
|
|
#define MODEL_8385 0x04
|
|
|
|
#define MODEL_8686 0x0b
|
|
|
|
#define MODEL_8688 0x10
|
|
|
|
|
|
|
|
static const struct lbs_fw_table fw_table[] = {
|
|
|
|
{ MODEL_8385, "libertas/sd8385_helper.bin", "libertas/sd8385.bin" },
|
|
|
|
{ MODEL_8385, "sd8385_helper.bin", "sd8385.bin" },
|
|
|
|
{ MODEL_8686, "libertas/sd8686_v9_helper.bin", "libertas/sd8686_v9.bin" },
|
|
|
|
{ MODEL_8686, "libertas/sd8686_v8_helper.bin", "libertas/sd8686_v8.bin" },
|
|
|
|
{ MODEL_8686, "sd8686_helper.bin", "sd8686.bin" },
|
|
|
|
{ MODEL_8688, "libertas/sd8688_helper.bin", "libertas/sd8688.bin" },
|
|
|
|
{ MODEL_8688, "sd8688_helper.bin", "sd8688.bin" },
|
|
|
|
{ 0, NULL, NULL }
|
2007-10-18 03:24:24 +07:00
|
|
|
};
|
2010-08-08 09:15:52 +07:00
|
|
|
MODULE_FIRMWARE("libertas/sd8385_helper.bin");
|
|
|
|
MODULE_FIRMWARE("libertas/sd8385.bin");
|
2009-11-08 05:00:03 +07:00
|
|
|
MODULE_FIRMWARE("sd8385_helper.bin");
|
|
|
|
MODULE_FIRMWARE("sd8385.bin");
|
2010-08-08 09:15:52 +07:00
|
|
|
MODULE_FIRMWARE("libertas/sd8686_v9_helper.bin");
|
|
|
|
MODULE_FIRMWARE("libertas/sd8686_v9.bin");
|
|
|
|
MODULE_FIRMWARE("libertas/sd8686_v8_helper.bin");
|
|
|
|
MODULE_FIRMWARE("libertas/sd8686_v8.bin");
|
2009-11-08 05:00:03 +07:00
|
|
|
MODULE_FIRMWARE("sd8686_helper.bin");
|
|
|
|
MODULE_FIRMWARE("sd8686.bin");
|
2010-08-08 09:15:52 +07:00
|
|
|
MODULE_FIRMWARE("libertas/sd8688_helper.bin");
|
|
|
|
MODULE_FIRMWARE("libertas/sd8688.bin");
|
2009-11-08 05:00:03 +07:00
|
|
|
MODULE_FIRMWARE("sd8688_helper.bin");
|
|
|
|
MODULE_FIRMWARE("sd8688.bin");
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
struct if_sdio_packet {
|
|
|
|
struct if_sdio_packet *next;
|
|
|
|
u16 nb;
|
|
|
|
u8 buffer[0] __attribute__((aligned(4)));
|
|
|
|
};
|
|
|
|
|
|
|
|
struct if_sdio_card {
|
|
|
|
struct sdio_func *func;
|
2007-11-23 21:43:44 +07:00
|
|
|
struct lbs_private *priv;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
int model;
|
|
|
|
unsigned long ioport;
|
2009-05-22 01:32:34 +07:00
|
|
|
unsigned int scratch_reg;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
const char *helper;
|
|
|
|
const char *firmware;
|
2010-08-12 12:04:33 +07:00
|
|
|
bool helper_allocated;
|
|
|
|
bool firmware_allocated;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
2010-08-22 18:22:40 +07:00
|
|
|
u8 buffer[65536] __attribute__((aligned(4)));
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
spinlock_t lock;
|
|
|
|
struct if_sdio_packet *packets;
|
2009-02-21 00:27:38 +07:00
|
|
|
|
|
|
|
struct workqueue_struct *workqueue;
|
2007-10-18 03:24:24 +07:00
|
|
|
struct work_struct packet_worker;
|
2009-05-20 09:48:19 +07:00
|
|
|
|
|
|
|
u8 rx_unit;
|
2007-10-18 03:24:24 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
/********************************************************************/
|
|
|
|
/* I/O */
|
|
|
|
/********************************************************************/
|
|
|
|
|
2009-05-22 01:32:34 +07:00
|
|
|
/*
|
|
|
|
* For SD8385/SD8686, this function reads firmware status after
|
|
|
|
* the image is downloaded, or reads RX packet length when
|
|
|
|
* interrupt (with IF_SDIO_H_INT_UPLD bit set) is received.
|
|
|
|
* For SD8688, this function reads firmware status only.
|
|
|
|
*/
|
2007-10-18 03:24:24 +07:00
|
|
|
static u16 if_sdio_read_scratch(struct if_sdio_card *card, int *err)
|
|
|
|
{
|
2009-05-22 01:32:34 +07:00
|
|
|
int ret;
|
2007-10-18 03:24:24 +07:00
|
|
|
u16 scratch;
|
|
|
|
|
2009-05-22 01:32:34 +07:00
|
|
|
scratch = sdio_readb(card->func, card->scratch_reg, &ret);
|
2007-10-18 03:24:24 +07:00
|
|
|
if (!ret)
|
2009-05-22 01:32:34 +07:00
|
|
|
scratch |= sdio_readb(card->func, card->scratch_reg + 1,
|
|
|
|
&ret) << 8;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
if (err)
|
|
|
|
*err = ret;
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return 0xffff;
|
|
|
|
|
|
|
|
return scratch;
|
|
|
|
}
|
|
|
|
|
2009-05-20 09:48:19 +07:00
|
|
|
static u8 if_sdio_read_rx_unit(struct if_sdio_card *card)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u8 rx_unit;
|
|
|
|
|
|
|
|
rx_unit = sdio_readb(card->func, IF_SDIO_RX_UNIT, &ret);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
rx_unit = 0;
|
|
|
|
|
|
|
|
return rx_unit;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u16 if_sdio_read_rx_len(struct if_sdio_card *card, int *err)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u16 rx_len;
|
|
|
|
|
|
|
|
switch (card->model) {
|
2010-08-08 09:15:52 +07:00
|
|
|
case MODEL_8385:
|
|
|
|
case MODEL_8686:
|
2009-05-20 09:48:19 +07:00
|
|
|
rx_len = if_sdio_read_scratch(card, &ret);
|
|
|
|
break;
|
2010-08-08 09:15:52 +07:00
|
|
|
case MODEL_8688:
|
2009-05-20 09:48:19 +07:00
|
|
|
default: /* for newer chipsets */
|
|
|
|
rx_len = sdio_readb(card->func, IF_SDIO_RX_LEN, &ret);
|
|
|
|
if (!ret)
|
|
|
|
rx_len <<= card->rx_unit;
|
|
|
|
else
|
|
|
|
rx_len = 0xffff; /* invalid length */
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
*err = ret;
|
|
|
|
|
|
|
|
return rx_len;
|
|
|
|
}
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
static int if_sdio_handle_cmd(struct if_sdio_card *card,
|
|
|
|
u8 *buffer, unsigned size)
|
|
|
|
{
|
2008-04-01 19:50:43 +07:00
|
|
|
struct lbs_private *priv = card->priv;
|
2007-10-18 03:24:24 +07:00
|
|
|
int ret;
|
|
|
|
unsigned long flags;
|
2008-04-01 19:50:43 +07:00
|
|
|
u8 i;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
2007-12-12 01:49:39 +07:00
|
|
|
if (size > LBS_CMD_BUFFER_SIZE) {
|
2007-10-18 03:24:24 +07:00
|
|
|
lbs_deb_sdio("response packet too large (%d bytes)\n",
|
|
|
|
(int)size);
|
|
|
|
ret = -E2BIG;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2008-04-01 19:50:43 +07:00
|
|
|
spin_lock_irqsave(&priv->driver_lock, flags);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
2008-04-01 19:50:43 +07:00
|
|
|
i = (priv->resp_idx == 0) ? 1 : 0;
|
|
|
|
BUG_ON(priv->resp_len[i]);
|
|
|
|
priv->resp_len[i] = size;
|
|
|
|
memcpy(priv->resp_buf[i], buffer, size);
|
|
|
|
lbs_notify_command_response(priv, i);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
2008-04-01 19:50:43 +07:00
|
|
|
spin_unlock_irqrestore(&card->priv->driver_lock, flags);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int if_sdio_handle_data(struct if_sdio_card *card,
|
|
|
|
u8 *buffer, unsigned size)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
char *data;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
|
|
|
if (size > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) {
|
|
|
|
lbs_deb_sdio("response packet too large (%d bytes)\n",
|
|
|
|
(int)size);
|
|
|
|
ret = -E2BIG;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2007-10-23 00:05:32 +07:00
|
|
|
skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + NET_IP_ALIGN);
|
2007-10-18 03:24:24 +07:00
|
|
|
if (!skb) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2007-10-23 00:05:32 +07:00
|
|
|
skb_reserve(skb, NET_IP_ALIGN);
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
data = skb_put(skb, size);
|
|
|
|
|
|
|
|
memcpy(data, buffer, size);
|
|
|
|
|
2007-11-16 06:05:47 +07:00
|
|
|
lbs_process_rxed_packet(card->priv, skb);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int if_sdio_handle_event(struct if_sdio_card *card,
|
|
|
|
u8 *buffer, unsigned size)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u32 event;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
2010-08-08 09:15:52 +07:00
|
|
|
if (card->model == MODEL_8385) {
|
2007-10-18 03:24:24 +07:00
|
|
|
event = sdio_readb(card->func, IF_SDIO_EVENT, &ret);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
2009-02-05 13:22:39 +07:00
|
|
|
|
|
|
|
/* right shift 3 bits to get the event id */
|
|
|
|
event >>= 3;
|
2007-10-18 03:24:24 +07:00
|
|
|
} else {
|
|
|
|
if (size < 4) {
|
|
|
|
lbs_deb_sdio("event packet too small (%d bytes)\n",
|
|
|
|
(int)size);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
event = buffer[3] << 24;
|
|
|
|
event |= buffer[2] << 16;
|
|
|
|
event |= buffer[1] << 8;
|
|
|
|
event |= buffer[0] << 0;
|
|
|
|
}
|
|
|
|
|
2008-04-01 19:50:43 +07:00
|
|
|
lbs_queue_event(card->priv, event & 0xFF);
|
2007-10-18 03:24:24 +07:00
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-04-16 03:27:44 +07:00
|
|
|
static int if_sdio_wait_status(struct if_sdio_card *card, const u8 condition)
|
|
|
|
{
|
|
|
|
u8 status;
|
|
|
|
unsigned long timeout;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
timeout = jiffies + HZ;
|
|
|
|
while (1) {
|
|
|
|
status = sdio_readb(card->func, IF_SDIO_STATUS, &ret);
|
2010-04-29 02:37:46 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
if ((status & condition) == condition)
|
2010-04-16 03:27:44 +07:00
|
|
|
break;
|
|
|
|
if (time_after(jiffies, timeout))
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
mdelay(1);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
static int if_sdio_card_to_host(struct if_sdio_card *card)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u16 size, type, chunk;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
2009-05-20 09:48:19 +07:00
|
|
|
size = if_sdio_read_rx_len(card, &ret);
|
2007-10-18 03:24:24 +07:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (size < 4) {
|
|
|
|
lbs_deb_sdio("invalid packet size (%d bytes) from firmware\n",
|
|
|
|
(int)size);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2010-04-16 03:27:44 +07:00
|
|
|
ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The transfer must be in one transaction or the firmware
|
2008-06-28 17:52:45 +07:00
|
|
|
* goes suicidal. There's no way to guarantee that for all
|
|
|
|
* controllers, but we can at least try.
|
2007-10-18 03:24:24 +07:00
|
|
|
*/
|
2008-06-28 17:52:45 +07:00
|
|
|
chunk = sdio_align_size(card->func, size);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
chunk = card->buffer[0] | (card->buffer[1] << 8);
|
|
|
|
type = card->buffer[2] | (card->buffer[3] << 8);
|
|
|
|
|
|
|
|
lbs_deb_sdio("packet of type %d and size %d bytes\n",
|
|
|
|
(int)type, (int)chunk);
|
|
|
|
|
|
|
|
if (chunk > size) {
|
|
|
|
lbs_deb_sdio("packet fragment (%d > %d)\n",
|
|
|
|
(int)chunk, (int)size);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chunk < size) {
|
|
|
|
lbs_deb_sdio("packet fragment (%d < %d)\n",
|
|
|
|
(int)chunk, (int)size);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case MVMS_CMD:
|
|
|
|
ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
break;
|
|
|
|
case MVMS_DAT:
|
|
|
|
ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
break;
|
|
|
|
case MVMS_EVENT:
|
|
|
|
ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
lbs_deb_sdio("invalid type (%d) from firmware\n",
|
|
|
|
(int)type);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (ret)
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("problem fetching packet from firmware\n");
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void if_sdio_host_to_card_worker(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct if_sdio_card *card;
|
|
|
|
struct if_sdio_packet *packet;
|
|
|
|
int ret;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
|
|
|
card = container_of(work, struct if_sdio_card, packet_worker);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
spin_lock_irqsave(&card->lock, flags);
|
|
|
|
packet = card->packets;
|
|
|
|
if (packet)
|
|
|
|
card->packets = packet->next;
|
|
|
|
spin_unlock_irqrestore(&card->lock, flags);
|
|
|
|
|
|
|
|
if (!packet)
|
|
|
|
break;
|
|
|
|
|
|
|
|
sdio_claim_host(card->func);
|
|
|
|
|
2010-04-16 03:27:44 +07:00
|
|
|
ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY);
|
|
|
|
if (ret == 0) {
|
|
|
|
ret = sdio_writesb(card->func, card->ioport,
|
|
|
|
packet->buffer, packet->nb);
|
2007-10-18 03:24:24 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret)
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("error %d sending packet to firmware\n", ret);
|
2010-04-16 03:27:44 +07:00
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
sdio_release_host(card->func);
|
|
|
|
|
|
|
|
kfree(packet);
|
|
|
|
}
|
|
|
|
|
|
|
|
lbs_deb_leave(LBS_DEB_SDIO);
|
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************/
|
|
|
|
/* Firmware */
|
|
|
|
/********************************************************************/
|
|
|
|
|
2010-04-16 03:27:44 +07:00
|
|
|
#define FW_DL_READY_STATUS (IF_SDIO_IO_RDY | IF_SDIO_DL_RDY)
|
|
|
|
|
2010-08-08 09:15:52 +07:00
|
|
|
static int if_sdio_prog_helper(struct if_sdio_card *card,
|
|
|
|
const struct firmware *fw)
|
2007-10-18 03:24:24 +07:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
unsigned long timeout;
|
|
|
|
u8 *chunk_buffer;
|
|
|
|
u32 chunk_size;
|
2008-05-24 00:37:51 +07:00
|
|
|
const u8 *firmware;
|
2007-10-18 03:24:24 +07:00
|
|
|
size_t size;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
|
|
|
chunk_buffer = kzalloc(64, GFP_KERNEL);
|
|
|
|
if (!chunk_buffer) {
|
|
|
|
ret = -ENOMEM;
|
2010-08-08 09:15:52 +07:00
|
|
|
goto out;
|
2007-10-18 03:24:24 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
sdio_claim_host(card->func);
|
|
|
|
|
|
|
|
ret = sdio_set_block_size(card->func, 32);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
firmware = fw->data;
|
|
|
|
size = fw->size;
|
|
|
|
|
|
|
|
while (size) {
|
2010-04-16 03:27:44 +07:00
|
|
|
ret = if_sdio_wait_status(card, FW_DL_READY_STATUS);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
2010-04-16 03:32:58 +07:00
|
|
|
/* On some platforms (like Davinci) the chip needs more time
|
|
|
|
* between helper blocks.
|
|
|
|
*/
|
|
|
|
mdelay(2);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
chunk_size = min(size, (size_t)60);
|
|
|
|
|
2007-12-07 22:30:44 +07:00
|
|
|
*((__le32*)chunk_buffer) = cpu_to_le32(chunk_size);
|
2007-10-18 03:24:24 +07:00
|
|
|
memcpy(chunk_buffer + 4, firmware, chunk_size);
|
|
|
|
/*
|
|
|
|
lbs_deb_sdio("sending %d bytes chunk\n", chunk_size);
|
|
|
|
*/
|
|
|
|
ret = sdio_writesb(card->func, card->ioport,
|
|
|
|
chunk_buffer, 64);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
firmware += chunk_size;
|
|
|
|
size -= chunk_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* an empty block marks the end of the transfer */
|
|
|
|
memset(chunk_buffer, 0, 4);
|
|
|
|
ret = sdio_writesb(card->func, card->ioport, chunk_buffer, 64);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
lbs_deb_sdio("waiting for helper to boot...\n");
|
|
|
|
|
|
|
|
/* wait for the helper to boot by looking at the size register */
|
|
|
|
timeout = jiffies + HZ;
|
|
|
|
while (1) {
|
|
|
|
u16 req_size;
|
|
|
|
|
|
|
|
req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, &ret);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, &ret) << 8;
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
if (req_size != 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (time_after(jiffies, timeout)) {
|
|
|
|
ret = -ETIMEDOUT;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
|
|
|
|
msleep(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
release:
|
|
|
|
sdio_release_host(card->func);
|
|
|
|
kfree(chunk_buffer);
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (ret)
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("failed to load helper firmware\n");
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-08-08 09:15:52 +07:00
|
|
|
static int if_sdio_prog_real(struct if_sdio_card *card,
|
|
|
|
const struct firmware *fw)
|
2007-10-18 03:24:24 +07:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
unsigned long timeout;
|
|
|
|
u8 *chunk_buffer;
|
|
|
|
u32 chunk_size;
|
2008-05-24 00:37:51 +07:00
|
|
|
const u8 *firmware;
|
2007-10-18 03:24:24 +07:00
|
|
|
size_t size, req_size;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
|
|
|
chunk_buffer = kzalloc(512, GFP_KERNEL);
|
|
|
|
if (!chunk_buffer) {
|
|
|
|
ret = -ENOMEM;
|
2010-08-08 09:15:52 +07:00
|
|
|
goto out;
|
2007-10-18 03:24:24 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
sdio_claim_host(card->func);
|
|
|
|
|
|
|
|
ret = sdio_set_block_size(card->func, 32);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
firmware = fw->data;
|
|
|
|
size = fw->size;
|
|
|
|
|
|
|
|
while (size) {
|
2010-04-16 03:27:44 +07:00
|
|
|
ret = if_sdio_wait_status(card, FW_DL_READY_STATUS);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, &ret);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, &ret) << 8;
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
/*
|
|
|
|
lbs_deb_sdio("firmware wants %d bytes\n", (int)req_size);
|
|
|
|
*/
|
|
|
|
if (req_size == 0) {
|
|
|
|
lbs_deb_sdio("firmware helper gave up early\n");
|
|
|
|
ret = -EIO;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req_size & 0x01) {
|
|
|
|
lbs_deb_sdio("firmware helper signalled error\n");
|
|
|
|
ret = -EIO;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req_size > size)
|
|
|
|
req_size = size;
|
|
|
|
|
|
|
|
while (req_size) {
|
|
|
|
chunk_size = min(req_size, (size_t)512);
|
|
|
|
|
|
|
|
memcpy(chunk_buffer, firmware, chunk_size);
|
|
|
|
/*
|
|
|
|
lbs_deb_sdio("sending %d bytes (%d bytes) chunk\n",
|
|
|
|
chunk_size, (chunk_size + 31) / 32 * 32);
|
|
|
|
*/
|
|
|
|
ret = sdio_writesb(card->func, card->ioport,
|
2008-12-14 02:33:13 +07:00
|
|
|
chunk_buffer, roundup(chunk_size, 32));
|
2007-10-18 03:24:24 +07:00
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
firmware += chunk_size;
|
|
|
|
size -= chunk_size;
|
|
|
|
req_size -= chunk_size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
lbs_deb_sdio("waiting for firmware to boot...\n");
|
|
|
|
|
|
|
|
/* wait for the firmware to boot */
|
|
|
|
timeout = jiffies + HZ;
|
|
|
|
while (1) {
|
|
|
|
u16 scratch;
|
|
|
|
|
|
|
|
scratch = if_sdio_read_scratch(card, &ret);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
if (scratch == IF_SDIO_FIRMWARE_OK)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (time_after(jiffies, timeout)) {
|
|
|
|
ret = -ETIMEDOUT;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
|
|
|
|
msleep(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
release:
|
|
|
|
sdio_release_host(card->func);
|
|
|
|
kfree(chunk_buffer);
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (ret)
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("failed to load firmware\n");
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int if_sdio_prog_firmware(struct if_sdio_card *card)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u16 scratch;
|
2010-08-08 09:15:52 +07:00
|
|
|
const struct firmware *helper = NULL;
|
|
|
|
const struct firmware *mainfw = NULL;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
2010-10-29 20:57:28 +07:00
|
|
|
/*
|
|
|
|
* Disable interrupts
|
|
|
|
*/
|
|
|
|
sdio_claim_host(card->func);
|
|
|
|
sdio_writeb(card->func, 0x00, IF_SDIO_H_INT_MASK, &ret);
|
|
|
|
sdio_release_host(card->func);
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
sdio_claim_host(card->func);
|
|
|
|
scratch = if_sdio_read_scratch(card, &ret);
|
|
|
|
sdio_release_host(card->func);
|
|
|
|
|
2010-10-29 20:57:28 +07:00
|
|
|
lbs_deb_sdio("firmware status = %#x\n", scratch);
|
|
|
|
lbs_deb_sdio("scratch ret = %d\n", ret);
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2009-05-22 01:32:34 +07:00
|
|
|
|
2010-10-29 20:57:28 +07:00
|
|
|
/*
|
|
|
|
* The manual clearly describes that FEDC is the right code to use
|
|
|
|
* to detect firmware presence, but for SD8686 it is not that simple.
|
|
|
|
* Scratch is also used to store the RX packet length, so we lose
|
|
|
|
* the FEDC value early on. So we use a non-zero check in order
|
|
|
|
* to validate firmware presence.
|
|
|
|
* Additionally, the SD8686 in the Gumstix always has the high scratch
|
|
|
|
* bit set, even when the firmware is not loaded. So we have to
|
|
|
|
* exclude that from the test.
|
|
|
|
*/
|
2007-10-18 03:24:24 +07:00
|
|
|
if (scratch == IF_SDIO_FIRMWARE_OK) {
|
|
|
|
lbs_deb_sdio("firmware already loaded\n");
|
|
|
|
goto success;
|
2010-10-29 20:57:28 +07:00
|
|
|
} else if ((card->model == MODEL_8686) && (scratch & 0x7fff)) {
|
|
|
|
lbs_deb_sdio("firmware may be running\n");
|
|
|
|
goto success;
|
2007-10-18 03:24:24 +07:00
|
|
|
}
|
|
|
|
|
2010-08-08 09:15:52 +07:00
|
|
|
ret = lbs_get_firmware(&card->func->dev, lbs_helper_name, lbs_fw_name,
|
|
|
|
card->model, &fw_table[0], &helper, &mainfw);
|
|
|
|
if (ret) {
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("failed to find firmware (%d)\n", ret);
|
2010-08-08 09:15:52 +07:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = if_sdio_prog_helper(card, helper);
|
2007-10-18 03:24:24 +07:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2010-10-29 20:57:28 +07:00
|
|
|
lbs_deb_sdio("Helper firmware loaded\n");
|
|
|
|
|
2010-08-08 09:15:52 +07:00
|
|
|
ret = if_sdio_prog_real(card, mainfw);
|
2007-10-18 03:24:24 +07:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2010-10-29 20:57:28 +07:00
|
|
|
lbs_deb_sdio("Firmware loaded\n");
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
success:
|
2009-05-20 09:48:20 +07:00
|
|
|
sdio_claim_host(card->func);
|
|
|
|
sdio_set_block_size(card->func, IF_SDIO_BLOCK_SIZE);
|
|
|
|
sdio_release_host(card->func);
|
2007-10-18 03:24:24 +07:00
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
out:
|
2010-08-08 09:15:52 +07:00
|
|
|
if (helper)
|
|
|
|
release_firmware(helper);
|
|
|
|
if (mainfw)
|
|
|
|
release_firmware(mainfw);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
2010-08-08 09:15:52 +07:00
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
2007-10-18 03:24:24 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************/
|
|
|
|
/* Libertas callbacks */
|
|
|
|
/*******************************************************************/
|
|
|
|
|
2007-11-23 21:43:44 +07:00
|
|
|
static int if_sdio_host_to_card(struct lbs_private *priv,
|
|
|
|
u8 type, u8 *buf, u16 nb)
|
2007-10-18 03:24:24 +07:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct if_sdio_card *card;
|
|
|
|
struct if_sdio_packet *packet, *cur;
|
|
|
|
u16 size;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
lbs_deb_enter_args(LBS_DEB_SDIO, "type %d, bytes %d", type, nb);
|
|
|
|
|
|
|
|
card = priv->card;
|
|
|
|
|
|
|
|
if (nb > (65536 - sizeof(struct if_sdio_packet) - 4)) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The transfer must be in one transaction or the firmware
|
2008-06-28 17:52:45 +07:00
|
|
|
* goes suicidal. There's no way to guarantee that for all
|
|
|
|
* controllers, but we can at least try.
|
2007-10-18 03:24:24 +07:00
|
|
|
*/
|
2008-06-28 17:52:45 +07:00
|
|
|
size = sdio_align_size(card->func, nb + 4);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
packet = kzalloc(sizeof(struct if_sdio_packet) + size,
|
|
|
|
GFP_ATOMIC);
|
|
|
|
if (!packet) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
packet->next = NULL;
|
|
|
|
packet->nb = size;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* SDIO specific header.
|
|
|
|
*/
|
|
|
|
packet->buffer[0] = (nb + 4) & 0xff;
|
|
|
|
packet->buffer[1] = ((nb + 4) >> 8) & 0xff;
|
|
|
|
packet->buffer[2] = type;
|
|
|
|
packet->buffer[3] = 0;
|
|
|
|
|
|
|
|
memcpy(packet->buffer + 4, buf, nb);
|
|
|
|
|
|
|
|
spin_lock_irqsave(&card->lock, flags);
|
|
|
|
|
|
|
|
if (!card->packets)
|
|
|
|
card->packets = packet;
|
|
|
|
else {
|
|
|
|
cur = card->packets;
|
|
|
|
while (cur->next)
|
|
|
|
cur = cur->next;
|
|
|
|
cur->next = packet;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case MVMS_CMD:
|
|
|
|
priv->dnld_sent = DNLD_CMD_SENT;
|
|
|
|
break;
|
|
|
|
case MVMS_DAT:
|
|
|
|
priv->dnld_sent = DNLD_DATA_SENT;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
lbs_deb_sdio("unknown packet type %d\n", (int)type);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&card->lock, flags);
|
|
|
|
|
2009-02-21 00:27:38 +07:00
|
|
|
queue_work(card->workqueue, &card->packet_worker);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-10-01 10:04:38 +07:00
|
|
|
static int if_sdio_enter_deep_sleep(struct lbs_private *priv)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct cmd_header cmd;
|
|
|
|
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
|
|
|
|
|
|
lbs_deb_sdio("send DEEP_SLEEP command\n");
|
|
|
|
ret = __lbs_cmd(priv, CMD_802_11_DEEP_SLEEP, &cmd, sizeof(cmd),
|
|
|
|
lbs_cmd_copyback, (unsigned long) &cmd);
|
|
|
|
if (ret)
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("DEEP_SLEEP cmd failed\n");
|
2009-10-01 10:04:38 +07:00
|
|
|
|
|
|
|
mdelay(200);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int if_sdio_exit_deep_sleep(struct lbs_private *priv)
|
|
|
|
{
|
|
|
|
struct if_sdio_card *card = priv->card;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
sdio_claim_host(card->func);
|
|
|
|
|
|
|
|
sdio_writeb(card->func, HOST_POWER_UP, CONFIGURATION_REG, &ret);
|
|
|
|
if (ret)
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("sdio_writeb failed!\n");
|
2009-10-01 10:04:38 +07:00
|
|
|
|
|
|
|
sdio_release_host(card->func);
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int if_sdio_reset_deep_sleep_wakeup(struct lbs_private *priv)
|
|
|
|
{
|
|
|
|
struct if_sdio_card *card = priv->card;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
sdio_claim_host(card->func);
|
|
|
|
|
|
|
|
sdio_writeb(card->func, 0, CONFIGURATION_REG, &ret);
|
|
|
|
if (ret)
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("sdio_writeb failed!\n");
|
2009-10-01 10:04:38 +07:00
|
|
|
|
|
|
|
sdio_release_host(card->func);
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
/*******************************************************************/
|
|
|
|
/* SDIO callbacks */
|
|
|
|
/*******************************************************************/
|
|
|
|
|
|
|
|
static void if_sdio_interrupt(struct sdio_func *func)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct if_sdio_card *card;
|
|
|
|
u8 cause;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
|
|
|
card = sdio_get_drvdata(func);
|
|
|
|
|
|
|
|
cause = sdio_readb(card->func, IF_SDIO_H_INT_STATUS, &ret);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
lbs_deb_sdio("interrupt: 0x%X\n", (unsigned)cause);
|
|
|
|
|
|
|
|
sdio_writeb(card->func, ~cause, IF_SDIO_H_INT_STATUS, &ret);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ignore the define name, this really means the card has
|
|
|
|
* successfully received the command.
|
|
|
|
*/
|
2009-10-01 10:04:38 +07:00
|
|
|
card->priv->is_activity_detected = 1;
|
2007-12-06 21:36:11 +07:00
|
|
|
if (cause & IF_SDIO_H_INT_DNLD)
|
|
|
|
lbs_host_to_card_done(card->priv);
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
if (cause & IF_SDIO_H_INT_UPLD) {
|
|
|
|
ret = if_sdio_card_to_host(card);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int if_sdio_probe(struct sdio_func *func,
|
|
|
|
const struct sdio_device_id *id)
|
|
|
|
{
|
|
|
|
struct if_sdio_card *card;
|
2007-11-23 21:43:44 +07:00
|
|
|
struct lbs_private *priv;
|
2007-10-18 03:24:24 +07:00
|
|
|
int ret, i;
|
|
|
|
unsigned int model;
|
|
|
|
struct if_sdio_packet *packet;
|
2010-04-06 15:52:44 +07:00
|
|
|
struct mmc_host *host = func->card->host;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
|
|
|
for (i = 0;i < func->card->num_info;i++) {
|
|
|
|
if (sscanf(func->card->info[i],
|
|
|
|
"802.11 SDIO ID: %x", &model) == 1)
|
|
|
|
break;
|
|
|
|
if (sscanf(func->card->info[i],
|
|
|
|
"ID: %x", &model) == 1)
|
|
|
|
break;
|
2009-05-20 09:48:18 +07:00
|
|
|
if (!strcmp(func->card->info[i], "IBIS Wireless SDIO Card")) {
|
2010-08-08 09:15:52 +07:00
|
|
|
model = MODEL_8385;
|
2009-05-20 09:48:18 +07:00
|
|
|
break;
|
|
|
|
}
|
2007-10-18 03:24:24 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (i == func->card->num_info) {
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("unable to identify card model\n");
|
2007-10-18 03:24:24 +07:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
card = kzalloc(sizeof(struct if_sdio_card), GFP_KERNEL);
|
|
|
|
if (!card)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
card->func = func;
|
|
|
|
card->model = model;
|
2009-05-22 01:32:34 +07:00
|
|
|
|
|
|
|
switch (card->model) {
|
2010-08-08 09:15:52 +07:00
|
|
|
case MODEL_8385:
|
2009-05-22 01:32:34 +07:00
|
|
|
card->scratch_reg = IF_SDIO_SCRATCH_OLD;
|
|
|
|
break;
|
2010-08-08 09:15:52 +07:00
|
|
|
case MODEL_8686:
|
2009-05-22 01:32:34 +07:00
|
|
|
card->scratch_reg = IF_SDIO_SCRATCH;
|
|
|
|
break;
|
2010-08-08 09:15:52 +07:00
|
|
|
case MODEL_8688:
|
2009-05-22 01:32:34 +07:00
|
|
|
default: /* for newer chipsets */
|
|
|
|
card->scratch_reg = IF_SDIO_FW_STATUS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
spin_lock_init(&card->lock);
|
2009-02-21 00:27:38 +07:00
|
|
|
card->workqueue = create_workqueue("libertas_sdio");
|
2007-10-18 03:24:24 +07:00
|
|
|
INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
|
|
|
|
|
2010-08-08 09:15:52 +07:00
|
|
|
/* Check if we support this card */
|
|
|
|
for (i = 0; i < ARRAY_SIZE(fw_table); i++) {
|
|
|
|
if (card->model == fw_table[i].model)
|
2007-10-18 03:24:24 +07:00
|
|
|
break;
|
|
|
|
}
|
2010-08-08 09:15:52 +07:00
|
|
|
if (i == ARRAY_SIZE(fw_table)) {
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("unknown card model 0x%x\n", card->model);
|
2007-10-18 03:24:24 +07:00
|
|
|
ret = -ENODEV;
|
|
|
|
goto free;
|
|
|
|
}
|
|
|
|
|
|
|
|
sdio_claim_host(func);
|
|
|
|
|
|
|
|
ret = sdio_enable_func(func);
|
|
|
|
if (ret)
|
|
|
|
goto release;
|
|
|
|
|
|
|
|
ret = sdio_claim_irq(func, if_sdio_interrupt);
|
|
|
|
if (ret)
|
|
|
|
goto disable;
|
|
|
|
|
2010-04-06 15:52:44 +07:00
|
|
|
/* For 1-bit transfers to the 8686 model, we need to enable the
|
|
|
|
* interrupt flag in the CCCR register. Set the MMC_QUIRK_LENIENT_FN0
|
|
|
|
* bit to allow access to non-vendor registers. */
|
2010-08-08 09:15:52 +07:00
|
|
|
if ((card->model == MODEL_8686) &&
|
2010-04-06 15:52:44 +07:00
|
|
|
(host->caps & MMC_CAP_SDIO_IRQ) &&
|
|
|
|
(host->ios.bus_width == MMC_BUS_WIDTH_1)) {
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
|
|
|
|
reg = sdio_f0_readb(func, SDIO_CCCR_IF, &ret);
|
|
|
|
if (ret)
|
|
|
|
goto release_int;
|
|
|
|
|
|
|
|
reg |= SDIO_BUS_ECSI;
|
|
|
|
sdio_f0_writeb(func, reg, SDIO_CCCR_IF, &ret);
|
|
|
|
if (ret)
|
|
|
|
goto release_int;
|
|
|
|
}
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
card->ioport = sdio_readb(func, IF_SDIO_IOPORT, &ret);
|
|
|
|
if (ret)
|
|
|
|
goto release_int;
|
|
|
|
|
|
|
|
card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 1, &ret) << 8;
|
|
|
|
if (ret)
|
|
|
|
goto release_int;
|
|
|
|
|
|
|
|
card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 2, &ret) << 16;
|
|
|
|
if (ret)
|
|
|
|
goto release_int;
|
|
|
|
|
|
|
|
sdio_release_host(func);
|
|
|
|
|
|
|
|
sdio_set_drvdata(func, card);
|
|
|
|
|
|
|
|
lbs_deb_sdio("class = 0x%X, vendor = 0x%X, "
|
|
|
|
"device = 0x%X, model = 0x%X, ioport = 0x%X\n",
|
|
|
|
func->class, func->vendor, func->device,
|
|
|
|
model, (unsigned)card->ioport);
|
|
|
|
|
|
|
|
ret = if_sdio_prog_firmware(card);
|
|
|
|
if (ret)
|
|
|
|
goto reclaim;
|
|
|
|
|
2007-11-16 06:05:47 +07:00
|
|
|
priv = lbs_add_card(card, &func->dev);
|
2007-10-18 03:24:24 +07:00
|
|
|
if (!priv) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto reclaim;
|
|
|
|
}
|
|
|
|
|
|
|
|
card->priv = priv;
|
|
|
|
|
|
|
|
priv->card = card;
|
|
|
|
priv->hw_host_to_card = if_sdio_host_to_card;
|
2009-10-01 10:04:38 +07:00
|
|
|
priv->enter_deep_sleep = if_sdio_enter_deep_sleep;
|
|
|
|
priv->exit_deep_sleep = if_sdio_exit_deep_sleep;
|
|
|
|
priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup;
|
2007-10-18 03:24:24 +07:00
|
|
|
|
2009-05-20 09:48:19 +07:00
|
|
|
sdio_claim_host(func);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get rx_unit if the chip is SD8688 or newer.
|
|
|
|
* SD8385 & SD8686 do not have rx_unit.
|
|
|
|
*/
|
2010-08-08 09:15:52 +07:00
|
|
|
if ((card->model != MODEL_8385)
|
|
|
|
&& (card->model != MODEL_8686))
|
2009-05-20 09:48:19 +07:00
|
|
|
card->rx_unit = if_sdio_read_rx_unit(card);
|
|
|
|
else
|
|
|
|
card->rx_unit = 0;
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
/*
|
|
|
|
* Enable interrupts now that everything is set up
|
|
|
|
*/
|
|
|
|
sdio_writeb(func, 0x0f, IF_SDIO_H_INT_MASK, &ret);
|
|
|
|
sdio_release_host(func);
|
|
|
|
if (ret)
|
|
|
|
goto reclaim;
|
|
|
|
|
2010-10-29 20:57:28 +07:00
|
|
|
priv->fw_ready = 1;
|
|
|
|
|
2009-05-20 09:48:20 +07:00
|
|
|
/*
|
|
|
|
* FUNC_INIT is required for SD8688 WLAN/BT multiple functions
|
|
|
|
*/
|
2010-08-08 09:15:52 +07:00
|
|
|
if (card->model == MODEL_8688) {
|
2009-06-02 08:04:36 +07:00
|
|
|
struct cmd_header cmd;
|
|
|
|
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
|
|
|
|
|
|
lbs_deb_sdio("send function INIT command\n");
|
|
|
|
if (__lbs_cmd(priv, CMD_FUNC_INIT, &cmd, sizeof(cmd),
|
|
|
|
lbs_cmd_copyback, (unsigned long) &cmd))
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_alert("CMD_FUNC_INIT cmd failed\n");
|
2009-06-02 08:04:36 +07:00
|
|
|
}
|
2009-05-20 09:48:20 +07:00
|
|
|
|
2007-11-16 06:05:47 +07:00
|
|
|
ret = lbs_start_card(priv);
|
2007-10-18 03:24:24 +07:00
|
|
|
if (ret)
|
|
|
|
goto err_activate_card;
|
|
|
|
|
|
|
|
out:
|
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
err_activate_card:
|
2009-02-21 00:27:38 +07:00
|
|
|
flush_workqueue(card->workqueue);
|
|
|
|
lbs_remove_card(priv);
|
2007-10-18 03:24:24 +07:00
|
|
|
reclaim:
|
|
|
|
sdio_claim_host(func);
|
|
|
|
release_int:
|
|
|
|
sdio_release_irq(func);
|
|
|
|
disable:
|
|
|
|
sdio_disable_func(func);
|
|
|
|
release:
|
|
|
|
sdio_release_host(func);
|
|
|
|
free:
|
2009-02-21 00:27:38 +07:00
|
|
|
destroy_workqueue(card->workqueue);
|
2007-10-18 03:24:24 +07:00
|
|
|
while (card->packets) {
|
|
|
|
packet = card->packets;
|
|
|
|
card->packets = card->packets->next;
|
|
|
|
kfree(packet);
|
|
|
|
}
|
|
|
|
|
2010-08-12 12:04:33 +07:00
|
|
|
if (card->helper_allocated)
|
|
|
|
kfree(card->helper);
|
|
|
|
if (card->firmware_allocated)
|
|
|
|
kfree(card->firmware);
|
2007-10-18 03:24:24 +07:00
|
|
|
kfree(card);
|
|
|
|
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void if_sdio_remove(struct sdio_func *func)
|
|
|
|
{
|
|
|
|
struct if_sdio_card *card;
|
|
|
|
struct if_sdio_packet *packet;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
|
|
|
card = sdio_get_drvdata(func);
|
|
|
|
|
2010-08-08 09:15:52 +07:00
|
|
|
if (user_rmmod && (card->model == MODEL_8688)) {
|
2009-06-02 08:04:36 +07:00
|
|
|
/*
|
|
|
|
* FUNC_SHUTDOWN is required for SD8688 WLAN/BT
|
|
|
|
* multiple functions
|
|
|
|
*/
|
|
|
|
struct cmd_header cmd;
|
|
|
|
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
|
|
|
|
|
|
lbs_deb_sdio("send function SHUTDOWN command\n");
|
|
|
|
if (__lbs_cmd(card->priv, CMD_FUNC_SHUTDOWN,
|
|
|
|
&cmd, sizeof(cmd), lbs_cmd_copyback,
|
|
|
|
(unsigned long) &cmd))
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_alert("CMD_FUNC_SHUTDOWN cmd failed\n");
|
2009-06-02 08:04:36 +07:00
|
|
|
}
|
2009-05-20 09:48:20 +07:00
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
lbs_deb_sdio("call remove card\n");
|
2009-06-02 08:04:36 +07:00
|
|
|
lbs_stop_card(card->priv);
|
2007-11-16 06:05:47 +07:00
|
|
|
lbs_remove_card(card->priv);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
2009-02-21 00:27:38 +07:00
|
|
|
flush_workqueue(card->workqueue);
|
|
|
|
destroy_workqueue(card->workqueue);
|
2007-10-18 03:24:24 +07:00
|
|
|
|
|
|
|
sdio_claim_host(func);
|
|
|
|
sdio_release_irq(func);
|
|
|
|
sdio_disable_func(func);
|
|
|
|
sdio_release_host(func);
|
|
|
|
|
|
|
|
while (card->packets) {
|
|
|
|
packet = card->packets;
|
|
|
|
card->packets = card->packets->next;
|
|
|
|
kfree(packet);
|
|
|
|
}
|
|
|
|
|
2010-08-12 12:04:33 +07:00
|
|
|
if (card->helper_allocated)
|
|
|
|
kfree(card->helper);
|
|
|
|
if (card->firmware_allocated)
|
|
|
|
kfree(card->firmware);
|
2007-10-18 03:24:24 +07:00
|
|
|
kfree(card);
|
|
|
|
|
|
|
|
lbs_deb_leave(LBS_DEB_SDIO);
|
|
|
|
}
|
|
|
|
|
2010-05-19 17:24:38 +07:00
|
|
|
static int if_sdio_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
|
|
int ret;
|
|
|
|
struct if_sdio_card *card = sdio_get_drvdata(func);
|
|
|
|
|
|
|
|
mmc_pm_flag_t flags = sdio_get_host_pm_caps(func);
|
|
|
|
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_info("%s: suspend: PM flags = 0x%x\n", sdio_func_id(func), flags);
|
2010-05-19 17:24:38 +07:00
|
|
|
|
|
|
|
/* If we aren't being asked to wake on anything, we should bail out
|
|
|
|
* and let the SD stack power down the card.
|
|
|
|
*/
|
|
|
|
if (card->priv->wol_criteria == EHS_REMOVE_WAKEUP) {
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_info("Suspend without wake params -- powering down card\n");
|
2010-05-19 17:24:38 +07:00
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(flags & MMC_PM_KEEP_POWER)) {
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_err("%s: cannot remain alive while host is suspended\n",
|
|
|
|
sdio_func_id(func));
|
2010-05-19 17:24:38 +07:00
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = lbs_suspend(card->priv);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int if_sdio_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct sdio_func *func = dev_to_sdio_func(dev);
|
|
|
|
struct if_sdio_card *card = sdio_get_drvdata(func);
|
|
|
|
int ret;
|
|
|
|
|
2011-05-03 06:49:14 +07:00
|
|
|
pr_info("%s: resume: we're back\n", sdio_func_id(func));
|
2010-05-19 17:24:38 +07:00
|
|
|
|
|
|
|
ret = lbs_resume(card->priv);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dev_pm_ops if_sdio_pm_ops = {
|
|
|
|
.suspend = if_sdio_suspend,
|
|
|
|
.resume = if_sdio_resume,
|
|
|
|
};
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
static struct sdio_driver if_sdio_driver = {
|
|
|
|
.name = "libertas_sdio",
|
|
|
|
.id_table = if_sdio_ids,
|
|
|
|
.probe = if_sdio_probe,
|
|
|
|
.remove = if_sdio_remove,
|
2010-05-19 17:24:38 +07:00
|
|
|
.drv = {
|
|
|
|
.pm = &if_sdio_pm_ops,
|
|
|
|
},
|
2007-10-18 03:24:24 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
/*******************************************************************/
|
|
|
|
/* Module functions */
|
|
|
|
/*******************************************************************/
|
|
|
|
|
2007-11-21 05:43:45 +07:00
|
|
|
static int __init if_sdio_init_module(void)
|
2007-10-18 03:24:24 +07:00
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
|
|
|
printk(KERN_INFO "libertas_sdio: Libertas SDIO driver\n");
|
|
|
|
printk(KERN_INFO "libertas_sdio: Copyright Pierre Ossman\n");
|
|
|
|
|
|
|
|
ret = sdio_register_driver(&if_sdio_driver);
|
|
|
|
|
2009-06-02 08:04:36 +07:00
|
|
|
/* Clear the flag in case user removes the card. */
|
|
|
|
user_rmmod = 0;
|
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2007-11-21 05:43:45 +07:00
|
|
|
static void __exit if_sdio_exit_module(void)
|
2007-10-18 03:24:24 +07:00
|
|
|
{
|
|
|
|
lbs_deb_enter(LBS_DEB_SDIO);
|
|
|
|
|
2009-06-02 08:04:36 +07:00
|
|
|
/* Set the flag as user is removing this module. */
|
|
|
|
user_rmmod = 1;
|
2009-05-20 09:48:20 +07:00
|
|
|
|
2007-10-18 03:24:24 +07:00
|
|
|
sdio_unregister_driver(&if_sdio_driver);
|
|
|
|
|
|
|
|
lbs_deb_leave(LBS_DEB_SDIO);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(if_sdio_init_module);
|
|
|
|
module_exit(if_sdio_exit_module);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("Libertas SDIO WLAN Driver");
|
|
|
|
MODULE_AUTHOR("Pierre Ossman");
|
|
|
|
MODULE_LICENSE("GPL");
|