blob: ecb71075d455da6cb09573145d3eab831c1fad5e [file] [log] [blame]
/**
*
* Name: sdiobus.c
* Project: Wireless LAN, Bus driver for SDIO interface
* Version: $Revision: 1.1 $
* Date: $Date: 2007/01/18 09:21:35 $
* Purpose: Bus driver for SDIO interface
*
* Copyright (C) 2003-2009, Marvell International Ltd.
*
* This software file (the "File") is distributed by Marvell International
* Ltd. under the terms of the GNU General Public License Version 2, June 1991
* (the "License"). You may use, redistribute and/or modify this File in
* accordance with the terms and conditions of the License, a copy of which
* is available by writing to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
* worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
* IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
* ARE EXPRESSLY DISCLAIMED. The License provides additional details about
* this warranty disclaimer.
*
*/
/******************************************************************************
*
* History:
*
* $Log: sdiobus.c,v $
* Revision 1.1 2007/01/18 09:21:35 pweber
* Put under CVS control
*
* Revision 1.2 2005/12/08 12:01:00 ebauer
* Driver ckecks AND update voltage registry value if not correct
*
* Revision 1.1 2005/10/07 08:43:47 jschmalz
* Put SDIO with FPGA under CVS control
*
*
******************************************************************************/
#ifndef _lint
static const char SysKonnectFileId[] = "@(#)" __FILE__ " (C) Marvell ";
#endif /* !_lint */
#include "diagvers.h" /* diag version control */
#include "h/skdrv1st.h"
#include "h/skdrv2nd.h"
#include <linux/proc_fs.h> // proc filesystem
#ifdef DBG
SK_U32 stdDebugLevel = DBG_DEFAULT;
#endif // #ifdef DBG
#define USE_DEBOUNCE_CARD_IN
/*--- mmoser 12/21/2006 ---*/
#define MAX_DEBOUNCE_TIME 100
DECLARE_MUTEX(sd_if_sema);
DECLARE_MUTEX(sd_client_sema);
static int sdio_major = 0;
int mrvlsdio_open(struct inode *inode, struct file *filp);
int mrvlsdio_release(struct inode *inode, struct file *filp);
int SDIOBus_IoCtl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg);
static void call_client_probe(PSD_DEVICE_DATA pDev, int fn);
//
// Module parameters
//
static int bus_type = SDIO_BUS_TYPE;
static int bus_width = SDIO_1_BIT;
int block_size = SD_BLOCK_SIZE;
int dma_support = 1;
static int clock_speed = 0;
static int gpi_0_mode = 1; // edge triggered
static int gpi_0_level = 1; // high->low
static int gpi_1_mode = 1; // edge triggered
static int gpi_1_level = 1; // high-->low
#define INTMODE_SDIO 0
#define INTMODE_GPIO 1
int intmode = 0; // GPIO intmode enabled
int gpiopin = 0; // GPIO pin number for SDIO alternative IRQ
EXPORT_SYMBOL(intmode);
EXPORT_SYMBOL(gpiopin);
//static int debug_flags = 0;
static int debug_flags = 0x0c;
static int sdio_voltage = FPGA_POWER_REG_3_3_V;
DECLARE_WAIT_QUEUE_HEAD(add_remove_queue);
// kernel 2.6.x
module_param(bus_type, int, S_IRUGO);
module_param(bus_width, int, S_IRUGO);
module_param(block_size, int, S_IRUGO);
module_param(clock_speed, int, S_IRUGO);
module_param(dma_support, int, S_IRUGO);
module_param(debug_flags, int, S_IRUGO);
module_param(sdio_voltage, int, S_IRUGO);
module_param(gpi_0_mode, int, S_IRUGO); // 0 = level triggered 1 = edge
// triggered
module_param(gpi_0_level, int, S_IRUGO); // 0 = low->high or low 1 =
// high->low or high
module_param(gpi_1_mode, int, S_IRUGO); // 0 = level triggered 1 = edge
// triggered
module_param(gpi_1_level, int, S_IRUGO); // 0 = low->high or low 1 =
// high->low or high
module_param(intmode, int, S_IRUGO);
module_param(gpiopin, int, S_IRUGO);
// prototypes
// for filling the "/proc/mrvsdio" entry
int mrvlsdio_read_procmem(char *buf, char **start, off_t offset, int count,
int *eof, void *data);
int sd_match_device(sd_driver * drv, PSD_DEVICE_DATA dev);
// static PSD_DEVICE_DATA gSD_dev_data = NULL;
struct list_head sd_dev_list;
spinlock_t sd_dev_list_lock;
unsigned long sd_dev_list_lock_flags;
struct list_head sd_client_list;
spinlock_t sd_client_list_lock;
unsigned long sd_client_list_lock_flags;
static int SDIOThread(void *data);
static int probe(struct pci_dev *dev, const struct pci_device_id *id);
static void remove(struct pci_dev *dev);
static DECLARE_COMPLETION(on_exit);
// kernel 2.6
static struct pci_device_id ids[] = {
{PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x8000),},
{0,}
};
MODULE_DEVICE_TABLE(pci, ids);
/*
* The fops
*/
struct file_operations mrvlsdio_fops = {
llseek:NULL,
read:NULL,
write:NULL,
ioctl:SDIOBus_IoCtl,
mmap:NULL,
open:mrvlsdio_open,
release:mrvlsdio_release,
};
/*
* Open and close
*/
int
mrvlsdio_open(struct inode *inode, struct file *filp)
{
ENTER();
if (list_empty(&sd_dev_list)) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"mrvlsdio_open: sd_dev_list is empty -> Adapter not in CardBus slot!\n"));
return -ENODEV;
}
filp->f_op = &mrvlsdio_fops;
// Pointer to first device in list
// This works ONLY with one CardBus slot and one SDIO2PCI Adapter !!!
// With more SDIO2PCI Adapter we need to implement virtual devices
filp->private_data = sd_dev_list.next;
LEAVE();
return 0; /* success */
}
int
mrvlsdio_release(struct inode *inode, struct file *filp)
{
ENTER();
LEAVE();
return 0;
}
/*
* The ioctl() implementation
*/
int
SDIOBus_IoCtl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
int ret = 0;
UCHAR ucValue;
ULONG ulValue;
ULONG cnt;
PSD_DEVICE_DATA sd_dev;
ENTER();
sd_dev = (SD_DEVICE_DATA *) filp->private_data;
if (sd_dev == NULL) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "SDIOBus_IoCtl : sd_dev= 0x%p !!!!!!!!!!!!!!!!\n",
sd_dev));
return -ENOTTY;
}
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"SDIOBus_IoCtl : sd_dev= 0x%p sd_dev->IOBase[0]= 0x%p\n", sd_dev,
sd_dev->IOBase[0]));
switch (cmd) {
case IOCTL_SDIOBUSENUM_SET_VOLTAGE:
{
ret = get_user(ucValue, (int *) arg);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"SDIOBus_IoCtl : IOCTL_SDIOBUSENUM_SET_VOLTAGE: 0x%2.02X\n",
ucValue));
MEM_WRITE_UCHAR(sd_dev, SK_SLOT_0, FPGA_POWER_REG_DATA, ucValue);
MEM_WRITE_UCHAR(sd_dev, SK_SLOT_0, FPGA_POWER_REG_CMD,
FPGA_POWER_REG_CMD_START);
DBGPRINT(DBG_LOAD, (KERN_DEBUG "wait for stable voltage\n"));
cnt = 0;
do {
MEM_READ_UCHAR(sd_dev, SK_SLOT_0, FPGA_POWER_REG_STATUS,
&ucValue);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "PowerRegulatorControl: 0x%x\n", ucValue));
} while (++cnt < 10000 && (ucValue & FPGA_POWER_REG_STABLE) == 0);
DBGPRINT(DBG_LOAD,
("IOCTL_SDIOBUSENUM_SET_VOLTAGE: cnt=%d\n", cnt));
break;
}
case IOCTL_SDIOBUSENUM_GET_BOARDREV:
{
MEM_READ_ULONG(sd_dev, SK_SLOT_0, FPGA_CARD_REVISION, &ulValue);
ret = put_user(ulValue, (int *) arg);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"SDIOBus_IoCtl : IOCTL_SDIOBUSENUM_GET_BOARDREV 0x%8.08X\n",
ulValue));
break;
}
case IOCTL_SDIOBUSENUM_GET_JUMPER:
{
DBGPRINT(DBG_LOAD, ("IOCTL_SDIOBUSENUM_GET_JUMPER\n"));
MEM_READ_ULONG(sd_dev, SK_SLOT_0, FPGA_CARD_REVISION, &ulValue);
ret = put_user(ulValue, (int *) arg);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"SDIOBus_IoCtl : IOCTL_SDIOBUSENUM_GET_JUMPER 0x%8.08X\n",
ulValue));
break;
}
case IOCTL_SDIOBUSENUM_SET_BUSTYPE:
{
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"SDIOBus_IoCtl : IOCTL_SDIOBUSENUM_SET_BUSTYPE --> IGNORED!\n"));
break;
}
case IOCTL_SDIOBUSENUM_SET_BUSWIDTH:
{
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"SDIOBus_IoCtl : IOCTL_SDIOBUSENUM_SET_BUSWIDTH --> IGNORED!\n"));
break;
}
case IOCTL_SDIOBUSENUM_SET_CLKSPEED:
{
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"SDIOBus_IoCtl : IOCTL_SDIOBUSENUM_SET_CLKSPEED --> IGNORED!\n"));
break;
}
default:
{
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "SDIOBus_IoCtl : invalid cmd=%d (0x%8.08X)!\n",
cmd, cmd));
return -ENOTTY;
}
}
LEAVE();
return ret;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
static void
card_out_work(PSD_DEVICE_DATA pDev)
#else
static void
card_out_work(struct work_struct *work)
#endif
{
int j;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,19)
PSD_DEVICE_DATA pDev = container_of(work, SD_DEVICE_DATA, card_out_work);
#endif
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "%s: CARD_REMOVE_EVENT received.\n", __FUNCTION__));
for (j = 1; j <= pDev->number_of_functions; j++) {
if (down_interruptible(&sd_client_sema))
return;
if (pDev->sd_dev[j].drv != NULL && pDev->sd_dev[j].drv->remove != NULL) {
if (pDev->sd_dev[j].remove_called == 0) {
DBGPRINT(DBG_LOAD | DBG_ERROR,
(KERN_DEBUG "%s: call remove() handler on fn=%d\n",
__FUNCTION__, j));
pDev->sd_dev[j].probe_called = 0;
pDev->sd_dev[j].remove_called = 1;
pDev->sd_dev[j].dev = NULL;
pDev->sd_dev[j].drv->remove(&pDev->sd_dev[j]);
}
} else {
// If we have no remove() handler at this point, we will never get
// one.
// So discard the CARD_REMOVE_EVENT to avoid an endless loop.
// This situation is considered a bug in the client driver !!!!
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"%s: no remove() handler installed for fn=%d! Revise client driver!!!\n",
__FUNCTION__, j));
}
up(&sd_client_sema);
}
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
static void
card_irq_work(PSD_DEVICE_DATA pDev)
#else
static void
card_irq_work(struct work_struct *work)
#endif
{
SK_U32 irq_handler_called;
SK_U8 int_pending;
SK_U32 ix;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,19)
PSD_DEVICE_DATA pDev = container_of(work, SD_DEVICE_DATA, irq_work);
#endif
irq_handler_called = 0;
// Read function specific interrupt information
GET_IF_SEMA_NO_RC();
if (SDHost_CMD52_Read(pDev, INT_PENDING_REG, 0, &int_pending)) {
REL_IF_SEMA();
DBGPRINT(DBG_IRQ,
(KERN_DEBUG "INT Pending = 0x%2.02X\n ", int_pending));
for (ix = 1; ix <= 7; ix++) {
if (int_pending & INT(ix)) {
// Function ix interrupt is pending and enabled
if (pDev->irq_device_cache[ix] != NULL) {
DBGPRINT(DBG_IRQ,
(KERN_DEBUG
"Call registered interrupt handler for function %d.\n",
ix));
irq_handler_called++;
if (pDev->irq_device_cache[ix]->functions[ix].int_handler !=
NULL) {
pDev->irq_device_cache[ix]->functions[ix].
int_handler(pDev->irq_device_cache[ix],
pDev->irq_device_cache[ix]->
pCurrent_Ids,
pDev->irq_device_cache[ix]->
functions[ix].context);
}
}
}
}
} else
REL_IF_SEMA();
if (irq_handler_called == 0) {
DBGPRINT(DBG_IRQ,
(KERN_DEBUG "No interrupt handler registered. (sd_dev=%p)\n",
pDev));
SDHost_EnableInterrupt(pDev, pDev->lastIRQSignalMask);
}
}
static struct pci_driver mrvlsdio = {
.name = "mrvlsdio",
.id_table = ids,
.probe = probe,
.remove = remove,
};
static int
probe(struct pci_dev *dev, const struct pci_device_id *id)
{
int i, ret;
SK_U8 ucValue;
SK_U32 mem;
SK_U32 ulValue;
PSD_DEVICE_DATA pTmpDev;
DBGPRINT(DBG_LOAD, (KERN_DEBUG "mrvlsdio : probe()\n"));
if (pci_enable_device(dev)) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: pci_enable_device() failed\n"));
return -EIO;
}
if (pci_set_dma_mask(dev, 0xffffffff)) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: 32-bit PCI DMA not supported"));
pci_disable_device(dev);
return -EIO;
}
pci_set_master(dev);
/*--- mmoser 6/21/2007 ---*/
pTmpDev = (PSD_DEVICE_DATA) kmalloc(sizeof(SD_DEVICE_DATA), GFP_KERNEL);
if (pTmpDev == NULL) {
return -ENOMEM;
}
#ifdef SDIO_MEM_TRACE
printk(">>> kmalloc(SD_DEVICE_DATA)\n");
#endif
memset(pTmpDev, 0, sizeof(SD_DEVICE_DATA));
pTmpDev->IOBaseIx = 0;
/*--- mmoser 10/11/2006 ---*/
pTmpDev->dev = dev;
pTmpDev->workqueue = create_workqueue("MRVL-SDIO");
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
INIT_WORK(&pTmpDev->irq_work, (void (*)(void *)) card_irq_work, pTmpDev);
INIT_WORK(&pTmpDev->card_out_work, (void (*)(void *)) card_out_work,
pTmpDev);
#else
INIT_WORK(&pTmpDev->irq_work, card_irq_work);
INIT_WORK(&pTmpDev->card_out_work, card_out_work);
#endif
if (pci_request_regions(dev, "MRVL-SDIO")) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: pci_request_regions() failed.\n"));
pci_disable_device(dev);
return -EIO;
}
for (i = 0; i < 5; i++) {
mem = pci_resource_start(dev, i);
if (mem != 0) {
if (IORESOURCE_MEM & pci_resource_flags(dev, i)) {
pTmpDev->IOBaseLength[pTmpDev->IOBaseIx] =
pci_resource_end(dev, i) - mem + 1;
pTmpDev->IOBase[pTmpDev->IOBaseIx] =
ioremap_nocache(mem,
pTmpDev->IOBaseLength[pTmpDev->IOBaseIx]);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"mrvlsdio: IOBase[%d] = 0x%8.08X IOBaseLength = %u\n",
i, (SK_U32) pTmpDev->IOBase[pTmpDev->IOBaseIx],
pTmpDev->IOBaseLength[pTmpDev->IOBaseIx]));
pTmpDev->IOBaseIx++;
if (pTmpDev->IOBaseIx >= 2) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"mrvlsdio : Too many memory address ranges!\n"));
pTmpDev->IOBaseIx = 0;
}
}
}
}
// Dummy read. Function return SUCCESS but value is messy !!!
pci_read_config_byte(dev, PCI_INTERRUPT_LINE,
(SK_U8 *) & pTmpDev->Interrupt);
for (i = 0; i < 3; i++) {
ret =
pci_read_config_byte(dev, PCI_INTERRUPT_LINE,
(SK_U8 *) & pTmpDev->Interrupt);
if (ret) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"mrvlsdio: Failed to retrieve Interrupt Line (%d) %d ret=%d\n",
i, pTmpDev->Interrupt, ret));
UDELAY(100);
continue;
} else {
break;
}
}
if (i >= 3) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"mrvlsdio: Failed to retrieve Interrupt Line (%d) ret=%d\n",
i, ret));
return -ENODEV;
}
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: Interrupt Line = %d (%d)\n",
pTmpDev->Interrupt, i));
MEM_READ_UCHAR(pTmpDev, SK_SLOT_0, FPGA_CARD_REVISION, &ucValue);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: CardRevision: 0x%X (sd_dev_data=0x%p)\n",
ucValue, &pTmpDev));
pTmpDev->bus_type = bus_type;
pTmpDev->bus_width = bus_width;
pTmpDev->ClockSpeed = clock_speed;
pTmpDev->debug_flags = debug_flags;
pTmpDev->sdio_voltage = sdio_voltage;
pci_read_config_byte(dev, 0x0c, &ucValue);
if (ucValue == 0)
pci_write_config_byte(dev, 0x0c, 2);
pci_read_config_byte(dev, 0x0c, &ucValue);
// printk("config[0x0c]=0x%2.02X\n",ucValue);
pci_read_config_byte(dev, 0x0d, &ucValue);
// printk("config[0x0d]=0x%2.02X\n",ucValue);
printk("debug_flags=0x%8.08X\n", debug_flags);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: bus_type : SDIO%s\n",
bus_type == SDIO_BUS_TYPE ? "" : "/SPI"));
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: bus_width : %s\n",
bus_width == SDIO_4_BIT ? "4-bit" : "1-bit"));
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: block_size : %d\n", block_size));
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: clock_speed : %d\n", clock_speed));
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: debug_flags : 0x%8.08X\n", debug_flags));
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: sdio_voltage : %d\n", sdio_voltage));
/*--- mmoser 3/12/2007 ---*/
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: gpi_0_mode : %s triggered\n",
gpi_0_mode == 0 ? "level" : "edge"));
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: gpi_0_level : %s\n",
gpi_0_level == 0 ? (gpi_0_mode ==
0 ? "low" : "low->high") : (gpi_0_mode ==
0 ? "high" :
"high->low")));
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: gpi_1_mode : %s triggered\n",
gpi_1_mode == 0 ? "level" : "edge"));
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: gpi_1_level : %s\n",
gpi_1_level == 0 ? (gpi_1_mode ==
0 ? "low" : "low->high") : (gpi_1_mode ==
0 ? "high" :
"high->low")));
// mmoser 2005-11-29
atomic_set(&pTmpDev->card_add_event, 0);
atomic_set(&pTmpDev->card_remove_event, 0);
/*--- mmoser 8/29/2006 ---*/
init_waitqueue_head(&pTmpDev->trans_complete_evt.wq);
init_waitqueue_head(&pTmpDev->cmd_complete_evt.wq);
init_waitqueue_head(&pTmpDev->thread_started_evt.wq);
/*--- mmoser 3/12/2007 ---*/
MEM_READ_ULONG(pTmpDev, SK_SLOT_0, 0x200, &ulValue);
ulValue &= 0x0FFFFFFF;
ulValue |=
((gpi_1_mode << 3 | gpi_1_level << 2 | gpi_0_mode << 1 | gpi_0_level) <<
28);
// printk("mrvlsdio: ulValue = 0x%8.08X\n",ulValue);
MEM_WRITE_ULONG(pTmpDev, SK_SLOT_0, 0x200, ulValue);
// printk("mrvlsdio: reg[0x200] = 0x%8.08X\n",ulValue);
spin_lock_init(&pTmpDev->sd_dev_lock);
spin_lock_irqsave(&sd_dev_list_lock, sd_dev_list_lock_flags);
list_add_tail((struct list_head *) pTmpDev, &sd_dev_list);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"%s@%d: sd_dev_list=%p sd_dev_list.prev=%p sd_dev_list.next=%p pTmpDev=%p\n",
__FUNCTION__, __LINE__, &sd_dev_list, sd_dev_list.prev,
sd_dev_list.next, pTmpDev));
spin_unlock_irqrestore(&sd_dev_list_lock, sd_dev_list_lock_flags);
SDHost_Init(pTmpDev);
// mmoser 2005-11-29
if (0 != request_irq(pTmpDev->Interrupt,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
SDIOBus_Isr, SA_SHIRQ,
#else
(irq_handler_t) SDIOBus_Isr, IRQF_SHARED,
#endif
MRVL_DEVICE_NAME, (PVOID) pTmpDev)
) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"mrvlsdio: Failed to register interrupt handler\n"));
return -ENODEV;
}
if (SDHost_isCardPresent(pTmpDev) == SK_TRUE) {
if (SDIOBus_CardPluggedIn(pTmpDev) == SK_TRUE) {
pTmpDev->CardIn = SK_TRUE;
}
SDHost_EnableInterrupt(pTmpDev, (STDHOST_NORMAL_IRQ_CARD_OUT_SIG_ENA |
STDHOST_NORMAL_IRQ_CARD_IN_SIG_ENA));
}
#ifdef SYSKT_DMA_MALIGN_TEST
pTmpDev->dma_tx_malign = 0;
pTmpDev->dma_rx_malign = 0;
// pTmpDev->dma_start_malign = 4096 - 32;
pTmpDev->dma_start_malign = 0;
#endif
// mmoser 2005-11-22 start the thread as the last action
// mmoser 2005-11-21
pTmpDev->stop_thread = SK_FALSE;
pTmpDev->thread_id = kernel_thread(SDIOThread,
pTmpDev,
(CLONE_FS | CLONE_FILES |
CLONE_SIGHAND));
if (pTmpDev->thread_id == 0) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "mrvlsdio: Failed to start kernel thread!\n"));
return -1;
} else {
if (SDHost_wait_event(pTmpDev, &pTmpDev->thread_started_evt, 1000)) {
pTmpDev->stop_thread = SK_TRUE;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
kill_pid(find_get_pid(pTmpDev->thread_id), SIGTERM, 1);
#else
kill_proc(pTmpDev->thread_id, SIGTERM, 1);
#endif
pTmpDev->thread_id = 0;
DBGPRINT(DBG_ERROR,
(KERN_DEBUG
"%s @ %d: Wait SDIOThread started ---> FAILED !\n",
__FUNCTION__, __LINE__));
return 0;
}
}
return 0;
}
static void
remove(struct pci_dev *dev)
{
struct list_head *pTmp;
struct list_head *pNext;
PSD_DEVICE_DATA pTmpDev;
int j;
SK_U8 ucValue;
SK_U32 cnt;
DBGPRINT(DBG_LOAD, (KERN_DEBUG "remove mrvlsdio ... \n"));
/* clean up any allocated resources and stuff here. like call
release_region(); */
/*
* iterate through the device list
* call remove() for each client driver
* remove device from list
* free allocated memory
*/
if (!list_empty(&sd_dev_list)) {
pTmp = sd_dev_list.next;
while (pTmp != &sd_dev_list) {
pNext = pTmp->next;
pTmpDev = (PSD_DEVICE_DATA) pTmp;
if (pTmpDev->dev == dev) {
pTmpDev->isRemoving = SK_TRUE;
for (j = 1; j <= pTmpDev->number_of_functions; j++) {
if (down_interruptible(&sd_client_sema))
return;
if (pTmpDev->sd_dev[j].drv != NULL &&
pTmpDev->sd_dev[j].drv->remove != NULL) {
if (pTmpDev->sd_dev[j].remove_called == 0) {
pTmpDev->sd_dev[j].probe_called = 0;
pTmpDev->sd_dev[j].remove_called = 1;
pTmpDev->sd_dev[j].dev = NULL;
pTmpDev->sd_dev[j].drv->remove(&pTmpDev->sd_dev[j]);
}
}
up(&sd_client_sema);
}
if (pTmpDev->thread_id) {
pTmpDev->stop_thread = SK_TRUE;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
kill_pid(find_get_pid(pTmpDev->thread_id), SIGTERM, 1);
#else
kill_proc(pTmpDev->thread_id, SIGTERM, 1);
#endif
wait_for_completion(&on_exit);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "%s: SDIOThread killed ... \n",
__FUNCTION__));
}
// Send I/O Card Reset
SDHost_CMD52_Write((PSD_DEVICE_DATA) pTmp, IO_ABORT_REG, FN0,
RES);
SDHost_DisableInterrupt((PSD_DEVICE_DATA) pTmp,
STDHOST_NORMAL_IRQ_ALL_SIG_ENA);
SDHost_SetClock((PSD_DEVICE_DATA) pTmp, 0);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
cancel_delayed_work(&pTmpDev->card_out_work);
cancel_delayed_work(&pTmpDev->irq_work);
#endif
flush_scheduled_work();
destroy_workqueue(pTmpDev->workqueue);
pTmpDev->workqueue = NULL;
// set host power voltage as low as possible
// (still need despite "turn off host power" below, b/c
// hotplug can reactivate host power independently in HW)
MEM_WRITE_UCHAR((PSD_DEVICE_DATA) pTmp, SK_SLOT_0,
FPGA_POWER_REG_DATA, FPGA_POWER_REG_0_7_V);
MEM_WRITE_UCHAR((PSD_DEVICE_DATA) pTmp, SK_SLOT_0,
FPGA_POWER_REG_CMD, FPGA_POWER_REG_CMD_START);
DBGPRINT(DBG_LOAD, (KERN_DEBUG "wait for stable voltage\n"));
cnt = 0;
do {
MEM_READ_UCHAR((PSD_DEVICE_DATA) pTmp, SK_SLOT_0,
FPGA_POWER_REG_STATUS, &ucValue);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "PowerRegulatorControl: 0x%x\n",
ucValue));
} while (++cnt < 10000 &&
(ucValue & FPGA_POWER_REG_STABLE) == 0);
DBGPRINT(DBG_LOAD, ("REMOVE_SET_VOLTAGE: cnt=%d\n", cnt));
// turn off host power
MEM_READ_UCHAR((PSD_DEVICE_DATA) pTmp, SK_SLOT_0,
STDHOST_POWER_CTRL, &ucValue);
MEM_WRITE_UCHAR((PSD_DEVICE_DATA) pTmp, SK_SLOT_0,
STDHOST_POWER_CTRL,
ucValue & ~STDHOST_POWER_ON);
// give some time for host power off to settle
mdelay(200);
free_irq(pTmpDev->Interrupt, pTmpDev);
for (j = 0; j < pTmpDev->IOBaseIx; j++)
iounmap(pTmpDev->IOBase[j]);
/*--- mmoser 10/11/2006 ---*/
pci_release_regions(pTmpDev->dev);
list_del((struct list_head *) pTmpDev);
kfree(pTmpDev);
#ifdef SDIO_MEM_TRACE
printk("<<< kfree(SD_DEVICE_DATA)\n");
#endif
return;
}
pTmp = pNext;
}
}
}
BOOLEAN
SDIOBus_CardRemoved(PSD_DEVICE_DATA SdData)
{
SK_U32 ulVal;
ENTER();
#ifdef USE_DEBOUNCE_CARD_IN
/*--- mmoser 12/21/2006 ---*/
mdelay(MAX_DEBOUNCE_TIME);
// Read present state register to check whether a SD card is present
MEM_READ_ULONG(SdData, SK_SLOT_0, STDHOST_PRESENT_STATE, &ulVal);
if ((ulVal & STDHOST_STATE_CARD_INSERTED) == 1) {
// There is probably still a card in the socket
DBGPRINT(DBG_W528D,
(KERN_DEBUG
"SDHost_isCardPresent(): Card is present. (%8.08X)\n",
ulVal));
return SK_FALSE;
}
#endif // USE_DEBOUNCE_CARD_IN
SdData->CardIn = SK_FALSE;
// mmoser 2005-11-29
queue_work(SdData->workqueue, &SdData->card_out_work);
LEAVE();
return SK_TRUE;
}
BOOLEAN
SDIOBus_CardPluggedIn(PSD_DEVICE_DATA SdData)
{
SK_U32 ulVal;
ENTER();
#ifdef USE_DEBOUNCE_CARD_IN
/*--- mmoser 12/21/2006 ---*/
mdelay(MAX_DEBOUNCE_TIME);
// Read present state register to check whether a SD card is present
MEM_READ_ULONG(SdData, SK_SLOT_0, STDHOST_PRESENT_STATE, &ulVal);
if ((ulVal & STDHOST_STATE_CARD_INSERTED) == 0) {
// There is probably no card in the socket
DBGPRINT(DBG_W528D,
(KERN_DEBUG
"SDHost_isCardPresent(): Card is NOT present. (%8.08X)\n",
ulVal));
return SK_FALSE;
}
#endif // USE_DEBOUNCE_CARD_IN
SDHost_Init(SdData);
if (!SDHost_InitializeCard(SdData)) {
SDHost_EnableInterrupt(SdData, SdData->lastIRQSignalMask);
return SK_FALSE;
}
SdData->CardIn = SK_TRUE;
DBGPRINT(DBG_LOAD, (KERN_DEBUG "Device Name : %s\n", SdData->DeviceName));
// mmoser 2005-11-29
atomic_inc(&SdData->card_add_event);
LEAVE();
return SK_TRUE;
}
static int
SDIOThread(void *data)
{
SK_U16 usVal;
SK_U32 card_in = 0;
SK_U32 card_out = 0;
PSD_DEVICE_DATA pDev;
int clients_found;
int fn = 0;
int j;
struct list_head *pTmp;
PSD_CLIENT pClientTmp;
int fn_mask;
u8 attached_fn[MAX_SDIO_FUNCTIONS];
daemonize("SDIOThread");
allow_signal(SIGTERM);
DBGPRINT(DBG_LOAD, (KERN_DEBUG "SDIOThread() started ... \n"));
if (data == NULL) {
DBGPRINT(DBG_LOAD, (KERN_DEBUG "SDIOThread() data == NULL !\n"));
complete_and_exit(&on_exit, 0);
return 1;
}
pDev = (PSD_DEVICE_DATA) data;
LED_OFF(4);
if (!test_and_set_bit(0, &pDev->thread_started_evt.event)) {
wake_up(&pDev->thread_started_evt.wq);
}
while (pDev != NULL && !pDev->stop_thread) {
LED_ON(4);
// mmoser 2005-11-21
// Read the Host Controller Version to check if SD Host adapter is
// available
MEM_READ_USHORT(pDev, SK_SLOT_0, STDHOST_HOST_CONTROLLER_VERSION,
&usVal);
if (usVal == 0xFFFF) {
// There is probably no SD Host Adapter available
printk("%s: SD Host Adapter is NOT present.\n", __FUNCTION__);
pDev->SurpriseRemoved = SK_TRUE;
pDev->CardIn = SK_FALSE;
pDev->thread_id = 0;
DBGPRINT(DBG_LOAD, (KERN_DEBUG "SDIOThread terminated ... \n"));
complete_and_exit(&on_exit, 0);
return -ENODEV;
}
// mmoser 2005-11-29
card_in = atomic_read(&pDev->card_add_event);
card_out = atomic_read(&pDev->card_remove_event);
if (card_in == 0 && card_out == 0) {
LED_OFF(4);
/*--- mmoser 6/21/2006 ---*/
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(10);
continue;
}
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "%s: card_in=%d card_out=%d\n", __FUNCTION__,
card_in, card_out));
// mmoser 2005-11-29
if (card_in > 0) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "%s: CARD_ADD_EVENT received.\n",
__FUNCTION__));
// Check whether we have a client driver in the global list for
// this device
if (!list_empty(&sd_client_list)) {
spin_lock_irqsave(&sd_client_list_lock,
sd_client_list_lock_flags);
fn = pDev->number_of_functions + 1;
clients_found = 0;
// walk through the list of client drivers
list_for_each(pTmp, &sd_client_list) {
pClientTmp = (PSD_CLIENT) pTmp;
if ((fn_mask = sd_match_device(pClientTmp->drv, pDev)) > 0) {
// Matching client driver found
// Assign the client driver to the lowest supported
// function
for (fn = 1; fn <= pDev->number_of_functions; fn++) {
if ((fn_mask & (1 << fn)) != 0) {
pDev->sd_dev[fn].drv = pClientTmp->drv;
pDev->sd_dev[fn].supported_functions = fn_mask;
pDev->sd_dev[fn].cisptr = &pDev->cisptr[0];
pDev->sd_dev[fn].sd_bus = pDev; // Backpointer
// to bus
// driver's
// device
// structure
pDev->sd_dev[fn].dev = &pDev->dev->dev; // Generic
// device
// interface
// for
// hotplug
attached_fn[clients_found] = fn;
clients_found++;
break;
}
}
}
}
spin_unlock_irqrestore(&sd_client_list_lock,
sd_client_list_lock_flags);
if (clients_found) {
// Call probe() for each previously attached client driver
for (j = 0; j < clients_found; j++) {
fn = attached_fn[j];
if (pDev->sd_dev[fn].drv->probe != NULL) {
if (pDev->sd_dev[fn].probe_called == 0) {
DBGPRINT(DBG_LOAD | DBG_ERROR,
(KERN_DEBUG
"%s: call probe() handler on fn=%d\n",
__FUNCTION__, fn));
pDev->SurpriseRemoved = 0;
pDev->sd_dev[fn].probe_called = 1;
pDev->sd_dev[fn].remove_called = 0;
call_client_probe(pDev, fn);
}
} else {
// If we have no probe() handler at this point, we
// will never get one.
// So discard the CARD_ADD_EVENT to avoid an
// endless loop.
// This situation is considered a bug in the client
// driver !!!!
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"%s@%d: no probe() handler installed for fn=%d! Revise client driver!!!\n",
__FUNCTION__, __LINE__, fn));
}
}
} else {
// There is at least one client driver registered with our
// bus driver
// but the sd_device_ids of this driver does not match the
// ids of the inserted card.
// We have to wait until a matching driver will be
// installed.
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"%s@%d: no matching client driver installed.\n",
__FUNCTION__, __LINE__));
}
} else {
// Until there is no client driver registered with our bus
// driver.
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "%s@%d: no client driver installed.\n",
__FUNCTION__, __LINE__));
}
// We can reset the card event here since the association with a
// matching client driver
// is already done or will be done in sd_register_driver()
atomic_set(&pDev->card_add_event, 0);
}
// mmoser 2005-11-29
else if (card_out > 0) {
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "%s: CARD_REMOVE_EVENT received.\n",
__FUNCTION__));
for (j = 1; j <= pDev->number_of_functions; j++) {
if (down_interruptible(&sd_client_sema))
return 0;
if (pDev->sd_dev[j].drv != NULL &&
pDev->sd_dev[j].drv->remove != NULL) {
if (pDev->sd_dev[j].remove_called == 0) {
DBGPRINT(DBG_LOAD | DBG_ERROR,
(KERN_DEBUG
"%s: call remove() handler on fn=%d\n",
__FUNCTION__, j));
pDev->sd_dev[j].probe_called = 0;
pDev->sd_dev[j].remove_called = 1;
pDev->sd_dev[j].dev = NULL;
pDev->sd_dev[j].drv->remove(&pDev->sd_dev[j]);
}
} else {
// If we have no remove() handler at this point, we will
// never get one.
// So discard the CARD_REMOVE_EVENT to avoid an endless
// loop.
// This situation is considered a bug in the client driver
// !!!!
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"%s: no remove() handler installed for fn=%d! Revise client driver!!!\n",
__FUNCTION__, j));
}
up(&sd_client_sema);
}
atomic_set(&pDev->card_remove_event, 0);
} else {
DBGPRINT(DBG_LOAD, (KERN_DEBUG "SDIOThread terminated ... \n"));
pDev->thread_id = 0;
complete_and_exit(&on_exit, 0);
return 1;
}
}
DBGPRINT(DBG_LOAD, (KERN_DEBUG "SDIOThread terminated ... \n"));
if (pDev != NULL) {
pDev->thread_id = 0;
}
complete_and_exit(&on_exit, 0);
return 0;
}
/************************* New SDIO Bus Driver API ************************/
/**
* @brief This function matches a client driver with a SDIO device
* @param drv pointer to client drivers device IDs
* @param dev pointer to SDIO device
* @return mask with supported functions -> 0 means no match found
*
* 76543210
* ||||||+-- fn 1 supported
* |||||+--- fn 2 supported
* ||||+---- fn 3 supported
* |||+----- fn 4 supported
* ||+------ fn 5 supported
* |+------- fn 6 supported
* +-------- fn 7 supported
*
* 0x02 -> fn 1 supported
* 0x06 -> fn 1 and 2 supported
* 0x12 -> fn 1 and 4 supported
*/
int
sd_match_device(sd_driver * drv, PSD_DEVICE_DATA dev)
{
psd_device_id pIds;
int i;
int matched_fn = 0;
int fn_mask = 0;
DBGPRINT(DBG_API,
(KERN_DEBUG
"%s: drv-name: %s device: vendor=0x%4.04X device=0x%4.04X\n",
__FUNCTION__, drv->name, dev->sd_ids[0].vendor,
dev->sd_ids[0].device));
for (pIds = drv->ids; pIds->device != 0 ||
pIds->vendor != 0 || pIds->class != 0 || pIds->fn != 0; pIds++) {
DBGPRINT(DBG_API,
(KERN_DEBUG
"vendor=0x%4.04X device=0x%4.04X class=0x%4.04X fn=0x%4.04X\n",
pIds->vendor, pIds->device, pIds->class, pIds->fn));
if ((pIds->vendor != SD_VENDOR_ANY) &&
(pIds->vendor != dev->sd_ids[0].vendor)) {
continue;
}
if (pIds->device != SD_DEVICE_ANY) {
matched_fn = 0;
for (i = 0; i <= dev->number_of_functions; i++) {
if (pIds->device == dev->sd_ids[i].device) {
matched_fn = i;
fn_mask |= (1 << matched_fn);
DBGPRINT(DBG_API,
(KERN_DEBUG "driver matches with function %d\n",
i));
break;
}
}
if (matched_fn == 0) {
continue;
}
}
if (pIds->class != SD_CLASS_ANY) {
matched_fn = 0;
for (i = 1; i <= dev->number_of_functions; i++) {
// printk("fn[%d] class-id=0x%4.04X\n",i,dev->sd_ids[i].class);
if (pIds->class == dev->sd_ids[i].class) {
matched_fn = i;
fn_mask |= (1 << matched_fn);
DBGPRINT(DBG_API,
(KERN_DEBUG "driver matches with function %d\n",
i));
break;
}
}
if (matched_fn == 0) {
continue;
}
} else {
// At this point vendor==ANY && device==ANY && class==ANY
// Now we have to check whether the card supports the desired
// function number
if (pIds->fn > 0 && pIds->fn <= dev->number_of_functions) {
matched_fn = pIds->fn;
fn_mask |= (1 << matched_fn);
}
}
if ((pIds->fn != SD_FUNCTION_ANY) && (pIds->fn != matched_fn)) {
continue;
}
}
printk("%s: supported function mask = 0x%2.02X\n", __FUNCTION__, fn_mask);
return (fn_mask);
}
/**
* @brief This function registers a client driver with the bus driver using device IDs
* @param drv pointer to client drivers device IDs and call backs
* @return 0 : Success < 0: Failed
*/
int
sd_driver_register(sd_driver * drv)
{
struct list_head *pTmp;
PSD_CLIENT pClientTmp;
PSD_DEVICE_DATA pDevTmp;
int fn_mask;
int fn;
GET_IF_SEMA();
ENTER();
pClientTmp = (PSD_CLIENT) kmalloc(sizeof(SD_CLIENT), GFP_KERNEL);
if (pClientTmp == NULL) {
REL_IF_SEMA();
LEAVE();
return -ENOMEM;
}
#ifdef SDIO_MEM_TRACE
printk(">>> kmalloc(SD_CLIENT)\n");
#endif
memset(pClientTmp, 0, sizeof(SD_CLIENT));
pClientTmp->drv = drv;
// Check whether there is a SDIO device available
if (!list_empty(&sd_dev_list)) {
list_for_each(pTmp, &sd_dev_list) {
pDevTmp = (PSD_DEVICE_DATA) pTmp;
spin_lock_irqsave(&pDevTmp->sd_dev_lock,
pDevTmp->sd_dev_lock_flags);
if ((fn_mask = sd_match_device(drv, pDevTmp)) > 0) {
// Assign the client driver to the lowest supported function
for (fn = 1; fn <= pDevTmp->number_of_functions; fn++) {
if ((fn_mask & (1 << fn)) != 0) {
break;
}
}
if (fn <=
MIN(pDevTmp->number_of_functions, MAX_SDIO_FUNCTIONS - 1)) {
if (pDevTmp->sd_dev[fn].drv == NULL) {
pDevTmp->sd_dev[fn].drv = drv;
pDevTmp->sd_dev[fn].supported_functions = fn_mask;
pDevTmp->sd_dev[fn].cisptr = &pDevTmp->cisptr[0];
pDevTmp->sd_dev[fn].sd_bus = pDevTmp; // Backpointer
// to bus
// driver's
// device
// structure
pDevTmp->sd_dev[fn].dev = &pDevTmp->dev->dev; // Generic
// device
// interface
// for
// hotplug
spin_unlock_irqrestore(&pDevTmp->sd_dev_lock,
pDevTmp->sd_dev_lock_flags);
DBGPRINT(DBG_API,
(KERN_DEBUG
"%s: Driver registered: %s for functions 0x%2.02X\n",
__FUNCTION__, pDevTmp->sd_dev[fn].drv->name,
fn_mask));
if (pDevTmp->sd_dev[fn].drv->probe != NULL) {
pDevTmp->sd_dev[fn].probe_called = 1;
pDevTmp->sd_dev[fn].remove_called = 0;
DBGPRINT(DBG_LOAD | DBG_ERROR,
(KERN_DEBUG
"%s: call probe() handler on fn=%d\n",
__FUNCTION__, fn));
REL_IF_SEMA();
call_client_probe(pDevTmp, fn);
GET_IF_SEMA();
} else {
// If we have no probe() handler at this point, we
// will never get one.
// This situation is considered a bug in the client
// driver !!!!
DBGPRINT(DBG_LOAD,
(KERN_DEBUG
"%s@%d: no probe() handler installed for fn=%d! Revise client driver!!!\n",
__FUNCTION__, __LINE__, fn));
}
break;
} else {
spin_unlock_irqrestore(&pDevTmp->sd_dev_lock,
pDevTmp->sd_dev_lock_flags);
continue;
}
}
} else {
spin_unlock_irqrestore(&pDevTmp->sd_dev_lock,
pDevTmp->sd_dev_lock_flags);
continue;
}
}
DBGPRINT(DBG_API,
(KERN_ERR
"%s: There are already drivers registered for all devices!\n",
__FUNCTION__));
}
// Add driver to global client list
spin_lock_irqsave(&sd_client_list_lock, sd_client_list_lock_flags);
list_add_tail((struct list_head *) pClientTmp, &sd_client_list);
spin_unlock_irqrestore(&sd_client_list_lock, sd_client_list_lock_flags);
REL_IF_SEMA();
LEAVE();
return 0;
}
/**
* @brief This function unregisters a client driver from the bus driver
* @param drv pointer to client drivers device IDs and call backs
* @return 0 : Success -1: Failed
*/
int
sd_driver_unregister(sd_driver * drv)
{
struct list_head *pTmp;
PSD_CLIENT pClientTmp;
PSD_DEVICE_DATA pDevTmp;
int j;
ENTER();
if (drv == NULL) {
printk("%s INVALID ARGS: drv==NULL!\n", __FUNCTION__);
LEAVE();
return -1;
}
if (down_interruptible(&sd_client_sema))
return 0;
// Delete any association with this client driver
spin_lock_irqsave(&sd_dev_list_lock, sd_dev_list_lock_flags);
if (!list_empty(&sd_dev_list)) {
list_for_each(pTmp, &sd_dev_list) {
pDevTmp = (PSD_DEVICE_DATA) pTmp;
spin_unlock_irqrestore(&sd_dev_list_lock, sd_dev_list_lock_flags);
for (j = 0; j < MAX_SDIO_FUNCTIONS; j++) {
if (pDevTmp->sd_dev[j].drv == drv) {
DBGPRINT(DBG_API,
(KERN_DEBUG
"%s: Driver unregistered: %s on fn = %d\n",
__FUNCTION__, pDevTmp->sd_dev[j].drv->name, j));
if (drv != NULL && drv->remove != NULL) {
if (pDevTmp->sd_dev[j].remove_called == 0) {
pDevTmp->sd_dev[j].probe_called = 0;
pDevTmp->sd_dev[j].remove_called = 1;
if (pDevTmp->SurpriseRemoved == SK_TRUE)
pDevTmp->sd_dev[j].dev = NULL;
pDevTmp->sd_dev[j].drv->remove(&pDevTmp->sd_dev[j]);
}
}
spin_lock_irqsave(&pDevTmp->sd_dev_lock,
pDevTmp->sd_dev_lock_flags);
pDevTmp->sd_dev[j].drv = NULL;
spin_unlock_irqrestore(&pDevTmp->sd_dev_lock,
pDevTmp->sd_dev_lock_flags);
}
}
}
DBGPRINT(DBG_API,
(KERN_ERR "%s: Driver %s is not registered!\n", __FUNCTION__,
drv->name));
} else {
DBGPRINT(DBG_API,
(KERN_ERR "%s: SDIO Device List is empty!\n", __FUNCTION__));
spin_unlock_irqrestore(&sd_dev_list_lock, sd_dev_list_lock_flags);
}
up(&sd_client_sema);
GET_IF_SEMA();
// Remove the client driver from global client list
if (!list_empty(&sd_client_list)) {
spin_lock_irqsave(&sd_client_list_lock, sd_client_list_lock_flags);
list_for_each(pTmp, &sd_client_list) {
pClientTmp = (PSD_CLIENT) pTmp;
if (pClientTmp->drv != drv) {
continue;
}
list_del(pTmp);
kfree(pClientTmp);
#ifdef SDIO_MEM_TRACE
printk("<<< kfree(SD_CLIENT)\n");
#endif
break;
}
spin_unlock_irqrestore(&sd_client_list_lock, sd_client_list_lock_flags);
} else {
DBGPRINT(DBG_API,
(KERN_ERR "%s: SDIO Client Driver List is empty!\n",
__FUNCTION__));
REL_IF_SEMA();
LEAVE();
return -1;
}
REL_IF_SEMA();
LEAVE();
return 0;
}
/**
* @brief Register an interrupt handler with a card function
* @param dev bus driver's device structure
* @param id sd_device_ids (contains function number)
* @param function interrupt handler
* @return 0 : Success -1: Failed
*/
int
sd_request_int(sd_device * dev, sd_device_id * id, sd_function * function)
{
PSD_DEVICE_DATA pDev;
if (dev == NULL || id == NULL || function == NULL) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: missing parameters: dev=%p id=%p function=%p\n",
__FUNCTION__, dev, id, function));
return -1;
}
if (id->fn < 1 || id->fn > 7) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: illegal function number: %d\n", __FUNCTION__,
id->fn));
return -1;
}
if (dev->functions[id->fn].int_handler != NULL) {
DBGPRINT(DBG_API,
(KERN_ERR
"%s: There is already an interrupt handler registered with function %d\n",
__FUNCTION__, id->fn));
return -1;
}
GET_IF_SEMA();
DBGPRINT(DBG_API,
(KERN_DEBUG "%s: FN%d handler=%p context=%p\n", __FUNCTION__,
id->fn, function->int_handler, function->context));
pDev = (PSD_DEVICE_DATA) dev->sd_bus;
SDHost_DisableInterrupt((PSD_DEVICE_DATA) dev->sd_bus,
STDHOST_NORMAL_IRQ_CARD_ALL_ENA);
dev->functions[id->fn].int_handler = function->int_handler;
dev->functions[id->fn].context = function->context;
// cache the device to speed up irq response
pDev->irq_device_cache[id->fn] = dev;
SDHost_EnableInterrupt((PSD_DEVICE_DATA) dev->sd_bus,
pDev->lastIRQSignalMask);
REL_IF_SEMA();
return 0;
}
/**
* @brief Remove an interrupt handler from a card function
* @param dev bus driver's device structure
* @param id sd_device_ids (contains function number)
* @return 0 : Success -1: Failed
*/
int
sd_release_int(sd_device * dev, sd_device_id * id)
{
PSD_DEVICE_DATA pDev;
if (dev == NULL || id == NULL) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: missing parameters: dev=%p id=%p\n",
__FUNCTION__, dev, id));
return -1;
}
if (id->fn < 1 || id->fn > 7) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: illegal function number: %d\n", __FUNCTION__,
id->fn));
return -1;
}
GET_IF_SEMA();
pDev = (PSD_DEVICE_DATA) dev->sd_bus;
DBGPRINT(DBG_API, (KERN_DEBUG "%s: FN%d\n", __FUNCTION__, id->fn));
SDHost_DisableInterrupt((PSD_DEVICE_DATA) dev->sd_bus,
STDHOST_NORMAL_IRQ_CARD_ALL_ENA);
dev->functions[id->fn].int_handler = NULL;
dev->functions[id->fn].context = NULL;
pDev->irq_device_cache[id->fn] = NULL;
SDHost_EnableInterrupt((PSD_DEVICE_DATA) dev->sd_bus,
pDev->lastIRQSignalMask);
REL_IF_SEMA();
return 0;
}
/**
* @brief Enable interrupt of function id->fn
* @param dev bus driver's device structure
* @param id sd_device_ids (contains function number)
* @return 0 : Success -1: Failed
*/
int
sd_unmask(sd_device * dev, sd_device_id * id)
{
PSD_DEVICE_DATA pDev;
if (dev == NULL || id == NULL) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: missing parameters: dev=%p id=%p\n",
__FUNCTION__, dev, id));
return 1;
}
if (id->fn < 1 || id->fn > 7) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: illegal function number: %d\n", __FUNCTION__,
id->fn));
return -1;
}
GET_IF_SEMA();
pDev = dev->sd_bus;
SDHost_EnableInterrupt(pDev, STDHOST_NORMAL_IRQ_CARD_ALL_ENA);
REL_IF_SEMA();
return 0;
}
/**
* @brief Enable interrupt of function id->fn
* @param dev bus driver's device structure
* @param id sd_device_ids (contains function number)
* @return 0 : Success -1: Failed
*/
int
sd_enable_int(sd_device * dev, sd_device_id * id)
{
PSD_DEVICE_DATA pDev;
SK_U8 R;
if (dev == NULL || id == NULL) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: missing parameters: dev=%p id=%p\n",
__FUNCTION__, dev, id));
return 1;
}
if (id->fn < 1 || id->fn > 7) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: illegal function number: %d\n", __FUNCTION__,
id->fn));
return -1;
}
GET_IF_SEMA();
pDev = dev->sd_bus;
// Set global interrupt enable
if (!SDHost_CMD52_Read(pDev, INT_ENABLE_REG, 0, &R)) {
REL_IF_SEMA();
return -1;
}
R |= (1 << id->fn);
if (SDHost_CMD52_Write(pDev, INT_ENABLE_REG, 0, R)) {
if (!SDHost_CMD52_Read(pDev, INT_ENABLE_REG, 0, &R)) {
REL_IF_SEMA();
return -1;
}
DBGPRINT(DBG_API,
(KERN_DEBUG "%s:FN0 : INT Enable Register: 0x%2.02X\n",
__FUNCTION__, R));
} else {
REL_IF_SEMA();
return -1;
}
SDHost_EnableInterrupt(pDev, STDHOST_NORMAL_IRQ_CARD_ALL_ENA);
REL_IF_SEMA();
return 0;
}
/**
* @brief Disable interrupt of function id->fn
* @param dev bus driver's device structure
* @param id sd_device_ids (contains function number)
* @return 0 : Success -1: Failed
*/
int
sd_disable_int(sd_device * dev, sd_device_id * id)
{
PSD_DEVICE_DATA pDev;
SK_U8 R;
if (dev == NULL || id == NULL) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: missing parameters: dev=%p id=%p\n",
__FUNCTION__, dev, id));
return -1;
}
if (id->fn < 1 || id->fn > 7) {
DBGPRINT(DBG_API,
(KERN_ERR "%s: illegal function number: %d\n", __FUNCTION__,
id->fn));
return -1;
}
GET_IF_SEMA();
pDev = dev->sd_bus;
// Set global interrupt enable
if (!SDHost_CMD52_Read(pDev, INT_ENABLE_REG, 0, &R)) {
REL_IF_SEMA();
return -1;
}
R &= ~(1 << id->fn);
if (SDHost_CMD52_Write(pDev, INT_ENABLE_REG, 0, R)) {
if (!SDHost_CMD52_Read(pDev, INT_ENABLE_REG, 0, &R)) {
REL_IF_SEMA();
return -1;
}
DBGPRINT(DBG_API,
(KERN_DEBUG "%s:FN0 : INT Disable Register: 0x%2.02X\n",
__FUNCTION__, R));
} else {
REL_IF_SEMA();
return -1;
}
REL_IF_SEMA();
return 0;
}
/**
* @brief Set SDIO bus width
* @param dev bus driver's device structure
* @param with SDIO bus with 1 or 4
* @return 0 : Success -1: Failed
*/
int
sd_set_buswidth(sd_device * dev, int width)
{
SK_BOOL rc;
GET_IF_SEMA();
if (width == 1 || width == 4) {
DBGPRINT(DBG_API, (KERN_DEBUG "%s: width = %d\n", __FUNCTION__, width));
rc = SDHost_SetBusWidth((PSD_DEVICE_DATA) dev->sd_bus, width);
REL_IF_SEMA();
return (rc ? 0 : -1);
} else {
REL_IF_SEMA();
return -1;
}
}
/**
* @brief Set GPO on or off
* @param dev bus driver's device structure
* @param on 1: turn GPO on 0: turn GPO off
* @return 0 : Success -1: Failed
*/
int
sd_set_gpo(sd_device * dev, int on)
{
SK_BOOL rc;
GET_IF_SEMA();
DBGPRINT(DBG_API, (KERN_DEBUG "%s: on = %d\n", __FUNCTION__, on));
rc = SDHost_SetGPO((PSD_DEVICE_DATA) dev->sd_bus, on);
REL_IF_SEMA();
return (rc ? 0 : -1);
}
/**
* @brief Set SDIO bus clock on
* @param dev bus driver's device structure
* @return 0 : Success -1: Failed
*/
int
sd_start_clock(sd_device * dev)
{
GET_IF_SEMA();
DBGPRINT(DBG_API, (KERN_DEBUG "%s\n", __FUNCTION__));
SDHost_SetClock((PSD_DEVICE_DATA) dev->sd_bus, 1);
REL_IF_SEMA();
return 0;
}
/**
* @brief Set SDIO bus clock on
* @param dev bus driver's device structure
* @return 0 : Success -1: Failed
*/
int
sd_stop_clock(sd_device * dev)
{
GET_IF_SEMA();
DBGPRINT(DBG_API, (KERN_DEBUG "%s\n", __FUNCTION__));
SDHost_SetClock((PSD_DEVICE_DATA) dev->sd_bus, 0);
REL_IF_SEMA();
return 0;
}
/**
* @brief Set SDIO bus clock
* @param dev bus driver's device structure
* @param clock SDIO bus clock frequency
* @return 0 : Success -1: Failed
*/
int
sd_set_busclock(sd_device * dev, int clock)
{
GET_IF_SEMA();
DBGPRINT(DBG_API, (KERN_DEBUG "%s: clock = %d\n", __FUNCTION__, clock));
SDHost_SetClockSpeed((PSD_DEVICE_DATA) dev->sd_bus, clock);
REL_IF_SEMA();
return 0;
}
/**
* @brief Read a byte from SDIO device
* @param dev bus driver's device structure
* @param fn function number 0..7
* @param reg register offset
* @param dat pointer to return value
* @return 0 : Success -1: Failed
*/
int
sdio_read_ioreg(sd_device * dev, u8 fn, u32 reg, u8 * dat)
{
GET_IF_SEMA();
if (SDHost_CMD52_Read((PSD_DEVICE_DATA) dev->sd_bus, reg, fn, dat)) {
// printk("%s: dev=%p fn=%d reg=0x%8.08X dat=0x%2.02x\n",__FUNCTION__,dev,fn,reg,*dat);
REL_IF_SEMA();
return 0;
} else {
printk("%s: dev=%p fn=%d reg=0x%8.08X FAILED!\n", __FUNCTION__, dev, fn,
reg);
REL_IF_SEMA();
return -1;
}
}
/**
* @brief Write a byte to SDIO device
* @param dev bus driver's device structure
* @param fn function number 0..7
* @param reg register offset
* @param dat value
* @return 0 : Success -1: Failed
*/
int
sdio_write_ioreg(sd_device * dev, u8 fn, u32 reg, u8 dat)
{
GET_IF_SEMA();
if (SDHost_CMD52_Write((PSD_DEVICE_DATA) dev->sd_bus, reg, fn, dat)) {
// printk("%s: dev=%p fn=%d reg=0x%8.08X dat=0x%2.02x\n",__FUNCTION__,dev,fn,reg,dat);
REL_IF_SEMA();
return 0;
} else {
printk("%s: dev=%p fn=%d reg=0x%8.08X FAILED!\n", __FUNCTION__, dev, fn,
reg);
REL_IF_SEMA();
return -1;
}
}
/**
* @brief Read multiple bytes from SDIO device
* @param dev bus driver's device structure
* @param fn function number 0..7
* @param address offset
* @param blkmode block or byte mode
* @param opcode fixed or auto-inc address
* @param blkcnt number of bytes or blocks (depends on blkmode)
* @param blksz size of block
* @param buffer pointer to data buffer
* @return 0 : Success -1: Failed
*/
int
sdio_read_iomem(sd_device * dev, u8 fn, u32 address, u8 blkmode, u8 opcode,
u32 blkcnt, u32 blksz, u8 * buffer)
{
// printk("%s: dev=%p fn=%d address=0x%8.08X blockcnt=%d blocksize=%d\n",__FUNCTION__,dev,fn,address,blkcnt,blksz);
GET_IF_SEMA();
if (SDHost_CMD53_ReadEx
((PSD_DEVICE_DATA) dev->sd_bus, address, fn, blkmode, opcode, buffer,
blkcnt, blksz)) {
REL_IF_SEMA();
return 0;
} else {
printk("%s: dev=%p fn=%d FAILED!\n", __FUNCTION__, dev, fn);
REL_IF_SEMA();
return -1;
}
}
/**
* @brief Write multiple bytes to SDIO device
* @param dev bus driver's device structure
* @param fn function number 0..7
* @param address offset
* @param blkmode block or byte mode
* @param opcode fixed or auto-inc address
* @param blkcnt number of bytes or blocks (depends on blkmode)
* @param blksz size of block
* @param buffer pointer to data buffer
* @return 0 : Success -1: Failed
*/
int
sdio_write_iomem(sd_device * dev, u8 fn, u32 address, u8 blkmode, u8 opcode,
u32 blkcnt, u32 blksz, u8 * buffer)
{
// printk("%s: dev=%p fn=%d address=0x%8.08X blockcnt=%d blocksize=%d\n",__FUNCTION__,dev,fn,address,blkcnt,blksz);
GET_IF_SEMA();
if (SDHost_CMD53_WriteEx
((PSD_DEVICE_DATA) dev->sd_bus, address, fn, blkmode, opcode, buffer,
blkcnt, blksz)) {
REL_IF_SEMA();
return 0;
} else {
printk("%s: dev=%p fn=%d FAILED!\n", __FUNCTION__, dev, fn);
REL_IF_SEMA();
return -1;
}
}
// for filling the "/proc/mrvsdio" entry
int
mrvlsdio_read_procmem(char *buf,
char **start,
off_t offset, int count, int *eof, void *data)
{
PSD_DEVICE_DATA pDevTmp;
int len = 0;
if (!list_empty(&sd_dev_list)) {
pDevTmp = (PSD_DEVICE_DATA) sd_dev_list.next;
// after probe-event, we have "good" data in /proc/mrvsdio
len =
sprintf(buf,
"mrvlsdio is running with:\nbus_type = 0x%x\nbus_with = 0x%x\nclock_speed = 0x%x\ndebug_flags = 0x%x\nsdio_voltage = 0x%x\n",
pDevTmp->bus_type, pDevTmp->bus_width, pDevTmp->ClockSpeed,
pDevTmp->debug_flags, pDevTmp->sdio_voltage);
} else {
// not probed yet
len = sprintf(buf, "mrvlsdio is up and waiting for card\n");
}
*eof = 1;
return (len);
}
static void
call_client_probe(PSD_DEVICE_DATA pDev, int fn)
{
if (down_interruptible(&sd_client_sema))
return;
if (pDev->sd_dev[fn].drv->probe != NULL) {
pDev->sd_dev[fn].drv->probe(&pDev->sd_dev[fn], &pDev->sd_ids[fn]);
}
up(&sd_client_sema);
}
static int
mrvlsdio_init_module(void)
{
int ret;
ENTER();
// generate /proc/ entry
create_proc_read_entry("mrvlsdio", // entry in "/proc/mrvlsdio"
0, // file attributes
NULL, // parent dir
mrvlsdio_read_procmem, // name of function
NULL); // client data
DBGPRINT(DBG_ALL, ("*** mrvlsdio ***\n"));
DBGPRINT(DBG_ALL, ("*** multifunction API ***\n"));
DBGPRINT(DBG_ALL, ("*** Built on %s %s ***\n", __DATE__, __TIME__));
/*--- mmoser 6/21/2007 ---*/
INIT_LIST_HEAD(&sd_dev_list);
spin_lock_init(&sd_dev_list_lock);
INIT_LIST_HEAD(&sd_client_list);
spin_lock_init(&sd_client_list_lock);
ret = register_chrdev(sdio_major, "mrvlsdio", &mrvlsdio_fops);
if (ret < 0) {
DBGPRINT(DBG_LOAD,
(KERN_WARNING "mrvlsdio: can't get major %d ret=%d\n",
sdio_major, ret));
} else {
DBGPRINT(DBG_LOAD, (KERN_DEBUG "mrvlsdio: major %d\n", ret));
}
if (sdio_major == 0)
sdio_major = ret; /* dynamic */
/** from 2.4 kernel source:
* pci_register_driver - register a new pci driver
*
* Adds the driver structure to the list of registered drivers
* Returns the number of pci devices which were claimed by the driver
* during registration. The driver remains registered even if the
* return value is zero.
*/
ret = pci_register_driver(&mrvlsdio);
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "%s: pci_register_driver with %d\n", __FILE__, ret));
LEAVE();
// return (ret);
return 0;
}
static void
mrvlsdio_exit_module(void)
{
PSD_DEVICE_DATA pTmpDev;
PSD_CLIENT pTmpClient;
// remove proc entry
remove_proc_entry("mrvlsdio", NULL);
DBGPRINT(DBG_LOAD, (KERN_DEBUG "Module mrvlsdio exit\n"));
unregister_chrdev(sdio_major, "mrvlsdio");
pci_unregister_driver(&mrvlsdio);
// Clean up device list
while (!list_empty(&sd_dev_list)) {
pTmpDev = (PSD_DEVICE_DATA) sd_dev_list.next;
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "%s@%d: pTmpDev=%p\n", __FUNCTION__, __LINE__,
pTmpDev));
list_del((struct list_head *) pTmpDev);
kfree(pTmpDev);
#ifdef SDIO_MEM_TRACE
printk("<<< kfree(SD_DEVICE_DATA)\n");
#endif
}
// Clean up client list
while (!list_empty(&sd_client_list)) {
pTmpClient = (PSD_CLIENT) sd_client_list.next;
DBGPRINT(DBG_LOAD,
(KERN_DEBUG "%s@%d: pTmpClient=%p\n", __FUNCTION__, __LINE__,
pTmpClient));
list_del((struct list_head *) pTmpClient);
kfree(pTmpClient);
#ifdef SDIO_MEM_TRACE
printk("<<< kfree(SD_CLIENT)\n");
#endif
}
}
module_init(mrvlsdio_init_module);
module_exit(mrvlsdio_exit_module);
EXPORT_SYMBOL(sd_driver_register);
EXPORT_SYMBOL(sd_driver_unregister);
EXPORT_SYMBOL(sd_request_int);
EXPORT_SYMBOL(sd_release_int);
EXPORT_SYMBOL(sd_unmask);
EXPORT_SYMBOL(sd_enable_int);
EXPORT_SYMBOL(sd_disable_int);
EXPORT_SYMBOL(sd_set_buswidth);
EXPORT_SYMBOL(sd_set_busclock);
EXPORT_SYMBOL(sd_start_clock);
EXPORT_SYMBOL(sd_stop_clock);
EXPORT_SYMBOL(sd_set_gpo);
EXPORT_SYMBOL(sdio_read_ioreg);
EXPORT_SYMBOL(sdio_write_ioreg);
EXPORT_SYMBOL(sdio_read_iomem);
EXPORT_SYMBOL(sdio_write_iomem);
MODULE_DESCRIPTION("Marvell SDIO Bus Driver");
MODULE_AUTHOR("Marvell International Ltd.");
MODULE_LICENSE("GPL");