summaryrefslogtreecommitdiffstats
path: root/drivers/staging/csr/sdio_mmc.c
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-06-19 16:15:42 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-06-19 16:37:01 -0700
commit635d2b00e5070378e7bf812acf47fb135c6ab928 (patch)
tree7048a0a511f3d221aa2dfe40aa3a401991f1b175 /drivers/staging/csr/sdio_mmc.c
parent15a4bc17b7f4e85cb019e683f14e834078ec2208 (diff)
downloadop-kernel-dev-635d2b00e5070378e7bf812acf47fb135c6ab928.zip
op-kernel-dev-635d2b00e5070378e7bf812acf47fb135c6ab928.tar.gz
Staging: add CSR wifi module
This consists of two modules, the driver, and a "helper" module that is just a wrapper around common kernel functions. The wrapper module will be removed soon, but for now it's needed. These files were based on the csr-linux-wifi-5.0.3-oss.tar.gz package provided by CSR and Blue Giga, and is covered under the license specified in the LICENSE.txt file (basically dual BSD and GPLv2). The files were flattened out of the deep directory mess they were originally in, and a few EXPORT_SYMBOL_GPL() were added in order for everything to link properly with the helper module setup. Cc: Mikko Virkkilä <mikko.virkkila@bluegiga.com> Cc: Lauri Hintsala <Lauri.Hintsala@bluegiga.com> Cc: Riku Mettälä <riku.mettala@bluegiga.com> Cc: Veli-Pekka Peltola <veli-pekka.peltola@bluegiga.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/staging/csr/sdio_mmc.c')
-rw-r--r--drivers/staging/csr/sdio_mmc.c1165
1 files changed, 1165 insertions, 0 deletions
diff --git a/drivers/staging/csr/sdio_mmc.c b/drivers/staging/csr/sdio_mmc.c
new file mode 100644
index 0000000..c850834
--- /dev/null
+++ b/drivers/staging/csr/sdio_mmc.c
@@ -0,0 +1,1165 @@
+/*
+ * ---------------------------------------------------------------------------
+ *
+ * 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 "unifi_priv.h"
+
+
+static CsrSdioFunctionDriver *sdio_func_drv;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+/*
+ * 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 is not patched,
+ * mmc_power_save_host() and mmc_power_restore_host() are no-ops.
+ */
+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, CsrUint32 address, CsrUint8 *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, CsrUint32 address, CsrUint8 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, CsrUint32 address, CsrUint16 *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, CsrUint32 address, CsrUint16 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, CsrUint32 address, CsrUint8 *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, CsrUint32 address, CsrUint8 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, CsrUint32 address, void *data, CsrUint32 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, CsrUint32 address, const void *data, CsrUint32 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))
+ return 0;
+
+ if (!card->cccr.high_speed)
+ return 0;
+
+ ret = csr_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed);
+ if (ret)
+ return ret;
+
+ speed |= SDIO_SPEED_EHS;
+
+ 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;
+
+ ret = csr_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed);
+ if (ret)
+ return ret;
+
+ speed &= ~SDIO_SPEED_EHS;
+
+ 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, CsrUint32 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;
+ CsrUint32 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) {
+ 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) {
+ 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, CsrUint16 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\n");
+ }
+#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;
+
+ 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;
+
+ /* 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() */
+
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * 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;
+ }
+
+#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);
+
+ /* 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);
+
+ 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);
+ }
+
+ 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.
+ *
+ * Arguments:
+ * dev The struct device owned by the MMC driver
+ *
+ * Returns:
+ * None
+ * ---------------------------------------------------------------------------
+ */
+static int
+uf_glue_sdio_suspend(struct device *dev)
+{
+ struct sdio_func *func;
+ CsrSdioFunction *sdio_ctx;
+
+ func_enter();
+
+ func = dev_to_sdio_func(dev);
+ WARN_ON(!func);
+
+ sdio_ctx = sdio_get_drvdata(func);
+ WARN_ON(!sdio_ctx);
+
+ unifi_trace(NULL, UDBG1, "System Suspend...\n");
+
+ /* Clean up the SDIO function driver */
+ if (sdio_func_drv && sdio_func_drv->suspend) {
+ sdio_func_drv->suspend(sdio_ctx);
+ }
+
+ func_exit();
+ return 0;
+} /* uf_glue_sdio_suspend */
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * uf_glue_sdio_resume
+ *
+ * Card resume callback.
+ *
+ * Arguments:
+ * dev The struct device owned by the MMC driver
+ *
+ * Returns:
+ * None
+ * ---------------------------------------------------------------------------
+ */
+static int
+uf_glue_sdio_resume(struct device *dev)
+{
+ struct sdio_func *func;
+ CsrSdioFunction *sdio_ctx;
+
+ func_enter();
+
+ func = dev_to_sdio_func(dev);
+ WARN_ON(!func);
+
+ sdio_ctx = sdio_get_drvdata(func);
+ WARN_ON(!sdio_ctx);
+
+ unifi_trace(NULL, UDBG1, "System Resume...\n");
+
+ /* Clean up the SDIO function driver */
+ if (sdio_func_drv && sdio_func_drv->resume) {
+ sdio_func_drv->resume(sdio_ctx);
+ }
+
+ 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;
+ }
+
+ /* 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;
+
+ /* 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");
+ sdio_unregister_driver(&unifi_driver);
+
+ sdio_func_drv = NULL;
+
+} /* CsrSdioFunctionDriverUnregister() */
+
OpenPOWER on IntegriCloud