From 51aa66a58494f869f491eedda86c409c50536c14 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 4 Dec 2012 17:06:18 +0530 Subject: mmc: sdio: fix resume failure due to lack of CMD52 reset If SDIO keep power flag (MMC_PM_KEEP_POWER) is not set, card would be reinitialized during resume but as we are not resetting (CMD52 reset) the SDIO card during this reinitialization, card may fail to respond back to subsequent commands (CMD5 etc...). This change resets the card before the reinitialization of card during resume. Signed-off-by: Subhash Jadavani Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/sdio.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 2273ce6b6c1a..34ad4c877c1f 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -937,10 +937,12 @@ static int mmc_sdio_resume(struct mmc_host *host) mmc_claim_host(host); /* No need to reinitialize powered-resumed nonremovable cards */ - if (mmc_card_is_removable(host) || !mmc_card_keep_power(host)) + if (mmc_card_is_removable(host) || !mmc_card_keep_power(host)) { + sdio_reset(host); + mmc_go_idle(host); err = mmc_sdio_init_card(host, host->ocr, host->card, mmc_card_keep_power(host)); - else if (mmc_card_keep_power(host) && mmc_card_wake_sdio_irq(host)) { + } else if (mmc_card_keep_power(host) && mmc_card_wake_sdio_irq(host)) { /* We may have switched to 1-bit mode during suspend */ err = sdio_enable_4bit_bus(host->card); if (err > 0) { -- cgit v1.2.3 From 41875e388401ad97c33252d5fa39d52e0b70ee9b Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Tue, 4 Dec 2012 17:06:19 +0530 Subject: mmc: sdio: Fix SDIO 3.0 UHS-I initialization sequence According to UHS-I initialization sequence for SDIO 3.0 cards, the host must set bit[24] (S18R) of OCR register during OCR handshake to know whether the SDIO card is capable of doing 1.8V I/O. Signed-off-by: Sujit Reddy Thumma Signed-off-by: Subhash Jadavani Reviewed-by: Johan Rudholm Reviewed-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/sdio.c | 22 +++++++++++----------- include/linux/mmc/host.h | 8 ++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 34ad4c877c1f..9565d38d91a4 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -157,10 +157,7 @@ static int sdio_read_cccr(struct mmc_card *card, u32 ocr) if (ret) goto out; - if (card->host->caps & - (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | - MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | - MMC_CAP_UHS_DDR50)) { + if (mmc_host_uhs(card->host)) { if (data & SDIO_UHS_DDR50) card->sw_caps.sd3_bus_mode |= SD_MODE_UHS_DDR50; @@ -478,8 +475,7 @@ static int sdio_set_bus_speed_mode(struct mmc_card *card) * If the host doesn't support any of the UHS-I modes, fallback on * default speed. */ - if (!(card->host->caps & (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | - MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_DDR50))) + if (!mmc_host_uhs(card->host)) return 0; bus_speed = SDIO_SPEED_SDR12; @@ -645,11 +641,7 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, * systems that claim 1.8v signalling in fact do not support * it. */ - if ((ocr & R4_18V_PRESENT) && - (host->caps & - (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | - MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | - MMC_CAP_UHS_DDR50))) { + if ((ocr & R4_18V_PRESENT) && mmc_host_uhs(host)) { err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180, true); if (err) { @@ -1022,6 +1014,10 @@ static int mmc_sdio_power_restore(struct mmc_host *host) goto out; } + if (mmc_host_uhs(host)) + /* to query card if 1.8V signalling is supported */ + host->ocr |= R4_18V_PRESENT; + ret = mmc_sdio_init_card(host, host->ocr, host->card, mmc_card_keep_power(host)); if (!ret && host->sdio_irqs) @@ -1087,6 +1083,10 @@ int mmc_attach_sdio(struct mmc_host *host) /* * Detect and init the card. */ + if (mmc_host_uhs(host)) + /* to query card if 1.8V signalling is supported */ + host->ocr |= R4_18V_PRESENT; + err = mmc_sdio_init_card(host, host->ocr, NULL, 0); if (err) { if (err == -EAGAIN) { diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 61a10c17aabd..c89a1bb87fa5 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -434,6 +434,14 @@ static inline int mmc_boot_partition_access(struct mmc_host *host) return !(host->caps2 & MMC_CAP2_BOOTPART_NOACC); } +static inline int mmc_host_uhs(struct mmc_host *host) +{ + return host->caps & + (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | + MMC_CAP_UHS_DDR50); +} + #ifdef CONFIG_MMC_CLKGATE void mmc_host_clk_hold(struct mmc_host *host); void mmc_host_clk_release(struct mmc_host *host); -- cgit v1.2.3 From 77e2ff08925c7ec7267dc87c27eda2e62d585b57 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 4 Dec 2012 17:06:20 +0530 Subject: mmc: sdio: print correct UHS mode during card detection When SDIO3.0 card is detected, incorrect bus speed mode is printed as part of card detection print in kernel logs. This change fixes it so that user won't be confused by looking at incorrect card detection message in logs. Signed-off-by: Subhash Jadavani Tested-by: Jackey Shen Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/sdio.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 9565d38d91a4..3a64933466b8 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -485,23 +485,27 @@ static int sdio_set_bus_speed_mode(struct mmc_card *card) bus_speed = SDIO_SPEED_SDR104; timing = MMC_TIMING_UHS_SDR104; card->sw_caps.uhs_max_dtr = UHS_SDR104_MAX_DTR; + card->sd_bus_speed = UHS_SDR104_BUS_SPEED; } else if ((card->host->caps & MMC_CAP_UHS_DDR50) && (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_DDR50)) { bus_speed = SDIO_SPEED_DDR50; timing = MMC_TIMING_UHS_DDR50; card->sw_caps.uhs_max_dtr = UHS_DDR50_MAX_DTR; + card->sd_bus_speed = UHS_DDR50_BUS_SPEED; } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_SDR50)) && (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR50)) { bus_speed = SDIO_SPEED_SDR50; timing = MMC_TIMING_UHS_SDR50; card->sw_caps.uhs_max_dtr = UHS_SDR50_MAX_DTR; + card->sd_bus_speed = UHS_SDR50_BUS_SPEED; } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR25)) && (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR25)) { bus_speed = SDIO_SPEED_SDR25; timing = MMC_TIMING_UHS_SDR25; card->sw_caps.uhs_max_dtr = UHS_SDR25_MAX_DTR; + card->sd_bus_speed = UHS_SDR25_BUS_SPEED; } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR25 | MMC_CAP_UHS_SDR12)) && (card->sw_caps.sd3_bus_mode & @@ -509,6 +513,7 @@ static int sdio_set_bus_speed_mode(struct mmc_card *card) bus_speed = SDIO_SPEED_SDR12; timing = MMC_TIMING_UHS_SDR12; card->sw_caps.uhs_max_dtr = UHS_SDR12_MAX_DTR; + card->sd_bus_speed = UHS_SDR12_BUS_SPEED; } err = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed); -- cgit v1.2.3 From 505a8680b78f580245cfb83f37971c7b62bcf991 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 11 Dec 2012 15:23:42 +0800 Subject: mmc: sdhci: query card presence from cd-gpio before asking SDHCI Call mmc_gpio_get_cd() to query card presence from cd-gpio before asking SDHCI. The rationale behind this change is that flag SDHCI_QUIRK_BROKEN_CARD_DETECTION is designed for SDHCI controller to tell that SDHCI_PRESENT_STATE is broken, and it should be used for this case only. So when cd-gpio is being used, the controller should set the flag to tell that SDHCI_PRESENT_STATE is not available. However, the existing code will skip checking cd-gpio as long as flag SDHCI_QUIRK_BROKEN_CARD_DETECTION is set. Change the querying order between cd-gpio and SDHCI to support the rationale above. Signed-off-by: Shawn Guo Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 6f0bfc0c8c9c..1b97fe2d70ab 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1258,7 +1258,7 @@ static int sdhci_set_power(struct sdhci_host *host, unsigned short power) static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct sdhci_host *host; - bool present; + int present; unsigned long flags; u32 tuning_opcode; @@ -1287,18 +1287,21 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) host->mrq = mrq; - /* If polling, assume that the card is always present. */ - if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) - present = true; - else - present = sdhci_readl(host, SDHCI_PRESENT_STATE) & - SDHCI_CARD_PRESENT; - - /* If we're using a cd-gpio, testing the presence bit might fail. */ - if (!present) { - int ret = mmc_gpio_get_cd(host->mmc); - if (ret > 0) - present = true; + /* + * Firstly check card presence from cd-gpio. The return could + * be one of the following possibilities: + * negative: cd-gpio is not available + * zero: cd-gpio is used, and card is removed + * one: cd-gpio is used, and card is present + */ + present = mmc_gpio_get_cd(host->mmc); + if (present < 0) { + /* If polling, assume that the card is always present. */ + if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) + present = 1; + else + present = sdhci_readl(host, SDHCI_PRESENT_STATE) & + SDHCI_CARD_PRESENT; } if (!present || host->flags & SDHCI_DEVICE_DEAD) { -- cgit v1.2.3 From d65b5ae8dabf48d8e7811a5319ec581e41b04d62 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 11 Dec 2012 22:32:18 +0800 Subject: mmc: slot-gpio: use devm_* managed functions to ease users Use devm_* managed functions, so that slot-gpio users do not have to call mmc_gpio_free_ro/cd to free up resources requested in mmc_gpio_request_ro/cd. Signed-off-by: Shawn Guo Acked-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/core/slot-gpio.c | 57 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/core/slot-gpio.c b/drivers/mmc/core/slot-gpio.c index 16a1c0b6f264..324235105519 100644 --- a/drivers/mmc/core/slot-gpio.c +++ b/drivers/mmc/core/slot-gpio.c @@ -92,6 +92,20 @@ int mmc_gpio_get_cd(struct mmc_host *host) } EXPORT_SYMBOL(mmc_gpio_get_cd); +/** + * mmc_gpio_request_ro - request a gpio for write-protection + * @host: mmc host + * @gpio: gpio number requested + * + * As devm_* managed functions are used in mmc_gpio_request_ro(), client + * drivers do not need to explicitly call mmc_gpio_free_ro() for freeing up, + * if the requesting and freeing are only needed at probing and unbinding time + * for once. However, if client drivers do something special like runtime + * switching for write-protection, they are responsible for calling + * mmc_gpio_request_ro() and mmc_gpio_free_ro() as a pair on their own. + * + * Returns zero on success, else an error. + */ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) { struct mmc_gpio *ctx; @@ -106,7 +120,8 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) ctx = host->slot.handler_priv; - ret = gpio_request_one(gpio, GPIOF_DIR_IN, ctx->ro_label); + ret = devm_gpio_request_one(&host->class_dev, gpio, GPIOF_DIR_IN, + ctx->ro_label); if (ret < 0) return ret; @@ -116,6 +131,20 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) } EXPORT_SYMBOL(mmc_gpio_request_ro); +/** + * mmc_gpio_request_cd - request a gpio for card-detection + * @host: mmc host + * @gpio: gpio number requested + * + * As devm_* managed functions are used in mmc_gpio_request_cd(), client + * drivers do not need to explicitly call mmc_gpio_free_cd() for freeing up, + * if the requesting and freeing are only needed at probing and unbinding time + * for once. However, if client drivers do something special like runtime + * switching for card-detection, they are responsible for calling + * mmc_gpio_request_cd() and mmc_gpio_free_cd() as a pair on their own. + * + * Returns zero on success, else an error. + */ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) { struct mmc_gpio *ctx; @@ -128,7 +157,8 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) ctx = host->slot.handler_priv; - ret = gpio_request_one(gpio, GPIOF_DIR_IN, ctx->cd_label); + ret = devm_gpio_request_one(&host->class_dev, gpio, GPIOF_DIR_IN, + ctx->cd_label); if (ret < 0) /* * don't bother freeing memory. It might still get used by other @@ -146,7 +176,8 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) irq = -EINVAL; if (irq >= 0) { - ret = request_threaded_irq(irq, NULL, mmc_gpio_cd_irqt, + ret = devm_request_threaded_irq(&host->class_dev, irq, + NULL, mmc_gpio_cd_irqt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, ctx->cd_label, host); if (ret < 0) @@ -164,6 +195,13 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) } EXPORT_SYMBOL(mmc_gpio_request_cd); +/** + * mmc_gpio_free_ro - free the write-protection gpio + * @host: mmc host + * + * It's provided only for cases that client drivers need to manually free + * up the write-protection gpio requested by mmc_gpio_request_ro(). + */ void mmc_gpio_free_ro(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; @@ -175,10 +213,17 @@ void mmc_gpio_free_ro(struct mmc_host *host) gpio = ctx->ro_gpio; ctx->ro_gpio = -EINVAL; - gpio_free(gpio); + devm_gpio_free(&host->class_dev, gpio); } EXPORT_SYMBOL(mmc_gpio_free_ro); +/** + * mmc_gpio_free_cd - free the card-detection gpio + * @host: mmc host + * + * It's provided only for cases that client drivers need to manually free + * up the card-detection gpio requested by mmc_gpio_request_cd(). + */ void mmc_gpio_free_cd(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; @@ -188,13 +233,13 @@ void mmc_gpio_free_cd(struct mmc_host *host) return; if (host->slot.cd_irq >= 0) { - free_irq(host->slot.cd_irq, host); + devm_free_irq(&host->class_dev, host->slot.cd_irq, host); host->slot.cd_irq = -EINVAL; } gpio = ctx->cd_gpio; ctx->cd_gpio = -EINVAL; - gpio_free(gpio); + devm_gpio_free(&host->class_dev, gpio); } EXPORT_SYMBOL(mmc_gpio_free_cd); -- cgit v1.2.3 From 164cda5236acf833f22a3edafdf8b95a7de7b402 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 11 Dec 2012 22:32:19 +0800 Subject: mmc: remove unncessary mmc_gpio_free_cd() call from slot-gpio users Since slot-gpio uses devm_* managed functions in mmc_gpio_request_cd() now, we can remove those mmc_gpio_free_cd() call from host drivers' .probe() error path and .remove(). Signed-off-by: Shawn Guo Acked-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-pxav3.c | 5 ----- drivers/mmc/host/sh_mmcif.c | 6 ------ drivers/mmc/host/tmio_mmc_pio.c | 8 -------- 3 files changed, 19 deletions(-) diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index fad0966427fd..b7ee7761bc26 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -316,7 +316,6 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) err_add_host: clk_disable_unprepare(clk); clk_put(clk); - mmc_gpio_free_cd(host->mmc); err_cd_req: err_clk_get: sdhci_pltfm_free(pdev); @@ -329,16 +328,12 @@ static int sdhci_pxav3_remove(struct platform_device *pdev) struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_pxa *pxa = pltfm_host->priv; - struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; sdhci_remove_host(host, 1); clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); - if (gpio_is_valid(pdata->ext_cd_gpio)) - mmc_gpio_free_cd(host->mmc); - sdhci_pltfm_free(pdev); kfree(pxa); diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 9a4c151067dd..741aeb95c7a4 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1404,8 +1404,6 @@ static int sh_mmcif_probe(struct platform_device *pdev) return ret; emmcaddh: - if (pd && pd->use_cd_gpio) - mmc_gpio_free_cd(mmc); erqcd: free_irq(irq[1], host); ereqirq1: @@ -1427,7 +1425,6 @@ ealloch: static int sh_mmcif_remove(struct platform_device *pdev) { struct sh_mmcif_host *host = platform_get_drvdata(pdev); - struct sh_mmcif_plat_data *pd = pdev->dev.platform_data; int irq[2]; host->dying = true; @@ -1436,9 +1433,6 @@ static int sh_mmcif_remove(struct platform_device *pdev) dev_pm_qos_hide_latency_limit(&pdev->dev); - if (pd && pd->use_cd_gpio) - mmc_gpio_free_cd(host->mmc); - mmc_remove_host(host->mmc); sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL); diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index 50bf495a988b..0f992e9ffc73 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -1060,16 +1060,8 @@ 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; - 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_gpio_free_cd(mmc); - if (!host->native_hotplug) pm_runtime_get_sync(&pdev->dev); -- cgit v1.2.3 From fbe5fdd12c4cdae61a8c3d371e564371177da86e Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 11 Dec 2012 22:32:20 +0800 Subject: mmc: sdhci-esdhc-imx: use slot-gpio helpers for CD and WP Use slot-gpio helpers to save some code in the driver. Signed-off-by: Shawn Guo Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 56 +++++++++++--------------------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index e07df812ff1e..dd7fcc137644 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -147,17 +148,16 @@ static u32 esdhc_readl_le(struct sdhci_host *host, int reg) struct pltfm_imx_data *imx_data = pltfm_host->priv; struct esdhc_platform_data *boarddata = &imx_data->boarddata; - /* fake CARD_PRESENT flag */ u32 val = readl(host->ioaddr + reg); - if (unlikely((reg == SDHCI_PRESENT_STATE) - && gpio_is_valid(boarddata->cd_gpio))) { - if (gpio_get_value(boarddata->cd_gpio)) - /* no card, if a valid gpio says so... */ + if (unlikely(reg == SDHCI_PRESENT_STATE)) { + /* + * After SDHCI core gets improved to never query + * SDHCI_CARD_PRESENT state in GPIO case, we can + * remove this check. + */ + if (boarddata->cd_type == ESDHC_CD_GPIO) val &= ~SDHCI_CARD_PRESENT; - else - /* ... in all other cases assume card is present */ - val |= SDHCI_CARD_PRESENT; } if (unlikely(reg == SDHCI_CAPABILITIES)) { @@ -362,8 +362,7 @@ static unsigned int esdhc_pltfm_get_ro(struct sdhci_host *host) switch (boarddata->wp_type) { case ESDHC_WP_GPIO: - if (gpio_is_valid(boarddata->wp_gpio)) - return gpio_get_value(boarddata->wp_gpio); + return mmc_gpio_get_ro(host->mmc); case ESDHC_WP_CONTROLLER: return !(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_WRITE_PROTECT); @@ -394,14 +393,6 @@ static struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = { .ops = &sdhci_esdhc_ops, }; -static irqreturn_t cd_irq(int irq, void *data) -{ - struct sdhci_host *sdhost = (struct sdhci_host *)data; - - tasklet_schedule(&sdhost->card_tasklet); - return IRQ_HANDLED; -}; - #ifdef CONFIG_OF static int sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, @@ -527,37 +518,22 @@ static int sdhci_esdhc_imx_probe(struct platform_device *pdev) /* write_protect */ if (boarddata->wp_type == ESDHC_WP_GPIO) { - err = devm_gpio_request_one(&pdev->dev, boarddata->wp_gpio, - GPIOF_IN, "ESDHC_WP"); + err = mmc_gpio_request_ro(host->mmc, boarddata->wp_gpio); if (err) { - dev_warn(mmc_dev(host->mmc), - "no write-protect pin available!\n"); - boarddata->wp_gpio = -EINVAL; + dev_err(mmc_dev(host->mmc), + "failed to request write-protect gpio!\n"); + goto disable_clk; } - } else { - boarddata->wp_gpio = -EINVAL; + host->mmc->caps2 |= MMC_CAP2_RO_ACTIVE_HIGH; } /* card_detect */ - if (boarddata->cd_type != ESDHC_CD_GPIO) - boarddata->cd_gpio = -EINVAL; - switch (boarddata->cd_type) { case ESDHC_CD_GPIO: - err = devm_gpio_request_one(&pdev->dev, boarddata->cd_gpio, - GPIOF_IN, "ESDHC_CD"); + err = mmc_gpio_request_cd(host->mmc, boarddata->cd_gpio); if (err) { dev_err(mmc_dev(host->mmc), - "no card-detect pin available!\n"); - goto disable_clk; - } - - err = devm_request_irq(&pdev->dev, - gpio_to_irq(boarddata->cd_gpio), cd_irq, - IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, - mmc_hostname(host->mmc), host); - if (err) { - dev_err(mmc_dev(host->mmc), "request irq error\n"); + "failed to request card-detect gpio!\n"); goto disable_clk; } /* fall through */ -- cgit v1.2.3 From 92ff0c5bc4000d6b2b1bfc8cd40bd1397a03c8ec Mon Sep 17 00:00:00 2001 From: Teppei Kamijou Date: Wed, 12 Dec 2012 15:38:05 +0100 Subject: mmc: sh_mmcif: force to fail CMD52 immediately mmc_rescan() sends CMD52 (SD_IO_RW_DIRECT) to reset SDIO card during card detection. CMD52 should be ignored by SD/eMMC cards, but we can also abort it in the driver immediately, since MMCIF doesn't support SDIO cards anyway. Signed-off-by: Teppei Kamijou Signed-off-by: Shinya Kuribayashi Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 741aeb95c7a4..8b4e98e2b130 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -911,6 +911,7 @@ static void sh_mmcif_request(struct mmc_host *mmc, struct mmc_request *mrq) if ((mrq->cmd->flags & MMC_CMD_MASK) != MMC_CMD_BCR) break; case MMC_APP_CMD: + case SD_IO_RW_DIRECT: host->state = STATE_IDLE; mrq->cmd->error = -ETIMEDOUT; mmc_request_done(mmc, mrq); -- cgit v1.2.3 From f8a8ced7f9e79916311c0ef08d1f6de7bf954807 Mon Sep 17 00:00:00 2001 From: Teppei Kamijou Date: Wed, 12 Dec 2012 15:38:06 +0100 Subject: mmc: sh_mmcif: ensure run-time suspend call is processed before suspend With this post-v2.6.35 change applied: commit a0a1a5fd4fb15ec61117c759fe9f5c16c53d9e9c Author: Tejun Heo Date: Tue Jun 29 10:07:12 2010 +0200 workqueue: reimplement workqueue freeze using max_active freeze_workqueues_begin() was introduced and workqueue now gets frozen before device drivers suspend operations. We have to ensure that run-time PM suspend operation completes before system-wide suspend is started. Signed-off-by: Teppei Kamijou Signed-off-by: Shinya Kuribayashi Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 8b4e98e2b130..2ff3e4774b67 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -982,7 +982,7 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) } } if (host->power) { - pm_runtime_put(&host->pd->dev); + pm_runtime_put_sync(&host->pd->dev); clk_disable(host->hclk); host->power = false; if (ios->power_mode == MMC_POWER_OFF) -- cgit v1.2.3 From 2cd5b3e061e3742de6b3a50f45f7d3b96aa50964 Mon Sep 17 00:00:00 2001 From: Shinya Kuribayashi Date: Mon, 14 Jan 2013 14:12:36 -0500 Subject: mmc: sh_mmcif: add support for bundled MMCIF IRQs On newer SoCs like R-Mobile U2, MMCIF interrupts are bundled. Signed-off-by: Shinya Kuribayashi Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 2ff3e4774b67..c7984bad8efc 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1307,10 +1307,11 @@ static int sh_mmcif_probe(struct platform_device *pdev) struct sh_mmcif_plat_data *pd = pdev->dev.platform_data; struct resource *res; void __iomem *reg; + const char *name; irq[0] = platform_get_irq(pdev, 0); irq[1] = platform_get_irq(pdev, 1); - if (irq[0] < 0 || irq[1] < 0) { + if (irq[0] < 0) { dev_err(&pdev->dev, "Get irq error\n"); return -ENXIO; } @@ -1375,15 +1376,19 @@ static int sh_mmcif_probe(struct platform_device *pdev) sh_mmcif_sync_reset(host); sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL); - ret = request_threaded_irq(irq[0], sh_mmcif_intr, sh_mmcif_irqt, 0, "sh_mmc:error", host); + name = irq[1] < 0 ? dev_name(&pdev->dev) : "sh_mmc:error"; + ret = request_threaded_irq(irq[0], sh_mmcif_intr, sh_mmcif_irqt, 0, name, host); if (ret) { - dev_err(&pdev->dev, "request_irq error (sh_mmc:error)\n"); + dev_err(&pdev->dev, "request_irq error (%s)\n", name); goto ereqirq0; } - ret = request_threaded_irq(irq[1], sh_mmcif_intr, sh_mmcif_irqt, 0, "sh_mmc:int", host); - if (ret) { - dev_err(&pdev->dev, "request_irq error (sh_mmc:int)\n"); - goto ereqirq1; + if (irq[1] >= 0) { + ret = request_threaded_irq(irq[1], sh_mmcif_intr, sh_mmcif_irqt, + 0, "sh_mmc:int", host); + if (ret) { + dev_err(&pdev->dev, "request_irq error (sh_mmc:int)\n"); + goto ereqirq1; + } } if (pd && pd->use_cd_gpio) { @@ -1406,7 +1411,8 @@ static int sh_mmcif_probe(struct platform_device *pdev) emmcaddh: erqcd: - free_irq(irq[1], host); + if (irq[1] >= 0) + free_irq(irq[1], host); ereqirq1: free_irq(irq[0], host); ereqirq0: @@ -1451,7 +1457,8 @@ static int sh_mmcif_remove(struct platform_device *pdev) irq[1] = platform_get_irq(pdev, 1); free_irq(irq[0], host); - free_irq(irq[1], host); + if (irq[1] >= 0) + free_irq(irq[1], host); platform_set_drvdata(pdev, NULL); -- cgit v1.2.3 From 555061f987787a966b01e62517b9befbd35e2f89 Mon Sep 17 00:00:00 2001 From: Teppei Kamijou Date: Wed, 12 Dec 2012 15:38:08 +0100 Subject: mmc: sh_mmcif: Add support for eMMC Dual Data Rate Some MMCIF implementations support the Dual Data Rate. With this patch, platforms can set the MMC_CAP_UHS_DDR50 capability flag in MMCIF platform data. This will let the MMC core to actually use the DDR mode. Signed-off-by: Teppei Kamijou Signed-off-by: Shinya Kuribayashi Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index c7984bad8efc..6d4328dce320 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -88,6 +88,7 @@ #define CMD_SET_TBIT (1 << 7) /* 1: tran mission bit "Low" */ #define CMD_SET_OPDM (1 << 6) /* 1: open/drain */ #define CMD_SET_CCSH (1 << 5) +#define CMD_SET_DARS (1 << 2) /* Dual Data Rate */ #define CMD_SET_DATW_1 ((0 << 1) | (0 << 0)) /* 1bit */ #define CMD_SET_DATW_4 ((0 << 1) | (1 << 0)) /* 4bit */ #define CMD_SET_DATW_8 ((1 << 1) | (0 << 0)) /* 8bit */ @@ -216,6 +217,7 @@ struct sh_mmcif_host { struct clk *hclk; unsigned int clk; int bus_width; + unsigned char timing; bool sd_error; bool dying; long timeout; @@ -781,6 +783,17 @@ static u32 sh_mmcif_set_cmd(struct sh_mmcif_host *host, dev_err(&host->pd->dev, "Unsupported bus width.\n"); break; } + switch (host->timing) { + case MMC_TIMING_UHS_DDR50: + /* + * MMC core will only set this timing, if the host + * advertises the MMC_CAP_UHS_DDR50 capability. MMCIF + * implementations with this capability, e.g. sh73a0, + * will have to set it in their platform data. + */ + tmp |= CMD_SET_DARS; + break; + } } /* DWEN */ if (opc == MMC_WRITE_BLOCK || opc == MMC_WRITE_MULTIPLE_BLOCK) @@ -1002,6 +1015,7 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) sh_mmcif_clock_control(host, ios->clock); } + host->timing = ios->timing; host->bus_width = ios->bus_width; host->state = STATE_IDLE; } -- cgit v1.2.3 From f9fd54f22e79046208ff1656afff7b5f9b7433cb Mon Sep 17 00:00:00 2001 From: Teppei Kamijou Date: Wed, 12 Dec 2012 15:38:09 +0100 Subject: mmc: sh_mmcif: Use msecs_to_jiffies() for host->timeout Timeout period should be properly normalized using msecs_to_jiffies(). Signed-off-by: Teppei Kamijou Signed-off-by: Shinya Kuribayashi Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 6d4328dce320..6d3644430877 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1348,7 +1348,7 @@ static int sh_mmcif_probe(struct platform_device *pdev) host = mmc_priv(mmc); host->mmc = mmc; host->addr = reg; - host->timeout = 1000; + host->timeout = msecs_to_jiffies(1000); host->pd = pdev; -- cgit v1.2.3 From a812ba0fd031c14c7680c94a1a22d5c60c7b6e2b Mon Sep 17 00:00:00 2001 From: Teppei Kamijou Date: Wed, 12 Dec 2012 15:38:10 +0100 Subject: mmc: sh_mmcif: Avoid unnecessary mmc_delay() at mmc_card_sleepawake() SH/R-Mobile MMCIF host controller can wait while the card signals busy. Set MMC_CAP_WAIT_WHILE_BUSY to inform an upper layer (core/mmc_ops.c) not to insert unnecessary mmc_delay(). Signed-off-by: Teppei Kamijou Signed-off-by: Shinya Kuribayashi Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 6d3644430877..663b92b364bb 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -758,6 +758,7 @@ static u32 sh_mmcif_set_cmd(struct sh_mmcif_host *host, } switch (opc) { /* RBSY */ + case MMC_SLEEP_AWAKE: case MMC_SWITCH: case MMC_STOP_TRANSMISSION: case MMC_SET_WRITE_PROT: @@ -851,6 +852,7 @@ static void sh_mmcif_start_cmd(struct sh_mmcif_host *host, switch (opc) { /* response busy check */ + case MMC_SLEEP_AWAKE: case MMC_SWITCH: case MMC_STOP_TRANSMISSION: case MMC_SET_WRITE_PROT: @@ -1357,7 +1359,7 @@ static int sh_mmcif_probe(struct platform_device *pdev) mmc->ops = &sh_mmcif_ops; sh_mmcif_init_ocr(host); - mmc->caps = MMC_CAP_MMC_HIGHSPEED; + mmc->caps = MMC_CAP_MMC_HIGHSPEED | MMC_CAP_WAIT_WHILE_BUSY; if (pd && pd->caps) mmc->caps |= pd->caps; mmc->max_segs = 32; -- cgit v1.2.3 From 5df460b15e10ffcf2c9a05d0c55b309568d330ea Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 12 Dec 2012 15:38:11 +0100 Subject: mmc: sh_mmcif: fix missing and consolidate IO completion timeouts Read block and write block operations are currently missing completion timeouts. Add missing timeouts and consolidate them at one location. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 663b92b364bb..f4b10c8f6384 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -564,7 +564,6 @@ static void sh_mmcif_single_read(struct sh_mmcif_host *host, BLOCK_SIZE_MASK) + 3; host->wait_for = MMCIF_WAIT_FOR_READ; - schedule_delayed_work(&host->timeout_work, host->timeout); /* buf read enable */ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFREN); @@ -606,7 +605,7 @@ static void sh_mmcif_multi_read(struct sh_mmcif_host *host, host->sg_idx = 0; host->sg_blkidx = 0; host->pio_ptr = sg_virt(data->sg); - schedule_delayed_work(&host->timeout_work, host->timeout); + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFREN); } @@ -629,7 +628,6 @@ static bool sh_mmcif_mread_block(struct sh_mmcif_host *host) if (!sh_mmcif_next_block(host, p)) return false; - schedule_delayed_work(&host->timeout_work, host->timeout); sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFREN); return true; @@ -642,7 +640,6 @@ static void sh_mmcif_single_write(struct sh_mmcif_host *host, BLOCK_SIZE_MASK) + 3; host->wait_for = MMCIF_WAIT_FOR_WRITE; - schedule_delayed_work(&host->timeout_work, host->timeout); /* buf write enable */ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN); @@ -684,7 +681,7 @@ static void sh_mmcif_multi_write(struct sh_mmcif_host *host, host->sg_idx = 0; host->sg_blkidx = 0; host->pio_ptr = sg_virt(data->sg); - schedule_delayed_work(&host->timeout_work, host->timeout); + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN); } @@ -707,7 +704,6 @@ static bool sh_mmcif_mwrite_block(struct sh_mmcif_host *host) if (!sh_mmcif_next_block(host, p)) return false; - schedule_delayed_work(&host->timeout_work, host->timeout); sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN); return true; @@ -900,7 +896,6 @@ static void sh_mmcif_stop_cmd(struct sh_mmcif_host *host, } host->wait_for = MMCIF_WAIT_FOR_STOP; - schedule_delayed_work(&host->timeout_work, host->timeout); } static void sh_mmcif_request(struct mmc_host *mmc, struct mmc_request *mrq) @@ -1121,6 +1116,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) { struct sh_mmcif_host *host = dev_id; struct mmc_request *mrq = host->mrq; + bool wait = false; cancel_delayed_work_sync(&host->timeout_work); @@ -1133,29 +1129,24 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) /* We're too late, the timeout has already kicked in */ return IRQ_HANDLED; case MMCIF_WAIT_FOR_CMD: - if (sh_mmcif_end_cmd(host)) - /* Wait for data */ - return IRQ_HANDLED; + /* Wait for data? */ + wait = sh_mmcif_end_cmd(host); break; case MMCIF_WAIT_FOR_MREAD: - if (sh_mmcif_mread_block(host)) - /* Wait for more data */ - return IRQ_HANDLED; + /* Wait for more data? */ + wait = sh_mmcif_mread_block(host); break; case MMCIF_WAIT_FOR_READ: - if (sh_mmcif_read_block(host)) - /* Wait for data end */ - return IRQ_HANDLED; + /* Wait for data end? */ + wait = sh_mmcif_read_block(host); break; case MMCIF_WAIT_FOR_MWRITE: - if (sh_mmcif_mwrite_block(host)) - /* Wait data to write */ - return IRQ_HANDLED; + /* Wait data to write? */ + wait = sh_mmcif_mwrite_block(host); break; case MMCIF_WAIT_FOR_WRITE: - if (sh_mmcif_write_block(host)) - /* Wait for data end */ - return IRQ_HANDLED; + /* Wait for data end? */ + wait = sh_mmcif_write_block(host); break; case MMCIF_WAIT_FOR_STOP: if (host->sd_error) { @@ -1174,6 +1165,12 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) BUG(); } + if (wait) { + schedule_delayed_work(&host->timeout_work, host->timeout); + /* Wait for more data */ + return IRQ_HANDLED; + } + if (host->wait_for != MMCIF_WAIT_FOR_STOP) { struct mmc_data *data = mrq->data; if (!mrq->cmd->error && data && !data->error) @@ -1182,8 +1179,10 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) if (mrq->stop && !mrq->cmd->error && (!data || !data->error)) { sh_mmcif_stop_cmd(host, mrq); - if (!mrq->stop->error) + if (!mrq->stop->error) { + schedule_delayed_work(&host->timeout_work, host->timeout); return IRQ_HANDLED; + } } } -- cgit v1.2.3 From eae309836509496c981ceaebdef57041de86ecd4 Mon Sep 17 00:00:00 2001 From: Teppei Kamijou Date: Wed, 12 Dec 2012 15:38:12 +0100 Subject: mmc: sh_mmcif: Terminate DMA transactions when detecting timeout or error If a DMA transaction fails, terminate all outstanding DMA transfers and unmap buffers. Signed-off-by: Teppei Kamijou Signed-off-by: Shinya Kuribayashi [g.liakhovetski@gmx.de: forward-port, add dma_unmap_sg() in error cases] Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index f4b10c8f6384..8aa7b0e6dec2 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -263,15 +263,6 @@ static void mmcif_dma_complete(void *arg) dev_name(&host->pd->dev))) return; - if (data->flags & MMC_DATA_READ) - dma_unmap_sg(host->chan_rx->device->dev, - data->sg, data->sg_len, - DMA_FROM_DEVICE); - else - dma_unmap_sg(host->chan_tx->device->dev, - data->sg, data->sg_len, - DMA_TO_DEVICE); - complete(&host->dma_complete); } @@ -1088,14 +1079,20 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) /* Running in the IRQ thread, can sleep */ time = wait_for_completion_interruptible_timeout(&host->dma_complete, host->timeout); + + if (data->flags & MMC_DATA_READ) + dma_unmap_sg(host->chan_rx->device->dev, + data->sg, data->sg_len, + DMA_FROM_DEVICE); + else + dma_unmap_sg(host->chan_tx->device->dev, + data->sg, data->sg_len, + DMA_TO_DEVICE); + if (host->sd_error) { dev_err(host->mmc->parent, "Error IRQ while waiting for DMA completion!\n"); /* Woken up by an error IRQ: abort DMA */ - if (data->flags & MMC_DATA_READ) - dmaengine_terminate_all(host->chan_rx); - else - dmaengine_terminate_all(host->chan_tx); data->error = sh_mmcif_error_manage(host); } else if (!time) { data->error = -ETIMEDOUT; @@ -1106,8 +1103,14 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) BUF_ACC_DMAREN | BUF_ACC_DMAWEN); host->dma_active = false; - if (data->error) + if (data->error) { data->bytes_xfered = 0; + /* Abort DMA */ + if (data->flags & MMC_DATA_READ) + dmaengine_terminate_all(host->chan_rx); + else + dmaengine_terminate_all(host->chan_tx); + } return false; } -- cgit v1.2.3 From 99eb9d8df996fc1695d4de687873875fbd8f6719 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 12 Dec 2012 15:38:13 +0100 Subject: mmc: sh_mmcif: (cosmetic) simplify boolean return blocks Use "return condition" instead of "if (condition) return true; return false" in functions, returning bool. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 8aa7b0e6dec2..de4b6d073599 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -542,10 +542,7 @@ static bool sh_mmcif_next_block(struct sh_mmcif_host *host, u32 *p) host->pio_ptr = p; } - if (host->sg_idx == data->sg_len) - return false; - - return true; + return host->sg_idx != data->sg_len; } static void sh_mmcif_single_read(struct sh_mmcif_host *host, @@ -1071,9 +1068,7 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) if (!host->dma_active) { data->error = sh_mmcif_data_trans(host, host->mrq, cmd->opcode); - if (!data->error) - return true; - return false; + return !data->error; } /* Running in the IRQ thread, can sleep */ -- cgit v1.2.3 From 8047310ee984b3efe932ee5b561f2523396466dd Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 12 Dec 2012 15:38:14 +0100 Subject: mmc: sh_mmcif: fix a race, causing an Oops on SMP Oopses have been observed on SMP in the sh-mmcif IRQ thread, when the two IRQ threads run simultaneously on two CPUs. Also take care to guard the timeout work and the DMA completion callback from possible NULL-pointer dereferences and races. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index de4b6d073599..3cfe383dc22b 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -196,6 +197,7 @@ enum mmcif_state { STATE_IDLE, STATE_REQUEST, STATE_IOS, + STATE_TIMEOUT, }; enum mmcif_wait_for { @@ -232,6 +234,7 @@ struct sh_mmcif_host { int sg_blkidx; bool power; bool card_present; + struct mutex thread_lock; /* DMA support */ struct dma_chan *chan_rx; @@ -255,11 +258,11 @@ static inline void sh_mmcif_bitclr(struct sh_mmcif_host *host, static void mmcif_dma_complete(void *arg) { struct sh_mmcif_host *host = arg; - struct mmc_data *data = host->mrq->data; + struct mmc_request *mrq = host->mrq; dev_dbg(&host->pd->dev, "Command completed\n"); - if (WARN(!data, "%s: NULL data in DMA completion!\n", + if (WARN(!mrq || !mrq->data, "%s: NULL data in DMA completion!\n", dev_name(&host->pd->dev))) return; @@ -1113,11 +1116,21 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) { struct sh_mmcif_host *host = dev_id; - struct mmc_request *mrq = host->mrq; + struct mmc_request *mrq; bool wait = false; cancel_delayed_work_sync(&host->timeout_work); + mutex_lock(&host->thread_lock); + + mrq = host->mrq; + if (!mrq) { + dev_dbg(&host->pd->dev, "IRQ thread state %u, wait %u: NULL mrq!\n", + host->state, host->wait_for); + mutex_unlock(&host->thread_lock); + return IRQ_HANDLED; + } + /* * All handlers return true, if processing continues, and false, if the * request has to be completed - successfully or not @@ -1125,6 +1138,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) switch (host->wait_for) { case MMCIF_WAIT_FOR_REQUEST: /* We're too late, the timeout has already kicked in */ + mutex_unlock(&host->thread_lock); return IRQ_HANDLED; case MMCIF_WAIT_FOR_CMD: /* Wait for data? */ @@ -1166,6 +1180,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) if (wait) { schedule_delayed_work(&host->timeout_work, host->timeout); /* Wait for more data */ + mutex_unlock(&host->thread_lock); return IRQ_HANDLED; } @@ -1179,6 +1194,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) sh_mmcif_stop_cmd(host, mrq); if (!mrq->stop->error) { schedule_delayed_work(&host->timeout_work, host->timeout); + mutex_unlock(&host->thread_lock); return IRQ_HANDLED; } } @@ -1189,6 +1205,8 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) host->mrq = NULL; mmc_request_done(host->mmc, mrq); + mutex_unlock(&host->thread_lock); + return IRQ_HANDLED; } @@ -1262,11 +1280,24 @@ static void mmcif_timeout_work(struct work_struct *work) struct delayed_work *d = container_of(work, struct delayed_work, work); struct sh_mmcif_host *host = container_of(d, struct sh_mmcif_host, timeout_work); struct mmc_request *mrq = host->mrq; + unsigned long flags; if (host->dying) /* Don't run after mmc_remove_host() */ return; + dev_dbg(&host->pd->dev, "Timeout waiting for %u, opcode %u\n", + host->wait_for, mrq->cmd->opcode); + + spin_lock_irqsave(&host->lock, flags); + if (host->state == STATE_IDLE) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + host->state = STATE_TIMEOUT; + spin_unlock_irqrestore(&host->lock, flags); + /* * Handle races with cancel_delayed_work(), unless * cancel_delayed_work_sync() is used @@ -1410,6 +1441,8 @@ static int sh_mmcif_probe(struct platform_device *pdev) goto erqcd; } + mutex_init(&host->thread_lock); + clk_disable(host->hclk); ret = mmc_add_host(mmc); if (ret < 0) -- cgit v1.2.3 From aba9d646785c8b1decb1a15ee157b0179a15bef9 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 12 Dec 2012 15:38:15 +0100 Subject: mmc: sh_mmcif: reset error code for any opcode If a command execution has produced an error, it has to be reset as a part of the error handling. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 3cfe383dc22b..14fafafc12d3 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1041,7 +1041,6 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) case MMC_SELECT_CARD: case MMC_APP_CMD: cmd->error = -ETIMEDOUT; - host->sd_error = false; break; default: cmd->error = sh_mmcif_error_manage(host); @@ -1049,6 +1048,7 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) cmd->opcode, cmd->error); break; } + host->sd_error = false; return false; } if (!(cmd->flags & MMC_RSP_PRESENT)) { -- cgit v1.2.3 From 90f1cb438e33bb88036649665c2165155561b54f Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 12 Dec 2012 15:38:16 +0100 Subject: mmc: sh_mmcif: reset DMA completion immediately before starting DMA DMA completion can be signalled from the DMA callback and from the error handler. If both are called, the completion struct can enter an inconsistent state. To prevent this move completion initialisation immediately before activating DMA. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 14fafafc12d3..1c37854c0f33 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -419,8 +419,6 @@ static void sh_mmcif_request_dma(struct sh_mmcif_host *host, if (ret < 0) goto ecfgrx; - init_completion(&host->dma_complete); - return; ecfgrx: @@ -1061,6 +1059,12 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) if (!data) return false; + /* + * Completion can be signalled from DMA callback and error, so, have to + * reset here, before setting .dma_active + */ + init_completion(&host->dma_complete); + if (data->flags & MMC_DATA_READ) { if (host->chan_rx) sh_mmcif_start_dma_rx(host); -- cgit v1.2.3 From 276bc96b2abfa2a6bb219aa4eda13c4c331c53eb Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 12 Dec 2012 15:38:17 +0100 Subject: mmc: sh_mmcif: fix I/O errors The INT_BUFWEN IRQ often arrives with other bits set too. If they are not cleared, an additional IRQ can be triggered, sometimes also after the MMC request has already been completed. This leads to block I/O errors. Earlier Teppei Kamijou also observed these additional interrupts and proposed to explicitly wait for them. This patch chooses an alternative approach of clearing all active bits immediately, when processing the main interrupt. Reported-by: Teppei Kamijou Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 1c37854c0f33..e6a6d2363a4d 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1238,7 +1238,9 @@ static irqreturn_t sh_mmcif_intr(int irq, void *dev_id) sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_BUFREN); sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MBUFREN); } else if (state & INT_BUFWEN) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_BUFWEN); + sh_mmcif_writel(host->addr, MMCIF_CE_INT, + ~(INT_BUFWEN | INT_DTRANE | INT_CMD12DRE | + INT_CMD12RBE | INT_CMD12CRE)); sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN); } else if (state & INT_CMD12DRE) { sh_mmcif_writel(host->addr, MMCIF_CE_INT, -- cgit v1.2.3 From e475b2702f612901772d12110614ad8c3fb5d89b Mon Sep 17 00:00:00 2001 From: Teppei Kamijou Date: Wed, 12 Dec 2012 15:38:18 +0100 Subject: mmc: sh_mmcif: report all errors Make error reporting in the driver more verbose. This patch is based on an earlier work by Teppei Kamijou, but we try to not add any new error messages to the log in the normal case to avoid confusing the user, and also add a few more dev_dbg() calls. Signed-off-by: Teppei Kamijou Signed-off-by: Shinya Kuribayashi [g.liakhovetski@gmx.de: avoid producing new errors in normal case] Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index e6a6d2363a4d..b4180c7bffc4 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -514,13 +514,16 @@ static int sh_mmcif_error_manage(struct sh_mmcif_host *host) } if (state2 & STS2_CRC_ERR) { - dev_dbg(&host->pd->dev, ": CRC error\n"); + dev_err(&host->pd->dev, " CRC error: state %u, wait %u\n", + host->state, host->wait_for); ret = -EIO; } else if (state2 & STS2_TIMEOUT_ERR) { - dev_dbg(&host->pd->dev, ": Timeout\n"); + dev_err(&host->pd->dev, " Timeout: state %u, wait %u\n", + host->state, host->wait_for); ret = -ETIMEDOUT; } else { - dev_dbg(&host->pd->dev, ": End/Index error\n"); + dev_dbg(&host->pd->dev, " End/Index error: state %u, wait %u\n", + host->state, host->wait_for); ret = -EIO; } return ret; @@ -566,6 +569,7 @@ static bool sh_mmcif_read_block(struct sh_mmcif_host *host) if (host->sd_error) { data->error = sh_mmcif_error_manage(host); + dev_dbg(&host->pd->dev, "%s(): %d\n", __func__, data->error); return false; } @@ -606,6 +610,7 @@ static bool sh_mmcif_mread_block(struct sh_mmcif_host *host) if (host->sd_error) { data->error = sh_mmcif_error_manage(host); + dev_dbg(&host->pd->dev, "%s(): %d\n", __func__, data->error); return false; } @@ -642,6 +647,7 @@ static bool sh_mmcif_write_block(struct sh_mmcif_host *host) if (host->sd_error) { data->error = sh_mmcif_error_manage(host); + dev_dbg(&host->pd->dev, "%s(): %d\n", __func__, data->error); return false; } @@ -682,6 +688,7 @@ static bool sh_mmcif_mwrite_block(struct sh_mmcif_host *host) if (host->sd_error) { data->error = sh_mmcif_error_manage(host); + dev_dbg(&host->pd->dev, "%s(): %d\n", __func__, data->error); return false; } @@ -823,7 +830,7 @@ static int sh_mmcif_data_trans(struct sh_mmcif_host *host, sh_mmcif_single_read(host, mrq); return 0; default: - dev_err(&host->pd->dev, "UNSUPPORTED CMD = d'%08d\n", opc); + dev_err(&host->pd->dev, "Unsupported CMD%d\n", opc); return -EINVAL; } } @@ -894,6 +901,7 @@ static void sh_mmcif_request(struct mmc_host *mmc, struct mmc_request *mrq) spin_lock_irqsave(&host->lock, flags); if (host->state != STATE_IDLE) { + dev_dbg(&host->pd->dev, "%s() rejected, state %u\n", __func__, host->state); spin_unlock_irqrestore(&host->lock, flags); mrq->cmd->error = -EAGAIN; mmc_request_done(mmc, mrq); @@ -957,6 +965,7 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) spin_lock_irqsave(&host->lock, flags); if (host->state != STATE_IDLE) { + dev_dbg(&host->pd->dev, "%s() rejected, state %u\n", __func__, host->state); spin_unlock_irqrestore(&host->lock, flags); return; } @@ -1042,10 +1051,10 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) break; default: cmd->error = sh_mmcif_error_manage(host); - dev_dbg(&host->pd->dev, "Cmd(d'%d) error %d\n", - cmd->opcode, cmd->error); break; } + dev_dbg(&host->pd->dev, "CMD%d error %d\n", + cmd->opcode, cmd->error); host->sd_error = false; return false; } @@ -1097,8 +1106,11 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) /* Woken up by an error IRQ: abort DMA */ data->error = sh_mmcif_error_manage(host); } else if (!time) { + dev_err(host->mmc->parent, "DMA timeout!\n"); data->error = -ETIMEDOUT; } else if (time < 0) { + dev_err(host->mmc->parent, + "wait_for_completion_...() error %ld!\n", time); data->error = time; } sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC, @@ -1167,6 +1179,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) case MMCIF_WAIT_FOR_STOP: if (host->sd_error) { mrq->stop->error = sh_mmcif_error_manage(host); + dev_dbg(&host->pd->dev, "%s(): %d\n", __func__, mrq->stop->error); break; } sh_mmcif_get_cmd12response(host, mrq->stop); @@ -1174,8 +1187,10 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) break; case MMCIF_WAIT_FOR_READ_END: case MMCIF_WAIT_FOR_WRITE_END: - if (host->sd_error) + if (host->sd_error) { mrq->data->error = sh_mmcif_error_manage(host); + dev_dbg(&host->pd->dev, "%s(): %d\n", __func__, mrq->data->error); + } break; default: BUG(); @@ -1292,7 +1307,7 @@ static void mmcif_timeout_work(struct work_struct *work) /* Don't run after mmc_remove_host() */ return; - dev_dbg(&host->pd->dev, "Timeout waiting for %u, opcode %u\n", + dev_err(&host->pd->dev, "Timeout waiting for %u on CMD%u\n", host->wait_for, mrq->cmd->opcode); spin_lock_irqsave(&host->lock, flags); -- cgit v1.2.3 From 8af5075088a0aaa64caed5ed212b485b5760bf0b Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 12 Dec 2012 15:45:14 +0100 Subject: mmc: sh_mmcif: simplify IRQ processing The classical way to process IRQs is read out the status, ack all triggered IRQs, possibly mask them, then process them. Follow this simple procesure instead of the current complex custom algorithm. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 65 +++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 46 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index b4180c7bffc4..0189efcb9e12 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -129,6 +129,10 @@ INT_CCSTO | INT_CRCSTO | INT_WDATTO | \ INT_RDATTO | INT_RBSYTO | INT_RSPTO) +#define INT_ALL (INT_RBSYE | INT_CRSPE | INT_BUFREN | \ + INT_BUFWEN | INT_CMD12DRE | INT_BUFRE | \ + INT_DTRANE | INT_CMD12RBE | INT_CMD12CRE) + /* CE_INT_MASK */ #define MASK_ALL 0x00000000 #define MASK_MCCSDE (1 << 29) @@ -160,6 +164,11 @@ MASK_MCCSTO | MASK_MCRCSTO | MASK_MWDATTO | \ MASK_MRDATTO | MASK_MRBSYTO | MASK_MRSPTO) +#define MASK_CLEAN (INT_ERR_STS | MASK_MRBSYE | MASK_MCRSPE | \ + MASK_MBUFREN | MASK_MBUFWEN | \ + MASK_MCMD12DRE | MASK_MBUFRE | MASK_MDTRANE | \ + MASK_MCMD12RBE | MASK_MCMD12CRE) + /* CE_HOST_STS1 */ #define STS1_CMDSEQ (1 << 31) @@ -1233,58 +1242,22 @@ static irqreturn_t sh_mmcif_intr(int irq, void *dev_id) { struct sh_mmcif_host *host = dev_id; u32 state; - int err = 0; state = sh_mmcif_readl(host->addr, MMCIF_CE_INT); + sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~state); + sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state & MASK_CLEAN); - if (state & INT_ERR_STS) { - /* error interrupts - process first */ - sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~state); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state); - err = 1; - } else if (state & INT_RBSYE) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, - ~(INT_RBSYE | INT_CRSPE)); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MRBSYE); - } else if (state & INT_CRSPE) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_CRSPE); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MCRSPE); - } else if (state & INT_BUFREN) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_BUFREN); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MBUFREN); - } else if (state & INT_BUFWEN) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, - ~(INT_BUFWEN | INT_DTRANE | INT_CMD12DRE | - INT_CMD12RBE | INT_CMD12CRE)); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN); - } else if (state & INT_CMD12DRE) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, - ~(INT_CMD12DRE | INT_CMD12RBE | - INT_CMD12CRE | INT_BUFRE)); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MCMD12DRE); - } else if (state & INT_BUFRE) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_BUFRE); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MBUFRE); - } else if (state & INT_DTRANE) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, - ~(INT_CMD12DRE | INT_CMD12RBE | - INT_CMD12CRE | INT_DTRANE)); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MDTRANE); - } else if (state & INT_CMD12RBE) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, - ~(INT_CMD12RBE | INT_CMD12CRE)); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MCMD12RBE); - } else { - dev_dbg(&host->pd->dev, "Unsupported interrupt: 0x%x\n", state); - sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~state); - sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state); - err = 1; - } - if (err) { + if (state & ~MASK_CLEAN) + dev_dbg(&host->pd->dev, "IRQ state = 0x%08x incompletely cleared\n", + state); + + if (state & INT_ERR_STS || state & ~INT_ALL) { host->sd_error = true; - dev_dbg(&host->pd->dev, "int err state = %08x\n", state); + dev_dbg(&host->pd->dev, "int err state = 0x%08x\n", state); } if (state & ~(INT_CMD12RBE | INT_CMD12CRE)) { + if (!host->mrq) + dev_dbg(&host->pd->dev, "NULL IRQ state = 0x%08x\n", state); if (!host->dma_active) return IRQ_WAKE_THREAD; else if (host->sd_error) -- cgit v1.2.3 From 369d321ed1baa7748e770aaaae4d8effad699633 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Wed, 26 Dec 2012 10:40:17 +0900 Subject: mmc: queue: exclude asynchronous transfer for special request Unlike normal r/w request, special requests(discard, flush) is finished with a one-time issue_fn. Request change to mqrq_prev makes unnecessary call. Signed-off-by: Seungwon Jeon Reviewed-by: Konstantin Dorfman Signed-off-by: Chris Ball --- drivers/mmc/card/queue.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index fadf52eb5d70..d630d9861e7b 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -24,6 +24,8 @@ #define MMC_QUEUE_SUSPENDED (1 << 0) +#define MMC_REQ_SPECIAL_MASK (REQ_DISCARD | REQ_FLUSH) + /* * Prepare a MMC request. This just filters out odd stuff. */ @@ -58,6 +60,7 @@ static int mmc_queue_thread(void *d) do { struct request *req = NULL; struct mmc_queue_req *tmp; + unsigned int cmd_flags = 0; spin_lock_irq(q->queue_lock); set_current_state(TASK_INTERRUPTIBLE); @@ -67,12 +70,19 @@ static int mmc_queue_thread(void *d) if (req || mq->mqrq_prev->req) { set_current_state(TASK_RUNNING); + cmd_flags = req ? req->cmd_flags : 0; mq->issue_fn(mq, req); /* * Current request becomes previous request * and vice versa. + * In case of special requests, current request + * has been finished. Do not assign it to previous + * request. */ + if (cmd_flags & MMC_REQ_SPECIAL_MASK) + mq->mqrq_cur->req = NULL; + mq->mqrq_prev->brq.mrq.data = NULL; mq->mqrq_prev->req = NULL; tmp = mq->mqrq_prev; -- cgit v1.2.3 From 2220eedfd7aea69008173a224975e10284fbe854 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Mon, 14 Jan 2013 14:28:17 -0500 Subject: mmc: fix async request mechanism for sequential read scenarios When current request is running on the bus and if next request fetched by mmcqd is NULL, mmc context (mmcqd thread) gets blocked until the current request completes. This means that if new request comes in while the mmcqd thread is blocked, this new request can not be prepared in parallel to current ongoing request. This may result in delaying the new request execution and increase it's latency. This change allows to wake up the MMC thread on new request arrival. Now once the MMC thread is woken up, a new request can be fetched and prepared in parallel to the current running request which means this new request can be started immediately after the current running request completes. With this change read throughput is improved by 16%. Signed-off-by: Konstantin Dorfman Reviewed-by: Seungwon Jeon Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 30 +++++++----- drivers/mmc/card/queue.c | 22 ++++++++- drivers/mmc/card/queue.h | 3 ++ drivers/mmc/core/bus.c | 1 + drivers/mmc/core/core.c | 121 +++++++++++++++++++++++++++++++++++++++++++++-- drivers/mmc/core/core.h | 1 + include/linux/mmc/card.h | 12 +++++ include/linux/mmc/core.h | 3 +- include/linux/mmc/host.h | 17 +++++++ 9 files changed, 191 insertions(+), 19 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 21056b9ef0a0..f79b4688e471 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -113,17 +113,6 @@ struct mmc_blk_data { static DEFINE_MUTEX(open_lock); -enum mmc_blk_status { - MMC_BLK_SUCCESS = 0, - MMC_BLK_PARTIAL, - MMC_BLK_CMD_ERR, - MMC_BLK_RETRY, - MMC_BLK_ABORT, - MMC_BLK_DATA_ERR, - MMC_BLK_ECC_ERR, - MMC_BLK_NOMEDIUM, -}; - module_param(perdev_minors, int, 0444); MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device"); @@ -1364,8 +1353,11 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) } else areq = NULL; areq = mmc_start_req(card->host, areq, (int *) &status); - if (!areq) + if (!areq) { + if (status == MMC_BLK_NEW_REQUEST) + mq->flags |= MMC_QUEUE_NEW_REQUEST; return 0; + } mq_rq = container_of(areq, struct mmc_queue_req, mmc_active); brq = &mq_rq->brq; @@ -1438,6 +1430,10 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) break; case MMC_BLK_NOMEDIUM: goto cmd_abort; + default: + pr_err("%s: Unhandled return value (%d)", + req->rq_disk->disk_name, status); + goto cmd_abort; } if (ret) { @@ -1472,6 +1468,8 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) int ret; struct mmc_blk_data *md = mq->data; struct mmc_card *card = md->queue.card; + struct mmc_host *host = card->host; + unsigned long flags; if (req && !mq->mqrq_prev->req) /* claim host only for the first request */ @@ -1486,6 +1484,7 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) goto out; } + mq->flags &= ~MMC_QUEUE_NEW_REQUEST; if (req && req->cmd_flags & REQ_DISCARD) { /* complete ongoing async transfer before issuing discard */ if (card->host->areq) @@ -1501,11 +1500,16 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) mmc_blk_issue_rw_rq(mq, NULL); ret = mmc_blk_issue_flush(mq, req); } else { + if (!req && host->areq) { + spin_lock_irqsave(&host->context_info.lock, flags); + host->context_info.is_waiting_last_req = true; + spin_unlock_irqrestore(&host->context_info.lock, flags); + } ret = mmc_blk_issue_rw_rq(mq, req); } out: - if (!req) + if (!req && !(mq->flags & MMC_QUEUE_NEW_REQUEST)) /* release host only when there are no more requests */ mmc_release_host(card->host); return ret; diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index d630d9861e7b..5e0971016ac5 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -22,7 +22,6 @@ #define MMC_QUEUE_BOUNCESZ 65536 -#define MMC_QUEUE_SUSPENDED (1 << 0) #define MMC_REQ_SPECIAL_MASK (REQ_DISCARD | REQ_FLUSH) @@ -72,6 +71,10 @@ static int mmc_queue_thread(void *d) set_current_state(TASK_RUNNING); cmd_flags = req ? req->cmd_flags : 0; mq->issue_fn(mq, req); + if (mq->flags & MMC_QUEUE_NEW_REQUEST) { + mq->flags &= ~MMC_QUEUE_NEW_REQUEST; + continue; /* fetch again */ + } /* * Current request becomes previous request @@ -113,6 +116,8 @@ static void mmc_request_fn(struct request_queue *q) { struct mmc_queue *mq = q->queuedata; struct request *req; + unsigned long flags; + struct mmc_context_info *cntx; if (!mq) { while ((req = blk_fetch_request(q)) != NULL) { @@ -122,7 +127,20 @@ static void mmc_request_fn(struct request_queue *q) return; } - if (!mq->mqrq_cur->req && !mq->mqrq_prev->req) + cntx = &mq->card->host->context_info; + if (!mq->mqrq_cur->req && mq->mqrq_prev->req) { + /* + * New MMC request arrived when MMC thread may be + * blocked on the previous request to be complete + * with no current request fetched + */ + spin_lock_irqsave(&cntx->lock, flags); + if (cntx->is_waiting_last_req) { + cntx->is_new_req = true; + wake_up_interruptible(&cntx->wait); + } + spin_unlock_irqrestore(&cntx->lock, flags); + } else if (!mq->mqrq_cur->req && !mq->mqrq_prev->req) wake_up_process(mq->thread); } diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index d2a1eb4b9f9f..e20c27b2b8b4 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -27,6 +27,9 @@ struct mmc_queue { struct task_struct *thread; struct semaphore thread_sem; unsigned int flags; +#define MMC_QUEUE_SUSPENDED (1 << 0) +#define MMC_QUEUE_NEW_REQUEST (1 << 1) + int (*issue_fn)(struct mmc_queue *, struct request *); void *data; struct request_queue *queue; diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 420cb6753c1e..e219c97a02a4 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -321,6 +321,7 @@ int mmc_add_card(struct mmc_card *card) #ifdef CONFIG_DEBUG_FS mmc_add_card_debugfs(card); #endif + mmc_init_context_info(card->host); ret = device_add(&card->dev); if (ret) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index aaed7687cf09..8b3a1222e665 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -319,11 +319,44 @@ out: } EXPORT_SYMBOL(mmc_start_bkops); +/* + * mmc_wait_data_done() - done callback for data request + * @mrq: done data request + * + * Wakes up mmc context, passed as a callback to host controller driver + */ +static void mmc_wait_data_done(struct mmc_request *mrq) +{ + mrq->host->context_info.is_done_rcv = true; + wake_up_interruptible(&mrq->host->context_info.wait); +} + static void mmc_wait_done(struct mmc_request *mrq) { complete(&mrq->completion); } +/* + *__mmc_start_data_req() - starts data request + * @host: MMC host to start the request + * @mrq: data request to start + * + * Sets the done callback to be called when request is completed by the card. + * Starts data mmc request execution + */ +static int __mmc_start_data_req(struct mmc_host *host, struct mmc_request *mrq) +{ + mrq->done = mmc_wait_data_done; + mrq->host = host; + if (mmc_card_removed(host->card)) { + mrq->cmd->error = -ENOMEDIUM; + return -ENOMEDIUM; + } + mmc_start_request(host, mrq); + + return 0; +} + static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) { init_completion(&mrq->completion); @@ -337,6 +370,62 @@ static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) return 0; } +/* + * mmc_wait_for_data_req_done() - wait for request completed + * @host: MMC host to prepare the command. + * @mrq: MMC request to wait for + * + * Blocks MMC context till host controller will ack end of data request + * execution or new request notification arrives from the block layer. + * Handles command retries. + * + * Returns enum mmc_blk_status after checking errors. + */ +static int mmc_wait_for_data_req_done(struct mmc_host *host, + struct mmc_request *mrq, + struct mmc_async_req *next_req) +{ + struct mmc_command *cmd; + struct mmc_context_info *context_info = &host->context_info; + int err; + unsigned long flags; + + while (1) { + wait_event_interruptible(context_info->wait, + (context_info->is_done_rcv || + context_info->is_new_req)); + spin_lock_irqsave(&context_info->lock, flags); + context_info->is_waiting_last_req = false; + spin_unlock_irqrestore(&context_info->lock, flags); + if (context_info->is_done_rcv) { + context_info->is_done_rcv = false; + context_info->is_new_req = false; + cmd = mrq->cmd; + if (!cmd->error || !cmd->retries || + mmc_card_removed(host->card)) { + err = host->areq->err_check(host->card, + host->areq); + break; /* return err */ + } else { + pr_info("%s: req failed (CMD%u): %d, retrying...\n", + mmc_hostname(host), + cmd->opcode, cmd->error); + cmd->retries--; + cmd->error = 0; + host->ops->request(host, mrq); + continue; /* wait for done/new event again */ + } + } else if (context_info->is_new_req) { + context_info->is_new_req = false; + if (!next_req) { + err = MMC_BLK_NEW_REQUEST; + break; /* return err */ + } + } + } + return err; +} + static void mmc_wait_for_req_done(struct mmc_host *host, struct mmc_request *mrq) { @@ -426,8 +515,17 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, mmc_pre_req(host, areq->mrq, !host->areq); if (host->areq) { - mmc_wait_for_req_done(host, host->areq->mrq); - err = host->areq->err_check(host->card, host->areq); + err = mmc_wait_for_data_req_done(host, host->areq->mrq, + areq); + if (err == MMC_BLK_NEW_REQUEST) { + if (error) + *error = err; + /* + * The previous request was not completed, + * nothing to return + */ + return NULL; + } /* * Check BKOPS urgency for each R1 response */ @@ -439,7 +537,7 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, } if (!err && areq) - start_err = __mmc_start_req(host, areq->mrq); + start_err = __mmc_start_data_req(host, areq->mrq); if (host->areq) mmc_post_req(host, host->areq->mrq, 0); @@ -2581,6 +2679,23 @@ int mmc_pm_notify(struct notifier_block *notify_block, } #endif +/** + * mmc_init_context_info() - init synchronization context + * @host: mmc host + * + * Init struct context_info needed to implement asynchronous + * request mechanism, used by mmc core, host driver and mmc requests + * supplier. + */ +void mmc_init_context_info(struct mmc_host *host) +{ + spin_lock_init(&host->context_info.lock); + host->context_info.is_new_req = false; + host->context_info.is_done_rcv = false; + host->context_info.is_waiting_last_req = false; + init_waitqueue_head(&host->context_info.wait); +} + static int __init mmc_init(void) { int ret; diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 3bdafbca354f..0272b3284b5e 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -76,5 +76,6 @@ void mmc_remove_host_debugfs(struct mmc_host *host); void mmc_add_card_debugfs(struct mmc_card *card); void mmc_remove_card_debugfs(struct mmc_card *card); +void mmc_init_context_info(struct mmc_host *host); #endif diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 5c69315d60cc..be2500a49925 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -187,6 +187,18 @@ struct sdio_func_tuple; #define SDIO_MAX_FUNCS 7 +enum mmc_blk_status { + MMC_BLK_SUCCESS = 0, + MMC_BLK_PARTIAL, + MMC_BLK_CMD_ERR, + MMC_BLK_RETRY, + MMC_BLK_ABORT, + MMC_BLK_DATA_ERR, + MMC_BLK_ECC_ERR, + MMC_BLK_NOMEDIUM, + MMC_BLK_NEW_REQUEST, +}; + /* The number of MMC physical partitions. These consist of: * boot partitions (2), general purpose partitions (4) in MMC v4.4. */ diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 5bf7c2274fcb..495d1336149c 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -120,6 +120,7 @@ struct mmc_data { s32 host_cookie; /* host private data */ }; +struct mmc_host; struct mmc_request { struct mmc_command *sbc; /* SET_BLOCK_COUNT for multiblock */ struct mmc_command *cmd; @@ -128,9 +129,9 @@ struct mmc_request { struct completion completion; void (*done)(struct mmc_request *);/* completion function */ + struct mmc_host *host; }; -struct mmc_host; struct mmc_card; struct mmc_async_req; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index c89a1bb87fa5..523d570f58ad 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -170,6 +170,22 @@ struct mmc_slot { void *handler_priv; }; +/** + * mmc_context_info - synchronization details for mmc context + * @is_done_rcv wake up reason was done request + * @is_new_req wake up reason was new request + * @is_waiting_last_req mmc context waiting for single running request + * @wait wait queue + * @lock lock to protect data fields + */ +struct mmc_context_info { + bool is_done_rcv; + bool is_new_req; + bool is_waiting_last_req; + wait_queue_head_t wait; + spinlock_t lock; +}; + struct regulator; struct mmc_supply { @@ -331,6 +347,7 @@ struct mmc_host { struct dentry *debugfs_root; struct mmc_async_req *areq; /* active async req */ + struct mmc_context_info context_info; /* async synchronization info */ #ifdef CONFIG_FAIL_MMC_REQUEST struct fault_attr fail_mmc_request; -- cgit v1.2.3 From 1a94715d4db7b71f825cd5585bc8d653eae1faf4 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Wed, 2 Jan 2013 22:34:49 -0700 Subject: mmc: add BCM2835 driver Add a very simple driver for the BCM2835 SoC, which is used in the Raspberry Pi board. Signed-off-by: Stephen Warren Signed-off-by: Chris Ball --- .../devicetree/bindings/mmc/brcm,bcm2835-sdhci.txt | 18 ++ drivers/mmc/host/Kconfig | 11 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/sdhci-bcm2835.c | 227 +++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/brcm,bcm2835-sdhci.txt create mode 100644 drivers/mmc/host/sdhci-bcm2835.c diff --git a/Documentation/devicetree/bindings/mmc/brcm,bcm2835-sdhci.txt b/Documentation/devicetree/bindings/mmc/brcm,bcm2835-sdhci.txt new file mode 100644 index 000000000000..59476fbdbfa1 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/brcm,bcm2835-sdhci.txt @@ -0,0 +1,18 @@ +Broadcom BCM2835 SDHCI controller + +This file documents differences between the core properties described +by mmc.txt and the properties that represent the BCM2835 controller. + +Required properties: +- compatible : Should be "brcm,bcm2835-sdhci". +- clocks : The clock feeding the SDHCI controller. + +Example: + +sdhci: sdhci { + compatible = "brcm,bcm2835-sdhci"; + reg = <0x7e300000 0x100>; + interrupts = <2 30>; + clocks = <&clk_mmc>; + bus-width = <4>; +}; diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 8d13c6594520..66a54aa68e25 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -241,6 +241,17 @@ config MMC_SDHCI_S3C_DMA YMMV. +config MMC_SDHCI_BCM2835 + tristate "SDHCI platform support for the BCM2835 SD/MMC Controller" + depends on ARCH_BCM2835 + depends on MMC_SDHCI_PLTFM + select MMC_SDHCI_IO_ACCESSORS + help + This selects the BCM2835 SD/MMC controller. If you have a BCM2835 + platform with SD or MMC devices, say Y or M here. + + If unsure, say N. + config MMC_OMAP tristate "TI OMAP Multimedia Card Interface support" depends on ARCH_OMAP diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index e4e218c930bd..d5ea072207ec 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o obj-$(CONFIG_MMC_SDHCI_TEGRA) += sdhci-tegra.o obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o +obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o ifeq ($(CONFIG_CB710_DEBUG),y) CFLAGS-cb710-mmc += -DDEBUG diff --git a/drivers/mmc/host/sdhci-bcm2835.c b/drivers/mmc/host/sdhci-bcm2835.c new file mode 100644 index 000000000000..453825fcc5cf --- /dev/null +++ b/drivers/mmc/host/sdhci-bcm2835.c @@ -0,0 +1,227 @@ +/* + * BCM2835 SDHCI + * Copyright (C) 2012 Stephen Warren + * Based on U-Boot's MMC driver for the BCM2835 by Oleksandr Tymoshenko & me + * Portions of the code there were obviously based on the Linux kernel at: + * git://github.com/raspberrypi/linux.git rpi-3.6.y + * commit f5b930b "Main bcm2708 linux port" signed-off-by Dom Cobley. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include "sdhci-pltfm.h" + +/* + * 400KHz is max freq for card ID etc. Use that as min card clock. We need to + * know the min to enable static calculation of max BCM2835_SDHCI_WRITE_DELAY. + */ +#define MIN_FREQ 400000 + +/* + * The Arasan has a bugette whereby it may lose the content of successive + * writes to registers that are within two SD-card clock cycles of each other + * (a clock domain crossing problem). It seems, however, that the data + * register does not have this problem, which is just as well - otherwise we'd + * have to nobble the DMA engine too. + * + * This should probably be dynamically calculated based on the actual card + * frequency. However, this is the longest we'll have to wait, and doesn't + * seem to slow access down too much, so the added complexity doesn't seem + * worth it for now. + * + * 1/MIN_FREQ is (max) time per tick of eMMC clock. + * 2/MIN_FREQ is time for two ticks. + * Multiply by 1000000 to get uS per two ticks. + * *1000000 for uSecs. + * +1 for hack rounding. + */ +#define BCM2835_SDHCI_WRITE_DELAY (((2 * 1000000) / MIN_FREQ) + 1) + +struct bcm2835_sdhci { + struct clk *clk; + u32 shadow; +}; + +static void bcm2835_sdhci_writel(struct sdhci_host *host, u32 val, int reg) +{ + writel(val, host->ioaddr + reg); + + udelay(BCM2835_SDHCI_WRITE_DELAY); +} + +static inline u32 bcm2835_sdhci_readl(struct sdhci_host *host, int reg) +{ + u32 val = readl(host->ioaddr + reg); + + if (reg == SDHCI_CAPABILITIES) + val |= SDHCI_CAN_VDD_330; + + return val; +} + +static void bcm2835_sdhci_writew(struct sdhci_host *host, u16 val, int reg) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct bcm2835_sdhci *bcm2835_host = pltfm_host->priv; + u32 oldval = (reg == SDHCI_COMMAND) ? bcm2835_host->shadow : + bcm2835_sdhci_readl(host, reg & ~3); + u32 word_num = (reg >> 1) & 1; + u32 word_shift = word_num * 16; + u32 mask = 0xffff << word_shift; + u32 newval = (oldval & ~mask) | (val << word_shift); + + if (reg == SDHCI_TRANSFER_MODE) + bcm2835_host->shadow = newval; + else + bcm2835_sdhci_writel(host, newval, reg & ~3); +} + +static u16 bcm2835_sdhci_readw(struct sdhci_host *host, int reg) +{ + u32 val = bcm2835_sdhci_readl(host, (reg & ~3)); + u32 word_num = (reg >> 1) & 1; + u32 word_shift = word_num * 16; + u32 word = (val >> word_shift) & 0xffff; + + return word; +} + +static void bcm2835_sdhci_writeb(struct sdhci_host *host, u8 val, int reg) +{ + u32 oldval = bcm2835_sdhci_readl(host, reg & ~3); + u32 byte_num = reg & 3; + u32 byte_shift = byte_num * 8; + u32 mask = 0xff << byte_shift; + u32 newval = (oldval & ~mask) | (val << byte_shift); + + bcm2835_sdhci_writel(host, newval, reg & ~3); +} + +static u8 bcm2835_sdhci_readb(struct sdhci_host *host, int reg) +{ + u32 val = bcm2835_sdhci_readl(host, (reg & ~3)); + u32 byte_num = reg & 3; + u32 byte_shift = byte_num * 8; + u32 byte = (val >> byte_shift) & 0xff; + + return byte; +} + +static unsigned int bcm2835_sdhci_get_max_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct bcm2835_sdhci *bcm2835_host = pltfm_host->priv; + + return clk_get_rate(bcm2835_host->clk); +} + +unsigned int bcm2835_sdhci_get_min_clock(struct sdhci_host *host) +{ + return MIN_FREQ; +} + +unsigned int bcm2835_sdhci_get_timeout_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct bcm2835_sdhci *bcm2835_host = pltfm_host->priv; + + return clk_get_rate(bcm2835_host->clk); +} + +static struct sdhci_ops bcm2835_sdhci_ops = { + .write_l = bcm2835_sdhci_writel, + .write_w = bcm2835_sdhci_writew, + .write_b = bcm2835_sdhci_writeb, + .read_l = bcm2835_sdhci_readl, + .read_w = bcm2835_sdhci_readw, + .read_b = bcm2835_sdhci_readb, + .get_max_clock = bcm2835_sdhci_get_max_clock, + .get_min_clock = bcm2835_sdhci_get_min_clock, + .get_timeout_clock = bcm2835_sdhci_get_timeout_clock, +}; + +static struct sdhci_pltfm_data bcm2835_sdhci_pdata = { + .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION, + .ops = &bcm2835_sdhci_ops, +}; + +static int bcm2835_sdhci_probe(struct platform_device *pdev) +{ + struct sdhci_host *host; + struct bcm2835_sdhci *bcm2835_host; + struct sdhci_pltfm_host *pltfm_host; + int ret; + + host = sdhci_pltfm_init(pdev, &bcm2835_sdhci_pdata); + if (IS_ERR(host)) + return PTR_ERR(host); + + bcm2835_host = devm_kzalloc(&pdev->dev, sizeof(*bcm2835_host), + GFP_KERNEL); + if (!bcm2835_host) { + dev_err(mmc_dev(host->mmc), + "failed to allocate bcm2835_sdhci\n"); + return -ENOMEM; + } + + pltfm_host = sdhci_priv(host); + pltfm_host->priv = bcm2835_host; + + bcm2835_host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(bcm2835_host->clk)) { + ret = PTR_ERR(bcm2835_host->clk); + goto err; + } + + return sdhci_add_host(host); + +err: + sdhci_pltfm_free(pdev); + return ret; +} + +static int bcm2835_sdhci_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); + + sdhci_remove_host(host, dead); + sdhci_pltfm_free(pdev); + + return 0; +} + +static const struct of_device_id bcm2835_sdhci_of_match[] = { + { .compatible = "brcm,bcm2835-sdhci" }, + { } +}; +MODULE_DEVICE_TABLE(of, bcm2835_sdhci_of_match); + +static struct platform_driver bcm2835_sdhci_driver = { + .driver = { + .name = "sdhci-bcm2835", + .owner = THIS_MODULE, + .of_match_table = bcm2835_sdhci_of_match, + .pm = SDHCI_PLTFM_PMOPS, + }, + .probe = bcm2835_sdhci_probe, + .remove = bcm2835_sdhci_remove, +}; +module_platform_driver(bcm2835_sdhci_driver); + +MODULE_DESCRIPTION("BCM2835 SDHCI driver"); +MODULE_AUTHOR("Stephen Warren"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From b0a8dece55ebe792b394e0c73fa365ad24c3ec32 Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Sat, 5 Jan 2013 17:18:28 +0800 Subject: mmc: sdhci: disable interrupt before free_irq Current code missed disabling interrupts before free irq which is shared. Notice below comments for function free_irq (kernel/irq/manage.c): On a shared IRQ the caller must ensure the interrupt is disabled on the card it drives before calling this function. Original code has below issue during suspend/resume when multiple SD hosts share the same IRQ: 1. Assume there are two hosts (host1 for emmc while host2 for sd) share the same mmc irq. 2. When system suspend, host2 will be suspended before host1. So the sequence is below: step1: irq handler for host2 removed -> step2: irq handler for host1 removed and irq disabled -> ... system suspended ... ... system resumed ... step3: irq enabled and the irq handler for host1 restored -> step4: irq handler for host2 restored 3. So there is the buggy time slot that the irq is enabled but the irq handler for host2 is removed. Then host2 interrupt can be triggered but can't be handled at that moment. Signed-off-by: Jialing Fu Signed-off-by: Kevin Liu Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 1b97fe2d70ab..1165376592b9 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2487,6 +2487,7 @@ int sdhci_suspend_host(struct sdhci_host *host) return ret; } + sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK); free_irq(host->irq, host); return ret; @@ -3142,6 +3143,7 @@ int sdhci_add_host(struct sdhci_host *host) #ifdef SDHCI_USE_LEDS_CLASS reset: sdhci_reset(host, SDHCI_RESET_ALL); + sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK); free_irq(host->irq, host); #endif untasklet: @@ -3184,6 +3186,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) if (!dead) sdhci_reset(host, SDHCI_RESET_ALL); + sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK); free_irq(host->irq, host); del_timer_sync(&host->timer); -- cgit v1.2.3 From ad080d7916a4701fda29a442a69be4fd54d19a2e Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Sat, 5 Jan 2013 17:21:33 +0800 Subject: mmc: sdhci: add IRQ wake up support Don't disable SD Host IRQ during suspend if it is wake up source. Enable wakeup event during suspend. Signed-off-by: Jialing Fu Signed-off-by: Kevin Liu Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 60 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 1165376592b9..b93076764ddd 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2458,6 +2458,32 @@ out: \*****************************************************************************/ #ifdef CONFIG_PM +void sdhci_enable_irq_wakeups(struct sdhci_host *host) +{ + u8 val; + u8 mask = SDHCI_WAKE_ON_INSERT | SDHCI_WAKE_ON_REMOVE + | SDHCI_WAKE_ON_INT; + + val = sdhci_readb(host, SDHCI_WAKE_UP_CONTROL); + val |= mask ; + /* Avoid fake wake up */ + if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) + val &= ~(SDHCI_WAKE_ON_INSERT | SDHCI_WAKE_ON_REMOVE); + sdhci_writeb(host, val, SDHCI_WAKE_UP_CONTROL); +} +EXPORT_SYMBOL_GPL(sdhci_enable_irq_wakeups); + +void sdhci_disable_irq_wakeups(struct sdhci_host *host) +{ + u8 val; + u8 mask = SDHCI_WAKE_ON_INSERT | SDHCI_WAKE_ON_REMOVE + | SDHCI_WAKE_ON_INT; + + val = sdhci_readb(host, SDHCI_WAKE_UP_CONTROL); + val &= ~mask; + sdhci_writeb(host, val, SDHCI_WAKE_UP_CONTROL); +} +EXPORT_SYMBOL_GPL(sdhci_disable_irq_wakeups); int sdhci_suspend_host(struct sdhci_host *host) { @@ -2487,9 +2513,13 @@ int sdhci_suspend_host(struct sdhci_host *host) return ret; } - sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK); - free_irq(host->irq, host); - + if (!device_may_wakeup(mmc_dev(host->mmc))) { + sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK); + free_irq(host->irq, host); + } else { + sdhci_enable_irq_wakeups(host); + enable_irq_wake(host->irq); + } return ret; } @@ -2504,10 +2534,15 @@ int sdhci_resume_host(struct sdhci_host *host) host->ops->enable_dma(host); } - ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, - mmc_hostname(host->mmc), host); - if (ret) - return ret; + if (!device_may_wakeup(mmc_dev(host->mmc))) { + ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, + mmc_hostname(host->mmc), host); + if (ret) + return ret; + } else { + sdhci_disable_irq_wakeups(host); + disable_irq_wake(host->irq); + } if ((host->mmc->pm_flags & MMC_PM_KEEP_POWER) && (host->quirks2 & SDHCI_QUIRK2_HOST_OFF_CARD_ON)) { @@ -2535,17 +2570,6 @@ int sdhci_resume_host(struct sdhci_host *host) } EXPORT_SYMBOL_GPL(sdhci_resume_host); - -void sdhci_enable_irq_wakeups(struct sdhci_host *host) -{ - u8 val; - val = sdhci_readb(host, SDHCI_WAKE_UP_CONTROL); - val |= SDHCI_WAKE_ON_INT; - sdhci_writeb(host, val, SDHCI_WAKE_UP_CONTROL); -} - -EXPORT_SYMBOL_GPL(sdhci_enable_irq_wakeups); - #endif /* CONFIG_PM */ #ifdef CONFIG_PM_RUNTIME -- cgit v1.2.3 From 740b7a44aedbe8d916ce21b636f78467f5da1c59 Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Mon, 14 Jan 2013 14:38:53 -0500 Subject: mmc: sdhci-pxav3: add IRQ wake up support [cjb: The MMP3 architecture requires a registered interrupt to retire wfi when waking from suspend.] Signed-off-by: Jialing Fu Signed-off-by: Kevin Liu Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-pxav3.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index b7ee7761bc26..3d20c10fc571 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -311,6 +311,13 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) platform_set_drvdata(pdev, host); + if (pdata->pm_caps & MMC_PM_KEEP_POWER) { + device_init_wakeup(&pdev->dev, 1); + host->mmc->pm_flags |= MMC_PM_WAKE_SDIO_IRQ; + } else { + device_init_wakeup(&pdev->dev, 0); + } + return 0; err_add_host: -- cgit v1.2.3 From 8213af3b7a4059f13ad8b5beb3e6d69d9301f27d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 7 Jan 2013 16:31:08 +0200 Subject: mmc: sdhci: introduce sdhci_update_clock helper to re-enable clock There are three places where same piece of code is used. Let's split it to a separate function. Signed-off-by: Andy Shevchenko Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index b93076764ddd..336ab06aeb2f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1189,6 +1189,15 @@ out: host->clock = clock; } +static inline void sdhci_update_clock(struct sdhci_host *host) +{ + unsigned int clock; + + clock = host->clock; + host->clock = 0; + sdhci_set_clock(host, clock); +} + static int sdhci_set_power(struct sdhci_host *host, unsigned short power) { u8 pwr = 0; @@ -1418,7 +1427,6 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) if (host->version >= SDHCI_SPEC_300) { u16 clk, ctrl_2; - unsigned int clock; /* In case of UHS-I modes, set High Speed Enable */ if ((ios->timing == MMC_TIMING_MMC_HS200) || @@ -1458,9 +1466,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); /* Re-enable SD Clock */ - clock = host->clock; - host->clock = 0; - sdhci_set_clock(host, clock); + sdhci_update_clock(host); } @@ -1491,9 +1497,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) } /* Re-enable SD Clock */ - clock = host->clock; - host->clock = 0; - sdhci_set_clock(host, clock); + sdhci_update_clock(host); } else sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); @@ -2083,14 +2087,9 @@ static void sdhci_tasklet_finish(unsigned long param) (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST))) { /* Some controllers need this kick or reset won't work here */ - if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) { - unsigned int clock; - + if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) /* This is to force an update */ - clock = host->clock; - host->clock = 0; - sdhci_set_clock(host, clock); - } + sdhci_update_clock(host); /* Spec says we should do both at the same time, but Ricoh controllers do not like that. */ -- cgit v1.2.3 From a043859043042e8bc6f68b40782e3b8c9d10a28d Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 15 Jan 2013 23:19:54 +0800 Subject: mmc: sdhci-esdhc-imx: remove ESDHC_CD_GPIO handling from IO accessory With commit 9444e07 (mmc: remove unncessary mmc_gpio_free_cd() call from slot-gpio users) in place, the ESDHC_CD_GPIO handling in IO accessories becomes unnecessary. Remove it. Signed-off-by: Shawn Guo Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index dd7fcc137644..ae68bc965ab5 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -150,16 +150,6 @@ static u32 esdhc_readl_le(struct sdhci_host *host, int reg) u32 val = readl(host->ioaddr + reg); - if (unlikely(reg == SDHCI_PRESENT_STATE)) { - /* - * After SDHCI core gets improved to never query - * SDHCI_CARD_PRESENT state in GPIO case, we can - * remove this check. - */ - if (boarddata->cd_type == ESDHC_CD_GPIO) - val &= ~SDHCI_CARD_PRESENT; - } - if (unlikely(reg == SDHCI_CAPABILITIES)) { /* In FSL esdhc IC module, only bit20 is used to indicate the * ADMA2 capability of esdhc, but this bit is messed up on @@ -192,13 +182,6 @@ static void esdhc_writel_le(struct sdhci_host *host, u32 val, int reg) u32 data; if (unlikely(reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE)) { - if (boarddata->cd_type == ESDHC_CD_GPIO) - /* - * These interrupts won't work with a custom - * card_detect gpio (only applied to mx25/35) - */ - val &= ~(SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT); - if (val & SDHCI_INT_CARD_INT) { /* * Clear and then set D3CD bit to avoid missing the -- cgit v1.2.3 From 3724482d4ce4790d4534bcecebfda2c7133244c7 Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Wed, 16 Jan 2013 14:13:57 +0100 Subject: mmc: mvsdio: use slot-gpio infrastructure for write protect gpio The MMC core subsystem provides in drivers/mmc/core/slot-gpio.c a nice set of helper functions to simplify the management of the write protect GPIO in MMC host drivers. This patch migrates the mvsdio driver to using those helpers, which will make the ->probe() code simpler, and therefore ease the process of adding a Device Tree binding for this driver. Signed-off-by: Thomas Petazzoni Signed-off-by: Andrew Lunn Tested-by: Stefan Peter Tested-by: Florian Fainelli Signed-off-by: Jason Cooper Signed-off-by: Chris Ball --- drivers/mmc/host/mvsdio.c | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/drivers/mmc/host/mvsdio.c b/drivers/mmc/host/mvsdio.c index f8dd36102949..c6dc8fd696ab 100644 --- a/drivers/mmc/host/mvsdio.c +++ b/drivers/mmc/host/mvsdio.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -52,7 +53,6 @@ struct mvsd_host { struct device *dev; struct clk *clk; int gpio_card_detect; - int gpio_write_protect; }; #define mvsd_write(offs, val) writel(val, iobase + (offs)) @@ -564,20 +564,6 @@ static void mvsd_enable_sdio_irq(struct mmc_host *mmc, int enable) spin_unlock_irqrestore(&host->lock, flags); } -static int mvsd_get_ro(struct mmc_host *mmc) -{ - struct mvsd_host *host = mmc_priv(mmc); - - if (host->gpio_write_protect) - return gpio_get_value(host->gpio_write_protect); - - /* - * Board doesn't support read only detection; let the mmc core - * decide what to do. - */ - return -ENOSYS; -} - static void mvsd_power_up(struct mvsd_host *host) { void __iomem *iobase = host->base; @@ -674,7 +660,7 @@ static void mvsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) static const struct mmc_host_ops mvsd_ops = { .request = mvsd_request, - .get_ro = mvsd_get_ro, + .get_ro = mmc_gpio_get_ro, .set_ios = mvsd_set_ios, .enable_sdio_irq = mvsd_enable_sdio_irq, }; @@ -793,15 +779,7 @@ static int __init mvsd_probe(struct platform_device *pdev) if (!host->gpio_card_detect) mmc->caps |= MMC_CAP_NEEDS_POLL; - if (mvsd_data->gpio_write_protect) { - ret = devm_gpio_request_one(&pdev->dev, - mvsd_data->gpio_write_protect, - GPIOF_IN, DRIVER_NAME " wp"); - if (ret == 0) { - host->gpio_write_protect = - mvsd_data->gpio_write_protect; - } - } + mmc_gpio_request_ro(mmc, mvsd_data->gpio_write_protect); setup_timer(&host->timer, mvsd_timeout_timer, (unsigned long)host); platform_set_drvdata(pdev, mmc); @@ -820,6 +798,7 @@ static int __init mvsd_probe(struct platform_device *pdev) out: if (mmc) { + mmc_gpio_free_ro(mmc); if (!IS_ERR(host->clk)) clk_disable_unprepare(host->clk); mmc_free_host(mmc); @@ -834,6 +813,7 @@ static int __exit mvsd_remove(struct platform_device *pdev) struct mvsd_host *host = mmc_priv(mmc); + mmc_gpio_free_ro(mmc); mmc_remove_host(mmc); del_timer_sync(&host->timer); mvsd_power_down(host); -- cgit v1.2.3 From 07728b77c03dc0721daaf551976d95e6f714af1a Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Wed, 16 Jan 2013 14:13:58 +0100 Subject: mmc: mvsdio: use slot-gpio for card detect gpio The MMC core subsystem provides in drivers/mmc/core/slot-gpio.c a nice set of helper functions to simplify the management of the card detect GPIO in MMC host drivers. This patch migrates the mvsdio driver to using those helpers, which will make the ->probe() code simpler, and therefore ease the process of adding a Device Tree binding for this driver. Signed-off-by: Thomas Petazzoni Signed-off-by: Andrew Lunn Tested-by: Stefan Peter Tested-by: Florian Fainelli Signed-off-by: Jason Cooper Signed-off-by: Chris Ball --- drivers/mmc/host/mvsdio.c | 39 +++++++++------------------------------ 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/drivers/mmc/host/mvsdio.c b/drivers/mmc/host/mvsdio.c index c6dc8fd696ab..704b7a3fe873 100644 --- a/drivers/mmc/host/mvsdio.c +++ b/drivers/mmc/host/mvsdio.c @@ -52,7 +52,6 @@ struct mvsd_host { struct mmc_host *mmc; struct device *dev; struct clk *clk; - int gpio_card_detect; }; #define mvsd_write(offs, val) writel(val, iobase + (offs)) @@ -538,13 +537,6 @@ static void mvsd_timeout_timer(unsigned long data) mmc_request_done(host->mmc, mrq); } -static irqreturn_t mvsd_card_detect_irq(int irq, void *dev) -{ - struct mvsd_host *host = dev; - mmc_detect_change(host->mmc, msecs_to_jiffies(100)); - return IRQ_HANDLED; -} - static void mvsd_enable_sdio_irq(struct mmc_host *mmc, int enable) { struct mvsd_host *host = mmc_priv(mmc); @@ -757,26 +749,11 @@ static int __init mvsd_probe(struct platform_device *pdev) if (!IS_ERR(host->clk)) clk_prepare_enable(host->clk); - if (mvsd_data->gpio_card_detect) { - ret = devm_gpio_request_one(&pdev->dev, - mvsd_data->gpio_card_detect, - GPIOF_IN, DRIVER_NAME " cd"); - if (ret == 0) { - irq = gpio_to_irq(mvsd_data->gpio_card_detect); - ret = devm_request_irq(&pdev->dev, irq, - mvsd_card_detect_irq, - IRQ_TYPE_EDGE_RISING | - IRQ_TYPE_EDGE_FALLING, - DRIVER_NAME " cd", host); - if (ret == 0) - host->gpio_card_detect = - mvsd_data->gpio_card_detect; - else - devm_gpio_free(&pdev->dev, - mvsd_data->gpio_card_detect); - } - } - if (!host->gpio_card_detect) + if (gpio_is_valid(mvsd_data->gpio_card_detect)) { + ret = mmc_gpio_request_cd(mmc, mvsd_data->gpio_card_detect); + if (ret) + goto out; + } else mmc->caps |= MMC_CAP_NEEDS_POLL; mmc_gpio_request_ro(mmc, mvsd_data->gpio_write_protect); @@ -789,15 +766,16 @@ static int __init mvsd_probe(struct platform_device *pdev) pr_notice("%s: %s driver initialized, ", mmc_hostname(mmc), DRIVER_NAME); - if (host->gpio_card_detect) + if (!(mmc->caps & MMC_CAP_NEEDS_POLL)) printk("using GPIO %d for card detection\n", - host->gpio_card_detect); + mvsd_data->gpio_card_detect); else printk("lacking card detect (fall back to polling)\n"); return 0; out: if (mmc) { + mmc_gpio_free_cd(mmc); mmc_gpio_free_ro(mmc); if (!IS_ERR(host->clk)) clk_disable_unprepare(host->clk); @@ -813,6 +791,7 @@ static int __exit mvsd_remove(struct platform_device *pdev) struct mvsd_host *host = mmc_priv(mmc); + mmc_gpio_free_cd(mmc); mmc_gpio_free_ro(mmc); mmc_remove_host(mmc); del_timer_sync(&host->timer); -- cgit v1.2.3 From 111936ff3bc33585b475c1033fc98cd6b3370a74 Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Wed, 16 Jan 2013 14:13:59 +0100 Subject: mmc: mvsdio: implement a Device Tree binding This patch adds a simple Device Tree binding for the mvsdio driver, as well as the necessary documentation for it. Compatibility with non-DT platforms is preserved, by keeping the platform_data based initialization. We introduce a small difference between non-DT and DT platforms: DT platforms are required to provide a clocks = <...> property, which the driver uses to get the frequency of the clock that goes to the SDIO IP. The behaviour on non-DT platforms is kept unchanged: a clock reference is not mandatory, but the clock frequency must be passed in the "clock" field of the mvsdio_platform_data structure. Signed-off-by: Thomas Petazzoni Signed-off-by: Andrew Lunn Tested-by: Stefan Peter Tested-by: Florian Fainelli Signed-off-by: Jason Cooper Signed-off-by: Chris Ball --- .../devicetree/bindings/mmc/orion-sdio.txt | 17 ++++++ drivers/mmc/host/mvsdio.c | 64 +++++++++++++++++----- 2 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 Documentation/devicetree/bindings/mmc/orion-sdio.txt diff --git a/Documentation/devicetree/bindings/mmc/orion-sdio.txt b/Documentation/devicetree/bindings/mmc/orion-sdio.txt new file mode 100644 index 000000000000..84f0ebd67a13 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/orion-sdio.txt @@ -0,0 +1,17 @@ +* Marvell orion-sdio controller + +This file documents differences between the core properties in mmc.txt +and the properties used by the orion-sdio driver. + +- compatible: Should be "marvell,orion-sdio" +- clocks: reference to the clock of the SDIO interface + +Example: + + mvsdio@d00d4000 { + compatible = "marvell,orion-sdio"; + reg = <0xd00d4000 0x200>; + interrupts = <54>; + clocks = <&gateclk 17>; + status = "disabled"; + }; diff --git a/drivers/mmc/host/mvsdio.c b/drivers/mmc/host/mvsdio.c index 704b7a3fe873..78d3abf837c2 100644 --- a/drivers/mmc/host/mvsdio.c +++ b/drivers/mmc/host/mvsdio.c @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include @@ -681,17 +683,17 @@ mv_conf_mbus_windows(struct mvsd_host *host, static int __init mvsd_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct mmc_host *mmc = NULL; struct mvsd_host *host = NULL; - const struct mvsdio_platform_data *mvsd_data; const struct mbus_dram_target_info *dram; struct resource *r; int ret, irq; + int gpio_card_detect, gpio_write_protect; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); - mvsd_data = pdev->dev.platform_data; - if (!r || irq < 0 || !mvsd_data) + if (!r || irq < 0) return -ENXIO; mmc = mmc_alloc_host(sizeof(struct mvsd_host), &pdev->dev); @@ -703,8 +705,39 @@ static int __init mvsd_probe(struct platform_device *pdev) host = mmc_priv(mmc); host->mmc = mmc; host->dev = &pdev->dev; - host->base_clock = mvsd_data->clock / 2; - host->clk = ERR_PTR(-EINVAL); + + /* + * Some non-DT platforms do not pass a clock, and the clock + * frequency is passed through platform_data. On DT platforms, + * a clock must always be passed, even if there is no gatable + * clock associated to the SDIO interface (it can simply be a + * fixed rate clock). + */ + host->clk = devm_clk_get(&pdev->dev, NULL); + if (!IS_ERR(host->clk)) + clk_prepare_enable(host->clk); + + if (np) { + if (IS_ERR(host->clk)) { + dev_err(&pdev->dev, "DT platforms must have a clock associated\n"); + ret = -EINVAL; + goto out; + } + + host->base_clock = clk_get_rate(host->clk) / 2; + gpio_card_detect = of_get_named_gpio(np, "cd-gpios", 0); + gpio_write_protect = of_get_named_gpio(np, "wp-gpios", 0); + } else { + const struct mvsdio_platform_data *mvsd_data; + mvsd_data = pdev->dev.platform_data; + if (!mvsd_data) { + ret = -ENXIO; + goto out; + } + host->base_clock = mvsd_data->clock / 2; + gpio_card_detect = mvsd_data->gpio_card_detect; + gpio_write_protect = mvsd_data->gpio_write_protect; + } mmc->ops = &mvsd_ops; @@ -743,20 +776,14 @@ static int __init mvsd_probe(struct platform_device *pdev) goto out; } - /* Not all platforms can gate the clock, so it is not - an error if the clock does not exists. */ - host->clk = devm_clk_get(&pdev->dev, NULL); - if (!IS_ERR(host->clk)) - clk_prepare_enable(host->clk); - - if (gpio_is_valid(mvsd_data->gpio_card_detect)) { - ret = mmc_gpio_request_cd(mmc, mvsd_data->gpio_card_detect); + if (gpio_is_valid(gpio_card_detect)) { + ret = mmc_gpio_request_cd(mmc, gpio_card_detect); if (ret) goto out; } else mmc->caps |= MMC_CAP_NEEDS_POLL; - mmc_gpio_request_ro(mmc, mvsd_data->gpio_write_protect); + mmc_gpio_request_ro(mmc, gpio_write_protect); setup_timer(&host->timer, mvsd_timeout_timer, (unsigned long)host); platform_set_drvdata(pdev, mmc); @@ -768,7 +795,7 @@ static int __init mvsd_probe(struct platform_device *pdev) mmc_hostname(mmc), DRIVER_NAME); if (!(mmc->caps & MMC_CAP_NEEDS_POLL)) printk("using GPIO %d for card detection\n", - mvsd_data->gpio_card_detect); + gpio_card_detect); else printk("lacking card detect (fall back to polling)\n"); return 0; @@ -832,12 +859,19 @@ static int mvsd_resume(struct platform_device *dev) #define mvsd_resume NULL #endif +static const struct of_device_id mvsdio_dt_ids[] = { + { .compatible = "marvell,orion-sdio" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mvsdio_dt_ids); + static struct platform_driver mvsd_driver = { .remove = __exit_p(mvsd_remove), .suspend = mvsd_suspend, .resume = mvsd_resume, .driver = { .name = DRIVER_NAME, + .of_match_table = mvsdio_dt_ids, }, }; -- cgit v1.2.3 From 6f1989bc982bc176b0d63e028e9b7f23ae1b4583 Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Mon, 28 Jan 2013 06:26:53 -0500 Subject: mmc: mvsdio: add pinctrl integration On many Marvell SoCs, the pins used for the SDIO interface are part of the MPP pins, that are muxable pins. In order to get the muxing of those pins correct, this commit integrates the mvsdio driver with the pinctrl infrastructure by calling devm_pinctrl_get_select_default() during ->probe(). Note that we permit this function to fail because not all Marvell platforms have yet been fully converted to using the pinctrl infrastructure. Signed-off-by: Thomas Petazzoni Signed-off-by: Andrew Lunn Tested-by: Stefan Peter Tested-by: Florian Fainelli Signed-off-by: Jason Cooper Signed-off-by: Chris Ball --- drivers/mmc/host/mvsdio.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/mmc/host/mvsdio.c b/drivers/mmc/host/mvsdio.c index 78d3abf837c2..145cdaf000d1 100644 --- a/drivers/mmc/host/mvsdio.c +++ b/drivers/mmc/host/mvsdio.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -690,6 +691,7 @@ static int __init mvsd_probe(struct platform_device *pdev) struct resource *r; int ret, irq; int gpio_card_detect, gpio_write_protect; + struct pinctrl *pinctrl; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); @@ -706,6 +708,10 @@ static int __init mvsd_probe(struct platform_device *pdev) host->mmc = mmc; host->dev = &pdev->dev; + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) + dev_warn(&pdev->dev, "no pins associated\n"); + /* * Some non-DT platforms do not pass a clock, and the clock * frequency is passed through platform_data. On DT platforms, -- cgit v1.2.3 From e7be434acc50ec436039dbc9840fcf1f670a1ef9 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 7 Jan 2013 20:41:52 -0200 Subject: mmc: mxs-mmc: Add MODULE_ALIAS() Add an entry for MODULE_ALIAS(). Signed-off-by: Fabio Estevam Acked-by: Marek Vasut Signed-off-by: Chris Ball --- drivers/mmc/host/mxs-mmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/mxs-mmc.c b/drivers/mmc/host/mxs-mmc.c index 206fe499ded5..b9a9ca01014f 100644 --- a/drivers/mmc/host/mxs-mmc.c +++ b/drivers/mmc/host/mxs-mmc.c @@ -804,3 +804,4 @@ module_platform_driver(mxs_mmc_driver); MODULE_DESCRIPTION("FREESCALE MXS MMC peripheral"); MODULE_AUTHOR("Freescale Semiconductor"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); -- cgit v1.2.3 From fd63ac761a2385a69859867b2d51b716d91fcc36 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 7 Jan 2013 22:42:35 -0200 Subject: mmc: mxs-mmc: Fix warning due to incorrect type Fixes the following warning when building with W=1 option: drivers/mmc/host/mxs-mmc.c: In function 'mxs_mmc_adtc': drivers/mmc/host/mxs-mmc.c:401:2: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] The warning happens because 'i' is used in 'for_each_sg(sgl, sg, sg_len, i)' and should be made unsigned. Signed-off-by: Fabio Estevam Acked-by: Marek Vasut Signed-off-by: Chris Ball --- drivers/mmc/host/mxs-mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/mxs-mmc.c b/drivers/mmc/host/mxs-mmc.c index b9a9ca01014f..493cd9eb8aa8 100644 --- a/drivers/mmc/host/mxs-mmc.c +++ b/drivers/mmc/host/mxs-mmc.c @@ -354,7 +354,7 @@ static void mxs_mmc_adtc(struct mxs_mmc_host *host) struct dma_async_tx_descriptor *desc; struct scatterlist *sgl = data->sg, *sg; unsigned int sg_len = data->sg_len; - int i; + unsigned int i; unsigned short dma_data_dir, timeout; enum dma_transfer_direction slave_dirn; -- cgit v1.2.3 From c148e9ff4bd45f26d3f0253c20efc497672c3c84 Mon Sep 17 00:00:00 2001 From: "Zhang, YiX X" Date: Tue, 8 Jan 2013 06:07:39 +0000 Subject: mmc: correct the EXCEPTION_EVENTS_STATUS value in comment The right value is 54 according to eMMC 4.5 specification. Signed-off-by: ZhangYi Signed-off-by: Chris Ball --- include/linux/mmc/card.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index be2500a49925..7069dcea27c5 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -83,7 +83,7 @@ struct mmc_ext_csd { unsigned int data_tag_unit_size; /* DATA TAG UNIT size */ unsigned int boot_ro_lock; /* ro lock support */ bool boot_ro_lockable; - u8 raw_exception_status; /* 53 */ + u8 raw_exception_status; /* 54 */ u8 raw_partition_support; /* 160 */ u8 raw_rpmb_size_mult; /* 168 */ u8 raw_erased_mem_count; /* 181 */ -- cgit v1.2.3 From a70aaa64da2205d32d1c9362d8f5d4be619cd58f Mon Sep 17 00:00:00 2001 From: Doug Anderson Date: Fri, 11 Jan 2013 17:03:50 +0000 Subject: mmc: dw_mmc: Add "disable-wp" device tree property The "disable-wp" property is used to specify that a given SD card slot doesn't have a concept of write protect. This eliminates the need for special case code for SD slots that should never be write protected (like a micro SD slot or a dev board). The dw_mmc driver is special in needing to specify "disable-wp" because the lack of a "wp-gpios" property means to use the special purpose write protect line. On some other mmc devices the lack of "wp-gpios" means that write protect should be disabled. Signed-off-by: Doug Anderson Acked-by: Seungwon Jeon Acked-by: Will Newton Acked-by: Olof Johansson Signed-off-by: Chris Ball --- .../devicetree/bindings/mmc/synopsis-dw-mshc.txt | 12 +++++-- drivers/mmc/host/dw_mmc.c | 41 +++++++++++++++++++++- include/linux/mmc/dw_mmc.h | 9 +++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/synopsis-dw-mshc.txt b/Documentation/devicetree/bindings/mmc/synopsis-dw-mshc.txt index 06cd32d08052..726fd2122a13 100644 --- a/Documentation/devicetree/bindings/mmc/synopsis-dw-mshc.txt +++ b/Documentation/devicetree/bindings/mmc/synopsis-dw-mshc.txt @@ -26,8 +26,16 @@ Required Properties: * bus-width: as documented in mmc core bindings. * wp-gpios: specifies the write protect gpio line. The format of the - gpio specifier depends on the gpio controller. If the write-protect - line is not available, this property is optional. + gpio specifier depends on the gpio controller. If a GPIO is not used + for write-protect, this property is optional. + + * disable-wp: If the wp-gpios property isn't present then (by default) + we'd assume that the write protect is hooked up directly to the + controller's special purpose write protect line (accessible via + the WRTPRT register). However, it's possible that we simply don't + want write protect. In that case specify 'disable-wp'. + NOTE: This property is not required for slots known to always + connect to eMMC or SDIO cards. Optional properties: diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 323c5022c2ca..90f7d990551b 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -74,6 +74,7 @@ struct idmac_desc { * struct dw_mci_slot - MMC slot state * @mmc: The mmc_host representing this slot. * @host: The MMC controller this slot is using. + * @quirks: Slot-level quirks (DW_MCI_SLOT_QUIRK_XXX) * @ctype: Card type for this slot. * @mrq: mmc_request currently being processed or waiting to be * processed, or NULL when the slot is idle. @@ -88,6 +89,8 @@ struct dw_mci_slot { struct mmc_host *mmc; struct dw_mci *host; + int quirks; + u32 ctype; struct mmc_request *mrq; @@ -825,7 +828,13 @@ static int dw_mci_get_ro(struct mmc_host *mmc) struct dw_mci_board *brd = slot->host->pdata; /* Use platform get_ro function, else try on board write protect */ - if (brd->quirks & DW_MCI_QUIRK_NO_WRITE_PROTECT) + + /* + * NOTE: DW_MCI_QUIRK_NO_WRITE_PROTECT will be removed in a future + * patch in the series once reference to it is removed. + */ + if ((brd->quirks & DW_MCI_QUIRK_NO_WRITE_PROTECT) || + (slot->quirks & DW_MCI_SLOT_QUIRK_NO_WRITE_PROTECT)) read_only = 0; else if (brd->get_ro) read_only = brd->get_ro(slot->id); @@ -1785,6 +1794,30 @@ static struct device_node *dw_mci_of_find_slot_node(struct device *dev, u8 slot) return NULL; } +static struct dw_mci_of_slot_quirks { + char *quirk; + int id; +} of_slot_quirks[] = { + { + .quirk = "disable-wp", + .id = DW_MCI_SLOT_QUIRK_NO_WRITE_PROTECT, + }, +}; + +static int dw_mci_of_get_slot_quirks(struct device *dev, u8 slot) +{ + struct device_node *np = dw_mci_of_find_slot_node(dev, slot); + int quirks = 0; + int idx; + + /* get quirks */ + for (idx = 0; idx < ARRAY_SIZE(of_slot_quirks); idx++) + if (of_get_property(np, of_slot_quirks[idx].quirk, NULL)) + quirks |= of_slot_quirks[idx].id; + + return quirks; +} + /* find out bus-width for a given slot */ static u32 dw_mci_of_get_bus_wd(struct device *dev, u8 slot) { @@ -1800,6 +1833,10 @@ static u32 dw_mci_of_get_bus_wd(struct device *dev, u8 slot) return bus_wd; } #else /* CONFIG_OF */ +static int dw_mci_of_get_slot_quirks(struct device *dev, u8 slot) +{ + return 0; +} static u32 dw_mci_of_get_bus_wd(struct device *dev, u8 slot) { return 1; @@ -1828,6 +1865,8 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) slot->host = host; host->slot[id] = slot; + slot->quirks = dw_mci_of_get_slot_quirks(host->dev, slot->id); + mmc->ops = &dw_mci_ops; mmc->f_min = DIV_ROUND_UP(host->bus_hz, 510); mmc->f_max = host->bus_hz; diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index 34be4f47293c..de61de5608c9 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -209,9 +209,18 @@ struct dw_mci_dma_ops { #define DW_MCI_QUIRK_HIGHSPEED BIT(2) /* Unreliable card detection */ #define DW_MCI_QUIRK_BROKEN_CARD_DETECTION BIT(3) + /* Write Protect detection not available */ +/* + * NOTE: DW_MCI_QUIRK_NO_WRITE_PROTECT will be removed in a future + * patch in the series once reference to it is removed. + */ #define DW_MCI_QUIRK_NO_WRITE_PROTECT BIT(4) +/* Slot level quirks */ +/* This slot has no write protect */ +#define DW_MCI_SLOT_QUIRK_NO_WRITE_PROTECT BIT(0) + struct dma_pdata; struct block_settings { -- cgit v1.2.3 From 488755b5ccf57765a50a4d4b14292b283d495919 Mon Sep 17 00:00:00 2001 From: Doug Anderson Date: Fri, 11 Jan 2013 17:03:51 +0000 Subject: ARM: dts: Add disable-wp for sd card slot on smdk5250 The next change will remove the code from the dw_mmc-exynos that added the DW_MCI_QUIRK_NO_WRITE_PROTECT. Keep existing functionality of having no write protect pin on smdk5250 by adding the disable-wp property. Signed-off-by: Doug Anderson Acked-by: Seungwon Jeon Acked-by: Olof Johansson Signed-off-by: Chris Ball --- arch/arm/boot/dts/exynos5250-smdk5250.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/boot/dts/exynos5250-smdk5250.dts b/arch/arm/boot/dts/exynos5250-smdk5250.dts index e05b18f3c33d..a37e89ddb198 100644 --- a/arch/arm/boot/dts/exynos5250-smdk5250.dts +++ b/arch/arm/boot/dts/exynos5250-smdk5250.dts @@ -146,6 +146,7 @@ reg = <0>; bus-width = <4>; samsung,cd-pinmux-gpio = <&gpc3 2 2 3 3>; + disable-wp; gpios = <&gpc3 0 2 0 3>, <&gpc3 1 2 0 3>, <&gpc3 3 2 3 3>, <&gpc3 4 2 3 3>, <&gpc3 5 2 3 3>, <&gpc3 6 2 3 3>, -- cgit v1.2.3 From 07b240411f1e7528ed36e09979f40ccf4b844c8c Mon Sep 17 00:00:00 2001 From: Doug Anderson Date: Fri, 11 Jan 2013 17:03:52 +0000 Subject: mmc: dw_mmc: exynos: Remove code for wp-gpios The exynos code claimed the write protect with devm_gpio_request() but never did anything with it. That meant that anyone using a write protect GPIO would effectively be write protected all the time. The handling for wp-gpios belongs in the main dw_mmc driver and has been moved there. Signed-off-by: Doug Anderson Acked-by: Seungwon Jeon Acked-by: Olof Johansson Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc-exynos.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/mmc/host/dw_mmc-exynos.c b/drivers/mmc/host/dw_mmc-exynos.c index 4d50da618166..72fd0f2c9013 100644 --- a/drivers/mmc/host/dw_mmc-exynos.c +++ b/drivers/mmc/host/dw_mmc-exynos.c @@ -175,16 +175,6 @@ static int dw_mci_exynos_setup_bus(struct dw_mci *host, } } - gpio = of_get_named_gpio(slot_np, "wp-gpios", 0); - if (gpio_is_valid(gpio)) { - if (devm_gpio_request(host->dev, gpio, "dw-mci-wp")) - dev_info(host->dev, "gpio [%d] request failed\n", - gpio); - } else { - dev_info(host->dev, "wp gpio not available"); - host->pdata->quirks |= DW_MCI_QUIRK_NO_WRITE_PROTECT; - } - if (host->pdata->quirks & DW_MCI_QUIRK_BROKEN_CARD_DETECTION) return 0; -- cgit v1.2.3 From 55a6ceb2d5b845f198bcd76aa18910e05a47d0c2 Mon Sep 17 00:00:00 2001 From: Doug Anderson Date: Fri, 11 Jan 2013 17:03:53 +0000 Subject: mmc: dw_mmc: Handle wp-gpios from device tree On some SoCs (like exynos5250) you need to use an external GPIO for write protect. Add support for wp-gpios to the core dw_mmc driver since it could be useful across multiple SoCs. With this change I am able to make use of the write protect for the external SD slot on exynos5250-snow. Signed-off-by: Doug Anderson Acked-by: Seungwon Jeon Acked-by: Olof Johansson Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 90f7d990551b..df6207909fe7 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "dw_mmc.h" @@ -75,6 +76,7 @@ struct idmac_desc { * @mmc: The mmc_host representing this slot. * @host: The MMC controller this slot is using. * @quirks: Slot-level quirks (DW_MCI_SLOT_QUIRK_XXX) + * @wp_gpio: If gpio_is_valid() we'll use this to read write protect. * @ctype: Card type for this slot. * @mrq: mmc_request currently being processed or waiting to be * processed, or NULL when the slot is idle. @@ -90,6 +92,7 @@ struct dw_mci_slot { struct dw_mci *host; int quirks; + int wp_gpio; u32 ctype; @@ -838,6 +841,8 @@ static int dw_mci_get_ro(struct mmc_host *mmc) read_only = 0; else if (brd->get_ro) read_only = brd->get_ro(slot->id); + else if (gpio_is_valid(slot->wp_gpio)) + read_only = gpio_get_value(slot->wp_gpio); else read_only = mci_readl(slot->host, WRTPRT) & (1 << slot->id) ? 1 : 0; @@ -1832,6 +1837,29 @@ static u32 dw_mci_of_get_bus_wd(struct device *dev, u8 slot) " as 1\n"); return bus_wd; } + +/* find the write protect gpio for a given slot; or -1 if none specified */ +static int dw_mci_of_get_wp_gpio(struct device *dev, u8 slot) +{ + struct device_node *np = dw_mci_of_find_slot_node(dev, slot); + int gpio; + + if (!np) + return -EINVAL; + + gpio = of_get_named_gpio(np, "wp-gpios", 0); + + /* Having a missing entry is valid; return silently */ + if (!gpio_is_valid(gpio)) + return -EINVAL; + + if (devm_gpio_request(dev, gpio, "dw-mci-wp")) { + dev_warn(dev, "gpio [%d] request failed\n", gpio); + return -EINVAL; + } + + return gpio; +} #else /* CONFIG_OF */ static int dw_mci_of_get_slot_quirks(struct device *dev, u8 slot) { @@ -1845,6 +1873,10 @@ static struct device_node *dw_mci_of_find_slot_node(struct device *dev, u8 slot) { return NULL; } +static int dw_mci_of_get_wp_gpio(struct device *dev, u8 slot) +{ + return -EINVAL; +} #endif /* CONFIG_OF */ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) @@ -1962,6 +1994,8 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) else clear_bit(DW_MMC_CARD_PRESENT, &slot->flags); + slot->wp_gpio = dw_mci_of_get_wp_gpio(host->dev, slot->id); + mmc_add_host(mmc); #if defined(CONFIG_DEBUG_FS) -- cgit v1.2.3 From 9640639b09313af4cd37a465408643aba927808e Mon Sep 17 00:00:00 2001 From: Doug Anderson Date: Fri, 11 Jan 2013 17:03:54 +0000 Subject: mmc: dw_mmc: Remove DW_MCI_QUIRK_NO_WRITE_PROTECT The original quirk was added in the change 'mmc: dw_mmc: add quirk to indicate missing write protect line'. The original quirk was added at a controller level even though each slot has its own write protect (so the quirk should be at the slot level). A recent change (mmc: dw_mmc: Add "disable-wp" device tree property) added a slot-level quirk and support for the quirk directly to dw_mmc. Signed-off-by: Doug Anderson Acked-by: Will Newton Acked-by: Olof Johansson Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc.c | 8 +------- include/linux/mmc/dw_mmc.h | 7 ------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index df6207909fe7..60063ccb4c4b 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -831,13 +831,7 @@ static int dw_mci_get_ro(struct mmc_host *mmc) struct dw_mci_board *brd = slot->host->pdata; /* Use platform get_ro function, else try on board write protect */ - - /* - * NOTE: DW_MCI_QUIRK_NO_WRITE_PROTECT will be removed in a future - * patch in the series once reference to it is removed. - */ - if ((brd->quirks & DW_MCI_QUIRK_NO_WRITE_PROTECT) || - (slot->quirks & DW_MCI_SLOT_QUIRK_NO_WRITE_PROTECT)) + if (slot->quirks & DW_MCI_SLOT_QUIRK_NO_WRITE_PROTECT) read_only = 0; else if (brd->get_ro) read_only = brd->get_ro(slot->id); diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index de61de5608c9..198f0fa44e9f 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -210,13 +210,6 @@ struct dw_mci_dma_ops { /* Unreliable card detection */ #define DW_MCI_QUIRK_BROKEN_CARD_DETECTION BIT(3) -/* Write Protect detection not available */ -/* - * NOTE: DW_MCI_QUIRK_NO_WRITE_PROTECT will be removed in a future - * patch in the series once reference to it is removed. - */ -#define DW_MCI_QUIRK_NO_WRITE_PROTECT BIT(4) - /* Slot level quirks */ /* This slot has no write protect */ #define DW_MCI_SLOT_QUIRK_NO_WRITE_PROTECT BIT(0) -- cgit v1.2.3 From 893613b06f10578851d6bc36f0da33d248c93554 Mon Sep 17 00:00:00 2001 From: Tony Prisk Date: Sun, 13 Jan 2013 19:19:20 +1300 Subject: mmc: vt8500: Remove erroneous __exitp in wmt_mci_driver With the __devinit/__devexit attributes having been removed, this __exitp attribute causes an unused function warning and should be removed as well. Signed-off-by: Tony Prisk Signed-off-by: Chris Ball --- drivers/mmc/host/wmt-sdmmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/wmt-sdmmc.c b/drivers/mmc/host/wmt-sdmmc.c index 154f0e8e931c..c6d001509e5a 100644 --- a/drivers/mmc/host/wmt-sdmmc.c +++ b/drivers/mmc/host/wmt-sdmmc.c @@ -1012,7 +1012,7 @@ static const struct dev_pm_ops wmt_mci_pm = { static struct platform_driver wmt_mci_driver = { .probe = wmt_mci_probe, - .remove = __exit_p(wmt_mci_remove), + .remove = wmt_mci_remove, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, -- cgit v1.2.3 From ef4d0888bb7e1b963880f086575081c3d39cad2d Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 15 Jan 2013 23:30:27 +0800 Subject: mmc: sdhci-esdhc-imx: fix host version read When commit 95a2482 (mmc: sdhci-esdhc-imx: add basic imx6q usdhc support) works around host version issue on imx6q, it gets the register address fixup "reg ^= 2" lost for imx25/35/51/53 esdhc. Thus, the controller version on these SoCs is wrongly identified as v1 while it's actually v2. Add the address fixup back and take a different approach to correct imx6q host version, so that the host version read gets back to work for all SoCs. Signed-off-by: Shawn Guo Cc: Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index ae68bc965ab5..ac6f971b625f 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -220,15 +220,18 @@ static void esdhc_writel_le(struct sdhci_host *host, u32 val, int reg) static u16 esdhc_readw_le(struct sdhci_host *host, int reg) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct pltfm_imx_data *imx_data = pltfm_host->priv; + if (unlikely(reg == SDHCI_HOST_VERSION)) { - u16 val = readw(host->ioaddr + (reg ^ 2)); - /* - * uSDHC supports SDHCI v3.0, but it's encoded as value - * 0x3 in host controller version register, which violates - * SDHCI_SPEC_300 definition. Work it around here. - */ - if ((val & SDHCI_SPEC_VER_MASK) == 3) - return --val; + reg ^= 2; + if (is_imx6q_usdhc(imx_data)) { + /* + * The usdhc register returns a wrong host version. + * Correct it here. + */ + return SDHCI_SPEC_300; + } } return readw(host->ioaddr + reg); -- cgit v1.2.3 From 6b40d18295a878c0e8ff02062cb9b8e9a6b156e4 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 15 Jan 2013 23:36:52 +0800 Subject: mmc: sdhci-esdhc-imx: remove D3CD check from SDHCI_HOST_CONTROL write SDHCI_CTRL_D3CD is not a standard SDHCI_HOST_CONTROL, so there is no need to check it in SDHCI_HOST_CONTROL write at all. Remove it. Signed-off-by: Shawn Guo Tested-by: Dirk Behme Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index ac6f971b625f..322eabfd61c6 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -297,10 +297,8 @@ static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg) */ return; case SDHCI_HOST_CONTROL: - /* FSL messed up here, so we can just keep those three */ - new_val = val & (SDHCI_CTRL_LED | \ - SDHCI_CTRL_4BITBUS | \ - SDHCI_CTRL_D3CD); + /* FSL messed up here, so we need to manually compose it. */ + new_val = val & (SDHCI_CTRL_LED | SDHCI_CTRL_4BITBUS); /* ensure the endianness */ new_val |= ESDHC_HOST_CONTROL_LE; /* bits 8&9 are reserved on mx25 */ -- cgit v1.2.3 From 60bf6396fb12357aeb231ae27196c63e83af9c39 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 15 Jan 2013 23:36:53 +0800 Subject: mmc: sdhci-esdhc-imx: name esdhc specific definitions with ESDHC_ prefix Rename esdhc local definitions with ESDHC_ rather than SDHCI_ prefix, so that we can distinguish them from SDHCI core definitions from name. A couple of bit fields are also changed use shift for consistency and better readability. Signed-off-by: Shawn Guo Tested-by: Dirk Behme Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 322eabfd61c6..6ffd15e7a3b1 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -30,12 +30,12 @@ #include "sdhci-pltfm.h" #include "sdhci-esdhc.h" -#define SDHCI_CTRL_D3CD 0x08 +#define ESDHC_CTRL_D3CD 0x08 /* VENDOR SPEC register */ -#define SDHCI_VENDOR_SPEC 0xC0 -#define SDHCI_VENDOR_SPEC_SDIO_QUIRK 0x00000002 -#define SDHCI_WTMK_LVL 0x44 -#define SDHCI_MIX_CTRL 0x48 +#define ESDHC_VENDOR_SPEC 0xc0 +#define ESDHC_VENDOR_SPEC_SDIO_QUIRK (1 << 1) +#define ESDHC_WTMK_LVL 0x44 +#define ESDHC_MIX_CTRL 0x48 /* * There is an INT DMA ERR mis-match between eSDHC and STD SDHC SPEC: @@ -43,7 +43,7 @@ * but bit28 is used as the INT DMA ERR in fsl eSDHC design. * Define this macro DMA error INT for fsl eSDHC */ -#define SDHCI_INT_VENDOR_SPEC_DMA_ERR 0x10000000 +#define ESDHC_INT_VENDOR_SPEC_DMA_ERR (1 << 28) /* * The CMDTYPE of the CMD register (offset 0xE) should be set to @@ -165,8 +165,8 @@ static u32 esdhc_readl_le(struct sdhci_host *host, int reg) } if (unlikely(reg == SDHCI_INT_STATUS)) { - if (val & SDHCI_INT_VENDOR_SPEC_DMA_ERR) { - val &= ~SDHCI_INT_VENDOR_SPEC_DMA_ERR; + if (val & ESDHC_INT_VENDOR_SPEC_DMA_ERR) { + val &= ~ESDHC_INT_VENDOR_SPEC_DMA_ERR; val |= SDHCI_INT_ADMA_ERROR; } } @@ -192,9 +192,9 @@ static void esdhc_writel_le(struct sdhci_host *host, u32 val, int reg) * re-sample it by the following steps. */ data = readl(host->ioaddr + SDHCI_HOST_CONTROL); - data &= ~SDHCI_CTRL_D3CD; + data &= ~ESDHC_CTRL_D3CD; writel(data, host->ioaddr + SDHCI_HOST_CONTROL); - data |= SDHCI_CTRL_D3CD; + data |= ESDHC_CTRL_D3CD; writel(data, host->ioaddr + SDHCI_HOST_CONTROL); } } @@ -203,15 +203,15 @@ static void esdhc_writel_le(struct sdhci_host *host, u32 val, int reg) && (reg == SDHCI_INT_STATUS) && (val & SDHCI_INT_DATA_END))) { u32 v; - v = readl(host->ioaddr + SDHCI_VENDOR_SPEC); - v &= ~SDHCI_VENDOR_SPEC_SDIO_QUIRK; - writel(v, host->ioaddr + SDHCI_VENDOR_SPEC); + v = readl(host->ioaddr + ESDHC_VENDOR_SPEC); + v &= ~ESDHC_VENDOR_SPEC_SDIO_QUIRK; + writel(v, host->ioaddr + ESDHC_VENDOR_SPEC); } if (unlikely(reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE)) { if (val & SDHCI_INT_ADMA_ERROR) { val &= ~SDHCI_INT_ADMA_ERROR; - val |= SDHCI_INT_VENDOR_SPEC_DMA_ERR; + val |= ESDHC_INT_VENDOR_SPEC_DMA_ERR; } } @@ -253,9 +253,9 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg) && (host->cmd->data->blocks > 1) && (host->cmd->data->flags & MMC_DATA_READ)) { u32 v; - v = readl(host->ioaddr + SDHCI_VENDOR_SPEC); - v |= SDHCI_VENDOR_SPEC_SDIO_QUIRK; - writel(v, host->ioaddr + SDHCI_VENDOR_SPEC); + v = readl(host->ioaddr + ESDHC_VENDOR_SPEC); + v |= ESDHC_VENDOR_SPEC_SDIO_QUIRK; + writel(v, host->ioaddr + ESDHC_VENDOR_SPEC); } imx_data->scratchpad = val; return; @@ -266,9 +266,9 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg) val |= SDHCI_CMD_ABORTCMD; if (is_imx6q_usdhc(imx_data)) { - u32 m = readl(host->ioaddr + SDHCI_MIX_CTRL); + u32 m = readl(host->ioaddr + ESDHC_MIX_CTRL); m = imx_data->scratchpad | (m & 0xffff0000); - writel(m, host->ioaddr + SDHCI_MIX_CTRL); + writel(m, host->ioaddr + ESDHC_MIX_CTRL); writel(val << 16, host->ioaddr + SDHCI_TRANSFER_MODE); } else { @@ -487,7 +487,7 @@ static int sdhci_esdhc_imx_probe(struct platform_device *pdev) * to something insane. Change it back here. */ if (is_imx6q_usdhc(imx_data)) - writel(0x08100810, host->ioaddr + SDHCI_WTMK_LVL); + writel(0x08100810, host->ioaddr + ESDHC_WTMK_LVL); boarddata = &imx_data->boarddata; if (sdhci_esdhc_imx_probe_dt(pdev, boarddata) < 0) { -- cgit v1.2.3 From b477426e3701946870d427d183ef492d7e54f111 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 16 Jan 2013 17:20:41 +0100 Subject: mmc: fix DT binding documentation SDHCI left-over The file Documentation/devicetree/bindings/mmc/mmc.txt is common for all MMC host drivers. Use a generic MMC host reference instead of an SDHCI left-over. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- Documentation/devicetree/bindings/mmc/mmc.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt index a591c6741d75..34f28ed97928 100644 --- a/Documentation/devicetree/bindings/mmc/mmc.txt +++ b/Documentation/devicetree/bindings/mmc/mmc.txt @@ -10,7 +10,7 @@ Required properties: - bus-width: Number of data lines, can be <1>, <4>, or <8> Card detection: -If no property below is supplied, standard SDHCI card detect is used. +If no property below is supplied, host native card detect is used. Only one of the properties in this section should be supplied: - broken-cd: There is no card detection available; polling must be used. - cd-gpios: Specify GPIOs for card detection, see gpio binding -- cgit v1.2.3 From da86a5d4ef0df1c8fbdbe3ac37ef271f93e412c5 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 17 Jan 2013 00:32:52 -0200 Subject: mmc: sdhci-esdhc-imx: Remove unused variables 3f175a6e5 (mmc: sdhci-esdhc-imx: remove ESDHC_CD_GPIO handling from IO accessory) introduced the following build warnings: drivers/mmc/host/sdhci-esdhc-imx.c:149:30: warning: unused variable 'boarddata' [-Wunused-variable] drivers/mmc/host/sdhci-esdhc-imx.c:181:30: warning: unused variable 'boarddata' [-Wunused-variable] Remove the unused variables. Signed-off-by: Fabio Estevam Acked-by: Shawn Guo Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 6ffd15e7a3b1..370c052f4293 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -144,10 +144,6 @@ static inline void esdhc_clrset_le(struct sdhci_host *host, u32 mask, u32 val, i static u32 esdhc_readl_le(struct sdhci_host *host, int reg) { - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - struct pltfm_imx_data *imx_data = pltfm_host->priv; - struct esdhc_platform_data *boarddata = &imx_data->boarddata; - u32 val = readl(host->ioaddr + reg); if (unlikely(reg == SDHCI_CAPABILITIES)) { @@ -178,7 +174,6 @@ static void esdhc_writel_le(struct sdhci_host *host, u32 val, int reg) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct pltfm_imx_data *imx_data = pltfm_host->priv; - struct esdhc_platform_data *boarddata = &imx_data->boarddata; u32 data; if (unlikely(reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE)) { -- cgit v1.2.3 From f9e37137e420a626df4f5e7c4f0869bf7a1d4929 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Thu, 17 Jan 2013 21:33:23 +0900 Subject: MAINTAINERS: mmc: add maintainer entry for dw_mmc driver Add maintainer entry for the Synopsys DW host driver which is used in various SOC including EXYNOS series. As Will Newton will no longer be able to take care of dw_mmc*, I and Jaehoon Chung are willing to maintain it. Signed-off-by: Seungwon Jeon Signed-off-by: Jaehoon Chung Acked-by: Will Newton Signed-off-by: Chris Ball --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 212c255b9347..b8ae059f7ca4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6597,6 +6597,14 @@ F: include/linux/dw_dmac.h F: drivers/dma/dw_dmac_regs.h F: drivers/dma/dw_dmac.c +SYNOPSYS DESIGNWARE MMC/SD/SDIO DRIVER +M: Seungwon Jeon +M: Jaehoon Chung +L: linux-mmc@vger.kernel.org +S: Maintained +F: include/linux/mmc/dw_mmc.h +F: drivers/mmc/host/dw_mmc* + TIMEKEEPING, NTP M: John Stultz M: Thomas Gleixner -- cgit v1.2.3 From 881d926d9d0bd2eb50f8f90c993bc403853382ce Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Mon, 28 Jan 2013 16:44:22 -0500 Subject: mmc: core: move the cache disabling operation to mmc_suspend Cache control is an eMMC feature and in therefore should be part of MMC's bus resume operations, performed in mmc_suspend, rather than in the generic mmc_suspend_host(). Signed-off-by: Maya Erez Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 7 +------ drivers/mmc/core/mmc.c | 8 +++++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 8b3a1222e665..39f28af34cb6 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2486,6 +2486,7 @@ EXPORT_SYMBOL(mmc_flush_cache); * Turn the cache ON/OFF. * Turning the cache OFF shall trigger flushing of the data * to the non-volatile storage. + * This function should be called with host claimed */ int mmc_cache_ctrl(struct mmc_host *host, u8 enable) { @@ -2497,7 +2498,6 @@ int mmc_cache_ctrl(struct mmc_host *host, u8 enable) mmc_card_is_removable(host)) return err; - mmc_claim_host(host); if (card && mmc_card_mmc(card) && (card->ext_csd.cache_size > 0)) { enable = !!enable; @@ -2515,7 +2515,6 @@ int mmc_cache_ctrl(struct mmc_host *host, u8 enable) card->ext_csd.cache_ctrl = enable; } } - mmc_release_host(host); return err; } @@ -2534,10 +2533,6 @@ int mmc_suspend_host(struct mmc_host *host) cancel_delayed_work(&host->detect); mmc_flush_scheduled_work(); - err = mmc_cache_ctrl(host, 0); - if (err) - goto out; - mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { if (host->bus_ops->suspend) { diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index e6e39111e05b..dc17d4097f9a 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1379,6 +1379,11 @@ static int mmc_suspend(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); + + err = mmc_cache_ctrl(host, 0); + if (err) + goto out; + if (mmc_can_poweroff_notify(host->card)) err = mmc_poweroff_notify(host->card, EXT_CSD_POWER_OFF_SHORT); else if (mmc_card_can_sleep(host)) @@ -1386,8 +1391,9 @@ static int mmc_suspend(struct mmc_host *host) else if (!mmc_host_is_spi(host)) err = mmc_deselect_cards(host); host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200); - mmc_release_host(host); +out: + mmc_release_host(host); return err; } -- cgit v1.2.3 From 69f5469822132c4ae0637eef2980dbaec5bb2b31 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Mon, 21 Jan 2013 19:02:24 +0800 Subject: mmc: sdhci-esdhc-imx: separate transfer mode from command write for usdhc The combining of SDHCI_TRANSFER_MODE and SDHCI_COMMAND writes is only required for esdhc, but not necessarily for usdhc. Different from esdhc where the bits for transfer mode and command are all in the same register CMD_XFR_TYP, usdhc has a newly introduced register MIX_CTRL to hold transfer mode bits. So it makes more sense to separate transfer mode from command write for usdhc. Signed-off-by: Shawn Guo Tested-by: Dirk Behme Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 370c052f4293..48832c567f72 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -239,10 +239,6 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg) switch (reg) { case SDHCI_TRANSFER_MODE: - /* - * Postpone this write, we must do it together with a - * command write that is down below. - */ if ((imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT) && (host->cmd->opcode == SD_IO_RW_EXTENDED) && (host->cmd->data->blocks > 1) @@ -252,7 +248,18 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg) v |= ESDHC_VENDOR_SPEC_SDIO_QUIRK; writel(v, host->ioaddr + ESDHC_VENDOR_SPEC); } - imx_data->scratchpad = val; + + if (is_imx6q_usdhc(imx_data)) { + u32 m = readl(host->ioaddr + ESDHC_MIX_CTRL); + m = val | (m & 0xffff0000); + writel(m, host->ioaddr + ESDHC_MIX_CTRL); + } else { + /* + * Postpone this write, we must do it together with a + * command write that is down below. + */ + imx_data->scratchpad = val; + } return; case SDHCI_COMMAND: if ((host->cmd->opcode == MMC_STOP_TRANSMISSION || @@ -260,16 +267,12 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg) (imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT)) val |= SDHCI_CMD_ABORTCMD; - if (is_imx6q_usdhc(imx_data)) { - u32 m = readl(host->ioaddr + ESDHC_MIX_CTRL); - m = imx_data->scratchpad | (m & 0xffff0000); - writel(m, host->ioaddr + ESDHC_MIX_CTRL); + if (is_imx6q_usdhc(imx_data)) writel(val << 16, host->ioaddr + SDHCI_TRANSFER_MODE); - } else { + else writel(val << 16 | imx_data->scratchpad, host->ioaddr + SDHCI_TRANSFER_MODE); - } return; case SDHCI_BLOCK_SIZE: val &= ~SDHCI_MAKE_BLKSZ(0x7, 0); -- cgit v1.2.3 From 58c8c4fbdb5576a0afa377b1a730566b25c25cba Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Mon, 21 Jan 2013 19:02:25 +0800 Subject: mmc: sdhci-esdhc-imx: manually reset MIX_CTRL for usdhc It's another violation to SDHC spec that software reset on usdhc does not reset MIX_CTRL register. Have to do it manually, otherwise the preserving of the register bits (e.g. AC23EN) may cause mmc card fail to be initialized. Signed-off-by: Shawn Guo Tested-by: Dirk Behme Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 48832c567f72..968a70f1a420 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -318,8 +318,15 @@ static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg) * circuit relies on. To work around it, we turn the clocks on back * to keep card detection circuit functional. */ - if ((reg == SDHCI_SOFTWARE_RESET) && (val & 1)) + if ((reg == SDHCI_SOFTWARE_RESET) && (val & 1)) { esdhc_clrset_le(host, 0x7, 0x7, ESDHC_SYSTEM_CONTROL); + /* + * The reset on usdhc fails to clear MIX_CTRL register. + * Do it manually here. + */ + if (is_imx6q_usdhc(imx_data)) + writel(0, host->ioaddr + ESDHC_MIX_CTRL); + } } static unsigned int esdhc_pltfm_get_max_clock(struct sdhci_host *host) -- cgit v1.2.3 From 2a15f981aec7c6b800c0d285ee1a63acc8487b9b Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Mon, 21 Jan 2013 19:02:26 +0800 Subject: mmc: sdhci-esdhc-imx: Auto CMD23 support for usdhc SDHCI core will try to use Auto CMD23 for mmc card. Currently, we will see the following message with mmc card on usdhc due to the lacking of Auto CMD23 support in the driver. $ mmc0: new high speed MMC card at address 0001 mmcblk1: mmc0:0001 MMC02G 1.87 GiB mmcblk1: error -84 transferring data, sector 0, nr 8, cmd response 0x900, card status 0xb00 mmcblk1: retrying using single block read mmcblk1: Enable Auto CMD23 support for usdhc so that mmc card can work in multiple block mode. Signed-off-by: Shawn Guo Tested-by: Dirk Behme Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 968a70f1a420..24daaf4e20ba 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -36,6 +36,9 @@ #define ESDHC_VENDOR_SPEC_SDIO_QUIRK (1 << 1) #define ESDHC_WTMK_LVL 0x44 #define ESDHC_MIX_CTRL 0x48 +#define ESDHC_MIX_CTRL_AC23EN (1 << 7) +/* Bits 3 and 6 are not SDHCI standard definitions */ +#define ESDHC_MIX_CTRL_SDHCI_MASK 0xb7 /* * There is an INT DMA ERR mis-match between eSDHC and STD SDHC SPEC: @@ -251,7 +254,12 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg) if (is_imx6q_usdhc(imx_data)) { u32 m = readl(host->ioaddr + ESDHC_MIX_CTRL); - m = val | (m & 0xffff0000); + /* Swap AC23 bit */ + if (val & SDHCI_TRNS_AUTO_CMD23) { + val &= ~SDHCI_TRNS_AUTO_CMD23; + val |= ESDHC_MIX_CTRL_AC23EN; + } + m = val | (m & ~ESDHC_MIX_CTRL_SDHCI_MASK); writel(m, host->ioaddr + ESDHC_MIX_CTRL); } else { /* -- cgit v1.2.3 From 7bc088d38f92f58df97e1cd9a8430331ee5491bb Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Mon, 21 Jan 2013 19:02:27 +0800 Subject: mmc: sdhci: rename platform_8bit_width to platform_bus_width The 8bit in the function name is misleading. When set, it will be used to set the bus width, regardless of whether 8bit or another bus width is requested, so change the function name to platform_bus_width. Signed-off-by: Sascha Hauer Signed-off-by: Shawn Guo Tested-by: Dirk Behme Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-pci.c | 4 ++-- drivers/mmc/host/sdhci-pxav2.c | 2 +- drivers/mmc/host/sdhci-s3c.c | 8 ++++---- drivers/mmc/host/sdhci-tegra.c | 4 ++-- drivers/mmc/host/sdhci.c | 8 ++++---- drivers/mmc/host/sdhci.h | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index c7dd0cbc99de..c7ccf3034dad 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -935,7 +935,7 @@ static int sdhci_pci_enable_dma(struct sdhci_host *host) return 0; } -static int sdhci_pci_8bit_width(struct sdhci_host *host, int width) +static int sdhci_pci_bus_width(struct sdhci_host *host, int width) { u8 ctrl; @@ -977,7 +977,7 @@ static void sdhci_pci_hw_reset(struct sdhci_host *host) static struct sdhci_ops sdhci_pci_ops = { .enable_dma = sdhci_pci_enable_dma, - .platform_8bit_width = sdhci_pci_8bit_width, + .platform_bus_width = sdhci_pci_bus_width, .hw_reset = sdhci_pci_hw_reset, }; diff --git a/drivers/mmc/host/sdhci-pxav2.c b/drivers/mmc/host/sdhci-pxav2.c index ac854aa192a8..7e5756593b68 100644 --- a/drivers/mmc/host/sdhci-pxav2.c +++ b/drivers/mmc/host/sdhci-pxav2.c @@ -121,7 +121,7 @@ static u32 pxav2_get_max_clock(struct sdhci_host *host) static struct sdhci_ops pxav2_sdhci_ops = { .get_max_clock = pxav2_get_max_clock, .platform_reset_exit = pxav2_set_private_registers, - .platform_8bit_width = pxav2_mmc_set_width, + .platform_bus_width = pxav2_mmc_set_width, }; #ifdef CONFIG_OF diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 82a8de148a8f..b16dae00cfd4 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -332,14 +332,14 @@ static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock) } /** - * sdhci_s3c_platform_8bit_width - support 8bit buswidth + * sdhci_s3c_platform_bus_width - support 8bit buswidth * @host: The SDHCI host being queried * @width: MMC_BUS_WIDTH_ macro for the bus width being requested * * We have 8-bit width support but is not a v3 controller. - * So we add platform_8bit_width() and support 8bit width. + * So we add platform_bus_width() and support 8bit width. */ -static int sdhci_s3c_platform_8bit_width(struct sdhci_host *host, int width) +static int sdhci_s3c_platform_bus_width(struct sdhci_host *host, int width) { u8 ctrl; @@ -369,7 +369,7 @@ static struct sdhci_ops sdhci_s3c_ops = { .get_max_clock = sdhci_s3c_get_max_clk, .set_clock = sdhci_s3c_set_clock, .get_min_clock = sdhci_s3c_get_min_clock, - .platform_8bit_width = sdhci_s3c_platform_8bit_width, + .platform_bus_width = sdhci_s3c_platform_bus_width, }; static void sdhci_s3c_notify_change(struct platform_device *dev, int state) diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index 3695b2e0cbd2..5a600a53b876 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -143,7 +143,7 @@ static void tegra_sdhci_reset_exit(struct sdhci_host *host, u8 mask) } } -static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) +static int tegra_sdhci_buswidth(struct sdhci_host *host, int bus_width) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_tegra *tegra_host = pltfm_host->priv; @@ -170,7 +170,7 @@ static struct sdhci_ops tegra_sdhci_ops = { .read_l = tegra_sdhci_readl, .read_w = tegra_sdhci_readw, .write_l = tegra_sdhci_writel, - .platform_8bit_width = tegra_sdhci_8bit, + .platform_bus_width = tegra_sdhci_buswidth, .platform_reset_exit = tegra_sdhci_reset_exit, }; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 336ab06aeb2f..3bb9b88772cf 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1395,11 +1395,11 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) /* * If your platform has 8-bit width support but is not a v3 controller, * or if it requires special setup code, you should implement that in - * platform_8bit_width(). + * platform_bus_width(). */ - if (host->ops->platform_8bit_width) - host->ops->platform_8bit_width(host, ios->bus_width); - else { + if (host->ops->platform_bus_width) { + host->ops->platform_bus_width(host, ios->bus_width); + } else { ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); if (ios->bus_width == MMC_BUS_WIDTH_8) { ctrl &= ~SDHCI_CTRL_4BITBUS; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index a6d69b7bdea2..c8d11b904a40 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -269,7 +269,7 @@ struct sdhci_ops { unsigned int (*get_max_clock)(struct sdhci_host *host); unsigned int (*get_min_clock)(struct sdhci_host *host); unsigned int (*get_timeout_clock)(struct sdhci_host *host); - int (*platform_8bit_width)(struct sdhci_host *host, + int (*platform_bus_width)(struct sdhci_host *host, int width); void (*platform_send_init_74_clocks)(struct sdhci_host *host, u8 power_mode); -- cgit v1.2.3 From af51079e68d4759e458b0592df5d1fab373c43ae Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Mon, 21 Jan 2013 19:02:28 +0800 Subject: mmc: sdhci-esdhc-imx: support 8bit mode The i.MX esdhc has a nonstandard bit layout for the SDHCI_HOST_CONTROL register. To support 8bit bus width on i.MX populate the platform_bus_width callback. This is tested on an i.MX25, but should according to the datasheets work on the other i.MX using this hardware aswell. The i.MX6, while having a SDHCI_SPEC_300 controller, still uses the same nonstandard register layout. Signed-off-by: Sascha Hauer Signed-off-by: Shawn Guo Tested-by: Dirk Behme Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-esdhc-imx.c | 56 +++++++++++++++++++++++++++-- include/linux/platform_data/mmc-esdhc-imx.h | 1 + 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 24daaf4e20ba..f7ee5e67516c 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -40,6 +40,13 @@ /* Bits 3 and 6 are not SDHCI standard definitions */ #define ESDHC_MIX_CTRL_SDHCI_MASK 0xb7 +/* + * Our interpretation of the SDHCI_HOST_CONTROL register + */ +#define ESDHC_CTRL_4BITBUS (0x1 << 1) +#define ESDHC_CTRL_8BITBUS (0x2 << 1) +#define ESDHC_CTRL_BUSWIDTH_MASK (0x3 << 1) + /* * There is an INT DMA ERR mis-match between eSDHC and STD SDHC SPEC: * Bit25 is used in STD SPEC, and is reserved in fsl eSDHC design, @@ -294,6 +301,7 @@ static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct pltfm_imx_data *imx_data = pltfm_host->priv; u32 new_val; + u32 mask; switch (reg) { case SDHCI_POWER_CONTROL: @@ -304,7 +312,7 @@ static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg) return; case SDHCI_HOST_CONTROL: /* FSL messed up here, so we need to manually compose it. */ - new_val = val & (SDHCI_CTRL_LED | SDHCI_CTRL_4BITBUS); + new_val = val & SDHCI_CTRL_LED; /* ensure the endianness */ new_val |= ESDHC_HOST_CONTROL_LE; /* bits 8&9 are reserved on mx25 */ @@ -313,7 +321,13 @@ static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg) new_val |= (val & SDHCI_CTRL_DMA_MASK) << 5; } - esdhc_clrset_le(host, 0xffff, new_val, reg); + /* + * Do not touch buswidth bits here. This is done in + * esdhc_pltfm_bus_width. + */ + mask = 0xffff & ~ESDHC_CTRL_BUSWIDTH_MASK; + + esdhc_clrset_le(host, mask, new_val, reg); return; } esdhc_clrset_le(host, 0xff, val, reg); @@ -370,6 +384,28 @@ static unsigned int esdhc_pltfm_get_ro(struct sdhci_host *host) return -ENOSYS; } +static int esdhc_pltfm_bus_width(struct sdhci_host *host, int width) +{ + u32 ctrl; + + switch (width) { + case MMC_BUS_WIDTH_8: + ctrl = ESDHC_CTRL_8BITBUS; + break; + case MMC_BUS_WIDTH_4: + ctrl = ESDHC_CTRL_4BITBUS; + break; + default: + ctrl = 0; + break; + } + + esdhc_clrset_le(host, ESDHC_CTRL_BUSWIDTH_MASK, ctrl, + SDHCI_HOST_CONTROL); + + return 0; +} + static struct sdhci_ops sdhci_esdhc_ops = { .read_l = esdhc_readl_le, .read_w = esdhc_readw_le, @@ -380,6 +416,7 @@ static struct sdhci_ops sdhci_esdhc_ops = { .get_max_clock = esdhc_pltfm_get_max_clock, .get_min_clock = esdhc_pltfm_get_min_clock, .get_ro = esdhc_pltfm_get_ro, + .platform_bus_width = esdhc_pltfm_bus_width, }; static struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = { @@ -417,6 +454,8 @@ sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, if (gpio_is_valid(boarddata->wp_gpio)) boarddata->wp_type = ESDHC_WP_GPIO; + of_property_read_u32(np, "bus-width", &boarddata->max_bus_width); + return 0; } #else @@ -548,6 +587,19 @@ static int sdhci_esdhc_imx_probe(struct platform_device *pdev) break; } + switch (boarddata->max_bus_width) { + case 8: + host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA; + break; + case 4: + host->mmc->caps |= MMC_CAP_4_BIT_DATA; + break; + case 1: + default: + host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA; + break; + } + err = sdhci_add_host(host); if (err) goto disable_clk; diff --git a/include/linux/platform_data/mmc-esdhc-imx.h b/include/linux/platform_data/mmc-esdhc-imx.h index aaf97481f413..b4a0521ce411 100644 --- a/include/linux/platform_data/mmc-esdhc-imx.h +++ b/include/linux/platform_data/mmc-esdhc-imx.h @@ -39,5 +39,6 @@ struct esdhc_platform_data { unsigned int cd_gpio; enum wp_types wp_type; enum cd_types cd_type; + int max_bus_width; }; #endif /* __ASM_ARCH_IMX_ESDHC_H */ -- cgit v1.2.3 From 28c2a62bd53300784b9e6815ae115fb888ac3cfd Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Mon, 28 Jan 2013 16:49:11 -0500 Subject: mmc: dt: bus-width can be an optional property None of mmc drivers implements bus-width as a required device tree property. Instead, some drivers like atmel-mci, dw_mmc, sdhci-s3c implement it as an optional one, and will force bus width to be 1 when the property is absent. Let's change the common binding to reflect what the drivers are usually doing. Signed-off-by: Shawn Guo Signed-off-by: Chris Ball --- Documentation/devicetree/bindings/mmc/mmc.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt index 34f28ed97928..469cf53d5a92 100644 --- a/Documentation/devicetree/bindings/mmc/mmc.txt +++ b/Documentation/devicetree/bindings/mmc/mmc.txt @@ -6,9 +6,6 @@ Interpreted by the OF core: - reg: Registers location and length. - interrupts: Interrupts used by the MMC controller. -Required properties: -- bus-width: Number of data lines, can be <1>, <4>, or <8> - Card detection: If no property below is supplied, host native card detect is used. Only one of the properties in this section should be supplied: @@ -17,6 +14,8 @@ Only one of the properties in this section should be supplied: - non-removable: non-removable slot (like eMMC); assume always present. Optional properties: +- bus-width: Number of data lines, can be <1>, <4>, or <8>. The default + will be <1> if the property is absent. - wp-gpios: Specify GPIOs for write protection, see gpio binding - cd-inverted: when present, polarity on the cd gpio line is inverted - wp-inverted: when present, polarity on the wp gpio line is inverted -- cgit v1.2.3 From 85c34d2e7b0e0425cd3efc243ffc4e3631daa0b5 Mon Sep 17 00:00:00 2001 From: Mike Lockwood Date: Mon, 21 Jan 2013 23:43:46 +0000 Subject: mmc: goldfish: emulated MMC device This driver handles the virtual MMC device present in the Goldfish emulator. The patch folds together initial work from Mike Lockwood and patches by San Mehat, Jun Nakajima and Tom Keel plus cleanups by Alan Cox to get it all into 3.6 shape. Signed-off-by: Mike A. Chan [cleaned up and x86 support added] Signed-off-by: Sheng Yang Signed-off-by: Yunhong Jiang Signed-off-by: Xiaohui Xin Signed-off-by: Jun Nakajima Signed-off-by: Bruce Beare [Moved to 3.4] Signed-off-by: Tom Keel [Moved to 3.7] Signed-off-by: Alan Cox Signed-off-by: Chris Ball --- drivers/mmc/host/Kconfig | 7 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/android-goldfish.c | 570 ++++++++++++++++++++++++++++++++++++ 3 files changed, 578 insertions(+) create mode 100644 drivers/mmc/host/android-goldfish.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 66a54aa68e25..961891fe61bb 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -375,6 +375,13 @@ config MMC_DAVINCI If you have an DAVINCI board with a Multimedia Card slot, say Y or M here. If unsure, say N. +config MMC_GOLDFISH + tristate "goldfish qemu Multimedia Card Interface support" + depends on GOLDFISH + help + This selects the Goldfish Multimedia card Interface emulation + found on the Goldfish Android virtual device emulation. + config MMC_SPI tristate "MMC/SD/SDIO over SPI" depends on SPI_MASTER && !HIGHMEM && HAS_DMA diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index d5ea072207ec..c380e3cf0a3b 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o obj-$(CONFIG_MMC_MSM) += msm_sdcc.o obj-$(CONFIG_MMC_MVSDIO) += mvsdio.o obj-$(CONFIG_MMC_DAVINCI) += davinci_mmc.o +obj-$(CONFIG_MMC_GOLDFISH) += android-goldfish.o obj-$(CONFIG_MMC_SPI) += mmc_spi.o ifeq ($(CONFIG_OF),y) obj-$(CONFIG_MMC_SPI) += of_mmc_spi.o diff --git a/drivers/mmc/host/android-goldfish.c b/drivers/mmc/host/android-goldfish.c new file mode 100644 index 000000000000..ef3aef0f376d --- /dev/null +++ b/drivers/mmc/host/android-goldfish.c @@ -0,0 +1,570 @@ +/* + * Copyright 2007, Google Inc. + * Copyright 2012, Intel Inc. + * + * based on omap.c driver, which was + * Copyright (C) 2004 Nokia Corporation + * Written by Tuukka Tikkanen and Juha Yrjölä + * Misc hacks here and there by Tony Lindgren + * Other hacks (DMA, SD, etc) by David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#define DRIVER_NAME "goldfish_mmc" + +#define BUFFER_SIZE 16384 + +#define GOLDFISH_MMC_READ(host, addr) (readl(host->reg_base + addr)) +#define GOLDFISH_MMC_WRITE(host, addr, x) (writel(x, host->reg_base + addr)) + +enum { + /* status register */ + MMC_INT_STATUS = 0x00, + /* set this to enable IRQ */ + MMC_INT_ENABLE = 0x04, + /* set this to specify buffer address */ + MMC_SET_BUFFER = 0x08, + + /* MMC command number */ + MMC_CMD = 0x0C, + + /* MMC argument */ + MMC_ARG = 0x10, + + /* MMC response (or R2 bits 0 - 31) */ + MMC_RESP_0 = 0x14, + + /* MMC R2 response bits 32 - 63 */ + MMC_RESP_1 = 0x18, + + /* MMC R2 response bits 64 - 95 */ + MMC_RESP_2 = 0x1C, + + /* MMC R2 response bits 96 - 127 */ + MMC_RESP_3 = 0x20, + + MMC_BLOCK_LENGTH = 0x24, + MMC_BLOCK_COUNT = 0x28, + + /* MMC state flags */ + MMC_STATE = 0x2C, + + /* MMC_INT_STATUS bits */ + + MMC_STAT_END_OF_CMD = 1U << 0, + MMC_STAT_END_OF_DATA = 1U << 1, + MMC_STAT_STATE_CHANGE = 1U << 2, + MMC_STAT_CMD_TIMEOUT = 1U << 3, + + /* MMC_STATE bits */ + MMC_STATE_INSERTED = 1U << 0, + MMC_STATE_READ_ONLY = 1U << 1, +}; + +/* + * Command types + */ +#define OMAP_MMC_CMDTYPE_BC 0 +#define OMAP_MMC_CMDTYPE_BCR 1 +#define OMAP_MMC_CMDTYPE_AC 2 +#define OMAP_MMC_CMDTYPE_ADTC 3 + + +struct goldfish_mmc_host { + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + struct mmc_host *mmc; + struct device *dev; + unsigned char id; /* 16xx chips have 2 MMC blocks */ + void __iomem *virt_base; + unsigned int phys_base; + int irq; + unsigned char bus_mode; + unsigned char hw_bus_mode; + + unsigned int sg_len; + unsigned dma_done:1; + unsigned dma_in_use:1; + + void __iomem *reg_base; +}; + +static inline int +goldfish_mmc_cover_is_open(struct goldfish_mmc_host *host) +{ + return 0; +} + +static ssize_t +goldfish_mmc_show_cover_switch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct goldfish_mmc_host *host = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", goldfish_mmc_cover_is_open(host) ? "open" : + "closed"); +} + +static DEVICE_ATTR(cover_switch, S_IRUGO, goldfish_mmc_show_cover_switch, NULL); + +static void +goldfish_mmc_start_command(struct goldfish_mmc_host *host, struct mmc_command *cmd) +{ + u32 cmdreg; + u32 resptype; + u32 cmdtype; + + host->cmd = cmd; + + resptype = 0; + cmdtype = 0; + + /* Our hardware needs to know exact type */ + switch (mmc_resp_type(cmd)) { + case MMC_RSP_NONE: + break; + case MMC_RSP_R1: + case MMC_RSP_R1B: + /* resp 1, 1b, 6, 7 */ + resptype = 1; + break; + case MMC_RSP_R2: + resptype = 2; + break; + case MMC_RSP_R3: + resptype = 3; + break; + default: + dev_err(mmc_dev(host->mmc), + "Invalid response type: %04x\n", mmc_resp_type(cmd)); + break; + } + + if (mmc_cmd_type(cmd) == MMC_CMD_ADTC) + cmdtype = OMAP_MMC_CMDTYPE_ADTC; + else if (mmc_cmd_type(cmd) == MMC_CMD_BC) + cmdtype = OMAP_MMC_CMDTYPE_BC; + else if (mmc_cmd_type(cmd) == MMC_CMD_BCR) + cmdtype = OMAP_MMC_CMDTYPE_BCR; + else + cmdtype = OMAP_MMC_CMDTYPE_AC; + + cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12); + + if (host->bus_mode == MMC_BUSMODE_OPENDRAIN) + cmdreg |= 1 << 6; + + if (cmd->flags & MMC_RSP_BUSY) + cmdreg |= 1 << 11; + + if (host->data && !(host->data->flags & MMC_DATA_WRITE)) + cmdreg |= 1 << 15; + + GOLDFISH_MMC_WRITE(host, MMC_ARG, cmd->arg); + GOLDFISH_MMC_WRITE(host, MMC_CMD, cmdreg); +} + +static void goldfish_mmc_xfer_done(struct goldfish_mmc_host *host, + struct mmc_data *data) +{ + if (host->dma_in_use) { + enum dma_data_direction dma_data_dir; + + if (data->flags & MMC_DATA_WRITE) + dma_data_dir = DMA_TO_DEVICE; + else + dma_data_dir = DMA_FROM_DEVICE; + + if (dma_data_dir == DMA_FROM_DEVICE) { + /* + * We don't really have DMA, so we need + * to copy from our platform driver buffer + */ + uint8_t *dest = (uint8_t *)sg_virt(data->sg); + memcpy(dest, host->virt_base, data->sg->length); + } + host->data->bytes_xfered += data->sg->length; + dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len, + dma_data_dir); + } + + host->data = NULL; + host->sg_len = 0; + + /* + * NOTE: MMC layer will sometimes poll-wait CMD13 next, issuing + * dozens of requests until the card finishes writing data. + * It'd be cheaper to just wait till an EOFB interrupt arrives... + */ + + if (!data->stop) { + host->mrq = NULL; + mmc_request_done(host->mmc, data->mrq); + return; + } + + goldfish_mmc_start_command(host, data->stop); +} + +static void goldfish_mmc_end_of_data(struct goldfish_mmc_host *host, + struct mmc_data *data) +{ + if (!host->dma_in_use) { + goldfish_mmc_xfer_done(host, data); + return; + } + if (host->dma_done) + goldfish_mmc_xfer_done(host, data); +} + +static void goldfish_mmc_cmd_done(struct goldfish_mmc_host *host, + struct mmc_command *cmd) +{ + host->cmd = NULL; + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + /* response type 2 */ + cmd->resp[3] = + GOLDFISH_MMC_READ(host, MMC_RESP_0); + cmd->resp[2] = + GOLDFISH_MMC_READ(host, MMC_RESP_1); + cmd->resp[1] = + GOLDFISH_MMC_READ(host, MMC_RESP_2); + cmd->resp[0] = + GOLDFISH_MMC_READ(host, MMC_RESP_3); + } else { + /* response types 1, 1b, 3, 4, 5, 6 */ + cmd->resp[0] = + GOLDFISH_MMC_READ(host, MMC_RESP_0); + } + } + + if (host->data == NULL || cmd->error) { + host->mrq = NULL; + mmc_request_done(host->mmc, cmd->mrq); + } +} + +static irqreturn_t goldfish_mmc_irq(int irq, void *dev_id) +{ + struct goldfish_mmc_host *host = (struct goldfish_mmc_host *)dev_id; + u16 status; + int end_command = 0; + int end_transfer = 0; + int transfer_error = 0; + int state_changed = 0; + int cmd_timeout = 0; + + while ((status = GOLDFISH_MMC_READ(host, MMC_INT_STATUS)) != 0) { + GOLDFISH_MMC_WRITE(host, MMC_INT_STATUS, status); + + if (status & MMC_STAT_END_OF_CMD) + end_command = 1; + + if (status & MMC_STAT_END_OF_DATA) + end_transfer = 1; + + if (status & MMC_STAT_STATE_CHANGE) + state_changed = 1; + + if (status & MMC_STAT_CMD_TIMEOUT) { + end_command = 0; + cmd_timeout = 1; + } + } + + if (cmd_timeout) { + struct mmc_request *mrq = host->mrq; + mrq->cmd->error = -ETIMEDOUT; + host->mrq = NULL; + mmc_request_done(host->mmc, mrq); + } + + if (end_command) + goldfish_mmc_cmd_done(host, host->cmd); + + if (transfer_error) + goldfish_mmc_xfer_done(host, host->data); + else if (end_transfer) { + host->dma_done = 1; + goldfish_mmc_end_of_data(host, host->data); + } else if (host->data != NULL) { + /* + * WORKAROUND -- after porting this driver from 2.6 to 3.4, + * during device initialization, cases where host->data is + * non-null but end_transfer is false would occur. Doing + * nothing in such cases results in no further interrupts, + * and initialization failure. + * TODO -- find the real cause. + */ + host->dma_done = 1; + goldfish_mmc_end_of_data(host, host->data); + } + + if (state_changed) { + u32 state = GOLDFISH_MMC_READ(host, MMC_STATE); + pr_info("%s: Card detect now %d\n", __func__, + (state & MMC_STATE_INSERTED)); + mmc_detect_change(host->mmc, 0); + } + + if (!end_command && !end_transfer && + !transfer_error && !state_changed && !cmd_timeout) { + status = GOLDFISH_MMC_READ(host, MMC_INT_STATUS); + dev_info(mmc_dev(host->mmc),"spurious irq 0x%04x\n", status); + if (status != 0) { + GOLDFISH_MMC_WRITE(host, MMC_INT_STATUS, status); + GOLDFISH_MMC_WRITE(host, MMC_INT_ENABLE, 0); + } + } + + return IRQ_HANDLED; +} + +static void goldfish_mmc_prepare_data(struct goldfish_mmc_host *host, + struct mmc_request *req) +{ + struct mmc_data *data = req->data; + int block_size; + unsigned sg_len; + enum dma_data_direction dma_data_dir; + + host->data = data; + if (data == NULL) { + GOLDFISH_MMC_WRITE(host, MMC_BLOCK_LENGTH, 0); + GOLDFISH_MMC_WRITE(host, MMC_BLOCK_COUNT, 0); + host->dma_in_use = 0; + return; + } + + block_size = data->blksz; + + GOLDFISH_MMC_WRITE(host, MMC_BLOCK_COUNT, data->blocks - 1); + GOLDFISH_MMC_WRITE(host, MMC_BLOCK_LENGTH, block_size - 1); + + /* + * Cope with calling layer confusion; it issues "single + * block" writes using multi-block scatterlists. + */ + sg_len = (data->blocks == 1) ? 1 : data->sg_len; + + if (data->flags & MMC_DATA_WRITE) + dma_data_dir = DMA_TO_DEVICE; + else + dma_data_dir = DMA_FROM_DEVICE; + + host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg, + sg_len, dma_data_dir); + host->dma_done = 0; + host->dma_in_use = 1; + + if (dma_data_dir == DMA_TO_DEVICE) { + /* + * We don't really have DMA, so we need to copy to our + * platform driver buffer + */ + const uint8_t *src = (uint8_t *)sg_virt(data->sg); + memcpy(host->virt_base, src, data->sg->length); + } +} + +static void goldfish_mmc_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct goldfish_mmc_host *host = mmc_priv(mmc); + + WARN_ON(host->mrq != NULL); + + host->mrq = req; + goldfish_mmc_prepare_data(host, req); + goldfish_mmc_start_command(host, req->cmd); + + /* + * This is to avoid accidentally being detected as an SDIO card + * in mmc_attach_sdio(). + */ + if (req->cmd->opcode == SD_IO_SEND_OP_COND && + req->cmd->flags == (MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR)) + req->cmd->error = -EINVAL; +} + +static void goldfish_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct goldfish_mmc_host *host = mmc_priv(mmc); + + host->bus_mode = ios->bus_mode; + host->hw_bus_mode = host->bus_mode; +} + +static int goldfish_mmc_get_ro(struct mmc_host *mmc) +{ + uint32_t state; + struct goldfish_mmc_host *host = mmc_priv(mmc); + + state = GOLDFISH_MMC_READ(host, MMC_STATE); + return ((state & MMC_STATE_READ_ONLY) != 0); +} + +static const struct mmc_host_ops goldfish_mmc_ops = { + .request = goldfish_mmc_request, + .set_ios = goldfish_mmc_set_ios, + .get_ro = goldfish_mmc_get_ro, +}; + +static int goldfish_mmc_probe(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct goldfish_mmc_host *host = NULL; + struct resource *res; + int ret = 0; + int irq; + dma_addr_t buf_addr; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (res == NULL || irq < 0) + return -ENXIO; + + mmc = mmc_alloc_host(sizeof(struct goldfish_mmc_host), &pdev->dev); + if (mmc == NULL) { + ret = -ENOMEM; + goto err_alloc_host_failed; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + + pr_err("mmc: Mapping %lX to %lX\n", (long)res->start, (long)res->end); + host->reg_base = ioremap(res->start, res->end - res->start + 1); + if (host->reg_base == NULL) { + ret = -ENOMEM; + goto ioremap_failed; + } + host->virt_base = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE, + &buf_addr, GFP_KERNEL); + + if (host->virt_base == 0) { + ret = -ENOMEM; + goto dma_alloc_failed; + } + host->phys_base = buf_addr; + + host->id = pdev->id; + host->irq = irq; + + mmc->ops = &goldfish_mmc_ops; + mmc->f_min = 400000; + mmc->f_max = 24000000; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->caps = MMC_CAP_4_BIT_DATA; + + /* Use scatterlist DMA to reduce per-transfer costs. + * NOTE max_seg_size assumption that small blocks aren't + * normally used (except e.g. for reading SD registers). + */ + mmc->max_segs = 32; + mmc->max_blk_size = 2048; /* MMC_BLOCK_LENGTH is 11 bits (+1) */ + mmc->max_blk_count = 2048; /* MMC_BLOCK_COUNT is 11 bits (+1) */ + mmc->max_req_size = BUFFER_SIZE; + mmc->max_seg_size = mmc->max_req_size; + + ret = request_irq(host->irq, goldfish_mmc_irq, 0, DRIVER_NAME, host); + if (ret) { + dev_err(&pdev->dev, "Failed IRQ Adding goldfish MMC\n"); + goto err_request_irq_failed; + } + + host->dev = &pdev->dev; + platform_set_drvdata(pdev, host); + + ret = device_create_file(&pdev->dev, &dev_attr_cover_switch); + if (ret) + dev_warn(mmc_dev(host->mmc), + "Unable to create sysfs attributes\n"); + + GOLDFISH_MMC_WRITE(host, MMC_SET_BUFFER, host->phys_base); + GOLDFISH_MMC_WRITE(host, MMC_INT_ENABLE, + MMC_STAT_END_OF_CMD | MMC_STAT_END_OF_DATA | + MMC_STAT_STATE_CHANGE | MMC_STAT_CMD_TIMEOUT); + + mmc_add_host(mmc); + return 0; + +err_request_irq_failed: + dma_free_coherent(&pdev->dev, BUFFER_SIZE, host->virt_base, + host->phys_base); +dma_alloc_failed: + iounmap(host->reg_base); +ioremap_failed: + mmc_free_host(host->mmc); +err_alloc_host_failed: + return ret; +} + +static int goldfish_mmc_remove(struct platform_device *pdev) +{ + struct goldfish_mmc_host *host = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + BUG_ON(host == NULL); + + mmc_remove_host(host->mmc); + free_irq(host->irq, host); + dma_free_coherent(&pdev->dev, BUFFER_SIZE, host->virt_base, host->phys_base); + iounmap(host->reg_base); + mmc_free_host(host->mmc); + return 0; +} + +static struct platform_driver goldfish_mmc_driver = { + .probe = goldfish_mmc_probe, + .remove = goldfish_mmc_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; + +module_platform_driver(goldfish_mmc_driver); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From d0123ccac55088811bde4f76c4a3fdbd39c3cfba Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Fri, 25 Jan 2013 17:00:30 +0530 Subject: mmc: core: expose RPMB partition only for CMD23 capable hosts SET_BLOCK_COUNT CMD23 is needed for all access to RPMB partition. If block count is not set by CMD23, all subsequent read/write commands fail as per eMMC specification. So, If the host does not support CMD23, do not expose RPMB partition. Accessing RPMB partition can cause hang / huge delay for hosts which do not support CMD23. Signed-off-by: Balaji T K Reported-and-Tested-by: Peter Ujfalusi Cc: stable Signed-off-by: Chris Ball --- drivers/mmc/core/mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index dc17d4097f9a..0d7a9795a6da 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -496,7 +496,7 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) * RPMB regions are defined in multiples of 128K. */ card->ext_csd.raw_rpmb_size_mult = ext_csd[EXT_CSD_RPMB_MULT]; - if (ext_csd[EXT_CSD_RPMB_MULT]) { + if (ext_csd[EXT_CSD_RPMB_MULT] && mmc_host_cmd23(card->host)) { mmc_part_add(card, ext_csd[EXT_CSD_RPMB_MULT] << 17, EXT_CSD_PART_CONFIG_ACC_RPMB, "rpmb", 0, false, -- cgit v1.2.3 From 3f8a7fabd60e83eb2fb7add00d353d455c0e7a78 Mon Sep 17 00:00:00 2001 From: Johan Rudholm Date: Mon, 28 Jan 2013 15:08:24 +0100 Subject: mmc: sd: Simplify by using mmc_host_uhs Signed-off-by: Johan Rudholm Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/sd.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 74972c241dff..937363948079 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -444,8 +444,7 @@ static void sd_update_bus_speed_mode(struct mmc_card *card) * If the host doesn't support any of the UHS-I modes, fallback on * default speed. */ - if (!(card->host->caps & (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | - MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_DDR50))) { + if (!mmc_host_uhs(card->host)) { card->sd_bus_speed = 0; return; } @@ -736,8 +735,7 @@ int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr) * If the host supports one of UHS-I modes, request the card * to switch to 1.8V signaling level. */ - if (host->caps & (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | - MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_DDR50)) + if (mmc_host_uhs(host)) ocr |= SD_OCR_S18R; /* -- cgit v1.2.3 From 276e090f9217b69e46d8255132d2667ac5eec51b Mon Sep 17 00:00:00 2001 From: Johan Rudholm Date: Mon, 28 Jan 2013 15:08:25 +0100 Subject: mmc: core: Add mmc_power_cycle Add mmc_power_cycle which can be used to power cycle for instance SD-cards. Signed-off-by: Johan Rudholm Acked-by: Ulf Hansson Tested-by: Wei WANG Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 8 ++++++++ drivers/mmc/core/core.h | 1 + 2 files changed, 9 insertions(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 39f28af34cb6..789056fbfc93 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1470,6 +1470,14 @@ void mmc_power_off(struct mmc_host *host) mmc_host_clk_release(host); } +void mmc_power_cycle(struct mmc_host *host) +{ + mmc_power_off(host); + /* Wait at least 1 ms according to SD spec */ + mmc_delay(1); + mmc_power_up(host); +} + /* * Cleanup when the last reference to the bus operator is dropped. */ diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 0272b3284b5e..9007de74d8a9 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -45,6 +45,7 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, void mmc_set_timing(struct mmc_host *host, unsigned int timing); void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type); void mmc_power_off(struct mmc_host *host); +void mmc_power_cycle(struct mmc_host *host); static inline void mmc_delay(unsigned int ms) { -- cgit v1.2.3 From d887874e0ead6a0b86b6046b872730c81c121352 Mon Sep 17 00:00:00 2001 From: Johan Rudholm Date: Mon, 28 Jan 2013 15:08:26 +0100 Subject: mmc: core: Add card_busy to host_ops This host_ops member is used to test if the card is signaling busy by pulling dat[0:3] low. Signed-off-by: Johan Rudholm Acked-by: Ulf Hansson Tested-by: Wei WANG Signed-off-by: Chris Ball --- include/linux/mmc/host.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 523d570f58ad..0373b0a6daac 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -131,6 +131,9 @@ struct mmc_host_ops { int (*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios); + /* Check if the card is pulling dat[0:3] low */ + int (*card_busy)(struct mmc_host *host); + /* The tuning command opcode value is different for SD and eMMC cards */ int (*execute_tuning)(struct mmc_host *host, u32 opcode); void (*enable_preset_value)(struct mmc_host *host, bool enable); -- cgit v1.2.3 From 567c89032cfdda8047562abe450947ac01f2d3c7 Mon Sep 17 00:00:00 2001 From: Johan Rudholm Date: Mon, 28 Jan 2013 15:08:27 +0100 Subject: mmc: core: Break out start_signal_voltage_switch Allow callers to access the start_signal_voltage_switch host_ops member without going through any cmd11 logic. This is mostly a preparation for the following signal voltage switch patch. Also, reset ios.signal_voltage to its original value if start_signal_voltage_switch fails. Signed-off-by: Johan Rudholm Acked-by: Ulf Hansson Tested-by: Wei WANG Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 35 +++++++++++++++++++++++------------ drivers/mmc/core/core.h | 4 ++-- drivers/mmc/core/mmc.c | 8 ++++---- drivers/mmc/core/sd.c | 2 +- drivers/mmc/core/sdio.c | 3 +-- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 789056fbfc93..bb794c784597 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1317,7 +1317,26 @@ u32 mmc_select_voltage(struct mmc_host *host, u32 ocr) return ocr; } -int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, bool cmd11) +int __mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage) +{ + int err = 0; + int old_signal_voltage = host->ios.signal_voltage; + + host->ios.signal_voltage = signal_voltage; + if (host->ops->start_signal_voltage_switch) { + mmc_host_clk_hold(host); + err = host->ops->start_signal_voltage_switch(host, &host->ios); + mmc_host_clk_release(host); + } + + if (err) + host->ios.signal_voltage = old_signal_voltage; + + return err; + +} + +int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage) { struct mmc_command cmd = {0}; int err = 0; @@ -1328,7 +1347,7 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, bool cmd11 * Send CMD11 only if the request is to switch the card to * 1.8V signalling. */ - if ((signal_voltage != MMC_SIGNAL_VOLTAGE_330) && cmd11) { + if (signal_voltage != MMC_SIGNAL_VOLTAGE_330) { cmd.opcode = SD_SWITCH_VOLTAGE; cmd.arg = 0; cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; @@ -1341,15 +1360,7 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, bool cmd11 return -EIO; } - host->ios.signal_voltage = signal_voltage; - - if (host->ops->start_signal_voltage_switch) { - mmc_host_clk_hold(host); - err = host->ops->start_signal_voltage_switch(host, &host->ios); - mmc_host_clk_release(host); - } - - return err; + return __mmc_set_signal_voltage(host, signal_voltage); } /* @@ -1412,7 +1423,7 @@ static void mmc_power_up(struct mmc_host *host) mmc_set_ios(host); /* Set signal voltage to 3.3V */ - mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330, false); + __mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330); /* * This delay should be sufficient to allow the power supply diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 9007de74d8a9..b9f18a2a8874 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -40,8 +40,8 @@ void mmc_set_ungated(struct mmc_host *host); void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode); void mmc_set_bus_width(struct mmc_host *host, unsigned int width); u32 mmc_select_voltage(struct mmc_host *host, u32 ocr); -int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, - bool cmd11); +int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage); +int __mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage); void mmc_set_timing(struct mmc_host *host, unsigned int timing); void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type); void mmc_power_off(struct mmc_host *host); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 0d7a9795a6da..8a3ad602a877 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -769,11 +769,11 @@ static int mmc_select_hs200(struct mmc_card *card) if (card->ext_csd.card_type & EXT_CSD_CARD_TYPE_SDR_1_2V && host->caps2 & MMC_CAP2_HS200_1_2V_SDR) - err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_120, 0); + err = __mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_120); if (err && card->ext_csd.card_type & EXT_CSD_CARD_TYPE_SDR_1_8V && host->caps2 & MMC_CAP2_HS200_1_8V_SDR) - err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180, 0); + err = __mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180); /* If fails try again during next card power cycle */ if (err) @@ -1221,8 +1221,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * WARNING: eMMC rules are NOT the same as SD DDR */ if (ddr == MMC_1_2V_DDR_MODE) { - err = mmc_set_signal_voltage(host, - MMC_SIGNAL_VOLTAGE_120, 0); + err = __mmc_set_signal_voltage(host, + MMC_SIGNAL_VOLTAGE_120); if (err) goto err; } diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 937363948079..9a59fcd55a75 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -757,7 +757,7 @@ try_again: */ if (!mmc_host_is_spi(host) && rocr && ((*rocr & 0x41000000) == 0x41000000)) { - err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180, true); + err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180); if (err) { ocr &= ~SD_OCR_S18R; goto try_again; diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 3a64933466b8..1a726aef211d 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -647,8 +647,7 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, * it. */ if ((ocr & R4_18V_PRESENT) && mmc_host_uhs(host)) { - err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180, - true); + err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180); if (err) { ocr &= ~R4_18V_PRESENT; host->ocr &= ~R4_18V_PRESENT; -- cgit v1.2.3 From 0797e5f1453b2bedc08bbcbea0ea4fbe20350823 Mon Sep 17 00:00:00 2001 From: Johan Rudholm Date: Mon, 28 Jan 2013 15:08:28 +0100 Subject: mmc: core: Fixup signal voltage switch When switching SD and SDIO cards from 3.3V to 1.8V signal levels, the clock should be gated for 5 ms during the step. After enabling the clock, the host should wait for at least 1 ms before checking for failure. Failure by the card to switch is indicated by dat[0:3] being pulled low. The host should check for this condition and power-cycle the card if failure is indicated. Add a retry mechanism for the SDIO case. If the voltage switch fails repeatedly, give up and continue the initialization using the original voltage. This patch places a couple of requirements on the host driver: 1) mmc_set_ios with ios.clock = 0 must gate the clock 2) mmc_power_off must actually cut the power to the card 3) The card_busy host_ops member must be implemented if these requirements are not fulfilled, the 1.8V signal voltage switch will still be attempted but may not be successful. Signed-off-by: Johan Rudholm Signed-off-by: Kevin Liu Acked-by: Ulf Hansson Tested-by: Wei WANG Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 83 +++++++++++++++++++++++++++++++++++++++++++------ drivers/mmc/core/sd.c | 21 ++++++++++--- drivers/mmc/core/sdio.c | 20 ++++++++++-- 3 files changed, 107 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index bb794c784597..e41badbf9b50 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1340,6 +1340,7 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage) { struct mmc_command cmd = {0}; int err = 0; + u32 clock; BUG_ON(!host); @@ -1347,20 +1348,82 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage) * Send CMD11 only if the request is to switch the card to * 1.8V signalling. */ - if (signal_voltage != MMC_SIGNAL_VOLTAGE_330) { - cmd.opcode = SD_SWITCH_VOLTAGE; - cmd.arg = 0; - cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + if (signal_voltage == MMC_SIGNAL_VOLTAGE_330) + return __mmc_set_signal_voltage(host, signal_voltage); - err = mmc_wait_for_cmd(host, &cmd, 0); - if (err) - return err; + /* + * If we cannot switch voltages, return failure so the caller + * can continue without UHS mode + */ + if (!host->ops->start_signal_voltage_switch) + return -EPERM; + if (!host->ops->card_busy) + pr_warning("%s: cannot verify signal voltage switch\n", + mmc_hostname(host)); + + cmd.opcode = SD_SWITCH_VOLTAGE; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + + err = mmc_wait_for_cmd(host, &cmd, 0); + if (err) + return err; - if (!mmc_host_is_spi(host) && (cmd.resp[0] & R1_ERROR)) - return -EIO; + if (!mmc_host_is_spi(host) && (cmd.resp[0] & R1_ERROR)) + return -EIO; + + mmc_host_clk_hold(host); + /* + * The card should drive cmd and dat[0:3] low immediately + * after the response of cmd11, but wait 1 ms to be sure + */ + mmc_delay(1); + if (host->ops->card_busy && !host->ops->card_busy(host)) { + err = -EAGAIN; + goto power_cycle; } + /* + * During a signal voltage level switch, the clock must be gated + * for 5 ms according to the SD spec + */ + clock = host->ios.clock; + host->ios.clock = 0; + mmc_set_ios(host); - return __mmc_set_signal_voltage(host, signal_voltage); + if (__mmc_set_signal_voltage(host, signal_voltage)) { + /* + * Voltages may not have been switched, but we've already + * sent CMD11, so a power cycle is required anyway + */ + err = -EAGAIN; + goto power_cycle; + } + + /* Keep clock gated for at least 5 ms */ + mmc_delay(5); + host->ios.clock = clock; + mmc_set_ios(host); + + /* Wait for at least 1 ms according to spec */ + mmc_delay(1); + + /* + * Failure to switch is indicated by the card holding + * dat[0:3] low + */ + if (host->ops->card_busy && host->ops->card_busy(host)) + err = -EAGAIN; + +power_cycle: + if (err) { + pr_debug("%s: Signal voltage switch failed, " + "power cycling card\n", mmc_hostname(host)); + mmc_power_cycle(host); + } + + mmc_host_clk_release(host); + + return err; } /* diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 9a59fcd55a75..03134b1e563c 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -712,6 +712,14 @@ int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr) { int err; u32 max_current; + int retries = 10; + +try_again: + if (!retries) { + ocr &= ~SD_OCR_S18R; + pr_warning("%s: Skipping voltage switch\n", + mmc_hostname(host)); + } /* * Since we're changing the OCR value, we seem to @@ -733,9 +741,10 @@ int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr) /* * If the host supports one of UHS-I modes, request the card - * to switch to 1.8V signaling level. + * to switch to 1.8V signaling level. If the card has failed + * repeatedly to switch however, skip this. */ - if (mmc_host_uhs(host)) + if (retries && mmc_host_uhs(host)) ocr |= SD_OCR_S18R; /* @@ -746,7 +755,6 @@ int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr) if (max_current > 150) ocr |= SD_OCR_XPC; -try_again: err = mmc_send_app_op_cond(host, ocr, rocr); if (err) return err; @@ -758,8 +766,11 @@ try_again: if (!mmc_host_is_spi(host) && rocr && ((*rocr & 0x41000000) == 0x41000000)) { err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180); - if (err) { - ocr &= ~SD_OCR_S18R; + if (err == -EAGAIN) { + retries--; + goto try_again; + } else if (err) { + retries = 0; goto try_again; } } diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 1a726aef211d..aa0719a4dfd1 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -584,10 +584,19 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, { struct mmc_card *card; int err; + int retries = 10; BUG_ON(!host); WARN_ON(!host->claimed); +try_again: + if (!retries) { + pr_warning("%s: Skipping voltage switch\n", + mmc_hostname(host)); + ocr &= ~R4_18V_PRESENT; + host->ocr &= ~R4_18V_PRESENT; + } + /* * Inform the card of the voltage */ @@ -646,9 +655,16 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, * systems that claim 1.8v signalling in fact do not support * it. */ - if ((ocr & R4_18V_PRESENT) && mmc_host_uhs(host)) { + if (!powered_resume && (ocr & R4_18V_PRESENT) && mmc_host_uhs(host)) { err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180); - if (err) { + if (err == -EAGAIN) { + sdio_reset(host); + mmc_go_idle(host); + mmc_send_if_cond(host, host->ocr_avail); + mmc_remove_card(card); + retries--; + goto try_again; + } else if (err) { ocr &= ~R4_18V_PRESENT; host->ocr &= ~R4_18V_PRESENT; } -- cgit v1.2.3 From 20b92a30b5610a5222060417961bc4ccb42ea5a5 Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Mon, 17 Dec 2012 19:29:26 +0800 Subject: mmc: sdhci: update signal voltage switch code The protocol related code is moved to core stack. So update the host driver accordingly. Signed-off-by: Kevin Liu Tested-by: Tim Wang Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 192 +++++++++++++++++++---------------------------- 1 file changed, 78 insertions(+), 114 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 3bb9b88772cf..efb112684787 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1615,145 +1615,95 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) spin_unlock_irqrestore(&host->lock, flags); } -static int sdhci_do_3_3v_signal_voltage_switch(struct sdhci_host *host, - u16 ctrl) +static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, + int signal_voltage) { + u16 ctrl; int ret; - /* Set 1.8V Signal Enable in the Host Control2 register to 0 */ - ctrl &= ~SDHCI_CTRL_VDD_180; - sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); - - if (host->vqmmc) { - ret = regulator_set_voltage(host->vqmmc, 2700000, 3600000); - if (ret) { - pr_warning("%s: Switching to 3.3V signalling voltage " - " failed\n", mmc_hostname(host->mmc)); - return -EIO; - } - } - /* Wait for 5ms */ - usleep_range(5000, 5500); + /* + * Signal Voltage Switching is only applicable for Host Controllers + * v3.00 and above. + */ + if (host->version < SDHCI_SPEC_300) + return 0; - /* 3.3V regulator output should be stable within 5 ms */ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); - if (!(ctrl & SDHCI_CTRL_VDD_180)) - return 0; - pr_warning("%s: 3.3V regulator output did not became stable\n", - mmc_hostname(host->mmc)); + switch (signal_voltage) { + case MMC_SIGNAL_VOLTAGE_330: + /* Set 1.8V Signal Enable in the Host Control2 register to 0 */ + ctrl &= ~SDHCI_CTRL_VDD_180; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); - return -EIO; -} + if (host->vqmmc) { + ret = regulator_set_voltage(host->vqmmc, 2700000, 3600000); + if (ret) { + pr_warning("%s: Switching to 3.3V signalling voltage " + " failed\n", mmc_hostname(host->mmc)); + return -EIO; + } + } + /* Wait for 5ms */ + usleep_range(5000, 5500); -static int sdhci_do_1_8v_signal_voltage_switch(struct sdhci_host *host, - u16 ctrl) -{ - u8 pwr; - u16 clk; - u32 present_state; - int ret; + /* 3.3V regulator output should be stable within 5 ms */ + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + if (!(ctrl & SDHCI_CTRL_VDD_180)) + return 0; - /* Stop SDCLK */ - clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); - clk &= ~SDHCI_CLOCK_CARD_EN; - sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + pr_warning("%s: 3.3V regulator output did not became stable\n", + mmc_hostname(host->mmc)); + + return -EAGAIN; + case MMC_SIGNAL_VOLTAGE_180: + if (host->vqmmc) { + ret = regulator_set_voltage(host->vqmmc, + 1700000, 1950000); + if (ret) { + pr_warning("%s: Switching to 1.8V signalling voltage " + " failed\n", mmc_hostname(host->mmc)); + return -EIO; + } + } - /* Check whether DAT[3:0] is 0000 */ - present_state = sdhci_readl(host, SDHCI_PRESENT_STATE); - if (!((present_state & SDHCI_DATA_LVL_MASK) >> - SDHCI_DATA_LVL_SHIFT)) { /* * Enable 1.8V Signal Enable in the Host Control2 * register */ - if (host->vqmmc) - ret = regulator_set_voltage(host->vqmmc, - 1700000, 1950000); - else - ret = 0; + ctrl |= SDHCI_CTRL_VDD_180; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); - if (!ret) { - ctrl |= SDHCI_CTRL_VDD_180; - sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + /* Wait for 5ms */ + usleep_range(5000, 5500); - /* Wait for 5ms */ - usleep_range(5000, 5500); + /* 1.8V regulator output should be stable within 5 ms */ + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + if (ctrl & SDHCI_CTRL_VDD_180) + return 0; - ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); - if (ctrl & SDHCI_CTRL_VDD_180) { - /* Provide SDCLK again and wait for 1ms */ - clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); - clk |= SDHCI_CLOCK_CARD_EN; - sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); - usleep_range(1000, 1500); + pr_warning("%s: 1.8V regulator output did not became stable\n", + mmc_hostname(host->mmc)); - /* - * If DAT[3:0] level is 1111b, then the card - * was successfully switched to 1.8V signaling. - */ - present_state = sdhci_readl(host, - SDHCI_PRESENT_STATE); - if ((present_state & SDHCI_DATA_LVL_MASK) == - SDHCI_DATA_LVL_MASK) - return 0; + return -EAGAIN; + case MMC_SIGNAL_VOLTAGE_120: + if (host->vqmmc) { + ret = regulator_set_voltage(host->vqmmc, 1100000, 1300000); + if (ret) { + pr_warning("%s: Switching to 1.2V signalling voltage " + " failed\n", mmc_hostname(host->mmc)); + return -EIO; } } - } - - /* - * If we are here, that means the switch to 1.8V signaling - * failed. We power cycle the card, and retry initialization - * sequence by setting S18R to 0. - */ - pwr = sdhci_readb(host, SDHCI_POWER_CONTROL); - pwr &= ~SDHCI_POWER_ON; - sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); - if (host->vmmc) - regulator_disable(host->vmmc); - - /* Wait for 1ms as per the spec */ - usleep_range(1000, 1500); - pwr |= SDHCI_POWER_ON; - sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); - if (host->vmmc) - regulator_enable(host->vmmc); - - pr_warning("%s: Switching to 1.8V signalling voltage failed, " - "retrying with S18R set to 0\n", mmc_hostname(host->mmc)); - - return -EAGAIN; -} - -static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, - struct mmc_ios *ios) -{ - u16 ctrl; - - /* - * Signal Voltage Switching is only applicable for Host Controllers - * v3.00 and above. - */ - if (host->version < SDHCI_SPEC_300) return 0; - - /* - * We first check whether the request is to set signalling voltage - * to 3.3V. If so, we change the voltage to 3.3V and return quickly. - */ - ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); - if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) - return sdhci_do_3_3v_signal_voltage_switch(host, ctrl); - else if (!(ctrl & SDHCI_CTRL_VDD_180) && - (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180)) - return sdhci_do_1_8v_signal_voltage_switch(host, ctrl); - else + default: /* No signal voltage switch required */ return 0; + } } static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, - struct mmc_ios *ios) + int signal_voltage) { struct sdhci_host *host = mmc_priv(mmc); int err; @@ -1761,11 +1711,24 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, if (host->version < SDHCI_SPEC_300) return 0; sdhci_runtime_pm_get(host); - err = sdhci_do_start_signal_voltage_switch(host, ios); + err = sdhci_do_start_signal_voltage_switch(host, signal_voltage); sdhci_runtime_pm_put(host); return err; } +static int sdhci_card_busy(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + u32 present_state; + + sdhci_runtime_pm_get(host); + /* Check whether DAT[3:0] is 0000 */ + present_state = sdhci_readl(host, SDHCI_PRESENT_STATE); + sdhci_runtime_pm_put(host); + + return !(present_state & SDHCI_DATA_LVL_MASK); +} + static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) { struct sdhci_host *host; @@ -2036,6 +1999,7 @@ static const struct mmc_host_ops sdhci_ops = { .execute_tuning = sdhci_execute_tuning, .enable_preset_value = sdhci_enable_preset_value, .card_event = sdhci_card_event, + .card_busy = sdhci_card_busy, }; /*****************************************************************************\ -- cgit v1.2.3 From d005d94359a8df84ea6e5ac137393707f9e87e81 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 28 Jan 2013 19:27:12 +0100 Subject: mmc: sdhci-pltfm: Add a common clk API implementation of get_timeout_clock Quite a few drivers have a implementation of the get_timeout_clock callback which simply returns the result of clk_get_rate on the device's clock. This patch adds a common implementation of this to the sdhci-pltfm module and replaces all custom implementations with the common one. Signed-off-by: Lars-Peter Clausen Tested-by: Stephen Warren Acked-by: Shawn Guo Tested-by: Kevin Liu Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-bcm2835.c | 27 +++++---------------------- drivers/mmc/host/sdhci-esdhc-imx.c | 9 +-------- drivers/mmc/host/sdhci-pltfm.c | 8 ++++++++ drivers/mmc/host/sdhci-pltfm.h | 2 ++ drivers/mmc/host/sdhci-pxav2.c | 9 +-------- drivers/mmc/host/sdhci-pxav3.c | 9 +-------- 6 files changed, 18 insertions(+), 46 deletions(-) diff --git a/drivers/mmc/host/sdhci-bcm2835.c b/drivers/mmc/host/sdhci-bcm2835.c index 453825fcc5cf..1e97b89dac66 100644 --- a/drivers/mmc/host/sdhci-bcm2835.c +++ b/drivers/mmc/host/sdhci-bcm2835.c @@ -51,7 +51,6 @@ #define BCM2835_SDHCI_WRITE_DELAY (((2 * 1000000) / MIN_FREQ) + 1) struct bcm2835_sdhci { - struct clk *clk; u32 shadow; }; @@ -120,27 +119,11 @@ static u8 bcm2835_sdhci_readb(struct sdhci_host *host, int reg) return byte; } -static unsigned int bcm2835_sdhci_get_max_clock(struct sdhci_host *host) -{ - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - struct bcm2835_sdhci *bcm2835_host = pltfm_host->priv; - - return clk_get_rate(bcm2835_host->clk); -} - unsigned int bcm2835_sdhci_get_min_clock(struct sdhci_host *host) { return MIN_FREQ; } -unsigned int bcm2835_sdhci_get_timeout_clock(struct sdhci_host *host) -{ - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - struct bcm2835_sdhci *bcm2835_host = pltfm_host->priv; - - return clk_get_rate(bcm2835_host->clk); -} - static struct sdhci_ops bcm2835_sdhci_ops = { .write_l = bcm2835_sdhci_writel, .write_w = bcm2835_sdhci_writew, @@ -148,9 +131,9 @@ static struct sdhci_ops bcm2835_sdhci_ops = { .read_l = bcm2835_sdhci_readl, .read_w = bcm2835_sdhci_readw, .read_b = bcm2835_sdhci_readb, - .get_max_clock = bcm2835_sdhci_get_max_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, .get_min_clock = bcm2835_sdhci_get_min_clock, - .get_timeout_clock = bcm2835_sdhci_get_timeout_clock, + .get_timeout_clock = sdhci_pltfm_clk_get_max_clock, }; static struct sdhci_pltfm_data bcm2835_sdhci_pdata = { @@ -180,9 +163,9 @@ static int bcm2835_sdhci_probe(struct platform_device *pdev) pltfm_host = sdhci_priv(host); pltfm_host->priv = bcm2835_host; - bcm2835_host->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(bcm2835_host->clk)) { - ret = PTR_ERR(bcm2835_host->clk); + pltfm_host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pltfm_host->clk)) { + ret = PTR_ERR(pltfm_host->clk); goto err; } diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index f7ee5e67516c..78ac00227c1a 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -351,13 +351,6 @@ static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg) } } -static unsigned int esdhc_pltfm_get_max_clock(struct sdhci_host *host) -{ - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - - return clk_get_rate(pltfm_host->clk); -} - static unsigned int esdhc_pltfm_get_min_clock(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -413,7 +406,7 @@ static struct sdhci_ops sdhci_esdhc_ops = { .write_w = esdhc_writew_le, .write_b = esdhc_writeb_le, .set_clock = esdhc_set_clock, - .get_max_clock = esdhc_pltfm_get_max_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, .get_min_clock = esdhc_pltfm_get_min_clock, .get_ro = esdhc_pltfm_get_ro, .platform_bus_width = esdhc_pltfm_bus_width, diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c index d4283ef5917a..3145a780b035 100644 --- a/drivers/mmc/host/sdhci-pltfm.c +++ b/drivers/mmc/host/sdhci-pltfm.c @@ -36,6 +36,14 @@ #endif #include "sdhci-pltfm.h" +unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + return clk_get_rate(pltfm_host->clk); +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_clk_get_max_clock); + static struct sdhci_ops sdhci_pltfm_ops = { }; diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h index 37e0e184a0bb..153b6c509ebe 100644 --- a/drivers/mmc/host/sdhci-pltfm.h +++ b/drivers/mmc/host/sdhci-pltfm.h @@ -98,6 +98,8 @@ extern int sdhci_pltfm_register(struct platform_device *pdev, struct sdhci_pltfm_data *pdata); extern int sdhci_pltfm_unregister(struct platform_device *pdev); +extern unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host); + #ifdef CONFIG_PM extern const struct dev_pm_ops sdhci_pltfm_pmops; #define SDHCI_PLTFM_PMOPS (&sdhci_pltfm_pmops) diff --git a/drivers/mmc/host/sdhci-pxav2.c b/drivers/mmc/host/sdhci-pxav2.c index 7e5756593b68..eeb7d439db1d 100644 --- a/drivers/mmc/host/sdhci-pxav2.c +++ b/drivers/mmc/host/sdhci-pxav2.c @@ -111,15 +111,8 @@ static int pxav2_mmc_set_width(struct sdhci_host *host, int width) return 0; } -static u32 pxav2_get_max_clock(struct sdhci_host *host) -{ - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - - return clk_get_rate(pltfm_host->clk); -} - static struct sdhci_ops pxav2_sdhci_ops = { - .get_max_clock = pxav2_get_max_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, .platform_reset_exit = pxav2_set_private_registers, .platform_bus_width = pxav2_mmc_set_width, }; diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index 3d20c10fc571..f09877fcc3ec 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -163,18 +163,11 @@ static int pxav3_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs) return 0; } -static u32 pxav3_get_max_clock(struct sdhci_host *host) -{ - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - - return clk_get_rate(pltfm_host->clk); -} - static struct sdhci_ops pxav3_sdhci_ops = { .platform_reset_exit = pxav3_set_private_registers, .set_uhs_signaling = pxav3_set_uhs_signaling, .platform_send_init_74_clocks = pxav3_gen_init_74_clocks, - .get_max_clock = pxav3_get_max_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, }; #ifdef CONFIG_OF -- cgit v1.2.3 From 14006bfbd2fe9f1307dc3350d7a12004ddd7fce0 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Wed, 6 Feb 2013 20:04:43 +0530 Subject: mmc: MAINTAINERS update for omap_hsmmc Update Maintainer email for omap_hsmmc, as Venkatraman will no longer be able to maintain omap_hsmmc driver. Signed-off-by: Balaji T K Acked-by: Venkatraman S Signed-off-by: Chris Ball --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index b8ae059f7ca4..5c88df755352 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5498,7 +5498,7 @@ S: Maintained F: drivers/mmc/host/omap.c OMAP HS MMC SUPPORT -M: Venkatraman S +M: Balaji T K L: linux-mmc@vger.kernel.org L: linux-omap@vger.kernel.org S: Maintained -- cgit v1.2.3 From 073350f7b562d06df0aedd36c963508e36437b3c Mon Sep 17 00:00:00 2001 From: Roland Stigge Date: Wed, 30 Jan 2013 18:05:08 +0100 Subject: mmc: mmc_spi: Fix return value evaluation of irq_of_parse_and_map() When irq_of_parse_and_map() returns an error, it does as zero. But in mmc_spi_get_pdata(), the error return case is compared against NO_IRQ. This might work where NO_IRQ is zero (defaults to zero when undefined, as on MIPS) but not where NO_IRQ is different, e.g. on ARM where it's -1. This patch changes to comparison with 0 which is the error return value of irq_of_parse_and_map(). Tested on ARM that mmc_spi is working now. Signed-off-by: Roland Stigge Signed-off-by: Chris Ball --- drivers/mmc/host/of_mmc_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/of_mmc_spi.c b/drivers/mmc/host/of_mmc_spi.c index 1534b582c419..d720b5e05b9c 100644 --- a/drivers/mmc/host/of_mmc_spi.c +++ b/drivers/mmc/host/of_mmc_spi.c @@ -146,7 +146,7 @@ struct mmc_spi_platform_data *mmc_spi_get_pdata(struct spi_device *spi) oms->pdata.get_ro = of_mmc_spi_get_ro; oms->detect_irq = irq_of_parse_and_map(np, 0); - if (oms->detect_irq != NO_IRQ) { + if (oms->detect_irq != 0) { oms->pdata.init = of_mmc_spi_init; oms->pdata.exit = of_mmc_spi_exit; } else { -- cgit v1.2.3 From 52983382c74f59a3953e622d7661a24e1bc4388a Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Thu, 31 Jan 2013 11:31:37 +0800 Subject: mmc: sdhci: enhance preset value function 4d55c5a1 ("mmc: sdhci: enable preset value after uhs initialization") added preset value support and enabled it by default during sd card init. Below are the enhancements introduced by this patch: 1. In current code, preset value is enabled after setting clock finished, which means the clock is manually set by driver firstly and then suddenly switched to preset value at this point. So the first setting is useless and unnecessary. What's more, the first clock setting may differ from the preset one. The better way is enable preset value just after switch to UHS mode so the preset value can take effect immediately. So move preset value enable from mmc_sd_init_card to sdhci_set_ios which will be called during set timing. 2. In current code, preset value is disabled at the beginning of mmc_attach_sd. It's too late since low freq (400khz) should be set in mmc_power_up. So move preset value disable to sdhci_set_ios which will be called during power up. 3. host->clock and ios->drv_type should also be updated according to the preset value if it's enabled. Current code missed this. 4. This patch also introduce a quirk to disable preset value in case preset value doesn't work. This patch has been verified on sdhci-pxav3 platform with both preset enabled and disabled. Signed-off-by: Kevin Liu Reviewed-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/sd.c | 17 ------ drivers/mmc/host/sdhci.c | 129 ++++++++++++++++++++++++++++++++-------------- drivers/mmc/host/sdhci.h | 12 +++++ include/linux/mmc/host.h | 1 - include/linux/mmc/sdhci.h | 1 + 5 files changed, 102 insertions(+), 58 deletions(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 03134b1e563c..9e645e19cec6 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -969,16 +969,6 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, /* Card is an ultra-high-speed card */ mmc_card_set_uhs(card); - - /* - * Since initialization is now complete, enable preset - * value registers for UHS-I cards. - */ - if (host->ops->enable_preset_value) { - mmc_host_clk_hold(card->host); - host->ops->enable_preset_value(host, true); - mmc_host_clk_release(card->host); - } } else { /* * Attempt to change to high-speed (if supported) @@ -1157,13 +1147,6 @@ int mmc_attach_sd(struct mmc_host *host) BUG_ON(!host); WARN_ON(!host->claimed); - /* Disable preset value enable if already set since last time */ - if (host->ops->enable_preset_value) { - mmc_host_clk_hold(host); - host->ops->enable_preset_value(host, false); - mmc_host_clk_release(host); - } - err = mmc_send_app_op_cond(host, 0, &ocr); if (err) return err; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index efb112684787..ba586ae99252 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -53,6 +53,7 @@ static void sdhci_send_command(struct sdhci_host *, struct mmc_command *); static void sdhci_finish_command(struct sdhci_host *); static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode); static void sdhci_tuning_timer(unsigned long data); +static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable); #ifdef CONFIG_PM_RUNTIME static int sdhci_runtime_pm_get(struct sdhci_host *host); @@ -1082,6 +1083,37 @@ static void sdhci_finish_command(struct sdhci_host *host) } } +static u16 sdhci_get_preset_value(struct sdhci_host *host) +{ + u16 ctrl, preset = 0; + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + + switch (ctrl & SDHCI_CTRL_UHS_MASK) { + case SDHCI_CTRL_UHS_SDR12: + preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR12); + break; + case SDHCI_CTRL_UHS_SDR25: + preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR25); + break; + case SDHCI_CTRL_UHS_SDR50: + preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR50); + break; + case SDHCI_CTRL_UHS_SDR104: + preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR104); + break; + case SDHCI_CTRL_UHS_DDR50: + preset = sdhci_readw(host, SDHCI_PRESET_FOR_DDR50); + break; + default: + pr_warn("%s: Invalid UHS-I mode selected\n", + mmc_hostname(host->mmc)); + preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR12); + break; + } + return preset; +} + static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) { int div = 0; /* Initialized for compiler warning */ @@ -1106,35 +1138,43 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) goto out; if (host->version >= SDHCI_SPEC_300) { + if (sdhci_readw(host, SDHCI_HOST_CONTROL2) & + SDHCI_CTRL_PRESET_VAL_ENABLE) { + u16 pre_val; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + pre_val = sdhci_get_preset_value(host); + div = (pre_val & SDHCI_PRESET_SDCLK_FREQ_MASK) + >> SDHCI_PRESET_SDCLK_FREQ_SHIFT; + if (host->clk_mul && + (pre_val & SDHCI_PRESET_CLKGEN_SEL_MASK)) { + clk = SDHCI_PROG_CLOCK_MODE; + real_div = div + 1; + clk_mul = host->clk_mul; + } else { + real_div = max_t(int, 1, div << 1); + } + goto clock_set; + } + /* * Check if the Host Controller supports Programmable Clock * Mode. */ if (host->clk_mul) { - u16 ctrl; - + for (div = 1; div <= 1024; div++) { + if ((host->max_clk * host->clk_mul / div) + <= clock) + break; + } /* - * We need to figure out whether the Host Driver needs - * to select Programmable Clock Mode, or the value can - * be set automatically by the Host Controller based on - * the Preset Value registers. + * Set Programmable Clock Mode in the Clock + * Control register. */ - ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); - if (!(ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) { - for (div = 1; div <= 1024; div++) { - if (((host->max_clk * host->clk_mul) / - div) <= clock) - break; - } - /* - * Set Programmable Clock Mode in the Clock - * Control register. - */ - clk = SDHCI_PROG_CLOCK_MODE; - real_div = div; - clk_mul = host->clk_mul; - div--; - } + clk = SDHCI_PROG_CLOCK_MODE; + real_div = div; + clk_mul = host->clk_mul; + div--; } else { /* Version 3.00 divisors must be a multiple of 2. */ if (host->max_clk <= clock) @@ -1159,6 +1199,7 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) div >>= 1; } +clock_set: if (real_div) host->mmc->actual_clock = (host->max_clk * clk_mul) / real_div; @@ -1376,6 +1417,10 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_reinit(host); } + if (host->version >= SDHCI_SPEC_300 && + (ios->power_mode == MMC_POWER_UP)) + sdhci_enable_preset_value(host, false); + sdhci_set_clock(host, ios->clock); if (ios->power_mode == MMC_POWER_OFF) @@ -1496,6 +1541,20 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); } + if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN) && + ((ios->timing == MMC_TIMING_UHS_SDR12) || + (ios->timing == MMC_TIMING_UHS_SDR25) || + (ios->timing == MMC_TIMING_UHS_SDR50) || + (ios->timing == MMC_TIMING_UHS_SDR104) || + (ios->timing == MMC_TIMING_UHS_DDR50))) { + u16 preset; + + sdhci_enable_preset_value(host, true); + preset = sdhci_get_preset_value(host); + ios->drv_type = (preset & SDHCI_PRESET_DRV_MASK) + >> SDHCI_PRESET_DRV_SHIFT; + } + /* Re-enable SD Clock */ sdhci_update_clock(host); } else @@ -1925,17 +1984,15 @@ out: return err; } -static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable) + +static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable) { u16 ctrl; - unsigned long flags; /* Host Controller v3.00 defines preset value registers */ if (host->version < SDHCI_SPEC_300) return; - spin_lock_irqsave(&host->lock, flags); - ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); /* @@ -1951,17 +2008,6 @@ static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable) sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); host->flags &= ~SDHCI_PV_ENABLED; } - - spin_unlock_irqrestore(&host->lock, flags); -} - -static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable) -{ - struct sdhci_host *host = mmc_priv(mmc); - - sdhci_runtime_pm_get(host); - sdhci_do_enable_preset_value(host, enable); - sdhci_runtime_pm_put(host); } static void sdhci_card_event(struct mmc_host *mmc) @@ -1997,7 +2043,6 @@ static const struct mmc_host_ops sdhci_ops = { .enable_sdio_irq = sdhci_enable_sdio_irq, .start_signal_voltage_switch = sdhci_start_signal_voltage_switch, .execute_tuning = sdhci_execute_tuning, - .enable_preset_value = sdhci_enable_preset_value, .card_event = sdhci_card_event, .card_busy = sdhci_card_busy, }; @@ -2591,8 +2636,12 @@ int sdhci_runtime_resume_host(struct sdhci_host *host) sdhci_do_set_ios(host, &host->mmc->ios); sdhci_do_start_signal_voltage_switch(host, &host->mmc->ios); - if (host_flags & SDHCI_PV_ENABLED) - sdhci_do_enable_preset_value(host, true); + if ((host_flags & SDHCI_PV_ENABLED) && + !(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN)) { + spin_lock_irqsave(&host->lock, flags); + sdhci_enable_preset_value(host, true); + spin_unlock_irqrestore(&host->lock, flags); + } /* Set the re-tuning expiration flag */ if (host->flags & SDHCI_USING_RETUNING_TIMER) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index c8d11b904a40..379e09d9f3c1 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -229,6 +229,18 @@ /* 60-FB reserved */ +#define SDHCI_PRESET_FOR_SDR12 0x66 +#define SDHCI_PRESET_FOR_SDR25 0x68 +#define SDHCI_PRESET_FOR_SDR50 0x6A +#define SDHCI_PRESET_FOR_SDR104 0x6C +#define SDHCI_PRESET_FOR_DDR50 0x6E +#define SDHCI_PRESET_DRV_MASK 0xC000 +#define SDHCI_PRESET_DRV_SHIFT 14 +#define SDHCI_PRESET_CLKGEN_SEL_MASK 0x400 +#define SDHCI_PRESET_CLKGEN_SEL_SHIFT 10 +#define SDHCI_PRESET_SDCLK_FREQ_MASK 0x3FF +#define SDHCI_PRESET_SDCLK_FREQ_SHIFT 0 + #define SDHCI_SLOT_INT_STATUS 0xFC #define SDHCI_HOST_VERSION 0xFE diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 0373b0a6daac..6c235e03de29 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -136,7 +136,6 @@ struct mmc_host_ops { /* The tuning command opcode value is different for SD and eMMC cards */ int (*execute_tuning)(struct mmc_host *host, u32 opcode); - void (*enable_preset_value)(struct mmc_host *host, bool enable); int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv); void (*hw_reset)(struct mmc_host *host); void (*card_event)(struct mmc_host *host); diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index 4bbc3301fbbf..b838ffc49e4a 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -94,6 +94,7 @@ struct sdhci_host { #define SDHCI_QUIRK2_HOST_NO_CMD23 (1<<1) /* The system physically doesn't support 1.8v, even if the host does */ #define SDHCI_QUIRK2_NO_1_8_V (1<<2) +#define SDHCI_QUIRK2_PRESET_VALUE_BROKEN (1<<3) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ -- cgit v1.2.3 From 9b844961c265a8ee4bdacd8404d078d7f1319957 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Tue, 22 Jan 2013 19:48:03 +0900 Subject: mmc: core: fix permanent sleep of mmcqd during card removal This patch is derived from: "mmc: fix async request mechanism for sequential read scenarios". According as async transfer, a request is handled with twice mmc_start_req. When the card is removed, the request is actually not issued in the first mmc_start_req [__mmc_start_data_req]. And then mmc_wait_for_data_req_done will come in the next mmc_start_req. But there is no event for completions. wake_up_interruptible is needed in __mmc_start_data_req for the case of removed card. Signed-off-by: Seungwon Jeon Acked-by: Jaehoon Chung Tested-by: Konstantin Dorfman Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index e41badbf9b50..6e95f6f11a28 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -350,6 +350,7 @@ static int __mmc_start_data_req(struct mmc_host *host, struct mmc_request *mrq) mrq->host = host; if (mmc_card_removed(host->card)) { mrq->cmd->error = -ENOMEDIUM; + mmc_wait_data_done(mrq); return -ENOMEDIUM; } mmc_start_request(host, mrq); -- cgit v1.2.3 From 7a81902fa52f2b6f5037e167f74ebb5a41cfc7d1 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Tue, 22 Jan 2013 19:48:07 +0900 Subject: mmc: block: don't start new request when the card is removed It's not necessary to start a new request while error handling if the card was removed. Signed-off-by: Seungwon Jeon Acked-by: Jaehoon Chung Tested-by: Konstantin Dorfman Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index f79b4688e471..1170afe1a596 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1456,8 +1456,14 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) start_new_req: if (rqc) { - mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq); - mmc_start_req(card->host, &mq->mqrq_cur->mmc_active, NULL); + if (mmc_card_removed(card)) { + rqc->cmd_flags |= REQ_QUIET; + blk_end_request_all(rqc, -EIO); + } else { + mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq); + mmc_start_req(card->host, + &mq->mqrq_cur->mmc_active, NULL); + } } return 0; -- cgit v1.2.3 From f5c2758fbb3095dc0fe4725e4a2eaec3580e1eba Mon Sep 17 00:00:00 2001 From: Jaehoon Chung Date: Fri, 1 Feb 2013 14:32:22 +0900 Subject: mmc: core: fix indentation This patch fixes incorrect indentation. (Just code cleanup) Signed-off-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 6e95f6f11a28..6f3bc1a97ba8 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -516,17 +516,16 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, mmc_pre_req(host, areq->mrq, !host->areq); if (host->areq) { - err = mmc_wait_for_data_req_done(host, host->areq->mrq, - areq); - if (err == MMC_BLK_NEW_REQUEST) { - if (error) - *error = err; - /* - * The previous request was not completed, - * nothing to return - */ - return NULL; - } + err = mmc_wait_for_data_req_done(host, host->areq->mrq, areq); + if (err == MMC_BLK_NEW_REQUEST) { + if (error) + *error = err; + /* + * The previous request was not completed, + * nothing to return + */ + return NULL; + } /* * Check BKOPS urgency for each R1 response */ @@ -545,7 +544,7 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, /* Cancel a prepared request if it was not started. */ if ((err || start_err) && areq) - mmc_post_req(host, areq->mrq, -EINVAL); + mmc_post_req(host, areq->mrq, -EINVAL); if (err) host->areq = NULL; -- cgit v1.2.3 From bb691ae464b77d30e74c66480e98d74e88d6b194 Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Fri, 1 Feb 2013 17:48:30 +0800 Subject: mmc: sdhci-pxav3: add pm runtime support Signed-off-by: Kevin Liu Signed-off-by: Jialing Fu Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-pxav3.c | 96 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index f09877fcc3ec..a0cdbc570a83 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -32,10 +32,14 @@ #include #include #include +#include +#include #include "sdhci.h" #include "sdhci-pltfm.h" +#define PXAV3_RPM_DELAY_MS 50 + #define SD_CLOCK_BURST_SIZE_SETUP 0x10A #define SDCLK_SEL 0x100 #define SDCLK_DELAY_SHIFT 9 @@ -296,9 +300,18 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) sdhci_get_of_property(pdev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, PXAV3_RPM_DELAY_MS); + pm_runtime_use_autosuspend(&pdev->dev); + pm_suspend_ignore_children(&pdev->dev, 1); + pm_runtime_get_noresume(&pdev->dev); + ret = sdhci_add_host(host); if (ret) { dev_err(&pdev->dev, "failed to add host\n"); + pm_runtime_forbid(&pdev->dev); + pm_runtime_disable(&pdev->dev); goto err_add_host; } @@ -311,6 +324,8 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) device_init_wakeup(&pdev->dev, 0); } + pm_runtime_put_autosuspend(&pdev->dev); + return 0; err_add_host: @@ -329,7 +344,9 @@ static int sdhci_pxav3_remove(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_pxa *pxa = pltfm_host->priv; + pm_runtime_get_sync(&pdev->dev); sdhci_remove_host(host, 1); + pm_runtime_disable(&pdev->dev); clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); @@ -342,6 +359,83 @@ static int sdhci_pxav3_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int sdhci_pxav3_suspend(struct device *dev) +{ + int ret; + struct sdhci_host *host = dev_get_drvdata(dev); + + pm_runtime_get_sync(dev); + ret = sdhci_suspend_host(host); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int sdhci_pxav3_resume(struct device *dev) +{ + int ret; + struct sdhci_host *host = dev_get_drvdata(dev); + + pm_runtime_get_sync(dev); + ret = sdhci_resume_host(host); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} +#endif + +#ifdef CONFIG_PM_RUNTIME +static int sdhci_pxav3_runtime_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + unsigned long flags; + + if (pltfm_host->clk) { + spin_lock_irqsave(&host->lock, flags); + host->runtime_suspended = true; + spin_unlock_irqrestore(&host->lock, flags); + + clk_disable_unprepare(pltfm_host->clk); + } + + return 0; +} + +static int sdhci_pxav3_runtime_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + unsigned long flags; + + if (pltfm_host->clk) { + clk_prepare_enable(pltfm_host->clk); + + spin_lock_irqsave(&host->lock, flags); + host->runtime_suspended = false; + spin_unlock_irqrestore(&host->lock, flags); + } + + return 0; +} +#endif + +#ifdef CONFIG_PM +static const struct dev_pm_ops sdhci_pxav3_pmops = { + SET_SYSTEM_SLEEP_PM_OPS(sdhci_pxav3_suspend, sdhci_pxav3_resume) + SET_RUNTIME_PM_OPS(sdhci_pxav3_runtime_suspend, + sdhci_pxav3_runtime_resume, NULL) +}; + +#define SDHCI_PXAV3_PMOPS (&sdhci_pxav3_pmops) + +#else +#define SDHCI_PXAV3_PMOPS NULL +#endif + static struct platform_driver sdhci_pxav3_driver = { .driver = { .name = "sdhci-pxav3", @@ -349,7 +443,7 @@ static struct platform_driver sdhci_pxav3_driver = { .of_match_table = sdhci_pxav3_of_match, #endif .owner = THIS_MODULE, - .pm = SDHCI_PLTFM_PMOPS, + .pm = SDHCI_PXAV3_PMOPS, }, .probe = sdhci_pxav3_probe, .remove = sdhci_pxav3_remove, -- cgit v1.2.3 From 5f56a8e6efb695b6ea7b56e8a33d25ec622e77bd Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Tue, 4 Dec 2012 15:01:02 +0100 Subject: mmc: use regulator_can_change_voltage() instead of regulator_count_voltages mmc_regulator_set_ocr() depends on the ability of regulator to change the voltage value. When regulator cannot change its voltage output, some code is skipped to avoid reporting false errors on some boards, which use MMC hosts with fixed regulators (e.g. Samsung Goni and UniversalC210 boards). This patch replaces a hacky workaround based on regulator_count_voltages() value with the correct call to recently introduced regulator_can_change_voltage() function in regulators core. Signed-off-by: Marek Szyprowski Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 6f3bc1a97ba8..08a3cf2a7610 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1235,7 +1235,7 @@ int mmc_regulator_set_ocr(struct mmc_host *mmc, */ voltage = regulator_get_voltage(supply); - if (regulator_count_voltages(supply) == 1) + if (!regulator_can_change_voltage(supply)) min_uV = max_uV = voltage; if (voltage < 0) -- cgit v1.2.3 From ac48653cec7dd9a5606a7057413a880d6880d5d3 Mon Sep 17 00:00:00 2001 From: Roger Tseng Date: Mon, 4 Feb 2013 15:45:57 +0800 Subject: mmc: rtsx: remove driving adjustment Several new models of readers use different way to select driving capability (a necessary adjustment along with voltage change). Removing this from device-independent rtsx_pci_sdmmc module. It will be implemented in device-depend calls encapsulated by rtsx_pci_switch_output_voltage(). Signed-off-by: Roger Tseng Reviewed-by: Wei WANG Signed-off-by: Chris Ball --- drivers/mmc/host/rtsx_pci_sdmmc.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index f74b5adca642..f93f100e81e5 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -1083,11 +1083,6 @@ static int sdmmc_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios) voltage = OUTPUT_1V8; if (voltage == OUTPUT_1V8) { - err = rtsx_pci_write_register(pcr, - SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_B); - if (err < 0) - goto out; - err = sd_wait_voltage_stable_1(host); if (err < 0) goto out; -- cgit v1.2.3 From abd9ac144947d9a604beb763339e2f77ce8bec79 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Wed, 6 Feb 2013 17:01:43 +0900 Subject: mmc: add packed command feature of eMMC4.5 This patch adds packed command feature of eMMC4.5. The maximum number for packing read (or write) is offered and exception event relevant to packed command which is used for error handling is enabled. If host wants to use this feature, MMC_CAP2_PACKED_CMD should be set. Signed-off-by: Seungwon Jeon Reviewed-by: Maya Erez Reviewed-by: Subhash Jadavani Reviewed-by: Namjae Jeon Signed-off-by: Chris Ball --- drivers/mmc/core/mmc.c | 28 ++++++++++++++++++++++++++++ include/linux/mmc/card.h | 3 +++ include/linux/mmc/host.h | 4 ++++ include/linux/mmc/mmc.h | 15 +++++++++++++-- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 8a3ad602a877..c8f3d6e0684e 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -538,6 +538,11 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) } else { card->ext_csd.data_tag_unit_size = 0; } + + card->ext_csd.max_packed_writes = + ext_csd[EXT_CSD_MAX_PACKED_WRITES]; + card->ext_csd.max_packed_reads = + ext_csd[EXT_CSD_MAX_PACKED_READS]; } else { card->ext_csd.data_sector_size = 512; } @@ -1275,6 +1280,29 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } } + /* + * The mandatory minimum values are defined for packed command. + * read: 5, write: 3 + */ + if (card->ext_csd.max_packed_writes >= 3 && + card->ext_csd.max_packed_reads >= 5 && + host->caps2 & MMC_CAP2_PACKED_CMD) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_EXP_EVENTS_CTRL, + EXT_CSD_PACKED_EVENT_EN, + card->ext_csd.generic_cmd6_time); + if (err && err != -EBADMSG) + goto free_card; + if (err) { + pr_warn("%s: Enabling packed event failed\n", + mmc_hostname(card->host)); + card->ext_csd.packed_event_en = 0; + err = 0; + } else { + card->ext_csd.packed_event_en = 1; + } + } + if (!oldcard) host->card = card; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 7069dcea27c5..237f253f2fe0 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -53,6 +53,9 @@ struct mmc_ext_csd { u8 part_config; u8 cache_ctrl; u8 rst_n_function; + u8 max_packed_writes; + u8 max_packed_reads; + u8 packed_event_en; unsigned int part_time; /* Units: ms */ unsigned int sa_timeout; /* Units: 100ns */ unsigned int generic_cmd6_time; /* Units: 10ms */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 6c235e03de29..a0466c03f5e1 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -276,6 +276,10 @@ struct mmc_host { #define MMC_CAP2_HC_ERASE_SZ (1 << 9) /* High-capacity erase size */ #define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */ #define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */ +#define MMC_CAP2_PACKED_RD (1 << 12) /* Allow packed read */ +#define MMC_CAP2_PACKED_WR (1 << 13) /* Allow packed write */ +#define MMC_CAP2_PACKED_CMD (MMC_CAP2_PACKED_RD | \ + MMC_CAP2_PACKED_WR) mmc_pm_flag_t pm_caps; /* supported pm features */ diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 94d532e41c61..50bcde3677ca 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -139,7 +139,7 @@ static inline bool mmc_op_multi(u32 opcode) #define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ #define R1_READY_FOR_DATA (1 << 8) /* sx, a */ #define R1_SWITCH_ERROR (1 << 7) /* sx, c */ -#define R1_EXCEPTION_EVENT (1 << 6) /* sx, a */ +#define R1_EXCEPTION_EVENT (1 << 6) /* sr, a */ #define R1_APP_CMD (1 << 5) /* sr, c */ #define R1_STATE_IDLE 0 @@ -275,7 +275,10 @@ struct _mmc_csd { #define EXT_CSD_FLUSH_CACHE 32 /* W */ #define EXT_CSD_CACHE_CTRL 33 /* R/W */ #define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ -#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO */ +#define EXT_CSD_PACKED_FAILURE_INDEX 35 /* RO */ +#define EXT_CSD_PACKED_CMD_STATUS 36 /* RO */ +#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO, 2 bytes */ +#define EXT_CSD_EXP_EVENTS_CTRL 56 /* R/W, 2 bytes */ #define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */ #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ @@ -324,6 +327,8 @@ struct _mmc_csd { #define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ #define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ #define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */ +#define EXT_CSD_MAX_PACKED_WRITES 500 /* RO */ +#define EXT_CSD_MAX_PACKED_READS 501 /* RO */ #define EXT_CSD_BKOPS_SUPPORT 502 /* RO */ #define EXT_CSD_HPI_FEATURES 503 /* RO */ @@ -385,6 +390,9 @@ struct _mmc_csd { #define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */ #define EXT_CSD_PWR_CL_8BIT_SHIFT 4 #define EXT_CSD_PWR_CL_4BIT_SHIFT 0 + +#define EXT_CSD_PACKED_EVENT_EN BIT(3) + /* * EXCEPTION_EVENT_STATUS field */ @@ -393,6 +401,9 @@ struct _mmc_csd { #define EXT_CSD_SYSPOOL_EXHAUSTED BIT(2) #define EXT_CSD_PACKED_FAILURE BIT(3) +#define EXT_CSD_PACKED_GENERIC_ERROR BIT(0) +#define EXT_CSD_PACKED_INDEXED_ERROR BIT(1) + /* * BKOPS status level */ -- cgit v1.2.3 From ce39f9d17c14e56ea6772aa84393e6e0cc8499c4 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Wed, 6 Feb 2013 17:02:46 +0900 Subject: mmc: support packed write command for eMMC4.5 devices This patch supports packed write command of eMMC4.5 devices. Several writes can be grouped in packed command and all data of the individual commands can be sent in a single transfer on the bus. Large amounts of data in one transfer rather than several data of small size are effective for eMMC write internally. As a result, packed command help write throughput be improved. The following tables show the results of packed write. Type A: test none | packed iozone 25.8 | 31 tiotest 27.6 | 31.2 lmdd 31.2 | 35.4 Type B: test none | packed iozone 44.1 | 51.1 tiotest 47.9 | 52.5 lmdd 51.6 | 59.2 Type C: test none | packed iozone 19.5 | 32 tiotest 19.9 | 34.5 lmdd 22.8 | 40.7 Signed-off-by: Seungwon Jeon Reviewed-by: Maya Erez Reviewed-by: Namjae Jeon Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 455 +++++++++++++++++++++++++++++++++++++++++++-- drivers/mmc/card/queue.c | 96 +++++++++- drivers/mmc/card/queue.h | 22 +++ drivers/mmc/core/mmc_ops.c | 1 + include/linux/mmc/card.h | 5 + include/linux/mmc/core.h | 4 + include/linux/mmc/host.h | 5 + 7 files changed, 571 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 1170afe1a596..5bab73b91c20 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -59,6 +59,12 @@ MODULE_ALIAS("mmc:block"); #define INAND_CMD38_ARG_SECTRIM2 0x88 #define MMC_BLK_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */ +#define mmc_req_rel_wr(req) (((req->cmd_flags & REQ_FUA) || \ + (req->cmd_flags & REQ_META)) && \ + (rq_data_dir(req) == WRITE)) +#define PACKED_CMD_VER 0x01 +#define PACKED_CMD_WR 0x02 + static DEFINE_MUTEX(block_mutex); /* @@ -89,6 +95,7 @@ struct mmc_blk_data { unsigned int flags; #define MMC_BLK_CMD23 (1 << 0) /* Can do SET_BLOCK_COUNT for multiblock */ #define MMC_BLK_REL_WR (1 << 1) /* MMC Reliable write support */ +#define MMC_BLK_PACKED_CMD (1 << 2) /* MMC packed command support */ unsigned int usage; unsigned int read_only; @@ -113,6 +120,12 @@ struct mmc_blk_data { static DEFINE_MUTEX(open_lock); +enum { + MMC_PACKED_NR_IDX = -1, + MMC_PACKED_NR_ZERO, + MMC_PACKED_NR_SINGLE, +}; + module_param(perdev_minors, int, 0444); MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device"); @@ -120,6 +133,19 @@ static inline int mmc_blk_part_switch(struct mmc_card *card, struct mmc_blk_data *md); static int get_card_status(struct mmc_card *card, u32 *status, int retries); +static inline void mmc_blk_clear_packed(struct mmc_queue_req *mqrq) +{ + struct mmc_packed *packed = mqrq->packed; + + BUG_ON(!packed); + + mqrq->cmd_type = MMC_PACKED_NONE; + packed->nr_entries = MMC_PACKED_NR_ZERO; + packed->idx_failure = MMC_PACKED_NR_IDX; + packed->retries = 0; + packed->blocks = 0; +} + static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk) { struct mmc_blk_data *md; @@ -1137,12 +1163,78 @@ static int mmc_blk_err_check(struct mmc_card *card, if (!brq->data.bytes_xfered) return MMC_BLK_RETRY; + if (mmc_packed_cmd(mq_mrq->cmd_type)) { + if (unlikely(brq->data.blocks << 9 != brq->data.bytes_xfered)) + return MMC_BLK_PARTIAL; + else + return MMC_BLK_SUCCESS; + } + if (blk_rq_bytes(req) != brq->data.bytes_xfered) return MMC_BLK_PARTIAL; return MMC_BLK_SUCCESS; } +static int mmc_blk_packed_err_check(struct mmc_card *card, + struct mmc_async_req *areq) +{ + struct mmc_queue_req *mq_rq = container_of(areq, struct mmc_queue_req, + mmc_active); + struct request *req = mq_rq->req; + struct mmc_packed *packed = mq_rq->packed; + int err, check, status; + u8 *ext_csd; + + BUG_ON(!packed); + + packed->retries--; + check = mmc_blk_err_check(card, areq); + err = get_card_status(card, &status, 0); + if (err) { + pr_err("%s: error %d sending status command\n", + req->rq_disk->disk_name, err); + return MMC_BLK_ABORT; + } + + if (status & R1_EXCEPTION_EVENT) { + ext_csd = kzalloc(512, GFP_KERNEL); + if (!ext_csd) { + pr_err("%s: unable to allocate buffer for ext_csd\n", + req->rq_disk->disk_name); + return -ENOMEM; + } + + err = mmc_send_ext_csd(card, ext_csd); + if (err) { + pr_err("%s: error %d sending ext_csd\n", + req->rq_disk->disk_name, err); + check = MMC_BLK_ABORT; + goto free; + } + + if ((ext_csd[EXT_CSD_EXP_EVENTS_STATUS] & + EXT_CSD_PACKED_FAILURE) && + (ext_csd[EXT_CSD_PACKED_CMD_STATUS] & + EXT_CSD_PACKED_GENERIC_ERROR)) { + if (ext_csd[EXT_CSD_PACKED_CMD_STATUS] & + EXT_CSD_PACKED_INDEXED_ERROR) { + packed->idx_failure = + ext_csd[EXT_CSD_PACKED_FAILURE_INDEX] - 1; + check = MMC_BLK_PARTIAL; + } + pr_err("%s: packed cmd failed, nr %u, sectors %u, " + "failure index: %d\n", + req->rq_disk->disk_name, packed->nr_entries, + packed->blocks, packed->idx_failure); + } +free: + kfree(ext_csd); + } + + return check; +} + static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, struct mmc_card *card, int disable_multi, @@ -1297,10 +1389,221 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, mmc_queue_bounce_pre(mqrq); } +static inline u8 mmc_calc_packed_hdr_segs(struct request_queue *q, + struct mmc_card *card) +{ + unsigned int hdr_sz = mmc_large_sector(card) ? 4096 : 512; + unsigned int max_seg_sz = queue_max_segment_size(q); + unsigned int len, nr_segs = 0; + + do { + len = min(hdr_sz, max_seg_sz); + hdr_sz -= len; + nr_segs++; + } while (hdr_sz); + + return nr_segs; +} + +static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) +{ + struct request_queue *q = mq->queue; + struct mmc_card *card = mq->card; + struct request *cur = req, *next = NULL; + struct mmc_blk_data *md = mq->data; + struct mmc_queue_req *mqrq = mq->mqrq_cur; + bool en_rel_wr = card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN; + unsigned int req_sectors = 0, phys_segments = 0; + unsigned int max_blk_count, max_phys_segs; + bool put_back = true; + u8 max_packed_rw = 0; + u8 reqs = 0; + + if (!(md->flags & MMC_BLK_PACKED_CMD)) + goto no_packed; + + if ((rq_data_dir(cur) == WRITE) && + mmc_host_packed_wr(card->host)) + max_packed_rw = card->ext_csd.max_packed_writes; + + if (max_packed_rw == 0) + goto no_packed; + + if (mmc_req_rel_wr(cur) && + (md->flags & MMC_BLK_REL_WR) && !en_rel_wr) + goto no_packed; + + if (mmc_large_sector(card) && + !IS_ALIGNED(blk_rq_sectors(cur), 8)) + goto no_packed; + + mmc_blk_clear_packed(mqrq); + + max_blk_count = min(card->host->max_blk_count, + card->host->max_req_size >> 9); + if (unlikely(max_blk_count > 0xffff)) + max_blk_count = 0xffff; + + max_phys_segs = queue_max_segments(q); + req_sectors += blk_rq_sectors(cur); + phys_segments += cur->nr_phys_segments; + + if (rq_data_dir(cur) == WRITE) { + req_sectors += mmc_large_sector(card) ? 8 : 1; + phys_segments += mmc_calc_packed_hdr_segs(q, card); + } + + do { + if (reqs >= max_packed_rw - 1) { + put_back = false; + break; + } + + spin_lock_irq(q->queue_lock); + next = blk_fetch_request(q); + spin_unlock_irq(q->queue_lock); + if (!next) { + put_back = false; + break; + } + + if (mmc_large_sector(card) && + !IS_ALIGNED(blk_rq_sectors(next), 8)) + break; + + if (next->cmd_flags & REQ_DISCARD || + next->cmd_flags & REQ_FLUSH) + break; + + if (rq_data_dir(cur) != rq_data_dir(next)) + break; + + if (mmc_req_rel_wr(next) && + (md->flags & MMC_BLK_REL_WR) && !en_rel_wr) + break; + + req_sectors += blk_rq_sectors(next); + if (req_sectors > max_blk_count) + break; + + phys_segments += next->nr_phys_segments; + if (phys_segments > max_phys_segs) + break; + + list_add_tail(&next->queuelist, &mqrq->packed->list); + cur = next; + reqs++; + } while (1); + + if (put_back) { + spin_lock_irq(q->queue_lock); + blk_requeue_request(q, next); + spin_unlock_irq(q->queue_lock); + } + + if (reqs > 0) { + list_add(&req->queuelist, &mqrq->packed->list); + mqrq->packed->nr_entries = ++reqs; + mqrq->packed->retries = reqs; + return reqs; + } + +no_packed: + mqrq->cmd_type = MMC_PACKED_NONE; + return 0; +} + +static void mmc_blk_packed_hdr_wrq_prep(struct mmc_queue_req *mqrq, + struct mmc_card *card, + struct mmc_queue *mq) +{ + struct mmc_blk_request *brq = &mqrq->brq; + struct request *req = mqrq->req; + struct request *prq; + struct mmc_blk_data *md = mq->data; + struct mmc_packed *packed = mqrq->packed; + bool do_rel_wr, do_data_tag; + u32 *packed_cmd_hdr; + u8 hdr_blocks; + u8 i = 1; + + BUG_ON(!packed); + + mqrq->cmd_type = MMC_PACKED_WRITE; + packed->blocks = 0; + packed->idx_failure = MMC_PACKED_NR_IDX; + + packed_cmd_hdr = packed->cmd_hdr; + memset(packed_cmd_hdr, 0, sizeof(packed->cmd_hdr)); + packed_cmd_hdr[0] = (packed->nr_entries << 16) | + (PACKED_CMD_WR << 8) | PACKED_CMD_VER; + hdr_blocks = mmc_large_sector(card) ? 8 : 1; + + /* + * Argument for each entry of packed group + */ + list_for_each_entry(prq, &packed->list, queuelist) { + do_rel_wr = mmc_req_rel_wr(prq) && (md->flags & MMC_BLK_REL_WR); + do_data_tag = (card->ext_csd.data_tag_unit_size) && + (prq->cmd_flags & REQ_META) && + (rq_data_dir(prq) == WRITE) && + ((brq->data.blocks * brq->data.blksz) >= + card->ext_csd.data_tag_unit_size); + /* Argument of CMD23 */ + packed_cmd_hdr[(i * 2)] = + (do_rel_wr ? MMC_CMD23_ARG_REL_WR : 0) | + (do_data_tag ? MMC_CMD23_ARG_TAG_REQ : 0) | + blk_rq_sectors(prq); + /* Argument of CMD18 or CMD25 */ + packed_cmd_hdr[((i * 2)) + 1] = + mmc_card_blockaddr(card) ? + blk_rq_pos(prq) : blk_rq_pos(prq) << 9; + packed->blocks += blk_rq_sectors(prq); + i++; + } + + memset(brq, 0, sizeof(struct mmc_blk_request)); + brq->mrq.cmd = &brq->cmd; + brq->mrq.data = &brq->data; + brq->mrq.sbc = &brq->sbc; + brq->mrq.stop = &brq->stop; + + brq->sbc.opcode = MMC_SET_BLOCK_COUNT; + brq->sbc.arg = MMC_CMD23_ARG_PACKED | (packed->blocks + hdr_blocks); + brq->sbc.flags = MMC_RSP_R1 | MMC_CMD_AC; + + brq->cmd.opcode = MMC_WRITE_MULTIPLE_BLOCK; + brq->cmd.arg = blk_rq_pos(req); + if (!mmc_card_blockaddr(card)) + brq->cmd.arg <<= 9; + brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; + + brq->data.blksz = 512; + brq->data.blocks = packed->blocks + hdr_blocks; + brq->data.flags |= MMC_DATA_WRITE; + + brq->stop.opcode = MMC_STOP_TRANSMISSION; + brq->stop.arg = 0; + brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; + + mmc_set_data_timeout(&brq->data, card); + + brq->data.sg = mqrq->sg; + brq->data.sg_len = mmc_queue_map_sg(mq, mqrq); + + mqrq->mmc_active.mrq = &brq->mrq; + mqrq->mmc_active.err_check = mmc_blk_packed_err_check; + + mmc_queue_bounce_pre(mqrq); +} + static int mmc_blk_cmd_err(struct mmc_blk_data *md, struct mmc_card *card, struct mmc_blk_request *brq, struct request *req, int ret) { + struct mmc_queue_req *mq_rq; + mq_rq = container_of(brq, struct mmc_queue_req, brq); + /* * If this is an SD card and we're writing, we can first * mark the known good sectors as ok. @@ -1317,11 +1620,84 @@ static int mmc_blk_cmd_err(struct mmc_blk_data *md, struct mmc_card *card, ret = blk_end_request(req, 0, blocks << 9); } } else { - ret = blk_end_request(req, 0, brq->data.bytes_xfered); + if (!mmc_packed_cmd(mq_rq->cmd_type)) + ret = blk_end_request(req, 0, brq->data.bytes_xfered); } return ret; } +static int mmc_blk_end_packed_req(struct mmc_queue_req *mq_rq) +{ + struct request *prq; + struct mmc_packed *packed = mq_rq->packed; + int idx = packed->idx_failure, i = 0; + int ret = 0; + + BUG_ON(!packed); + + while (!list_empty(&packed->list)) { + prq = list_entry_rq(packed->list.next); + if (idx == i) { + /* retry from error index */ + packed->nr_entries -= idx; + mq_rq->req = prq; + ret = 1; + + if (packed->nr_entries == MMC_PACKED_NR_SINGLE) { + list_del_init(&prq->queuelist); + mmc_blk_clear_packed(mq_rq); + } + return ret; + } + list_del_init(&prq->queuelist); + blk_end_request(prq, 0, blk_rq_bytes(prq)); + i++; + } + + mmc_blk_clear_packed(mq_rq); + return ret; +} + +static void mmc_blk_abort_packed_req(struct mmc_queue_req *mq_rq) +{ + struct request *prq; + struct mmc_packed *packed = mq_rq->packed; + + BUG_ON(!packed); + + while (!list_empty(&packed->list)) { + prq = list_entry_rq(packed->list.next); + list_del_init(&prq->queuelist); + blk_end_request(prq, -EIO, blk_rq_bytes(prq)); + } + + mmc_blk_clear_packed(mq_rq); +} + +static void mmc_blk_revert_packed_req(struct mmc_queue *mq, + struct mmc_queue_req *mq_rq) +{ + struct request *prq; + struct request_queue *q = mq->queue; + struct mmc_packed *packed = mq_rq->packed; + + BUG_ON(!packed); + + while (!list_empty(&packed->list)) { + prq = list_entry_rq(packed->list.prev); + if (prq->queuelist.prev != &packed->list) { + list_del_init(&prq->queuelist); + spin_lock_irq(q->queue_lock); + blk_requeue_request(mq->queue, prq); + spin_unlock_irq(q->queue_lock); + } else { + list_del_init(&prq->queuelist); + } + } + + mmc_blk_clear_packed(mq_rq); +} + static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) { struct mmc_blk_data *md = mq->data; @@ -1332,10 +1708,15 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) struct mmc_queue_req *mq_rq; struct request *req = rqc; struct mmc_async_req *areq; + const u8 packed_nr = 2; + u8 reqs = 0; if (!rqc && !mq->mqrq_prev->req) return 0; + if (rqc) + reqs = mmc_blk_prep_packed_list(mq, rqc); + do { if (rqc) { /* @@ -1346,9 +1727,15 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) (card->ext_csd.data_sector_size == 4096)) { pr_err("%s: Transfer size is not 4KB sector size aligned\n", req->rq_disk->disk_name); + mq_rq = mq->mqrq_cur; goto cmd_abort; } - mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq); + + if (reqs >= packed_nr) + mmc_blk_packed_hdr_wrq_prep(mq->mqrq_cur, + card, mq); + else + mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq); areq = &mq->mqrq_cur->mmc_active; } else areq = NULL; @@ -1372,8 +1759,15 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) * A block was successfully transferred. */ mmc_blk_reset_success(md, type); - ret = blk_end_request(req, 0, + + if (mmc_packed_cmd(mq_rq->cmd_type)) { + ret = mmc_blk_end_packed_req(mq_rq); + break; + } else { + ret = blk_end_request(req, 0, brq->data.bytes_xfered); + } + /* * If the blk_end_request function returns non-zero even * though all data has been transferred and no errors @@ -1406,7 +1800,8 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) err = mmc_blk_reset(md, card->host, type); if (!err) break; - if (err == -ENODEV) + if (err == -ENODEV || + mmc_packed_cmd(mq_rq->cmd_type)) goto cmd_abort; /* Fall through */ } @@ -1437,22 +1832,38 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) } if (ret) { - /* - * In case of a incomplete request - * prepare it again and resend. - */ - mmc_blk_rw_rq_prep(mq_rq, card, disable_multi, mq); - mmc_start_req(card->host, &mq_rq->mmc_active, NULL); + if (mmc_packed_cmd(mq_rq->cmd_type)) { + if (!mq_rq->packed->retries) + goto cmd_abort; + mmc_blk_packed_hdr_wrq_prep(mq_rq, card, mq); + mmc_start_req(card->host, + &mq_rq->mmc_active, NULL); + } else { + + /* + * In case of a incomplete request + * prepare it again and resend. + */ + mmc_blk_rw_rq_prep(mq_rq, card, + disable_multi, mq); + mmc_start_req(card->host, + &mq_rq->mmc_active, NULL); + } } } while (ret); return 1; cmd_abort: - if (mmc_card_removed(card)) - req->cmd_flags |= REQ_QUIET; - while (ret) - ret = blk_end_request(req, -EIO, blk_rq_cur_bytes(req)); + if (mmc_packed_cmd(mq_rq->cmd_type)) { + mmc_blk_abort_packed_req(mq_rq); + } else { + if (mmc_card_removed(card)) + req->cmd_flags |= REQ_QUIET; + while (ret) + ret = blk_end_request(req, -EIO, + blk_rq_cur_bytes(req)); + } start_new_req: if (rqc) { @@ -1460,6 +1871,12 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) rqc->cmd_flags |= REQ_QUIET; blk_end_request_all(rqc, -EIO); } else { + /* + * If current request is packed, it needs to put back. + */ + if (mmc_packed_cmd(mq->mqrq_cur->cmd_type)) + mmc_blk_revert_packed_req(mq, mq->mqrq_cur); + mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq); mmc_start_req(card->host, &mq->mqrq_cur->mmc_active, NULL); @@ -1634,6 +2051,14 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, blk_queue_flush(md->queue.queue, REQ_FLUSH | REQ_FUA); } + if (mmc_card_mmc(card) && + (area_type == MMC_BLK_DATA_AREA_MAIN) && + (md->flags & MMC_BLK_CMD23) && + card->ext_csd.packed_event_en) { + if (!mmc_packed_init(&md->queue, card)) + md->flags |= MMC_BLK_PACKED_CMD; + } + return md; err_putdisk: @@ -1742,6 +2167,8 @@ static void mmc_blk_remove_req(struct mmc_blk_data *md) /* Then flush out any already in there */ mmc_cleanup_queue(&md->queue); + if (md->flags & MMC_BLK_PACKED_CMD) + mmc_packed_clean(&md->queue); mmc_blk_put(md); } } diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 5e0971016ac5..fa4e44ee7961 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -362,6 +362,49 @@ void mmc_cleanup_queue(struct mmc_queue *mq) } EXPORT_SYMBOL(mmc_cleanup_queue); +int mmc_packed_init(struct mmc_queue *mq, struct mmc_card *card) +{ + struct mmc_queue_req *mqrq_cur = &mq->mqrq[0]; + struct mmc_queue_req *mqrq_prev = &mq->mqrq[1]; + int ret = 0; + + + mqrq_cur->packed = kzalloc(sizeof(struct mmc_packed), GFP_KERNEL); + if (!mqrq_cur->packed) { + pr_warn("%s: unable to allocate packed cmd for mqrq_cur\n", + mmc_card_name(card)); + ret = -ENOMEM; + goto out; + } + + mqrq_prev->packed = kzalloc(sizeof(struct mmc_packed), GFP_KERNEL); + if (!mqrq_prev->packed) { + pr_warn("%s: unable to allocate packed cmd for mqrq_prev\n", + mmc_card_name(card)); + kfree(mqrq_cur->packed); + mqrq_cur->packed = NULL; + ret = -ENOMEM; + goto out; + } + + INIT_LIST_HEAD(&mqrq_cur->packed->list); + INIT_LIST_HEAD(&mqrq_prev->packed->list); + +out: + return ret; +} + +void mmc_packed_clean(struct mmc_queue *mq) +{ + struct mmc_queue_req *mqrq_cur = &mq->mqrq[0]; + struct mmc_queue_req *mqrq_prev = &mq->mqrq[1]; + + kfree(mqrq_cur->packed); + mqrq_cur->packed = NULL; + kfree(mqrq_prev->packed); + mqrq_prev->packed = NULL; +} + /** * mmc_queue_suspend - suspend a MMC request queue * @mq: MMC queue to suspend @@ -406,6 +449,41 @@ void mmc_queue_resume(struct mmc_queue *mq) } } +static unsigned int mmc_queue_packed_map_sg(struct mmc_queue *mq, + struct mmc_packed *packed, + struct scatterlist *sg, + enum mmc_packed_type cmd_type) +{ + struct scatterlist *__sg = sg; + unsigned int sg_len = 0; + struct request *req; + + if (mmc_packed_wr(cmd_type)) { + unsigned int hdr_sz = mmc_large_sector(mq->card) ? 4096 : 512; + unsigned int max_seg_sz = queue_max_segment_size(mq->queue); + unsigned int len, remain, offset = 0; + u8 *buf = (u8 *)packed->cmd_hdr; + + remain = hdr_sz; + do { + len = min(remain, max_seg_sz); + sg_set_buf(__sg, buf + offset, len); + offset += len; + remain -= len; + (__sg++)->page_link &= ~0x02; + sg_len++; + } while (remain); + } + + list_for_each_entry(req, &packed->list, queuelist) { + sg_len += blk_rq_map_sg(mq->queue, req, __sg); + __sg = sg + (sg_len - 1); + (__sg++)->page_link &= ~0x02; + } + sg_mark_end(sg + (sg_len - 1)); + return sg_len; +} + /* * Prepare the sg list(s) to be handed of to the host driver */ @@ -414,14 +492,26 @@ unsigned int mmc_queue_map_sg(struct mmc_queue *mq, struct mmc_queue_req *mqrq) unsigned int sg_len; size_t buflen; struct scatterlist *sg; + enum mmc_packed_type cmd_type; int i; - if (!mqrq->bounce_buf) - return blk_rq_map_sg(mq->queue, mqrq->req, mqrq->sg); + cmd_type = mqrq->cmd_type; + + if (!mqrq->bounce_buf) { + if (mmc_packed_cmd(cmd_type)) + return mmc_queue_packed_map_sg(mq, mqrq->packed, + mqrq->sg, cmd_type); + else + return blk_rq_map_sg(mq->queue, mqrq->req, mqrq->sg); + } BUG_ON(!mqrq->bounce_sg); - sg_len = blk_rq_map_sg(mq->queue, mqrq->req, mqrq->bounce_sg); + if (mmc_packed_cmd(cmd_type)) + sg_len = mmc_queue_packed_map_sg(mq, mqrq->packed, + mqrq->bounce_sg, cmd_type); + else + sg_len = blk_rq_map_sg(mq->queue, mqrq->req, mqrq->bounce_sg); mqrq->bounce_sg_len = sg_len; diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index e20c27b2b8b4..031bf6376c99 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -12,6 +12,23 @@ struct mmc_blk_request { struct mmc_data data; }; +enum mmc_packed_type { + MMC_PACKED_NONE = 0, + MMC_PACKED_WRITE, +}; + +#define mmc_packed_cmd(type) ((type) != MMC_PACKED_NONE) +#define mmc_packed_wr(type) ((type) == MMC_PACKED_WRITE) + +struct mmc_packed { + struct list_head list; + u32 cmd_hdr[1024]; + unsigned int blocks; + u8 nr_entries; + u8 retries; + s16 idx_failure; +}; + struct mmc_queue_req { struct request *req; struct mmc_blk_request brq; @@ -20,6 +37,8 @@ struct mmc_queue_req { struct scatterlist *bounce_sg; unsigned int bounce_sg_len; struct mmc_async_req mmc_active; + enum mmc_packed_type cmd_type; + struct mmc_packed *packed; }; struct mmc_queue { @@ -49,4 +68,7 @@ extern unsigned int mmc_queue_map_sg(struct mmc_queue *, extern void mmc_queue_bounce_pre(struct mmc_queue_req *); extern void mmc_queue_bounce_post(struct mmc_queue_req *); +extern int mmc_packed_init(struct mmc_queue *, struct mmc_card *); +extern void mmc_packed_clean(struct mmc_queue *); + #endif diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 6d8f7012d73a..49f04bc9d0eb 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -363,6 +363,7 @@ int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD, ext_csd, 512); } +EXPORT_SYMBOL_GPL(mmc_send_ext_csd); int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp) { diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 237f253f2fe0..61b2c30c903b 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -310,6 +310,11 @@ static inline void mmc_part_add(struct mmc_card *card, unsigned int size, card->nr_parts++; } +static inline bool mmc_large_sector(struct mmc_card *card) +{ + return card->ext_csd.data_sector_size == 4096; +} + /* * The world is not perfect and supplies us with broken mmc/sdio devices. * For at least some of these bugs we need a work-around. diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 495d1336149c..39613b9a6fc5 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -18,6 +18,9 @@ struct mmc_request; struct mmc_command { u32 opcode; u32 arg; +#define MMC_CMD23_ARG_REL_WR (1 << 31) +#define MMC_CMD23_ARG_PACKED ((0 << 31) | (1 << 30)) +#define MMC_CMD23_ARG_TAG_REQ (1 << 29) u32 resp[4]; unsigned int flags; /* expected response type */ #define MMC_RSP_PRESENT (1 << 0) @@ -148,6 +151,7 @@ extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, extern void mmc_start_bkops(struct mmc_card *card, bool from_exception); extern int __mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int, bool); extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int); +extern int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd); #define MMC_ERASE_ARG 0x00000000 #define MMC_SECURE_ERASE_ARG 0x80000000 diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index a0466c03f5e1..fd5fd5a6026f 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -465,6 +465,11 @@ static inline int mmc_host_uhs(struct mmc_host *host) MMC_CAP_UHS_DDR50); } +static inline int mmc_host_packed_wr(struct mmc_host *host) +{ + return host->caps2 & MMC_CAP2_PACKED_WR; +} + #ifdef CONFIG_MMC_CLKGATE void mmc_host_clk_hold(struct mmc_host *host); void mmc_host_clk_release(struct mmc_host *host); -- cgit v1.2.3 From 29866a98be716111016da69f2747e012843b61e4 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 8 Feb 2013 20:56:27 -0700 Subject: mmc: bcm2835: set SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK does basically the same as implementing struct sdhci_ops .get_timeout_clock, so simply set that quirk and remove the custom code to simplify the driver. Reported-by: Lars-Peter Clausen Signed-off-by: Stephen Warren Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-bcm2835.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-bcm2835.c b/drivers/mmc/host/sdhci-bcm2835.c index 1e97b89dac66..8ffea05152c6 100644 --- a/drivers/mmc/host/sdhci-bcm2835.c +++ b/drivers/mmc/host/sdhci-bcm2835.c @@ -133,11 +133,11 @@ static struct sdhci_ops bcm2835_sdhci_ops = { .read_b = bcm2835_sdhci_readb, .get_max_clock = sdhci_pltfm_clk_get_max_clock, .get_min_clock = bcm2835_sdhci_get_min_clock, - .get_timeout_clock = sdhci_pltfm_clk_get_max_clock, }; static struct sdhci_pltfm_data bcm2835_sdhci_pdata = { - .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION, + .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION | + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK, .ops = &bcm2835_sdhci_ops, }; -- cgit v1.2.3 From a4f8f257eddcdf13417476c8479c616560a4417a Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Tue, 12 Feb 2013 09:01:36 +0100 Subject: mmc: sdhci: check voltage range only on regulators aware of voltage value Some regulators don't report any voltage values, so checking supported voltage range results in disabling all SDHCI_CAN_VDD_* flags and registration failure. This patch finally provides a correct fix for the registration of SDHCI driver with all possible voltage regulators: dummy, fixed and regulated without using regulator_count_voltages() hacks. Signed-off-by: Marek Szyprowski Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index ba586ae99252..735526bf8d58 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2976,7 +2976,11 @@ int sdhci_add_host(struct sdhci_host *host) } #ifdef CONFIG_REGULATOR - if (host->vmmc) { + /* + * Voltage range check makes sense only if regulator reports + * any voltage value. + */ + if (host->vmmc && regulator_get_voltage(host->vmmc) > 0) { ret = regulator_is_supported_voltage(host->vmmc, 2700000, 3600000); if ((ret <= 0) || (!(caps[0] & SDHCI_CAN_VDD_330))) -- cgit v1.2.3 From 21f5998f10a4d10dce48e9831ab2deb7e8f1a1fa Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 14 Feb 2013 10:35:03 -0200 Subject: mmc: sdhci: Fix parameter of sdhci_do_start_signal_voltage_switch() 3714f4315354 ("mmc: sdhci: update signal voltage switch code") changed the type of the second parameter of sdhci_do_start_signal_voltage_switch(), from "struct mmc_ios *ios" to "int signal_voltage" which causes the following build warning: drivers/mmc/host/sdhci.c:2044:2: warning: initialization from incompatible pointer type [enabled by default] drivers/mmc/host/sdhci.c:2044:2: warning: (near initialization for 'sdhci_ops.start_signal_voltage_switch') [enabled by default] Use the previous type so that it matches the start_signal_voltage_switch() definition from host.h. Signed-off-by: Fabio Estevam Reviewed-by: Johan Rudholm Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 735526bf8d58..51bbba486f38 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1675,7 +1675,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) } static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, - int signal_voltage) + struct mmc_ios *ios) { u16 ctrl; int ret; @@ -1689,7 +1689,7 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); - switch (signal_voltage) { + switch (ios->signal_voltage) { case MMC_SIGNAL_VOLTAGE_330: /* Set 1.8V Signal Enable in the Host Control2 register to 0 */ ctrl &= ~SDHCI_CTRL_VDD_180; @@ -1762,7 +1762,7 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, } static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, - int signal_voltage) + struct mmc_ios *ios) { struct sdhci_host *host = mmc_priv(mmc); int err; @@ -1770,7 +1770,7 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, if (host->version < SDHCI_SPEC_300) return 0; sdhci_runtime_pm_get(host); - err = sdhci_do_start_signal_voltage_switch(host, signal_voltage); + err = sdhci_do_start_signal_voltage_switch(host, ios); sdhci_runtime_pm_put(host); return err; } -- cgit v1.2.3 From 7b952137fdc5e2c52c990e7cbaf09450bcf01d5e Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:13:50 +0100 Subject: mmc: sdhi, tmio: only check flags in tmio-mmc driver proper tmio-mmc platform flags can be set by various means, including caller drivers and device-tree bindings, therefore it is better to only check them in the tmio-mmc driver proper, not in caller drivers themselves. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 3 +-- drivers/mmc/host/tmio_mmc_pio.c | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 524a7f773820..e0ca0abba0e5 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -153,10 +153,9 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) mmc_data->clk_enable = sh_mobile_sdhi_clk_enable; mmc_data->clk_disable = sh_mobile_sdhi_clk_disable; mmc_data->capabilities = MMC_CAP_MMC_HIGHSPEED; + mmc_data->write16_hook = sh_mobile_sdhi_write16_hook; if (p) { mmc_data->flags = p->tmio_flags; - if (mmc_data->flags & TMIO_MMC_HAS_IDLE_WAIT) - 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->capabilities2 |= p->tmio_caps2; diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index 0f992e9ffc73..b25adb4160f5 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -928,6 +928,9 @@ int tmio_mmc_host_probe(struct tmio_mmc_host **host, int ret; u32 irq_mask = TMIO_MASK_CMD; + if (!(pdata->flags & TMIO_MMC_HAS_IDLE_WAIT)) + pdata->write16_hook = NULL; + res_ctl = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res_ctl) return -EINVAL; -- cgit v1.2.3 From 6da15e96fb14e7428ecdc69306d8a3287296a968 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:13:51 +0100 Subject: mmc: detailed definition of CD and WP MMC line polarities in DT Clarify ways to specify write-protect and card-detect MMC lines in FDT. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- Documentation/devicetree/bindings/mmc/mmc.txt | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt index 469cf53d5a92..654b705fc5b7 100644 --- a/Documentation/devicetree/bindings/mmc/mmc.txt +++ b/Documentation/devicetree/bindings/mmc/mmc.txt @@ -17,12 +17,31 @@ Optional properties: - bus-width: Number of data lines, can be <1>, <4>, or <8>. The default will be <1> if the property is absent. - wp-gpios: Specify GPIOs for write protection, see gpio binding -- cd-inverted: when present, polarity on the cd gpio line is inverted -- wp-inverted: when present, polarity on the wp gpio line is inverted +- cd-inverted: when present, polarity on the CD line is inverted. See the note + below for the case, when a GPIO is used for the CD line +- wp-inverted: when present, polarity on the WP line is inverted. See the note + below for the case, when a GPIO is used for the WP line - max-frequency: maximum operating clock frequency - no-1-8-v: when present, denotes that 1.8v card voltage is not supported on this system, even if the controller claims it is. +*NOTE* on CD and WP polarity. To use common for all SD/MMC host controllers line +polarity properties, we have to fix the meaning of the "normal" and "inverted" +line levels. We choose to follow the SDHCI standard, which specifies both those +lines as "active low." Therefore, using the "cd-inverted" property means, that +the CD line is active high, i.e. it is high, when a card is inserted. Similar +logic applies to the "wp-inverted" property. + +CD and WP lines can be implemented on the hardware in one of two ways: as GPIOs, +specified in cd-gpios and wp-gpios properties, or as dedicated pins. Polarity of +dedicated pins can be specified, using *-inverted properties. GPIO polarity can +also be specified using the OF_GPIO_ACTIVE_LOW flag. This creates an ambiguity +in the latter case. We choose to use the XOR logic for GPIO CD and WP lines. +This means, the two properties are "superimposed," for example leaving the +OF_GPIO_ACTIVE_LOW flag clear and specifying the respective *-inverted +property results in a double-inversion and actually means the "normal" line +polarity is in effect. + Optional SDIO properties: - keep-power-in-suspend: Preserves card power during a suspend/resume cycle - enable-sdio-wakeup: Enables wake up of host system on SDIO IRQ assertion -- cgit v1.2.3 From 6c56e7a0fff166904ce2715f7ab1746460c1f11b Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Sat, 16 Feb 2013 16:21:16 +0100 Subject: mmc: provide a standard MMC device-tree binding parser centrally MMC defines a number of standard DT bindings. Having each driver parse them individually adds code redundancy and is error prone. Provide a standard function to unify the parsing. After all drivers are converted to using it instead of their own parsers, this function can be integrated into mmc_alloc_host(). Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/core/host.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/host.h | 1 + 2 files changed, 111 insertions(+) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index ee2e16b17017..9c53452e73e1 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include +#include #include "core.h" #include "host.h" @@ -294,6 +297,113 @@ static inline void mmc_host_clk_sysfs_init(struct mmc_host *host) #endif +/** + * mmc_of_parse() - parse host's device-tree node + * @host: host whose node should be parsed. + * + * To keep the rest of the MMC subsystem unaware of whether DT has been + * used to to instantiate and configure this host instance or not, we + * parse the properties and set respective generic mmc-host flags and + * parameters. + */ +void mmc_of_parse(struct mmc_host *host) +{ + struct device_node *np; + u32 bus_width; + bool explicit_inv_wp, gpio_inv_wp = false; + enum of_gpio_flags flags; + int len, ret, gpio; + + if (!host->parent || !host->parent->of_node) + return; + + np = host->parent->of_node; + + /* "bus-width" is translated to MMC_CAP_*_BIT_DATA flags */ + if (of_property_read_u32(np, "bus-width", &bus_width) < 0) { + dev_dbg(host->parent, + "\"bus-width\" property is missing, assuming 1 bit.\n"); + bus_width = 1; + } + + switch (bus_width) { + case 8: + host->caps |= MMC_CAP_8_BIT_DATA; + /* Hosts capable of 8-bit transfers can also do 4 bits */ + case 4: + host->caps |= MMC_CAP_4_BIT_DATA; + break; + case 1: + break; + default: + dev_err(host->parent, + "Invalid \"bus-width\" value %ud!\n", bus_width); + } + + /* f_max is obtained from the optional "max-frequency" property */ + of_property_read_u32(np, "max-frequency", &host->f_max); + + /* + * Configure CD and WP pins. They are both by default active low to + * match the SDHCI spec. If GPIOs are provided for CD and / or WP, the + * mmc-gpio helpers are used to attach, configure and use them. If + * polarity inversion is specified in DT, one of MMC_CAP2_CD_ACTIVE_HIGH + * and MMC_CAP2_RO_ACTIVE_HIGH capability-2 flags is set. If the + * "broken-cd" property is provided, the MMC_CAP_NEEDS_POLL capability + * is set. If the "non-removable" property is found, the + * MMC_CAP_NONREMOVABLE capability is set and no card-detection + * configuration is performed. + */ + + /* Parse Card Detection */ + if (of_find_property(np, "non-removable", &len)) { + host->caps |= MMC_CAP_NONREMOVABLE; + } else { + bool explicit_inv_cd, gpio_inv_cd = false; + + explicit_inv_cd = of_property_read_bool(np, "cd-inverted"); + + if (of_find_property(np, "broken-cd", &len)) + host->caps |= MMC_CAP_NEEDS_POLL; + + gpio = of_get_named_gpio_flags(np, "cd-gpios", 0, &flags); + if (gpio_is_valid(gpio)) { + if (!(flags & OF_GPIO_ACTIVE_LOW)) + gpio_inv_cd = true; + + ret = mmc_gpio_request_cd(host, gpio); + if (ret < 0) + dev_err(host->parent, + "Failed to request CD GPIO #%d: %d!\n", + gpio, ret); + else + dev_info(host->parent, "Got CD GPIO #%d.\n", + gpio); + } + + if (explicit_inv_cd ^ gpio_inv_cd) + host->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH; + } + + /* Parse Write Protection */ + explicit_inv_wp = of_property_read_bool(np, "wp-inverted"); + + gpio = of_get_named_gpio_flags(np, "wp-gpios", 0, &flags); + if (gpio_is_valid(gpio)) { + if (!(flags & OF_GPIO_ACTIVE_LOW)) + gpio_inv_wp = true; + + ret = mmc_gpio_request_ro(host, gpio); + if (ret < 0) + dev_err(host->parent, + "Failed to request WP GPIO: %d!\n", ret); + } + if (explicit_inv_wp ^ gpio_inv_wp) + host->caps2 |= MMC_CAP2_RO_ACTIVE_HIGH; +} + +EXPORT_SYMBOL(mmc_of_parse); + /** * mmc_alloc_host - initialise the per-host structure. * @extra: sizeof private data structure diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index fd5fd5a6026f..bf93c9a6d729 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -368,6 +368,7 @@ extern struct mmc_host *mmc_alloc_host(int extra, struct device *); extern int mmc_add_host(struct mmc_host *); extern void mmc_remove_host(struct mmc_host *); extern void mmc_free_host(struct mmc_host *); +void mmc_of_parse(struct mmc_host *host); static inline void *mmc_priv(struct mmc_host *host) { -- cgit v1.2.3 From 8c9beb117bf31cdb757bc80992281004be8a177b Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:13:53 +0100 Subject: mmc: (cosmetic) remove "extern" from function declarations The "extern" keyword isn't required in function declarations, remove it. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- include/linux/mmc/host.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index bf93c9a6d729..d6f20cc6415e 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -364,10 +364,10 @@ struct mmc_host { unsigned long private[0] ____cacheline_aligned; }; -extern struct mmc_host *mmc_alloc_host(int extra, struct device *); -extern int mmc_add_host(struct mmc_host *); -extern void mmc_remove_host(struct mmc_host *); -extern void mmc_free_host(struct mmc_host *); +struct mmc_host *mmc_alloc_host(int extra, struct device *); +int mmc_add_host(struct mmc_host *); +void mmc_remove_host(struct mmc_host *); +void mmc_free_host(struct mmc_host *); void mmc_of_parse(struct mmc_host *host); static inline void *mmc_priv(struct mmc_host *host) @@ -381,16 +381,16 @@ static inline void *mmc_priv(struct mmc_host *host) #define mmc_classdev(x) (&(x)->class_dev) #define mmc_hostname(x) (dev_name(&(x)->class_dev)) -extern int mmc_suspend_host(struct mmc_host *); -extern int mmc_resume_host(struct mmc_host *); +int mmc_suspend_host(struct mmc_host *); +int mmc_resume_host(struct mmc_host *); -extern int mmc_power_save_host(struct mmc_host *host); -extern int mmc_power_restore_host(struct mmc_host *host); +int mmc_power_save_host(struct mmc_host *host); +int mmc_power_restore_host(struct mmc_host *host); -extern void mmc_detect_change(struct mmc_host *, unsigned long delay); -extern void mmc_request_done(struct mmc_host *, struct mmc_request *); +void mmc_detect_change(struct mmc_host *, unsigned long delay); +void mmc_request_done(struct mmc_host *, struct mmc_request *); -extern int mmc_cache_ctrl(struct mmc_host *, u8); +int mmc_cache_ctrl(struct mmc_host *, u8); static inline void mmc_signal_sdio_irq(struct mmc_host *host) { -- cgit v1.2.3 From eca889f60c6dc6415bc0130615561285f02e31ed Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:13:54 +0100 Subject: mmc: sh_mmcif: use mmc_of_parse() to parse standard MMC DT bindings Use mmc_of_parse() to get interface capability flags and used GPIOs from device-tree bindings. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 0189efcb9e12..ba76a532ae30 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1369,6 +1369,7 @@ static int sh_mmcif_probe(struct platform_device *pdev) ret = -ENOMEM; goto ealloch; } + mmc_of_parse(mmc); host = mmc_priv(mmc); host->mmc = mmc; host->addr = reg; @@ -1381,7 +1382,7 @@ static int sh_mmcif_probe(struct platform_device *pdev) mmc->ops = &sh_mmcif_ops; sh_mmcif_init_ocr(host); - mmc->caps = MMC_CAP_MMC_HIGHSPEED | MMC_CAP_WAIT_WHILE_BUSY; + mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_WAIT_WHILE_BUSY; if (pd && pd->caps) mmc->caps |= pd->caps; mmc->max_segs = 32; -- cgit v1.2.3 From d804820fa3302cb3d11f68a840e718d082967ad4 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:13:55 +0100 Subject: mmc: tmio-mmc: define device-tree bindings Define device-tree bindings for the tmio-mmc driver to be able to specify parameters, currently provided in platform data. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- Documentation/devicetree/bindings/mmc/tmio_mmc.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/tmio_mmc.txt diff --git a/Documentation/devicetree/bindings/mmc/tmio_mmc.txt b/Documentation/devicetree/bindings/mmc/tmio_mmc.txt new file mode 100644 index 000000000000..df204e18e030 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/tmio_mmc.txt @@ -0,0 +1,20 @@ +* Toshiba Mobile IO SD/MMC controller + +The tmio-mmc driver doesn't probe its devices actively, instead its binding to +devices is managed by either MFD drivers or by the sh_mobile_sdhi platform +driver. Those drivers supply the tmio-mmc driver with platform data, that either +describe hardware capabilities, known to them, or are obtained by them from +their own platform data or from their DT information. In the latter case all +compulsory and any optional properties, common to all SD/MMC drivers, as +described in mmc.txt, can be used. Additionally the following tmio_mmc-specific +optional bindings can be used. + +Optional properties: +- toshiba,mmc-wrprotect-disable: write-protect detection is unavailable + +When used with Renesas SDHI hardware, the following compatibility strings +configure various model-specific properties: + +"renesas,sh7372-sdhi": (default) compatible with SH7372 +"renesas,r8a7740-sdhi": compatible with R8A7740: certain MMC/SD commands have to + wait for the interface to become idle. -- cgit v1.2.3 From 5a00a971a2b12edf52825fb196b146b5fb6335a3 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:13:56 +0100 Subject: mmc: tmio-mmc: parse device-tree bindings Add parsing of common and driver-specific DT bindings to the tmio-mmc MMC host driver and the sh_mobile_sdhi interface layer. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 32 ++++++++++++++++++++++++++------ drivers/mmc/host/tmio_mmc_pio.c | 20 ++++++++++++++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index e0ca0abba0e5..06e1bc98c536 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,16 @@ #include "tmio_mmc.h" +struct sh_mobile_sdhi_of_data { + unsigned long tmio_flags; +}; + +static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = { + { + .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT, + }, +}; + struct sh_mobile_sdhi { struct clk *clk; struct tmio_mmc_data mmc_data; @@ -117,8 +128,18 @@ static const struct sh_mobile_sdhi_ops sdhi_ops = { .cd_wakeup = sh_mobile_sdhi_cd_wakeup, }; +static const struct of_device_id sh_mobile_sdhi_of_match[] = { + { .compatible = "renesas,shmobile-sdhi" }, + { .compatible = "renesas,sh7372-sdhi" }, + { .compatible = "renesas,r8a7740-sdhi", .data = &sh_mobile_sdhi_of_cfg[0], }, + {}, +}; +MODULE_DEVICE_TABLE(of, sh_mobile_sdhi_of_match); + static int sh_mobile_sdhi_probe(struct platform_device *pdev) { + const struct of_device_id *of_id = + of_match_device(sh_mobile_sdhi_of_match, &pdev->dev); struct sh_mobile_sdhi *priv; struct tmio_mmc_data *mmc_data; struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; @@ -186,6 +207,11 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) */ mmc_data->flags |= TMIO_MMC_SDIO_IRQ; + if (of_id && of_id->data) { + const struct sh_mobile_sdhi_of_data *of_data = of_id->data; + mmc_data->flags |= of_data->tmio_flags; + } + ret = tmio_mmc_host_probe(&host, pdev, mmc_data); if (ret < 0) goto eprobe; @@ -313,12 +339,6 @@ static const struct dev_pm_ops tmio_mmc_dev_pm_ops = { .runtime_resume = tmio_mmc_host_runtime_resume, }; -static const struct of_device_id sh_mobile_sdhi_of_match[] = { - { .compatible = "renesas,shmobile-sdhi" }, - { } -}; -MODULE_DEVICE_TABLE(of, sh_mobile_sdhi_of_match); - static struct platform_driver sh_mobile_sdhi_driver = { .driver = { .name = "sh_mobile_sdhi", diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index b25adb4160f5..e5b3fcf55c4c 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -918,6 +918,17 @@ static void tmio_mmc_init_ocr(struct tmio_mmc_host *host) dev_warn(mmc_dev(mmc), "Platform OCR mask is ignored\n"); } +static void tmio_mmc_of_parse(struct platform_device *pdev, + struct tmio_mmc_data *pdata) +{ + const struct device_node *np = pdev->dev.of_node; + if (!np) + return; + + if (of_get_property(np, "toshiba,mmc-wrprotect-disable", NULL)) + pdata->flags |= TMIO_MMC_WRPROTECT_DISABLE; +} + int tmio_mmc_host_probe(struct tmio_mmc_host **host, struct platform_device *pdev, struct tmio_mmc_data *pdata) @@ -928,6 +939,8 @@ int tmio_mmc_host_probe(struct tmio_mmc_host **host, int ret; u32 irq_mask = TMIO_MASK_CMD; + tmio_mmc_of_parse(pdev, pdata); + if (!(pdata->flags & TMIO_MMC_HAS_IDLE_WAIT)) pdata->write16_hook = NULL; @@ -939,6 +952,8 @@ int tmio_mmc_host_probe(struct tmio_mmc_host **host, if (!mmc) return -ENOMEM; + mmc_of_parse(mmc); + pdata->dev = &pdev->dev; _host = mmc_priv(mmc); _host->pdata = pdata; @@ -959,7 +974,7 @@ int tmio_mmc_host_probe(struct tmio_mmc_host **host, } mmc->ops = &tmio_mmc_ops; - mmc->caps = MMC_CAP_4_BIT_DATA | pdata->capabilities; + mmc->caps |= MMC_CAP_4_BIT_DATA | pdata->capabilities; mmc->caps2 = pdata->capabilities2; mmc->max_segs = 32; mmc->max_blk_size = 512; @@ -971,7 +986,8 @@ int tmio_mmc_host_probe(struct tmio_mmc_host **host, _host->native_hotplug = !(pdata->flags & TMIO_MMC_USE_GPIO_CD || mmc->caps & MMC_CAP_NEEDS_POLL || - mmc->caps & MMC_CAP_NONREMOVABLE); + mmc->caps & MMC_CAP_NONREMOVABLE || + mmc->slot.cd_irq >= 0); _host->power = false; pm_runtime_enable(&pdev->dev); -- cgit v1.2.3 From 76a411f9f9423cbc9f62e70173459c5af54323f4 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:13:57 +0100 Subject: mmc: sh_mobile_sdhi: remove unused .pdata field The struct sh_mobile_sdhi_info::pdata field was only used for platform- based card detection and isn't used anymore since the migration to GPIO- based MMC slot functions. Remove it. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 4 ---- include/linux/mmc/sh_mobile_sdhi.h | 2 -- 2 files changed, 6 deletions(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 06e1bc98c536..175ab970952a 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -156,7 +156,6 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) mmc_data = &priv->mmc_data; if (p) { - p->pdata = mmc_data; if (p->init) { ret = p->init(pdev, &sdhi_ops); if (ret) @@ -310,9 +309,6 @@ static int sh_mobile_sdhi_remove(struct platform_device *pdev) struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; int i = 0, irq; - if (p) - p->pdata = NULL; - tmio_mmc_host_remove(host); while (1) { diff --git a/include/linux/mmc/sh_mobile_sdhi.h b/include/linux/mmc/sh_mobile_sdhi.h index b65679ffa880..b76bcf0621f6 100644 --- a/include/linux/mmc/sh_mobile_sdhi.h +++ b/include/linux/mmc/sh_mobile_sdhi.h @@ -4,7 +4,6 @@ #include struct platform_device; -struct tmio_mmc_data; #define SH_MOBILE_SDHI_IRQ_CARD_DETECT "card_detect" #define SH_MOBILE_SDHI_IRQ_SDCARD "sdcard" @@ -26,7 +25,6 @@ struct sh_mobile_sdhi_info { unsigned long tmio_caps2; u32 tmio_ocr_mask; /* available MMC voltages */ unsigned int cd_gpio; - struct tmio_mmc_data *pdata; void (*set_pwr)(struct platform_device *pdev, int state); int (*get_cd)(struct platform_device *pdev); -- cgit v1.2.3 From ac51b9611db597aa55e06218c62c3511702d772f Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:13:58 +0100 Subject: mmc: sh_mobile_sdhi: use managed resource allocations Use managed allocations to get memory, clock and interrupts . This significantly simplifies clean up paths. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 57 ++++++++++----------------------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 175ab970952a..fe90853900b4 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -147,7 +147,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) int irq, ret, i = 0; bool multiplexed_isr = true; - priv = kzalloc(sizeof(struct sh_mobile_sdhi), GFP_KERNEL); + priv = devm_kzalloc(&pdev->dev, sizeof(struct sh_mobile_sdhi), GFP_KERNEL); if (priv == NULL) { dev_err(&pdev->dev, "kzalloc failed\n"); return -ENOMEM; @@ -159,11 +159,11 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) if (p->init) { ret = p->init(pdev, &sdhi_ops); if (ret) - goto einit; + return ret; } } - priv->clk = clk_get(&pdev->dev, NULL); + priv->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(priv->clk)) { ret = PTR_ERR(priv->clk); dev_err(&pdev->dev, "cannot get clock: %d\n", ret); @@ -223,33 +223,33 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_CARD_DETECT); if (irq >= 0) { multiplexed_isr = false; - ret = request_irq(irq, tmio_mmc_card_detect_irq, 0, + ret = devm_request_irq(&pdev->dev, irq, tmio_mmc_card_detect_irq, 0, dev_name(&pdev->dev), host); if (ret) - goto eirq_card_detect; + goto eirq; } irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDIO); if (irq >= 0) { multiplexed_isr = false; - ret = request_irq(irq, tmio_mmc_sdio_irq, 0, + ret = devm_request_irq(&pdev->dev, irq, tmio_mmc_sdio_irq, 0, dev_name(&pdev->dev), host); if (ret) - goto eirq_sdio; + goto eirq; } irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDCARD); if (irq >= 0) { multiplexed_isr = false; - ret = request_irq(irq, tmio_mmc_sdcard_irq, 0, + ret = devm_request_irq(&pdev->dev, irq, tmio_mmc_sdcard_irq, 0, dev_name(&pdev->dev), host); if (ret) - goto eirq_sdcard; + goto eirq; } else if (!multiplexed_isr) { dev_err(&pdev->dev, "Principal SD-card IRQ is missing among named interrupts\n"); ret = irq; - goto eirq_sdcard; + goto eirq; } if (multiplexed_isr) { @@ -258,15 +258,15 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) if (irq < 0) break; i++; - ret = request_irq(irq, tmio_mmc_irq, 0, + ret = devm_request_irq(&pdev->dev, irq, tmio_mmc_irq, 0, dev_name(&pdev->dev), host); if (ret) - goto eirq_multiplexed; + goto eirq; } /* There must be at least one IRQ source */ if (!i) - goto eirq_multiplexed; + goto eirq; } dev_info(&pdev->dev, "%s base at 0x%08lx clock rate %u MHz\n", @@ -276,28 +276,12 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) return ret; -eirq_multiplexed: - while (i--) { - irq = platform_get_irq(pdev, i); - free_irq(irq, host); - } -eirq_sdcard: - irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDIO); - if (irq >= 0) - free_irq(irq, host); -eirq_sdio: - irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_CARD_DETECT); - if (irq >= 0) - free_irq(irq, host); -eirq_card_detect: +eirq: tmio_mmc_host_remove(host); eprobe: - clk_put(priv->clk); eclkget: if (p && p->cleanup) p->cleanup(pdev); -einit: - kfree(priv); return ret; } @@ -305,26 +289,13 @@ static int sh_mobile_sdhi_remove(struct platform_device *pdev) { struct mmc_host *mmc = platform_get_drvdata(pdev); struct tmio_mmc_host *host = mmc_priv(mmc); - struct sh_mobile_sdhi *priv = container_of(host->pdata, struct sh_mobile_sdhi, mmc_data); struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; - int i = 0, irq; tmio_mmc_host_remove(host); - while (1) { - irq = platform_get_irq(pdev, i++); - if (irq < 0) - break; - free_irq(irq, host); - } - - clk_put(priv->clk); - if (p && p->cleanup) p->cleanup(pdev); - kfree(priv); - return 0; } -- cgit v1.2.3 From 27902c14aa2376d53755b6c02e3be671fd890e30 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:13:59 +0100 Subject: mmc: tmio: remove unused and deprecated symbols The tmio_mmc_cd_wakeup() inline function has been deprecated since 3.4 and is unused since 3.4 too. Remove them. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- include/linux/mfd/tmio.h | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/include/linux/mfd/tmio.h b/include/linux/mfd/tmio.h index d83af39815ab..99bf3e665997 100644 --- a/include/linux/mfd/tmio.h +++ b/include/linux/mfd/tmio.h @@ -64,12 +64,6 @@ * Some controllers can support SDIO IRQ signalling. */ #define TMIO_MMC_SDIO_IRQ (1 << 2) -/* - * Some platforms can detect card insertion events with controller powered - * down, using a GPIO IRQ, in which case they have to fill in cd_irq, cd_gpio, - * and cd_flags fields of struct tmio_mmc_data. - */ -#define TMIO_MMC_HAS_COLD_CD (1 << 3) /* * Some controllers require waiting for the SD bus to become * idle before writing to some registers. @@ -116,18 +110,6 @@ struct tmio_mmc_data { void (*clk_disable)(struct platform_device *pdev); }; -/* - * This function is deprecated and will be removed soon. Please, convert your - * platform to use drivers/mmc/core/cd-gpio.c - */ -#include -static inline void tmio_mmc_cd_wakeup(struct tmio_mmc_data *pdata) -{ - if (pdata) - mmc_detect_change(dev_get_drvdata(pdata->dev), - msecs_to_jiffies(100)); -} - /* * data for the NAND controller */ -- cgit v1.2.3 From 619b08d45a40229040d6db8c9fb1f40b7e58b71f Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:14:00 +0100 Subject: mmc: tmio: add support for the VccQ regulator Some SD/MMC interfaces use 2 power regulators: one to power the card itself (Vcc) and another one to pull signal lines up (VccQ). In case of eMMC and UHS SD cards the regulators also have to be configured to supply different voltages. The preferred order of turning supply power on and off is to turn Vcc first on and last off. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/tmio_mmc_pio.c | 56 +++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index e5b3fcf55c4c..f508ecb5b8a7 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -155,6 +156,7 @@ static void tmio_mmc_set_clock(struct tmio_mmc_host *host, int new_clock) host->set_clk_div(host->pdev, (clk>>22) & 1); sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, clk & 0x1ff); + msleep(10); } static void tmio_mmc_clk_stop(struct tmio_mmc_host *host) @@ -768,16 +770,48 @@ static int tmio_mmc_clk_update(struct mmc_host *mmc) return ret; } -static void tmio_mmc_set_power(struct tmio_mmc_host *host, struct mmc_ios *ios) +static void tmio_mmc_power_on(struct tmio_mmc_host *host, unsigned short vdd) { struct mmc_host *mmc = host->mmc; + int ret = 0; + + /* .set_ios() is returning void, so, no chance to report an error */ if (host->set_pwr) - host->set_pwr(host->pdev, ios->power_mode != MMC_POWER_OFF); + host->set_pwr(host->pdev, 1); + + if (!IS_ERR(mmc->supply.vmmc)) { + ret = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd); + /* + * Attention: empiric value. With a b43 WiFi SDIO card this + * delay proved necessary for reliable card-insertion probing. + * 100us were not enough. Is this the same 140us delay, as in + * tmio_mmc_set_ios()? + */ + udelay(200); + } + /* + * It seems, VccQ should be switched on after Vcc, this is also what the + * omap_hsmmc.c driver does. + */ + if (!IS_ERR(mmc->supply.vqmmc) && !ret) { + regulator_enable(mmc->supply.vqmmc); + udelay(200); + } +} + +static void tmio_mmc_power_off(struct tmio_mmc_host *host) +{ + struct mmc_host *mmc = host->mmc; + + if (!IS_ERR(mmc->supply.vqmmc)) + regulator_disable(mmc->supply.vqmmc); + if (!IS_ERR(mmc->supply.vmmc)) - /* Errors ignored... */ - mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, - ios->power_mode ? ios->vdd : 0); + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); + + if (host->set_pwr) + host->set_pwr(host->pdev, 0); } /* Set MMC clock / power. @@ -828,18 +862,20 @@ static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) if (!host->power) { tmio_mmc_clk_update(mmc); pm_runtime_get_sync(dev); - host->power = true; } tmio_mmc_set_clock(host, ios->clock); - /* power up SD bus */ - tmio_mmc_set_power(host, ios); + if (!host->power) { + /* power up SD card and the bus */ + tmio_mmc_power_on(host, ios->vdd); + host->power = true; + } /* start bus clock */ tmio_mmc_clk_start(host); } else if (ios->power_mode != MMC_POWER_UP) { - if (ios->power_mode == MMC_POWER_OFF) - tmio_mmc_set_power(host, ios); if (host->power) { struct tmio_mmc_data *pdata = host->pdata; + if (ios->power_mode == MMC_POWER_OFF) + tmio_mmc_power_off(host); tmio_mmc_clk_stop(host); host->power = false; pm_runtime_put(dev); -- cgit v1.2.3 From 2fdb6e2d9bf4b599d1cf8bc2b7874a06608fc7ee Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Feb 2013 16:14:01 +0100 Subject: mmc: add DT bindings for more MMC capability flags Many MMC capability flags are platform-dependent and are traditionally set in platform data. With DT often each such capability requires a special binding. Add bindings for MMC_CAP_SD_HIGHSPEED, MMC_CAP_MMC_HIGHSPEED, MMC_CAP_POWER_OFF_CARD and MMC_CAP_SDIO_IRQ capabilities. Also add code to DT parser to look up "keep-power-in-suspend" and "enable-sdio-wakeup" bindings and set MMC_PM_KEEP_POWER and MMC_PM_WAKE_SDIO_IRQ respectively, if found. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- Documentation/devicetree/bindings/mmc/mmc.txt | 4 ++++ drivers/mmc/core/host.c | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt index 654b705fc5b7..85aada2263d5 100644 --- a/Documentation/devicetree/bindings/mmc/mmc.txt +++ b/Documentation/devicetree/bindings/mmc/mmc.txt @@ -24,6 +24,10 @@ Optional properties: - max-frequency: maximum operating clock frequency - no-1-8-v: when present, denotes that 1.8v card voltage is not supported on this system, even if the controller claims it is. +- cap-sd-highspeed: SD high-speed timing is supported +- cap-mmc-highspeed: MMC high-speed timing is supported +- cap-power-off-card: powering off the card is safe +- cap-sdio-irq: enable SDIO IRQ signalling on this interface *NOTE* on CD and WP polarity. To use common for all SD/MMC host controllers line polarity properties, we have to fix the meaning of the "normal" and "inverted" diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 9c53452e73e1..821cd8224137 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -400,6 +400,19 @@ void mmc_of_parse(struct mmc_host *host) } if (explicit_inv_wp ^ gpio_inv_wp) host->caps2 |= MMC_CAP2_RO_ACTIVE_HIGH; + + if (of_find_property(np, "cap-sd-highspeed", &len)) + host->caps |= MMC_CAP_SD_HIGHSPEED; + if (of_find_property(np, "cap-mmc-highspeed", &len)) + host->caps |= MMC_CAP_MMC_HIGHSPEED; + if (of_find_property(np, "cap-power-off-card", &len)) + host->caps |= MMC_CAP_POWER_OFF_CARD; + if (of_find_property(np, "cap-sdio-irq", &len)) + host->caps |= MMC_CAP_SDIO_IRQ; + if (of_find_property(np, "keep-power-in-suspend", &len)) + host->pm_caps |= MMC_PM_KEEP_POWER; + if (of_find_property(np, "enable-sdio-wakeup", &len)) + host->pm_caps |= MMC_PM_WAKE_SDIO_IRQ; } EXPORT_SYMBOL(mmc_of_parse); -- cgit v1.2.3 From 0e786102949d7461859c6ce9f39c2c8d28e42db3 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 15 Feb 2013 15:07:19 -0700 Subject: mmc: tegra: assume CONFIG_OF, remove platform data Tegra only supports, and always enables, device tree. Remove all ifdefs and runtime checks for DT support from the driver. Platform data is therefore no longer required. Rework the driver to parse the device tree directly into struct sdhci_tegra. Signed-off-by: Stephen Warren Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-tegra.c | 119 ++++++++++---------------- include/linux/platform_data/mmc-sdhci-tegra.h | 28 ------ 2 files changed, 45 insertions(+), 102 deletions(-) delete mode 100644 include/linux/platform_data/mmc-sdhci-tegra.h diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index 5a600a53b876..08b06e9a3a21 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -27,8 +27,6 @@ #include -#include - #include "sdhci-pltfm.h" /* Tegra SDHOST controller vendor register definitions */ @@ -45,8 +43,11 @@ struct sdhci_tegra_soc_data { }; struct sdhci_tegra { - const struct tegra_sdhci_platform_data *plat; const struct sdhci_tegra_soc_data *soc_data; + int cd_gpio; + int wp_gpio; + int power_gpio; + int is_8bit; }; static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) @@ -108,12 +109,11 @@ static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host) { 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)) + if (!gpio_is_valid(tegra_host->wp_gpio)) return -1; - return gpio_get_value(plat->wp_gpio); + return gpio_get_value(tegra_host->wp_gpio); } static irqreturn_t carddetect_irq(int irq, void *data) @@ -147,11 +147,10 @@ static int tegra_sdhci_buswidth(struct sdhci_host *host, int bus_width) { 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; u32 ctrl; ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); - if (plat->is_8bit && bus_width == MMC_BUS_WIDTH_8) { + if (tegra_host->is_8bit && bus_width == MMC_BUS_WIDTH_8) { ctrl &= ~SDHCI_CTRL_4BITBUS; ctrl |= SDHCI_CTRL_8BITBUS; } else { @@ -217,31 +216,19 @@ static const struct of_device_id sdhci_tegra_dt_match[] = { }; MODULE_DEVICE_TABLE(of, sdhci_dt_ids); -static struct tegra_sdhci_platform_data *sdhci_tegra_dt_parse_pdata( - struct platform_device *pdev) +static void sdhci_tegra_parse_dt(struct device *dev, + struct sdhci_tegra *tegra_host) { - struct tegra_sdhci_platform_data *plat; - struct device_node *np = pdev->dev.of_node; + struct device_node *np = dev->of_node; u32 bus_width; - if (!np) - return NULL; - - plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL); - if (!plat) { - dev_err(&pdev->dev, "Can't allocate platform data\n"); - return NULL; - } - - plat->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); - plat->wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); - plat->power_gpio = of_get_named_gpio(np, "power-gpios", 0); + tegra_host->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); + tegra_host->wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); + tegra_host->power_gpio = of_get_named_gpio(np, "power-gpios", 0); if (of_property_read_u32(np, "bus-width", &bus_width) == 0 && bus_width == 8) - plat->is_8bit = 1; - - return plat; + tegra_host->is_8bit = 1; } static int sdhci_tegra_probe(struct platform_device *pdev) @@ -250,7 +237,6 @@ static int sdhci_tegra_probe(struct platform_device *pdev) 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_tegra *tegra_host; struct clk *clk; int rc; @@ -263,52 +249,40 @@ static int sdhci_tegra_probe(struct platform_device *pdev) host = sdhci_pltfm_init(pdev, soc_data->pdata); if (IS_ERR(host)) return PTR_ERR(host); - pltfm_host = sdhci_priv(host); - plat = pdev->dev.platform_data; - - if (plat == NULL) - plat = sdhci_tegra_dt_parse_pdata(pdev); - - if (plat == NULL) { - dev_err(mmc_dev(host->mmc), "missing platform data\n"); - rc = -ENXIO; - goto err_no_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; + goto err_alloc_tegra_host; } - - 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"); + sdhci_tegra_parse_dt(&pdev->dev, tegra_host); + + if (gpio_is_valid(tegra_host->power_gpio)) { + rc = gpio_request(tegra_host->power_gpio, "sdhci_power"); if (rc) { dev_err(mmc_dev(host->mmc), "failed to allocate power gpio\n"); goto err_power_req; } - gpio_direction_output(plat->power_gpio, 1); + gpio_direction_output(tegra_host->power_gpio, 1); } - if (gpio_is_valid(plat->cd_gpio)) { - rc = gpio_request(plat->cd_gpio, "sdhci_cd"); + if (gpio_is_valid(tegra_host->cd_gpio)) { + rc = gpio_request(tegra_host->cd_gpio, "sdhci_cd"); if (rc) { dev_err(mmc_dev(host->mmc), "failed to allocate cd gpio\n"); goto err_cd_req; } - gpio_direction_input(plat->cd_gpio); + gpio_direction_input(tegra_host->cd_gpio); - rc = request_irq(gpio_to_irq(plat->cd_gpio), carddetect_irq, + rc = request_irq(gpio_to_irq(tegra_host->cd_gpio), + carddetect_irq, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, mmc_hostname(host->mmc), host); @@ -319,14 +293,14 @@ static int sdhci_tegra_probe(struct platform_device *pdev) } - if (gpio_is_valid(plat->wp_gpio)) { - rc = gpio_request(plat->wp_gpio, "sdhci_wp"); + if (gpio_is_valid(tegra_host->wp_gpio)) { + rc = gpio_request(tegra_host->wp_gpio, "sdhci_wp"); if (rc) { dev_err(mmc_dev(host->mmc), "failed to allocate wp gpio\n"); goto err_wp_req; } - gpio_direction_input(plat->wp_gpio); + gpio_direction_input(tegra_host->wp_gpio); } clk = clk_get(mmc_dev(host->mmc), NULL); @@ -338,9 +312,7 @@ static int sdhci_tegra_probe(struct platform_device *pdev) clk_prepare_enable(clk); pltfm_host->clk = clk; - host->mmc->pm_caps = plat->pm_flags; - - if (plat->is_8bit) + if (tegra_host->is_8bit) host->mmc->caps |= MMC_CAP_8_BIT_DATA; rc = sdhci_add_host(host); @@ -353,19 +325,19 @@ err_add_host: clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); err_clk_get: - if (gpio_is_valid(plat->wp_gpio)) - gpio_free(plat->wp_gpio); + if (gpio_is_valid(tegra_host->wp_gpio)) + gpio_free(tegra_host->wp_gpio); err_wp_req: - if (gpio_is_valid(plat->cd_gpio)) - free_irq(gpio_to_irq(plat->cd_gpio), host); + if (gpio_is_valid(tegra_host->cd_gpio)) + free_irq(gpio_to_irq(tegra_host->cd_gpio), host); err_cd_irq_req: - if (gpio_is_valid(plat->cd_gpio)) - gpio_free(plat->cd_gpio); + if (gpio_is_valid(tegra_host->cd_gpio)) + gpio_free(tegra_host->cd_gpio); err_cd_req: - if (gpio_is_valid(plat->power_gpio)) - gpio_free(plat->power_gpio); + if (gpio_is_valid(tegra_host->power_gpio)) + gpio_free(tegra_host->power_gpio); err_power_req: -err_no_plat: +err_alloc_tegra_host: sdhci_pltfm_free(pdev); return rc; } @@ -375,21 +347,20 @@ static int 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 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); - if (gpio_is_valid(plat->wp_gpio)) - gpio_free(plat->wp_gpio); + if (gpio_is_valid(tegra_host->wp_gpio)) + gpio_free(tegra_host->wp_gpio); - if (gpio_is_valid(plat->cd_gpio)) { - free_irq(gpio_to_irq(plat->cd_gpio), host); - gpio_free(plat->cd_gpio); + if (gpio_is_valid(tegra_host->cd_gpio)) { + free_irq(gpio_to_irq(tegra_host->cd_gpio), host); + gpio_free(tegra_host->cd_gpio); } - if (gpio_is_valid(plat->power_gpio)) - gpio_free(plat->power_gpio); + if (gpio_is_valid(tegra_host->power_gpio)) + gpio_free(tegra_host->power_gpio); clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); diff --git a/include/linux/platform_data/mmc-sdhci-tegra.h b/include/linux/platform_data/mmc-sdhci-tegra.h deleted file mode 100644 index 8f8430697686..000000000000 --- a/include/linux/platform_data/mmc-sdhci-tegra.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2009 Palm, Inc. - * Author: Yvonne Yip - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ -#ifndef __PLATFORM_DATA_TEGRA_SDHCI_H -#define __PLATFORM_DATA_TEGRA_SDHCI_H - -#include - -struct tegra_sdhci_platform_data { - int cd_gpio; - int wp_gpio; - int power_gpio; - int is_8bit; - int pm_flags; -}; - -#endif -- cgit v1.2.3