blob: d3fd57cdde0b8139e2201ea9c858513d76a06b96 [file] [log] [blame]
/*
* ---------------------------------------------------------------------------
*
* FILE: sdio_mmc.c
*
* PURPOSE: SDIO driver interface for generic MMC stack.
*
* Copyright (C) 2008-2009 by Cambridge Silicon Radio Ltd.
*
* ---------------------------------------------------------------------------
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/gfp.h>
#include <linux/mmc/core.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/sdio_ids.h>
#include <linux/mmc/sdio.h>
#include <linux/suspend.h>
#include "unifi_priv.h"
#ifdef ANDROID_BUILD
struct wake_lock unifi_sdio_wake_lock; /* wakelock to prevent suspend while resuming */
#endif
static CsrSdioFunctionDriver *sdio_func_drv;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#ifdef CONFIG_PM
static int uf_sdio_mmc_power_event(struct notifier_block *this, unsigned long event, void *ptr);
#endif
/*
* We need to keep track of the power on/off because we can not call
* mmc_power_restore_host() when the card is already powered.
* Even then, we need to patch the MMC driver to add a power_restore handler
* in the mmc_sdio_ops structure. If the MMC driver before 2.6.37 is not patched,
* mmc_power_save_host() and mmc_power_restore_host() are no-ops in the kernel,
* returning immediately (at least on x86).
*/
static int card_is_powered = 1;
#endif /* 2.6.32 */
/* MMC uses ENOMEDIUM to indicate card gone away */
static CsrResult
ConvertSdioToCsrSdioResult(int r)
{
CsrResult csrResult = CSR_RESULT_FAILURE;
switch (r) {
case 0:
csrResult = CSR_RESULT_SUCCESS;
break;
case -EIO:
case -EILSEQ:
csrResult = CSR_SDIO_RESULT_CRC_ERROR;
break;
/* Timeout errors */
case -ETIMEDOUT:
case -EBUSY:
csrResult = CSR_SDIO_RESULT_TIMEOUT;
break;
case -ENODEV:
case -ENOMEDIUM:
csrResult = CSR_SDIO_RESULT_NO_DEVICE;
break;
case -EINVAL:
csrResult = CSR_SDIO_RESULT_INVALID_VALUE;
break;
case -ENOMEM:
case -ENOSYS:
case -ERANGE:
case -ENXIO:
csrResult = CSR_RESULT_FAILURE;
break;
default:
unifi_warning(NULL, "Unrecognised SDIO error code: %d\n", r);
break;
}
return csrResult;
}
static int
csr_io_rw_direct(struct mmc_card *card, int write, uint8_t fn,
uint32_t addr, uint8_t in, uint8_t* out)
{
struct mmc_command cmd;
int err;
BUG_ON(!card);
BUG_ON(fn > 7);
memset(&cmd, 0, sizeof(struct mmc_command));
cmd.opcode = SD_IO_RW_DIRECT;
cmd.arg = write ? 0x80000000 : 0x00000000;
cmd.arg |= fn << 28;
cmd.arg |= (write && out) ? 0x08000000 : 0x00000000;
cmd.arg |= addr << 9;
cmd.arg |= in;
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC;
err = mmc_wait_for_cmd(card->host, &cmd, 0);
if (err)
return err;
/* this function is not exported, so we will need to sort it out here
* for now, lets hard code it to sdio */
if (0) {
/* old arg (mmc_host_is_spi(card->host)) { */
/* host driver already reported errors */
} else {
if (cmd.resp[0] & R5_ERROR) {
printk(KERN_ERR "%s: r5 error 0x%02x\n",
__FUNCTION__, cmd.resp[0]);
return -EIO;
}
if (cmd.resp[0] & R5_FUNCTION_NUMBER)
return -EINVAL;
if (cmd.resp[0] & R5_OUT_OF_RANGE)
return -ERANGE;
}
if (out) {
if (0) { /* old argument (mmc_host_is_spi(card->host)) */
*out = (cmd.resp[0] >> 8) & 0xFF;
}
else {
*out = cmd.resp[0] & 0xFF;
}
}
return CSR_RESULT_SUCCESS;
}
CsrResult
CsrSdioRead8(CsrSdioFunction *function, u32 address, u8 *data)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err = 0;
_sdio_claim_host(func);
*data = sdio_readb(func, address, &err);
_sdio_release_host(func);
if (err) {
func_exit_r(err);
return ConvertSdioToCsrSdioResult(err);
}
return CSR_RESULT_SUCCESS;
} /* CsrSdioRead8() */
CsrResult
CsrSdioWrite8(CsrSdioFunction *function, u32 address, u8 data)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err = 0;
_sdio_claim_host(func);
sdio_writeb(func, data, address, &err);
_sdio_release_host(func);
if (err) {
func_exit_r(err);
return ConvertSdioToCsrSdioResult(err);
}
return CSR_RESULT_SUCCESS;
} /* CsrSdioWrite8() */
CsrResult
CsrSdioRead16(CsrSdioFunction *function, u32 address, u16 *data)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err;
uint8_t b0, b1;
_sdio_claim_host(func);
b0 = sdio_readb(func, address, &err);
if (err) {
_sdio_release_host(func);
return ConvertSdioToCsrSdioResult(err);
}
b1 = sdio_readb(func, address+1, &err);
if (err) {
_sdio_release_host(func);
return ConvertSdioToCsrSdioResult(err);
}
_sdio_release_host(func);
*data = ((uint16_t)b1 << 8) | b0;
return CSR_RESULT_SUCCESS;
} /* CsrSdioRead16() */
CsrResult
CsrSdioWrite16(CsrSdioFunction *function, u32 address, u16 data)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err;
uint8_t b0, b1;
_sdio_claim_host(func);
b1 = (data >> 8) & 0xFF;
sdio_writeb(func, b1, address+1, &err);
if (err) {
_sdio_release_host(func);
return ConvertSdioToCsrSdioResult(err);
}
b0 = data & 0xFF;
sdio_writeb(func, b0, address, &err);
if (err) {
_sdio_release_host(func);
return ConvertSdioToCsrSdioResult(err);
}
_sdio_release_host(func);
return CSR_RESULT_SUCCESS;
} /* CsrSdioWrite16() */
CsrResult
CsrSdioF0Read8(CsrSdioFunction *function, u32 address, u8 *data)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err = 0;
_sdio_claim_host(func);
#ifdef MMC_QUIRK_LENIENT_FN0
*data = sdio_f0_readb(func, address, &err);
#else
err = csr_io_rw_direct(func->card, 0, 0, address, 0, data);
#endif
_sdio_release_host(func);
if (err) {
func_exit_r(err);
return ConvertSdioToCsrSdioResult(err);
}
return CSR_RESULT_SUCCESS;
} /* CsrSdioF0Read8() */
CsrResult
CsrSdioF0Write8(CsrSdioFunction *function, u32 address, u8 data)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err = 0;
_sdio_claim_host(func);
#ifdef MMC_QUIRK_LENIENT_FN0
sdio_f0_writeb(func, data, address, &err);
#else
err = csr_io_rw_direct(func->card, 1, 0, address, data, NULL);
#endif
_sdio_release_host(func);
if (err) {
func_exit_r(err);
return ConvertSdioToCsrSdioResult(err);
}
return CSR_RESULT_SUCCESS;
} /* CsrSdioF0Write8() */
CsrResult
CsrSdioRead(CsrSdioFunction *function, u32 address, void *data, u32 length)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err;
_sdio_claim_host(func);
err = sdio_readsb(func, data, address, length);
_sdio_release_host(func);
if (err) {
func_exit_r(err);
return ConvertSdioToCsrSdioResult(err);
}
return CSR_RESULT_SUCCESS;
} /* CsrSdioRead() */
CsrResult
CsrSdioWrite(CsrSdioFunction *function, u32 address, const void *data, u32 length)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err;
_sdio_claim_host(func);
err = sdio_writesb(func, address, (void*)data, length);
_sdio_release_host(func);
if (err) {
func_exit_r(err);
return ConvertSdioToCsrSdioResult(err);
}
return CSR_RESULT_SUCCESS;
} /* CsrSdioWrite() */
static int
csr_sdio_enable_hs(struct mmc_card *card)
{
int ret;
u8 speed;
if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED)) {
/* We've asked for HS clock rates, but controller doesn't
* claim to support it. We should limit the clock
* to 25MHz via module parameter.
*/
printk(KERN_INFO "unifi: request HS but not MMC_CAP_SD_HIGHSPEED");
return 0;
}
if (!card->cccr.high_speed)
return 0;
#if 1
ret = csr_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed);
if (ret)
return ret;
speed |= SDIO_SPEED_EHS;
#else
/* Optimisation: Eliminate read by always assuming SHS and that reserved bits can be zero */
speed = SDIO_SPEED_EHS | SDIO_SPEED_SHS;
#endif
ret = csr_io_rw_direct(card, 1, 0, SDIO_CCCR_SPEED, speed, NULL);
if (ret)
return ret;
mmc_card_set_highspeed(card);
card->host->ios.timing = MMC_TIMING_SD_HS;
card->host->ops->set_ios(card->host, &card->host->ios);
return 0;
}
static int
csr_sdio_disable_hs(struct mmc_card *card)
{
int ret;
u8 speed;
if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED))
return 0;
if (!card->cccr.high_speed)
return 0;
#if 1
ret = csr_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed);
if (ret)
return ret;
speed &= ~SDIO_SPEED_EHS;
#else
/* Optimisation: Eliminate read by always assuming SHS and that reserved bits can be zero */
speed = SDIO_SPEED_SHS; /* clear SDIO_SPEED_EHS */
#endif
ret = csr_io_rw_direct(card, 1, 0, SDIO_CCCR_SPEED, speed, NULL);
if (ret)
return ret;
card->state &= ~MMC_STATE_HIGHSPEED;
card->host->ios.timing = MMC_TIMING_LEGACY;
card->host->ops->set_ios(card->host, &card->host->ios);
return 0;
}
/*
* ---------------------------------------------------------------------------
* CsrSdioMaxBusClockFrequencySet
*
* Set the maximum SDIO bus clock speed to use.
*
* Arguments:
* sdio SDIO context pointer
* maxFrequency maximum clock speed in Hz
*
* Returns:
* an error code.
* ---------------------------------------------------------------------------
*/
CsrResult
CsrSdioMaxBusClockFrequencySet(CsrSdioFunction *function, u32 maxFrequency)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
struct mmc_host *host = func->card->host;
struct mmc_ios *ios = &host->ios;
unsigned int max_hz;
int err;
u32 max_khz = maxFrequency/1000;
if (!max_khz || max_khz > sdio_clock) {
max_khz = sdio_clock;
}
_sdio_claim_host(func);
max_hz = 1000 * max_khz;
if (max_hz > host->f_max) {
max_hz = host->f_max;
}
if (max_hz > 25000000) {
err = csr_sdio_enable_hs(func->card);
} else {
err = csr_sdio_disable_hs(func->card);
}
if (err) {
printk(KERN_ERR "SDIO warning: Failed to configure SDIO clock mode\n");
_sdio_release_host(func);
return CSR_RESULT_SUCCESS;
}
ios->clock = max_hz;
host->ops->set_ios(host, ios);
_sdio_release_host(func);
return CSR_RESULT_SUCCESS;
} /* CsrSdioMaxBusClockFrequencySet() */
/*
* ---------------------------------------------------------------------------
* CsrSdioInterruptEnable
* CsrSdioInterruptDisable
*
* Enable or disable the SDIO interrupt.
* The driver disables the SDIO interrupt until the i/o thread can
* process it.
* The SDIO interrupt can be disabled by modifying the SDIO_INT_ENABLE
* register in the Card Common Control Register block, but this requires
* two CMD52 operations. A better solution is to mask the interrupt at
* the host controller.
*
* Arguments:
* sdio SDIO context pointer
*
* Returns:
* Zero on success or a UniFi driver error code.
*
* ---------------------------------------------------------------------------
*/
CsrResult
CsrSdioInterruptEnable(CsrSdioFunction *function)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err = 0;
#ifdef CSR_CONFIG_MMC_INT_BYPASS_KSOFTIRQD
sdio_unblock_card_irq(func);
#else
_sdio_claim_host(func);
/* Write the Int Enable in CCCR block */
#ifdef MMC_QUIRK_LENIENT_FN0
sdio_f0_writeb(func, 0x3, SDIO_CCCR_IENx, &err);
#else
err = csr_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, 0x03, NULL);
#endif
_sdio_release_host(func);
func_exit();
if (err) {
printk(KERN_ERR "unifi: %s: error %d writing IENx\n", __FUNCTION__, err);
return ConvertSdioToCsrSdioResult(err);
}
#endif
return CSR_RESULT_SUCCESS;
} /* CsrSdioInterruptEnable() */
CsrResult
CsrSdioInterruptDisable(CsrSdioFunction *function)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err = 0;
#ifdef CSR_CONFIG_MMC_INT_BYPASS_KSOFTIRQD
sdio_block_card_irq(func);
#else
_sdio_claim_host(func);
/* Write the Int Enable in CCCR block */
#ifdef MMC_QUIRK_LENIENT_FN0
sdio_f0_writeb(func, 0, SDIO_CCCR_IENx, &err);
#else
err = csr_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, 0x00, NULL);
#endif
_sdio_release_host(func);
func_exit();
if (err) {
printk(KERN_ERR "unifi: %s: error %d writing IENx\n", __FUNCTION__, err);
return ConvertSdioToCsrSdioResult(err);
}
#endif
return CSR_RESULT_SUCCESS;
} /* CsrSdioInterruptDisable() */
void CsrSdioInterruptAcknowledge(CsrSdioFunction *function)
{
}
/*
* ---------------------------------------------------------------------------
* CsrSdioFunctionEnable
*
* Enable i/o on function 1.
*
* Arguments:
* sdio SDIO context pointer
*
* Returns:
* UniFi driver error code.
* ---------------------------------------------------------------------------
*/
CsrResult
CsrSdioFunctionEnable(CsrSdioFunction *function)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err;
func_enter();
/* Enable UniFi function 1 (the 802.11 part). */
_sdio_claim_host(func);
err = sdio_enable_func(func);
_sdio_release_host(func);
if (err) {
unifi_error(NULL, "Failed to enable SDIO function %d\n", func->num);
}
func_exit();
return ConvertSdioToCsrSdioResult(err);
} /* CsrSdioFunctionEnable() */
/*
* ---------------------------------------------------------------------------
* CsrSdioFunctionDisable
*
* Enable i/o on function 1.
*
* Arguments:
* sdio SDIO context pointer
*
* Returns:
* UniFi driver error code.
* ---------------------------------------------------------------------------
*/
CsrResult
CsrSdioFunctionDisable(CsrSdioFunction *function)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int err;
func_enter();
/* Disable UniFi function 1 (the 802.11 part). */
_sdio_claim_host(func);
err = sdio_disable_func(func);
_sdio_release_host(func);
if (err) {
unifi_error(NULL, "Failed to disable SDIO function %d\n", func->num);
}
func_exit();
return ConvertSdioToCsrSdioResult(err);
} /* CsrSdioFunctionDisable() */
/*
* ---------------------------------------------------------------------------
* CsrSdioFunctionActive
*
* No-op as the bus goes to an active state at the start of every
* command.
*
* Arguments:
* sdio SDIO context pointer
* ---------------------------------------------------------------------------
*/
void
CsrSdioFunctionActive(CsrSdioFunction *function)
{
} /* CsrSdioFunctionActive() */
/*
* ---------------------------------------------------------------------------
* CsrSdioFunctionIdle
*
* Set the function as idle.
*
* Arguments:
* sdio SDIO context pointer
* ---------------------------------------------------------------------------
*/
void
CsrSdioFunctionIdle(CsrSdioFunction *function)
{
} /* CsrSdioFunctionIdle() */
/*
* ---------------------------------------------------------------------------
* CsrSdioPowerOn
*
* Power on UniFi.
*
* Arguments:
* sdio SDIO context pointer
* ---------------------------------------------------------------------------
*/
CsrResult
CsrSdioPowerOn(CsrSdioFunction *function)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
struct sdio_func *func = (struct sdio_func *)function->priv;
struct mmc_host *host = func->card->host;
_sdio_claim_host(func);
if (!card_is_powered) {
mmc_power_restore_host(host);
card_is_powered = 1;
} else {
printk(KERN_INFO "SDIO: Skip power on; card is already powered.\n");
}
_sdio_release_host(func);
#endif /* 2.6.32 */
return CSR_RESULT_SUCCESS;
} /* CsrSdioPowerOn() */
/*
* ---------------------------------------------------------------------------
* CsrSdioPowerOff
*
* Power off UniFi.
*
* Arguments:
* sdio SDIO context pointer
* ---------------------------------------------------------------------------
*/
void
CsrSdioPowerOff(CsrSdioFunction *function)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
struct sdio_func *func = (struct sdio_func *)function->priv;
struct mmc_host *host = func->card->host;
_sdio_claim_host(func);
if (card_is_powered) {
mmc_power_save_host(host);
card_is_powered = 0;
} else {
printk(KERN_INFO "SDIO: Skip power off; card is already powered off.\n");
}
_sdio_release_host(func);
#endif /* 2.6.32 */
} /* CsrSdioPowerOff() */
static int
sdio_set_block_size_ignore_first_error(struct sdio_func *func, unsigned blksz)
{
int ret;
if (blksz > func->card->host->max_blk_size)
return -EINVAL;
if (blksz == 0) {
blksz = min(func->max_blksize, func->card->host->max_blk_size);
blksz = min(blksz, 512u);
}
/*
* Ignore -ERANGE (OUT_OF_RANGE in R5) on the first byte as
* the block size may be invalid until both bytes are written.
*/
ret = csr_io_rw_direct(func->card, 1, 0,
SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE,
blksz & 0xff, NULL);
if (ret && ret != -ERANGE)
return ret;
ret = csr_io_rw_direct(func->card, 1, 0,
SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE + 1,
(blksz >> 8) & 0xff, NULL);
if (ret)
return ret;
func->cur_blksize = blksz;
return 0;
}
CsrResult
CsrSdioBlockSizeSet(CsrSdioFunction *function, u16 blockSize)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int r = 0;
/* Module parameter overrides */
if (sdio_block_size > -1) {
blockSize = sdio_block_size;
}
unifi_trace(NULL, UDBG1, "Set SDIO function block size to %d\n",
blockSize);
_sdio_claim_host(func);
r = sdio_set_block_size(func, blockSize);
_sdio_release_host(func);
/*
* The MMC driver for kernels prior to 2.6.32 may fail this request
* with -ERANGE. In this case use our workaround.
*/
if (r == -ERANGE) {
_sdio_claim_host(func);
r = sdio_set_block_size_ignore_first_error(func, blockSize);
_sdio_release_host(func);
}
if (r) {
unifi_error(NULL, "Error %d setting block size\n", r);
}
/* Determine the achieved block size to pass to the core */
function->blockSize = func->cur_blksize;
return ConvertSdioToCsrSdioResult(r);
} /* CsrSdioBlockSizeSet() */
/*
* ---------------------------------------------------------------------------
* CsrSdioHardReset
*
* Hard Resets UniFi is possible.
*
* Arguments:
* sdio SDIO context pointer
* ---------------------------------------------------------------------------
*/
CsrResult
CsrSdioHardReset(CsrSdioFunction *function)
{
return CSR_RESULT_FAILURE;
} /* CsrSdioHardReset() */
/*
* ---------------------------------------------------------------------------
* uf_glue_sdio_int_handler
*
* Interrupt callback function for SDIO interrupts.
* This is called in kernel context (i.e. not interrupt context).
*
* Arguments:
* func SDIO context pointer
*
* Returns:
* None.
*
* Note: Called with host already claimed.
* ---------------------------------------------------------------------------
*/
static void
uf_glue_sdio_int_handler(struct sdio_func *func)
{
CsrSdioFunction *sdio_ctx;
CsrSdioInterruptDsrCallback func_dsr_callback;
int r;
sdio_ctx = sdio_get_drvdata(func);
if (!sdio_ctx) {
return;
}
#ifndef CSR_CONFIG_MMC_INT_BYPASS_KSOFTIRQD
/*
* Normally, we are not allowed to do any SDIO commands here.
* However, this is called in a thread context and with the SDIO lock
* so we disable the interrupts here instead of trying to do complicated
* things with the SDIO lock.
*/
#ifdef MMC_QUIRK_LENIENT_FN0
sdio_f0_writeb(func, 0, SDIO_CCCR_IENx, &r);
#else
r = csr_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, 0x00, NULL);
#endif
if (r) {
printk(KERN_ERR "UniFi MMC Int handler: Failed to disable interrupts %d\n", r);
}
#endif
/* If the function driver has registered a handler, call it */
if (sdio_func_drv && sdio_func_drv->intr) {
func_dsr_callback = sdio_func_drv->intr(sdio_ctx);
/* If interrupt handle returns a DSR handle, call it */
if (func_dsr_callback) {
func_dsr_callback(sdio_ctx);
}
}
} /* uf_glue_sdio_int_handler() */
/*
* ---------------------------------------------------------------------------
* csr_sdio_linux_remove_irq
*
* Unregister the interrupt handler.
* This means that the linux layer can not process interrupts any more.
*
* Arguments:
* sdio SDIO context pointer
*
* Returns:
* Status of the removal.
* ---------------------------------------------------------------------------
*/
int
csr_sdio_linux_remove_irq(CsrSdioFunction *function)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int r;
unifi_trace(NULL, UDBG1, "csr_sdio_linux_remove_irq\n");
sdio_claim_host(func);
r = sdio_release_irq(func);
sdio_release_host(func);
return r;
} /* csr_sdio_linux_remove_irq() */
/*
* ---------------------------------------------------------------------------
* csr_sdio_linux_install_irq
*
* Register the interrupt handler.
* This means that the linux layer can process interrupts.
*
* Arguments:
* sdio SDIO context pointer
*
* Returns:
* Status of the removal.
* ---------------------------------------------------------------------------
*/
int
csr_sdio_linux_install_irq(CsrSdioFunction *function)
{
struct sdio_func *func = (struct sdio_func *)function->priv;
int r;
unifi_trace(NULL, UDBG1, "csr_sdio_linux_install_irq\n");
/* Register our interrupt handle */
sdio_claim_host(func);
r = sdio_claim_irq(func, uf_glue_sdio_int_handler);
sdio_release_host(func);
/* If the interrupt was installed earlier, is fine */
if (r == -EBUSY) {
r = 0;
}
return r;
} /* csr_sdio_linux_install_irq() */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#ifdef CONFIG_PM
/*
* Power Management notifier
*/
struct uf_sdio_mmc_pm_notifier
{
struct list_head list;
CsrSdioFunction *sdio_ctx;
struct notifier_block pm_notifier;
};
/* PM notifier list head */
static struct uf_sdio_mmc_pm_notifier uf_sdio_mmc_pm_notifiers = {
.sdio_ctx = NULL,
};
/*
* ---------------------------------------------------------------------------
* uf_sdio_mmc_register_pm_notifier
* uf_sdio_mmc_unregister_pm_notifier
*
* Register/unregister for power management events. A list is used to
* allow multiple card instances to be supported.
*
* Arguments:
* sdio_ctx - CSR SDIO context to associate PM notifier to
*
* Returns:
* Register function returns NULL on error
* ---------------------------------------------------------------------------
*/
static struct uf_sdio_mmc_pm_notifier *
uf_sdio_mmc_register_pm_notifier(CsrSdioFunction *sdio_ctx)
{
/* Allocate notifier context for this card instance */
struct uf_sdio_mmc_pm_notifier *notifier_ctx = kmalloc(sizeof(struct uf_sdio_mmc_pm_notifier), GFP_KERNEL);
if (notifier_ctx)
{
notifier_ctx->sdio_ctx = sdio_ctx;
notifier_ctx->pm_notifier.notifier_call = uf_sdio_mmc_power_event;
list_add(&notifier_ctx->list, &uf_sdio_mmc_pm_notifiers.list);
if (register_pm_notifier(&notifier_ctx->pm_notifier)) {
printk(KERN_ERR "unifi: register_pm_notifier failed\n");
}
}
return notifier_ctx;
}
static void
uf_sdio_mmc_unregister_pm_notifier(CsrSdioFunction *sdio_ctx)
{
struct uf_sdio_mmc_pm_notifier *notifier_ctx;
struct list_head *node, *q;
list_for_each_safe(node, q, &uf_sdio_mmc_pm_notifiers.list) {
notifier_ctx = list_entry(node, struct uf_sdio_mmc_pm_notifier, list);
/* If it matches, unregister and free the notifier context */
if (notifier_ctx && notifier_ctx->sdio_ctx == sdio_ctx)
{
if (unregister_pm_notifier(&notifier_ctx->pm_notifier)) {
printk(KERN_ERR "unifi: unregister_pm_notifier failed\n");
}
/* Remove from list */
notifier_ctx->sdio_ctx = NULL;
list_del(node);
kfree(notifier_ctx);
}
}
}
/*
* ---------------------------------------------------------------------------
* uf_sdio_mmc_power_event
*
* Handler for power management events.
*
* We need to handle suspend/resume events while the userspace is unsuspended
* to allow the SME to run its suspend/resume state machines.
*
* Arguments:
* event event ID
*
* Returns:
* Status of the event handling
* ---------------------------------------------------------------------------
*/
static int
uf_sdio_mmc_power_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct uf_sdio_mmc_pm_notifier *notifier_ctx = container_of(this,
struct uf_sdio_mmc_pm_notifier,
pm_notifier);
/* Call the CSR SDIO function driver's suspend/resume method
* while the userspace is unsuspended.
*/
switch (event) {
case PM_POST_HIBERNATION:
case PM_POST_SUSPEND:
printk(KERN_INFO "%s:%d resume\n", __FUNCTION__, __LINE__ );
if (sdio_func_drv && sdio_func_drv->resume) {
sdio_func_drv->resume(notifier_ctx->sdio_ctx);
}
break;
case PM_HIBERNATION_PREPARE:
case PM_SUSPEND_PREPARE:
printk(KERN_INFO "%s:%d suspend\n", __FUNCTION__, __LINE__ );
if (sdio_func_drv && sdio_func_drv->suspend) {
sdio_func_drv->suspend(notifier_ctx->sdio_ctx);
}
break;
}
return NOTIFY_DONE;
}
#endif /* CONFIG_PM */
#endif /* 2.6.32 */
/*
* ---------------------------------------------------------------------------
* uf_glue_sdio_probe
*
* Card insert callback.
*
* Arguments:
* func Our (glue layer) context pointer.
*
* Returns:
* UniFi driver error code.
* ---------------------------------------------------------------------------
*/
static int
uf_glue_sdio_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
int instance;
CsrSdioFunction *sdio_ctx;
func_enter();
/* First of all claim the SDIO driver */
sdio_claim_host(func);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
/* Assume that the card is already powered */
card_is_powered = 1;
#endif
/* Assumes one card per host, which is true for SDIO */
instance = func->card->host->index;
printk("sdio bus_id: %16s - UniFi card 0x%X inserted\n",
sdio_func_id(func), instance);
/* Allocate context */
sdio_ctx = (CsrSdioFunction *)kmalloc(sizeof(CsrSdioFunction),
GFP_KERNEL);
if (sdio_ctx == NULL) {
sdio_release_host(func);
return -ENOMEM;
}
/* Initialise the context */
sdio_ctx->sdioId.manfId = func->vendor;
sdio_ctx->sdioId.cardId = func->device;
sdio_ctx->sdioId.sdioFunction = func->num;
sdio_ctx->sdioId.sdioInterface = func->class;
sdio_ctx->blockSize = func->cur_blksize;
sdio_ctx->priv = (void *)func;
sdio_ctx->features = 0;
/* Module parameter enables byte mode */
if (sdio_byte_mode) {
sdio_ctx->features |= CSR_SDIO_FEATURE_BYTE_MODE;
}
if (func->card->host->caps & MMC_CAP_SD_HIGHSPEED) {
unifi_trace(NULL, UDBG1, "MMC_CAP_SD_HIGHSPEED is available\n");
}
#ifdef MMC_QUIRK_LENIENT_FN0
func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
#endif
/* Pass context to the SDIO driver */
sdio_set_drvdata(func, sdio_ctx);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#ifdef CONFIG_PM
/* Register to get PM events */
if (uf_sdio_mmc_register_pm_notifier(sdio_ctx) == NULL) {
unifi_error(NULL, "%s: Failed to register for PM events\n", __FUNCTION__);
}
#endif
#endif
/* Register this device with the SDIO function driver */
/* Call the main UniFi driver inserted handler */
if (sdio_func_drv && sdio_func_drv->inserted) {
uf_add_os_device(instance, &func->dev);
sdio_func_drv->inserted(sdio_ctx);
}
/* We have finished, so release the SDIO driver */
sdio_release_host(func);
#ifdef ANDROID_BUILD
/* Take the wakelock */
unifi_trace(NULL, UDBG1, "probe: take wake lock\n");
wake_lock(&unifi_sdio_wake_lock);
#endif
func_exit();
return 0;
} /* uf_glue_sdio_probe() */
/*
* ---------------------------------------------------------------------------
* uf_glue_sdio_remove
*
* Card removal callback.
*
* Arguments:
* func Our (glue layer) context pointer.
*
* Returns:
* UniFi driver error code.
* ---------------------------------------------------------------------------
*/
static void
uf_glue_sdio_remove(struct sdio_func *func)
{
CsrSdioFunction *sdio_ctx;
sdio_ctx = sdio_get_drvdata(func);
if (!sdio_ctx) {
return;
}
func_enter();
unifi_info(NULL, "UniFi card removed\n");
/* Clean up the SDIO function driver */
if (sdio_func_drv && sdio_func_drv->removed) {
uf_remove_os_device(func->card->host->index);
sdio_func_drv->removed(sdio_ctx);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#ifdef CONFIG_PM
/* Unregister for PM events */
uf_sdio_mmc_unregister_pm_notifier(sdio_ctx);
#endif
#endif
kfree(sdio_ctx);
func_exit();
} /* uf_glue_sdio_remove */
/*
* SDIO ids *must* be statically declared, so we can't take
* them from the list passed in csr_sdio_register_driver().
*/
static const struct sdio_device_id unifi_ids[] = {
{ SDIO_DEVICE(SDIO_MANF_ID_CSR,SDIO_CARD_ID_UNIFI_3) },
{ SDIO_DEVICE(SDIO_MANF_ID_CSR,SDIO_CARD_ID_UNIFI_4) },
{ /* end: all zeroes */ },
};
MODULE_DEVICE_TABLE(sdio, unifi_ids);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#ifdef CONFIG_PM
/*
* ---------------------------------------------------------------------------
* uf_glue_sdio_suspend
*
* Card suspend callback. The userspace will already be suspended.
*
* Arguments:
* dev The struct device owned by the MMC driver
*
* Returns:
* None
* ---------------------------------------------------------------------------
*/
static int
uf_glue_sdio_suspend(struct device *dev)
{
func_enter();
unifi_trace(NULL, UDBG1, "uf_glue_sdio_suspend");
func_exit();
return 0;
} /* uf_glue_sdio_suspend */
/*
* ---------------------------------------------------------------------------
* uf_glue_sdio_resume
*
* Card resume callback. The userspace will still be suspended.
*
* Arguments:
* dev The struct device owned by the MMC driver
*
* Returns:
* None
* ---------------------------------------------------------------------------
*/
static int
uf_glue_sdio_resume(struct device *dev)
{
func_enter();
unifi_trace(NULL, UDBG1, "uf_glue_sdio_resume");
#ifdef ANDROID_BUILD
unifi_trace(NULL, UDBG1, "resume: take wakelock\n");
wake_lock(&unifi_sdio_wake_lock);
#endif
func_exit();
return 0;
} /* uf_glue_sdio_resume */
static struct dev_pm_ops unifi_pm_ops = {
.suspend = uf_glue_sdio_suspend,
.resume = uf_glue_sdio_resume,
};
#define UNIFI_PM_OPS (&unifi_pm_ops)
#else
#define UNIFI_PM_OPS NULL
#endif /* CONFIG_PM */
#endif /* 2.6.32 */
static struct sdio_driver unifi_driver = {
.probe = uf_glue_sdio_probe,
.remove = uf_glue_sdio_remove,
.name = "unifi",
.id_table = unifi_ids,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
.drv.pm = UNIFI_PM_OPS,
#endif /* 2.6.32 */
};
/*
* ---------------------------------------------------------------------------
* CsrSdioFunctionDriverRegister
* CsrSdioFunctionDriverUnregister
*
* These functions are called from the main module load and unload
* functions. They perform the appropriate operations for the
* linux MMC/SDIO driver.
*
* Arguments:
* sdio_drv Pointer to the function driver's SDIO structure.
*
* Returns:
* None.
* ---------------------------------------------------------------------------
*/
CsrResult
CsrSdioFunctionDriverRegister(CsrSdioFunctionDriver *sdio_drv)
{
int r;
printk("UniFi: Using native Linux MMC driver for SDIO.\n");
if (sdio_func_drv) {
unifi_error(NULL, "sdio_mmc: UniFi driver already registered\n");
return CSR_SDIO_RESULT_INVALID_VALUE;
}
#ifdef ANDROID_BUILD
wake_lock_init(&unifi_sdio_wake_lock, WAKE_LOCK_SUSPEND, "unifi_sdio_work");
#endif
/* Save the registered driver description */
/*
* FIXME:
* Need a table here to handle a call to register for just one function.
* mmc only allows us to register for the whole device
*/
sdio_func_drv = sdio_drv;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#ifdef CONFIG_PM
/* Initialise PM notifier list */
INIT_LIST_HEAD(&uf_sdio_mmc_pm_notifiers.list);
#endif
#endif
/* Register ourself with mmc_core */
r = sdio_register_driver(&unifi_driver);
if (r) {
printk(KERN_ERR "unifi_sdio: Failed to register UniFi SDIO driver: %d\n", r);
return ConvertSdioToCsrSdioResult(r);
}
return CSR_RESULT_SUCCESS;
} /* CsrSdioFunctionDriverRegister() */
void
CsrSdioFunctionDriverUnregister(CsrSdioFunctionDriver *sdio_drv)
{
printk(KERN_INFO "UniFi: unregister from MMC sdio\n");
#ifdef ANDROID_BUILD
wake_lock_destroy(&unifi_sdio_wake_lock);
#endif
sdio_unregister_driver(&unifi_driver);
sdio_func_drv = NULL;
} /* CsrSdioFunctionDriverUnregister() */