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