| /* |
| * --------------------------------------------------------------------------- |
| * |
| * 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/version.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) { |
| 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) |
| { |
| 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(¬ifier_ctx->list, &uf_sdio_mmc_pm_notifiers.list); |
| |
| if (register_pm_notifier(¬ifier_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(¬ifier_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; |
| |
| func_enter(); |
| |
| /* 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 = (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); |
| |
| #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 |
| |
| 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); |
| } |
| |
| #ifdef CONFIG_PM |
| /* Unregister for PM events */ |
| uf_sdio_mmc_unregister_pm_notifier(sdio_ctx); |
| #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); |
| |
| #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 */ |
| |
| 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() */ |
| |