diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-29 05:59:45 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-29 05:59:45 +0200 |
commit | b5174fa3a7f4f8f150bfa3b917c92608953dfa0f (patch) | |
tree | 5efd32dd52fe55f760094e78f18acd3ff869751d /drivers/mmc | |
parent | Merge branch 'for-linus' of git://git.open-osd.org/linux-open-osd (diff) | |
parent | mmc: sh_mmcif: simplify bitmask macros (diff) | |
download | linux-b5174fa3a7f4f8f150bfa3b917c92608953dfa0f.tar.xz linux-b5174fa3a7f4f8f150bfa3b917c92608953dfa0f.zip |
Merge tag 'mmc-merge-for-3.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc
Pull MMC updates from Chris Ball:
Core:
* Support for MMC 4.5 Data Tag feature -- we tag REQ_META, so devices
that support Data Tag will provide increased throughput for metadata.
* Faster detection of card removal on I/O errors.
Drivers:
* dw_mmc now supports eMMC Power Off Notify, has PCI support, and
implements pre_req and post_req for asynchronous requests.
* omap_hsmmc now supports device tree.
* esdhc now has power management support.
* sdhci-tegra now supports Tegra30 devices.
* sdhci-spear now supports hibernation.
* tmio_mmc now supports using a GPIO for card detection.
* Intel PCH now supports 8-bit bus transfers.
* tag 'mmc-merge-for-3.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc: (53 commits)
mmc: sh_mmcif: simplify bitmask macros
mmc: sh_mobile_sdhi: support modular mmc-core with non-standard hotplug
mmc: sh_mobile_sdhi: add a callback for board specific init code
mmc: tmio: cosmetic: prettify the tmio_mmc_set_ios() function
mmc: sh_mobile_sdhi: do not manage PM clocks manually
mmc: tmio_mmc: remove unused sdio_irq_enabled flag
mmc: tmio_mmc: power status flag doesn't have to be exposed in platform data
mmc: sh_mobile_sdhi: pass card hotplug GPIO number to TMIO MMC
mmc: tmio_mmc: support the generic MMC GPIO card hotplug helper
mmc: tmio: calculate the native hotplug condition only once
mmc: simplify mmc_cd_gpio_request() by removing two parameters
mmc: sdhci-pci: allow 8-bit bus width for Intel PCH
mmc: sdhci: check interrupt flags in ISR again
mmc: sdhci-pci: Add MSI support
mmc: core: warn when card doesn't support HPI
mmc: davinci: Poll status for small size transfers
mmc: davinci: Eliminate spurious interrupts
mmc: omap_hsmmc: Avoid a regulator voltage change with dt
mmc: omap_hsmmc: Convert hsmmc driver to use device tree
mmc: sdhci-pci: add SDHCI_QUIRK2_HOST_OFF_CARD_ON for Medfield SDIO
...
Diffstat (limited to 'drivers/mmc')
26 files changed, 1058 insertions, 645 deletions
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 4c3b2847e47c..eed213a5c8cb 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1079,6 +1079,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, struct mmc_blk_request *brq = &mqrq->brq; struct request *req = mqrq->req; struct mmc_blk_data *md = mq->data; + bool do_data_tag; /* * Reliable writes are used to implement Forced Unit Access and @@ -1155,6 +1156,16 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, mmc_apply_rel_rw(brq, card, req); /* + * Data tag is used only during writing meta data to speed + * up write and any subsequent read of this meta data + */ + do_data_tag = (card->ext_csd.data_tag_unit_size) && + (req->cmd_flags & REQ_META) && + (rq_data_dir(req) == WRITE) && + ((brq->data.blocks * brq->data.blksz) >= + card->ext_csd.data_tag_unit_size); + + /* * Pre-defined multi-block transfers are preferable to * open ended-ones (and necessary for reliable writes). * However, it is not sufficient to just send CMD23, @@ -1172,13 +1183,13 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, * We'll avoid using CMD23-bounded multiblock writes for * these, while retaining features like reliable writes. */ - - if ((md->flags & MMC_BLK_CMD23) && - mmc_op_multi(brq->cmd.opcode) && - (do_rel_wr || !(card->quirks & MMC_QUIRK_BLK_NO_CMD23))) { + if ((md->flags & MMC_BLK_CMD23) && mmc_op_multi(brq->cmd.opcode) && + (do_rel_wr || !(card->quirks & MMC_QUIRK_BLK_NO_CMD23) || + do_data_tag)) { brq->sbc.opcode = MMC_SET_BLOCK_COUNT; brq->sbc.arg = brq->data.blocks | - (do_rel_wr ? (1 << 31) : 0); + (do_rel_wr ? (1 << 31) : 0) | + (do_data_tag ? (1 << 29) : 0); brq->sbc.flags = MMC_RSP_R1 | MMC_CMD_AC; brq->mrq.sbc = &brq->sbc; } diff --git a/drivers/mmc/core/cd-gpio.c b/drivers/mmc/core/cd-gpio.c index 082202ae4a03..29de31e260dd 100644 --- a/drivers/mmc/core/cd-gpio.c +++ b/drivers/mmc/core/cd-gpio.c @@ -28,13 +28,17 @@ static irqreturn_t mmc_cd_gpio_irqt(int irq, void *dev_id) return IRQ_HANDLED; } -int mmc_cd_gpio_request(struct mmc_host *host, unsigned int gpio, - unsigned int irq, unsigned long flags) +int mmc_cd_gpio_request(struct mmc_host *host, unsigned int gpio) { size_t len = strlen(dev_name(host->parent)) + 4; - struct mmc_cd_gpio *cd = kmalloc(sizeof(*cd) + len, GFP_KERNEL); + struct mmc_cd_gpio *cd; + int irq = gpio_to_irq(gpio); int ret; + if (irq < 0) + return irq; + + cd = kmalloc(sizeof(*cd) + len, GFP_KERNEL); if (!cd) return -ENOMEM; @@ -45,7 +49,8 @@ int mmc_cd_gpio_request(struct mmc_host *host, unsigned int gpio, goto egpioreq; ret = request_threaded_irq(irq, NULL, mmc_cd_gpio_irqt, - flags, cd->label, host); + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + cd->label, host); if (ret < 0) goto eirqreq; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 132378b89d76..14f262e9246d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -188,6 +188,12 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) struct scatterlist *sg; #endif + if (mrq->sbc) { + pr_debug("<%s: starting CMD%u arg %08x flags %08x>\n", + mmc_hostname(host), mrq->sbc->opcode, + mrq->sbc->arg, mrq->sbc->flags); + } + pr_debug("%s: starting CMD%u arg %08x flags %08x\n", mmc_hostname(host), mrq->cmd->opcode, mrq->cmd->arg, mrq->cmd->flags); @@ -243,16 +249,17 @@ static void mmc_wait_done(struct mmc_request *mrq) complete(&mrq->completion); } -static void __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) +static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) { init_completion(&mrq->completion); mrq->done = mmc_wait_done; if (mmc_card_removed(host->card)) { mrq->cmd->error = -ENOMEDIUM; complete(&mrq->completion); - return; + return -ENOMEDIUM; } mmc_start_request(host, mrq); + return 0; } static void mmc_wait_for_req_done(struct mmc_host *host, @@ -336,6 +343,7 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, struct mmc_async_req *areq, int *error) { int err = 0; + int start_err = 0; struct mmc_async_req *data = host->areq; /* Prepare a new request */ @@ -345,30 +353,23 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, if (host->areq) { mmc_wait_for_req_done(host, host->areq->mrq); err = host->areq->err_check(host->card, host->areq); - if (err) { - /* post process the completed failed request */ - mmc_post_req(host, host->areq->mrq, 0); - if (areq) - /* - * Cancel the new prepared request, because - * it can't run until the failed - * request has been properly handled. - */ - mmc_post_req(host, areq->mrq, -EINVAL); - - host->areq = NULL; - goto out; - } } - if (areq) - __mmc_start_req(host, areq->mrq); + if (!err && areq) + start_err = __mmc_start_req(host, areq->mrq); if (host->areq) mmc_post_req(host, host->areq->mrq, 0); - host->areq = areq; - out: + /* Cancel a prepared request if it was not started. */ + if ((err || start_err) && areq) + mmc_post_req(host, areq->mrq, -EINVAL); + + if (err) + host->areq = NULL; + else + host->areq = areq; + if (error) *error = err; return data; @@ -599,105 +600,6 @@ unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz) EXPORT_SYMBOL(mmc_align_data_size); /** - * mmc_host_enable - enable a host. - * @host: mmc host to enable - * - * Hosts that support power saving can use the 'enable' and 'disable' - * methods to exit and enter power saving states. For more information - * see comments for struct mmc_host_ops. - */ -int mmc_host_enable(struct mmc_host *host) -{ - if (!(host->caps & MMC_CAP_DISABLE)) - return 0; - - if (host->en_dis_recurs) - return 0; - - if (host->nesting_cnt++) - return 0; - - cancel_delayed_work_sync(&host->disable); - - if (host->enabled) - return 0; - - if (host->ops->enable) { - int err; - - host->en_dis_recurs = 1; - mmc_host_clk_hold(host); - err = host->ops->enable(host); - mmc_host_clk_release(host); - host->en_dis_recurs = 0; - - if (err) { - pr_debug("%s: enable error %d\n", - mmc_hostname(host), err); - return err; - } - } - host->enabled = 1; - return 0; -} -EXPORT_SYMBOL(mmc_host_enable); - -static int mmc_host_do_disable(struct mmc_host *host, int lazy) -{ - if (host->ops->disable) { - int err; - - host->en_dis_recurs = 1; - mmc_host_clk_hold(host); - err = host->ops->disable(host, lazy); - mmc_host_clk_release(host); - host->en_dis_recurs = 0; - - if (err < 0) { - pr_debug("%s: disable error %d\n", - mmc_hostname(host), err); - return err; - } - if (err > 0) { - unsigned long delay = msecs_to_jiffies(err); - - mmc_schedule_delayed_work(&host->disable, delay); - } - } - host->enabled = 0; - return 0; -} - -/** - * mmc_host_disable - disable a host. - * @host: mmc host to disable - * - * Hosts that support power saving can use the 'enable' and 'disable' - * methods to exit and enter power saving states. For more information - * see comments for struct mmc_host_ops. - */ -int mmc_host_disable(struct mmc_host *host) -{ - int err; - - if (!(host->caps & MMC_CAP_DISABLE)) - return 0; - - if (host->en_dis_recurs) - return 0; - - if (--host->nesting_cnt) - return 0; - - if (!host->enabled) - return 0; - - err = mmc_host_do_disable(host, 0); - return err; -} -EXPORT_SYMBOL(mmc_host_disable); - -/** * __mmc_claim_host - exclusively claim a host * @host: mmc host to claim * @abort: whether or not the operation should be aborted @@ -735,8 +637,8 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) wake_up(&host->wq); spin_unlock_irqrestore(&host->lock, flags); remove_wait_queue(&host->wq, &wait); - if (!stop) - mmc_host_enable(host); + if (host->ops->enable && !stop && host->claim_cnt == 1) + host->ops->enable(host); return stop; } @@ -761,21 +663,28 @@ int mmc_try_claim_host(struct mmc_host *host) claimed_host = 1; } spin_unlock_irqrestore(&host->lock, flags); + if (host->ops->enable && claimed_host && host->claim_cnt == 1) + host->ops->enable(host); return claimed_host; } EXPORT_SYMBOL(mmc_try_claim_host); /** - * mmc_do_release_host - release a claimed host + * mmc_release_host - release a host * @host: mmc host to release * - * If you successfully claimed a host, this function will - * release it again. + * Release a MMC host, allowing others to claim the host + * for their operations. */ -void mmc_do_release_host(struct mmc_host *host) +void mmc_release_host(struct mmc_host *host) { unsigned long flags; + WARN_ON(!host->claimed); + + if (host->ops->disable && host->claim_cnt == 1) + host->ops->disable(host); + spin_lock_irqsave(&host->lock, flags); if (--host->claim_cnt) { /* Release for nested claim */ @@ -787,67 +696,6 @@ void mmc_do_release_host(struct mmc_host *host) wake_up(&host->wq); } } -EXPORT_SYMBOL(mmc_do_release_host); - -void mmc_host_deeper_disable(struct work_struct *work) -{ - struct mmc_host *host = - container_of(work, struct mmc_host, disable.work); - - /* If the host is claimed then we do not want to disable it anymore */ - if (!mmc_try_claim_host(host)) - return; - mmc_host_do_disable(host, 1); - mmc_do_release_host(host); -} - -/** - * mmc_host_lazy_disable - lazily disable a host. - * @host: mmc host to disable - * - * Hosts that support power saving can use the 'enable' and 'disable' - * methods to exit and enter power saving states. For more information - * see comments for struct mmc_host_ops. - */ -int mmc_host_lazy_disable(struct mmc_host *host) -{ - if (!(host->caps & MMC_CAP_DISABLE)) - return 0; - - if (host->en_dis_recurs) - return 0; - - if (--host->nesting_cnt) - return 0; - - if (!host->enabled) - return 0; - - if (host->disable_delay) { - mmc_schedule_delayed_work(&host->disable, - msecs_to_jiffies(host->disable_delay)); - return 0; - } else - return mmc_host_do_disable(host, 1); -} -EXPORT_SYMBOL(mmc_host_lazy_disable); - -/** - * mmc_release_host - release a host - * @host: mmc host to release - * - * Release a MMC host, allowing others to claim the host - * for their operations. - */ -void mmc_release_host(struct mmc_host *host) -{ - WARN_ON(!host->claimed); - - mmc_host_lazy_disable(host); - - mmc_do_release_host(host); -} - EXPORT_SYMBOL(mmc_release_host); /* @@ -2115,18 +1963,36 @@ int _mmc_detect_card_removed(struct mmc_host *host) int mmc_detect_card_removed(struct mmc_host *host) { struct mmc_card *card = host->card; + int ret; WARN_ON(!host->claimed); + + if (!card) + return 1; + + ret = mmc_card_removed(card); /* * The card will be considered unchanged unless we have been asked to * detect a change or host requires polling to provide card detection. */ - if (card && !host->detect_change && !(host->caps & MMC_CAP_NEEDS_POLL)) - return mmc_card_removed(card); + if (!host->detect_change && !(host->caps & MMC_CAP_NEEDS_POLL) && + !(host->caps2 & MMC_CAP2_DETECT_ON_ERR)) + return ret; host->detect_change = 0; + if (!ret) { + ret = _mmc_detect_card_removed(host); + if (ret && (host->caps2 & MMC_CAP2_DETECT_ON_ERR)) { + /* + * Schedule a detect work as soon as possible to let a + * rescan handle the card removal. + */ + cancel_delayed_work(&host->detect); + mmc_detect_change(host, 0); + } + } - return _mmc_detect_card_removed(host); + return ret; } EXPORT_SYMBOL(mmc_detect_card_removed); @@ -2203,8 +2069,6 @@ void mmc_stop_host(struct mmc_host *host) spin_unlock_irqrestore(&host->lock, flags); #endif - if (host->caps & MMC_CAP_DISABLE) - cancel_delayed_work(&host->disable); cancel_delayed_work_sync(&host->detect); mmc_flush_scheduled_work(); @@ -2399,13 +2263,11 @@ int mmc_suspend_host(struct mmc_host *host) { int err = 0; - if (host->caps & MMC_CAP_DISABLE) - cancel_delayed_work(&host->disable); cancel_delayed_work(&host->detect); mmc_flush_scheduled_work(); if (mmc_try_claim_host(host)) { err = mmc_cache_ctrl(host, 0); - mmc_do_release_host(host); + mmc_release_host(host); } else { err = -EBUSY; } @@ -2426,7 +2288,7 @@ int mmc_suspend_host(struct mmc_host *host) if (host->bus_ops->suspend) { err = host->bus_ops->suspend(host); } - mmc_do_release_host(host); + mmc_release_host(host); if (err == -ENOSYS || !host->bus_ops->resume) { /* diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index c3704e293a7b..91c84c7a1829 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -330,7 +330,6 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) spin_lock_init(&host->lock); init_waitqueue_head(&host->wq); INIT_DELAYED_WORK(&host->detect, mmc_rescan); - INIT_DELAYED_WORK_DEFERRABLE(&host->disable, mmc_host_deeper_disable); #ifdef CONFIG_PM host->pm_notify.notifier_call = mmc_pm_notify; #endif diff --git a/drivers/mmc/core/host.h b/drivers/mmc/core/host.h index 08a7852ade44..f2ab9e578126 100644 --- a/drivers/mmc/core/host.h +++ b/drivers/mmc/core/host.h @@ -14,7 +14,6 @@ int mmc_register_host_class(void); void mmc_unregister_host_class(void); -void mmc_host_deeper_disable(struct work_struct *work); #endif diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 2b9ed1401dc4..02914d609a91 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -519,6 +519,20 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) ext_csd[EXT_CSD_CACHE_SIZE + 1] << 8 | ext_csd[EXT_CSD_CACHE_SIZE + 2] << 16 | ext_csd[EXT_CSD_CACHE_SIZE + 3] << 24; + + if (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] == 1) + card->ext_csd.data_sector_size = 4096; + else + card->ext_csd.data_sector_size = 512; + + if ((ext_csd[EXT_CSD_DATA_TAG_SUPPORT] & 1) && + (ext_csd[EXT_CSD_TAG_UNIT_SIZE] <= 8)) { + card->ext_csd.data_tag_unit_size = + ((unsigned int) 1 << ext_csd[EXT_CSD_TAG_UNIT_SIZE]) * + (card->ext_csd.data_sector_size); + } else { + card->ext_csd.data_tag_unit_size = 0; + } } out: @@ -938,7 +952,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * If enhanced_area_en is TRUE, host needs to enable ERASE_GRP_DEF * bit. This bit will be lost every time after a reset or power off. */ - if (card->ext_csd.enhanced_area_en) { + if (card->ext_csd.enhanced_area_en || + (card->ext_csd.rev >= 3 && (host->caps2 & MMC_CAP2_HC_ERASE_SZ))) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_ERASE_GROUP_DEF, 1, card->ext_csd.generic_cmd6_time); @@ -1033,22 +1048,6 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } /* - * Enable HPI feature (if supported) - */ - if (card->ext_csd.hpi) { - err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_HPI_MGMT, 1, 0); - if (err && err != -EBADMSG) - goto free_card; - if (err) { - pr_warning("%s: Enabling HPI failed\n", - mmc_hostname(card->host)); - err = 0; - } else - card->ext_csd.hpi_en = 1; - } - - /* * Compute bus speed. */ max_dtr = (unsigned int)-1; @@ -1097,9 +1096,12 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * 4. execute tuning for HS200 */ if ((host->caps2 & MMC_CAP2_HS200) && - card->host->ops->execute_tuning) + card->host->ops->execute_tuning) { + mmc_host_clk_hold(card->host); err = card->host->ops->execute_tuning(card->host, MMC_SEND_TUNING_BLOCK_HS200); + mmc_host_clk_release(card->host); + } if (err) { pr_warning("%s: tuning execution failed\n", mmc_hostname(card->host)); @@ -1219,6 +1221,23 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } /* + * Enable HPI feature (if supported) + */ + if (card->ext_csd.hpi) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HPI_MGMT, 1, + card->ext_csd.generic_cmd6_time); + if (err && err != -EBADMSG) + goto free_card; + if (err) { + pr_warning("%s: Enabling HPI failed\n", + mmc_hostname(card->host)); + err = 0; + } else + card->ext_csd.hpi_en = 1; + } + + /* * If cache size is higher than 0, this indicates * the existence of cache and it can be turned on. */ diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 4d41fa984c93..69370f494e05 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -553,18 +553,22 @@ int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status) { struct mmc_command cmd = {0}; unsigned int opcode; - unsigned int flags; int err; + if (!card->ext_csd.hpi) { + pr_warning("%s: Card didn't support HPI command\n", + mmc_hostname(card->host)); + return -EINVAL; + } + opcode = card->ext_csd.hpi_cmd; if (opcode == MMC_STOP_TRANSMISSION) - flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; else if (opcode == MMC_SEND_STATUS) - flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; cmd.opcode = opcode; cmd.arg = card->rca << 16 | 1; - cmd.flags = flags; cmd.cmd_timeout_ms = card->ext_csd.out_of_int_time; err = mmc_wait_for_cmd(card->host, &cmd, 0); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index ecbee9bf87b2..2bc06e7344db 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -533,6 +533,31 @@ config MMC_DW_IDMAC Designware Mobile Storage IP block. This disables the external DMA interface. +config MMC_DW_PLTFM + tristate "Synopsys Designware MCI Support as platform device" + depends on MMC_DW + default y + help + This selects the common helper functions support for Host Controller + Interface based platform driver. Please select this option if the IP + is present as a platform device. This is the common interface for the + Synopsys Designware IP. + + If you have a controller with this interface, say Y or M here. + + If unsure, say Y. + +config MMC_DW_PCI + tristate "Synopsys Designware MCI support on PCI bus" + depends on MMC_DW && PCI + help + This selects the PCI bus for the Synopsys Designware Mobile Storage IP. + Select this option if the IP is present on PCI platform. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_SH_MMCIF tristate "SuperH Internal MMCIF support" depends on MMC_BLOCK && (SUPERH || ARCH_SHMOBILE) diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 745f8fce2519..3e7e26d08073 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -39,6 +39,8 @@ obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_DW) += dw_mmc.o +obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o +obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o obj-$(CONFIG_MMC_VUB300) += vub300.o diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index e4449a54ae8f..390863e7efbd 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -1975,7 +1975,7 @@ static bool atmci_configure_dma(struct atmel_mci *host) return false; } else { dev_info(&host->pdev->dev, - "Using %s for DMA transfers\n", + "using %s for DMA transfers\n", dma_chan_name(host->dma.chan)); return true; } diff --git a/drivers/mmc/host/davinci_mmc.c b/drivers/mmc/host/davinci_mmc.c index 64a8325a4a8a..c1f3673ae1ef 100644 --- a/drivers/mmc/host/davinci_mmc.c +++ b/drivers/mmc/host/davinci_mmc.c @@ -160,6 +160,16 @@ module_param(rw_threshold, uint, S_IRUGO); MODULE_PARM_DESC(rw_threshold, "Read/Write threshold. Default = 32"); +static unsigned poll_threshold = 128; +module_param(poll_threshold, uint, S_IRUGO); +MODULE_PARM_DESC(poll_threshold, + "Polling transaction size threshold. Default = 128"); + +static unsigned poll_loopcount = 32; +module_param(poll_loopcount, uint, S_IRUGO); +MODULE_PARM_DESC(poll_loopcount, + "Maximum polling loop count. Default = 32"); + static unsigned __initdata use_dma = 1; module_param(use_dma, uint, 0); MODULE_PARM_DESC(use_dma, "Whether to use DMA or not. Default = 1"); @@ -193,6 +203,7 @@ struct mmc_davinci_host { bool use_dma; bool do_dma; bool sdio_int; + bool active_request; /* Scatterlist DMA uses one or more parameter RAM entries: * the main one (associated with rxdma or txdma) plus zero or @@ -219,6 +230,7 @@ struct mmc_davinci_host { #endif }; +static irqreturn_t mmc_davinci_irq(int irq, void *dev_id); /* PIO only */ static void mmc_davinci_sg_to_buf(struct mmc_davinci_host *host) @@ -376,7 +388,20 @@ static void mmc_davinci_start_command(struct mmc_davinci_host *host, writel(cmd->arg, host->base + DAVINCI_MMCARGHL); writel(cmd_reg, host->base + DAVINCI_MMCCMD); - writel(im_val, host->base + DAVINCI_MMCIM); + + host->active_request = true; + + if (!host->do_dma && host->bytes_left <= poll_threshold) { + u32 count = poll_loopcount; + + while (host->active_request && count--) { + mmc_davinci_irq(0, host); + cpu_relax(); + } + } + + if (host->active_request) + writel(im_val, host->base + DAVINCI_MMCIM); } /*----------------------------------------------------------------------*/ @@ -915,6 +940,7 @@ mmc_davinci_xfer_done(struct mmc_davinci_host *host, struct mmc_data *data) if (!data->stop || (host->cmd && host->cmd->error)) { mmc_request_done(host->mmc, data->mrq); writel(0, host->base + DAVINCI_MMCIM); + host->active_request = false; } else mmc_davinci_start_command(host, data->stop); } @@ -942,6 +968,7 @@ static void mmc_davinci_cmd_done(struct mmc_davinci_host *host, cmd->mrq->cmd->retries = 0; mmc_request_done(host->mmc, cmd->mrq); writel(0, host->base + DAVINCI_MMCIM); + host->active_request = false; } } @@ -1009,12 +1036,33 @@ static irqreturn_t mmc_davinci_irq(int irq, void *dev_id) * by read. So, it is not unbouned loop even in the case of * non-dma. */ - while (host->bytes_left && (status & (MMCST0_DXRDY | MMCST0_DRRDY))) { - davinci_fifo_data_trans(host, rw_threshold); - status = readl(host->base + DAVINCI_MMCST0); - if (!status) - break; - qstatus |= status; + if (host->bytes_left && (status & (MMCST0_DXRDY | MMCST0_DRRDY))) { + unsigned long im_val; + + /* + * If interrupts fire during the following loop, they will be + * handled by the handler, but the PIC will still buffer these. + * As a result, the handler will be called again to serve these + * needlessly. In order to avoid these spurious interrupts, + * keep interrupts masked during the loop. + */ + im_val = readl(host->base + DAVINCI_MMCIM); + writel(0, host->base + DAVINCI_MMCIM); + + do { + davinci_fifo_data_trans(host, rw_threshold); + status = readl(host->base + DAVINCI_MMCST0); + qstatus |= status; + } while (host->bytes_left && + (status & (MMCST0_DXRDY | MMCST0_DRRDY))); + + /* + * If an interrupt is pending, it is assumed it will fire when + * it is unmasked. This assumption is also taken when the MMCIM + * is first set. Otherwise, writing to MMCIM after reading the + * status is race-prone. + */ + writel(im_val, host->base + DAVINCI_MMCIM); } if (qstatus & MMCST0_DATDNE) { @@ -1418,17 +1466,14 @@ static int davinci_mmcsd_suspend(struct device *dev) struct mmc_davinci_host *host = platform_get_drvdata(pdev); int ret; - mmc_host_enable(host->mmc); ret = mmc_suspend_host(host->mmc); if (!ret) { writel(0, host->base + DAVINCI_MMCIM); mmc_davinci_reset_ctrl(host, 1); - mmc_host_disable(host->mmc); clk_disable(host->clk); host->suspended = 1; } else { host->suspended = 0; - mmc_host_disable(host->mmc); } return ret; @@ -1444,7 +1489,6 @@ static int davinci_mmcsd_resume(struct device *dev) return 0; clk_enable(host->clk); - mmc_host_enable(host->mmc); mmc_davinci_reset_ctrl(host, 0); ret = mmc_resume_host(host->mmc); diff --git a/drivers/mmc/host/dw_mmc-pci.c b/drivers/mmc/host/dw_mmc-pci.c new file mode 100644 index 000000000000..dc0d25a013e0 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-pci.c @@ -0,0 +1,158 @@ +/* + * Synopsys DesignWare Multimedia Card PCI Interface driver + * + * Copyright (C) 2012 Vayavya Labs Pvt. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/dw_mmc.h> +#include "dw_mmc.h" + +#define PCI_BAR_NO 2 +#define COMPLETE_BAR 0 +#define SYNOPSYS_DW_MCI_VENDOR_ID 0x700 +#define SYNOPSYS_DW_MCI_DEVICE_ID 0x1107 +/* Defining the Capabilities */ +#define DW_MCI_CAPABILITIES (MMC_CAP_4_BIT_DATA | MMC_CAP_MMC_HIGHSPEED |\ + MMC_CAP_SD_HIGHSPEED | MMC_CAP_8_BIT_DATA |\ + MMC_CAP_SDIO_IRQ) + +static struct dw_mci_board pci_board_data = { + .num_slots = 1, + .caps = DW_MCI_CAPABILITIES, + .bus_hz = 33 * 1000 * 1000, + .detect_delay_ms = 200, + .fifo_depth = 32, +}; + +static int __devinit dw_mci_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *entries) +{ + struct dw_mci *host; + int ret; + + ret = pci_enable_device(pdev); + if (ret) + return ret; + if (pci_request_regions(pdev, "dw_mmc_pci")) { + ret = -ENODEV; + goto err_disable_dev; + } + + host = kzalloc(sizeof(struct dw_mci), GFP_KERNEL); + if (!host) { + ret = -ENOMEM; + goto err_release; + } + + host->irq = pdev->irq; + host->irq_flags = IRQF_SHARED; + host->dev = pdev->dev; + host->pdata = &pci_board_data; + + host->regs = pci_iomap(pdev, PCI_BAR_NO, COMPLETE_BAR); + if (!host->regs) { + ret = -EIO; + goto err_unmap; + } + + pci_set_drvdata(pdev, host); + ret = dw_mci_probe(host); + if (ret) + goto err_probe_failed; + return ret; + +err_probe_failed: + pci_iounmap(pdev, host->regs); +err_unmap: + kfree(host); +err_release: + pci_release_regions(pdev); +err_disable_dev: + pci_disable_device(pdev); + return ret; +} + +static void __devexit dw_mci_pci_remove(struct pci_dev *pdev) +{ + struct dw_mci *host = pci_get_drvdata(pdev); + + dw_mci_remove(host); + pci_set_drvdata(pdev, NULL); + pci_release_regions(pdev); + pci_iounmap(pdev, host->regs); + kfree(host); + pci_disable_device(pdev); +} + +#ifdef CONFIG_PM_SLEEP +static int dw_mci_pci_suspend(struct device *dev) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + struct dw_mci *host = pci_get_drvdata(pdev); + + ret = dw_mci_suspend(host); + return ret; +} + +static int dw_mci_pci_resume(struct device *dev) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + struct dw_mci *host = pci_get_drvdata(pdev); + + ret = dw_mci_resume(host); + return ret; +} +#else +#define dw_mci_pci_suspend NULL +#define dw_mci_pci_resume NULL +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(dw_mci_pci_pmops, dw_mci_pci_suspend, dw_mci_pci_resume); + +static DEFINE_PCI_DEVICE_TABLE(dw_mci_pci_id) = { + { PCI_DEVICE(SYNOPSYS_DW_MCI_VENDOR_ID, SYNOPSYS_DW_MCI_DEVICE_ID) }, + {} +}; +MODULE_DEVICE_TABLE(pci, dw_mci_pci_id); + +static struct pci_driver dw_mci_pci_driver = { + .name = "dw_mmc_pci", + .id_table = dw_mci_pci_id, + .probe = dw_mci_pci_probe, + .remove = dw_mci_pci_remove, + .driver = { + .pm = &dw_mci_pci_pmops + }, +}; + +static int __init dw_mci_init(void) +{ + return pci_register_driver(&dw_mci_pci_driver); +} + +static void __exit dw_mci_exit(void) +{ + pci_unregister_driver(&dw_mci_pci_driver); +} + +module_init(dw_mci_init); +module_exit(dw_mci_exit); + +MODULE_DESCRIPTION("DW Multimedia Card PCI Interface driver"); +MODULE_AUTHOR("Shashidhar Hiremath <shashidharh@vayavyalabs.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c new file mode 100644 index 000000000000..92ec3eb3aae7 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -0,0 +1,134 @@ +/* + * Synopsys DesignWare Multimedia Card Interface driver + * + * Copyright (C) 2009 NXP Semiconductors + * Copyright (C) 2009, 2010 Imagination Technologies Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/dw_mmc.h> +#include "dw_mmc.h" + +static int dw_mci_pltfm_probe(struct platform_device *pdev) +{ + struct dw_mci *host; + struct resource *regs; + int ret; + + host = kzalloc(sizeof(struct dw_mci), GFP_KERNEL); + if (!host) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + ret = -ENXIO; + goto err_free; + } + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) { + ret = host->irq; + goto err_free; + } + + host->dev = pdev->dev; + host->irq_flags = 0; + host->pdata = pdev->dev.platform_data; + ret = -ENOMEM; + host->regs = ioremap(regs->start, resource_size(regs)); + if (!host->regs) + goto err_free; + platform_set_drvdata(pdev, host); + ret = dw_mci_probe(host); + if (ret) + goto err_out; + return ret; +err_out: + iounmap(host->regs); +err_free: + kfree(host); + return ret; +} + +static int __exit dw_mci_pltfm_remove(struct platform_device *pdev) +{ + struct dw_mci *host = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + dw_mci_remove(host); + iounmap(host->regs); + kfree(host); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +/* + * TODO: we should probably disable the clock to the card in the suspend path. + */ +static int dw_mci_pltfm_suspend(struct device *dev) +{ + int ret; + struct dw_mci *host = dev_get_drvdata(dev); + + ret = dw_mci_suspend(host); + if (ret) + return ret; + + return 0; +} + +static int dw_mci_pltfm_resume(struct device *dev) +{ + int ret; + struct dw_mci *host = dev_get_drvdata(dev); + + ret = dw_mci_resume(host); + if (ret) + return ret; + + return 0; +} +#else +#define dw_mci_pltfm_suspend NULL +#define dw_mci_pltfm_resume NULL +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(dw_mci_pltfm_pmops, dw_mci_pltfm_suspend, dw_mci_pltfm_resume); + +static struct platform_driver dw_mci_pltfm_driver = { + .remove = __exit_p(dw_mci_pltfm_remove), + .driver = { + .name = "dw_mmc", + .pm = &dw_mci_pltfm_pmops, + }, +}; + +static int __init dw_mci_init(void) +{ + return platform_driver_probe(&dw_mci_pltfm_driver, dw_mci_pltfm_probe); +} + +static void __exit dw_mci_exit(void) +{ + platform_driver_unregister(&dw_mci_pltfm_driver); +} + +module_init(dw_mci_init); +module_exit(dw_mci_exit); + +MODULE_DESCRIPTION("DW Multimedia Card Interface driver"); +MODULE_AUTHOR("NXP Semiconductor VietNam"); +MODULE_AUTHOR("Imagination Technologies Ltd"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 8bec1c36b159..bf3c9b456aaf 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -268,7 +268,7 @@ static void dw_mci_start_command(struct dw_mci *host, struct mmc_command *cmd, u32 cmd_flags) { host->cmd = cmd; - dev_vdbg(&host->pdev->dev, + dev_vdbg(&host->dev, "start command: ARGR=0x%08x CMDR=0x%08x\n", cmd->arg, cmd_flags); @@ -295,15 +295,25 @@ static void dw_mci_stop_dma(struct dw_mci *host) } } +static int dw_mci_get_dma_dir(struct mmc_data *data) +{ + if (data->flags & MMC_DATA_WRITE) + return DMA_TO_DEVICE; + else + return DMA_FROM_DEVICE; +} + #ifdef CONFIG_MMC_DW_IDMAC static void dw_mci_dma_cleanup(struct dw_mci *host) { struct mmc_data *data = host->data; if (data) - dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len, - ((data->flags & MMC_DATA_WRITE) - ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); + if (!data->host_cookie) + dma_unmap_sg(&host->dev, + data->sg, + data->sg_len, + dw_mci_get_dma_dir(data)); } static void dw_mci_idmac_stop_dma(struct dw_mci *host) @@ -326,7 +336,7 @@ static void dw_mci_idmac_complete_dma(struct dw_mci *host) { struct mmc_data *data = host->data; - dev_vdbg(&host->pdev->dev, "DMA complete\n"); + dev_vdbg(&host->dev, "DMA complete\n"); host->dma_ops->cleanup(host); @@ -428,17 +438,15 @@ static struct dw_mci_dma_ops dw_mci_idmac_ops = { }; #endif /* CONFIG_MMC_DW_IDMAC */ -static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) +static int dw_mci_pre_dma_transfer(struct dw_mci *host, + struct mmc_data *data, + bool next) { struct scatterlist *sg; - unsigned int i, direction, sg_len; - u32 temp; + unsigned int i, sg_len; - host->using_dma = 0; - - /* If we don't have a channel, we can't do DMA */ - if (!host->use_dma) - return -ENODEV; + if (!next && data->host_cookie) + return data->host_cookie; /* * We don't do DMA on "complex" transfers, i.e. with @@ -447,6 +455,7 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) */ if (data->blocks * data->blksz < DW_MCI_DMA_THRESHOLD) return -EINVAL; + if (data->blksz & 3) return -EINVAL; @@ -455,17 +464,74 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) return -EINVAL; } - host->using_dma = 1; + sg_len = dma_map_sg(&host->dev, + data->sg, + data->sg_len, + dw_mci_get_dma_dir(data)); + if (sg_len == 0) + return -EINVAL; - if (data->flags & MMC_DATA_READ) - direction = DMA_FROM_DEVICE; - else - direction = DMA_TO_DEVICE; + if (next) + data->host_cookie = sg_len; - sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, - direction); + return sg_len; +} - dev_vdbg(&host->pdev->dev, +static void dw_mci_pre_req(struct mmc_host *mmc, + struct mmc_request *mrq, + bool is_first_req) +{ + struct dw_mci_slot *slot = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!slot->host->use_dma || !data) + return; + + if (data->host_cookie) { + data->host_cookie = 0; + return; + } + + if (dw_mci_pre_dma_transfer(slot->host, mrq->data, 1) < 0) + data->host_cookie = 0; +} + +static void dw_mci_post_req(struct mmc_host *mmc, + struct mmc_request *mrq, + int err) +{ + struct dw_mci_slot *slot = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!slot->host->use_dma || !data) + return; + + if (data->host_cookie) + dma_unmap_sg(&slot->host->dev, + data->sg, + data->sg_len, + dw_mci_get_dma_dir(data)); + data->host_cookie = 0; +} + +static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) +{ + int sg_len; + u32 temp; + + host->using_dma = 0; + + /* If we don't have a channel, we can't do DMA */ + if (!host->use_dma) + return -ENODEV; + + sg_len = dw_mci_pre_dma_transfer(host, data, 0); + if (sg_len < 0) + return sg_len; + + host->using_dma = 1; + + dev_vdbg(&host->dev, "sd sg_cpu: %#lx sg_dma: %#lx sg_len: %d\n", (unsigned long)host->sg_cpu, (unsigned long)host->sg_dma, sg_len); @@ -579,8 +645,8 @@ static void dw_mci_setup_bus(struct dw_mci_slot *slot) SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0); /* enable clock */ - mci_writel(host, CLKENA, SDMMC_CLKEN_ENABLE | - SDMMC_CLKEN_LOW_PWR); + mci_writel(host, CLKENA, ((SDMMC_CLKEN_ENABLE | + SDMMC_CLKEN_LOW_PWR) << slot->id)); /* inform CIU */ mci_send_cmd(slot, @@ -800,6 +866,8 @@ static void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb) static const struct mmc_host_ops dw_mci_ops = { .request = dw_mci_request, + .pre_req = dw_mci_pre_req, + .post_req = dw_mci_post_req, .set_ios = dw_mci_set_ios, .get_ro = dw_mci_get_ro, .get_cd = dw_mci_get_cd, @@ -821,12 +889,12 @@ static void dw_mci_request_end(struct dw_mci *host, struct mmc_request *mrq) slot = list_entry(host->queue.next, struct dw_mci_slot, queue_node); list_del(&slot->queue_node); - dev_vdbg(&host->pdev->dev, "list not empty: %s is next\n", + dev_vdbg(&host->dev, "list not empty: %s is next\n", mmc_hostname(slot->mmc)); host->state = STATE_SENDING_CMD; dw_mci_start_request(host, slot); } else { - dev_vdbg(&host->pdev->dev, "list empty\n"); + dev_vdbg(&host->dev, "list empty\n"); host->state = STATE_IDLE; } @@ -965,7 +1033,7 @@ static void dw_mci_tasklet_func(unsigned long priv) data->bytes_xfered = 0; data->error = -ETIMEDOUT; } else { - dev_err(&host->pdev->dev, + dev_err(&host->dev, "data FIFO error " "(status=%08x)\n", status); @@ -1682,7 +1750,7 @@ static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id) struct mmc_host *mmc; struct dw_mci_slot *slot; - mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), &host->pdev->dev); + mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), &host->dev); if (!mmc) return -ENOMEM; @@ -1720,13 +1788,11 @@ static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id) if (host->pdata->quirks & DW_MCI_QUIRK_HIGHSPEED) mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED; -#ifdef CONFIG_MMC_DW_IDMAC - mmc->max_segs = host->ring_size; - mmc->max_blk_size = 65536; - mmc->max_blk_count = host->ring_size; - mmc->max_seg_size = 0x1000; - mmc->max_req_size = mmc->max_seg_size * mmc->max_blk_count; -#else + if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY) + mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; + else + mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE; + if (host->pdata->blk_settings) { mmc->max_segs = host->pdata->blk_settings->max_segs; mmc->max_blk_size = host->pdata->blk_settings->max_blk_size; @@ -1735,13 +1801,20 @@ static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id) mmc->max_seg_size = host->pdata->blk_settings->max_seg_size; } else { /* Useful defaults if platform data is unset. */ +#ifdef CONFIG_MMC_DW_IDMAC + mmc->max_segs = host->ring_size; + mmc->max_blk_size = 65536; + mmc->max_blk_count = host->ring_size; + mmc->max_seg_size = 0x1000; + mmc->max_req_size = mmc->max_seg_size * mmc->max_blk_count; +#else mmc->max_segs = 64; mmc->max_blk_size = 65536; /* BLKSIZ is 16 bits */ mmc->max_blk_count = 512; mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; mmc->max_seg_size = mmc->max_req_size; - } #endif /* CONFIG_MMC_DW_IDMAC */ + } host->vmmc = regulator_get(mmc_dev(mmc), "vmmc"); if (IS_ERR(host->vmmc)) { @@ -1789,10 +1862,10 @@ static void dw_mci_cleanup_slot(struct dw_mci_slot *slot, unsigned int id) static void dw_mci_init_dma(struct dw_mci *host) { /* Alloc memory for sg translation */ - host->sg_cpu = dma_alloc_coherent(&host->pdev->dev, PAGE_SIZE, + host->sg_cpu = dma_alloc_coherent(&host->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL); if (!host->sg_cpu) { - dev_err(&host->pdev->dev, "%s: could not alloc DMA memory\n", + dev_err(&host->dev, "%s: could not alloc DMA memory\n", __func__); goto no_dma; } @@ -1800,7 +1873,7 @@ static void dw_mci_init_dma(struct dw_mci *host) /* Determine which DMA interface to use */ #ifdef CONFIG_MMC_DW_IDMAC host->dma_ops = &dw_mci_idmac_ops; - dev_info(&host->pdev->dev, "Using internal DMA controller.\n"); + dev_info(&host->dev, "Using internal DMA controller.\n"); #endif if (!host->dma_ops) @@ -1808,12 +1881,12 @@ static void dw_mci_init_dma(struct dw_mci *host) if (host->dma_ops->init) { if (host->dma_ops->init(host)) { - dev_err(&host->pdev->dev, "%s: Unable to initialize " + dev_err(&host->dev, "%s: Unable to initialize " "DMA Controller.\n", __func__); goto no_dma; } } else { - dev_err(&host->pdev->dev, "DMA initialization not found.\n"); + dev_err(&host->dev, "DMA initialization not found.\n"); goto no_dma; } @@ -1821,7 +1894,7 @@ static void dw_mci_init_dma(struct dw_mci *host) return; no_dma: - dev_info(&host->pdev->dev, "Using PIO mode.\n"); + dev_info(&host->dev, "Using PIO mode.\n"); host->use_dma = 0; return; } @@ -1847,61 +1920,37 @@ static bool mci_wait_reset(struct device *dev, struct dw_mci *host) return false; } -static int dw_mci_probe(struct platform_device *pdev) +int dw_mci_probe(struct dw_mci *host) { - struct dw_mci *host; - struct resource *regs; - struct dw_mci_board *pdata; - int irq, ret, i, width; + int width, i, ret = 0; u32 fifo_size; - regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!regs) - return -ENXIO; - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - host = kzalloc(sizeof(struct dw_mci), GFP_KERNEL); - if (!host) - return -ENOMEM; - - host->pdev = pdev; - host->pdata = pdata = pdev->dev.platform_data; - if (!pdata || !pdata->init) { - dev_err(&pdev->dev, + if (!host->pdata || !host->pdata->init) { + dev_err(&host->dev, "Platform data must supply init function\n"); - ret = -ENODEV; - goto err_freehost; + return -ENODEV; } - if (!pdata->select_slot && pdata->num_slots > 1) { - dev_err(&pdev->dev, + if (!host->pdata->select_slot && host->pdata->num_slots > 1) { + dev_err(&host->dev, "Platform data must supply select_slot function\n"); - ret = -ENODEV; - goto err_freehost; + return -ENODEV; } - if (!pdata->bus_hz) { - dev_err(&pdev->dev, + if (!host->pdata->bus_hz) { + dev_err(&host->dev, "Platform data must supply bus speed\n"); - ret = -ENODEV; - goto err_freehost; + return -ENODEV; } - host->bus_hz = pdata->bus_hz; - host->quirks = pdata->quirks; + host->bus_hz = host->pdata->bus_hz; + host->quirks = host->pdata->quirks; spin_lock_init(&host->lock); INIT_LIST_HEAD(&host->queue); - ret = -ENOMEM; - host->regs = ioremap(regs->start, resource_size(regs)); - if (!host->regs) - goto err_freehost; - host->dma_ops = pdata->dma_ops; + host->dma_ops = host->pdata->dma_ops; dw_mci_init_dma(host); /* @@ -1931,7 +1980,7 @@ static int dw_mci_probe(struct platform_device *pdev) } /* Reset all blocks */ - if (!mci_wait_reset(&pdev->dev, host)) { + if (!mci_wait_reset(&host->dev, host)) { ret = -ENODEV; goto err_dmaunmap; } @@ -1974,13 +2023,10 @@ static int dw_mci_probe(struct platform_device *pdev) if (!dw_mci_card_workqueue) goto err_dmaunmap; INIT_WORK(&host->card_work, dw_mci_work_routine_card); - - ret = request_irq(irq, dw_mci_interrupt, 0, "dw-mci", host); + ret = request_irq(host->irq, dw_mci_interrupt, host->irq_flags, "dw-mci", host); if (ret) goto err_workqueue; - platform_set_drvdata(pdev, host); - if (host->pdata->num_slots) host->num_slots = host->pdata->num_slots; else @@ -2000,7 +2046,7 @@ static int dw_mci_probe(struct platform_device *pdev) * Need to check the version-id and set data-offset for DATA register. */ host->verid = SDMMC_GET_VERID(mci_readl(host, VERID)); - dev_info(&pdev->dev, "Version ID is %04x\n", host->verid); + dev_info(&host->dev, "Version ID is %04x\n", host->verid); if (host->verid < DW_MMC_240A) host->data_offset = DATA_OFFSET; @@ -2017,12 +2063,12 @@ static int dw_mci_probe(struct platform_device *pdev) DW_MCI_ERROR_FLAGS | SDMMC_INT_CD); mci_writel(host, CTRL, SDMMC_CTRL_INT_ENABLE); /* Enable mci interrupt */ - dev_info(&pdev->dev, "DW MMC controller at irq %d, " + dev_info(&host->dev, "DW MMC controller at irq %d, " "%d bit host data width, " "%u deep fifo\n", - irq, width, fifo_size); + host->irq, width, fifo_size); if (host->quirks & DW_MCI_QUIRK_IDMAC_DTO) - dev_info(&pdev->dev, "Internal DMAC interrupt fix enabled.\n"); + dev_info(&host->dev, "Internal DMAC interrupt fix enabled.\n"); return 0; @@ -2033,7 +2079,7 @@ err_init_slot: dw_mci_cleanup_slot(host->slot[i], i); i--; } - free_irq(irq, host); + free_irq(host->irq, host); err_workqueue: destroy_workqueue(dw_mci_card_workqueue); @@ -2041,33 +2087,26 @@ err_workqueue: err_dmaunmap: if (host->use_dma && host->dma_ops->exit) host->dma_ops->exit(host); - dma_free_coherent(&host->pdev->dev, PAGE_SIZE, + dma_free_coherent(&host->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); - iounmap(host->regs); if (host->vmmc) { regulator_disable(host->vmmc); regulator_put(host->vmmc); } - - -err_freehost: - kfree(host); return ret; } +EXPORT_SYMBOL(dw_mci_probe); -static int __exit dw_mci_remove(struct platform_device *pdev) +void dw_mci_remove(struct dw_mci *host) { - struct dw_mci *host = platform_get_drvdata(pdev); int i; mci_writel(host, RINTSTS, 0xFFFFFFFF); mci_writel(host, INTMASK, 0); /* disable all mmc interrupt first */ - platform_set_drvdata(pdev, NULL); - for (i = 0; i < host->num_slots; i++) { - dev_dbg(&pdev->dev, "remove slot %d\n", i); + dev_dbg(&host->dev, "remove slot %d\n", i); if (host->slot[i]) dw_mci_cleanup_slot(host->slot[i], i); } @@ -2076,9 +2115,9 @@ static int __exit dw_mci_remove(struct platform_device *pdev) mci_writel(host, CLKENA, 0); mci_writel(host, CLKSRC, 0); - free_irq(platform_get_irq(pdev, 0), host); + free_irq(host->irq, host); destroy_workqueue(dw_mci_card_workqueue); - dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); + dma_free_coherent(&host->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); if (host->use_dma && host->dma_ops->exit) host->dma_ops->exit(host); @@ -2088,20 +2127,18 @@ static int __exit dw_mci_remove(struct platform_device *pdev) regulator_put(host->vmmc); } - iounmap(host->regs); - - kfree(host); - return 0; } +EXPORT_SYMBOL(dw_mci_remove); + + #ifdef CONFIG_PM_SLEEP /* * TODO: we should probably disable the clock to the card in the suspend path. */ -static int dw_mci_suspend(struct device *dev) +int dw_mci_suspend(struct dw_mci *host) { - int i, ret; - struct dw_mci *host = dev_get_drvdata(dev); + int i, ret = 0; for (i = 0; i < host->num_slots; i++) { struct dw_mci_slot *slot = host->slot[i]; @@ -2123,11 +2160,11 @@ static int dw_mci_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL(dw_mci_suspend); -static int dw_mci_resume(struct device *dev) +int dw_mci_resume(struct dw_mci *host) { int i, ret; - struct dw_mci *host = dev_get_drvdata(dev); if (host->vmmc) regulator_enable(host->vmmc); @@ -2135,7 +2172,7 @@ static int dw_mci_resume(struct device *dev) if (host->dma_ops->init) host->dma_ops->init(host); - if (!mci_wait_reset(dev, host)) { + if (!mci_wait_reset(&host->dev, host)) { ret = -ENODEV; return ret; } @@ -2157,32 +2194,19 @@ static int dw_mci_resume(struct device *dev) if (ret < 0) return ret; } - return 0; } -#else -#define dw_mci_suspend NULL -#define dw_mci_resume NULL +EXPORT_SYMBOL(dw_mci_resume); #endif /* CONFIG_PM_SLEEP */ -static SIMPLE_DEV_PM_OPS(dw_mci_pmops, dw_mci_suspend, dw_mci_resume); - -static struct platform_driver dw_mci_driver = { - .remove = __exit_p(dw_mci_remove), - .driver = { - .name = "dw_mmc", - .pm = &dw_mci_pmops, - }, -}; - static int __init dw_mci_init(void) { - return platform_driver_probe(&dw_mci_driver, dw_mci_probe); + printk(KERN_INFO "Synopsys Designware Multimedia Card Interface Driver"); + return 0; } static void __exit dw_mci_exit(void) { - platform_driver_unregister(&dw_mci_driver); } module_init(dw_mci_init); diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index df392a1143f2..15c27e17c23f 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -175,4 +175,11 @@ (*(volatile u64 __force *)((dev)->regs + SDMMC_##reg) = (value)) #endif +extern int dw_mci_probe(struct dw_mci *host); +extern void dw_mci_remove(struct dw_mci *host); +#ifdef CONFIG_PM +extern int dw_mci_suspend(struct dw_mci *host); +extern int dw_mci_resume(struct dw_mci *host); +#endif + #endif /* _DW_MMC_H_ */ diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index fd0c661bbad3..47adb161d3ad 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -26,6 +26,9 @@ #include <linux/platform_device.h> #include <linux/timer.h> #include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/of_device.h> #include <linux/mmc/host.h> #include <linux/mmc/core.h> #include <linux/mmc/mmc.h> @@ -106,17 +109,6 @@ #define SOFTRESET (1 << 1) #define RESETDONE (1 << 0) -/* - * FIXME: Most likely all the data using these _DEVID defines should come - * from the platform_data, or implemented in controller and slot specific - * functions. - */ -#define OMAP_MMC1_DEVID 0 -#define OMAP_MMC2_DEVID 1 -#define OMAP_MMC3_DEVID 2 -#define OMAP_MMC4_DEVID 3 -#define OMAP_MMC5_DEVID 4 - #define MMC_AUTOSUSPEND_DELAY 100 #define MMC_TIMEOUT_MS 20 #define OMAP_MMC_MIN_CLOCK 400000 @@ -164,7 +156,6 @@ struct omap_hsmmc_host { void __iomem *base; resource_size_t mapbase; spinlock_t irq_lock; /* Prevent races with irq handler */ - unsigned int id; unsigned int dma_len; unsigned int dma_sg_idx; unsigned char bus_mode; @@ -179,7 +170,6 @@ struct omap_hsmmc_host { int got_dbclk; int response_busy; int context_loss; - int dpm_state; int vdd; int protect_card; int reqs_blocked; @@ -241,28 +231,7 @@ static int omap_hsmmc_resume_cdirq(struct device *dev, int slot) #ifdef CONFIG_REGULATOR -static int omap_hsmmc_1_set_power(struct device *dev, int slot, int power_on, - int vdd) -{ - struct omap_hsmmc_host *host = - platform_get_drvdata(to_platform_device(dev)); - int ret; - - if (mmc_slot(host).before_set_reg) - mmc_slot(host).before_set_reg(dev, slot, power_on, vdd); - - if (power_on) - ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd); - else - ret = mmc_regulator_set_ocr(host->mmc, host->vcc, 0); - - if (mmc_slot(host).after_set_reg) - mmc_slot(host).after_set_reg(dev, slot, power_on, vdd); - - return ret; -} - -static int omap_hsmmc_235_set_power(struct device *dev, int slot, int power_on, +static int omap_hsmmc_set_power(struct device *dev, int slot, int power_on, int vdd) { struct omap_hsmmc_host *host = @@ -275,6 +244,13 @@ static int omap_hsmmc_235_set_power(struct device *dev, int slot, int power_on, */ if (!host->vcc) return 0; + /* + * With DT, never turn OFF the regulator. This is because + * the pbias cell programming support is still missing when + * booting with Device tree + */ + if (of_have_populated_dt() && !vdd) + return 0; if (mmc_slot(host).before_set_reg) mmc_slot(host).before_set_reg(dev, slot, power_on, vdd); @@ -318,106 +294,16 @@ static int omap_hsmmc_235_set_power(struct device *dev, int slot, int power_on, return ret; } -static int omap_hsmmc_4_set_power(struct device *dev, int slot, int power_on, - int vdd) -{ - return 0; -} - -static int omap_hsmmc_1_set_sleep(struct device *dev, int slot, int sleep, - int vdd, int cardsleep) -{ - struct omap_hsmmc_host *host = - platform_get_drvdata(to_platform_device(dev)); - int mode = sleep ? REGULATOR_MODE_STANDBY : REGULATOR_MODE_NORMAL; - - return regulator_set_mode(host->vcc, mode); -} - -static int omap_hsmmc_235_set_sleep(struct device *dev, int slot, int sleep, - int vdd, int cardsleep) -{ - struct omap_hsmmc_host *host = - platform_get_drvdata(to_platform_device(dev)); - int err, mode; - - /* - * If we don't see a Vcc regulator, assume it's a fixed - * voltage always-on regulator. - */ - if (!host->vcc) - return 0; - - mode = sleep ? REGULATOR_MODE_STANDBY : REGULATOR_MODE_NORMAL; - - if (!host->vcc_aux) - return regulator_set_mode(host->vcc, mode); - - if (cardsleep) { - /* VCC can be turned off if card is asleep */ - if (sleep) - err = mmc_regulator_set_ocr(host->mmc, host->vcc, 0); - else - err = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd); - } else - err = regulator_set_mode(host->vcc, mode); - if (err) - return err; - - if (!mmc_slot(host).vcc_aux_disable_is_sleep) - return regulator_set_mode(host->vcc_aux, mode); - - if (sleep) - return regulator_disable(host->vcc_aux); - else - return regulator_enable(host->vcc_aux); -} - -static int omap_hsmmc_4_set_sleep(struct device *dev, int slot, int sleep, - int vdd, int cardsleep) -{ - return 0; -} - static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) { struct regulator *reg; - int ret = 0; int ocr_value = 0; - switch (host->id) { - case OMAP_MMC1_DEVID: - /* On-chip level shifting via PBIAS0/PBIAS1 */ - mmc_slot(host).set_power = omap_hsmmc_1_set_power; - mmc_slot(host).set_sleep = omap_hsmmc_1_set_sleep; - break; - case OMAP_MMC2_DEVID: - case OMAP_MMC3_DEVID: - case OMAP_MMC5_DEVID: - /* Off-chip level shifting, or none */ - mmc_slot(host).set_power = omap_hsmmc_235_set_power; - mmc_slot(host).set_sleep = omap_hsmmc_235_set_sleep; - break; - case OMAP_MMC4_DEVID: - mmc_slot(host).set_power = omap_hsmmc_4_set_power; - mmc_slot(host).set_sleep = omap_hsmmc_4_set_sleep; - default: - pr_err("MMC%d configuration not supported!\n", host->id); - return -EINVAL; - } + mmc_slot(host).set_power = omap_hsmmc_set_power; reg = regulator_get(host->dev, "vmmc"); if (IS_ERR(reg)) { dev_dbg(host->dev, "vmmc regulator missing\n"); - /* - * HACK: until fixed.c regulator is usable, - * we don't require a main regulator - * for MMC2 or MMC3 - */ - if (host->id == OMAP_MMC1_DEVID) { - ret = PTR_ERR(reg); - goto err; - } } else { host->vcc = reg; ocr_value = mmc_regulator_get_ocrmask(reg); @@ -425,8 +311,8 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) mmc_slot(host).ocr_mask = ocr_value; } else { if (!(mmc_slot(host).ocr_mask & ocr_value)) { - pr_err("MMC%d ocrmask %x is not supported\n", - host->id, mmc_slot(host).ocr_mask); + dev_err(host->dev, "ocrmask %x is not supported\n", + mmc_slot(host).ocr_mask); mmc_slot(host).ocr_mask = 0; return -EINVAL; } @@ -459,11 +345,6 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) } return 0; - -err: - mmc_slot(host).set_power = NULL; - mmc_slot(host).set_sleep = NULL; - return ret; } static void omap_hsmmc_reg_put(struct omap_hsmmc_host *host) @@ -471,7 +352,6 @@ static void omap_hsmmc_reg_put(struct omap_hsmmc_host *host) regulator_put(host->vcc); regulator_put(host->vcc_aux); mmc_slot(host).set_power = NULL; - mmc_slot(host).set_sleep = NULL; } static inline int omap_hsmmc_have_reg(void) @@ -710,7 +590,7 @@ static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host) OMAP_HSMMC_WRITE(host->base, SYSCONFIG, OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE); - if (host->id == OMAP_MMC1_DEVID) { + if (host->pdata->controller_flags & OMAP_HSMMC_SUPPORTS_DUAL_VOLT) { if (host->power_mode != MMC_POWER_OFF && (1 << ios->vdd) <= MMC_VDD_23_24) hctl = SDVS18; @@ -1261,14 +1141,14 @@ static void omap_hsmmc_protect_card(struct omap_hsmmc_host *host) host->reqs_blocked = 0; if (mmc_slot(host).get_cover_state(host->dev, host->slot_id)) { if (host->protect_card) { - pr_info("%s: cover is closed, " + dev_info(host->dev, "%s: cover is closed, " "card is now accessible\n", mmc_hostname(host->mmc)); host->protect_card = 0; } } else { if (!host->protect_card) { - pr_info("%s: cover is open, " + dev_info(host->dev, "%s: cover is open, " "card is now inaccessible\n", mmc_hostname(host->mmc)); host->protect_card = 1; @@ -1405,7 +1285,7 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host, if (!next && data->host_cookie && data->host_cookie != host->next_data.cookie) { - pr_warning("[%s] invalid cookie: data->host_cookie %d" + dev_warn(host->dev, "[%s] invalid cookie: data->host_cookie %d" " host->next_data.cookie %d\n", __func__, data->host_cookie, host->next_data.cookie); data->host_cookie = 0; @@ -1663,7 +1543,13 @@ static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) * of external transceiver; but they all handle 1.8V. */ if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) && - (ios->vdd == DUAL_VOLT_OCR_BIT)) { + (ios->vdd == DUAL_VOLT_OCR_BIT) && + /* + * With pbias cell programming missing, this + * can't be allowed when booting with device + * tree. + */ + (!of_have_populated_dt())) { /* * The mmc_select_voltage fn of the core does * not seem to set the power_mode to @@ -1748,7 +1634,7 @@ static int omap_hsmmc_enable_fclk(struct mmc_host *mmc) return 0; } -static int omap_hsmmc_disable_fclk(struct mmc_host *mmc, int lazy) +static int omap_hsmmc_disable_fclk(struct mmc_host *mmc) { struct omap_hsmmc_host *host = mmc_priv(mmc); @@ -1782,15 +1668,8 @@ static int omap_hsmmc_regs_show(struct seq_file *s, void *data) if (host->pdata->get_context_loss_count) context_loss = host->pdata->get_context_loss_count(host->dev); - seq_printf(s, "mmc%d:\n" - " enabled:\t%d\n" - " dpm_state:\t%d\n" - " nesting_cnt:\t%d\n" - " ctx_loss:\t%d:%d\n" - "\nregs:\n", - mmc->index, mmc->enabled ? 1 : 0, - host->dpm_state, mmc->nesting_cnt, - host->context_loss, context_loss); + seq_printf(s, "mmc%d:\n ctx_loss:\t%d:%d\n\nregs:\n", + mmc->index, host->context_loss, context_loss); if (host->suspended) { seq_printf(s, "host suspended, can't read registers\n"); @@ -1847,6 +1726,65 @@ static void omap_hsmmc_debugfs(struct mmc_host *mmc) #endif +#ifdef CONFIG_OF +static u16 omap4_reg_offset = 0x100; + +static const struct of_device_id omap_mmc_of_match[] = { + { + .compatible = "ti,omap2-hsmmc", + }, + { + .compatible = "ti,omap3-hsmmc", + }, + { + .compatible = "ti,omap4-hsmmc", + .data = &omap4_reg_offset, + }, + {}, +} +MODULE_DEVICE_TABLE(of, omap_mmc_of_match); + +static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev) +{ + struct omap_mmc_platform_data *pdata; + struct device_node *np = dev->of_node; + u32 bus_width; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; /* out of memory */ + + if (of_find_property(np, "ti,dual-volt", NULL)) + pdata->controller_flags |= OMAP_HSMMC_SUPPORTS_DUAL_VOLT; + + /* This driver only supports 1 slot */ + pdata->nr_slots = 1; + pdata->slots[0].switch_pin = of_get_named_gpio(np, "cd-gpios", 0); + pdata->slots[0].gpio_wp = of_get_named_gpio(np, "wp-gpios", 0); + + if (of_find_property(np, "ti,non-removable", NULL)) { + pdata->slots[0].nonremovable = true; + pdata->slots[0].no_regulator_off_init = true; + } + of_property_read_u32(np, "ti,bus-width", &bus_width); + if (bus_width == 4) + pdata->slots[0].caps |= MMC_CAP_4_BIT_DATA; + else if (bus_width == 8) + pdata->slots[0].caps |= MMC_CAP_8_BIT_DATA; + + if (of_find_property(np, "ti,needs-special-reset", NULL)) + pdata->slots[0].features |= HSMMC_HAS_UPDATED_RESET; + + return pdata; +} +#else +static inline struct omap_mmc_platform_data + *of_get_hsmmc_pdata(struct device *dev) +{ + return NULL; +} +#endif + static int __init omap_hsmmc_probe(struct platform_device *pdev) { struct omap_mmc_platform_data *pdata = pdev->dev.platform_data; @@ -1854,6 +1792,16 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev) struct omap_hsmmc_host *host = NULL; struct resource *res; int ret, irq; + const struct of_device_id *match; + + match = of_match_device(of_match_ptr(omap_mmc_of_match), &pdev->dev); + if (match) { + pdata = of_get_hsmmc_pdata(&pdev->dev); + if (match->data) { + u16 *offsetp = match->data; + pdata->reg_offset = *offsetp; + } + } if (pdata == NULL) { dev_err(&pdev->dev, "Platform Data is missing\n"); @@ -1894,7 +1842,6 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev) host->dev->dma_mask = &pdata->dma_mask; host->dma_ch = -1; host->irq = irq; - host->id = pdev->id; host->slot_id = 0; host->mapbase = res->start; host->base = ioremap(host->mapbase, SZ_4K); @@ -1912,8 +1859,12 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev) if (mmc_slot(host).vcc_aux_disable_is_sleep) mmc_slot(host).no_off = 1; - mmc->f_min = OMAP_MMC_MIN_CLOCK; - mmc->f_max = OMAP_MMC_MAX_CLOCK; + mmc->f_min = OMAP_MMC_MIN_CLOCK; + + if (pdata->max_freq > 0) + mmc->f_max = pdata->max_freq; + else + mmc->f_max = OMAP_MMC_MAX_CLOCK; spin_lock_init(&host->irq_lock); @@ -1926,7 +1877,6 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev) omap_hsmmc_context_save(host); - mmc->caps |= MMC_CAP_DISABLE; if (host->pdata->controller_flags & OMAP_HSMMC_BROKEN_MULTIBLOCK_READ) { dev_info(&pdev->dev, "multiblock reads disabled due to 35xx erratum 2.1.1.128; MMC read performance may suffer\n"); mmc->caps2 |= MMC_CAP2_NO_MULTI_READ; @@ -1977,32 +1927,19 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev) omap_hsmmc_conf_bus_power(host); - /* Select DMA lines */ - switch (host->id) { - case OMAP_MMC1_DEVID: - host->dma_line_tx = OMAP24XX_DMA_MMC1_TX; - host->dma_line_rx = OMAP24XX_DMA_MMC1_RX; - break; - case OMAP_MMC2_DEVID: - host->dma_line_tx = OMAP24XX_DMA_MMC2_TX; - host->dma_line_rx = OMAP24XX_DMA_MMC2_RX; - break; - case OMAP_MMC3_DEVID: - host->dma_line_tx = OMAP34XX_DMA_MMC3_TX; - host->dma_line_rx = OMAP34XX_DMA_MMC3_RX; - break; - case OMAP_MMC4_DEVID: - host->dma_line_tx = OMAP44XX_DMA_MMC4_TX; - host->dma_line_rx = OMAP44XX_DMA_MMC4_RX; - break; - case OMAP_MMC5_DEVID: - host->dma_line_tx = OMAP44XX_DMA_MMC5_TX; - host->dma_line_rx = OMAP44XX_DMA_MMC5_RX; - break; - default: - dev_err(mmc_dev(host->mmc), "Invalid MMC id\n"); + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); + if (!res) { + dev_err(mmc_dev(host->mmc), "cannot get DMA TX channel\n"); + goto err_irq; + } + host->dma_line_tx = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); + if (!res) { + dev_err(mmc_dev(host->mmc), "cannot get DMA RX channel\n"); goto err_irq; } + host->dma_line_rx = res->start; /* Request IRQ for MMC operations */ ret = request_irq(host->irq, omap_hsmmc_irq, 0, @@ -2083,6 +2020,7 @@ err_irq_cd_init: err_irq: pm_runtime_mark_last_busy(host->dev); pm_runtime_put_autosuspend(host->dev); + pm_runtime_disable(host->dev); clk_put(host->fclk); if (host->got_dbclk) { clk_disable(host->dbclk); @@ -2269,6 +2207,7 @@ static struct platform_driver omap_hsmmc_driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .pm = &omap_hsmmc_dev_pm_ops, + .of_match_table = of_match_ptr(omap_mmc_of_match), }, }; diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index 5d876ff86f37..f8eb1fb0c921 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -1,7 +1,7 @@ /* * Freescale eSDHC controller driver. * - * Copyright (c) 2007, 2010 Freescale Semiconductor, Inc. + * Copyright (c) 2007, 2010, 2012 Freescale Semiconductor, Inc. * Copyright (c) 2009 MontaVista Software, Inc. * * Authors: Xiaobo Xie <X.Xie@freescale.com> @@ -14,6 +14,7 @@ */ #include <linux/io.h> +#include <linux/of.h> #include <linux/delay.h> #include <linux/module.h> #include <linux/mmc/host.h> @@ -114,6 +115,34 @@ static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host) return pltfm_host->clock / 256 / 16; } +static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) +{ + /* Workaround to reduce the clock frequency for p1010 esdhc */ + if (of_find_compatible_node(NULL, NULL, "fsl,p1010-esdhc")) { + if (clock > 20000000) + clock -= 5000000; + if (clock > 40000000) + clock -= 5000000; + } + + /* Set the clock */ + esdhc_set_clock(host, clock); +} + +#ifdef CONFIG_PM +static u32 esdhc_proctl; +static void esdhc_of_suspend(struct sdhci_host *host) +{ + esdhc_proctl = sdhci_be32bs_readl(host, SDHCI_HOST_CONTROL); +} + +static void esdhc_of_resume(struct sdhci_host *host) +{ + esdhc_of_enable_dma(host); + sdhci_be32bs_writel(host, esdhc_proctl, SDHCI_HOST_CONTROL); +} +#endif + static struct sdhci_ops sdhci_esdhc_ops = { .read_l = sdhci_be32bs_readl, .read_w = esdhc_readw, @@ -121,10 +150,14 @@ static struct sdhci_ops sdhci_esdhc_ops = { .write_l = sdhci_be32bs_writel, .write_w = esdhc_writew, .write_b = esdhc_writeb, - .set_clock = esdhc_set_clock, + .set_clock = esdhc_of_set_clock, .enable_dma = esdhc_of_enable_dma, .get_max_clock = esdhc_of_get_max_clock, .get_min_clock = esdhc_of_get_min_clock, +#ifdef CONFIG_PM + .platform_suspend = esdhc_of_suspend, + .platform_resume = esdhc_of_resume, +#endif }; static struct sdhci_pltfm_data sdhci_esdhc_pdata = { diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 6ebdc4010e7c..fbbebe251e01 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -29,6 +29,12 @@ #include "sdhci.h" /* + * PCI device IDs + */ +#define PCI_DEVICE_ID_INTEL_PCH_SDIO0 0x8809 +#define PCI_DEVICE_ID_INTEL_PCH_SDIO1 0x880a + +/* * PCI registers */ @@ -47,6 +53,7 @@ struct sdhci_pci_slot; struct sdhci_pci_fixes { unsigned int quirks; + unsigned int quirks2; bool allow_runtime_pm; int (*probe) (struct sdhci_pci_chip *); @@ -73,6 +80,7 @@ struct sdhci_pci_chip { struct pci_dev *pdev; unsigned int quirks; + unsigned int quirks2; bool allow_runtime_pm; const struct sdhci_pci_fixes *fixes; @@ -172,6 +180,12 @@ static int mrst_hc_probe(struct sdhci_pci_chip *chip) return 0; } +static int pch_hc_probe_slot(struct sdhci_pci_slot *slot) +{ + slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA; + return 0; +} + #ifdef CONFIG_PM_RUNTIME static irqreturn_t sdhci_pci_sd_cd(int irq, void *dev_id) @@ -244,7 +258,8 @@ static inline void sdhci_pci_remove_own_cd(struct sdhci_pci_slot *slot) static int mfd_emmc_probe_slot(struct sdhci_pci_slot *slot) { slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE; - slot->host->mmc->caps2 = MMC_CAP2_BOOTPART_NOACC; + slot->host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC | + MMC_CAP2_HC_ERASE_SZ; return 0; } @@ -271,6 +286,7 @@ static const struct sdhci_pci_fixes sdhci_intel_mfd_sd = { static const struct sdhci_pci_fixes sdhci_intel_mfd_sdio = { .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, + .quirks2 = SDHCI_QUIRK2_HOST_OFF_CARD_ON, .allow_runtime_pm = true, .probe_slot = mfd_sdio_probe_slot, }; @@ -281,6 +297,11 @@ static const struct sdhci_pci_fixes sdhci_intel_mfd_emmc = { .probe_slot = mfd_emmc_probe_slot, }; +static const struct sdhci_pci_fixes sdhci_intel_pch_sdio = { + .quirks = SDHCI_QUIRK_BROKEN_ADMA, + .probe_slot = pch_hc_probe_slot, +}; + /* O2Micro extra registers */ #define O2_SD_LOCK_WP 0xD3 #define O2_SD_MULTI_VCC3V 0xEE @@ -817,6 +838,22 @@ static const struct pci_device_id pci_ids[] __devinitdata = { }, { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_PCH_SDIO0, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (kernel_ulong_t)&sdhci_intel_pch_sdio, + }, + + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_PCH_SDIO1, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (kernel_ulong_t)&sdhci_intel_pch_sdio, + }, + + { .vendor = PCI_VENDOR_ID_O2, .device = PCI_DEVICE_ID_O2_8120, .subvendor = PCI_ANY_ID, @@ -1206,6 +1243,7 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( host->hw_name = "PCI"; host->ops = &sdhci_pci_ops; host->quirks = chip->quirks; + host->quirks2 = chip->quirks2; host->irq = pdev->irq; @@ -1365,6 +1403,7 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev, chip->fixes = (const struct sdhci_pci_fixes *)ent->driver_data; if (chip->fixes) { chip->quirks = chip->fixes->quirks; + chip->quirks2 = chip->fixes->quirks2; chip->allow_runtime_pm = chip->fixes->allow_runtime_pm; } chip->num_slots = slots; @@ -1379,6 +1418,8 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev, slots = chip->num_slots; /* Quirk may have changed this */ + pci_enable_msi(pdev); + for (i = 0; i < slots; i++) { slot = sdhci_pci_probe_slot(pdev, chip, first_bar, i); if (IS_ERR(slot)) { @@ -1397,6 +1438,8 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev, return 0; free: + pci_disable_msi(pdev); + pci_set_drvdata(pdev, NULL); kfree(chip); @@ -1419,6 +1462,8 @@ static void __devexit sdhci_pci_remove(struct pci_dev *pdev) for (i = 0; i < chip->num_slots; i++) sdhci_pci_remove_slot(chip->slots[i]); + pci_disable_msi(pdev); + pci_set_drvdata(pdev, NULL); kfree(chip); } diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index b7f8b33c5f19..6dfa82e03c7e 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -300,20 +300,15 @@ static int sdhci_resume(struct device *dev) return sdhci_resume_host(host); } - -const struct dev_pm_ops sdhci_pm_ops = { - .suspend = sdhci_suspend, - .resume = sdhci_resume, -}; #endif +static SIMPLE_DEV_PM_OPS(sdhci_pm_ops, sdhci_suspend, sdhci_resume); + static struct platform_driver sdhci_driver = { .driver = { .name = "sdhci", .owner = THIS_MODULE, -#ifdef CONFIG_PM .pm = &sdhci_pm_ops, -#endif }, .probe = sdhci_probe, .remove = __devexit_p(sdhci_remove), diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index cb348569454b..53b26502f6e2 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -19,6 +19,7 @@ #include <linux/clk.h> #include <linux/io.h> #include <linux/of.h> +#include <linux/of_device.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <linux/mmc/card.h> @@ -31,6 +32,19 @@ #include "sdhci-pltfm.h" +#define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0) +#define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1) + +struct sdhci_tegra_soc_data { + struct sdhci_pltfm_data *pdata; + u32 nvquirks; +}; + +struct sdhci_tegra { + const struct tegra_sdhci_platform_data *plat; + const struct sdhci_tegra_soc_data *soc_data; +}; + static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) { u32 val; @@ -46,7 +60,12 @@ static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) { - if (unlikely(reg == SDHCI_HOST_VERSION)) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; + + if (unlikely((soc_data->nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200) && + (reg == SDHCI_HOST_VERSION))) { /* Erratum: Version register is invalid in HW. */ return SDHCI_SPEC_200; } @@ -56,6 +75,10 @@ static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; + /* Seems like we're getting spurious timeout and crc errors, so * disable signalling of them. In case of real errors software * timers should take care of eventually detecting them. @@ -65,7 +88,8 @@ static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) writel(val, host->ioaddr + reg); - if (unlikely(reg == SDHCI_INT_ENABLE)) { + if (unlikely((soc_data->nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET) && + (reg == SDHCI_INT_ENABLE))) { /* Erratum: Must enable block gap interrupt detection */ u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); if (val & SDHCI_INT_CARD_INT) @@ -76,10 +100,11 @@ static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) } } -static unsigned int tegra_sdhci_get_ro(struct sdhci_host *sdhci) +static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host) { - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); - struct tegra_sdhci_platform_data *plat = pltfm_host->priv; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct tegra_sdhci_platform_data *plat = tegra_host->plat; if (!gpio_is_valid(plat->wp_gpio)) return -1; @@ -98,7 +123,8 @@ static irqreturn_t carddetect_irq(int irq, void *data) static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - struct tegra_sdhci_platform_data *plat = pltfm_host->priv; + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct tegra_sdhci_platform_data *plat = tegra_host->plat; u32 ctrl; ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); @@ -124,16 +150,44 @@ static struct sdhci_ops tegra_sdhci_ops = { .platform_8bit_width = tegra_sdhci_8bit, }; -static struct sdhci_pltfm_data sdhci_tegra_pdata = { +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +static struct sdhci_pltfm_data sdhci_tegra20_pdata = { + .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | + SDHCI_QUIRK_SINGLE_POWER_WRITE | + SDHCI_QUIRK_NO_HISPD_BIT | + SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, + .ops = &tegra_sdhci_ops, +}; + +static struct sdhci_tegra_soc_data soc_data_tegra20 = { + .pdata = &sdhci_tegra20_pdata, + .nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 | + NVQUIRK_ENABLE_BLOCK_GAP_DET, +}; +#endif + +#ifdef CONFIG_ARCH_TEGRA_3x_SOC +static struct sdhci_pltfm_data sdhci_tegra30_pdata = { .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | SDHCI_QUIRK_SINGLE_POWER_WRITE | SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, .ops = &tegra_sdhci_ops, }; +static struct sdhci_tegra_soc_data soc_data_tegra30 = { + .pdata = &sdhci_tegra30_pdata, +}; +#endif + static const struct of_device_id sdhci_tegra_dt_match[] __devinitdata = { - { .compatible = "nvidia,tegra20-sdhci", }, +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + { .compatible = "nvidia,tegra30-sdhci", .data = &soc_data_tegra30 }, +#endif +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + { .compatible = "nvidia,tegra20-sdhci", .data = &soc_data_tegra20 }, +#endif {} }; MODULE_DEVICE_TABLE(of, sdhci_dt_ids); @@ -164,13 +218,22 @@ static struct tegra_sdhci_platform_data * __devinit sdhci_tegra_dt_parse_pdata( static int __devinit sdhci_tegra_probe(struct platform_device *pdev) { + const struct of_device_id *match; + const struct sdhci_tegra_soc_data *soc_data; + struct sdhci_host *host; struct sdhci_pltfm_host *pltfm_host; struct tegra_sdhci_platform_data *plat; - struct sdhci_host *host; + struct sdhci_tegra *tegra_host; struct clk *clk; int rc; - host = sdhci_pltfm_init(pdev, &sdhci_tegra_pdata); + match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); + if (match) + soc_data = match->data; + else + soc_data = &soc_data_tegra20; + + host = sdhci_pltfm_init(pdev, soc_data->pdata); if (IS_ERR(host)) return PTR_ERR(host); @@ -187,7 +250,17 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev) goto err_no_plat; } - pltfm_host->priv = plat; + tegra_host = devm_kzalloc(&pdev->dev, sizeof(*tegra_host), GFP_KERNEL); + if (!tegra_host) { + dev_err(mmc_dev(host->mmc), "failed to allocate tegra_host\n"); + rc = -ENOMEM; + goto err_no_plat; + } + + tegra_host->plat = plat; + tegra_host->soc_data = soc_data; + + pltfm_host->priv = tegra_host; if (gpio_is_valid(plat->power_gpio)) { rc = gpio_request(plat->power_gpio, "sdhci_power"); @@ -283,7 +356,8 @@ static int __devexit sdhci_tegra_remove(struct platform_device *pdev) { struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - struct tegra_sdhci_platform_data *plat = pltfm_host->priv; + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct tegra_sdhci_platform_data *plat = tegra_host->plat; int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); sdhci_remove_host(host, dead); @@ -326,5 +400,5 @@ static struct platform_driver sdhci_tegra_driver = { module_platform_driver(sdhci_tegra_driver); MODULE_DESCRIPTION("SDHCI driver for Tegra"); -MODULE_AUTHOR(" Google, Inc."); +MODULE_AUTHOR("Google, Inc."); MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 8d66706824a6..8262cadfdab7 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2267,8 +2267,8 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) { irqreturn_t result; struct sdhci_host *host = dev_id; - u32 intmask; - int cardint = 0; + u32 intmask, unexpected = 0; + int cardint = 0, max_loops = 16; spin_lock(&host->lock); @@ -2286,6 +2286,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) goto out; } +again: DBG("*** %s got interrupt: 0x%08x\n", mmc_hostname(host->mmc), intmask); @@ -2344,19 +2345,23 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) intmask &= ~SDHCI_INT_CARD_INT; if (intmask) { - pr_err("%s: Unexpected interrupt 0x%08x.\n", - mmc_hostname(host->mmc), intmask); - sdhci_dumpregs(host); - + unexpected |= intmask; sdhci_writel(host, intmask, SDHCI_INT_STATUS); } result = IRQ_HANDLED; - mmiowb(); + intmask = sdhci_readl(host, SDHCI_INT_STATUS); + if (intmask && --max_loops) + goto again; out: spin_unlock(&host->lock); + if (unexpected) { + pr_err("%s: Unexpected interrupt 0x%08x.\n", + mmc_hostname(host->mmc), unexpected); + sdhci_dumpregs(host); + } /* * We have to delay this as it calls back into the driver. */ @@ -2379,6 +2384,9 @@ int sdhci_suspend_host(struct sdhci_host *host) int ret; bool has_tuning_timer; + if (host->ops->platform_suspend) + host->ops->platform_suspend(host); + sdhci_disable_card_detection(host); /* Disable tuning since we are suspending */ @@ -2423,12 +2431,24 @@ int sdhci_resume_host(struct sdhci_host *host) if (ret) return ret; - sdhci_init(host, (host->mmc->pm_flags & MMC_PM_KEEP_POWER)); - mmiowb(); + if ((host->mmc->pm_flags & MMC_PM_KEEP_POWER) && + (host->quirks2 & SDHCI_QUIRK2_HOST_OFF_CARD_ON)) { + /* Card keeps power but host controller does not */ + sdhci_init(host, 0); + host->pwr = 0; + host->clock = 0; + sdhci_do_set_ios(host, &host->mmc->ios); + } else { + sdhci_init(host, (host->mmc->pm_flags & MMC_PM_KEEP_POWER)); + mmiowb(); + } ret = mmc_resume_host(host->mmc); sdhci_enable_card_detection(host); + if (host->ops->platform_resume) + host->ops->platform_resume(host); + /* Set the re-tuning expiration flag */ if ((host->version >= SDHCI_SPEC_300) && host->tuning_count && (host->tuning_mode == SDHCI_TUNING_MODE_1)) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index ad265b96b75b..f761f23d2a28 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -275,6 +275,8 @@ struct sdhci_ops { void (*platform_reset_exit)(struct sdhci_host *host, u8 mask); int (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs); void (*hw_reset)(struct sdhci_host *host); + void (*platform_suspend)(struct sdhci_host *host); + void (*platform_resume)(struct sdhci_host *host); }; #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 75a485448796..60f205708f54 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -746,7 +746,6 @@ static u32 sh_mmcif_set_cmd(struct sh_mmcif_host *host, case MMC_SET_WRITE_PROT: case MMC_CLR_WRITE_PROT: case MMC_ERASE: - case MMC_GEN_CMD: tmp |= CMD_SET_RBSY; break; } @@ -829,7 +828,6 @@ static void sh_mmcif_start_cmd(struct sh_mmcif_host *host, case MMC_SET_WRITE_PROT: case MMC_CLR_WRITE_PROT: case MMC_ERASE: - case MMC_GEN_CMD: mask = MASK_START_CMD | MASK_MRBSYE; break; default: diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 58da3c44acc5..934b68e9efc3 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -90,6 +90,15 @@ static int sh_mobile_sdhi_write16_hook(struct tmio_mmc_host *host, int addr) return 0; } +static void sh_mobile_sdhi_cd_wakeup(const struct platform_device *pdev) +{ + mmc_detect_change(dev_get_drvdata(&pdev->dev), msecs_to_jiffies(100)); +} + +static const struct sh_mobile_sdhi_ops sdhi_ops = { + .cd_wakeup = sh_mobile_sdhi_cd_wakeup, +}; + static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) { struct sh_mobile_sdhi *priv; @@ -109,6 +118,12 @@ static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) mmc_data = &priv->mmc_data; p->pdata = mmc_data; + if (p->init) { + ret = p->init(pdev, &sdhi_ops); + if (ret) + goto einit; + } + snprintf(clk_name, sizeof(clk_name), "sdhi%d", pdev->id); priv->clk = clk_get(&pdev->dev, clk_name); if (IS_ERR(priv->clk)) { @@ -117,8 +132,6 @@ static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) goto eclkget; } - clk_enable(priv->clk); - mmc_data->hclk = clk_get_rate(priv->clk); mmc_data->set_pwr = sh_mobile_sdhi_set_pwr; mmc_data->get_cd = sh_mobile_sdhi_get_cd; @@ -129,6 +142,7 @@ static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) mmc_data->write16_hook = sh_mobile_sdhi_write16_hook; mmc_data->ocr_mask = p->tmio_ocr_mask; mmc_data->capabilities |= p->tmio_caps; + mmc_data->cd_gpio = p->cd_gpio; if (p->dma_slave_tx > 0 && p->dma_slave_rx > 0) { priv->param_tx.slave_id = p->dma_slave_tx; @@ -211,7 +225,7 @@ static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) dev_info(&pdev->dev, "%s base at 0x%08lx clock rate %u MHz\n", mmc_hostname(host->mmc), (unsigned long) - (platform_get_resource(pdev,IORESOURCE_MEM, 0)->start), + (platform_get_resource(pdev, IORESOURCE_MEM, 0)->start), mmc_data->hclk / 1000000); return ret; @@ -232,9 +246,11 @@ eirq_sdio: eirq_card_detect: tmio_mmc_host_remove(host); eprobe: - clk_disable(priv->clk); clk_put(priv->clk); eclkget: + if (p->cleanup) + p->cleanup(pdev); +einit: kfree(priv); return ret; } @@ -258,8 +274,11 @@ static int sh_mobile_sdhi_remove(struct platform_device *pdev) free_irq(irq, host); } - clk_disable(priv->clk); clk_put(priv->clk); + + if (p->cleanup) + p->cleanup(pdev); + kfree(priv); return 0; diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index f96c536d130a..d857f5c6e7d9 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -47,16 +47,14 @@ struct tmio_mmc_host { struct mmc_request *mrq; struct mmc_data *data; struct mmc_host *mmc; - unsigned int sdio_irq_enabled; + + /* Controller power state */ + bool power; /* Callbacks for clock / power control */ void (*set_pwr)(struct platform_device *host, int state); void (*set_clk_div)(struct platform_device *host, int state); - int pm_error; - /* recognise system-wide suspend in runtime PM methods */ - bool pm_global; - /* pio related stuff */ struct scatterlist *sg_ptr; struct scatterlist *sg_orig; @@ -86,6 +84,7 @@ struct tmio_mmc_host { spinlock_t lock; /* protect host private data */ unsigned long last_req_ts; struct mutex ios_lock; /* protect set_ios() context */ + bool native_hotplug; }; int tmio_mmc_host_probe(struct tmio_mmc_host **host, diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index e21988901c36..9a7996ade58e 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -34,6 +34,7 @@ #include <linux/io.h> #include <linux/irq.h> #include <linux/mfd/tmio.h> +#include <linux/mmc/cd-gpio.h> #include <linux/mmc/host.h> #include <linux/mmc/tmio.h> #include <linux/module.h> @@ -127,7 +128,6 @@ static void tmio_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) struct tmio_mmc_host *host = mmc_priv(mmc); if (enable) { - host->sdio_irq_enabled = 1; host->sdio_irq_mask = TMIO_SDIO_MASK_ALL & ~TMIO_SDIO_STAT_IOIRQ; sd_ctrl_write16(host, CTL_TRANSACTION_CTL, 0x0001); @@ -136,7 +136,6 @@ static void tmio_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) host->sdio_irq_mask = TMIO_SDIO_MASK_ALL; sd_ctrl_write16(host, CTL_SDIO_IRQ_MASK, host->sdio_irq_mask); sd_ctrl_write16(host, CTL_TRANSACTION_CTL, 0x0000); - host->sdio_irq_enabled = 0; } } @@ -304,6 +303,7 @@ static int tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command { struct mmc_data *data = host->data; int c = cmd->opcode; + u32 irq_mask = TMIO_MASK_CMD; /* Command 12 is handled by hardware */ if (cmd->opcode == 12 && !cmd->arg) { @@ -339,7 +339,9 @@ static int tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command c |= TRANSFER_READ; } - tmio_mmc_enable_mmc_irqs(host, TMIO_MASK_CMD); + if (!host->native_hotplug) + irq_mask &= ~(TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT); + tmio_mmc_enable_mmc_irqs(host, irq_mask); /* Fire off the command */ sd_ctrl_write32(host, CTL_ARG_REG, cmd->arg); @@ -758,7 +760,7 @@ fail: static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct tmio_mmc_host *host = mmc_priv(mmc); - struct tmio_mmc_data *pdata = host->pdata; + struct device *dev = &host->pdev->dev; unsigned long flags; mutex_lock(&host->ios_lock); @@ -766,13 +768,13 @@ static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) spin_lock_irqsave(&host->lock, flags); if (host->mrq) { if (IS_ERR(host->mrq)) { - dev_dbg(&host->pdev->dev, + dev_dbg(dev, "%s.%d: concurrent .set_ios(), clk %u, mode %u\n", current->comm, task_pid_nr(current), ios->clock, ios->power_mode); host->mrq = ERR_PTR(-EINTR); } else { - dev_dbg(&host->pdev->dev, + dev_dbg(dev, "%s.%d: CMD%u active since %lu, now %lu!\n", current->comm, task_pid_nr(current), host->mrq->cmd->opcode, host->last_req_ts, jiffies); @@ -788,13 +790,15 @@ static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) spin_unlock_irqrestore(&host->lock, flags); /* - * pdata->power == false only if COLD_CD is available, otherwise only - * in short time intervals during probing or resuming + * host->power toggles between false and true in both cases - either + * or not the controller can be runtime-suspended during inactivity. + * But if the controller has to be kept on, the runtime-pm usage_count + * is kept positive, so no suspending actually takes place. */ if (ios->power_mode == MMC_POWER_ON && ios->clock) { - if (!pdata->power) { - pm_runtime_get_sync(&host->pdev->dev); - pdata->power = true; + if (!host->power) { + pm_runtime_get_sync(dev); + host->power = true; } tmio_mmc_set_clock(host, ios->clock); /* power up SD bus */ @@ -805,9 +809,9 @@ static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) } else if (ios->power_mode != MMC_POWER_UP) { if (host->set_pwr && ios->power_mode == MMC_POWER_OFF) host->set_pwr(host->pdev, 0); - if (pdata->power) { - pdata->power = false; - pm_runtime_put(&host->pdev->dev); + if (host->power) { + host->power = false; + pm_runtime_put(dev); } tmio_mmc_clk_stop(host); } @@ -913,7 +917,11 @@ int __devinit tmio_mmc_host_probe(struct tmio_mmc_host **host, else mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; - pdata->power = false; + _host->native_hotplug = !(pdata->flags & TMIO_MMC_USE_GPIO_CD || + mmc->caps & MMC_CAP_NEEDS_POLL || + mmc->caps & MMC_CAP_NONREMOVABLE); + + _host->power = false; pm_runtime_enable(&pdev->dev); ret = pm_runtime_resume(&pdev->dev); if (ret < 0) @@ -926,14 +934,13 @@ int __devinit tmio_mmc_host_probe(struct tmio_mmc_host **host, * 3) a worker thread polls the sdhi - indicated by MMC_CAP_NEEDS_POLL * 4) the medium is non-removable - indicated by MMC_CAP_NONREMOVABLE * - * While we increment the rtpm counter for all scenarios when the mmc - * core activates us by calling an appropriate set_ios(), we must + * While we increment the runtime PM counter for all scenarios when + * the mmc core activates us by calling an appropriate set_ios(), we + * must additionally ensure that in case 2) the tmio mmc hardware stays * additionally ensure that in case 2) the tmio mmc hardware stays * powered on during runtime for the card detection to work. */ - if (!(pdata->flags & TMIO_MMC_HAS_COLD_CD - || mmc->caps & MMC_CAP_NEEDS_POLL - || mmc->caps & MMC_CAP_NONREMOVABLE)) + if (_host->native_hotplug) pm_runtime_get_noresume(&pdev->dev); tmio_mmc_clk_stop(_host); @@ -963,9 +970,19 @@ int __devinit tmio_mmc_host_probe(struct tmio_mmc_host **host, irq_mask |= TMIO_MASK_READOP; if (!_host->chan_tx) irq_mask |= TMIO_MASK_WRITEOP; + if (!_host->native_hotplug) + irq_mask &= ~(TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT); tmio_mmc_enable_mmc_irqs(_host, irq_mask); + if (pdata->flags & TMIO_MMC_USE_GPIO_CD) { + ret = mmc_cd_gpio_request(mmc, pdata->cd_gpio); + if (ret < 0) { + tmio_mmc_host_remove(_host); + return ret; + } + } + *host = _host; return 0; @@ -983,22 +1000,22 @@ EXPORT_SYMBOL(tmio_mmc_host_probe); void tmio_mmc_host_remove(struct tmio_mmc_host *host) { struct platform_device *pdev = host->pdev; + struct tmio_mmc_data *pdata = host->pdata; + struct mmc_host *mmc = host->mmc; - /* - * We don't have to manipulate pdata->power here: if there is a card in - * the slot, the runtime PM is active and our .runtime_resume() will not - * be run. If there is no card in the slot and the platform can suspend - * the controller, the runtime PM is suspended and pdata->power == false, - * so, our .runtime_resume() will not try to detect a card in the slot. - */ - if (host->pdata->flags & TMIO_MMC_HAS_COLD_CD - || host->mmc->caps & MMC_CAP_NEEDS_POLL - || host->mmc->caps & MMC_CAP_NONREMOVABLE) + if (pdata->flags & TMIO_MMC_USE_GPIO_CD) + /* + * This means we can miss a card-eject, but this is anyway + * possible, because of delayed processing of hotplug events. + */ + mmc_cd_gpio_free(mmc); + + if (!host->native_hotplug) pm_runtime_get_sync(&pdev->dev); dev_pm_qos_hide_latency_limit(&pdev->dev); - mmc_remove_host(host->mmc); + mmc_remove_host(mmc); cancel_work_sync(&host->done); cancel_delayed_work_sync(&host->delayed_reset_work); tmio_mmc_release_dma(host); @@ -1007,7 +1024,7 @@ void tmio_mmc_host_remove(struct tmio_mmc_host *host) pm_runtime_disable(&pdev->dev); iounmap(host->ctl); - mmc_free_host(host->mmc); + mmc_free_host(mmc); } EXPORT_SYMBOL(tmio_mmc_host_remove); @@ -1021,8 +1038,6 @@ int tmio_mmc_host_suspend(struct device *dev) if (!ret) tmio_mmc_disable_mmc_irqs(host, TMIO_MASK_ALL); - host->pm_error = pm_runtime_put_sync(dev); - return ret; } EXPORT_SYMBOL(tmio_mmc_host_suspend); @@ -1032,20 +1047,10 @@ int tmio_mmc_host_resume(struct device *dev) struct mmc_host *mmc = dev_get_drvdata(dev); struct tmio_mmc_host *host = mmc_priv(mmc); - /* The MMC core will perform the complete set up */ - host->pdata->power = false; - - host->pm_global = true; - if (!host->pm_error) - pm_runtime_get_sync(dev); - - if (host->pm_global) { - /* Runtime PM resume callback didn't run */ - tmio_mmc_reset(host); - tmio_mmc_enable_dma(host, true); - host->pm_global = false; - } + tmio_mmc_reset(host); + tmio_mmc_enable_dma(host, true); + /* The MMC core will perform the complete set up */ return mmc_resume_host(mmc); } EXPORT_SYMBOL(tmio_mmc_host_resume); @@ -1062,19 +1067,10 @@ int tmio_mmc_host_runtime_resume(struct device *dev) { struct mmc_host *mmc = dev_get_drvdata(dev); struct tmio_mmc_host *host = mmc_priv(mmc); - struct tmio_mmc_data *pdata = host->pdata; tmio_mmc_reset(host); tmio_mmc_enable_dma(host, true); - if (pdata->power) { - /* Only entered after a card-insert interrupt */ - if (!mmc->card) - tmio_mmc_set_ios(mmc, &mmc->ios); - mmc_detect_change(mmc, msecs_to_jiffies(100)); - } - host->pm_global = false; - return 0; } EXPORT_SYMBOL(tmio_mmc_host_runtime_resume); |