Kernel  |  3.10

下载     查看原文件
C++程序  |  1289行  |  33.08 KB
/*
 * ---------------------------------------------------------------------------
 *
 * 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;

#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;

/* 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) {
        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) {
        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) {
        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) {
        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) {
        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) {
        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);

    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);

    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;

    /* 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);
    }

    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;

    /* 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);
    }

    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)
{
    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);

    return CSR_RESULT_SUCCESS;
} /* CsrSdioPowerOn() */

/*
 * ---------------------------------------------------------------------------
 *  CsrSdioPowerOff
 *
 *      Power off UniFi.
 *
 *  Arguments:
 *      sdio            SDIO context pointer
 * ---------------------------------------------------------------------------
 */
void
CsrSdioPowerOff(CsrSdioFunction *function)
{
    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);
} /* 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() */

#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 */

/*
 * ---------------------------------------------------------------------------
 *  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;

    /* First of all claim the SDIO driver */
    sdio_claim_host(func);

    /* Assume that the card is already powered */
    card_is_powered = 1;

    /* 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 = 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);

#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

    /* 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

    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;
    }

    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);
    }

#ifdef CONFIG_PM
    /* Unregister for PM events */
    uf_sdio_mmc_unregister_pm_notifier(sdio_ctx);
#endif

    kfree(sdio_ctx);

} /* 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);

#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)
{
    unifi_trace(NULL, UDBG1, "uf_glue_sdio_suspend");

    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)
{
    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

    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 */

static struct sdio_driver unifi_driver = {
    .probe      = uf_glue_sdio_probe,
    .remove     = uf_glue_sdio_remove,
    .name       = "unifi",
    .id_table	= unifi_ids,
    .drv.pm     = UNIFI_PM_OPS,
};


/*
 * ---------------------------------------------------------------------------
 *  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;

#ifdef CONFIG_PM
    /* Initialise PM notifier list */
    INIT_LIST_HEAD(&uf_sdio_mmc_pm_notifiers.list);
#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() */