From 4e262d7f6fd43d798175cf26223c5bd3ebfd7b13 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 23 Oct 2013 14:51:31 +0200 Subject: mmc: sh_mobile_sdhi: Use modern PM macros to define pm callbacks Signed-off-by: Ulf Hansson Acked-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 2d6ce257a273..03fe7518bba4 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -316,10 +316,10 @@ static int sh_mobile_sdhi_remove(struct platform_device *pdev) } static const struct dev_pm_ops tmio_mmc_dev_pm_ops = { - .suspend = tmio_mmc_host_suspend, - .resume = tmio_mmc_host_resume, - .runtime_suspend = tmio_mmc_host_runtime_suspend, - .runtime_resume = tmio_mmc_host_runtime_resume, + SET_SYSTEM_SLEEP_PM_OPS(tmio_mmc_host_suspend, tmio_mmc_host_resume) + SET_RUNTIME_PM_OPS(tmio_mmc_host_runtime_suspend, + tmio_mmc_host_runtime_resume, + NULL) }; static struct platform_driver sh_mobile_sdhi_driver = { -- cgit v1.2.3 From c8964481d0273ef77a37ed2c627482fde3a1222c Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 23 Oct 2013 14:57:50 +0200 Subject: mmc: tmio_mmc: Convert from legacy to modern PM ops Signed-off-by: Ulf Hansson Acked-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/tmio_mmc.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc.c b/drivers/mmc/host/tmio_mmc.c index 1900abb04236..cfad844730d8 100644 --- a/drivers/mmc/host/tmio_mmc.c +++ b/drivers/mmc/host/tmio_mmc.c @@ -23,38 +23,37 @@ #include "tmio_mmc.h" -#ifdef CONFIG_PM -static int tmio_mmc_suspend(struct platform_device *dev, pm_message_t state) +#ifdef CONFIG_PM_SLEEP +static int tmio_mmc_suspend(struct device *dev) { - const struct mfd_cell *cell = mfd_get_cell(dev); + struct platform_device *pdev = to_platform_device(dev); + const struct mfd_cell *cell = mfd_get_cell(pdev); int ret; - ret = tmio_mmc_host_suspend(&dev->dev); + ret = tmio_mmc_host_suspend(dev); /* Tell MFD core it can disable us now.*/ if (!ret && cell->disable) - cell->disable(dev); + cell->disable(pdev); return ret; } -static int tmio_mmc_resume(struct platform_device *dev) +static int tmio_mmc_resume(struct device *dev) { - const struct mfd_cell *cell = mfd_get_cell(dev); + struct platform_device *pdev = to_platform_device(dev); + const struct mfd_cell *cell = mfd_get_cell(pdev); int ret = 0; /* Tell the MFD core we are ready to be enabled */ if (cell->resume) - ret = cell->resume(dev); + ret = cell->resume(pdev); if (!ret) - ret = tmio_mmc_host_resume(&dev->dev); + ret = tmio_mmc_host_resume(dev); return ret; } -#else -#define tmio_mmc_suspend NULL -#define tmio_mmc_resume NULL #endif static int tmio_mmc_probe(struct platform_device *pdev) @@ -134,15 +133,18 @@ static int tmio_mmc_remove(struct platform_device *pdev) /* ------------------- device registration ----------------------- */ +static const struct dev_pm_ops tmio_mmc_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tmio_mmc_suspend, tmio_mmc_resume) +}; + static struct platform_driver tmio_mmc_driver = { .driver = { .name = "tmio-mmc", .owner = THIS_MODULE, + .pm = &tmio_mmc_dev_pm_ops, }, .probe = tmio_mmc_probe, .remove = tmio_mmc_remove, - .suspend = tmio_mmc_suspend, - .resume = tmio_mmc_resume, }; module_platform_driver(tmio_mmc_driver); -- cgit v1.2.3 From 710dec95d579bf59c157358cc9cf7b42907d1c0f Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 23 Oct 2013 14:55:07 +0200 Subject: mmc: tmio: Adapt to proper PM configs for exported functions Since the users of the exported PM functions are now using the modern PM ops macros, we can convert to the proper corresponding PM configs. Signed-off-by: Ulf Hansson Acked-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/tmio_mmc.h | 7 +++---- drivers/mmc/host/tmio_mmc_pio.c | 7 ++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index aaa9c7e9e730..100ffe0b2faf 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -162,16 +162,15 @@ static inline void tmio_mmc_abort_dma(struct tmio_mmc_host *host) } #endif -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP int tmio_mmc_host_suspend(struct device *dev); int tmio_mmc_host_resume(struct device *dev); -#else -#define tmio_mmc_host_suspend NULL -#define tmio_mmc_host_resume NULL #endif +#ifdef CONFIG_PM_RUNTIME int tmio_mmc_host_runtime_suspend(struct device *dev); int tmio_mmc_host_runtime_resume(struct device *dev); +#endif static inline u16 sd_ctrl_read16(struct tmio_mmc_host *host, int addr) { diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index 8d8abf23a611..faf0924e71cb 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -1142,7 +1142,7 @@ void tmio_mmc_host_remove(struct tmio_mmc_host *host) } EXPORT_SYMBOL(tmio_mmc_host_remove); -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP int tmio_mmc_host_suspend(struct device *dev) { struct mmc_host *mmc = dev_get_drvdata(dev); @@ -1165,9 +1165,9 @@ int tmio_mmc_host_resume(struct device *dev) return 0; } EXPORT_SYMBOL(tmio_mmc_host_resume); +#endif -#endif /* CONFIG_PM */ - +#ifdef CONFIG_PM_RUNTIME int tmio_mmc_host_runtime_suspend(struct device *dev) { return 0; @@ -1184,5 +1184,6 @@ int tmio_mmc_host_runtime_resume(struct device *dev) return 0; } EXPORT_SYMBOL(tmio_mmc_host_runtime_resume); +#endif MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 2501c9179dff2add6aadd3898cd729e94e777d3a Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 30 Oct 2013 01:00:18 +0100 Subject: mmc: core: Use MMC_UNSAFE_RESUME as default behavior Invoking system suspend or shutdown without using the Kconfig option MMC_UNSAFE_RESUME, did trigger an ungraceful power cut of the card. To improve the situation, change the behavior to always make use of the available bus_ops callbacks that handles system suspend and shutdown properly. By changing the behavior MMC_UNSAFE_RESUME becomes redundant, so lets's remove it. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/Kconfig | 15 --------------- drivers/mmc/core/core.c | 17 ----------------- drivers/mmc/core/mmc.c | 23 +---------------------- drivers/mmc/core/sd.c | 23 +---------------------- include/linux/mmc/host.h | 5 +---- 5 files changed, 3 insertions(+), 80 deletions(-) diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index 269d072ef55e..9ebee72d9c3f 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -2,21 +2,6 @@ # MMC core configuration # -config MMC_UNSAFE_RESUME - bool "Assume MMC/SD cards are non-removable (DANGEROUS)" - help - If you say Y here, the MMC layer will assume that all cards - stayed in their respective slots during the suspend. The - normal behaviour is to remove them at suspend and - redetecting them at resume. Breaking this assumption will - in most cases result in data corruption. - - This option is usually just for embedded systems which use - a MMC/SD card for rootfs. Most people should say N here. - - This option sets a default which can be overridden by the - module parameter "removable=0" or "removable=1". - config MMC_CLKGATE bool "MMC host clock gating" help diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 098374b1ab2b..10856ec64412 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -64,23 +64,6 @@ static const unsigned freqs[] = { 400000, 300000, 200000, 100000 }; bool use_spi_crc = 1; module_param(use_spi_crc, bool, 0); -/* - * We normally treat cards as removed during suspend if they are not - * known to be on a non-removable bus, to avoid the risk of writing - * back data to a different card after resume. Allow this to be - * overridden if necessary. - */ -#ifdef CONFIG_MMC_UNSAFE_RESUME -bool mmc_assume_removable; -#else -bool mmc_assume_removable = 1; -#endif -EXPORT_SYMBOL(mmc_assume_removable); -module_param_named(removable, mmc_assume_removable, bool, 0644); -MODULE_PARM_DESC( - removable, - "MMC/SD cards are removable and may be removed during suspend"); - /* * Internal function. Schedule delayed work in the MMC work queue. */ diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 98e9eb0f6643..20f046895228 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1634,16 +1634,6 @@ static int mmc_power_restore(struct mmc_host *host) } static const struct mmc_bus_ops mmc_ops = { - .remove = mmc_remove, - .detect = mmc_detect, - .suspend = NULL, - .resume = NULL, - .power_restore = mmc_power_restore, - .alive = mmc_alive, - .shutdown = mmc_shutdown, -}; - -static const struct mmc_bus_ops mmc_ops_unsafe = { .remove = mmc_remove, .detect = mmc_detect, .suspend = mmc_suspend, @@ -1655,17 +1645,6 @@ static const struct mmc_bus_ops mmc_ops_unsafe = { .shutdown = mmc_shutdown, }; -static void mmc_attach_bus_ops(struct mmc_host *host) -{ - const struct mmc_bus_ops *bus_ops; - - if (!mmc_card_is_removable(host)) - bus_ops = &mmc_ops_unsafe; - else - bus_ops = &mmc_ops; - mmc_attach_bus(host, bus_ops); -} - /* * Starting point for MMC card init. */ @@ -1685,7 +1664,7 @@ int mmc_attach_mmc(struct mmc_host *host) if (err) return err; - mmc_attach_bus_ops(host); + mmc_attach_bus(host, &mmc_ops); if (host->ocr_avail_mmc) host->ocr_avail = host->ocr_avail_mmc; diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 692fdb177294..2dd359d2242f 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1207,16 +1207,6 @@ static int mmc_sd_power_restore(struct mmc_host *host) } static const struct mmc_bus_ops mmc_sd_ops = { - .remove = mmc_sd_remove, - .detect = mmc_sd_detect, - .suspend = NULL, - .resume = NULL, - .power_restore = mmc_sd_power_restore, - .alive = mmc_sd_alive, - .shutdown = mmc_sd_suspend, -}; - -static const struct mmc_bus_ops mmc_sd_ops_unsafe = { .remove = mmc_sd_remove, .detect = mmc_sd_detect, .runtime_suspend = mmc_sd_runtime_suspend, @@ -1228,17 +1218,6 @@ static const struct mmc_bus_ops mmc_sd_ops_unsafe = { .shutdown = mmc_sd_suspend, }; -static void mmc_sd_attach_bus_ops(struct mmc_host *host) -{ - const struct mmc_bus_ops *bus_ops; - - if (!mmc_card_is_removable(host)) - bus_ops = &mmc_sd_ops_unsafe; - else - bus_ops = &mmc_sd_ops; - mmc_attach_bus(host, bus_ops); -} - /* * Starting point for SD card init. */ @@ -1254,7 +1233,7 @@ int mmc_attach_sd(struct mmc_host *host) if (err) return err; - mmc_sd_attach_bus_ops(host); + mmc_attach_bus(host, &mmc_sd_ops); if (host->ocr_avail_sd) host->ocr_avail = host->ocr_avail_sd; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 99f5709ac343..2a139b2bdb9e 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -424,12 +424,9 @@ static inline int mmc_regulator_get_supply(struct mmc_host *mmc) int mmc_pm_notify(struct notifier_block *notify_block, unsigned long, void *); -/* Module parameter */ -extern bool mmc_assume_removable; - static inline int mmc_card_is_removable(struct mmc_host *host) { - return !(host->caps & MMC_CAP_NONREMOVABLE) && mmc_assume_removable; + return !(host->caps & MMC_CAP_NONREMOVABLE); } static inline int mmc_card_keep_power(struct mmc_host *host) -- cgit v1.2.3 From 5601aaf73e5c2f0aa5e3607fee7b7d3511edfea9 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 30 Oct 2013 23:15:30 +0100 Subject: mmc: core: Remove unnecessary validations for bus_ops callbacks Due to the removal of the Kconfig option MMC_UNSAFE_RESUME, several validations of a present bus_ops callback became redundant. Let's remove these. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/bus.c | 12 ++---------- drivers/mmc/core/core.c | 13 +++++-------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 64145a32b917..824644875d41 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -185,24 +185,16 @@ static int mmc_runtime_suspend(struct device *dev) { struct mmc_card *card = mmc_dev_to_card(dev); struct mmc_host *host = card->host; - int ret = 0; - if (host->bus_ops->runtime_suspend) - ret = host->bus_ops->runtime_suspend(host); - - return ret; + return host->bus_ops->runtime_suspend(host); } static int mmc_runtime_resume(struct device *dev) { struct mmc_card *card = mmc_dev_to_card(dev); struct mmc_host *host = card->host; - int ret = 0; - if (host->bus_ops->runtime_resume) - ret = host->bus_ops->runtime_resume(host); - - return ret; + return host->bus_ops->runtime_resume(host); } static int mmc_runtime_idle(struct device *dev) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 10856ec64412..22427c684b36 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2231,9 +2231,6 @@ static int mmc_do_hw_reset(struct mmc_host *host, int check) { struct mmc_card *card = host->card; - if (!host->bus_ops->power_restore) - return -EOPNOTSUPP; - if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) return -EOPNOTSUPP; @@ -2335,7 +2332,7 @@ int _mmc_detect_card_removed(struct mmc_host *host) { int ret; - if ((host->caps & MMC_CAP_NONREMOVABLE) || !host->bus_ops->alive) + if (host->caps & MMC_CAP_NONREMOVABLE) return 0; if (!host->card || mmc_card_removed(host->card)) @@ -2418,7 +2415,7 @@ void mmc_rescan(struct work_struct *work) * if there is a _removable_ card registered, check whether it is * still present */ - if (host->bus_ops && host->bus_ops->detect && !host->bus_dead + if (host->bus_ops && !host->bus_dead && !(host->caps & MMC_CAP_NONREMOVABLE)) host->bus_ops->detect(host); @@ -2520,7 +2517,7 @@ int mmc_power_save_host(struct mmc_host *host) mmc_bus_get(host); - if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) { + if (!host->bus_ops || host->bus_dead) { mmc_bus_put(host); return -EINVAL; } @@ -2546,7 +2543,7 @@ int mmc_power_restore_host(struct mmc_host *host) mmc_bus_get(host); - if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) { + if (!host->bus_ops || host->bus_dead) { mmc_bus_put(host); return -EINVAL; } @@ -2651,7 +2648,7 @@ int mmc_pm_notify(struct notifier_block *notify_block, /* Validate prerequisites for suspend */ if (host->bus_ops->pre_suspend) err = host->bus_ops->pre_suspend(host); - if (!err && host->bus_ops->suspend) + if (!err) break; /* Calling bus_ops->remove() with a claimed host can deadlock */ -- cgit v1.2.3 From a2d1086de6cc3ae2378d9db8b92712911c9e5fef Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 16 Dec 2013 14:37:26 +0100 Subject: mmc: card: Remove host cap MMC_CAP2_SANITIZE There is no need for keeping a host cap for MMC_CAP2_SANITIZE, instead we just make the feature default available. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 3 +-- include/linux/mmc/host.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 7b5424f398ac..c2187d5e9070 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -415,8 +415,7 @@ static int ioctl_do_sanitize(struct mmc_card *card) { int err; - if (!(mmc_can_sanitize(card) && - (card->host->caps2 & MMC_CAP2_SANITIZE))) { + if (!mmc_can_sanitize(card)) { pr_warn("%s: %s - SANITIZE is not supported\n", mmc_hostname(card->host), __func__); err = -EOPNOTSUPP; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 2a139b2bdb9e..40f832e5db40 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -281,7 +281,6 @@ struct mmc_host { #define MMC_CAP2_PACKED_CMD (MMC_CAP2_PACKED_RD | \ MMC_CAP2_PACKED_WR) #define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14) /* Don't power up before scan */ -#define MMC_CAP2_SANITIZE (1 << 15) /* Support Sanitize */ mmc_pm_flag_t pm_caps; /* supported pm features */ -- cgit v1.2.3 From 325e2f96b926ae852fa2aa5e64d1040ed9518ae1 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 16 Dec 2013 14:43:51 +0100 Subject: mmc: core: Remove unused host cap MMC_CAP2_BROKEN_VOLTAGE Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- include/linux/mmc/host.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 40f832e5db40..461c69f19425 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -272,7 +272,6 @@ struct mmc_host { #define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */ #define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \ MMC_CAP2_HS200_1_2V_SDR) -#define MMC_CAP2_BROKEN_VOLTAGE (1 << 7) /* Use the broken voltage */ #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 */ -- cgit v1.2.3 From 469a00b017a9b2c630bff962ffd64ba626977830 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 16 Dec 2013 14:46:00 +0100 Subject: mmc: core: Remove support for MMC_CAP2_NO_SLEEP_CMD There are no active users of this host capability. The primary reason for adding this cap was due to a bug in ux500 boot loader code, which is not a relevant issue any more. So, let's remove it. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/mmc.c | 3 --- include/linux/mmc/host.h | 1 - 2 files changed, 4 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 20f046895228..5c4ee6ab2125 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1358,9 +1358,6 @@ static int mmc_sleep(struct mmc_host *host) struct mmc_card *card = host->card; int err; - if (host->caps2 & MMC_CAP2_NO_SLEEP_CMD) - return 0; - err = mmc_deselect_cards(host); if (err) return err; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 461c69f19425..4c0176a8e474 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -267,7 +267,6 @@ struct mmc_host { #define MMC_CAP2_CACHE_CTRL (1 << 1) /* Allow cache control */ #define MMC_CAP2_FULL_PWR_CYCLE (1 << 2) /* Can do full power cycle */ #define MMC_CAP2_NO_MULTI_READ (1 << 3) /* Multiblock reads don't work */ -#define MMC_CAP2_NO_SLEEP_CMD (1 << 4) /* Don't allow sleep command */ #define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */ #define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */ #define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \ -- cgit v1.2.3 From 10e5d9652499a8bc0a99ffc2a96a3030fee576cb Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 16 Dec 2013 16:23:22 +0100 Subject: mmc: core: Use mmc_flush_cache() during mmc suspend Earlier we disabled the cache during suspend, which meant a flush was internally at the eMMC performed as well. To simplify code we can make use of the mmc_flush_cache(), during mmc suspend, which makes the mmc_cache_ctrl() redundant so then we can remove it. Signed-off-by: Ulf Hansson Acked-by: Seungwon Jeon Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 38 -------------------------------------- drivers/mmc/core/mmc.c | 2 +- include/linux/mmc/host.h | 2 -- 3 files changed, 1 insertion(+), 41 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 22427c684b36..8928f9f4cfe1 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2582,44 +2582,6 @@ int mmc_flush_cache(struct mmc_card *card) } 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) -{ - struct mmc_card *card = host->card; - unsigned int timeout; - int err = 0; - - if (!(host->caps2 & MMC_CAP2_CACHE_CTRL) || - mmc_card_is_removable(host)) - return err; - - if (card && mmc_card_mmc(card) && - (card->ext_csd.cache_size > 0)) { - enable = !!enable; - - if (card->ext_csd.cache_ctrl ^ enable) { - timeout = enable ? card->ext_csd.generic_cmd6_time : 0; - err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_CACHE_CTRL, enable, timeout); - if (err) - pr_err("%s: cache %s error %d\n", - mmc_hostname(card->host), - enable ? "on" : "off", - err); - else - card->ext_csd.cache_ctrl = enable; - } - } - - return err; -} -EXPORT_SYMBOL(mmc_cache_ctrl); - #ifdef CONFIG_PM /* Do the card removal on suspend if card is assumed removeable diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 5c4ee6ab2125..6d446e217f36 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1481,7 +1481,7 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) goto out; } - err = mmc_cache_ctrl(host, 0); + err = mmc_flush_cache(host->card); if (err) goto out; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 4c0176a8e474..f69bd70b1046 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -385,8 +385,6 @@ int mmc_power_restore_host(struct mmc_host *host); void mmc_detect_change(struct mmc_host *, unsigned long delay); void mmc_request_done(struct mmc_host *, struct mmc_request *); -int mmc_cache_ctrl(struct mmc_host *, u8); - static inline void mmc_signal_sdio_irq(struct mmc_host *host) { host->ops->enable_sdio_irq(host, 0); -- cgit v1.2.3 From 7536d3f83aa42ba1a3b1c6b30c2b6d94a820cbb2 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 18 Dec 2013 11:59:17 +0100 Subject: mmc: core: Enable MMC_CAP2_CACHE_CTRL as default There are no reason to why the use of a non-volatile internal eMMC cache should be controlled by a host cap. Instead let's just enable it if the eMMC card supports it. Signed-off-by: Ulf Hansson Acked-by: Seungwon Jeon Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 4 ---- drivers/mmc/core/mmc.c | 3 +-- include/linux/mmc/host.h | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 8928f9f4cfe1..f5a068d55c36 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2562,12 +2562,8 @@ EXPORT_SYMBOL(mmc_power_restore_host); */ int mmc_flush_cache(struct mmc_card *card) { - struct mmc_host *host = card->host; int err = 0; - if (!(host->caps2 & MMC_CAP2_CACHE_CTRL)) - return err; - if (mmc_card_mmc(card) && (card->ext_csd.cache_size > 0) && (card->ext_csd.cache_ctrl & 1)) { diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 6d446e217f36..072171183d5b 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1287,8 +1287,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * If cache size is higher than 0, this indicates * the existence of cache and it can be turned on. */ - if ((host->caps2 & MMC_CAP2_CACHE_CTRL) && - card->ext_csd.cache_size > 0) { + if (card->ext_csd.cache_size > 0) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_CACHE_CTRL, 1, card->ext_csd.generic_cmd6_time); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index f69bd70b1046..719db89ef134 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -264,7 +264,6 @@ struct mmc_host { u32 caps2; /* More host capabilities */ #define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */ -#define MMC_CAP2_CACHE_CTRL (1 << 1) /* Allow cache control */ #define MMC_CAP2_FULL_PWR_CYCLE (1 << 2) /* Can do full power cycle */ #define MMC_CAP2_NO_MULTI_READ (1 << 3) /* Multiblock reads don't work */ #define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */ -- cgit v1.2.3 From 2772ef30eacd72fef013ac2fade2f384342ba6c7 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 20 Feb 2014 16:54:44 -0800 Subject: mmc: sdhi: tidyup sh_mobile_sdhi_of_match position It is easier to read if sh_mobile_sdhi_of_cfg and sh_mobile_sdhi_of_match are closer. Signed-off-by: Kuninori Morimoto Acked-by: Simon Horman Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 03fe7518bba4..a7a3f689a309 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -45,6 +45,19 @@ static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = { }, }; +static const struct of_device_id sh_mobile_sdhi_of_match[] = { + { .compatible = "renesas,sdhi-shmobile" }, + { .compatible = "renesas,sdhi-sh7372" }, + { .compatible = "renesas,sdhi-sh73a0", .data = &sh_mobile_sdhi_of_cfg[0], }, + { .compatible = "renesas,sdhi-r8a73a4", .data = &sh_mobile_sdhi_of_cfg[0], }, + { .compatible = "renesas,sdhi-r8a7740", .data = &sh_mobile_sdhi_of_cfg[0], }, + { .compatible = "renesas,sdhi-r8a7778", .data = &sh_mobile_sdhi_of_cfg[0], }, + { .compatible = "renesas,sdhi-r8a7779", .data = &sh_mobile_sdhi_of_cfg[0], }, + { .compatible = "renesas,sdhi-r8a7790", .data = &sh_mobile_sdhi_of_cfg[0], }, + {}, +}; +MODULE_DEVICE_TABLE(of, sh_mobile_sdhi_of_match); + struct sh_mobile_sdhi { struct clk *clk; struct tmio_mmc_data mmc_data; @@ -114,19 +127,6 @@ 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,sdhi-shmobile" }, - { .compatible = "renesas,sdhi-sh7372" }, - { .compatible = "renesas,sdhi-sh73a0", .data = &sh_mobile_sdhi_of_cfg[0], }, - { .compatible = "renesas,sdhi-r8a73a4", .data = &sh_mobile_sdhi_of_cfg[0], }, - { .compatible = "renesas,sdhi-r8a7740", .data = &sh_mobile_sdhi_of_cfg[0], }, - { .compatible = "renesas,sdhi-r8a7778", .data = &sh_mobile_sdhi_of_cfg[0], }, - { .compatible = "renesas,sdhi-r8a7779", .data = &sh_mobile_sdhi_of_cfg[0], }, - { .compatible = "renesas,sdhi-r8a7790", .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 = -- cgit v1.2.3 From b3a5d4ce65162d27a495b8fa3ac21dcdf58738b1 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 20 Feb 2014 16:54:58 -0800 Subject: mmc: sdhi: update sh_mobile_sdhi_of_data for r8a7778 This patch updates r8a7778 DT data to have SoC specific settings. Signed-off-by: Kuninori Morimoto Acked-by: Simon Horman Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index a7a3f689a309..e956bc4e28c1 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -37,6 +37,7 @@ struct sh_mobile_sdhi_of_data { unsigned long tmio_flags; + unsigned long capabilities; }; static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = { @@ -45,13 +46,18 @@ static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = { }, }; +static const struct sh_mobile_sdhi_of_data of_rcar_gen1_compatible = { + .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE, + .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ, +}; + static const struct of_device_id sh_mobile_sdhi_of_match[] = { { .compatible = "renesas,sdhi-shmobile" }, { .compatible = "renesas,sdhi-sh7372" }, { .compatible = "renesas,sdhi-sh73a0", .data = &sh_mobile_sdhi_of_cfg[0], }, { .compatible = "renesas,sdhi-r8a73a4", .data = &sh_mobile_sdhi_of_cfg[0], }, { .compatible = "renesas,sdhi-r8a7740", .data = &sh_mobile_sdhi_of_cfg[0], }, - { .compatible = "renesas,sdhi-r8a7778", .data = &sh_mobile_sdhi_of_cfg[0], }, + { .compatible = "renesas,sdhi-r8a7778", .data = &of_rcar_gen1_compatible, }, { .compatible = "renesas,sdhi-r8a7779", .data = &sh_mobile_sdhi_of_cfg[0], }, { .compatible = "renesas,sdhi-r8a7790", .data = &sh_mobile_sdhi_of_cfg[0], }, {}, @@ -212,6 +218,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) 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; + mmc_data->capabilities |= of_data->capabilities; } /* SD control register space size is 0x100, 0x200 for bus_shift=1 */ -- cgit v1.2.3 From 81bbbc7278fa109fb691446a521dcfc18b87e57d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 20 Feb 2014 16:55:12 -0800 Subject: mmc: sdhi: update sh_mobile_sdhi_of_data for r8a7779 This patch updates r8a7779 DT data to have SoC specific settings. Signed-off-by: Kuninori Morimoto Acked-by: Simon Horman Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index e956bc4e28c1..38acf2665bdf 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -58,7 +58,7 @@ static const struct of_device_id sh_mobile_sdhi_of_match[] = { { .compatible = "renesas,sdhi-r8a73a4", .data = &sh_mobile_sdhi_of_cfg[0], }, { .compatible = "renesas,sdhi-r8a7740", .data = &sh_mobile_sdhi_of_cfg[0], }, { .compatible = "renesas,sdhi-r8a7778", .data = &of_rcar_gen1_compatible, }, - { .compatible = "renesas,sdhi-r8a7779", .data = &sh_mobile_sdhi_of_cfg[0], }, + { .compatible = "renesas,sdhi-r8a7779", .data = &of_rcar_gen1_compatible, }, { .compatible = "renesas,sdhi-r8a7790", .data = &sh_mobile_sdhi_of_cfg[0], }, {}, }; -- cgit v1.2.3 From 423f6c2e977de73b8723e73d8cc585de40893f5b Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 20 Feb 2014 16:55:27 -0800 Subject: mmc: sdhi: update sh_mobile_sdhi_of_data for r8a7790 This patch updates r8a7790 DT data to have SoC specific settings. Signed-off-by: Kuninori Morimoto Acked-by: Simon Horman Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 38acf2665bdf..cfc37ec26458 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -38,6 +38,7 @@ struct sh_mobile_sdhi_of_data { unsigned long tmio_flags; unsigned long capabilities; + unsigned long capabilities2; }; static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = { @@ -51,6 +52,12 @@ static const struct sh_mobile_sdhi_of_data of_rcar_gen1_compatible = { .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ, }; +static const struct sh_mobile_sdhi_of_data of_rcar_gen2_compatible = { + .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE, + .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ, + .capabilities2 = MMC_CAP2_NO_MULTI_READ, +}; + static const struct of_device_id sh_mobile_sdhi_of_match[] = { { .compatible = "renesas,sdhi-shmobile" }, { .compatible = "renesas,sdhi-sh7372" }, @@ -59,7 +66,7 @@ static const struct of_device_id sh_mobile_sdhi_of_match[] = { { .compatible = "renesas,sdhi-r8a7740", .data = &sh_mobile_sdhi_of_cfg[0], }, { .compatible = "renesas,sdhi-r8a7778", .data = &of_rcar_gen1_compatible, }, { .compatible = "renesas,sdhi-r8a7779", .data = &of_rcar_gen1_compatible, }, - { .compatible = "renesas,sdhi-r8a7790", .data = &sh_mobile_sdhi_of_cfg[0], }, + { .compatible = "renesas,sdhi-r8a7790", .data = &of_rcar_gen2_compatible, }, {}, }; MODULE_DEVICE_TABLE(of, sh_mobile_sdhi_of_match); @@ -219,6 +226,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) const struct sh_mobile_sdhi_of_data *of_data = of_id->data; mmc_data->flags |= of_data->tmio_flags; mmc_data->capabilities |= of_data->capabilities; + mmc_data->capabilities2 |= of_data->capabilities2; } /* SD control register space size is 0x100, 0x200 for bus_shift=1 */ -- cgit v1.2.3 From 81918d25a7ae2a7eada4237d00b8d6dbecffda6c Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 20 Feb 2014 16:55:40 -0800 Subject: mmc: sdhi: update sh_mobile_sdhi_of_data for r8a7791 This patch adds DT support for r8a7791. Signed-off-by: Kuninori Morimoto Acked-by: Simon Horman Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index cfc37ec26458..91058dabd11a 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -67,6 +67,7 @@ static const struct of_device_id sh_mobile_sdhi_of_match[] = { { .compatible = "renesas,sdhi-r8a7778", .data = &of_rcar_gen1_compatible, }, { .compatible = "renesas,sdhi-r8a7779", .data = &of_rcar_gen1_compatible, }, { .compatible = "renesas,sdhi-r8a7790", .data = &of_rcar_gen2_compatible, }, + { .compatible = "renesas,sdhi-r8a7791", .data = &of_rcar_gen2_compatible, }, {}, }; MODULE_DEVICE_TABLE(of, sh_mobile_sdhi_of_match); -- cgit v1.2.3 From 0e5c93e0200e9759561377d51d5478134f50f7ee Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Sat, 22 Feb 2014 18:01:37 +0200 Subject: mmc: omap: Fix NULL pointer dereference due uninitialized cover_tasklet Omap MMC driver initialization can cause a NULL pointer dereference in tasklet_hi_action on Nokia N810 if its miniSD cover is open during driver initialization. [ 1.070000] Unable to handle kernel NULL pointer dereference at virtual address 00000000 [ 1.080000] pgd = c0004000 [ 1.080000] [00000000] *pgd=00000000 [ 1.080000] Internal error: Oops: 80000005 [#1] PREEMPT ARM [ 1.080000] Modules linked in: [ 1.080000] CPU: 0 PID: 24 Comm: kworker/0:1 Not tainted 3.13.0-rc2+ #95 [ 1.080000] Workqueue: events menelaus_work [ 1.080000] task: c7863340 ti: c7878000 task.ti: c7878000 [ 1.080000] PC is at 0x0 [ 1.080000] LR is at tasklet_hi_action+0x68/0xa4 ... [ 1.080000] [] (tasklet_hi_action+0x68/0xa4) from [] (__do_softirq+0xbc/0x208) [ 1.080000] [] (__do_softirq+0xbc/0x208) from [] (irq_exit+0x84/0xac) [ 1.080000] [] (irq_exit+0x84/0xac) from [] (handle_IRQ+0x64/0x84) [ 1.080000] [] (handle_IRQ+0x64/0x84) from [] (omap2_intc_handle_irq+0x54/0x68) [ 1.080000] [] (omap2_intc_handle_irq+0x54/0x68) from [] (__irq_svc+0x40/0x74) [ 1.080000] Exception stack(0xc7879d70 to 0xc7879db8) [ 1.080000] 9d60: 000003f1 0000000a 00000009 0000001c [ 1.080000] 9d80: c7879e70 c780bc10 c780bc10 00000000 00000001 00008603 c780bc78 c7879e4e [ 1.080000] 9da0: 00000002 c7879db8 c00343b0 c0160c9c 20000113 ffffffff [ 1.080000] [] (__irq_svc+0x40/0x74) from [] (__aeabi_uidiv+0x20/0x9c) [ 1.080000] [] (__aeabi_uidiv+0x20/0x9c) from [] (msecs_to_jiffies+0x18/0x24) [ 1.080000] [] (msecs_to_jiffies+0x18/0x24) from [] (omap_i2c_xfer+0x30c/0x458) [ 1.080000] [] (omap_i2c_xfer+0x30c/0x458) from [] (__i2c_transfer+0x3c/0x74) [ 1.080000] [] (__i2c_transfer+0x3c/0x74) from [] (i2c_transfer+0x78/0x94) [ 1.080000] [] (i2c_transfer+0x78/0x94) from [] (i2c_smbus_xfer+0x3c0/0x4f8) [ 1.080000] [] (i2c_smbus_xfer+0x3c0/0x4f8) from [] (i2c_smbus_write_byte_data+0x34/0x3c) [ 1.080000] [] (i2c_smbus_write_byte_data+0x34/0x3c) from [] (menelaus_write_reg+0x1c/0x40) [ 1.080000] [] (menelaus_write_reg+0x1c/0x40) from [] (menelaus_work+0xa0/0xc4) [ 1.080000] [] (menelaus_work+0xa0/0xc4) from [] (process_one_work+0x1fc/0x334) [ 1.080000] [] (process_one_work+0x1fc/0x334) from [] (worker_thread+0x244/0x380) [ 1.080000] [] (worker_thread+0x244/0x380) from [] (kthread+0xc0/0xd4) [ 1.080000] [] (kthread+0xc0/0xd4) from [] (ret_from_fork+0x14/0x3c) [ 1.080000] Code: bad PC value [ 1.090000] ---[ end trace 7bc2fc7cd14f1d95 ]--- [ 1.100000] Kernel panic - not syncing: Fatal exception in interrupt Reason for this is that omap_notify_cover_event which calls tasklet_hi_schedule gets called before struct cover_tasklet is initialized. Call to omap_notify_cover_event on Nokia N810 happens from menelaus.c PMIC driver via board-n8x0.c during execution of mmc_add_host in case of open miniSD cover. Fix this by moving cover_timer and cover_tasklet initialization before mmc_add_host call in mmc_omap_new_slot. Signed-off-by: Jarkko Nikula Acked-by: Tony Lindgren Tested-by: Aaro Koskinen Signed-off-by: Chris Ball --- drivers/mmc/host/omap.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 98b6b6ef7e5c..42b665dfaa73 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1262,6 +1262,13 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id) mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; mmc->max_seg_size = mmc->max_req_size; + if (slot->pdata->get_cover_state != NULL) { + setup_timer(&slot->cover_timer, mmc_omap_cover_timer, + (unsigned long)slot); + tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler, + (unsigned long)slot); + } + r = mmc_add_host(mmc); if (r < 0) goto err_remove_host; @@ -1278,11 +1285,6 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id) &dev_attr_cover_switch); if (r < 0) goto err_remove_slot_name; - - setup_timer(&slot->cover_timer, mmc_omap_cover_timer, - (unsigned long)slot); - tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler, - (unsigned long)slot); tasklet_schedule(&slot->cover_tasklet); } -- cgit v1.2.3 From ae9b79c634b91d60fecd8663324434219b68b10f Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Sat, 22 Feb 2014 18:01:38 +0200 Subject: mmc: omap: Convert to devm_kzalloc Signed-off-by: Jarkko Nikula Acked-by: Tony Lindgren Tested-by: Aaro Koskinen Signed-off-by: Chris Ball --- drivers/mmc/host/omap.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 42b665dfaa73..927ed24d0708 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1345,7 +1345,8 @@ static int mmc_omap_probe(struct platform_device *pdev) if (res == NULL) return -EBUSY; - host = kzalloc(sizeof(struct mmc_omap_host), GFP_KERNEL); + host = devm_kzalloc(&pdev->dev, sizeof(struct mmc_omap_host), + GFP_KERNEL); if (host == NULL) { ret = -ENOMEM; goto err_free_mem_region; @@ -1465,7 +1466,6 @@ err_free_iclk: err_free_mmc_host: iounmap(host->virt_base); err_ioremap: - kfree(host); err_free_mem_region: release_mem_region(res->start, resource_size(res)); return ret; @@ -1500,8 +1500,6 @@ static int mmc_omap_remove(struct platform_device *pdev) pdev->resource[0].end - pdev->resource[0].start + 1); destroy_workqueue(host->mmc_omap_wq); - kfree(host); - return 0; } -- cgit v1.2.3 From 5b7d23aa5ddf2d3318370d369d287c9646156ca4 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Sat, 22 Feb 2014 18:01:39 +0200 Subject: mmc: omap: Remove duplicate host->irq assignment host-irq is set twice so remove needless one. Signed-off-by: Jarkko Nikula Acked-by: Tony Lindgren Tested-by: Aaro Koskinen Signed-off-by: Chris Ball --- drivers/mmc/host/omap.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 927ed24d0708..b438f0addb3c 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1375,7 +1375,6 @@ static int mmc_omap_probe(struct platform_device *pdev) host->mem_res = res; host->irq = irq; host->use_dma = 1; - host->irq = irq; host->phys_base = host->mem_res->start; host->virt_base = ioremap(res->start, resource_size(res)); if (!host->virt_base) -- cgit v1.2.3 From 2ca5dc6ffa3d9fefadfff0fee77e1ad52e2c0b72 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Sat, 22 Feb 2014 18:01:40 +0200 Subject: mmc: omap: Remove mem_res field from struct mmc_omap_host Field mem_res in struct mmc_omap_host is used only once in mmc_omap_probe when setting the phys_base field so we may just se the phys_base straight and remove needless mem_res. Signed-off-by: Jarkko Nikula Acked-by: Tony Lindgren Tested-by: Aaro Koskinen Signed-off-by: Chris Ball --- drivers/mmc/host/omap.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index b438f0addb3c..0d669cfa6af6 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -130,7 +130,6 @@ struct mmc_omap_host { u32 dma_rx_burst; struct dma_chan *dma_tx; u32 dma_tx_burst; - struct resource *mem_res; void __iomem *virt_base; unsigned int phys_base; int irq; @@ -1372,10 +1371,9 @@ static int mmc_omap_probe(struct platform_device *pdev) platform_set_drvdata(pdev, host); host->id = pdev->id; - host->mem_res = res; host->irq = irq; host->use_dma = 1; - host->phys_base = host->mem_res->start; + host->phys_base = res->start; host->virt_base = ioremap(res->start, resource_size(res)); if (!host->virt_base) goto err_ioremap; -- cgit v1.2.3 From 64ac16ec80bc5aacdc2549226f2c0d48d185123c Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Sat, 22 Feb 2014 18:01:41 +0200 Subject: mmc: omap: Convert to devm_ioremap_resource Simplify probe and cleanup code by using devm_ioremap_resource. This also makes probe code to follow more common allocate private struct followed by other initialization style. Signed-off-by: Jarkko Nikula Acked-by: Tony Lindgren Tested-by: Aaro Koskinen Signed-off-by: Chris Ball --- drivers/mmc/host/omap.c | 41 ++++++++++++----------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 0d669cfa6af6..e4c7a0380a63 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1334,22 +1334,19 @@ static int mmc_omap_probe(struct platform_device *pdev) return -EPROBE_DEFER; } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host = devm_kzalloc(&pdev->dev, sizeof(struct mmc_omap_host), + GFP_KERNEL); + if (host == NULL) + return -ENOMEM; + irq = platform_get_irq(pdev, 0); - if (res == NULL || irq < 0) + if (irq < 0) return -ENXIO; - res = request_mem_region(res->start, resource_size(res), - pdev->name); - if (res == NULL) - return -EBUSY; - - host = devm_kzalloc(&pdev->dev, sizeof(struct mmc_omap_host), - GFP_KERNEL); - if (host == NULL) { - ret = -ENOMEM; - goto err_free_mem_region; - } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->virt_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->virt_base)) + return PTR_ERR(host->virt_base); INIT_WORK(&host->slot_release_work, mmc_omap_slot_release_work); INIT_WORK(&host->send_stop_work, mmc_omap_send_stop_work); @@ -1374,15 +1371,9 @@ static int mmc_omap_probe(struct platform_device *pdev) host->irq = irq; host->use_dma = 1; host->phys_base = res->start; - host->virt_base = ioremap(res->start, resource_size(res)); - if (!host->virt_base) - goto err_ioremap; - host->iclk = clk_get(&pdev->dev, "ick"); - if (IS_ERR(host->iclk)) { - ret = PTR_ERR(host->iclk); - goto err_free_mmc_host; - } + if (IS_ERR(host->iclk)) + return PTR_ERR(host->iclk); clk_enable(host->iclk); host->fclk = clk_get(&pdev->dev, "fck"); @@ -1460,11 +1451,6 @@ err_free_dma: err_free_iclk: clk_disable(host->iclk); clk_put(host->iclk); -err_free_mmc_host: - iounmap(host->virt_base); -err_ioremap: -err_free_mem_region: - release_mem_region(res->start, resource_size(res)); return ret; } @@ -1492,9 +1478,6 @@ static int mmc_omap_remove(struct platform_device *pdev) if (host->dma_rx) dma_release_channel(host->dma_rx); - iounmap(host->virt_base); - release_mem_region(pdev->resource[0].start, - pdev->resource[0].end - pdev->resource[0].start + 1); destroy_workqueue(host->mmc_omap_wq); return 0; -- cgit v1.2.3 From a6c668fb95ac655fe94c08c1fb419f685040cf23 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Sat, 22 Feb 2014 18:01:42 +0200 Subject: mmc: omap: Remove always set use_dma flag from struct mmc_omap_host Because use_dma is set only in mmc_omap_probe and unset nowhere there is no need to carry that flag in struct mmc_omap_host for mmc_omap_prepare_data function. Signed-off-by: Jarkko Nikula Acked-by: Tony Lindgren Tested-by: Aaro Koskinen Signed-off-by: Chris Ball --- drivers/mmc/host/omap.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index e4c7a0380a63..42175cd90435 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -152,7 +152,6 @@ struct mmc_omap_host { u32 total_bytes_left; unsigned features; - unsigned use_dma:1; unsigned brs_received:1, dma_done:1; unsigned dma_in_use:1; spinlock_t dma_lock; @@ -944,7 +943,7 @@ static void mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) { struct mmc_data *data = req->data; - int i, use_dma, block_size; + int i, use_dma = 1, block_size; unsigned sg_len; host->data = data; @@ -969,13 +968,10 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) sg_len = (data->blocks == 1) ? 1 : data->sg_len; /* Only do DMA for entire blocks */ - use_dma = host->use_dma; - if (use_dma) { - for (i = 0; i < sg_len; i++) { - if ((data->sg[i].length % block_size) != 0) { - use_dma = 0; - break; - } + for (i = 0; i < sg_len; i++) { + if ((data->sg[i].length % block_size) != 0) { + use_dma = 0; + break; } } @@ -1369,7 +1365,6 @@ static int mmc_omap_probe(struct platform_device *pdev) host->id = pdev->id; host->irq = irq; - host->use_dma = 1; host->phys_base = res->start; host->iclk = clk_get(&pdev->dev, "ick"); if (IS_ERR(host->iclk)) -- cgit v1.2.3 From b13d1f0f9ad64bc498ced07344495ba27321419e Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Sat, 22 Feb 2014 18:01:43 +0200 Subject: mmc: omap: Add erase capability This patch adds the erase capability to OMAP1/OMAP2420 MMC driver. Idea is the same than in commit 93caf8e ("omap_hsmmc: add erase capability") that we disable the data timeout interrupt for erases. Signed-off-by: Jarkko Nikula Acked-by: Tony Lindgren Tested-by: Aaro Koskinen Signed-off-by: Chris Ball --- drivers/mmc/host/omap.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 42175cd90435..5c2e58b29305 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -336,6 +337,7 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) u32 cmdreg; u32 resptype; u32 cmdtype; + u16 irq_mask; host->cmd = cmd; @@ -388,12 +390,14 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) OMAP_MMC_WRITE(host, CTO, 200); OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff); OMAP_MMC_WRITE(host, ARGH, cmd->arg >> 16); - OMAP_MMC_WRITE(host, IE, - OMAP_MMC_STAT_A_EMPTY | OMAP_MMC_STAT_A_FULL | - OMAP_MMC_STAT_CMD_CRC | OMAP_MMC_STAT_CMD_TOUT | - OMAP_MMC_STAT_DATA_CRC | OMAP_MMC_STAT_DATA_TOUT | - OMAP_MMC_STAT_END_OF_CMD | OMAP_MMC_STAT_CARD_ERR | - OMAP_MMC_STAT_END_OF_DATA); + irq_mask = OMAP_MMC_STAT_A_EMPTY | OMAP_MMC_STAT_A_FULL | + OMAP_MMC_STAT_CMD_CRC | OMAP_MMC_STAT_CMD_TOUT | + OMAP_MMC_STAT_DATA_CRC | OMAP_MMC_STAT_DATA_TOUT | + OMAP_MMC_STAT_END_OF_CMD | OMAP_MMC_STAT_CARD_ERR | + OMAP_MMC_STAT_END_OF_DATA; + if (cmd->opcode == MMC_ERASE) + irq_mask &= ~OMAP_MMC_STAT_DATA_TOUT; + OMAP_MMC_WRITE(host, IE, irq_mask); OMAP_MMC_WRITE(host, CMD, cmdreg); } @@ -1234,7 +1238,7 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id) mmc->caps = 0; if (host->pdata->slots[id].wires >= 4) - mmc->caps |= MMC_CAP_4_BIT_DATA; + mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_ERASE; mmc->ops = &mmc_omap_ops; mmc->f_min = 400000; -- cgit v1.2.3 From 9107ebbf9652c033eb5dd10a6ea34a132db3cde1 Mon Sep 17 00:00:00 2001 From: Micky Ching Date: Fri, 21 Feb 2014 18:40:35 +0800 Subject: mmc: sdhci: add support for realtek rts5250 Add support for realtek rts5250 pci card reader. The card reader has some problems with DDR50 mode, so add a new quirks2 for broken ddr50. Signed-off-by: Micky Ching Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-pci.c | 20 ++++++++++++++++++++ drivers/mmc/host/sdhci.c | 3 ++- include/linux/mmc/sdhci.h | 2 ++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 0955777b6c7e..fdc612120362 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -610,6 +610,18 @@ static const struct sdhci_pci_fixes sdhci_via = { .probe = via_probe, }; +static int rtsx_probe_slot(struct sdhci_pci_slot *slot) +{ + slot->host->mmc->caps2 |= MMC_CAP2_HS200; + return 0; +} + +static const struct sdhci_pci_fixes sdhci_rtsx = { + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | + SDHCI_QUIRK2_BROKEN_DDR50, + .probe_slot = rtsx_probe_slot, +}; + static const struct pci_device_id pci_ids[] = { { .vendor = PCI_VENDOR_ID_RICOH, @@ -731,6 +743,14 @@ static const struct pci_device_id pci_ids[] = { .driver_data = (kernel_ulong_t)&sdhci_via, }, + { + .vendor = PCI_VENDOR_ID_REALTEK, + .device = 0x5250, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (kernel_ulong_t)&sdhci_rtsx, + }, + { .vendor = PCI_VENDOR_ID_INTEL, .device = PCI_DEVICE_ID_INTEL_MRST_SD0, diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 9ddef4763541..8958edc03e04 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3020,7 +3020,8 @@ int sdhci_add_host(struct sdhci_host *host) } else if (caps[1] & SDHCI_SUPPORT_SDR50) mmc->caps |= MMC_CAP_UHS_SDR50; - if (caps[1] & SDHCI_SUPPORT_DDR50) + if ((caps[1] & SDHCI_SUPPORT_DDR50) && + !(host->quirks2 & SDHCI_QUIRK2_BROKEN_DDR50)) mmc->caps |= MMC_CAP_UHS_DDR50; /* Does the host need tuning for SDR50? */ diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index 362927c48f97..7be12b883485 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -100,6 +100,8 @@ struct sdhci_host { #define SDHCI_QUIRK2_BROKEN_HOST_CONTROL (1<<5) /* Controller does not support HS200 */ #define SDHCI_QUIRK2_BROKEN_HS200 (1<<6) +/* Controller does not support DDR50 */ +#define SDHCI_QUIRK2_BROKEN_DDR50 (1<<7) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ -- cgit v1.2.3 From 640e09bc45f5d03622da5230d131c0bfd0d2da3f Mon Sep 17 00:00:00 2001 From: Micky Ching Date: Mon, 17 Feb 2014 16:45:46 +0800 Subject: mmc: rtsx: fix card poweroff bug If the host driver removed while card in the slot, the host will not power off card power correctly. This bug is produced because host eject flag set before the last mmc_set_ios callback, we should set the eject flag after power off. Signed-off-by: Micky Ching Signed-off-by: Chris Ball --- drivers/mmc/host/rtsx_pci_sdmmc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index c46feda07d56..cc80e3119d1d 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -1297,7 +1297,6 @@ static int rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev) pcr->slots[RTSX_SD_CARD].p_dev = NULL; pcr->slots[RTSX_SD_CARD].card_event = NULL; mmc = host->mmc; - host->eject = true; mutex_lock(&host->host_mutex); if (host->mrq) { @@ -1315,6 +1314,8 @@ static int rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev) mutex_unlock(&host->host_mutex); mmc_remove_host(mmc); + host->eject = true; + mmc_free_host(mmc); dev_dbg(&(pdev->dev), -- cgit v1.2.3 From abcc6b2943145ae2e17a52632ccab50cd612914c Mon Sep 17 00:00:00 2001 From: Micky Ching Date: Mon, 17 Feb 2014 16:45:47 +0800 Subject: mmc: rtsx: modify phase searching method for tuning The new phase searching method is more concise and easier to understand. Signed-off-by: Micky Ching Signed-off-by: Chris Ball --- drivers/mmc/host/rtsx_pci_sdmmc.c | 112 +++++++++++--------------------------- include/linux/mfd/rtsx_pci.h | 2 +- 2 files changed, 33 insertions(+), 81 deletions(-) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index cc80e3119d1d..0b9ded13a3ae 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -31,16 +31,6 @@ #include #include -/* SD Tuning Data Structure - * Record continuous timing phase path - */ -struct timing_phase_path { - int start; - int end; - int mid; - int len; -}; - struct realtek_pci_sdmmc { struct platform_device *pdev; struct rtsx_pcr *pcr; @@ -511,85 +501,47 @@ static int sd_change_phase(struct realtek_pci_sdmmc *host, return 0; } -static u8 sd_search_final_phase(struct realtek_pci_sdmmc *host, u32 phase_map) +static inline u32 test_phase_bit(u32 phase_map, unsigned int bit) { - struct timing_phase_path path[MAX_PHASE + 1]; - int i, j, cont_path_cnt; - int new_block, max_len, final_path_idx; - u8 final_phase = 0xFF; + bit %= RTSX_PHASE_MAX; + return phase_map & (1 << bit); +} - /* Parse phase_map, take it as a bit-ring */ - cont_path_cnt = 0; - new_block = 1; - j = 0; - for (i = 0; i < MAX_PHASE + 1; i++) { - if (phase_map & (1 << i)) { - if (new_block) { - new_block = 0; - j = cont_path_cnt++; - path[j].start = i; - path[j].end = i; - } else { - path[j].end = i; - } - } else { - new_block = 1; - if (cont_path_cnt) { - /* Calculate path length and middle point */ - int idx = cont_path_cnt - 1; - path[idx].len = - path[idx].end - path[idx].start + 1; - path[idx].mid = - path[idx].start + path[idx].len / 2; - } - } - } +static int sd_get_phase_len(u32 phase_map, unsigned int start_bit) +{ + int i; - if (cont_path_cnt == 0) { - dev_dbg(sdmmc_dev(host), "No continuous phase path\n"); - goto finish; - } else { - /* Calculate last continuous path length and middle point */ - int idx = cont_path_cnt - 1; - path[idx].len = path[idx].end - path[idx].start + 1; - path[idx].mid = path[idx].start + path[idx].len / 2; + for (i = 0; i < RTSX_PHASE_MAX; i++) { + if (test_phase_bit(phase_map, start_bit + i) == 0) + return i; } + return RTSX_PHASE_MAX; +} + +static u8 sd_search_final_phase(struct realtek_pci_sdmmc *host, u32 phase_map) +{ + int start = 0, len = 0; + int start_final = 0, len_final = 0; + u8 final_phase = 0xFF; - /* Connect the first and last continuous paths if they are adjacent */ - if (!path[0].start && (path[cont_path_cnt - 1].end == MAX_PHASE)) { - /* Using negative index */ - path[0].start = path[cont_path_cnt - 1].start - MAX_PHASE - 1; - path[0].len += path[cont_path_cnt - 1].len; - path[0].mid = path[0].start + path[0].len / 2; - /* Convert negative middle point index to positive one */ - if (path[0].mid < 0) - path[0].mid += MAX_PHASE + 1; - cont_path_cnt--; + if (phase_map == 0) { + dev_err(sdmmc_dev(host), "phase error: [map:%x]\n", phase_map); + return final_phase; } - /* Choose the longest continuous phase path */ - max_len = 0; - final_phase = 0; - final_path_idx = 0; - for (i = 0; i < cont_path_cnt; i++) { - if (path[i].len > max_len) { - max_len = path[i].len; - final_phase = (u8)path[i].mid; - final_path_idx = i; + while (start < RTSX_PHASE_MAX) { + len = sd_get_phase_len(phase_map, start); + if (len_final < len) { + start_final = start; + len_final = len; } - - dev_dbg(sdmmc_dev(host), "path[%d].start = %d\n", - i, path[i].start); - dev_dbg(sdmmc_dev(host), "path[%d].end = %d\n", - i, path[i].end); - dev_dbg(sdmmc_dev(host), "path[%d].len = %d\n", - i, path[i].len); - dev_dbg(sdmmc_dev(host), "path[%d].mid = %d\n", - i, path[i].mid); + start += len ? len : 1; } -finish: - dev_dbg(sdmmc_dev(host), "Final chosen phase: %d\n", final_phase); + final_phase = (start_final + len_final / 2) % RTSX_PHASE_MAX; + dev_dbg(sdmmc_dev(host), "phase: [map:%x] [maxlen:%d] [final:%d]\n", + phase_map, len_final, final_phase); + return final_phase; } @@ -635,7 +587,7 @@ static int sd_tuning_phase(struct realtek_pci_sdmmc *host, int err, i; u32 raw_phase_map = 0; - for (i = MAX_PHASE; i >= 0; i--) { + for (i = 0; i < RTSX_PHASE_MAX; i++) { err = sd_tuning_rx_cmd(host, opcode, (u8)i); if (err == 0) raw_phase_map |= 1 << i; diff --git a/include/linux/mfd/rtsx_pci.h b/include/linux/mfd/rtsx_pci.h index 0ce772105508..a3835976f7c6 100644 --- a/include/linux/mfd/rtsx_pci.h +++ b/include/linux/mfd/rtsx_pci.h @@ -144,7 +144,7 @@ #define HOST_TO_DEVICE 0 #define DEVICE_TO_HOST 1 -#define MAX_PHASE 31 +#define RTSX_PHASE_MAX 32 #define RX_TUNING_CNT 3 /* SG descriptor */ -- cgit v1.2.3 From c42deffd5b53c9e583d83c7964854ede2f12410d Mon Sep 17 00:00:00 2001 From: Micky Ching Date: Mon, 17 Feb 2014 16:45:48 +0800 Subject: mmc: rtsx: add support for pre_req and post_req Add support for non-blocking request, pre_req() runs dma_map_sg() and post_req() runs dma_unmap_sg(). This patch can increase card read/write speed, especially for high speed card and slow CPU(for some embedded platform). Users can get a great benefit from this patch. if CPU frequency is 800MHz, SDR104 or DDR50 card read/write speed may increase more than 15%. test results: intel i3(800MHz - 2.3GHz), SD card clock 208MHz performance mode(2.3GHz): Before: dd if=/dev/mmcblk0p1 of=/dev/null bs=64k count=1024 67108864 bytes (67 MB) copied, 1.18191 s, 56.8 MB/s After: dd if=/dev/mmcblk0p1 of=/dev/null bs=64k count=1024 67108864 bytes (67 MB) copied, 1.09276 s, 61.4 MB/s powersave mode(800MHz): Before: dd if=/dev/mmcblk0p1 of=/dev/null bs=64k count=1024 67108864 bytes (67 MB) copied, 1.29569 s, 51.8 MB/s After: dd if=/dev/mmcblk0p1 of=/dev/null bs=64k count=1024 67108864 bytes (67 MB) copied, 1.11218 s, 60.3 MB/s Signed-off-by: Micky Ching Signed-off-by: Chris Ball --- drivers/mfd/rtsx_pcr.c | 132 ++++++++---- drivers/mmc/host/rtsx_pci_sdmmc.c | 418 +++++++++++++++++++++++++++++++------- include/linux/mfd/rtsx_common.h | 1 + include/linux/mfd/rtsx_pci.h | 6 + 4 files changed, 448 insertions(+), 109 deletions(-) diff --git a/drivers/mfd/rtsx_pcr.c b/drivers/mfd/rtsx_pcr.c index 1d15735f9ef9..c9de3d598ea5 100644 --- a/drivers/mfd/rtsx_pcr.c +++ b/drivers/mfd/rtsx_pcr.c @@ -338,58 +338,28 @@ int rtsx_pci_transfer_data(struct rtsx_pcr *pcr, struct scatterlist *sglist, int num_sg, bool read, int timeout) { struct completion trans_done; - u8 dir; - int err = 0, i, count; + int err = 0, count; long timeleft; unsigned long flags; - struct scatterlist *sg; - enum dma_data_direction dma_dir; - u32 val; - dma_addr_t addr; - unsigned int len; - - dev_dbg(&(pcr->pci->dev), "--> %s: num_sg = %d\n", __func__, num_sg); - - /* don't transfer data during abort processing */ - if (pcr->remove_pci) - return -EINVAL; - - if ((sglist == NULL) || (num_sg <= 0)) - return -EINVAL; - if (read) { - dir = DEVICE_TO_HOST; - dma_dir = DMA_FROM_DEVICE; - } else { - dir = HOST_TO_DEVICE; - dma_dir = DMA_TO_DEVICE; - } - - count = dma_map_sg(&(pcr->pci->dev), sglist, num_sg, dma_dir); + count = rtsx_pci_dma_map_sg(pcr, sglist, num_sg, read); if (count < 1) { dev_err(&(pcr->pci->dev), "scatterlist map failed\n"); return -EINVAL; } dev_dbg(&(pcr->pci->dev), "DMA mapping count: %d\n", count); - val = ((u32)(dir & 0x01) << 29) | TRIG_DMA | ADMA_MODE; - pcr->sgi = 0; - for_each_sg(sglist, sg, count, i) { - addr = sg_dma_address(sg); - len = sg_dma_len(sg); - rtsx_pci_add_sg_tbl(pcr, addr, len, i == count - 1); - } spin_lock_irqsave(&pcr->lock, flags); pcr->done = &trans_done; pcr->trans_result = TRANS_NOT_READY; init_completion(&trans_done); - rtsx_pci_writel(pcr, RTSX_HDBAR, pcr->host_sg_tbl_addr); - rtsx_pci_writel(pcr, RTSX_HDBCTLR, val); spin_unlock_irqrestore(&pcr->lock, flags); + rtsx_pci_dma_transfer(pcr, sglist, count, read); + timeleft = wait_for_completion_interruptible_timeout( &trans_done, msecs_to_jiffies(timeout)); if (timeleft <= 0) { @@ -413,7 +383,7 @@ out: pcr->done = NULL; spin_unlock_irqrestore(&pcr->lock, flags); - dma_unmap_sg(&(pcr->pci->dev), sglist, num_sg, dma_dir); + rtsx_pci_dma_unmap_sg(pcr, sglist, num_sg, read); if ((err < 0) && (err != -ENODEV)) rtsx_pci_stop_cmd(pcr); @@ -425,6 +395,73 @@ out: } EXPORT_SYMBOL_GPL(rtsx_pci_transfer_data); +int rtsx_pci_dma_map_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int num_sg, bool read) +{ + enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + if (pcr->remove_pci) + return -EINVAL; + + if ((sglist == NULL) || num_sg < 1) + return -EINVAL; + + return dma_map_sg(&(pcr->pci->dev), sglist, num_sg, dir); +} +EXPORT_SYMBOL_GPL(rtsx_pci_dma_map_sg); + +int rtsx_pci_dma_unmap_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int num_sg, bool read) +{ + enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + if (pcr->remove_pci) + return -EINVAL; + + if (sglist == NULL || num_sg < 1) + return -EINVAL; + + dma_unmap_sg(&(pcr->pci->dev), sglist, num_sg, dir); + return num_sg; +} +EXPORT_SYMBOL_GPL(rtsx_pci_dma_unmap_sg); + +int rtsx_pci_dma_transfer(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int sg_count, bool read) +{ + struct scatterlist *sg; + dma_addr_t addr; + unsigned int len; + int i; + u32 val; + u8 dir = read ? DEVICE_TO_HOST : HOST_TO_DEVICE; + unsigned long flags; + + if (pcr->remove_pci) + return -EINVAL; + + if ((sglist == NULL) || (sg_count < 1)) + return -EINVAL; + + val = ((u32)(dir & 0x01) << 29) | TRIG_DMA | ADMA_MODE; + pcr->sgi = 0; + for_each_sg(sglist, sg, sg_count, i) { + addr = sg_dma_address(sg); + len = sg_dma_len(sg); + rtsx_pci_add_sg_tbl(pcr, addr, len, i == sg_count - 1); + } + + spin_lock_irqsave(&pcr->lock, flags); + + rtsx_pci_writel(pcr, RTSX_HDBAR, pcr->host_sg_tbl_addr); + rtsx_pci_writel(pcr, RTSX_HDBCTLR, val); + + spin_unlock_irqrestore(&pcr->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_dma_transfer); + int rtsx_pci_read_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len) { int err; @@ -836,6 +873,8 @@ static irqreturn_t rtsx_pci_isr(int irq, void *dev_id) int_reg = rtsx_pci_readl(pcr, RTSX_BIPR); /* Clear interrupt flag */ rtsx_pci_writel(pcr, RTSX_BIPR, int_reg); + dev_dbg(&pcr->pci->dev, "=========== BIPR 0x%8x ==========\n", int_reg); + if ((int_reg & pcr->bier) == 0) { spin_unlock(&pcr->lock); return IRQ_NONE; @@ -866,17 +905,28 @@ static irqreturn_t rtsx_pci_isr(int irq, void *dev_id) } if (int_reg & (NEED_COMPLETE_INT | DELINK_INT)) { - if (int_reg & (TRANS_FAIL_INT | DELINK_INT)) { + if (int_reg & (TRANS_FAIL_INT | DELINK_INT)) pcr->trans_result = TRANS_RESULT_FAIL; - if (pcr->done) - complete(pcr->done); - } else if (int_reg & TRANS_OK_INT) { + else if (int_reg & TRANS_OK_INT) pcr->trans_result = TRANS_RESULT_OK; - if (pcr->done) - complete(pcr->done); + + if (pcr->done) + complete(pcr->done); + + if (int_reg & SD_EXIST) { + struct rtsx_slot *slot = &pcr->slots[RTSX_SD_CARD]; + if (slot && slot->done_transfer) + slot->done_transfer(slot->p_dev); + } + + if (int_reg & MS_EXIST) { + struct rtsx_slot *slot = &pcr->slots[RTSX_SD_CARD]; + if (slot && slot->done_transfer) + slot->done_transfer(slot->p_dev); } } + if (pcr->card_inserted || pcr->card_removed) schedule_delayed_work(&pcr->carddet_work, msecs_to_jiffies(200)); diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index 0b9ded13a3ae..5fb994f9a653 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -31,14 +31,28 @@ #include #include +struct realtek_next { + unsigned int sg_count; + s32 cookie; +}; + struct realtek_pci_sdmmc { struct platform_device *pdev; struct rtsx_pcr *pcr; struct mmc_host *mmc; struct mmc_request *mrq; - - struct mutex host_mutex; - + struct mmc_command *cmd; + struct mmc_data *data; + + spinlock_t lock; + struct timer_list timer; + struct tasklet_struct cmd_tasklet; + struct tasklet_struct data_tasklet; + struct tasklet_struct finish_tasklet; + + u8 rsp_type; + u8 rsp_len; + int sg_count; u8 ssc_depth; unsigned int clock; bool vpclk; @@ -48,8 +62,13 @@ struct realtek_pci_sdmmc { int power_state; #define SDMMC_POWER_ON 1 #define SDMMC_POWER_OFF 0 + + struct realtek_next next_data; }; +static int sd_start_multi_rw(struct realtek_pci_sdmmc *host, + struct mmc_request *mrq); + static inline struct device *sdmmc_dev(struct realtek_pci_sdmmc *host) { return &(host->pdev->dev); @@ -86,6 +105,95 @@ static void sd_print_debug_regs(struct realtek_pci_sdmmc *host) #define sd_print_debug_regs(host) #endif /* DEBUG */ +static void sd_isr_done_transfer(struct platform_device *pdev) +{ + struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev); + + spin_lock(&host->lock); + if (host->cmd) + tasklet_schedule(&host->cmd_tasklet); + if (host->data) + tasklet_schedule(&host->data_tasklet); + spin_unlock(&host->lock); +} + +static void sd_request_timeout(unsigned long host_addr) +{ + struct realtek_pci_sdmmc *host = (struct realtek_pci_sdmmc *)host_addr; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if (!host->mrq) { + dev_err(sdmmc_dev(host), "error: no request exist\n"); + goto out; + } + + if (host->cmd) + host->cmd->error = -ETIMEDOUT; + if (host->data) + host->data->error = -ETIMEDOUT; + + dev_dbg(sdmmc_dev(host), "timeout for request\n"); + +out: + tasklet_schedule(&host->finish_tasklet); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void sd_finish_request(unsigned long host_addr) +{ + struct realtek_pci_sdmmc *host = (struct realtek_pci_sdmmc *)host_addr; + struct rtsx_pcr *pcr = host->pcr; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + unsigned long flags; + bool any_error; + + spin_lock_irqsave(&host->lock, flags); + + del_timer(&host->timer); + mrq = host->mrq; + if (!mrq) { + dev_err(sdmmc_dev(host), "error: no request need finish\n"); + goto out; + } + + cmd = mrq->cmd; + data = mrq->data; + + any_error = (mrq->sbc && mrq->sbc->error) || + (mrq->stop && mrq->stop->error) || + (cmd && cmd->error) || (data && data->error); + + if (any_error) { + rtsx_pci_stop_cmd(pcr); + sd_clear_error(host); + } + + if (data) { + if (any_error) + data->bytes_xfered = 0; + else + data->bytes_xfered = data->blocks * data->blksz; + + if (!data->host_cookie) + rtsx_pci_dma_unmap_sg(pcr, data->sg, data->sg_len, + data->flags & MMC_DATA_READ); + + } + + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + +out: + spin_unlock_irqrestore(&host->lock, flags); + mutex_unlock(&pcr->pcr_mutex); + mmc_request_done(host->mmc, mrq); +} + static int sd_read_data(struct realtek_pci_sdmmc *host, u8 *cmd, u16 byte_cnt, u8 *buf, int buf_len, int timeout) { @@ -203,8 +311,7 @@ static int sd_write_data(struct realtek_pci_sdmmc *host, u8 *cmd, u16 byte_cnt, return 0; } -static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host, - struct mmc_command *cmd) +static void sd_send_cmd(struct realtek_pci_sdmmc *host, struct mmc_command *cmd) { struct rtsx_pcr *pcr = host->pcr; u8 cmd_idx = (u8)cmd->opcode; @@ -212,11 +319,14 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host, int err = 0; int timeout = 100; int i; - u8 *ptr; - int stat_idx = 0; u8 rsp_type; int rsp_len = 5; - bool clock_toggled = false; + unsigned long flags; + + if (host->cmd) + dev_err(sdmmc_dev(host), "error: cmd already exist\n"); + + host->cmd = cmd; dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD %d, arg = 0x%08x\n", __func__, cmd_idx, arg); @@ -251,6 +361,8 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host, err = -EINVAL; goto out; } + host->rsp_type = rsp_type; + host->rsp_len = rsp_len; if (rsp_type == SD_RSP_TYPE_R1b) timeout = 3000; @@ -260,8 +372,6 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host, 0xFF, SD_CLK_TOGGLE_EN); if (err < 0) goto out; - - clock_toggled = true; } rtsx_pci_init_cmd(pcr); @@ -285,25 +395,60 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host, /* Read data from ping-pong buffer */ for (i = PPBUF_BASE2; i < PPBUF_BASE2 + 16; i++) rtsx_pci_add_cmd(pcr, READ_REG_CMD, (u16)i, 0, 0); - stat_idx = 16; } else if (rsp_type != SD_RSP_TYPE_R0) { /* Read data from SD_CMDx registers */ for (i = SD_CMD0; i <= SD_CMD4; i++) rtsx_pci_add_cmd(pcr, READ_REG_CMD, (u16)i, 0, 0); - stat_idx = 5; } rtsx_pci_add_cmd(pcr, READ_REG_CMD, SD_STAT1, 0, 0); - err = rtsx_pci_send_cmd(pcr, timeout); - if (err < 0) { - sd_print_debug_regs(host); - sd_clear_error(host); - dev_dbg(sdmmc_dev(host), - "rtsx_pci_send_cmd error (err = %d)\n", err); + mod_timer(&host->timer, jiffies + msecs_to_jiffies(timeout)); + + spin_lock_irqsave(&pcr->lock, flags); + pcr->trans_result = TRANS_NOT_READY; + rtsx_pci_send_cmd_no_wait(pcr); + spin_unlock_irqrestore(&pcr->lock, flags); + + return; + +out: + cmd->error = err; + tasklet_schedule(&host->finish_tasklet); +} + +static void sd_get_rsp(unsigned long host_addr) +{ + struct realtek_pci_sdmmc *host = (struct realtek_pci_sdmmc *)host_addr; + struct rtsx_pcr *pcr = host->pcr; + struct mmc_command *cmd; + int i, err = 0, stat_idx; + u8 *ptr, rsp_type; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + cmd = host->cmd; + host->cmd = NULL; + + if (!cmd) { + dev_err(sdmmc_dev(host), "error: cmd not exist\n"); goto out; } + spin_lock(&pcr->lock); + if (pcr->trans_result == TRANS_NO_DEVICE) + err = -ENODEV; + else if (pcr->trans_result != TRANS_RESULT_OK) + err = -EINVAL; + spin_unlock(&pcr->lock); + + if (err < 0) + goto out; + + rsp_type = host->rsp_type; + stat_idx = host->rsp_len; + if (rsp_type == SD_RSP_TYPE_R0) { err = 0; goto out; @@ -340,26 +485,106 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host, cmd->resp[0]); } + if (cmd == host->mrq->sbc) { + sd_send_cmd(host, host->mrq->cmd); + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + if (cmd == host->mrq->stop) + goto out; + + if (cmd->data) { + sd_start_multi_rw(host, host->mrq); + spin_unlock_irqrestore(&host->lock, flags); + return; + } + out: cmd->error = err; - if (err && clock_toggled) - rtsx_pci_write_register(pcr, SD_BUS_STAT, - SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, 0); + tasklet_schedule(&host->finish_tasklet); + spin_unlock_irqrestore(&host->lock, flags); +} + +static int sd_pre_dma_transfer(struct realtek_pci_sdmmc *host, + struct mmc_data *data, struct realtek_next *next) +{ + struct rtsx_pcr *pcr = host->pcr; + int read = data->flags & MMC_DATA_READ; + int sg_count = 0; + + if (!next && data->host_cookie && + data->host_cookie != host->next_data.cookie) { + dev_err(sdmmc_dev(host), + "error: invalid cookie data[%d] host[%d]\n", + data->host_cookie, host->next_data.cookie); + data->host_cookie = 0; + } + + if (next || (!next && data->host_cookie != host->next_data.cookie)) + sg_count = rtsx_pci_dma_map_sg(pcr, + data->sg, data->sg_len, read); + else + sg_count = host->next_data.sg_count; + + if (next) { + next->sg_count = sg_count; + if (++next->cookie < 0) + next->cookie = 1; + data->host_cookie = next->cookie; + } + + return sg_count; +} + +static void sdmmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq, + bool is_first_req) +{ + struct realtek_pci_sdmmc *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (data->host_cookie) { + dev_err(sdmmc_dev(host), + "error: descard already cookie data[%d]\n", + data->host_cookie); + data->host_cookie = 0; + } + + dev_dbg(sdmmc_dev(host), "dma sg prepared: %d\n", + sd_pre_dma_transfer(host, data, &host->next_data)); +} + +static void sdmmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq, + int err) +{ + struct realtek_pci_sdmmc *host = mmc_priv(mmc); + struct rtsx_pcr *pcr = host->pcr; + struct mmc_data *data = mrq->data; + int read = data->flags & MMC_DATA_READ; + + rtsx_pci_dma_unmap_sg(pcr, data->sg, data->sg_len, read); + data->host_cookie = 0; } -static int sd_rw_multi(struct realtek_pci_sdmmc *host, struct mmc_request *mrq) +static int sd_start_multi_rw(struct realtek_pci_sdmmc *host, + struct mmc_request *mrq) { struct rtsx_pcr *pcr = host->pcr; struct mmc_host *mmc = host->mmc; struct mmc_card *card = mmc->card; struct mmc_data *data = mrq->data; int uhs = mmc_card_uhs(card); - int read = (data->flags & MMC_DATA_READ) ? 1 : 0; + int read = data->flags & MMC_DATA_READ; u8 cfg2, trans_mode; int err; size_t data_len = data->blksz * data->blocks; + if (host->data) + dev_err(sdmmc_dev(host), "error: data already exist\n"); + + host->data = data; + if (read) { cfg2 = SD_CALCULATE_CRC7 | SD_CHECK_CRC16 | SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_0; @@ -410,17 +635,56 @@ static int sd_rw_multi(struct realtek_pci_sdmmc *host, struct mmc_request *mrq) rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, SD_TRANSFER, SD_TRANSFER_END, SD_TRANSFER_END); + mod_timer(&host->timer, jiffies + 10 * HZ); rtsx_pci_send_cmd_no_wait(pcr); - err = rtsx_pci_transfer_data(pcr, data->sg, data->sg_len, read, 10000); + err = rtsx_pci_dma_transfer(pcr, data->sg, host->sg_count, read); if (err < 0) { - sd_clear_error(host); - return err; + data->error = err; + tasklet_schedule(&host->finish_tasklet); } - return 0; } +static void sd_finish_multi_rw(unsigned long host_addr) +{ + struct realtek_pci_sdmmc *host = (struct realtek_pci_sdmmc *)host_addr; + struct rtsx_pcr *pcr = host->pcr; + struct mmc_data *data; + int err = 0; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if (!host->data) { + dev_err(sdmmc_dev(host), "error: no data exist\n"); + goto out; + } + + data = host->data; + host->data = NULL; + + if (pcr->trans_result == TRANS_NO_DEVICE) + err = -ENODEV; + else if (pcr->trans_result != TRANS_RESULT_OK) + err = -EINVAL; + + if (err < 0) { + data->error = err; + goto out; + } + + if (!host->mrq->sbc && data->stop) { + sd_send_cmd(host, data->stop); + spin_unlock_irqrestore(&host->lock, flags); + return; + } + +out: + tasklet_schedule(&host->finish_tasklet); + spin_unlock_irqrestore(&host->lock, flags); +} + static inline void sd_enable_initial_mode(struct realtek_pci_sdmmc *host) { rtsx_pci_write_register(host->pcr, SD_CFG1, @@ -637,6 +901,13 @@ static int sd_tuning_rx(struct realtek_pci_sdmmc *host, u8 opcode) return 0; } +static inline bool sd_use_muti_rw(struct mmc_command *cmd) +{ + return mmc_op_multi(cmd->opcode) || + (cmd->opcode == MMC_READ_SINGLE_BLOCK) || + (cmd->opcode == MMC_WRITE_BLOCK); +} + static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct realtek_pci_sdmmc *host = mmc_priv(mmc); @@ -645,6 +916,14 @@ static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq) struct mmc_data *data = mrq->data; unsigned int data_size = 0; int err; + unsigned long flags; + + mutex_lock(&pcr->pcr_mutex); + spin_lock_irqsave(&host->lock, flags); + + if (host->mrq) + dev_err(sdmmc_dev(host), "error: request already exist\n"); + host->mrq = mrq; if (host->eject) { cmd->error = -ENOMEDIUM; @@ -657,8 +936,6 @@ static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq) goto finish; } - mutex_lock(&pcr->pcr_mutex); - rtsx_pci_start_run(pcr); rtsx_pci_switch_clock(pcr, host->clock, host->ssc_depth, @@ -667,46 +944,28 @@ static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq) rtsx_pci_write_register(pcr, CARD_SHARE_MODE, CARD_SHARE_MASK, CARD_SHARE_48_SD); - mutex_lock(&host->host_mutex); - host->mrq = mrq; - mutex_unlock(&host->host_mutex); - if (mrq->data) data_size = data->blocks * data->blksz; - if (!data_size || mmc_op_multi(cmd->opcode) || - (cmd->opcode == MMC_READ_SINGLE_BLOCK) || - (cmd->opcode == MMC_WRITE_BLOCK)) { - sd_send_cmd_get_rsp(host, cmd); - - if (!cmd->error && data_size) { - sd_rw_multi(host, mrq); + if (sd_use_muti_rw(cmd)) + host->sg_count = sd_pre_dma_transfer(host, data, NULL); - if (mmc_op_multi(cmd->opcode) && mrq->stop) - sd_send_cmd_get_rsp(host, mrq->stop); - } + if (!data_size || sd_use_muti_rw(cmd)) { + if (mrq->sbc) + sd_send_cmd(host, mrq->sbc); + else + sd_send_cmd(host, cmd); + spin_unlock_irqrestore(&host->lock, flags); } else { + spin_unlock_irqrestore(&host->lock, flags); sd_normal_rw(host, mrq); + tasklet_schedule(&host->finish_tasklet); } - - if (mrq->data) { - if (cmd->error || data->error) - data->bytes_xfered = 0; - else - data->bytes_xfered = data->blocks * data->blksz; - } - - mutex_unlock(&pcr->pcr_mutex); + return; finish: - if (cmd->error) - dev_dbg(sdmmc_dev(host), "cmd->error = %d\n", cmd->error); - - mutex_lock(&host->host_mutex); - host->mrq = NULL; - mutex_unlock(&host->host_mutex); - - mmc_request_done(mmc, mrq); + tasklet_schedule(&host->finish_tasklet); + spin_unlock_irqrestore(&host->lock, flags); } static int sd_set_bus_width(struct realtek_pci_sdmmc *host, @@ -1141,6 +1400,8 @@ out: } static const struct mmc_host_ops realtek_pci_sdmmc_ops = { + .pre_req = sdmmc_pre_req, + .post_req = sdmmc_post_req, .request = sdmmc_request, .set_ios = sdmmc_set_ios, .get_ro = sdmmc_get_ro, @@ -1204,6 +1465,7 @@ static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev) struct realtek_pci_sdmmc *host; struct rtsx_pcr *pcr; struct pcr_handle *handle = pdev->dev.platform_data; + unsigned long host_addr; if (!handle) return -ENXIO; @@ -1227,8 +1489,15 @@ static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev) pcr->slots[RTSX_SD_CARD].p_dev = pdev; pcr->slots[RTSX_SD_CARD].card_event = rtsx_pci_sdmmc_card_event; - mutex_init(&host->host_mutex); + host_addr = (unsigned long)host; + host->next_data.cookie = 1; + setup_timer(&host->timer, sd_request_timeout, host_addr); + tasklet_init(&host->cmd_tasklet, sd_get_rsp, host_addr); + tasklet_init(&host->data_tasklet, sd_finish_multi_rw, host_addr); + tasklet_init(&host->finish_tasklet, sd_finish_request, host_addr); + spin_lock_init(&host->lock); + pcr->slots[RTSX_SD_CARD].done_transfer = sd_isr_done_transfer; realtek_init_host(host); mmc_add_host(mmc); @@ -1241,6 +1510,8 @@ static int rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev) struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev); struct rtsx_pcr *pcr; struct mmc_host *mmc; + struct mmc_request *mrq; + unsigned long flags; if (!host) return 0; @@ -1248,22 +1519,33 @@ static int rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev) pcr = host->pcr; pcr->slots[RTSX_SD_CARD].p_dev = NULL; pcr->slots[RTSX_SD_CARD].card_event = NULL; + pcr->slots[RTSX_SD_CARD].done_transfer = NULL; mmc = host->mmc; + mrq = host->mrq; - mutex_lock(&host->host_mutex); + spin_lock_irqsave(&host->lock, flags); if (host->mrq) { dev_dbg(&(pdev->dev), "%s: Controller removed during transfer\n", mmc_hostname(mmc)); - rtsx_pci_complete_unfinished_transfer(pcr); + if (mrq->sbc) + mrq->sbc->error = -ENOMEDIUM; + if (mrq->cmd) + mrq->cmd->error = -ENOMEDIUM; + if (mrq->stop) + mrq->stop->error = -ENOMEDIUM; + if (mrq->data) + mrq->data->error = -ENOMEDIUM; - host->mrq->cmd->error = -ENOMEDIUM; - if (host->mrq->stop) - host->mrq->stop->error = -ENOMEDIUM; - mmc_request_done(mmc, host->mrq); + tasklet_schedule(&host->finish_tasklet); } - mutex_unlock(&host->host_mutex); + spin_unlock_irqrestore(&host->lock, flags); + + del_timer_sync(&host->timer); + tasklet_kill(&host->cmd_tasklet); + tasklet_kill(&host->data_tasklet); + tasklet_kill(&host->finish_tasklet); mmc_remove_host(mmc); host->eject = true; diff --git a/include/linux/mfd/rtsx_common.h b/include/linux/mfd/rtsx_common.h index 443176ee1ab0..7c36cc55d2c7 100644 --- a/include/linux/mfd/rtsx_common.h +++ b/include/linux/mfd/rtsx_common.h @@ -45,6 +45,7 @@ struct platform_device; struct rtsx_slot { struct platform_device *p_dev; void (*card_event)(struct platform_device *p_dev); + void (*done_transfer)(struct platform_device *p_dev); }; #endif diff --git a/include/linux/mfd/rtsx_pci.h b/include/linux/mfd/rtsx_pci.h index a3835976f7c6..8d6bbd609ad9 100644 --- a/include/linux/mfd/rtsx_pci.h +++ b/include/linux/mfd/rtsx_pci.h @@ -943,6 +943,12 @@ void rtsx_pci_send_cmd_no_wait(struct rtsx_pcr *pcr); int rtsx_pci_send_cmd(struct rtsx_pcr *pcr, int timeout); int rtsx_pci_transfer_data(struct rtsx_pcr *pcr, struct scatterlist *sglist, int num_sg, bool read, int timeout); +int rtsx_pci_dma_map_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int num_sg, bool read); +int rtsx_pci_dma_unmap_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int num_sg, bool read); +int rtsx_pci_dma_transfer(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int sg_count, bool read); int rtsx_pci_read_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len); int rtsx_pci_write_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len); int rtsx_pci_card_pull_ctl_enable(struct rtsx_pcr *pcr, int card); -- cgit v1.2.3 From b66bd0e4d00c89102851c1bc9a0f738a4ad9ca2d Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Fri, 14 Feb 2014 13:27:07 +0100 Subject: mmc: core: Add DT bindings for SD card's UHS bus speed modes Provide the option to configure these speed modes per host, for those host driver's that can't distinguish this in runtime. Signed-off-by: Ulf Hansson Acked-by: Jaehoon Chung Signed-off-by: Chris Ball --- Documentation/devicetree/bindings/mmc/mmc.txt | 5 +++++ drivers/mmc/core/host.c | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt index 458b57f199af..bd2ce67dbb9e 100644 --- a/Documentation/devicetree/bindings/mmc/mmc.txt +++ b/Documentation/devicetree/bindings/mmc/mmc.txt @@ -26,6 +26,11 @@ Optional properties: 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 +- sd-uhs-sdr12: SD UHS SDR12 speed is supported +- sd-uhs-sdr25: SD UHS SDR25 speed is supported +- sd-uhs-sdr50: SD UHS SDR50 speed is supported +- sd-uhs-sdr104: SD UHS SDR104 speed is supported +- sd-uhs-ddr50: SD UHS DDR50 speed is supported - cap-power-off-card: powering off the card is safe - cap-sdio-irq: enable SDIO IRQ signalling on this interface - full-pwr-cycle: full power cycle of the card is supported diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 49bc403e31f0..2644d91fd336 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -419,6 +419,16 @@ int mmc_of_parse(struct mmc_host *host) 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, "sd-uhs-sdr12", &len)) + host->caps |= MMC_CAP_UHS_SDR12; + if (of_find_property(np, "sd-uhs-sdr25", &len)) + host->caps |= MMC_CAP_UHS_SDR25; + if (of_find_property(np, "sd-uhs-sdr50", &len)) + host->caps |= MMC_CAP_UHS_SDR50; + if (of_find_property(np, "sd-uhs-sdr104", &len)) + host->caps |= MMC_CAP_UHS_SDR104; + if (of_find_property(np, "sd-uhs-ddr50", &len)) + host->caps |= MMC_CAP_UHS_DDR50; 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)) -- cgit v1.2.3 From c0baf8485928b2db8a9530672d522d758746a2b6 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Fri, 14 Feb 2014 13:27:08 +0100 Subject: mmc: core: Add DT bindings for eMMC high-speed DDR 1.8/1.2V Provide the option to configure these speed modes per host, for those host driver's that can't distinguish this in runtime. Signed-off-by: Ulf Hansson Acked-by: Jaehoon Chung Signed-off-by: Chris Ball --- Documentation/devicetree/bindings/mmc/mmc.txt | 2 ++ drivers/mmc/core/host.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt index bd2ce67dbb9e..519d9523a413 100644 --- a/Documentation/devicetree/bindings/mmc/mmc.txt +++ b/Documentation/devicetree/bindings/mmc/mmc.txt @@ -34,6 +34,8 @@ Optional properties: - cap-power-off-card: powering off the card is safe - cap-sdio-irq: enable SDIO IRQ signalling on this interface - full-pwr-cycle: full power cycle of the card is supported +- mmc-highspeed-ddr-1_8v: eMMC high-speed DDR mode(1.8V I/O) is supported +- mmc-highspeed-ddr-1_2v: eMMC high-speed DDR mode(1.2V I/O) is supported *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 2644d91fd336..d014127f1043 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -439,6 +439,10 @@ int mmc_of_parse(struct mmc_host *host) host->pm_caps |= MMC_PM_KEEP_POWER; if (of_find_property(np, "enable-sdio-wakeup", &len)) host->pm_caps |= MMC_PM_WAKE_SDIO_IRQ; + if (of_find_property(np, "mmc-ddr-1_8v", &len)) + host->caps |= MMC_CAP_1_8V_DDR; + if (of_find_property(np, "mmc-ddr-1_2v", &len)) + host->caps |= MMC_CAP_1_2V_DDR; return 0; -- cgit v1.2.3 From 321bd41de1580f953c637de0fd81e06fcba8b3e8 Mon Sep 17 00:00:00 2001 From: Jaehoon Chung Date: Fri, 14 Feb 2014 13:27:09 +0100 Subject: mmc: core: Add DT bindings for eMMC HS200 1.8/1.2V Provide the option to configure these speed modes per host, for those host driver's that can't distinguish this in runtime. Signed-off-by: Jaehoon Chung Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- Documentation/devicetree/bindings/mmc/mmc.txt | 2 ++ drivers/mmc/core/host.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt index 519d9523a413..9dce540771fb 100644 --- a/Documentation/devicetree/bindings/mmc/mmc.txt +++ b/Documentation/devicetree/bindings/mmc/mmc.txt @@ -36,6 +36,8 @@ Optional properties: - full-pwr-cycle: full power cycle of the card is supported - mmc-highspeed-ddr-1_8v: eMMC high-speed DDR mode(1.8V I/O) is supported - mmc-highspeed-ddr-1_2v: eMMC high-speed DDR mode(1.2V I/O) is supported +- mmc-hs200-1_8v: eMMC HS200 mode(1.8V I/O) is supported +- mmc-hs200-1_2v: eMMC HS200 mode(1.2V I/O) is supported *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 d014127f1043..453573d5ab08 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -443,6 +443,10 @@ int mmc_of_parse(struct mmc_host *host) host->caps |= MMC_CAP_1_8V_DDR; if (of_find_property(np, "mmc-ddr-1_2v", &len)) host->caps |= MMC_CAP_1_2V_DDR; + if (of_find_property(np, "mmc-hs200-1_8v", &len)) + host->caps2 |= MMC_CAP2_HS200_1_8V_SDR; + if (of_find_property(np, "mmc-hs200-1_2v", &len)) + host->caps2 |= MMC_CAP2_HS200_1_2V_SDR; return 0; -- cgit v1.2.3 From e18eaf8ff940d75d240d7dee6ab84363438f379e Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 19 Feb 2014 00:01:05 +0100 Subject: MAINTAINERS: Add maintainer for the mmc subsystem Put myself as a (co)maintainer of the mmc subsystem to help out Chris Ball in a more official role. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index b2cf5cfb4d29..37d58a04f598 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5800,6 +5800,7 @@ F: include/linux/mfd/ MULTIMEDIA CARD (MMC), SECURE DIGITAL (SD) AND SDIO SUBSYSTEM M: Chris Ball +M: Ulf Hansson L: linux-mmc@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc.git S: Maintained -- cgit v1.2.3 From 68eb80e06bfa06035d0304686124974780308fae Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 18 Dec 2013 09:57:38 +0100 Subject: mmc: core: Rename max_discard_to to max_busy_timeout Rename host->max_discard_to to host->max_busy_timeout, to reflect that it tells the mmc core layer about the maximum supported busy detection timeout by the host. This timeout is at the moment only applicable to erase/trim/discard commands. By the renaming we provide the option of make use of it for other commands that cares about busy detection. In other words, those commands that wants an R1B response, like for example the mmc switch command. Do note that the max_busy_timeout is supposed to be specified only by hosts supporting MMC_CAP_WAIT_WHILE_BUSY. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 6 +++--- drivers/mmc/host/sdhci.c | 2 +- include/linux/mmc/host.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index f5a068d55c36..d9c1efa2ce15 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2120,7 +2120,7 @@ static unsigned int mmc_do_calc_max_discard(struct mmc_card *card, y = 0; for (x = 1; x && x <= max_qty && max_qty - x >= qty; x <<= 1) { timeout = mmc_erase_timeout(card, arg, qty + x); - if (timeout > host->max_discard_to) + if (timeout > host->max_busy_timeout) break; if (timeout < last_timeout) break; @@ -2152,7 +2152,7 @@ unsigned int mmc_calc_max_discard(struct mmc_card *card) struct mmc_host *host = card->host; unsigned int max_discard, max_trim; - if (!host->max_discard_to) + if (!host->max_busy_timeout) return UINT_MAX; /* @@ -2172,7 +2172,7 @@ unsigned int mmc_calc_max_discard(struct mmc_card *card) max_discard = 0; } pr_debug("%s: calculated max. discard sectors %u for timeout %u ms\n", - mmc_hostname(host), max_discard, host->max_discard_to); + mmc_hostname(host), max_discard, host->max_busy_timeout); return max_discard; } EXPORT_SYMBOL(mmc_calc_max_discard); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 8958edc03e04..d1e1bf52fe52 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2941,7 +2941,7 @@ int sdhci_add_host(struct sdhci_host *host) if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK) host->timeout_clk = mmc->f_max / 1000; - mmc->max_discard_to = (1 << 27) / host->timeout_clk; + mmc->max_busy_timeout = (1 << 27) / host->timeout_clk; mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 719db89ef134..cb61ea4d6945 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -300,7 +300,7 @@ struct mmc_host { unsigned int max_req_size; /* maximum number of bytes in one req */ unsigned int max_blk_size; /* maximum size of one mmc block */ unsigned int max_blk_count; /* maximum number of blocks in one req */ - unsigned int max_discard_to; /* max. discard timeout in ms */ + unsigned int max_busy_timeout; /* max busy timeout in ms */ /* private data */ spinlock_t lock; /* lock for claim and bus ops */ -- cgit v1.2.3 From 1d4d77444bf4212c44585146a2b353ca24c815f9 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 8 Jan 2014 15:06:08 +0100 Subject: mmc: core: Rename cmd_timeout_ms to busy_timeout To better reflect that the cmd_timeout_ms is directly related to the busy detection timeout, let's rename it. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 2 +- drivers/mmc/core/mmc_ops.c | 2 +- drivers/mmc/host/sdhci.c | 8 ++++---- include/linux/mmc/core.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index d9c1efa2ce15..1935812e4215 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1933,7 +1933,7 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from, cmd.opcode = MMC_ERASE; cmd.arg = arg; cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; - cmd.cmd_timeout_ms = mmc_erase_timeout(card, arg, qty); + cmd.busy_timeout = mmc_erase_timeout(card, arg, qty); err = mmc_wait_for_cmd(card->host, &cmd, 0); if (err) { pr_err("mmc_erase: erase error %d, status %#x\n", diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index e5b5eeb548d1..3377bbfc3585 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -432,7 +432,7 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1; - cmd.cmd_timeout_ms = timeout_ms; + cmd.busy_timeout = timeout_ms; if (index == EXT_CSD_SANITIZE_START) cmd.sanitize_busy = true; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index d1e1bf52fe52..7f95211e9449 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -675,12 +675,12 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) return 0xE; /* Unspecified timeout, assume max */ - if (!data && !cmd->cmd_timeout_ms) + if (!data && !cmd->busy_timeout) return 0xE; /* timeout in us */ if (!data) - target_timeout = cmd->cmd_timeout_ms * 1000; + target_timeout = cmd->busy_timeout * 1000; else { target_timeout = data->timeout_ns / 1000; if (host->clock) @@ -1019,8 +1019,8 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) } timeout = jiffies; - if (!cmd->data && cmd->cmd_timeout_ms > 9000) - timeout += DIV_ROUND_UP(cmd->cmd_timeout_ms, 1000) * HZ + HZ; + if (!cmd->data && cmd->busy_timeout > 9000) + timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ; else timeout += 10 * HZ; mod_timer(&host->timer, timeout); diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 87079fc38011..b27699612888 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -95,7 +95,7 @@ struct mmc_command { * actively failing requests */ - unsigned int cmd_timeout_ms; /* in milliseconds */ + unsigned int busy_timeout; /* busy detect timeout in ms */ /* Set this flag only for blocking sanitize request */ bool sanitize_busy; -- cgit v1.2.3 From 4509f847751c9d2a724f37fe831393fbac34b80f Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 8 Jan 2014 16:09:33 +0100 Subject: mmc: core: Add ignore_crc flag to __mmc_switch Instead of handle specific adaptations, releated to certain switch operations, inside __mmc_switch, push this to be handled by the caller instead. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 3 ++- drivers/mmc/core/mmc.c | 13 +++++++------ drivers/mmc/core/mmc_ops.c | 19 ++++++++++--------- include/linux/mmc/core.h | 2 +- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 1935812e4215..dc7a5fb81a5c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -285,7 +285,8 @@ void mmc_start_bkops(struct mmc_card *card, bool from_exception) } err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_BKOPS_START, 1, timeout, use_busy_signal, true); + EXT_CSD_BKOPS_START, 1, timeout, + use_busy_signal, true, false); if (err) { pr_warn("%s: Error %d starting bkops\n", mmc_hostname(card->host), err); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 072171183d5b..c944692f3c4e 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -856,8 +856,8 @@ static int mmc_select_hs200(struct mmc_card *card) /* switch to HS200 mode if bus width set successfully */ if (!err) - err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_HS_TIMING, 2, 0); + err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, 2, 0, true, true, true); err: return err; } @@ -1074,9 +1074,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, host->caps2 & MMC_CAP2_HS200) err = mmc_select_hs200(card); else if (host->caps & MMC_CAP_MMC_HIGHSPEED) - err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_HS_TIMING, 1, - card->ext_csd.generic_cmd6_time); + err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, 1, + card->ext_csd.generic_cmd6_time, + true, true, true); if (err && err != -EBADMSG) goto free_card; @@ -1400,7 +1401,7 @@ static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type) err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_POWER_OFF_NOTIFICATION, - notify_type, timeout, true, false); + notify_type, timeout, true, false, false); if (err) pr_err("%s: Power Off Notification timed out, %u\n", mmc_hostname(card->host), timeout); diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 3377bbfc3585..5e1a2cbdc229 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -405,17 +405,18 @@ int mmc_spi_set_crc(struct mmc_host *host, int use_crc) * timeout of zero implies maximum possible timeout * @use_busy_signal: use the busy signal as response type * @send_status: send status cmd to poll for busy + * @ignore_crc: ignore CRC errors when sending status cmd to poll for busy * * Modifies the EXT_CSD register for selected card. */ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, - unsigned int timeout_ms, bool use_busy_signal, bool send_status) + unsigned int timeout_ms, bool use_busy_signal, bool send_status, + bool ignore_crc) { int err; struct mmc_command cmd = {0}; unsigned long timeout; u32 status = 0; - bool ignore_crc = false; BUG_ON(!card); BUG_ON(!card->host); @@ -445,14 +446,13 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, return 0; /* - * Must check status to be sure of no errors - * If CMD13 is to check the busy completion of the timing change, - * disable the check of CRC error. + * CRC errors shall only be ignored in cases were CMD13 is used to poll + * to detect busy completion. */ - if (index == EXT_CSD_HS_TIMING && - !(card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)) - ignore_crc = true; + if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) + ignore_crc = false; + /* Must check status to be sure of no errors. */ timeout = jiffies + msecs_to_jiffies(MMC_OPS_TIMEOUT_MS); do { if (send_status) { @@ -501,7 +501,8 @@ EXPORT_SYMBOL_GPL(__mmc_switch); int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, unsigned int timeout_ms) { - return __mmc_switch(card, set, index, value, timeout_ms, true, true); + return __mmc_switch(card, set, index, value, timeout_ms, true, true, + false); } EXPORT_SYMBOL_GPL(mmc_switch); diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index b27699612888..f206e29f94d7 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -152,7 +152,7 @@ extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, struct mmc_command *, int); 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, - bool); + bool, 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); -- cgit v1.2.3 From 636bd13c12d2331ad835b89681428eccb4cf102e Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 28 Jan 2014 14:05:39 +0100 Subject: mmc: core: Minor simplifications to __mmc_switch Instead of using several references to card->host, let's use a local variable. That means we can remove the BUG_ON verifications for the same pointers. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/mmc_ops.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 5e1a2cbdc229..04ecdb913aa0 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -413,14 +413,12 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, unsigned int timeout_ms, bool use_busy_signal, bool send_status, bool ignore_crc) { + struct mmc_host *host = card->host; int err; struct mmc_command cmd = {0}; unsigned long timeout; u32 status = 0; - BUG_ON(!card); - BUG_ON(!card->host); - cmd.opcode = MMC_SWITCH; cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (index << 16) | @@ -437,7 +435,7 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, if (index == EXT_CSD_SANITIZE_START) cmd.sanitize_busy = true; - err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); + err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); if (err) return err; @@ -449,7 +447,7 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, * CRC errors shall only be ignored in cases were CMD13 is used to poll * to detect busy completion. */ - if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) + if (host->caps & MMC_CAP_WAIT_WHILE_BUSY) ignore_crc = false; /* Must check status to be sure of no errors. */ @@ -460,9 +458,9 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, if (err) return err; } - if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) + if (host->caps & MMC_CAP_WAIT_WHILE_BUSY) break; - if (mmc_host_is_spi(card->host)) + if (mmc_host_is_spi(host)) break; /* @@ -478,18 +476,18 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, /* Timeout if the device never leaves the program state. */ if (time_after(jiffies, timeout)) { pr_err("%s: Card stuck in programming state! %s\n", - mmc_hostname(card->host), __func__); + mmc_hostname(host), __func__); return -ETIMEDOUT; } } while (R1_CURRENT_STATE(status) == R1_STATE_PRG); - if (mmc_host_is_spi(card->host)) { + if (mmc_host_is_spi(host)) { if (status & R1_SPI_ILLEGAL_COMMAND) return -EBADMSG; } else { if (status & 0xFDFFA000) - pr_warning("%s: unexpected status %#x after " - "switch", mmc_hostname(card->host), status); + pr_warn("%s: unexpected status %#x after switch\n", + mmc_hostname(host), status); if (status & R1_SWITCH_ERROR) return -EBADMSG; } -- cgit v1.2.3 From b9ec26160f998493509b782be999ff59e4372971 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 28 Jan 2014 14:15:34 +0100 Subject: mmc: core: Fixup busy detection for mmc switch operations If the host controller supports busy detection in HW, we expect the MMC_CAP_WAIT_WHILE_BUSY to be set. Likewise the corresponding host->max_busy_timeout should reflect the maximum busy detection timeout supported by the host. Previously we expected a host that supported MMC_CAP_WAIT_WHILE_BUSY to cope with any timeout, which just isn't feasible due to HW limitations. For most switch operations, R1B responses are expected and thus we need to check for busy detection completion. To cope with cases where the requested busy detection timeout is greater than what the host are able to support, we fallback to use a R1 response instead. This will prevent the host from doing HW busy detection. In those cases, busy detection completion is handled by polling the for the card's status using CMD13. This is the same mechanism used when the host doesn't support MMC_CAP_WAIT_WHILE_BUSY. Do note, a host->max_busy_timeout set to zero, is interpreted by the mmc core as it don't know what the host supports. It will then provide the host with whatever timeout the mmc core finds suitable. For some cases the mmc core has unfurtunate no clue of what timeout to use. In these cases we provide the host with a timeout value of zero, which the host may interpret as use whatever timeout it finds suitable. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/mmc_ops.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 04ecdb913aa0..f51b5ba3bbea 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -418,6 +418,17 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, struct mmc_command cmd = {0}; unsigned long timeout; u32 status = 0; + bool use_r1b_resp = use_busy_signal; + + /* + * If the cmd timeout and the max_busy_timeout of the host are both + * specified, let's validate them. A failure means we need to prevent + * the host from doing hw busy detection, which is done by converting + * to a R1 response instead of a R1B. + */ + if (timeout_ms && host->max_busy_timeout && + (timeout_ms > host->max_busy_timeout)) + use_r1b_resp = false; cmd.opcode = MMC_SWITCH; cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | @@ -425,13 +436,17 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, (value << 8) | set; cmd.flags = MMC_CMD_AC; - if (use_busy_signal) + if (use_r1b_resp) { cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B; - else + /* + * A busy_timeout of zero means the host can decide to use + * whatever value it finds suitable. + */ + cmd.busy_timeout = timeout_ms; + } else { cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1; + } - - cmd.busy_timeout = timeout_ms; if (index == EXT_CSD_SANITIZE_START) cmd.sanitize_busy = true; @@ -447,18 +462,22 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, * CRC errors shall only be ignored in cases were CMD13 is used to poll * to detect busy completion. */ - if (host->caps & MMC_CAP_WAIT_WHILE_BUSY) + if ((host->caps & MMC_CAP_WAIT_WHILE_BUSY) && use_r1b_resp) ignore_crc = false; + /* We have an unspecified cmd timeout, use the fallback value. */ + if (!timeout_ms) + timeout_ms = MMC_OPS_TIMEOUT_MS; + /* Must check status to be sure of no errors. */ - timeout = jiffies + msecs_to_jiffies(MMC_OPS_TIMEOUT_MS); + timeout = jiffies + msecs_to_jiffies(timeout_ms); do { if (send_status) { err = __mmc_send_status(card, &status, ignore_crc); if (err) return err; } - if (host->caps & MMC_CAP_WAIT_WHILE_BUSY) + if ((host->caps & MMC_CAP_WAIT_WHILE_BUSY) && use_r1b_resp) break; if (mmc_host_is_spi(host)) break; -- cgit v1.2.3 From 57de31f6351ecad9212a26d2112b546e4c8b4a5b Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 14 Jan 2014 17:36:21 +0100 Subject: mmc: core: Use generic CMD6 time while switching to eMMC HS200 mode Conform to the eMMC spec and use the CMD6 generic timeout from the EXT_CSD register, when switching to HS200 mode. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/mmc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index c944692f3c4e..6b7ccef51beb 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -857,7 +857,9 @@ static int mmc_select_hs200(struct mmc_card *card) /* switch to HS200 mode if bus width set successfully */ if (!err) err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_HS_TIMING, 2, 0, true, true, true); + EXT_CSD_HS_TIMING, 2, + card->ext_csd.generic_cmd6_time, + true, true, true); err: return err; } -- cgit v1.2.3 From cb962e04b04fb67dbaf1455d3c60d64297ef4933 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 14 Jan 2014 23:17:36 +0100 Subject: mmc: core: Respect host's max_busy_timeout when sending sleep cmd When sending the sleep command for host drivers supporting MMC_CAP_WAIT_WHILE_BUSY, we need to confirm that max_busy_timeout is big enough comparing to the sleep timeout specified from card's EXT_CSD. If this isn't case, we use a R1 response instead of R1B and fallback to use a delay instead. Do note that a max_busy_timeout set to zero by the host, is interpreted as it can cope with whatever timeout the mmc core provides it with. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/mmc.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 6b7ccef51beb..1ab5f3a0af5b 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1358,6 +1358,7 @@ static int mmc_sleep(struct mmc_host *host) { struct mmc_command cmd = {0}; struct mmc_card *card = host->card; + unsigned int timeout_ms = DIV_ROUND_UP(card->ext_csd.sa_timeout, 10000); int err; err = mmc_deselect_cards(host); @@ -1368,7 +1369,19 @@ static int mmc_sleep(struct mmc_host *host) cmd.arg = card->rca << 16; cmd.arg |= 1 << 15; - cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + /* + * If the max_busy_timeout of the host is specified, validate it against + * the sleep cmd timeout. A failure means we need to prevent the host + * from doing hw busy detection, which is done by converting to a R1 + * response instead of a R1B. + */ + if (host->max_busy_timeout && (timeout_ms > host->max_busy_timeout)) { + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + } else { + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + cmd.busy_timeout = timeout_ms; + } + err = mmc_wait_for_cmd(host, &cmd, 0); if (err) return err; @@ -1379,8 +1392,8 @@ static int mmc_sleep(struct mmc_host *host) * SEND_STATUS command to poll the status because that command (and most * others) is invalid while the card sleeps. */ - if (!(host->caps & MMC_CAP_WAIT_WHILE_BUSY)) - mmc_delay(DIV_ROUND_UP(card->ext_csd.sa_timeout, 10000)); + if (!cmd.busy_timeout || !(host->caps & MMC_CAP_WAIT_WHILE_BUSY)) + mmc_delay(timeout_ms); return err; } -- cgit v1.2.3 From bcc3e1726d827c2d6f62f0e0e7bbc99eed7ad925 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 14 Jan 2014 21:24:21 +0100 Subject: mmc: block: Use R1 responses for stop cmds for read requests While using open ended transmission and thus ending the transfer by sending a stop command, we shall use R1B only for writes and R1 shall be used for reads. Previously R1B were used in both cases. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index c2187d5e9070..156cabfa61c3 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1334,7 +1334,6 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, brq->data.blksz = 512; brq->stop.opcode = MMC_STOP_TRANSMISSION; brq->stop.arg = 0; - brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; brq->data.blocks = blk_rq_sectors(req); /* @@ -1377,9 +1376,15 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, if (rq_data_dir(req) == READ) { brq->cmd.opcode = readcmd; brq->data.flags |= MMC_DATA_READ; + if (brq->mrq.stop) + brq->stop.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | + MMC_CMD_AC; } else { brq->cmd.opcode = writecmd; brq->data.flags |= MMC_DATA_WRITE; + if (brq->mrq.stop) + brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | + MMC_CMD_AC; } if (do_rel_wr) -- cgit v1.2.3 From c49433fb66935bd01930e37c7f47d38b6f8135fc Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 29 Jan 2014 11:01:55 +0100 Subject: mmc: block: Implement card_busy_detect() for busy detection To complete a data write request we poll for the card's status register by sending CMD13. The are other scenarios when this polling method are needed, which is why we here moves this code to it's own function. No functional change. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 82 +++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 156cabfa61c3..fffa83359c67 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -749,6 +749,49 @@ static int get_card_status(struct mmc_card *card, u32 *status, int retries) return err; } +static int card_busy_detect(struct mmc_card *card, unsigned int timeout_ms, + struct request *req, int *gen_err) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms); + int err = 0; + u32 status; + + do { + err = get_card_status(card, &status, 5); + if (err) { + pr_err("%s: error %d requesting status\n", + req->rq_disk->disk_name, err); + return err; + } + + if (status & R1_ERROR) { + pr_err("%s: %s: error sending status cmd, status %#x\n", + req->rq_disk->disk_name, __func__, status); + *gen_err = 1; + } + + /* + * Timeout if the device never becomes ready for data and never + * leaves the program state. + */ + if (time_after(jiffies, timeout)) { + pr_err("%s: Card stuck in programming state! %s %s\n", + mmc_hostname(card->host), + req->rq_disk->disk_name, __func__); + return -ETIMEDOUT; + } + + /* + * Some cards mishandle the status bits, + * so make sure to check both the busy + * indication and the card state. + */ + } while (!(status & R1_READY_FOR_DATA) || + (R1_CURRENT_STATE(status) == R1_STATE_PRG)); + + return err; +} + #define ERR_NOMEDIUM 3 #define ERR_RETRY 2 #define ERR_ABORT 1 @@ -1156,8 +1199,7 @@ static int mmc_blk_err_check(struct mmc_card *card, * program mode, which we have to wait for it to complete. */ if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) { - u32 status; - unsigned long timeout; + int err; /* Check stop command response */ if (brq->stop.resp[0] & R1_ERROR) { @@ -1167,39 +1209,9 @@ static int mmc_blk_err_check(struct mmc_card *card, gen_err = 1; } - timeout = jiffies + msecs_to_jiffies(MMC_BLK_TIMEOUT_MS); - do { - int err = get_card_status(card, &status, 5); - if (err) { - pr_err("%s: error %d requesting status\n", - req->rq_disk->disk_name, err); - return MMC_BLK_CMD_ERR; - } - - if (status & R1_ERROR) { - pr_err("%s: %s: general error sending status command, card status %#x\n", - req->rq_disk->disk_name, __func__, - status); - gen_err = 1; - } - - /* Timeout if the device never becomes ready for data - * and never leaves the program state. - */ - if (time_after(jiffies, timeout)) { - pr_err("%s: Card stuck in programming state!"\ - " %s %s\n", mmc_hostname(card->host), - req->rq_disk->disk_name, __func__); - - return MMC_BLK_CMD_ERR; - } - /* - * Some cards mishandle the status bits, - * so make sure to check both the busy - * indication and the card state. - */ - } while (!(status & R1_READY_FOR_DATA) || - (R1_CURRENT_STATE(status) == R1_STATE_PRG)); + err = card_busy_detect(card, MMC_BLK_TIMEOUT_MS, req, &gen_err); + if (err) + return MMC_BLK_CMD_ERR; } /* if general error occurs, retry the write operation. */ -- cgit v1.2.3 From 95a91298fc09db0c21193ace96d1934598dd92dd Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 29 Jan 2014 13:11:27 +0100 Subject: mmc: block: Respect hw busy detection in card_busy_detect() Currently for write request we don't trust the hw busy detection to be fully handled by host, thus we also poll the card's status until we see it's gets out of the busy state. Still there are scenarios where it will a benefit to trust the hw busy detection done by the host, since no additional polling is needed. Let's prepare card_busy_detect() to be able to handle this. Signed-off-by: Ulf Hansson 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 fffa83359c67..813ec83b74c9 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -750,7 +750,7 @@ static int get_card_status(struct mmc_card *card, u32 *status, int retries) } static int card_busy_detect(struct mmc_card *card, unsigned int timeout_ms, - struct request *req, int *gen_err) + bool hw_busy_detect, struct request *req, int *gen_err) { unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms); int err = 0; @@ -770,6 +770,11 @@ static int card_busy_detect(struct mmc_card *card, unsigned int timeout_ms, *gen_err = 1; } + /* We may rely on the host hw to handle busy detection.*/ + if ((card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) && + hw_busy_detect) + break; + /* * Timeout if the device never becomes ready for data and never * leaves the program state. @@ -1209,7 +1214,8 @@ static int mmc_blk_err_check(struct mmc_card *card, gen_err = 1; } - err = card_busy_detect(card, MMC_BLK_TIMEOUT_MS, req, &gen_err); + err = card_busy_detect(card, MMC_BLK_TIMEOUT_MS, false, req, + &gen_err); if (err) return MMC_BLK_CMD_ERR; } -- cgit v1.2.3 From bb5cba40dc7f079ea7ee3ae760b7c388b6eb5fc3 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 14 Jan 2014 21:31:35 +0100 Subject: mmc: block: Fixup busy detection while invoking stop cmd at recovery When sending a stop command at the recovery path, use a R1B response when the failing data request are a WRITE. Thus we also care about the busy detection completion in this case. For a failing READ request, we use a R1 response for the stop command, since we don't need to care about busy detection in this case. To align behavior between hosts supporting MMC_CAP_WAIT_WHILE_BUSY and those who are not, we add a CMD13 polling method for the card's status. We also respect whether the host has specified the max_busy_timeout, which means we may fallback to CMD13 polling if the timeout is greater than what the host are able to support. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 83 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 813ec83b74c9..452782bffebc 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -721,19 +721,6 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) return result; } -static int send_stop(struct mmc_card *card, u32 *status) -{ - struct mmc_command cmd = {0}; - int err; - - cmd.opcode = MMC_STOP_TRANSMISSION; - cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; - err = mmc_wait_for_cmd(card->host, &cmd, 5); - if (err == 0) - *status = cmd.resp[0]; - return err; -} - static int get_card_status(struct mmc_card *card, u32 *status, int retries) { struct mmc_command cmd = {0}; @@ -797,6 +784,51 @@ static int card_busy_detect(struct mmc_card *card, unsigned int timeout_ms, return err; } +static int send_stop(struct mmc_card *card, unsigned int timeout_ms, + struct request *req, int *gen_err, u32 *stop_status) +{ + struct mmc_host *host = card->host; + struct mmc_command cmd = {0}; + int err; + bool use_r1b_resp = rq_data_dir(req) == WRITE; + + /* + * Normally we use R1B responses for WRITE, but in cases where the host + * has specified a max_busy_timeout we need to validate it. A failure + * means we need to prevent the host from doing hw busy detection, which + * is done by converting to a R1 response instead. + */ + if (host->max_busy_timeout && (timeout_ms > host->max_busy_timeout)) + use_r1b_resp = false; + + cmd.opcode = MMC_STOP_TRANSMISSION; + if (use_r1b_resp) { + cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; + cmd.busy_timeout = timeout_ms; + } else { + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; + } + + err = mmc_wait_for_cmd(host, &cmd, 5); + if (err) + return err; + + *stop_status = cmd.resp[0]; + + /* No need to check card status in case of READ. */ + if (rq_data_dir(req) == READ) + return 0; + + if (!mmc_host_is_spi(host) && + (*stop_status & R1_ERROR)) { + pr_err("%s: %s: general error sending stop command, resp %#x\n", + req->rq_disk->disk_name, __func__, *stop_status); + *gen_err = 1; + } + + return card_busy_detect(card, timeout_ms, use_r1b_resp, req, gen_err); +} + #define ERR_NOMEDIUM 3 #define ERR_RETRY 2 #define ERR_ABORT 1 @@ -913,26 +945,21 @@ static int mmc_blk_cmd_recovery(struct mmc_card *card, struct request *req, */ if (R1_CURRENT_STATE(status) == R1_STATE_DATA || R1_CURRENT_STATE(status) == R1_STATE_RCV) { - err = send_stop(card, &stop_status); - if (err) + err = send_stop(card, + DIV_ROUND_UP(brq->data.timeout_ns, 1000000), + req, gen_err, &stop_status); + if (err) { pr_err("%s: error %d sending stop command\n", req->rq_disk->disk_name, err); - - /* - * If the stop cmd also timed out, the card is probably - * not present, so abort. Other errors are bad news too. - */ - if (err) + /* + * If the stop cmd also timed out, the card is probably + * not present, so abort. Other errors are bad news too. + */ return ERR_ABORT; + } + if (stop_status & R1_CARD_ECC_FAILED) *ecc_err = 1; - if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) - if (stop_status & R1_ERROR) { - pr_err("%s: %s: general error sending stop command, stop cmd response %#x\n", - req->rq_disk->disk_name, __func__, - stop_status); - *gen_err = 1; - } } /* Check for set block count errors */ -- cgit v1.2.3 From e7f3d22289e4307b3071cc18b1d8ecc6598c0be4 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Fri, 10 Jan 2014 14:51:42 +0100 Subject: mmc: mmci: Handle CMD irq before DATA irq In case of a read operation both MCI_CMDRESPEND and MCI_DATAEND can be set in the status register when entering the interrupt handler. This is due to that the card start sending data before the host has acknowledged the command response. To resolve the issue for this scenario, we must start by handling the CMD irq instead of the DATA irq. The reason is beacuse the completion of the DATA irq will not respect the current command and then causing it to be garbled. Cc: Russell King Cc: Johan Rudholm Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/host/mmci.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index b93122636531..8324e3ec21c2 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -1144,16 +1144,17 @@ static irqreturn_t mmci_irq(int irq, void *dev_id) dev_dbg(mmc_dev(host->mmc), "irq0 (data+cmd) %08x\n", status); + cmd = host->cmd; + if (status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT|MCI_CMDSENT| + MCI_CMDRESPEND) && cmd) + mmci_cmd_irq(host, cmd, status); + data = host->data; if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_STARTBITERR| MCI_TXUNDERRUN|MCI_RXOVERRUN|MCI_DATAEND| MCI_DATABLOCKEND) && data) mmci_data_irq(host, data, status); - cmd = host->cmd; - if (status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT|MCI_CMDSENT|MCI_CMDRESPEND) && cmd) - mmci_cmd_irq(host, cmd, status); - ret = 1; } while (status); -- cgit v1.2.3 From 8d94b54d99ea968a9d188ca0e68793ebed601220 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 13 Jan 2014 16:49:31 +0100 Subject: mmc: mmci: Enable support for busy detection for ux500 variant The ux500 variants have HW busy detection support, which is indicated by the busy_detect flag. For these variants let's enable the MMC_CAP_WAIT_WHILE_BUSY flag and add the support for it. The mmc core will provide the RSP_BUSY command flag for those requests we should care about busy detection. Regarding the max_busy_timeout, the HW don't support busy detection timeouts so at this initial step let's make it simple and set it to zero to indicate we are able to support any timeout. Cc: Russell King Cc: Johan Rudholm Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/host/mmci.c | 51 +++++++++++++++++++++++++++++++++++++++++-------- drivers/mmc/host/mmci.h | 2 ++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 8324e3ec21c2..771c60ab4a32 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -921,6 +921,29 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd, { void __iomem *base = host->base; bool sbc = (cmd == host->mrq->sbc); + bool busy_resp = host->variant->busy_detect && + (cmd->flags & MMC_RSP_BUSY); + + /* Check if we need to wait for busy completion. */ + if (host->busy_status && (status & MCI_ST_CARDBUSY)) + return; + + /* Enable busy completion if needed and supported. */ + if (!host->busy_status && busy_resp && + !(status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT)) && + (readl(base + MMCISTATUS) & MCI_ST_CARDBUSY)) { + writel(readl(base + MMCIMASK0) | MCI_ST_BUSYEND, + base + MMCIMASK0); + host->busy_status = status & (MCI_CMDSENT|MCI_CMDRESPEND); + return; + } + + /* At busy completion, mask the IRQ and complete the request. */ + if (host->busy_status) { + writel(readl(base + MMCIMASK0) & ~MCI_ST_BUSYEND, + base + MMCIMASK0); + host->busy_status = 0; + } host->cmd = NULL; @@ -1139,14 +1162,19 @@ static irqreturn_t mmci_irq(int irq, void *dev_id) status &= ~MCI_IRQ1MASK; } + /* + * We intentionally clear the MCI_ST_CARDBUSY IRQ here (if it's + * enabled) since the HW seems to be triggering the IRQ on both + * edges while monitoring DAT0 for busy completion. + */ status &= readl(host->base + MMCIMASK0); writel(status, host->base + MMCICLEAR); dev_dbg(mmc_dev(host->mmc), "irq0 (data+cmd) %08x\n", status); cmd = host->cmd; - if (status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT|MCI_CMDSENT| - MCI_CMDRESPEND) && cmd) + if ((status|host->busy_status) & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT| + MCI_CMDSENT|MCI_CMDRESPEND) && cmd) mmci_cmd_irq(host, cmd, status); data = host->data; @@ -1155,6 +1183,10 @@ static irqreturn_t mmci_irq(int irq, void *dev_id) MCI_DATABLOCKEND) && data) mmci_data_irq(host, data, status); + /* Don't poll for busy completion in irq context. */ + if (host->busy_status) + status &= ~MCI_ST_CARDBUSY; + ret = 1; } while (status); @@ -1504,12 +1536,6 @@ static int mmci_probe(struct amba_device *dev, goto clk_disable; } - if (variant->busy_detect) { - mmci_ops.card_busy = mmci_card_busy; - mmci_write_datactrlreg(host, MCI_ST_DPSM_BUSYMODE); - } - - mmc->ops = &mmci_ops; /* * The ARM and ST versions of the block have slightly different * clock divider equations which means that the minimum divider @@ -1543,6 +1569,15 @@ static int mmci_probe(struct amba_device *dev, mmc->caps = plat->capabilities; mmc->caps2 = plat->capabilities2; + if (variant->busy_detect) { + mmci_ops.card_busy = mmci_card_busy; + mmci_write_datactrlreg(host, MCI_ST_DPSM_BUSYMODE); + mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY; + mmc->max_busy_timeout = 0; + } + + mmc->ops = &mmci_ops; + /* We support these PM capabilities. */ mmc->pm_caps = MMC_PM_KEEP_POWER; diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h index 168bc72f7a94..b008ace3e8ee 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -139,6 +139,7 @@ /* Extended status bits for the ST Micro variants */ #define MCI_ST_SDIOITMASK (1 << 22) #define MCI_ST_CEATAENDMASK (1 << 23) +#define MCI_ST_BUSYEND (1 << 24) #define MMCIMASK1 0x040 #define MMCIFIFOCNT 0x048 @@ -186,6 +187,7 @@ struct mmci_host { u32 pwr_reg; u32 clk_reg; u32 datactrl_reg; + u32 busy_status; bool vqmmc_enabled; struct mmci_platform_data *plat; struct variant_data *variant; -- cgit v1.2.3 From a6492c02073cd966ae490c1afcdebdd0d5ba3d81 Mon Sep 17 00:00:00 2001 From: Tim Kryger Date: Thu, 5 Dec 2013 11:20:41 -0800 Subject: mmc: sdhci-bcm-kona: Add basic use of clocks Enable the external clock needed by the host controller during the probe and disable it during the remove. Signed-off-by: Tim Kryger Reviewed-by: Markus Mayer Reviewed-by: Matt Porter Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-bcm-kona.c | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-bcm-kona.c b/drivers/mmc/host/sdhci-bcm-kona.c index 7a190fe4dff1..923eefabf801 100644 --- a/drivers/mmc/host/sdhci-bcm-kona.c +++ b/drivers/mmc/host/sdhci-bcm-kona.c @@ -54,6 +54,7 @@ struct sdhci_bcm_kona_dev { struct mutex write_lock; /* protect back to back writes */ + struct clk *external_clk; }; @@ -257,6 +258,24 @@ static int sdhci_bcm_kona_probe(struct platform_device *pdev) goto err_pltfm_free; } + /* Get and enable the external clock */ + kona_dev->external_clk = devm_clk_get(dev, NULL); + if (IS_ERR(kona_dev->external_clk)) { + dev_err(dev, "Failed to get external clock\n"); + ret = PTR_ERR(kona_dev->external_clk); + goto err_pltfm_free; + } + + if (clk_set_rate(kona_dev->external_clk, host->mmc->f_max) != 0) { + dev_err(dev, "Failed to set rate external clock\n"); + goto err_pltfm_free; + } + + if (clk_prepare_enable(kona_dev->external_clk) != 0) { + dev_err(dev, "Failed to enable external clock\n"); + goto err_pltfm_free; + } + dev_dbg(dev, "non-removable=%c\n", (host->mmc->caps & MMC_CAP_NONREMOVABLE) ? 'Y' : 'N'); dev_dbg(dev, "cd_gpio %c, wp_gpio %c\n", @@ -271,7 +290,7 @@ static int sdhci_bcm_kona_probe(struct platform_device *pdev) ret = sdhci_bcm_kona_sd_reset(host); if (ret) - goto err_pltfm_free; + goto err_clk_disable; sdhci_bcm_kona_sd_init(host); @@ -307,6 +326,9 @@ err_remove_host: err_reset: sdhci_bcm_kona_sd_reset(host); +err_clk_disable: + clk_disable_unprepare(kona_dev->external_clk); + err_pltfm_free: sdhci_pltfm_free(pdev); @@ -316,7 +338,18 @@ err_pltfm_free: static int __exit sdhci_bcm_kona_remove(struct platform_device *pdev) { - return sdhci_pltfm_unregister(pdev); + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host); + struct sdhci_bcm_kona_dev *kona_dev = sdhci_pltfm_priv(pltfm_priv); + int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); + + sdhci_remove_host(host, dead); + + clk_disable_unprepare(kona_dev->external_clk); + + sdhci_pltfm_free(pdev); + + return 0; } static struct platform_driver sdhci_bcm_kona_driver = { -- cgit v1.2.3 From c4c7fb19bbf1129da1c3b9fcad9713bd79f3207c Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:19 +0530 Subject: mmc: ushc: Fix incorrect parameter in sizeof sizeof should be of the parent structure type. Signed-off-by: Sachin Kamat Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/host/ushc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/ushc.c b/drivers/mmc/host/ushc.c index c0105a2e269a..d2c386f09d69 100644 --- a/drivers/mmc/host/ushc.c +++ b/drivers/mmc/host/ushc.c @@ -504,7 +504,7 @@ static int ushc_probe(struct usb_interface *intf, const struct usb_device_id *id ret = -ENOMEM; goto err; } - ushc->csw = kzalloc(sizeof(struct ushc_cbw), GFP_KERNEL); + ushc->csw = kzalloc(sizeof(struct ushc_csw), GFP_KERNEL); if (ushc->csw == NULL) { ret = -ENOMEM; goto err; -- cgit v1.2.3 From 43aaa50f17c38c16cce2a9a3e73909b7937892a3 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:20 +0530 Subject: mmc: wmt-sdmmc: Fix NULL pointer dereference 'of_id' is dereferenced before NULL pointer check. Move it to after the check. Signed-off-by: Sachin Kamat Acked-by: Tony Prisk Signed-off-by: Chris Ball --- drivers/mmc/host/wmt-sdmmc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/wmt-sdmmc.c b/drivers/mmc/host/wmt-sdmmc.c index e902ed7846b0..498d1f799085 100644 --- a/drivers/mmc/host/wmt-sdmmc.c +++ b/drivers/mmc/host/wmt-sdmmc.c @@ -757,7 +757,7 @@ static int wmt_mci_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; const struct of_device_id *of_id = of_match_device(wmt_mci_dt_ids, &pdev->dev); - const struct wmt_mci_caps *wmt_caps = of_id->data; + const struct wmt_mci_caps *wmt_caps; int ret; int regular_irq, dma_irq; @@ -766,6 +766,8 @@ static int wmt_mci_probe(struct platform_device *pdev) return -EFAULT; } + wmt_caps = of_id->data; + if (!np) { dev_err(&pdev->dev, "Missing SDMMC description in devicetree\n"); return -EFAULT; -- cgit v1.2.3 From eab36b2478aaed00ef381f4e855d4e0c14778a32 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:21 +0530 Subject: mmc: sdhci-spear: Fix NULL pointer dereference pdata could be NULL if cd_gpio = -1. Dereference pdata only if it is not NULL. Signed-off-by: Sachin Kamat Acked-by: Viresh Kumar Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-spear.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index 2dba9f8d1760..76ddd89a688e 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -84,14 +84,12 @@ static struct sdhci_plat_data *sdhci_probe_config_dt(struct platform_device *pde /* If pdata is required */ if (cd_gpio != -1) { pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) { + if (!pdata) dev_err(&pdev->dev, "DT: kzalloc failed\n"); - return ERR_PTR(-ENOMEM); - } + else + pdata->card_int_gpio = cd_gpio; } - pdata->card_int_gpio = cd_gpio; - return pdata; } #else -- cgit v1.2.3 From 6fad51284dee833aa981b026816be1c9f1314388 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:22 +0530 Subject: mmc: davinci: Remove redundant of_match_ptr The data structure of_match_ptr() protects is always compiled in. Hence of_match_ptr() is not needed. Signed-off-by: Sachin Kamat Signed-off-by: Chris Ball --- drivers/mmc/host/davinci_mmc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/davinci_mmc.c b/drivers/mmc/host/davinci_mmc.c index d6153740b77f..5d4c5e0fba2f 100644 --- a/drivers/mmc/host/davinci_mmc.c +++ b/drivers/mmc/host/davinci_mmc.c @@ -1192,7 +1192,7 @@ static struct davinci_mmc_config struct device_node *np; struct davinci_mmc_config *pdata = pdev->dev.platform_data; const struct of_device_id *match = - of_match_device(of_match_ptr(davinci_mmc_dt_ids), &pdev->dev); + of_match_device(davinci_mmc_dt_ids, &pdev->dev); u32 data; np = pdev->dev.of_node; @@ -1468,7 +1468,7 @@ static struct platform_driver davinci_mmcsd_driver = { .name = "davinci_mmc", .owner = THIS_MODULE, .pm = davinci_mmcsd_pm_ops, - .of_match_table = of_match_ptr(davinci_mmc_dt_ids), + .of_match_table = davinci_mmc_dt_ids, }, .remove = __exit_p(davinci_mmcsd_remove), .id_table = davinci_mmc_devtype, -- cgit v1.2.3 From cf109bc0e29c0f31904c636ea82e5273fe8afbd5 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:23 +0530 Subject: mmc: dw_mmc: Remove redundant of_match_ptr The data structure of_match_ptr() protects is always compiled in. Hence of_match_ptr() is not needed. Signed-off-by: Sachin Kamat Acked-by: Jaehoon Chung Acked-by: Seungwon Jeon Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc-pltfm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index 5c4965655297..2553bfbfc6df 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -123,7 +123,7 @@ static struct platform_driver dw_mci_pltfm_driver = { .remove = dw_mci_pltfm_remove, .driver = { .name = "dw_mmc", - .of_match_table = of_match_ptr(dw_mci_pltfm_match), + .of_match_table = dw_mci_pltfm_match, .pm = &dw_mci_pltfm_pmops, }, }; -- cgit v1.2.3 From 5941fd07594b111336b58976b488ee75ce6e474f Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:24 +0530 Subject: mmc: sdhci-dove: Remove redundant of_match_ptr The data structure of_match_ptr() protects is always compiled in. Hence of_match_ptr() is not needed. Signed-off-by: Sachin Kamat Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-dove.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-dove.c b/drivers/mmc/host/sdhci-dove.c index 8424839660f8..736d7a2eb7ec 100644 --- a/drivers/mmc/host/sdhci-dove.c +++ b/drivers/mmc/host/sdhci-dove.c @@ -208,7 +208,7 @@ static struct platform_driver sdhci_dove_driver = { .name = "sdhci-dove", .owner = THIS_MODULE, .pm = SDHCI_PLTFM_PMOPS, - .of_match_table = of_match_ptr(sdhci_dove_of_match_table), + .of_match_table = sdhci_dove_of_match_table, }, .probe = sdhci_dove_probe, .remove = sdhci_dove_remove, -- cgit v1.2.3 From 5532ec5137f2cfd16c2edda217ad4660ce0d4f2f Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:25 +0530 Subject: mmc: dw_mmc: Add missing description Commit 0976f16d ("mmc: dw_mmc: add support tuning scheme") introduced the execute_tuning hook but did not add its description for kernel docs. Update the same. Signed-off-by: Sachin Kamat Acked-by: Jaehoon Chung Acked-by: Seungwon Jeon Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 6bf24ab917e6..306451fbbfe1 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -244,6 +244,7 @@ struct dw_mci_tuning_data { * @prepare_command: handle CMD register extensions. * @set_ios: handle bus specific extensions. * @parse_dt: parse implementation specific device tree properties. + * @execute_tuning: implementation specific tuning procedure. * * Provide controller implementation specific extensions. The usage of this * data structure is fully optional and usage of each member in this structure -- cgit v1.2.3 From 2a5153aaae498f645573a13f60fed8226cc4e865 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:26 +0530 Subject: mmc: msm: Cleanup mmc-msm_sdcc.h header Commit 1ef21f6343ff ("ARM: msm: move platform_data definitions") moved the file to the current location but forgot to remove the pointer to its previous location. Clean it up. While at it also change the header file protection macros appropriately. Signed-off-by: Sachin Kamat Signed-off-by: Chris Ball --- include/linux/platform_data/mmc-msm_sdcc.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/include/linux/platform_data/mmc-msm_sdcc.h b/include/linux/platform_data/mmc-msm_sdcc.h index ffcd9e3a6a7e..55aa873c9396 100644 --- a/include/linux/platform_data/mmc-msm_sdcc.h +++ b/include/linux/platform_data/mmc-msm_sdcc.h @@ -1,8 +1,5 @@ -/* - * arch/arm/include/asm/mach/mmc.h - */ -#ifndef ASMARM_MACH_MMC_H -#define ASMARM_MACH_MMC_H +#ifndef __MMC_MSM_SDCC_H +#define __MMC_MSM_SDCC_H #include #include -- cgit v1.2.3 From 550459eeb21b8f3553283d506fdcb92c9147c1eb Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:27 +0530 Subject: mmc: mvsdio: Cleanup mmc-mvsdio.h header Commit c02cecb92ed4 ("ARM: orion: move platform_data definitions") moved the file to the current location but forgot to remove the pointer to its previous location. Clean it up. While at it also change the header file protection macros appropriately. Signed-off-by: Sachin Kamat Signed-off-by: Chris Ball --- include/linux/platform_data/mmc-mvsdio.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/linux/platform_data/mmc-mvsdio.h b/include/linux/platform_data/mmc-mvsdio.h index 1190efedcb94..d02704cd3695 100644 --- a/include/linux/platform_data/mmc-mvsdio.h +++ b/include/linux/platform_data/mmc-mvsdio.h @@ -1,13 +1,11 @@ /* - * arch/arm/plat-orion/include/plat/mvsdio.h - * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied. */ -#ifndef __MACH_MVSDIO_H -#define __MACH_MVSDIO_H +#ifndef __MMC_MVSDIO_H +#define __MMC_MVSDIO_H #include -- cgit v1.2.3 From 17c8bc85f27227e073ed8e59da39ff32f1bee873 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Tue, 25 Feb 2014 15:18:28 +0530 Subject: mmc: dw_mmc: Fix NULL pointer dereference If mrq->sbc is not NULL but data->stop happens to be NULL, it will lead to NULL pointer dereferencing. Avoid this by having a NULL check for data->stop. Signed-off-by: Sachin Kamat Acked-by: Seungwon Jeon Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 55cd110a49c4..0c56faa6730e 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1345,7 +1345,7 @@ static void dw_mci_tasklet_func(unsigned long priv) if (!err) { if (!data->stop || mrq->sbc) { - if (mrq->sbc) + if (mrq->sbc && data->stop) data->stop->error = 0; dw_mci_request_end(host, mrq); goto unlock; -- cgit v1.2.3 From 47a1f522d7ff5105c7efa56fe7fd41d46202f8cc Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Mon, 17 Feb 2014 20:31:00 -0600 Subject: mmc: dw_mmc-socfpga: Remove the SOCFPGA specific platform for dw_mmc It turns now that the only really platform specific code that is needed for SOCFPGA is using the SDMMC_CMD_USE_HOLD_REG in the prepare_command function. Since the Rockchip already has this functionality, re-use the code that is already in dw_mmc-pltfm.c. Signed-off-by: Dinh Nguyen Acked-by: Jaehoon Chung Acked-by: Steffen Trumtrar Tested-by: Steffen Trumtrar Acked-by: Seungwon Jeon Signed-off-by: Chris Ball --- drivers/mmc/host/Kconfig | 8 --- drivers/mmc/host/Makefile | 1 - drivers/mmc/host/dw_mmc-socfpga.c | 138 -------------------------------------- 3 files changed, 147 deletions(-) delete mode 100644 drivers/mmc/host/dw_mmc-socfpga.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 1384f67abe21..82cc34df1af7 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -580,14 +580,6 @@ config MMC_DW_EXYNOS Synopsys DesignWare Memory Card Interface driver. Select this option for platforms based on Exynos4 and Exynos5 SoC's. -config MMC_DW_SOCFPGA - tristate "SOCFPGA specific extensions for Synopsys DW Memory Card Interface" - depends on MMC_DW && MFD_SYSCON - select MMC_DW_PLTFM - help - This selects support for Altera SoCFPGA specific extensions to the - Synopsys DesignWare Memory Card Interface driver. - config MMC_DW_K3 tristate "K3 specific extensions for Synopsys DW Memory Card Interface" depends on MMC_DW diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 3483b6b6b880..f162f87a049c 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -43,7 +43,6 @@ obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_DW) += dw_mmc.o obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o -obj-$(CONFIG_MMC_DW_SOCFPGA) += dw_mmc-socfpga.o obj-$(CONFIG_MMC_DW_K3) += dw_mmc-k3.o obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o diff --git a/drivers/mmc/host/dw_mmc-socfpga.c b/drivers/mmc/host/dw_mmc-socfpga.c deleted file mode 100644 index 3e8e53ae3302..000000000000 --- a/drivers/mmc/host/dw_mmc-socfpga.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Altera SoCFPGA Specific Extensions for Synopsys DW Multimedia Card Interface - * driver - * - * Copyright (C) 2012, Samsung Electronics Co., Ltd. - * Copyright (C) 2013 Altera Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Taken from dw_mmc-exynos.c - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dw_mmc.h" -#include "dw_mmc-pltfm.h" - -#define SYSMGR_SDMMCGRP_CTRL_OFFSET 0x108 -#define DRV_CLK_PHASE_SHIFT_SEL_MASK 0x7 -#define SYSMGR_SDMMC_CTRL_SET(smplsel, drvsel) \ - ((((smplsel) & 0x7) << 3) | (((drvsel) & 0x7) << 0)) - -/* SOCFPGA implementation specific driver private data */ -struct dw_mci_socfpga_priv_data { - u8 ciu_div; /* card interface unit divisor */ - u32 hs_timing; /* bitmask for CIU clock phase shift */ - struct regmap *sysreg; /* regmap for system manager register */ -}; - -static int dw_mci_socfpga_priv_init(struct dw_mci *host) -{ - return 0; -} - -static int dw_mci_socfpga_setup_clock(struct dw_mci *host) -{ - struct dw_mci_socfpga_priv_data *priv = host->priv; - - clk_disable_unprepare(host->ciu_clk); - regmap_write(priv->sysreg, SYSMGR_SDMMCGRP_CTRL_OFFSET, - priv->hs_timing); - clk_prepare_enable(host->ciu_clk); - - host->bus_hz /= (priv->ciu_div + 1); - return 0; -} - -static void dw_mci_socfpga_prepare_command(struct dw_mci *host, u32 *cmdr) -{ - struct dw_mci_socfpga_priv_data *priv = host->priv; - - if (priv->hs_timing & DRV_CLK_PHASE_SHIFT_SEL_MASK) - *cmdr |= SDMMC_CMD_USE_HOLD_REG; -} - -static int dw_mci_socfpga_parse_dt(struct dw_mci *host) -{ - struct dw_mci_socfpga_priv_data *priv; - struct device_node *np = host->dev->of_node; - u32 timing[2]; - u32 div = 0; - int ret; - - priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) { - dev_err(host->dev, "mem alloc failed for private data\n"); - return -ENOMEM; - } - - priv->sysreg = syscon_regmap_lookup_by_compatible("altr,sys-mgr"); - if (IS_ERR(priv->sysreg)) { - dev_err(host->dev, "regmap for altr,sys-mgr lookup failed.\n"); - return PTR_ERR(priv->sysreg); - } - - ret = of_property_read_u32(np, "altr,dw-mshc-ciu-div", &div); - if (ret) - dev_info(host->dev, "No dw-mshc-ciu-div specified, assuming 1"); - priv->ciu_div = div; - - ret = of_property_read_u32_array(np, - "altr,dw-mshc-sdr-timing", timing, 2); - if (ret) - return ret; - - priv->hs_timing = SYSMGR_SDMMC_CTRL_SET(timing[0], timing[1]); - host->priv = priv; - return 0; -} - -static const struct dw_mci_drv_data socfpga_drv_data = { - .init = dw_mci_socfpga_priv_init, - .setup_clock = dw_mci_socfpga_setup_clock, - .prepare_command = dw_mci_socfpga_prepare_command, - .parse_dt = dw_mci_socfpga_parse_dt, -}; - -static const struct of_device_id dw_mci_socfpga_match[] = { - { .compatible = "altr,socfpga-dw-mshc", - .data = &socfpga_drv_data, }, - {}, -}; -MODULE_DEVICE_TABLE(of, dw_mci_socfpga_match); - -static int dw_mci_socfpga_probe(struct platform_device *pdev) -{ - const struct dw_mci_drv_data *drv_data; - const struct of_device_id *match; - - match = of_match_node(dw_mci_socfpga_match, pdev->dev.of_node); - drv_data = match->data; - return dw_mci_pltfm_register(pdev, drv_data); -} - -static struct platform_driver dw_mci_socfpga_pltfm_driver = { - .probe = dw_mci_socfpga_probe, - .remove = __exit_p(dw_mci_pltfm_remove), - .driver = { - .name = "dwmmc_socfpga", - .of_match_table = dw_mci_socfpga_match, - .pm = &dw_mci_pltfm_pmops, - }, -}; - -module_platform_driver(dw_mci_socfpga_pltfm_driver); - -MODULE_DESCRIPTION("Altera SOCFPGA Specific DW-MSHC Driver Extension"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:dwmmc-socfpga"); -- cgit v1.2.3 From ec1e5d703e4a75fb19e99a92d20b4b70861bf483 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Mon, 17 Feb 2014 20:31:01 -0600 Subject: mmc: dw_mmc: Add support for SOCFPGA's platform specific implementation Like the rockchip, Altera's SOCFPGA platform specific implementation of the dw_mmc driver requires using the HOLD register for SD commands. This patch renames dw_mci_rockchip_prepare_command to dw_mci_pltfm_prepare_command so that SOCFPGA and Rockchip can use it. Signed-off-by: Dinh Nguyen Acked-by: Steffen Trumtrar Tested-by: Steffen Trumtrar Acked-by: Seungwon Jeon Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc-pltfm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index 2553bfbfc6df..d4a47a9f5584 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -25,13 +25,17 @@ #include "dw_mmc.h" #include "dw_mmc-pltfm.h" -static void dw_mci_rockchip_prepare_command(struct dw_mci *host, u32 *cmdr) +static void dw_mci_pltfm_prepare_command(struct dw_mci *host, u32 *cmdr) { *cmdr |= SDMMC_CMD_USE_HOLD_REG; } static const struct dw_mci_drv_data rockchip_drv_data = { - .prepare_command = dw_mci_rockchip_prepare_command, + .prepare_command = dw_mci_pltfm_prepare_command, +}; + +static const struct dw_mci_drv_data socfpga_drv_data = { + .prepare_command = dw_mci_pltfm_prepare_command, }; int dw_mci_pltfm_register(struct platform_device *pdev, @@ -92,6 +96,8 @@ static const struct of_device_id dw_mci_pltfm_match[] = { { .compatible = "snps,dw-mshc", }, { .compatible = "rockchip,rk2928-dw-mshc", .data = &rockchip_drv_data }, + { .compatible = "altr,socfpga-dw-mshc", + .data = &socfpga_drv_data }, {}, }; MODULE_DEVICE_TABLE(of, dw_mci_pltfm_match); -- cgit v1.2.3 From d9c3f5df539a8a74cc830b35838670fe0541fed1 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Mon, 17 Feb 2014 20:31:02 -0600 Subject: dts: socfpga: Add support for SD/MMC on the SOCFPGA platform Introduce "altr,socfpga-dw-mshc" to enable Altera's SOCFPGA platform specific implementation of the dw_mmc driver. Also add the "syscon" binding to the "altr,sys-mgr" node. The clock driver can use the syscon driver to toggle the register for the SD/MMC clock phase shift settings. Finally, fix an indentation error for the sysmgr node. Signed-off-by: Dinh Nguyen Acked-by: Steffen Trumtrar Tested-by: Steffen Trumtrar Signed-off-by: Chris Ball --- .../devicetree/bindings/mmc/socfpga-dw-mshc.txt | 23 ++++++++++++++++++++++ arch/arm/boot/dts/socfpga.dtsi | 17 +++++++++++++--- arch/arm/boot/dts/socfpga_arria5.dtsi | 11 +++++++++++ arch/arm/boot/dts/socfpga_cyclone5.dtsi | 11 +++++++++++ arch/arm/boot/dts/socfpga_vt.dts | 11 +++++++++++ 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 Documentation/devicetree/bindings/mmc/socfpga-dw-mshc.txt diff --git a/Documentation/devicetree/bindings/mmc/socfpga-dw-mshc.txt b/Documentation/devicetree/bindings/mmc/socfpga-dw-mshc.txt new file mode 100644 index 000000000000..4897bea7e3f8 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/socfpga-dw-mshc.txt @@ -0,0 +1,23 @@ +* Altera SOCFPGA specific extensions to the Synopsys Designware Mobile + Storage Host Controller + +The Synopsys designware mobile storage host controller is used to interface +a SoC with storage medium such as eMMC or SD/MMC cards. This file documents +differences between the core Synopsys dw mshc controller properties described +by synopsys-dw-mshc.txt and the properties used by the Altera SOCFPGA specific +extensions to the Synopsys Designware Mobile Storage Host Controller. + +Required Properties: + +* compatible: should be + - "altr,socfpga-dw-mshc": for Altera's SOCFPGA platform + +Example: + + mmc: dwmmc0@ff704000 { + compatible = "altr,socfpga-dw-mshc"; + reg = <0xff704000 0x1000>; + interrupts = <0 129 4>; + #address-cells = <1>; + #size-cells = <0>; + }; diff --git a/arch/arm/boot/dts/socfpga.dtsi b/arch/arm/boot/dts/socfpga.dtsi index 537f1a5c07f5..5f582467fccc 100644 --- a/arch/arm/boot/dts/socfpga.dtsi +++ b/arch/arm/boot/dts/socfpga.dtsi @@ -473,6 +473,17 @@ arm,data-latency = <2 1 1>; }; + mmc: dwmmc0@ff704000 { + compatible = "altr,socfpga-dw-mshc"; + reg = <0xff704000 0x1000>; + interrupts = <0 139 4>; + fifo-depth = <0x400>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&l4_mp_clk>, <&sdmmc_clk>; + clock-names = "biu", "ciu"; + }; + /* Local timer */ timer@fffec600 { compatible = "arm,cortex-a9-twd-timer"; @@ -527,8 +538,8 @@ }; sysmgr@ffd08000 { - compatible = "altr,sys-mgr"; - reg = <0xffd08000 0x4000>; - }; + compatible = "altr,sys-mgr", "syscon"; + reg = <0xffd08000 0x4000>; + }; }; }; diff --git a/arch/arm/boot/dts/socfpga_arria5.dtsi b/arch/arm/boot/dts/socfpga_arria5.dtsi index a85b4043f888..6c87b7070ca7 100644 --- a/arch/arm/boot/dts/socfpga_arria5.dtsi +++ b/arch/arm/boot/dts/socfpga_arria5.dtsi @@ -27,6 +27,17 @@ }; }; + dwmmc0@ff704000 { + num-slots = <1>; + supports-highspeed; + broken-cd; + + slot@0 { + reg = <0>; + bus-width = <4>; + }; + }; + serial0@ffc02000 { clock-frequency = <100000000>; }; diff --git a/arch/arm/boot/dts/socfpga_cyclone5.dtsi b/arch/arm/boot/dts/socfpga_cyclone5.dtsi index a8716f6dbe2e..ca41b0ebf461 100644 --- a/arch/arm/boot/dts/socfpga_cyclone5.dtsi +++ b/arch/arm/boot/dts/socfpga_cyclone5.dtsi @@ -28,6 +28,17 @@ }; }; + dwmmc0@ff704000 { + num-slots = <1>; + supports-highspeed; + broken-cd; + + slot@0 { + reg = <0>; + bus-width = <4>; + }; + }; + ethernet@ff702000 { phy-mode = "rgmii"; phy-addr = <0xffffffff>; /* probe for phy addr */ diff --git a/arch/arm/boot/dts/socfpga_vt.dts b/arch/arm/boot/dts/socfpga_vt.dts index d1ec0cab2dee..222313f5420b 100644 --- a/arch/arm/boot/dts/socfpga_vt.dts +++ b/arch/arm/boot/dts/socfpga_vt.dts @@ -41,6 +41,17 @@ }; }; + dwmmc0@ff704000 { + num-slots = <1>; + supports-highspeed; + broken-cd; + + slot@0 { + reg = <0>; + bus-width = <4>; + }; + }; + ethernet@ff700000 { phy-mode = "gmii"; status = "okay"; -- cgit v1.2.3 From 8880a4a526340335403696f30daf4e05a413f3bd Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Sat, 11 Jan 2014 22:39:01 +0100 Subject: mmc: sdhci-s3c: Use shifts to divide by powers of two Current implementation of sdhci_s3c_consider_clock() is highly inefficient due to multiple integer divisions by variable performed in a loop. Since only divisors that are powers of two are considered, this patch replaces them with respective shifts, removing all the integer divisions. Signed-off-by: Tomasz Figa Tested-by: Heiko Stuebner Acked-by: Heiko Stuebner Tested-by: Jaehoon Chung Acked-by; Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-s3c.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 6debda952155..52770d58cc47 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -144,7 +144,7 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, { unsigned long rate; struct clk *clksrc = ourhost->clk_bus[src]; - int div; + int shift; if (!clksrc) return UINT_MAX; @@ -160,15 +160,15 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, rate = clk_get_rate(clksrc); - for (div = 1; div < 256; div *= 2) { - if ((rate / div) <= wanted) + for (shift = 0; shift < 8; ++shift) { + if ((rate >> shift) <= wanted) break; } dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n", - src, rate, wanted, rate / div); + src, rate, wanted, rate >> shift); - return wanted - (rate / div); + return wanted - (rate >> shift); } /** -- cgit v1.2.3 From 6eb28bdcb27249ca9981fb063d3f2485fd344500 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Sat, 11 Jan 2014 22:39:02 +0100 Subject: mmc: sdhci-s3c: Cache bus clock rates To fix scheduling while atomic happening in sdhci_s3c_set_clock() caused by calling clk_get_rate() that might sleep, this patch modifies the driver to cache rates of all bus clocks at probe time and then only use those cache values. Signed-off-by: Tomasz Figa Tested-by: Heiko Stuebner Acked-by: Heiko Stuebner Tested-by: Jaehoon Chung Acked-by; Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-s3c.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 52770d58cc47..9b783915b02a 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -57,6 +57,7 @@ struct sdhci_s3c { struct clk *clk_io; struct clk *clk_bus[MAX_BUS_CLK]; + unsigned long clk_rates[MAX_BUS_CLK]; }; /** @@ -158,7 +159,7 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, return wanted - rate; } - rate = clk_get_rate(clksrc); + rate = ourhost->clk_rates[src]; for (shift = 0; shift < 8; ++shift) { if ((rate >> shift) <= wanted) @@ -215,7 +216,7 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); ourhost->cur_clk = best_src; - host->max_clk = clk_get_rate(clk); + host->max_clk = ourhost->clk_rates[best_src]; ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; @@ -583,8 +584,10 @@ static int sdhci_s3c_probe(struct platform_device *pdev) */ sc->cur_clk = ptr; + sc->clk_rates[ptr] = clk_get_rate(sc->clk_bus[ptr]); + dev_info(dev, "clock source %d: %s (%ld Hz)\n", - ptr, name, clk_get_rate(clk)); + ptr, name, sc->clk_rates[ptr]); } if (clks == 0) { -- cgit v1.2.3 From 8f4b78d9bb042aacce43e0213c727da861a128f6 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Sat, 11 Jan 2014 22:39:03 +0100 Subject: mmc: sdhci-s3c: Use correct condition to check for clock presence IS_ERR() must be used to make sure that not a valid clock was returned by clk_get() and company. Signed-off-by: Tomasz Figa Tested-by: Heiko Stuebner Acked-by: Heiko Stuebner Tested-by: Jaehoon Chung Acked-by; Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-s3c.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 9b783915b02a..7fde938facb0 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -147,7 +147,7 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, struct clk *clksrc = ourhost->clk_bus[src]; int shift; - if (!clksrc) + if (IS_ERR(clksrc)) return UINT_MAX; /* @@ -567,16 +567,14 @@ static int sdhci_s3c_probe(struct platform_device *pdev) clk_prepare_enable(sc->clk_io); for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) { - struct clk *clk; char name[14]; snprintf(name, 14, "mmc_busclk.%d", ptr); - clk = devm_clk_get(dev, name); - if (IS_ERR(clk)) + sc->clk_bus[ptr] = devm_clk_get(dev, name); + if (IS_ERR(sc->clk_bus[ptr])) continue; clks++; - sc->clk_bus[ptr] = clk; /* * save current clock index to know which clock bus -- cgit v1.2.3 From 222a13c5d0c67333e443a1ea2fcc746ad61f8d68 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Sat, 11 Jan 2014 22:39:04 +0100 Subject: mmc: sdhci-s3c: Simplify min/max clock calculation This patch reimplements functions calculating minimum and maximum clock rates to leverage clock rate cache introduced by previous patches. In addition, the calculation is simplified to just comparing input clock rates (max case) or input clock rates divided by maximum divisor (min case), which is basically what the original code did, but with much more unnecessary work. Signed-off-by: Tomasz Figa Tested-by: Heiko Stuebner Acked-by: Heiko Stuebner Tested-by: Jaehoon Chung Acked-by; Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-s3c.c | 60 +++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 7fde938facb0..7e14db07eb30 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -112,20 +112,16 @@ static void sdhci_s3c_check_sclk(struct sdhci_host *host) static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host) { struct sdhci_s3c *ourhost = to_s3c(host); - struct clk *busclk; - unsigned int rate, max; - int clk; + unsigned long rate, max = 0; + int src; /* note, a reset will reset the clock source */ sdhci_s3c_check_sclk(host); - for (max = 0, clk = 0; clk < MAX_BUS_CLK; clk++) { - busclk = ourhost->clk_bus[clk]; - if (!busclk) - continue; - rate = clk_get_rate(busclk); + for (src = 0; src < MAX_BUS_CLK; src++) { + rate = ourhost->clk_rates[src]; if (rate > max) max = rate; } @@ -255,17 +251,17 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) static unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host) { struct sdhci_s3c *ourhost = to_s3c(host); - unsigned int delta, min = UINT_MAX; + unsigned long rate, min = ULONG_MAX; int src; for (src = 0; src < MAX_BUS_CLK; src++) { - delta = sdhci_s3c_consider_clock(ourhost, src, 0); - if (delta == UINT_MAX) + rate = ourhost->clk_rates[src] / 256; + if (!rate) continue; - /* delta is a negative value in this case */ - if (-delta < min) - min = -delta; + if (rate < min) + min = rate; } + return min; } @@ -273,20 +269,44 @@ static unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host) static unsigned int sdhci_cmu_get_max_clock(struct sdhci_host *host) { struct sdhci_s3c *ourhost = to_s3c(host); + unsigned long rate, max = 0; + int src; + + for (src = 0; src < MAX_BUS_CLK; src++) { + struct clk *clk; + + clk = ourhost->clk_bus[src]; + if (IS_ERR(clk)) + continue; + + rate = clk_round_rate(clk, ULONG_MAX); + if (rate > max) + max = rate; + } - return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], UINT_MAX); + return max; } /* sdhci_cmu_get_min_clock - callback to get minimal supported clock value. */ static unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host) { struct sdhci_s3c *ourhost = to_s3c(host); + unsigned long rate, min = ULONG_MAX; + int src; - /* - * initial clock can be in the frequency range of - * 100KHz-400KHz, so we set it as max value. - */ - return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], 400000); + for (src = 0; src < MAX_BUS_CLK; src++) { + struct clk *clk; + + clk = ourhost->clk_bus[src]; + if (IS_ERR(clk)) + continue; + + rate = clk_round_rate(clk, 0); + if (rate < min) + min = rate; + } + + return min; } /* sdhci_cmu_set_clock - callback on clock change.*/ -- cgit v1.2.3 From 3ac147facfbd234acf3a990968de9746a399f133 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Sat, 11 Jan 2014 22:39:05 +0100 Subject: mmc: sdhci-s3c: Fix handling of bus clock switching Currently the driver assumes at probe that controller is configured for last valid enumerated bus clock. This assumption is completely wrong, as there is no way to ensure such configuration until the hardware gets first configured (by calling sdhci_s3c_set_clock()). This patch modifies the driver to set current clock at probe to unknown state (represented by negative value) and make sure that the hardware gets actually configured to selected clock in sdhci_s3c_set_clock(). Signed-off-by: Tomasz Figa Tested-by: Heiko Stuebner Acked-by: Heiko Stuebner Tested-by: Jaehoon Chung Acked-by; Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-s3c.c | 78 ++++++++++---------------------------------- 1 file changed, 17 insertions(+), 61 deletions(-) diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 7e14db07eb30..bad0e00a3e8f 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -51,7 +51,7 @@ struct sdhci_s3c { struct platform_device *pdev; struct resource *ioarea; struct s3c_sdhci_platdata *pdata; - unsigned int cur_clk; + int cur_clk; int ext_cd_irq; int ext_cd_gpio; @@ -77,32 +77,6 @@ static inline struct sdhci_s3c *to_s3c(struct sdhci_host *host) return sdhci_priv(host); } -/** - * get_curclk - convert ctrl2 register to clock source number - * @ctrl2: Control2 register value. - */ -static u32 get_curclk(u32 ctrl2) -{ - ctrl2 &= S3C_SDHCI_CTRL2_SELBASECLK_MASK; - ctrl2 >>= S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; - - return ctrl2; -} - -static void sdhci_s3c_check_sclk(struct sdhci_host *host) -{ - struct sdhci_s3c *ourhost = to_s3c(host); - u32 tmp = readl(host->ioaddr + S3C_SDHCI_CONTROL2); - - if (get_curclk(tmp) != ourhost->cur_clk) { - dev_dbg(&ourhost->pdev->dev, "restored ctrl2 clock setting\n"); - - tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; - tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; - writel(tmp, host->ioaddr + S3C_SDHCI_CONTROL2); - } -} - /** * sdhci_s3c_get_max_clk - callback to get maximum clock frequency. * @host: The SDHCI host instance. @@ -115,11 +89,6 @@ static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host) unsigned long rate, max = 0; int src; - /* note, a reset will reset the clock source */ - - sdhci_s3c_check_sclk(host); - - for (src = 0; src < MAX_BUS_CLK; src++) { rate = ourhost->clk_rates[src]; if (rate > max) @@ -206,20 +175,22 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) struct clk *clk = ourhost->clk_bus[best_src]; clk_prepare_enable(clk); - clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]); - - /* turn clock off to card before changing clock source */ - writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); + if (ourhost->cur_clk >= 0) + clk_disable_unprepare( + ourhost->clk_bus[ourhost->cur_clk]); ourhost->cur_clk = best_src; host->max_clk = ourhost->clk_rates[best_src]; - - ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); - ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; - ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; - writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); } + /* turn clock off to card before changing clock source */ + writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); + + ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); + ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; + ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; + writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); + /* reprogram default hardware configuration */ writel(S3C64XX_SDHCI_CONTROL4_DRIVE_9mA, host->ioaddr + S3C64XX_SDHCI_CONTROL4); @@ -573,6 +544,7 @@ static int sdhci_s3c_probe(struct platform_device *pdev) sc->host = host; sc->pdev = pdev; sc->pdata = pdata; + sc->cur_clk = -1; platform_set_drvdata(pdev, host); @@ -595,13 +567,6 @@ static int sdhci_s3c_probe(struct platform_device *pdev) continue; clks++; - - /* - * save current clock index to know which clock bus - * is used later in overriding functions. - */ - sc->cur_clk = ptr; - sc->clk_rates[ptr] = clk_get_rate(sc->clk_bus[ptr]); dev_info(dev, "clock source %d: %s (%ld Hz)\n", @@ -614,10 +579,6 @@ static int sdhci_s3c_probe(struct platform_device *pdev) goto err_no_busclks; } -#ifndef CONFIG_PM_RUNTIME - clk_prepare_enable(sc->clk_bus[sc->cur_clk]); -#endif - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); host->ioaddr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(host->ioaddr)) { @@ -730,10 +691,6 @@ static int sdhci_s3c_probe(struct platform_device *pdev) return 0; err_req_regs: -#ifndef CONFIG_PM_RUNTIME - clk_disable_unprepare(sc->clk_bus[sc->cur_clk]); -#endif - err_no_busclks: clk_disable_unprepare(sc->clk_io); @@ -764,9 +721,6 @@ static int sdhci_s3c_remove(struct platform_device *pdev) pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_disable(&pdev->dev); -#ifndef CONFIG_PM_RUNTIME - clk_disable_unprepare(sc->clk_bus[sc->cur_clk]); -#endif clk_disable_unprepare(sc->clk_io); sdhci_free_host(host); @@ -800,7 +754,8 @@ static int sdhci_s3c_runtime_suspend(struct device *dev) ret = sdhci_runtime_suspend_host(host); - clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]); + if (ourhost->cur_clk >= 0) + clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]); clk_disable_unprepare(busclk); return ret; } @@ -813,7 +768,8 @@ static int sdhci_s3c_runtime_resume(struct device *dev) int ret; clk_prepare_enable(busclk); - clk_prepare_enable(ourhost->clk_bus[ourhost->cur_clk]); + if (ourhost->cur_clk >= 0) + clk_prepare_enable(ourhost->clk_bus[ourhost->cur_clk]); ret = sdhci_runtime_resume_host(host); return ret; } -- cgit v1.2.3 From 2200300060ada5e32136d597502e446ac2ef2a27 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Sat, 11 Jan 2014 22:39:06 +0100 Subject: mmc: sdhci-s3c: Do not allow frequencies higher than requested This patch modifies sdhci_s3c_consider_clock() to fail if bus clock being considered can not provide frequency lower or equal requested, instead of returning the lowest supported. Signed-off-by: Tomasz Figa Tested-by: Heiko Stuebner Acked-by: Heiko Stuebner Tested-by: Jaehoon Chung Acked-by; Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-s3c.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index bad0e00a3e8f..d61eb5a70833 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -126,11 +126,18 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, rate = ourhost->clk_rates[src]; - for (shift = 0; shift < 8; ++shift) { + for (shift = 0; shift <= 8; ++shift) { if ((rate >> shift) <= wanted) break; } + if (shift > 8) { + dev_dbg(&ourhost->pdev->dev, + "clk %d: rate %ld, min rate %lu > wanted %u\n", + src, rate, rate / 256, wanted); + return UINT_MAX; + } + dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n", src, rate, wanted, rate >> shift); -- cgit v1.2.3 From 370aede6a18a41e01f2668108d847b598443fbc0 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 25 Feb 2014 08:57:44 -0600 Subject: mmc: dw_mmc: fix possible build error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the following build errors: drivers/mmc/host/dw_mmc-k3.c: In function ‘dw_mci_k3_suspend’: drivers/mmc/host/dw_mmc-k3.c:58:2: error: implicit declaration of function ‘dw_mci_suspend’ [-Werror=implicit-function-declaration] ret = dw_mci_suspend(host); ^ drivers/mmc/host/dw_mmc-k3.c: In function ‘dw_mci_k3_resume’: drivers/mmc/host/dw_mmc-k3.c:76:2: error: implicit declaration of function ‘dw_mci_resume’ [-Werror=implicit-function-declaration] return dw_mci_resume(host); ^ drivers/mmc/host/dw_mmc-k3.c: At top level: drivers/mmc/host/dw_mmc-k3.c:53:12: warning: ‘dw_mci_k3_suspend’ defined but not used [-Wunused-function] static int dw_mci_k3_suspend(struct device *dev) ^ drivers/mmc/host/dw_mmc-k3.c:65:12: warning: ‘dw_mci_k3_resume’ defined but not used [-Wunused-function] static int dw_mci_k3_resume(struct device *dev) ^ Signed-off-by: Felipe Balbi Acked-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc-k3.c | 2 ++ drivers/mmc/host/dw_mmc.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/dw_mmc-k3.c b/drivers/mmc/host/dw_mmc-k3.c index f567c219cff4..650f9cc3f7a6 100644 --- a/drivers/mmc/host/dw_mmc-k3.c +++ b/drivers/mmc/host/dw_mmc-k3.c @@ -50,6 +50,7 @@ static int dw_mci_k3_probe(struct platform_device *pdev) return dw_mci_pltfm_register(pdev, drv_data); } +#ifdef CONFIG_PM_SLEEP static int dw_mci_k3_suspend(struct device *dev) { struct dw_mci *host = dev_get_drvdata(dev); @@ -75,6 +76,7 @@ static int dw_mci_k3_resume(struct device *dev) return dw_mci_resume(host); } +#endif /* CONFIG_PM_SLEEP */ static SIMPLE_DEV_PM_OPS(dw_mci_k3_pmops, dw_mci_k3_suspend, dw_mci_k3_resume); diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 306451fbbfe1..68349779c396 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -185,7 +185,7 @@ extern int dw_mci_probe(struct dw_mci *host); extern void dw_mci_remove(struct dw_mci *host); -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP extern int dw_mci_suspend(struct dw_mci *host); extern int dw_mci_resume(struct dw_mci *host); #endif -- cgit v1.2.3 From b38313d627e29d7dfc4aed242fc007806a85d3f0 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 30 Jan 2014 15:15:18 +0300 Subject: mmc: omap_hsmmc: remove a duplicative test Static checkers complain that testing for both "next" and "!next" is duplicative. Signed-off-by: Dan Carpenter Acked-by: Balaji T K Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index dbd32ad3b749..4ff906cfd23c 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -1236,8 +1236,7 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host, } /* Check if next job is already prepared */ - if (next || - (!next && data->host_cookie != host->next_data.cookie)) { + if (next || data->host_cookie != host->next_data.cookie) { dma_len = dma_map_sg(chan->device->dev, data->sg, data->sg_len, omap_hsmmc_get_dma_dir(host, data)); -- cgit v1.2.3 From 59445b10d08144d870d8340ce25634014bf972d5 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Thu, 13 Feb 2014 23:45:48 -0600 Subject: mmc: omap_hsmmc: Add support for quirky omap3 hsmmc controller When device is booted using devicetree, platforms impacted by Erratum 2.1.1.128 is not detected easily in the mmc driver. This erratum indicates that the module cannot do multi-block transfers. Platforms such as LDP which use OMAP3 ES revision prior to ES3.0 are impacted by this. Provide a new compatible property "ti,omap3-pre-es3-hsmmc" to allow driver to determine if driver needs to implement quirks associated with the specific module version (primarily because the IP revision information is not sufficient for the same). Signed-off-by: Nishanth Menon Acked-by: Tony Lindgren Acked-by: Balaji T K Signed-off-by: Chris Ball --- .../devicetree/bindings/mmc/ti-omap-hsmmc.txt | 1 + drivers/mmc/host/omap_hsmmc.c | 26 ++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt index 8c8908ab84ba..ce8056116fb0 100644 --- a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt +++ b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt @@ -10,6 +10,7 @@ Required properties: - compatible: Should be "ti,omap2-hsmmc", for OMAP2 controllers Should be "ti,omap3-hsmmc", for OMAP3 controllers + Should be "ti,omap3-pre-es3-hsmmc" for OMAP3 controllers pre ES3.0 Should be "ti,omap4-hsmmc", for OMAP4 controllers - ti,hwmods: Must be "mmc", n is controller instance starting 1 diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 4ff906cfd23c..b1ac26a76306 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -192,6 +192,11 @@ struct omap_hsmmc_host { struct omap_mmc_platform_data *pdata; }; +struct omap_mmc_of_data { + u32 reg_offset; + u8 controller_flags; +}; + static int omap_hsmmc_card_detect(struct device *dev, int slot) { struct omap_hsmmc_host *host = dev_get_drvdata(dev); @@ -1677,18 +1682,29 @@ static void omap_hsmmc_debugfs(struct mmc_host *mmc) #endif #ifdef CONFIG_OF -static u16 omap4_reg_offset = 0x100; +static const struct omap_mmc_of_data omap3_pre_es3_mmc_of_data = { + /* See 35xx errata 2.1.1.128 in SPRZ278F */ + .controller_flags = OMAP_HSMMC_BROKEN_MULTIBLOCK_READ, +}; + +static const struct omap_mmc_of_data omap4_mmc_of_data = { + .reg_offset = 0x100, +}; static const struct of_device_id omap_mmc_of_match[] = { { .compatible = "ti,omap2-hsmmc", }, + { + .compatible = "ti,omap3-pre-es3-hsmmc", + .data = &omap3_pre_es3_mmc_of_data, + }, { .compatible = "ti,omap3-hsmmc", }, { .compatible = "ti,omap4-hsmmc", - .data = &omap4_reg_offset, + .data = &omap4_mmc_of_data, }, {}, }; @@ -1758,6 +1774,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev) dma_cap_mask_t mask; unsigned tx_req, rx_req; struct pinctrl *pinctrl; + const struct omap_mmc_of_data *data; match = of_match_device(of_match_ptr(omap_mmc_of_match), &pdev->dev); if (match) { @@ -1767,8 +1784,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev) return PTR_ERR(pdata); if (match->data) { - const u16 *offsetp = match->data; - pdata->reg_offset = *offsetp; + data = match->data; + pdata->reg_offset = data->reg_offset; + pdata->controller_flags |= data->controller_flags; } } -- cgit v1.2.3 From a8d6fb5de83bc49f8feccdc34a8663978f74820c Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Thu, 13 Feb 2014 23:45:49 -0600 Subject: ARM: dts: omap3-ldp: fix mmc configuration MMC1 is the only MMC interface available on the platform. Further, since the platform is based on older revision of SoC which is not capable of doing multi-block reads, mark it with compatibility for the same and add pinmux to ensure that all relevant pins are configured for non-MMC boot mode. Signed-off-by: Nishanth Menon Acked-by: Tony Lindgren Acked-by: Balaji T K Signed-off-by: Chris Ball --- arch/arm/boot/dts/omap3-ldp.dts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/arch/arm/boot/dts/omap3-ldp.dts b/arch/arm/boot/dts/omap3-ldp.dts index ddce0d807f70..0abe986a4ecc 100644 --- a/arch/arm/boot/dts/omap3-ldp.dts +++ b/arch/arm/boot/dts/omap3-ldp.dts @@ -174,8 +174,20 @@ }; &mmc1 { + /* See 35xx errata 2.1.1.128 in SPRZ278F */ + compatible = "ti,omap3-pre-es3-hsmmc"; vmmc-supply = <&vmmc1>; bus-width = <4>; + pinctrl-names = "default"; + pinctrl-0 = <&mmc1_pins>; +}; + +&mmc2 { + status="disabled"; +}; + +&mmc3 { + status="disabled"; }; &omap3_pmx_core { @@ -209,6 +221,17 @@ 0x174 (PIN_OUTPUT | MUX_MODE0) /* hsusb0_stp.hsusb0_stp */ >; }; + + mmc1_pins: pinmux_mmc1_pins { + pinctrl-single,pins = < + OMAP3_CORE1_IOPAD(0x2144, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_clk.mmc1_clk */ + OMAP3_CORE1_IOPAD(0x2146, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_cmd.mmc1_cmd */ + OMAP3_CORE1_IOPAD(0x2148, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat0.mmc1_dat0 */ + OMAP3_CORE1_IOPAD(0x214A, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat1.mmc1_dat1 */ + OMAP3_CORE1_IOPAD(0x214C, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat2.mmc1_dat2 */ + OMAP3_CORE1_IOPAD(0x214e, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat3.mmc1_dat3 */ + >; + }; }; &usb_otg_hs { -- cgit v1.2.3 From c9ae64db673d5b0bdd6494a59afcc5ed72669932 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Mon, 17 Feb 2014 12:36:33 +0100 Subject: mmc: omap_hsmmc: support more DT properties This should probably be done implicitly through mmc_of_parse(), but that doesn't play well along with the multi-slot model the hsmmc driver features. Hence, for now, do it manually. The properties are already documented in Documentation/devicetree/bindings/mmc/mmc.txt. Signed-off-by: Daniel Mack Acked-by: Balaji T K Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index b1ac26a76306..83240f05e917 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -1753,6 +1753,12 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev) if (of_find_property(np, "ti,needs-special-hs-handling", NULL)) pdata->slots[0].features |= HSMMC_HAS_HSPE_SUPPORT; + if (of_find_property(np, "keep-power-in-suspend", NULL)) + pdata->slots[0].pm_caps |= MMC_PM_KEEP_POWER; + + if (of_find_property(np, "enable-sdio-wakeup", NULL)) + pdata->slots[0].pm_caps |= MMC_PM_WAKE_SDIO_IRQ; + return pdata; } #else -- cgit v1.2.3 From f2ddc1dab61018becf4c5e275bb559d96eec8035 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Wed, 19 Feb 2014 20:26:40 +0530 Subject: mmc: omap_hsmmc: use devm_regulator API Use devm_regulator API, while at it use devm_regulator_get_optional for optional vmmc_aux supply Signed-off-by: Balaji T K Acked-by: Tony Lindgren Tested-by: Florian Vaussard Tested-by: Stefan Roese Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 83240f05e917..36fb91eefd82 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -321,7 +321,7 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) struct regulator *reg; int ocr_value = 0; - reg = regulator_get(host->dev, "vmmc"); + reg = devm_regulator_get(host->dev, "vmmc"); if (IS_ERR(reg)) { dev_err(host->dev, "vmmc regulator missing\n"); return PTR_ERR(reg); @@ -341,7 +341,7 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) } /* Allow an aux regulator */ - reg = regulator_get(host->dev, "vmmc_aux"); + reg = devm_regulator_get_optional(host->dev, "vmmc_aux"); host->vcc_aux = IS_ERR(reg) ? NULL : reg; /* For eMMC do not power off when not in sleep state */ @@ -371,8 +371,6 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) static void omap_hsmmc_reg_put(struct omap_hsmmc_host *host) { - regulator_put(host->vcc); - regulator_put(host->vcc_aux); mmc_slot(host).set_power = NULL; } -- cgit v1.2.3 From 987fd49b894ec964231dfc5b6bbb2bc12e2b56b5 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Wed, 19 Feb 2014 20:26:40 +0530 Subject: mmc: omap_hsmmc: handle vcc and vcc_aux independently handle vcc and vcc_aux independently to reduce indent. Signed-off-by: Balaji T K Acked-by: Tony Lindgren Tested-by: Florian Vaussard Tested-by: Stefan Roese Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 50 ++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 36fb91eefd82..6f4b6b47c1c0 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -291,11 +291,12 @@ static int omap_hsmmc_set_power(struct device *dev, int slot, int power_on, * chips/cards need an interface voltage rail too. */ if (power_on) { - ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd); + if (host->vcc) + ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd); /* Enable interface voltage rail, if needed */ if (ret == 0 && host->vcc_aux) { ret = regulator_enable(host->vcc_aux); - if (ret < 0) + if (ret < 0 && host->vcc) ret = mmc_regulator_set_ocr(host->mmc, host->vcc, 0); } @@ -303,7 +304,7 @@ static int omap_hsmmc_set_power(struct device *dev, int slot, int power_on, /* Shut down the rail */ if (host->vcc_aux) ret = regulator_disable(host->vcc_aux); - if (!ret) { + if (host->vcc) { /* Then proceed to shut down the local regulator */ ret = mmc_regulator_set_ocr(host->mmc, host->vcc, 0); @@ -323,10 +324,10 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) reg = devm_regulator_get(host->dev, "vmmc"); if (IS_ERR(reg)) { - dev_err(host->dev, "vmmc regulator missing\n"); + dev_err(host->dev, "unable to get vmmc regulator %ld\n", + PTR_ERR(reg)); return PTR_ERR(reg); } else { - mmc_slot(host).set_power = omap_hsmmc_set_power; host->vcc = reg; ocr_value = mmc_regulator_get_ocrmask(reg); if (!mmc_slot(host).ocr_mask) { @@ -339,31 +340,26 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) return -EINVAL; } } + } + mmc_slot(host).set_power = omap_hsmmc_set_power; - /* Allow an aux regulator */ - reg = devm_regulator_get_optional(host->dev, "vmmc_aux"); - host->vcc_aux = IS_ERR(reg) ? NULL : reg; + /* Allow an aux regulator */ + reg = devm_regulator_get_optional(host->dev, "vmmc_aux"); + host->vcc_aux = IS_ERR(reg) ? NULL : reg; - /* For eMMC do not power off when not in sleep state */ - if (mmc_slot(host).no_regulator_off_init) - return 0; - /* - * UGLY HACK: workaround regulator framework bugs. - * When the bootloader leaves a supply active, it's - * initialized with zero usecount ... and we can't - * disable it without first enabling it. Until the - * framework is fixed, we need a workaround like this - * (which is safe for MMC, but not in general). - */ - if (regulator_is_enabled(host->vcc) > 0 || - (host->vcc_aux && regulator_is_enabled(host->vcc_aux))) { - int vdd = ffs(mmc_slot(host).ocr_mask) - 1; + /* For eMMC do not power off when not in sleep state */ + if (mmc_slot(host).no_regulator_off_init) + return 0; + /* + * To disable boot_on regulator, enable regulator + * to increase usecount and then disable it. + */ + if ((host->vcc && regulator_is_enabled(host->vcc) > 0) || + (host->vcc_aux && regulator_is_enabled(host->vcc_aux))) { + int vdd = ffs(mmc_slot(host).ocr_mask) - 1; - mmc_slot(host).set_power(host->dev, host->slot_id, - 1, vdd); - mmc_slot(host).set_power(host->dev, host->slot_id, - 0, 0); - } + mmc_slot(host).set_power(host->dev, host->slot_id, 1, vdd); + mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0); } return 0; -- cgit v1.2.3 From 11469e0bb1c5eedd3b489a4b19dbd26c02577674 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Wed, 19 Feb 2014 20:26:40 +0530 Subject: regulator: add pbias regulator support pbias register controls internal power supply to sd card i/o pads in most OMAPs (OMAP2-5, DRA7). Control bits for selecting voltage level and enabling/disabling are in the same PBIAS register. Signed-off-by: Balaji T K Acked-by: Tony Lindgren Acked-by: Mark Brown Tested-by: Florian Vaussard Tested-by: Stefan Roese Signed-off-by: Chris Ball --- .../bindings/regulator/pbias-regulator.txt | 27 +++ drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/pbias-regulator.c | 255 +++++++++++++++++++++ 4 files changed, 292 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/pbias-regulator.txt create mode 100644 drivers/regulator/pbias-regulator.c diff --git a/Documentation/devicetree/bindings/regulator/pbias-regulator.txt b/Documentation/devicetree/bindings/regulator/pbias-regulator.txt new file mode 100644 index 000000000000..32aa26f1e434 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/pbias-regulator.txt @@ -0,0 +1,27 @@ +PBIAS internal regulator for SD card dual voltage i/o pads on OMAP SoCs. + +Required properties: +- compatible: + - "ti,pbias-omap" for OMAP2, OMAP3, OMAP4, OMAP5, DRA7. +- reg: pbias register offset from syscon base and size of pbias register. +- syscon : phandle of the system control module +- regulator-name : should be + pbias_mmc_omap2430 for OMAP2430, OMAP3 SoCs + pbias_sim_omap3 for OMAP3 SoCs + pbias_mmc_omap4 for OMAP4 SoCs + pbias_mmc_omap5 for OMAP5 and DRA7 SoC + +Optional properties: +- Any optional property defined in bindings/regulator/regulator.txt + +Example: + + pbias_regulator: pbias_regulator { + compatible = "ti,pbias-omap"; + reg = <0 0x4>; + syscon = <&omap5_padconf_global>; + pbias_mmc_reg: pbias_mmc_omap5 { + regulator-name = "pbias_mmc_omap5"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3000000>; + }; diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 6a7932822e37..58f08d1d24e8 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -384,6 +384,15 @@ config REGULATOR_PALMAS on the muxing. This is handled automatically in the driver by reading the mux info from OTP. +config REGULATOR_PBIAS + tristate "PBIAS OMAP regulator driver" + depends on (ARCH_OMAP || COMPILE_TEST) && MFD_SYSCON + help + Say y here to support pbias regulator for mmc1:SD card i/o + on OMAP SoCs. + This driver provides support for OMAP pbias modelled + regulators. + config REGULATOR_PCAP tristate "Motorola PCAP2 regulator driver" depends on EZX_PCAP diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 979f9ddcf259..001712ebbf25 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o obj-$(CONFIG_REGULATOR_TPS51632) += tps51632-regulator.o +obj-$(CONFIG_REGULATOR_PBIAS) += pbias-regulator.o obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o obj-$(CONFIG_REGULATOR_RC5T583) += rc5t583-regulator.o diff --git a/drivers/regulator/pbias-regulator.c b/drivers/regulator/pbias-regulator.c new file mode 100644 index 000000000000..ded3b3574209 --- /dev/null +++ b/drivers/regulator/pbias-regulator.c @@ -0,0 +1,255 @@ +/* + * pbias-regulator.c + * + * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Balaji T K + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct pbias_reg_info { + u32 enable; + u32 enable_mask; + u32 vmode; + unsigned int enable_time; + char *name; +}; + +struct pbias_regulator_data { + struct regulator_desc desc; + void __iomem *pbias_addr; + unsigned int pbias_reg; + struct regulator_dev *dev; + struct regmap *syscon; + const struct pbias_reg_info *info; + int voltage; +}; + +static int pbias_regulator_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct pbias_regulator_data *data = rdev_get_drvdata(dev); + const struct pbias_reg_info *info = data->info; + int ret, vmode; + + if (min_uV <= 1800000) + vmode = 0; + else if (min_uV > 1800000) + vmode = info->vmode; + + ret = regmap_update_bits(data->syscon, data->pbias_reg, + info->vmode, vmode); + + return ret; +} + +static int pbias_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct pbias_regulator_data *data = rdev_get_drvdata(rdev); + const struct pbias_reg_info *info = data->info; + int value, voltage; + + regmap_read(data->syscon, data->pbias_reg, &value); + value &= info->vmode; + + voltage = value ? 3000000 : 1800000; + + return voltage; +} + +static int pbias_regulator_enable(struct regulator_dev *rdev) +{ + struct pbias_regulator_data *data = rdev_get_drvdata(rdev); + const struct pbias_reg_info *info = data->info; + int ret; + + ret = regmap_update_bits(data->syscon, data->pbias_reg, + info->enable_mask, info->enable); + + return ret; +} + +static int pbias_regulator_disable(struct regulator_dev *rdev) +{ + struct pbias_regulator_data *data = rdev_get_drvdata(rdev); + const struct pbias_reg_info *info = data->info; + int ret; + + ret = regmap_update_bits(data->syscon, data->pbias_reg, + info->enable_mask, 0); + return ret; +} + +static int pbias_regulator_is_enable(struct regulator_dev *rdev) +{ + struct pbias_regulator_data *data = rdev_get_drvdata(rdev); + const struct pbias_reg_info *info = data->info; + int value; + + regmap_read(data->syscon, data->pbias_reg, &value); + + return (value & info->enable_mask) == info->enable_mask; +} + +static struct regulator_ops pbias_regulator_voltage_ops = { + .set_voltage = pbias_regulator_set_voltage, + .get_voltage = pbias_regulator_get_voltage, + .enable = pbias_regulator_enable, + .disable = pbias_regulator_disable, + .is_enabled = pbias_regulator_is_enable, +}; + +static const struct pbias_reg_info pbias_mmc_omap2430 = { + .enable = BIT(1), + .enable_mask = BIT(1), + .vmode = BIT(0), + .enable_time = 100, + .name = "pbias_mmc_omap2430" +}; + +static const struct pbias_reg_info pbias_sim_omap3 = { + .enable = BIT(9), + .enable_mask = BIT(9), + .vmode = BIT(8), + .enable_time = 100, + .name = "pbias_sim_omap3" +}; + +static const struct pbias_reg_info pbias_mmc_omap4 = { + .enable = BIT(26) | BIT(22), + .enable_mask = BIT(26) | BIT(25) | BIT(22), + .vmode = BIT(21), + .enable_time = 100, + .name = "pbias_mmc_omap4" +}; + +static const struct pbias_reg_info pbias_mmc_omap5 = { + .enable = BIT(27) | BIT(26), + .enable_mask = BIT(27) | BIT(25) | BIT(26), + .vmode = BIT(21), + .enable_time = 100, + .name = "pbias_mmc_omap5" +}; + +static struct of_regulator_match pbias_matches[] = { + { .name = "pbias_mmc_omap2430", .driver_data = (void *)&pbias_mmc_omap2430}, + { .name = "pbias_sim_omap3", .driver_data = (void *)&pbias_sim_omap3}, + { .name = "pbias_mmc_omap4", .driver_data = (void *)&pbias_mmc_omap4}, + { .name = "pbias_mmc_omap5", .driver_data = (void *)&pbias_mmc_omap5}, +}; +#define PBIAS_NUM_REGS ARRAY_SIZE(pbias_matches) + +static const struct of_device_id pbias_of_match[] = { + { .compatible = "ti,pbias-omap", }, + {}, +}; +MODULE_DEVICE_TABLE(of, pbias_of_match); + +static int pbias_regulator_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct pbias_regulator_data *drvdata; + struct resource *res; + struct regulator_config cfg = { }; + struct regmap *syscon; + const struct pbias_reg_info *info; + int ret = 0; + int count, idx, data_idx = 0; + + count = of_regulator_match(&pdev->dev, np, pbias_matches, + PBIAS_NUM_REGS); + if (count < 0) + return count; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct pbias_regulator_data) + * count, GFP_KERNEL); + if (drvdata == NULL) { + dev_err(&pdev->dev, "Failed to allocate device data\n"); + return -ENOMEM; + } + + syscon = syscon_regmap_lookup_by_phandle(np, "syscon"); + if (IS_ERR(syscon)) + return PTR_ERR(syscon); + + cfg.dev = &pdev->dev; + + for (idx = 0; idx < PBIAS_NUM_REGS && data_idx < count; idx++) { + if (!pbias_matches[idx].init_data || + !pbias_matches[idx].of_node) + continue; + + info = pbias_matches[idx].driver_data; + if (!info) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + drvdata[data_idx].pbias_reg = res->start; + drvdata[data_idx].syscon = syscon; + drvdata[data_idx].info = info; + drvdata[data_idx].desc.name = info->name; + drvdata[data_idx].desc.owner = THIS_MODULE; + drvdata[data_idx].desc.type = REGULATOR_VOLTAGE; + drvdata[data_idx].desc.ops = &pbias_regulator_voltage_ops; + drvdata[data_idx].desc.n_voltages = 2; + drvdata[data_idx].desc.enable_time = info->enable_time; + + cfg.init_data = pbias_matches[idx].init_data; + cfg.driver_data = &drvdata[data_idx]; + cfg.of_node = pbias_matches[idx].of_node; + + drvdata[data_idx].dev = devm_regulator_register(&pdev->dev, + &drvdata[data_idx].desc, &cfg); + if (IS_ERR(drvdata[data_idx].dev)) { + ret = PTR_ERR(drvdata[data_idx].dev); + dev_err(&pdev->dev, + "Failed to register regulator: %d\n", ret); + goto err_regulator; + } + data_idx++; + } + + platform_set_drvdata(pdev, drvdata); + +err_regulator: + return ret; +} + +static struct platform_driver pbias_regulator_driver = { + .probe = pbias_regulator_probe, + .driver = { + .name = "pbias-regulator", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pbias_of_match), + }, +}; + +module_platform_driver(pbias_regulator_driver); + +MODULE_AUTHOR("Balaji T K "); +MODULE_DESCRIPTION("pbias voltage regulator"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pbias-regulator"); -- cgit v1.2.3 From e99448ff1f738412bbfec595b5e682c1f0a50279 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Wed, 19 Feb 2014 20:26:40 +0530 Subject: mmc: omap_hsmmc: adapt hsmmc to use pbias regulator In DT case, PBAIS registers are programmed via regulator, use regulator APIs to control PBIAS. Signed-off-by: Balaji T K Tested-by: Florian Vaussard Tested-by: Stefan Roese Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 6f4b6b47c1c0..f46190cfae17 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -126,6 +126,10 @@ #define OMAP_MMC_MAX_CLOCK 52000000 #define DRIVER_NAME "omap_hsmmc" +#define VDD_1V8 1800000 /* 180000 uV */ +#define VDD_3V0 3000000 /* 300000 uV */ +#define VDD_165_195 (ffs(MMC_VDD_165_195) - 1) + /* * One controller can have multiple slots, like on some omap boards using * omap.c controller driver. Luckily this is not currently done on any known @@ -164,6 +168,8 @@ struct omap_hsmmc_host { */ struct regulator *vcc; struct regulator *vcc_aux; + struct regulator *pbias; + bool pbias_enabled; int pbias_disable; void __iomem *base; resource_size_t mapbase; @@ -277,6 +283,15 @@ static int omap_hsmmc_set_power(struct device *dev, int slot, int power_on, if (mmc_slot(host).before_set_reg) mmc_slot(host).before_set_reg(dev, slot, power_on, vdd); + if (host->pbias) { + if (host->pbias_enabled == 1) { + ret = regulator_disable(host->pbias); + if (!ret) + host->pbias_enabled = 0; + } + regulator_set_voltage(host->pbias, VDD_3V0, VDD_3V0); + } + /* * Assume Vcc regulator is used only to power the card ... OMAP * VDDS is used to power the pins, optionally with a transceiver to @@ -311,9 +326,27 @@ static int omap_hsmmc_set_power(struct device *dev, int slot, int power_on, } } + if (host->pbias) { + if (vdd <= VDD_165_195) + ret = regulator_set_voltage(host->pbias, VDD_1V8, + VDD_1V8); + else + ret = regulator_set_voltage(host->pbias, VDD_3V0, + VDD_3V0); + if (ret < 0) + goto error_set_power; + + if (host->pbias_enabled == 0) { + ret = regulator_enable(host->pbias); + if (!ret) + host->pbias_enabled = 1; + } + } + if (mmc_slot(host).after_set_reg) mmc_slot(host).after_set_reg(dev, slot, power_on, vdd); +error_set_power: return ret; } @@ -347,6 +380,9 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) reg = devm_regulator_get_optional(host->dev, "vmmc_aux"); host->vcc_aux = IS_ERR(reg) ? NULL : reg; + reg = devm_regulator_get_optional(host->dev, "pbias"); + host->pbias = IS_ERR(reg) ? NULL : reg; + /* For eMMC do not power off when not in sleep state */ if (mmc_slot(host).no_regulator_off_init) return 0; @@ -1831,6 +1867,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev) host->base = ioremap(host->mapbase, SZ_4K); host->power_mode = MMC_POWER_OFF; host->next_data.cookie = 1; + host->pbias_enabled = 0; platform_set_drvdata(pdev, host); -- cgit v1.2.3 From cd042fe5c1f67a7b60e027802a0784b95c4dee6f Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Wed, 19 Feb 2014 20:26:40 +0530 Subject: ARM: dts: add pbias dt node Add pbias regulator node as a child of system control module - syscon. Signed-off-by: Balaji T K Acked-by: Tony Lindgren Tested-by: Florian Vaussard Tested-by: Stefan Roese Signed-off-by: Chris Ball --- arch/arm/boot/dts/dra7.dtsi | 17 +++++++++++++++++ arch/arm/boot/dts/omap2430.dtsi | 17 +++++++++++++++++ arch/arm/boot/dts/omap3.dtsi | 17 +++++++++++++++++ arch/arm/boot/dts/omap4.dtsi | 17 +++++++++++++++++ arch/arm/boot/dts/omap5.dtsi | 17 +++++++++++++++++ 5 files changed, 85 insertions(+) diff --git a/arch/arm/boot/dts/dra7.dtsi b/arch/arm/boot/dts/dra7.dtsi index 1fd75aa4639d..881e49262d09 100644 --- a/arch/arm/boot/dts/dra7.dtsi +++ b/arch/arm/boot/dts/dra7.dtsi @@ -149,6 +149,22 @@ ti,hwmods = "counter_32k"; }; + dra7_ctrl_general: tisyscon@4a002e00 { + compatible = "syscon"; + reg = <0x4a002e00 0x7c>; + }; + + pbias_regulator: pbias_regulator { + compatible = "ti,pbias-omap"; + reg = <0 0x4>; + syscon = <&dra7_ctrl_general>; + pbias_mmc_reg: pbias_mmc_omap5 { + regulator-name = "pbias_mmc_omap5"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3000000>; + }; + }; + dra7_pmx_core: pinmux@4a003400 { compatible = "pinctrl-single"; reg = <0x4a003400 0x0464>; @@ -524,6 +540,7 @@ dmas = <&sdma 61>, <&sdma 62>; dma-names = "tx", "rx"; status = "disabled"; + pbias-supply = <&pbias_mmc_reg>; }; mmc2: mmc@480b4000 { diff --git a/arch/arm/boot/dts/omap2430.dtsi b/arch/arm/boot/dts/omap2430.dtsi index d624345666f5..a5837b37c5e2 100644 --- a/arch/arm/boot/dts/omap2430.dtsi +++ b/arch/arm/boot/dts/omap2430.dtsi @@ -29,6 +29,22 @@ pinctrl-single,function-mask = <0x3f>; }; + omap2_scm_general: tisyscon@49002270 { + compatible = "syscon"; + reg = <0x49002270 0x240>; + }; + + pbias_regulator: pbias_regulator { + compatible = "ti,pbias-omap"; + reg = <0x230 0x4>; + syscon = <&omap2_scm_general>; + pbias_mmc_reg: pbias_mmc_omap2430 { + regulator-name = "pbias_mmc_omap2430"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3000000>; + }; + }; + gpio1: gpio@4900c000 { compatible = "ti,omap2-gpio"; reg = <0x4900c000 0x200>; @@ -183,6 +199,7 @@ ti,dual-volt; dmas = <&sdma 61>, <&sdma 62>; dma-names = "tx", "rx"; + pbias-supply = <&pbias_mmc_reg>; }; mmc2: mmc@480b4000 { diff --git a/arch/arm/boot/dts/omap3.dtsi b/arch/arm/boot/dts/omap3.dtsi index a5fc83b9c835..c42514a8e18b 100644 --- a/arch/arm/boot/dts/omap3.dtsi +++ b/arch/arm/boot/dts/omap3.dtsi @@ -176,6 +176,22 @@ pinctrl-single,function-mask = <0xff1f>; }; + omap3_scm_general: tisyscon@48002270 { + compatible = "syscon"; + reg = <0x48002270 0x2f0>; + }; + + pbias_regulator: pbias_regulator { + compatible = "ti,pbias-omap"; + reg = <0x2b0 0x4>; + syscon = <&omap3_scm_general>; + pbias_mmc_reg: pbias_mmc_omap2430 { + regulator-name = "pbias_mmc_omap2430"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3000000>; + }; + }; + gpio1: gpio@48310000 { compatible = "ti,omap3-gpio"; reg = <0x48310000 0x200>; @@ -390,6 +406,7 @@ ti,dual-volt; dmas = <&sdma 61>, <&sdma 62>; dma-names = "tx", "rx"; + pbias-supply = <&pbias_mmc_reg>; }; mmc2: mmc@480b4000 { diff --git a/arch/arm/boot/dts/omap4.dtsi b/arch/arm/boot/dts/omap4.dtsi index d3f8a6e8ca20..070e4f45092b 100644 --- a/arch/arm/boot/dts/omap4.dtsi +++ b/arch/arm/boot/dts/omap4.dtsi @@ -186,6 +186,22 @@ pinctrl-single,function-mask = <0x7fff>; }; + omap4_padconf_global: tisyscon@4a1005a0 { + compatible = "syscon"; + reg = <0x4a1005a0 0x170>; + }; + + pbias_regulator: pbias_regulator { + compatible = "ti,pbias-omap"; + reg = <0x60 0x4>; + syscon = <&omap4_padconf_global>; + pbias_mmc_reg: pbias_mmc_omap4 { + regulator-name = "pbias_mmc_omap4"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3000000>; + }; + }; + sdma: dma-controller@4a056000 { compatible = "ti,omap4430-sdma"; reg = <0x4a056000 0x1000>; @@ -419,6 +435,7 @@ ti,needs-special-reset; dmas = <&sdma 61>, <&sdma 62>; dma-names = "tx", "rx"; + pbias-supply = <&pbias_mmc_reg>; }; mmc2: mmc@480b4000 { diff --git a/arch/arm/boot/dts/omap5.dtsi b/arch/arm/boot/dts/omap5.dtsi index a72813a9663e..1f328ad9476f 100644 --- a/arch/arm/boot/dts/omap5.dtsi +++ b/arch/arm/boot/dts/omap5.dtsi @@ -192,6 +192,22 @@ pinctrl-single,function-mask = <0x7fff>; }; + omap5_padconf_global: tisyscon@4a002da0 { + compatible = "syscon"; + reg = <0x4A002da0 0xec>; + }; + + pbias_regulator: pbias_regulator { + compatible = "ti,pbias-omap"; + reg = <0x60 0x4>; + syscon = <&omap5_padconf_global>; + pbias_mmc_reg: pbias_mmc_omap5 { + regulator-name = "pbias_mmc_omap5"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3000000>; + }; + }; + sdma: dma-controller@4a056000 { compatible = "ti,omap4430-sdma"; reg = <0x4a056000 0x1000>; @@ -471,6 +487,7 @@ ti,needs-special-reset; dmas = <&sdma 61>, <&sdma 62>; dma-names = "tx", "rx"; + pbias-supply = <&pbias_mmc_reg>; }; mmc2: mmc@480b4000 { -- cgit v1.2.3 From 12a7108787da44381aa2e1bd268231b9ea19b752 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Wed, 19 Feb 2014 20:26:40 +0530 Subject: ARM: OMAP: enable SYSCON and REGULATOR_PBIAS in omap2plus_defconfig Enable REGULATOR_PBIAS needed for SD card on most OMAPs. Signed-off-by: Balaji T K Acked-by: Tony Lindgren Tested-by: Florian Vaussard Tested-by: Stefan Roese Signed-off-by: Chris Ball --- arch/arm/configs/omap2plus_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/omap2plus_defconfig b/arch/arm/configs/omap2plus_defconfig index 3a0b53d225e7..e4fec1ceb3f1 100644 --- a/arch/arm/configs/omap2plus_defconfig +++ b/arch/arm/configs/omap2plus_defconfig @@ -169,6 +169,7 @@ CONFIG_DRA752_THERMAL=y CONFIG_WATCHDOG=y CONFIG_OMAP_WATCHDOG=y CONFIG_TWL4030_WATCHDOG=y +CONFIG_MFD_SYSCON=y CONFIG_MFD_PALMAS=y CONFIG_MFD_TPS65217=y CONFIG_MFD_TPS65910=y @@ -180,6 +181,7 @@ CONFIG_REGULATOR_TPS6507X=y CONFIG_REGULATOR_TPS65217=y CONFIG_REGULATOR_TPS65910=y CONFIG_REGULATOR_TWL4030=y +CONFIG_REGULATOR_PBIAS=y CONFIG_FB=y CONFIG_FIRMWARE_EDID=y CONFIG_FB_MODE_HELPERS=y -- cgit v1.2.3 From 2cf171cb2579e82f3d587e8ea438afb2b78ddff2 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Wed, 19 Feb 2014 20:26:40 +0530 Subject: mmc: omap_hsmmc: remove pbias workaround remove pbias workaround Signed-off-by: Balaji T K Acked-by: Tony Lindgren Tested-by: Florian Vaussard Tested-by: Stefan Roese Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index f46190cfae17..cc7e84f28c18 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -170,7 +170,6 @@ struct omap_hsmmc_host { struct regulator *vcc_aux; struct regulator *pbias; bool pbias_enabled; - int pbias_disable; void __iomem *base; resource_size_t mapbase; spinlock_t irq_lock; /* Prevent races with irq handler */ @@ -272,13 +271,6 @@ static int omap_hsmmc_set_power(struct device *dev, int slot, int power_on, */ if (!host->vcc) return 0; - /* - * With DT, never turn OFF the regulator for MMC1. This is because - * the pbias cell programming support is still missing when - * booting with Device tree - */ - if (host->pbias_disable && !vdd) - return 0; if (mmc_slot(host).before_set_reg) mmc_slot(host).before_set_reg(dev, slot, power_on, vdd); @@ -1543,13 +1535,7 @@ static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) * of external transceiver; but they all handle 1.8V. */ if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) && - (ios->vdd == DUAL_VOLT_OCR_BIT) && - /* - * With pbias cell programming missing, this - * can't be allowed on MMC1 when booting with device - * tree. - */ - !host->pbias_disable) { + (ios->vdd == DUAL_VOLT_OCR_BIT)) { /* * The mmc_select_voltage fn of the core does * not seem to set the power_mode to @@ -1901,10 +1887,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev) omap_hsmmc_context_save(host); - /* This can be removed once we support PBIAS with DT */ - if (host->dev->of_node && res->start == 0x4809c000) - host->pbias_disable = 1; - host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck"); /* * MMC can still work without debounce clock. -- cgit v1.2.3 From 19df45bcd7d153b1c7140e7dbe885a55ddcb3449 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Fri, 28 Feb 2014 19:08:18 +0530 Subject: mmc: omap_hsmmc: fix return error code for of_get_hsmmc_pdata of_get_hsmmc_pdata returns a pointer, returning NULL is invalid, return ERR_PTR for error case. Signed-off-by: Balaji T K Reported-by: Dan Carpenter Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index cc7e84f28c18..f4e5a59a84c0 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -1740,7 +1740,7 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev) pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) - return NULL; /* out of memory */ + return ERR_PTR(-ENOMEM); /* out of memory */ if (of_find_property(np, "ti,dual-volt", NULL)) pdata->controller_flags |= OMAP_HSMMC_SUPPORTS_DUAL_VOLT; @@ -1781,7 +1781,7 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev) static inline struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev) { - return NULL; + return ERR_PTR(-EINVAL); } #endif -- cgit v1.2.3 From 4ea42235b5a999eff57f38696ae532a6b9377c55 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Fri, 28 Feb 2014 19:08:24 +0530 Subject: mmc: omap_hsmmc: remove redundant reset done Remove redundant reset done check since omap hwmod layer ensures IP reset. Signed-off-by: Balaji T K Reported-by: Dan Carpenter Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index f4e5a59a84c0..d9c84ea46b63 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -97,7 +97,6 @@ #define SRC (1 << 25) #define SRD (1 << 26) #define SOFTRESET (1 << 1) -#define RESETDONE (1 << 0) /* Interrupt masks for IE and ISE register */ #define CC_EN (1 << 0) @@ -632,9 +631,6 @@ static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host) u32 hctl, capa; unsigned long timeout; - if (!OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) - return 1; - if (host->con == OMAP_HSMMC_READ(host->base, CON) && host->hctl == OMAP_HSMMC_READ(host->base, HCTL) && host->sysctl == OMAP_HSMMC_READ(host->base, SYSCTL) && -- cgit v1.2.3 From 6e3076c27da0ac653dda6503dadd2852c2d0eac1 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Tue, 21 Jan 2014 19:54:42 +0530 Subject: mmc: omap_hsmmc: save clock rate to use in interrupt context clk_get_rate throws DEBUG_LOCKS_WARN_ON(in_interrupt()) warning if called from interrupt context. use cached clock rate in set_data_timeout, so that set_data_timeout can be called from interrupt context. Signed-off-by: Balaji T K Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index d9c84ea46b63..6cb4e19dcf07 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -192,6 +192,7 @@ struct omap_hsmmc_host { int reqs_blocked; int use_reg; int req_in_progress; + unsigned long clk_rate; struct omap_hsmmc_next next_data; struct omap_mmc_platform_data *pdata; }; @@ -1360,7 +1361,7 @@ static void set_data_timeout(struct omap_hsmmc_host *host, if (clkd == 0) clkd = 1; - cycle_ns = 1000000000 / (clk_get_rate(host->fclk) / clkd); + cycle_ns = 1000000000 / (host->clk_rate / clkd); timeout = timeout_ns / cycle_ns; timeout += timeout_clks; if (timeout) { @@ -1484,6 +1485,7 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req) host->reqs_blocked = 0; WARN_ON(host->mrq != NULL); host->mrq = req; + host->clk_rate = clk_get_rate(host->fclk); err = omap_hsmmc_prepare_data(host, req); if (err) { req->cmd->error = err; -- cgit v1.2.3 From d4b2c375fc7d6fe56bbc438ffe3060f87f22cdb7 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Tue, 21 Jan 2014 19:54:42 +0530 Subject: mmc: omap_hsmmc: fix request done for sbc error case mrq is not populated for set block count(cmd23) command. Use block read/write mmc_commond pointer for request done and avoid NULL pointer access in error case for sbc (cmd23). Signed-off-by: Balaji T K Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 6cb4e19dcf07..40914f570585 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -916,7 +916,7 @@ omap_hsmmc_cmd_done(struct omap_hsmmc_host *host, struct mmc_command *cmd) } } if ((host->data == NULL && !host->response_busy) || cmd->error) - omap_hsmmc_request_done(host, cmd->mrq); + omap_hsmmc_request_done(host, host->mrq); } /* -- cgit v1.2.3 From 9d0253341b4ef89cf9471656d0990774bc8ab95c Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Tue, 21 Jan 2014 19:54:42 +0530 Subject: mmc: omap_hsmmc: split dma setup split start dma function into setup and start dma to keep track of host_cookie when cmd23 support is enabled along with async request. Signed-off-by: Balaji T K Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 40914f570585..cdeca5e6fae4 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -1285,7 +1285,7 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host, /* * Routine to configure and start DMA for the MMC card */ -static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host, +static int omap_hsmmc_setup_dma_transfer(struct omap_hsmmc_host *host, struct mmc_request *req) { struct dma_slave_config cfg; @@ -1344,8 +1344,6 @@ static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host, host->dma_ch = 1; - dma_async_issue_pending(chan); - return 0; } @@ -1386,6 +1384,21 @@ static void set_data_timeout(struct omap_hsmmc_host *host, OMAP_HSMMC_WRITE(host->base, SYSCTL, reg); } +static void omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host) +{ + struct mmc_request *req = host->mrq; + struct dma_chan *chan; + + if (!req->data) + return; + OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz) + | (req->data->blocks << 16)); + set_data_timeout(host, req->data->timeout_ns, + req->data->timeout_clks); + chan = omap_hsmmc_get_dma_chan(host, req->data); + dma_async_issue_pending(chan); +} + /* * Configure block length for MMC/SD cards and initiate the transfer. */ @@ -1406,12 +1419,8 @@ omap_hsmmc_prepare_data(struct omap_hsmmc_host *host, struct mmc_request *req) return 0; } - OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz) - | (req->data->blocks << 16)); - set_data_timeout(host, req->data->timeout_ns, req->data->timeout_clks); - if (host->use_dma) { - ret = omap_hsmmc_start_dma_transfer(host, req); + ret = omap_hsmmc_setup_dma_transfer(host, req); if (ret != 0) { dev_err(mmc_dev(host->mmc), "MMC start dma failure\n"); return ret; @@ -1496,6 +1505,7 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req) return; } + omap_hsmmc_start_dma_transfer(host); omap_hsmmc_start_command(host, req->cmd, req->data); } -- cgit v1.2.3 From bf129e1ca19f973731d56478297be3de3181efcb Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Tue, 21 Jan 2014 19:54:42 +0530 Subject: mmc: omap_hsmmc: add cmd23 support Add set block count command support for close ended multiblock read/write. Signed-off-by: Balaji T K Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index cdeca5e6fae4..476c6a632bfc 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -202,6 +202,8 @@ struct omap_mmc_of_data { u8 controller_flags; }; +static void omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host); + static int omap_hsmmc_card_detect(struct device *dev, int slot) { struct omap_hsmmc_host *host = dev_get_drvdata(dev); @@ -888,11 +890,10 @@ omap_hsmmc_xfer_done(struct omap_hsmmc_host *host, struct mmc_data *data) else data->bytes_xfered = 0; - if (!data->stop) { + if (data->stop && (data->error || !host->mrq->sbc)) + omap_hsmmc_start_command(host, data->stop, NULL); + else omap_hsmmc_request_done(host, data->mrq); - return; - } - omap_hsmmc_start_command(host, data->stop, NULL); } /* @@ -903,6 +904,14 @@ omap_hsmmc_cmd_done(struct omap_hsmmc_host *host, struct mmc_command *cmd) { host->cmd = NULL; + if (host->mrq->sbc && (host->cmd == host->mrq->sbc) && + !host->mrq->sbc->error) { + omap_hsmmc_start_dma_transfer(host); + omap_hsmmc_start_command(host, host->mrq->cmd, + host->mrq->data); + return; + } + if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { /* response type 2 */ @@ -1504,6 +1513,10 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req) mmc_request_done(mmc, req); return; } + if (req->sbc) { + omap_hsmmc_start_command(host, req->sbc, NULL); + return; + } omap_hsmmc_start_dma_transfer(host); omap_hsmmc_start_command(host, req->cmd, req->data); -- cgit v1.2.3 From a2e771522c2a60459a79844004722f109cb4e13d Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Tue, 21 Jan 2014 19:54:42 +0530 Subject: mmc: omap_hsmmc: add autocmd23 support Add support for autocmd23 support Signed-off-by: Balaji T K Signed-off-by: Chris Ball --- drivers/mmc/host/omap_hsmmc.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 476c6a632bfc..e91ee21549d0 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -45,6 +45,7 @@ /* OMAP HSMMC Host Controller Registers */ #define OMAP_HSMMC_SYSSTATUS 0x0014 #define OMAP_HSMMC_CON 0x002C +#define OMAP_HSMMC_SDMASA 0x0100 #define OMAP_HSMMC_BLK 0x0104 #define OMAP_HSMMC_ARG 0x0108 #define OMAP_HSMMC_CMD 0x010C @@ -58,6 +59,7 @@ #define OMAP_HSMMC_STAT 0x0130 #define OMAP_HSMMC_IE 0x0134 #define OMAP_HSMMC_ISE 0x0138 +#define OMAP_HSMMC_AC12 0x013C #define OMAP_HSMMC_CAPA 0x0140 #define VS18 (1 << 26) @@ -81,6 +83,7 @@ #define DTO_MASK 0x000F0000 #define DTO_SHIFT 16 #define INIT_STREAM (1 << 1) +#define ACEN_ACMD23 (2 << 2) #define DP_SELECT (1 << 21) #define DDIR (1 << 4) #define DMAE 0x1 @@ -111,13 +114,21 @@ #define DTO_EN (1 << 20) #define DCRC_EN (1 << 21) #define DEB_EN (1 << 22) +#define ACE_EN (1 << 24) #define CERR_EN (1 << 28) #define BADA_EN (1 << 29) -#define INT_EN_MASK (BADA_EN | CERR_EN | DEB_EN | DCRC_EN |\ +#define INT_EN_MASK (BADA_EN | CERR_EN | ACE_EN | DEB_EN | DCRC_EN |\ DTO_EN | CIE_EN | CEB_EN | CCRC_EN | CTO_EN | \ BRR_EN | BWR_EN | TC_EN | CC_EN) +#define CNI (1 << 7) +#define ACIE (1 << 4) +#define ACEB (1 << 3) +#define ACCE (1 << 2) +#define ACTO (1 << 1) +#define ACNE (1 << 0) + #define MMC_AUTOSUSPEND_DELAY 100 #define MMC_TIMEOUT_MS 20 /* 20 mSec */ #define MMC_TIMEOUT_US 20000 /* 20000 micro Sec */ @@ -129,6 +140,7 @@ #define VDD_3V0 3000000 /* 300000 uV */ #define VDD_165_195 (ffs(MMC_VDD_165_195) - 1) +#define AUTO_CMD23 (1 << 1) /* Auto CMD23 support */ /* * One controller can have multiple slots, like on some omap boards using * omap.c controller driver. Luckily this is not currently done on any known @@ -193,6 +205,7 @@ struct omap_hsmmc_host { int use_reg; int req_in_progress; unsigned long clk_rate; + unsigned int flags; struct omap_hsmmc_next next_data; struct omap_mmc_platform_data *pdata; }; @@ -813,6 +826,11 @@ omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd, cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22); + if ((host->flags & AUTO_CMD23) && mmc_op_multi(cmd->opcode) && + host->mrq->sbc) { + cmdreg |= ACEN_ACMD23; + OMAP_HSMMC_WRITE(host->base, SDMASA, host->mrq->sbc->arg); + } if (data) { cmdreg |= DP_SELECT | MSBS | BCE; if (data->flags & MMC_DATA_READ) @@ -905,7 +923,7 @@ omap_hsmmc_cmd_done(struct omap_hsmmc_host *host, struct mmc_command *cmd) host->cmd = NULL; if (host->mrq->sbc && (host->cmd == host->mrq->sbc) && - !host->mrq->sbc->error) { + !host->mrq->sbc->error && !(host->flags & AUTO_CMD23)) { omap_hsmmc_start_dma_transfer(host); omap_hsmmc_start_command(host, host->mrq->cmd, host->mrq->data); @@ -1048,6 +1066,7 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status) { struct mmc_data *data; int end_cmd = 0, end_trans = 0; + int error = 0; data = host->data; dev_vdbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status); @@ -1062,6 +1081,20 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status) else if (status & (CCRC_EN | DCRC_EN)) hsmmc_command_incomplete(host, -EILSEQ, end_cmd); + if (status & ACE_EN) { + u32 ac12; + ac12 = OMAP_HSMMC_READ(host->base, AC12); + if (!(ac12 & ACNE) && host->mrq->sbc) { + end_cmd = 1; + if (ac12 & ACTO) + error = -ETIMEDOUT; + else if (ac12 & (ACCE | ACEB | ACIE)) + error = -EILSEQ; + host->mrq->sbc->error = error; + hsmmc_command_incomplete(host, error, end_cmd); + } + dev_dbg(mmc_dev(host->mmc), "AC12 err: 0x%x\n", ac12); + } if (host->data || host->response_busy) { end_trans = !end_cmd; host->response_busy = 0; @@ -1513,7 +1546,7 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req) mmc_request_done(mmc, req); return; } - if (req->sbc) { + if (req->sbc && !(host->flags & AUTO_CMD23)) { omap_hsmmc_start_command(host, req->sbc, NULL); return; } -- cgit v1.2.3 From 4025ce24f326830135341814307c072f6c2a7738 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 4 Mar 2014 16:25:51 -0500 Subject: mmc: sdhci-bcm-kona: fix build errors when built-in `sdhci_bcm_kona_remove' referenced in section `.data' of drivers/built-in.o: defined in discarded section `.exit.text' of drivers/built-in.o Fixes: 058feb53666f ("mmc: sdhci-bcm-kona: make linker-section warning go away") Signed-off-by: Russell King Tested-by: Markus Mayer Acked-by: Matt Porter Cc: Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-bcm-kona.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-bcm-kona.c b/drivers/mmc/host/sdhci-bcm-kona.c index 923eefabf801..6f166e63b817 100644 --- a/drivers/mmc/host/sdhci-bcm-kona.c +++ b/drivers/mmc/host/sdhci-bcm-kona.c @@ -336,7 +336,7 @@ err_pltfm_free: return ret; } -static int __exit sdhci_bcm_kona_remove(struct platform_device *pdev) +static int sdhci_bcm_kona_remove(struct platform_device *pdev) { struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host); -- cgit v1.2.3 From 142dbab951fb9d2281e69f8d7522e6b7e0553ec0 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 28 Feb 2014 21:32:29 +0000 Subject: mmc: sdhci-spear: fix error handling paths for DT Fix the error handling paths for DT and simplify using the devm_* API for clk_get(). Signed-off-by: Russell King Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-spear.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index 76ddd89a688e..394714f71b2f 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -129,7 +129,7 @@ static int sdhci_probe(struct platform_device *pdev) } /* clk enable */ - sdhci->clk = clk_get(&pdev->dev, NULL); + sdhci->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(sdhci->clk)) { ret = PTR_ERR(sdhci->clk); dev_dbg(&pdev->dev, "Error getting clock\n"); @@ -139,7 +139,7 @@ static int sdhci_probe(struct platform_device *pdev) ret = clk_prepare_enable(sdhci->clk); if (ret) { dev_dbg(&pdev->dev, "Error enabling clock\n"); - goto put_clk; + goto err; } ret = clk_set_rate(sdhci->clk, 50000000); @@ -151,7 +151,7 @@ static int sdhci_probe(struct platform_device *pdev) sdhci->data = sdhci_probe_config_dt(pdev); if (IS_ERR(sdhci->data)) { dev_err(&pdev->dev, "DT: Failed to get pdata\n"); - return -ENODEV; + goto disable_clk; } } else { sdhci->data = dev_get_platdata(&pdev->dev); @@ -261,8 +261,6 @@ free_host: sdhci_free_host(host); disable_clk: clk_disable_unprepare(sdhci->clk); -put_clk: - clk_put(sdhci->clk); err: dev_err(&pdev->dev, "spear-sdhci probe failed: %d\n", ret); return ret; @@ -282,7 +280,6 @@ static int sdhci_remove(struct platform_device *pdev) sdhci_remove_host(host, dead); sdhci_free_host(host); clk_disable_unprepare(sdhci->clk); - clk_put(sdhci->clk); return 0; } -- cgit v1.2.3 From fcdb7c8f5019f77b1f55739a1caf9168d3b455d4 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 28 Feb 2014 21:32:34 +0000 Subject: mmc: sdhci-spear: fix platform_data usage sdhci-spear is unsafe should a probe fail or defer, since it overwrites the platform_data with its own driver-private data. It's trivial to fix as SDHCI allows for driver-private data to be appended to its own structure - we just need to arrange the code to allow this. Signed-off-by: Russell King Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-spear.c | 43 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index 394714f71b2f..d89267eb14b6 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -105,6 +105,7 @@ static int sdhci_probe(struct platform_device *pdev) struct sdhci_host *host; struct resource *iomem; struct spear_sdhci *sdhci; + struct device *dev; int ret; iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -121,25 +122,28 @@ static int sdhci_probe(struct platform_device *pdev) goto err; } - sdhci = devm_kzalloc(&pdev->dev, sizeof(*sdhci), GFP_KERNEL); - if (!sdhci) { - ret = -ENOMEM; + dev = pdev->dev.parent ? pdev->dev.parent : &pdev->dev; + host = sdhci_alloc_host(dev, sizeof(*sdhci)); + if (IS_ERR(host)) { + ret = PTR_ERR(host); dev_dbg(&pdev->dev, "cannot allocate memory for sdhci\n"); goto err; } + sdhci = sdhci_priv(host); + /* clk enable */ sdhci->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(sdhci->clk)) { ret = PTR_ERR(sdhci->clk); dev_dbg(&pdev->dev, "Error getting clock\n"); - goto err; + goto err_host; } ret = clk_prepare_enable(sdhci->clk); if (ret) { dev_dbg(&pdev->dev, "Error enabling clock\n"); - goto err; + goto err_host; } ret = clk_set_rate(sdhci->clk, 50000000); @@ -157,19 +161,6 @@ static int sdhci_probe(struct platform_device *pdev) sdhci->data = dev_get_platdata(&pdev->dev); } - pdev->dev.platform_data = sdhci; - - if (pdev->dev.parent) - host = sdhci_alloc_host(pdev->dev.parent, 0); - else - host = sdhci_alloc_host(&pdev->dev, 0); - - if (IS_ERR(host)) { - ret = PTR_ERR(host); - dev_dbg(&pdev->dev, "error allocating host\n"); - goto disable_clk; - } - host->hw_name = "sdhci"; host->ops = &sdhci_pltfm_ops; host->irq = platform_get_irq(pdev, 0); @@ -180,13 +171,13 @@ static int sdhci_probe(struct platform_device *pdev) if (!host->ioaddr) { ret = -ENOMEM; dev_dbg(&pdev->dev, "failed to remap registers\n"); - goto free_host; + goto disable_clk; } ret = sdhci_add_host(host); if (ret) { dev_dbg(&pdev->dev, "error adding host\n"); - goto free_host; + goto disable_clk; } platform_set_drvdata(pdev, host); @@ -257,10 +248,10 @@ static int sdhci_probe(struct platform_device *pdev) set_drvdata: sdhci_remove_host(host, 1); -free_host: - sdhci_free_host(host); disable_clk: clk_disable_unprepare(sdhci->clk); +err_host: + sdhci_free_host(host); err: dev_err(&pdev->dev, "spear-sdhci probe failed: %d\n", ret); return ret; @@ -269,7 +260,7 @@ err: static int sdhci_remove(struct platform_device *pdev) { struct sdhci_host *host = platform_get_drvdata(pdev); - struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev); + struct spear_sdhci *sdhci = sdhci_priv(host); int dead = 0; u32 scratch; @@ -278,8 +269,8 @@ static int sdhci_remove(struct platform_device *pdev) dead = 1; sdhci_remove_host(host, dead); - sdhci_free_host(host); clk_disable_unprepare(sdhci->clk); + sdhci_free_host(host); return 0; } @@ -288,7 +279,7 @@ static int sdhci_remove(struct platform_device *pdev) static int sdhci_suspend(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); - struct spear_sdhci *sdhci = dev_get_platdata(dev); + struct spear_sdhci *sdhci = sdhci_priv(host); int ret; ret = sdhci_suspend_host(host); @@ -301,7 +292,7 @@ static int sdhci_suspend(struct device *dev) static int sdhci_resume(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); - struct spear_sdhci *sdhci = dev_get_platdata(dev); + struct spear_sdhci *sdhci = sdhci_priv(host); int ret; ret = clk_enable(sdhci->clk); -- cgit v1.2.3 From 475d9e3ebf869cc637342b74af20632551769902 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 28 Feb 2014 21:32:39 +0000 Subject: mmc: sdhci-spear: simplify resource handling Use devm_ioremap_resource() to simplify iomem resource handling in the probe path. Signed-off-by: Russell King Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-spear.c | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index d89267eb14b6..fc6eac5b1064 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -108,20 +108,6 @@ static int sdhci_probe(struct platform_device *pdev) struct device *dev; int ret; - iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!iomem) { - ret = -ENOMEM; - dev_dbg(&pdev->dev, "memory resource not defined\n"); - goto err; - } - - if (!devm_request_mem_region(&pdev->dev, iomem->start, - resource_size(iomem), "spear-sdhci")) { - ret = -EBUSY; - dev_dbg(&pdev->dev, "cannot request region\n"); - goto err; - } - dev = pdev->dev.parent ? pdev->dev.parent : &pdev->dev; host = sdhci_alloc_host(dev, sizeof(*sdhci)); if (IS_ERR(host)) { @@ -130,6 +116,19 @@ static int sdhci_probe(struct platform_device *pdev) goto err; } + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->ioaddr = devm_ioremap_resource(&pdev->dev, iomem); + if (IS_ERR(host->ioaddr)) { + ret = PTR_ERR(host->ioaddr); + dev_dbg(&pdev->dev, "unable to map iomem: %d\n", ret); + goto err_host; + } + + host->hw_name = "sdhci"; + host->ops = &sdhci_pltfm_ops; + host->irq = platform_get_irq(pdev, 0); + host->quirks = SDHCI_QUIRK_BROKEN_ADMA; + sdhci = sdhci_priv(host); /* clk enable */ @@ -161,19 +160,6 @@ static int sdhci_probe(struct platform_device *pdev) sdhci->data = dev_get_platdata(&pdev->dev); } - host->hw_name = "sdhci"; - host->ops = &sdhci_pltfm_ops; - host->irq = platform_get_irq(pdev, 0); - host->quirks = SDHCI_QUIRK_BROKEN_ADMA; - - host->ioaddr = devm_ioremap(&pdev->dev, iomem->start, - resource_size(iomem)); - if (!host->ioaddr) { - ret = -ENOMEM; - dev_dbg(&pdev->dev, "failed to remap registers\n"); - goto disable_clk; - } - ret = sdhci_add_host(host); if (ret) { dev_dbg(&pdev->dev, "error adding host\n"); -- cgit v1.2.3 From 42c1add97073b5a701b8aee0fdb69ef0345d4c50 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 28 Feb 2014 21:32:44 +0000 Subject: mmc: sdhci-spear: remove support for power gpio None of this code is currently used: there are no definitions of struct sdhci_plat_data in arch/arm, neither are there any DT properties which use card_power_gpio/power_active_high/power_always_enb. In any case, slot power control should be rigged up via vmmc and the regulator subsystem in the DT case. Signed-off-by: Russell King Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-spear.c | 32 -------------------------------- include/linux/mmc/sdhci-spear.h | 8 -------- 2 files changed, 40 deletions(-) diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index fc6eac5b1064..676df4623057 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -56,14 +56,6 @@ static irqreturn_t sdhci_gpio_irq(int irq, void *dev_id) gpio_irq_type = val ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH; irq_set_irq_type(irq, gpio_irq_type); - if (sdhci->data->card_power_gpio >= 0) { - if (!sdhci->data->power_always_enb) { - /* if card inserted, give power, otherwise remove it */ - val = sdhci->data->power_active_high ? !val : val ; - gpio_set_value(sdhci->data->card_power_gpio, val); - } - } - /* inform sdhci driver about card insertion/removal */ tasklet_schedule(&host->card_tasklet); @@ -179,30 +171,6 @@ static int sdhci_probe(struct platform_device *pdev) if (!sdhci->data) return 0; - if (sdhci->data->card_power_gpio >= 0) { - int val = 0; - - ret = devm_gpio_request(&pdev->dev, - sdhci->data->card_power_gpio, "sdhci"); - if (ret < 0) { - dev_dbg(&pdev->dev, "gpio request fail: %d\n", - sdhci->data->card_power_gpio); - goto set_drvdata; - } - - if (sdhci->data->power_always_enb) - val = sdhci->data->power_active_high; - else - val = !sdhci->data->power_active_high; - - ret = gpio_direction_output(sdhci->data->card_power_gpio, val); - if (ret) { - dev_dbg(&pdev->dev, "gpio set direction fail: %d\n", - sdhci->data->card_power_gpio); - goto set_drvdata; - } - } - if (sdhci->data->card_int_gpio >= 0) { ret = devm_gpio_request(&pdev->dev, sdhci->data->card_int_gpio, "sdhci"); diff --git a/include/linux/mmc/sdhci-spear.h b/include/linux/mmc/sdhci-spear.h index e78c0e236e9d..8cc095a76cf8 100644 --- a/include/linux/mmc/sdhci-spear.h +++ b/include/linux/mmc/sdhci-spear.h @@ -18,17 +18,9 @@ /* * struct sdhci_plat_data: spear sdhci platform data structure * - * @card_power_gpio: gpio pin for enabling/disabling power to sdhci socket - * @power_active_high: if set, enable power to sdhci socket by setting - * card_power_gpio - * @power_always_enb: If set, then enable power on probe, otherwise enable only - * on card insertion and disable on card removal. * card_int_gpio: gpio pin used for card detection */ struct sdhci_plat_data { - int card_power_gpio; - int power_active_high; - int power_always_enb; int card_int_gpio; }; -- cgit v1.2.3 From b42b9b12e9b768884f258e31caf4f3a2a2cea2fd Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 28 Feb 2014 21:32:49 +0000 Subject: mmc: sdhci-spear: use generic card detection gpio support sdhci has support for using GPIOs for card detection. If we have a GPIO specified, we can use that directly, without needing our own interrupt handler. Signed-off-by: Russell King Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-spear.c | 79 +++++++++--------------------------------- 1 file changed, 17 insertions(+), 62 deletions(-) diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index 676df4623057..0316dec3f006 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "sdhci.h" @@ -40,28 +41,6 @@ static const struct sdhci_ops sdhci_pltfm_ops = { /* Nothing to do for now. */ }; -/* gpio card detection interrupt handler */ -static irqreturn_t sdhci_gpio_irq(int irq, void *dev_id) -{ - struct platform_device *pdev = dev_id; - struct sdhci_host *host = platform_get_drvdata(pdev); - struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev); - unsigned long gpio_irq_type; - int val; - - val = gpio_get_value(sdhci->data->card_int_gpio); - - /* val == 1 -> card removed, val == 0 -> card inserted */ - /* if card removed - set irq for low level, else vice versa */ - gpio_irq_type = val ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH; - irq_set_irq_type(irq, gpio_irq_type); - - /* inform sdhci driver about card insertion/removal */ - tasklet_schedule(&host->card_tasklet); - - return IRQ_HANDLED; -} - #ifdef CONFIG_OF static struct sdhci_plat_data *sdhci_probe_config_dt(struct platform_device *pdev) { @@ -152,6 +131,22 @@ static int sdhci_probe(struct platform_device *pdev) sdhci->data = dev_get_platdata(&pdev->dev); } + /* + * It is optional to use GPIOs for sdhci card detection. If + * sdhci->data is NULL, then use original sdhci lines otherwise + * GPIO lines. We use the built-in GPIO support for this. + */ + if (sdhci->data && sdhci->data->card_int_gpio >= 0) { + ret = mmc_gpio_request_cd(host->mmc, + sdhci->data->card_int_gpio, 0); + if (ret < 0) { + dev_dbg(&pdev->dev, + "failed to request card-detect gpio%d\n", + sdhci->data->card_int_gpio); + goto disable_clk; + } + } + ret = sdhci_add_host(host); if (ret) { dev_dbg(&pdev->dev, "error adding host\n"); @@ -160,48 +155,8 @@ static int sdhci_probe(struct platform_device *pdev) platform_set_drvdata(pdev, host); - /* - * It is optional to use GPIOs for sdhci Power control & sdhci card - * interrupt detection. If sdhci->data is NULL, then use original sdhci - * lines otherwise GPIO lines. - * If GPIO is selected for power control, then power should be disabled - * after card removal and should be enabled when card insertion - * interrupt occurs - */ - if (!sdhci->data) - return 0; - - if (sdhci->data->card_int_gpio >= 0) { - ret = devm_gpio_request(&pdev->dev, sdhci->data->card_int_gpio, - "sdhci"); - if (ret < 0) { - dev_dbg(&pdev->dev, "gpio request fail: %d\n", - sdhci->data->card_int_gpio); - goto set_drvdata; - } - - ret = gpio_direction_input(sdhci->data->card_int_gpio); - if (ret) { - dev_dbg(&pdev->dev, "gpio set direction fail: %d\n", - sdhci->data->card_int_gpio); - goto set_drvdata; - } - ret = devm_request_irq(&pdev->dev, - gpio_to_irq(sdhci->data->card_int_gpio), - sdhci_gpio_irq, IRQF_TRIGGER_LOW, - mmc_hostname(host->mmc), pdev); - if (ret) { - dev_dbg(&pdev->dev, "gpio request irq fail: %d\n", - sdhci->data->card_int_gpio); - goto set_drvdata; - } - - } - return 0; -set_drvdata: - sdhci_remove_host(host, 1); disable_clk: clk_disable_unprepare(sdhci->clk); err_host: -- cgit v1.2.3 From 2b5cc85135279c3504c72ab51bd089d102a2f8e9 Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Mon, 17 Mar 2014 09:04:57 -0400 Subject: Revert "dts: socfpga: Add support for SD/MMC on the SOCFPGA platform" This reverts commit d9c3f5df539a8a74cc830b35838670fe0541fed1, which should not have been merged via mmc-next. It's in arm-soc instead now. --- .../devicetree/bindings/mmc/socfpga-dw-mshc.txt | 23 ---------------------- arch/arm/boot/dts/socfpga.dtsi | 17 +++------------- arch/arm/boot/dts/socfpga_arria5.dtsi | 11 ----------- arch/arm/boot/dts/socfpga_cyclone5.dtsi | 11 ----------- arch/arm/boot/dts/socfpga_vt.dts | 11 ----------- 5 files changed, 3 insertions(+), 70 deletions(-) delete mode 100644 Documentation/devicetree/bindings/mmc/socfpga-dw-mshc.txt diff --git a/Documentation/devicetree/bindings/mmc/socfpga-dw-mshc.txt b/Documentation/devicetree/bindings/mmc/socfpga-dw-mshc.txt deleted file mode 100644 index 4897bea7e3f8..000000000000 --- a/Documentation/devicetree/bindings/mmc/socfpga-dw-mshc.txt +++ /dev/null @@ -1,23 +0,0 @@ -* Altera SOCFPGA specific extensions to the Synopsys Designware Mobile - Storage Host Controller - -The Synopsys designware mobile storage host controller is used to interface -a SoC with storage medium such as eMMC or SD/MMC cards. This file documents -differences between the core Synopsys dw mshc controller properties described -by synopsys-dw-mshc.txt and the properties used by the Altera SOCFPGA specific -extensions to the Synopsys Designware Mobile Storage Host Controller. - -Required Properties: - -* compatible: should be - - "altr,socfpga-dw-mshc": for Altera's SOCFPGA platform - -Example: - - mmc: dwmmc0@ff704000 { - compatible = "altr,socfpga-dw-mshc"; - reg = <0xff704000 0x1000>; - interrupts = <0 129 4>; - #address-cells = <1>; - #size-cells = <0>; - }; diff --git a/arch/arm/boot/dts/socfpga.dtsi b/arch/arm/boot/dts/socfpga.dtsi index 5f582467fccc..537f1a5c07f5 100644 --- a/arch/arm/boot/dts/socfpga.dtsi +++ b/arch/arm/boot/dts/socfpga.dtsi @@ -473,17 +473,6 @@ arm,data-latency = <2 1 1>; }; - mmc: dwmmc0@ff704000 { - compatible = "altr,socfpga-dw-mshc"; - reg = <0xff704000 0x1000>; - interrupts = <0 139 4>; - fifo-depth = <0x400>; - #address-cells = <1>; - #size-cells = <0>; - clocks = <&l4_mp_clk>, <&sdmmc_clk>; - clock-names = "biu", "ciu"; - }; - /* Local timer */ timer@fffec600 { compatible = "arm,cortex-a9-twd-timer"; @@ -538,8 +527,8 @@ }; sysmgr@ffd08000 { - compatible = "altr,sys-mgr", "syscon"; - reg = <0xffd08000 0x4000>; - }; + compatible = "altr,sys-mgr"; + reg = <0xffd08000 0x4000>; + }; }; }; diff --git a/arch/arm/boot/dts/socfpga_arria5.dtsi b/arch/arm/boot/dts/socfpga_arria5.dtsi index 6c87b7070ca7..a85b4043f888 100644 --- a/arch/arm/boot/dts/socfpga_arria5.dtsi +++ b/arch/arm/boot/dts/socfpga_arria5.dtsi @@ -27,17 +27,6 @@ }; }; - dwmmc0@ff704000 { - num-slots = <1>; - supports-highspeed; - broken-cd; - - slot@0 { - reg = <0>; - bus-width = <4>; - }; - }; - serial0@ffc02000 { clock-frequency = <100000000>; }; diff --git a/arch/arm/boot/dts/socfpga_cyclone5.dtsi b/arch/arm/boot/dts/socfpga_cyclone5.dtsi index ca41b0ebf461..a8716f6dbe2e 100644 --- a/arch/arm/boot/dts/socfpga_cyclone5.dtsi +++ b/arch/arm/boot/dts/socfpga_cyclone5.dtsi @@ -28,17 +28,6 @@ }; }; - dwmmc0@ff704000 { - num-slots = <1>; - supports-highspeed; - broken-cd; - - slot@0 { - reg = <0>; - bus-width = <4>; - }; - }; - ethernet@ff702000 { phy-mode = "rgmii"; phy-addr = <0xffffffff>; /* probe for phy addr */ diff --git a/arch/arm/boot/dts/socfpga_vt.dts b/arch/arm/boot/dts/socfpga_vt.dts index 222313f5420b..d1ec0cab2dee 100644 --- a/arch/arm/boot/dts/socfpga_vt.dts +++ b/arch/arm/boot/dts/socfpga_vt.dts @@ -41,17 +41,6 @@ }; }; - dwmmc0@ff704000 { - num-slots = <1>; - supports-highspeed; - broken-cd; - - slot@0 { - reg = <0>; - bus-width = <4>; - }; - }; - ethernet@ff700000 { phy-mode = "gmii"; status = "okay"; -- cgit v1.2.3 From 842f4bdd37c7a0984e22aa919ad1f043137ac5c8 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 10 Mar 2014 15:02:39 +0200 Subject: mmc: slot-gpio: Record GPIO descriptors instead of GPIO numbers In preparation for adding a descriptor-based CD GPIO API, switch from recording GPIO numbers to recording GPIO descriptors. Signed-off-by: Adrian Hunter Tested-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/core/slot-gpio.c | 45 ++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/drivers/mmc/core/slot-gpio.c b/drivers/mmc/core/slot-gpio.c index 46596b71a32f..86547a2a82c6 100644 --- a/drivers/mmc/core/slot-gpio.c +++ b/drivers/mmc/core/slot-gpio.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -18,8 +19,10 @@ #include struct mmc_gpio { - int ro_gpio; - int cd_gpio; + struct gpio_desc *ro_gpio; + struct gpio_desc *cd_gpio; + bool override_ro_active_level; + bool override_cd_active_level; char *ro_label; char cd_label[0]; }; @@ -57,8 +60,6 @@ static int mmc_gpio_alloc(struct mmc_host *host) ctx->ro_label = ctx->cd_label + len; snprintf(ctx->cd_label, len, "%s cd", dev_name(host->parent)); snprintf(ctx->ro_label, len, "%s ro", dev_name(host->parent)); - ctx->cd_gpio = -EINVAL; - ctx->ro_gpio = -EINVAL; host->slot.handler_priv = ctx; } } @@ -72,11 +73,14 @@ int mmc_gpio_get_ro(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; - if (!ctx || !gpio_is_valid(ctx->ro_gpio)) + if (!ctx || !ctx->ro_gpio) return -ENOSYS; - return !gpio_get_value_cansleep(ctx->ro_gpio) ^ - !!(host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH); + if (ctx->override_ro_active_level) + return !gpiod_get_raw_value_cansleep(ctx->ro_gpio) ^ + !!(host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH); + + return gpiod_get_value_cansleep(ctx->ro_gpio); } EXPORT_SYMBOL(mmc_gpio_get_ro); @@ -84,11 +88,14 @@ int mmc_gpio_get_cd(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; - if (!ctx || !gpio_is_valid(ctx->cd_gpio)) + if (!ctx || !ctx->cd_gpio) return -ENOSYS; - return !gpio_get_value_cansleep(ctx->cd_gpio) ^ - !!(host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH); + if (ctx->override_cd_active_level) + return !gpiod_get_raw_value_cansleep(ctx->cd_gpio) ^ + !!(host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH); + + return gpiod_get_value_cansleep(ctx->cd_gpio); } EXPORT_SYMBOL(mmc_gpio_get_cd); @@ -125,7 +132,8 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) if (ret < 0) return ret; - ctx->ro_gpio = gpio; + ctx->override_ro_active_level = true; + ctx->ro_gpio = gpio_to_desc(gpio); return 0; } @@ -201,7 +209,8 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio, if (irq < 0) host->caps |= MMC_CAP_NEEDS_POLL; - ctx->cd_gpio = gpio; + ctx->override_cd_active_level = true; + ctx->cd_gpio = gpio_to_desc(gpio); return 0; } @@ -219,11 +228,11 @@ void mmc_gpio_free_ro(struct mmc_host *host) struct mmc_gpio *ctx = host->slot.handler_priv; int gpio; - if (!ctx || !gpio_is_valid(ctx->ro_gpio)) + if (!ctx || !ctx->ro_gpio) return; - gpio = ctx->ro_gpio; - ctx->ro_gpio = -EINVAL; + gpio = desc_to_gpio(ctx->ro_gpio); + ctx->ro_gpio = NULL; devm_gpio_free(&host->class_dev, gpio); } @@ -241,7 +250,7 @@ void mmc_gpio_free_cd(struct mmc_host *host) struct mmc_gpio *ctx = host->slot.handler_priv; int gpio; - if (!ctx || !gpio_is_valid(ctx->cd_gpio)) + if (!ctx || !ctx->cd_gpio) return; if (host->slot.cd_irq >= 0) { @@ -249,8 +258,8 @@ void mmc_gpio_free_cd(struct mmc_host *host) host->slot.cd_irq = -EINVAL; } - gpio = ctx->cd_gpio; - ctx->cd_gpio = -EINVAL; + gpio = desc_to_gpio(ctx->cd_gpio); + ctx->cd_gpio = NULL; devm_gpio_free(&host->class_dev, gpio); } -- cgit v1.2.3 From 26652671338a443fd33cf47b50658dd8b095d54a Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 10 Mar 2014 15:02:40 +0200 Subject: mmc: slot-gpio: Split out CD IRQ request into a separate function In preparation for adding a descriptor-based CD GPIO API, split out CD IRQ request into a separate function. Signed-off-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/mmc/core/slot-gpio.c | 58 ++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/drivers/mmc/core/slot-gpio.c b/drivers/mmc/core/slot-gpio.c index 86547a2a82c6..47fa07e3604d 100644 --- a/drivers/mmc/core/slot-gpio.c +++ b/drivers/mmc/core/slot-gpio.c @@ -139,6 +139,39 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) } EXPORT_SYMBOL(mmc_gpio_request_ro); +static void mmc_gpiod_request_cd_irq(struct mmc_host *host) +{ + struct mmc_gpio *ctx = host->slot.handler_priv; + int ret, irq; + + if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio) + return; + + irq = gpiod_to_irq(ctx->cd_gpio); + + /* + * Even if gpiod_to_irq() returns a valid IRQ number, the platform might + * still prefer to poll, e.g., because that IRQ number is already used + * by another unit and cannot be shared. + */ + if (irq >= 0 && host->caps & MMC_CAP_NEEDS_POLL) + irq = -EINVAL; + + if (irq >= 0) { + 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) + irq = ret; + } + + host->slot.cd_irq = irq; + + if (irq < 0) + host->caps |= MMC_CAP_NEEDS_POLL; +} + /** * mmc_gpio_request_cd - request a gpio for card-detection * @host: mmc host @@ -162,7 +195,6 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio, unsigned int debounce) { struct mmc_gpio *ctx; - int irq = gpio_to_irq(gpio); int ret; ret = mmc_gpio_alloc(host); @@ -187,31 +219,11 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio, return ret; } - /* - * Even if gpio_to_irq() returns a valid IRQ number, the platform might - * still prefer to poll, e.g., because that IRQ number is already used - * by another unit and cannot be shared. - */ - if (irq >= 0 && host->caps & MMC_CAP_NEEDS_POLL) - irq = -EINVAL; - - if (irq >= 0) { - 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) - irq = ret; - } - - host->slot.cd_irq = irq; - - if (irq < 0) - host->caps |= MMC_CAP_NEEDS_POLL; - ctx->override_cd_active_level = true; ctx->cd_gpio = gpio_to_desc(gpio); + mmc_gpiod_request_cd_irq(host); + return 0; } EXPORT_SYMBOL(mmc_gpio_request_cd); -- cgit v1.2.3 From 740a221ef0e579dc7c675cf6b90f5313509788f7 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 10 Mar 2014 15:02:41 +0200 Subject: mmc: slot-gpio: Add GPIO descriptor based CD GPIO API Add functions to request a CD GPIO using the GPIO descriptor API. Note that the new request function is paired with mmc_gpiod_free_cd() not mmc_gpio_free_cd(). Note also that it must be called prior to mmc_add_host() otherwise the caller must also call mmc_gpiod_request_cd_irq(). Signed-off-by: Adrian Hunter Reviewed-by: Linus Walleij Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 4 +++ drivers/mmc/core/slot-gpio.c | 81 ++++++++++++++++++++++++++++++++++++++++++- include/linux/mmc/slot-gpio.h | 6 ++++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index dc7a5fb81a5c..acbc3f2aaaf9 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "core.h" #include "bus.h" @@ -2471,6 +2472,7 @@ void mmc_start_host(struct mmc_host *host) mmc_power_off(host); else mmc_power_up(host, host->ocr_avail); + mmc_gpiod_request_cd_irq(host); _mmc_detect_change(host, 0, false); } @@ -2482,6 +2484,8 @@ void mmc_stop_host(struct mmc_host *host) host->removed = 1; spin_unlock_irqrestore(&host->lock, flags); #endif + if (host->slot.cd_irq >= 0) + disable_irq(host->slot.cd_irq); host->rescan_disable = 1; cancel_delayed_work_sync(&host->detect); diff --git a/drivers/mmc/core/slot-gpio.c b/drivers/mmc/core/slot-gpio.c index 47fa07e3604d..f7650b899e3d 100644 --- a/drivers/mmc/core/slot-gpio.c +++ b/drivers/mmc/core/slot-gpio.c @@ -139,7 +139,7 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) } EXPORT_SYMBOL(mmc_gpio_request_ro); -static void mmc_gpiod_request_cd_irq(struct mmc_host *host) +void mmc_gpiod_request_cd_irq(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; int ret, irq; @@ -171,6 +171,7 @@ static void mmc_gpiod_request_cd_irq(struct mmc_host *host) if (irq < 0) host->caps |= MMC_CAP_NEEDS_POLL; } +EXPORT_SYMBOL(mmc_gpiod_request_cd_irq); /** * mmc_gpio_request_cd - request a gpio for card-detection @@ -276,3 +277,81 @@ void mmc_gpio_free_cd(struct mmc_host *host) devm_gpio_free(&host->class_dev, gpio); } EXPORT_SYMBOL(mmc_gpio_free_cd); + +/** + * mmc_gpiod_request_cd - request a gpio descriptor for card-detection + * @host: mmc host + * @con_id: function within the GPIO consumer + * @idx: index of the GPIO to obtain in the consumer + * @override_active_level: ignore %GPIO_ACTIVE_LOW flag + * @debounce: debounce time in microseconds + * + * Use this function in place of mmc_gpio_request_cd() to use the GPIO + * descriptor API. Note that it is paired with mmc_gpiod_free_cd() not + * mmc_gpio_free_cd(). Note also that it must be called prior to mmc_add_host() + * otherwise the caller must also call mmc_gpiod_request_cd_irq(). + * + * Returns zero on success, else an error. + */ +int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id, + unsigned int idx, bool override_active_level, + unsigned int debounce) +{ + struct mmc_gpio *ctx; + struct gpio_desc *desc; + int ret; + + ret = mmc_gpio_alloc(host); + if (ret < 0) + return ret; + + ctx = host->slot.handler_priv; + + if (!con_id) + con_id = ctx->cd_label; + + desc = devm_gpiod_get_index(host->parent, con_id, idx); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + ret = gpiod_direction_input(desc); + if (ret < 0) + return ret; + + if (debounce) { + ret = gpiod_set_debounce(desc, debounce); + if (ret < 0) + return ret; + } + + ctx->override_cd_active_level = override_active_level; + ctx->cd_gpio = desc; + + return 0; +} +EXPORT_SYMBOL(mmc_gpiod_request_cd); + +/** + * mmc_gpiod_free_cd - free the card-detection gpio descriptor + * @host: mmc host + * + * It's provided only for cases that client drivers need to manually free + * up the card-detection gpio requested by mmc_gpiod_request_cd(). + */ +void mmc_gpiod_free_cd(struct mmc_host *host) +{ + struct mmc_gpio *ctx = host->slot.handler_priv; + + if (!ctx || !ctx->cd_gpio) + return; + + if (host->slot.cd_irq >= 0) { + devm_free_irq(&host->class_dev, host->slot.cd_irq, host); + host->slot.cd_irq = -EINVAL; + } + + devm_gpiod_put(&host->class_dev, ctx->cd_gpio); + + ctx->cd_gpio = NULL; +} +EXPORT_SYMBOL(mmc_gpiod_free_cd); diff --git a/include/linux/mmc/slot-gpio.h b/include/linux/mmc/slot-gpio.h index b0c73e4cacea..d2433381e828 100644 --- a/include/linux/mmc/slot-gpio.h +++ b/include/linux/mmc/slot-gpio.h @@ -22,4 +22,10 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio, unsigned int debounce); void mmc_gpio_free_cd(struct mmc_host *host); +int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id, + unsigned int idx, bool override_active_level, + unsigned int debounce); +void mmc_gpiod_free_cd(struct mmc_host *host); +void mmc_gpiod_request_cd_irq(struct mmc_host *host); + #endif -- cgit v1.2.3 From 4fd4409c81e2c756a5cfbedc598c48d6a3ed3fd5 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 10 Mar 2014 15:02:42 +0200 Subject: mmc: sdhci-acpi: Fix broken card detect for ACPI HID 80860F14 Some 80860F14 devices do not support card detect and must rely completely on GPIO. Presently the card detect GPIO is used only to wake-up from runtime suspend. Change to using mmc_gpioid_request_cd() which will cause the SDHCI driver to prefer the GPIO to the host controller's native card detect. Signed-off-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-acpi.c | 77 +++++++++---------------------------------- 1 file changed, 15 insertions(+), 62 deletions(-) diff --git a/drivers/mmc/host/sdhci-acpi.c b/drivers/mmc/host/sdhci-acpi.c index 9ce17f6e4014..98c7420c5f75 100644 --- a/drivers/mmc/host/sdhci-acpi.c +++ b/drivers/mmc/host/sdhci-acpi.c @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -40,13 +39,15 @@ #include #include +#include #include #include "sdhci.h" enum { - SDHCI_ACPI_SD_CD = BIT(0), - SDHCI_ACPI_RUNTIME_PM = BIT(1), + SDHCI_ACPI_SD_CD = BIT(0), + SDHCI_ACPI_RUNTIME_PM = BIT(1), + SDHCI_ACPI_SD_CD_OVERRIDE_LEVEL = BIT(2), }; struct sdhci_acpi_chip { @@ -128,7 +129,8 @@ static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sdio = { }; static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sd = { - .flags = SDHCI_ACPI_SD_CD | SDHCI_ACPI_RUNTIME_PM, + .flags = SDHCI_ACPI_SD_CD | SDHCI_ACPI_SD_CD_OVERRIDE_LEVEL | + SDHCI_ACPI_RUNTIME_PM, .quirks2 = SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON, }; @@ -192,59 +194,6 @@ static const struct sdhci_acpi_slot *sdhci_acpi_get_slot(acpi_handle handle, return slot; } -#ifdef CONFIG_PM_RUNTIME - -static irqreturn_t sdhci_acpi_sd_cd(int irq, void *dev_id) -{ - mmc_detect_change(dev_id, msecs_to_jiffies(200)); - return IRQ_HANDLED; -} - -static int sdhci_acpi_add_own_cd(struct device *dev, struct mmc_host *mmc) -{ - struct gpio_desc *desc; - unsigned long flags; - int err, irq; - - desc = devm_gpiod_get_index(dev, "sd_cd", 0); - if (IS_ERR(desc)) { - err = PTR_ERR(desc); - goto out; - } - - err = gpiod_direction_input(desc); - if (err) - goto out_free; - - irq = gpiod_to_irq(desc); - if (irq < 0) { - err = irq; - goto out_free; - } - - flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; - err = devm_request_irq(dev, irq, sdhci_acpi_sd_cd, flags, "sd_cd", mmc); - if (err) - goto out_free; - - return 0; - -out_free: - devm_gpiod_put(dev, desc); -out: - dev_warn(dev, "failed to setup card detect wake up\n"); - return err; -} - -#else - -static int sdhci_acpi_add_own_cd(struct device *dev, struct mmc_host *mmc) -{ - return 0; -} - -#endif - static int sdhci_acpi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -332,15 +281,19 @@ static int sdhci_acpi_probe(struct platform_device *pdev) host->mmc->caps2 |= MMC_CAP2_NO_PRESCAN_POWERUP; - err = sdhci_add_host(host); - if (err) - goto err_free; - if (sdhci_acpi_flag(c, SDHCI_ACPI_SD_CD)) { - if (sdhci_acpi_add_own_cd(dev, host->mmc)) + bool v = sdhci_acpi_flag(c, SDHCI_ACPI_SD_CD_OVERRIDE_LEVEL); + + if (mmc_gpiod_request_cd(host->mmc, NULL, 0, v, 0)) { + dev_warn(dev, "failed to setup card detect gpio\n"); c->use_runtime_pm = false; + } } + err = sdhci_add_host(host); + if (err) + goto err_free; + if (c->use_runtime_pm) { pm_runtime_set_active(dev); pm_suspend_ignore_children(dev, 1); -- cgit v1.2.3 From aad95dc49c6dad19b49af7cd90c53473ec0536d1 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 10 Mar 2014 15:02:43 +0200 Subject: mmc: sdhci-acpi: Add device id 80860F16 Add ACPI HID 80860F16 as a host controller for a SD card. Signed-off-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-acpi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/host/sdhci-acpi.c b/drivers/mmc/host/sdhci-acpi.c index 98c7420c5f75..0d372f02dc6a 100644 --- a/drivers/mmc/host/sdhci-acpi.c +++ b/drivers/mmc/host/sdhci-acpi.c @@ -143,6 +143,7 @@ struct sdhci_acpi_uid_slot { static const struct sdhci_acpi_uid_slot sdhci_acpi_uids[] = { { "80860F14" , "1" , &sdhci_acpi_slot_int_emmc }, { "80860F14" , "3" , &sdhci_acpi_slot_int_sd }, + { "80860F16" , NULL, &sdhci_acpi_slot_int_sd }, { "INT33BB" , "2" , &sdhci_acpi_slot_int_sdio }, { "INT33C6" , NULL, &sdhci_acpi_slot_int_sdio }, { "INT3436" , NULL, &sdhci_acpi_slot_int_sdio }, @@ -152,6 +153,7 @@ static const struct sdhci_acpi_uid_slot sdhci_acpi_uids[] = { static const struct acpi_device_id sdhci_acpi_ids[] = { { "80860F14" }, + { "80860F16" }, { "INT33BB" }, { "INT33C6" }, { "INT3436" }, -- cgit v1.2.3 From 655bca7616bf6076d30b14d1478bca6807d49c45 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 11 Mar 2014 10:09:36 +0200 Subject: mmc: sdhci: Allow for irq being shared If the SDHCI irq is shared with another device then the interrupt handler can get called while SDHCI is runtime suspended. That is harmless but the warning message is not useful so remove it. Also returning IRQ_NONE is more appropriate. Signed-off-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 7f95211e9449..04a5e257a2ab 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2434,9 +2434,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) if (host->runtime_suspended) { spin_unlock(&host->lock); - pr_warning("%s: got irq while runtime suspended\n", - mmc_hostname(host->mmc)); - return IRQ_HANDLED; + return IRQ_NONE; } intmask = sdhci_readl(host, SDHCI_INT_STATUS); -- cgit v1.2.3 From fc2bd2e492a1e4f131a91c69c21c65be9e2e0125 Mon Sep 17 00:00:00 2001 From: Christian Daudt Date: Mon, 23 Sep 2013 10:20:35 -0700 Subject: mmc: rename ARCH_BCM to ARCH_BCM_MOBILE Currently ARCH_BCM has been used for Broadcom Mobile V7 based SoCs. In order to allow other Broadcom SoCs to also use mach-bcm directory and files, this patch renames the original ARCH_BCM to ARCH_BCM_MOBILE, and uses ARCH_BCM to define any Broadcom chip residing in mach-bcm directory. Signed-off-by: Christian Daudt Acked-by: Olof Johansson Signed-off-by: Chris Ball --- drivers/mmc/host/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 82cc34df1af7..8555a7afa056 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -263,7 +263,7 @@ config MMC_SDHCI_S3C_DMA config MMC_SDHCI_BCM_KONA tristate "SDHCI support on Broadcom KONA platform" - depends on ARCH_BCM + depends on ARCH_BCM_MOBILE select MMC_SDHCI_PLTFM help This selects the Broadcom Kona Secure Digital Host Controller -- cgit v1.2.3 From 2bc024855e881c697fc9bf9799f377c392192a43 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Sat, 4 Jan 2014 13:51:26 +0100 Subject: sdhci: only reprogram retuning timer when flag is set When the host->tuning_count is zero it means that the retuning is disabled. This is checked on the first run of sdhci_execute_tuning() by the if statement below: if (!(host->flags & SDHCI_NEEDS_RETUNING) && host->tuning_count && (host->tuning_mode == SDHCI_TUNING_MODE_1)) { So only when tuning_count is non-zero it will set the host flag SDHCI_USING_RETUNING_TIMER. The else statement is only for re-programming the timer, which means that flag must be set. Because that is not checked the else statement is executed in the first run when tuning_count is zero. This was seen on a host controller which indicated SDHCI_TUNING_MODE_1 (0) and tuning_count being zero. Suspect that (one of) these registers is not properly set. Signed-off-by: Arend van Spriel Acked-by: Ulf Hansson Reviewed-by: Aaron Lu Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 04a5e257a2ab..9a79fc4b60ca 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2026,12 +2026,11 @@ out: host->tuning_count * HZ); /* Tuning mode 1 limits the maximum data length to 4MB */ mmc->max_blk_count = (4 * 1024 * 1024) / mmc->max_blk_size; - } else { + } else if (host->flags & SDHCI_USING_RETUNING_TIMER) { host->flags &= ~SDHCI_NEEDS_RETUNING; /* Reload the new initial value for timer */ - if (host->tuning_mode == SDHCI_TUNING_MODE_1) - mod_timer(&host->tuning_timer, jiffies + - host->tuning_count * HZ); + mod_timer(&host->tuning_timer, jiffies + + host->tuning_count * HZ); } /* -- cgit v1.2.3 From d2529500813c77db1d0fc9d23da0de05486859d8 Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Mon, 10 Mar 2014 17:37:11 +0200 Subject: mmc: sdhci-msm: Qualcomm SDHCI binding documentation This patch adds the device-tree binding documentation for Qualcomm SDHCI driver. It contains the differences between the core properties in mmc.txt and the properties used by the sdhci-msm driver. Signed-off-by: Georgi Djakov Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- .../devicetree/bindings/mmc/sdhci-msm.txt | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/sdhci-msm.txt diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt new file mode 100644 index 000000000000..81b33b5b20fc --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -0,0 +1,55 @@ +* Qualcomm SDHCI controller (sdhci-msm) + +This file documents differences between the core properties in mmc.txt +and the properties used by the sdhci-msm driver. + +Required properties: +- compatible: Should contain "qcom,sdhci-msm-v4". +- reg: Base address and length of the register in the following order: + - Host controller register map (required) + - SD Core register map (required) +- interrupts: Should contain an interrupt-specifiers for the interrupts: + - Host controller interrupt (required) +- pinctrl-names: Should contain only one value - "default". +- pinctrl-0: Should specify pin control groups used for this controller. +- clocks: A list of phandle + clock-specifier pairs for the clocks listed in clock-names. +- clock-names: Should contain the following: + "iface" - Main peripheral bus clock (PCLK/HCLK - AHB Bus clock) (required) + "core" - SDC MMC clock (MCLK) (required) + "bus" - SDCC bus voter clock (optional) + +Example: + + sdhc_1: sdhci@f9824900 { + compatible = "qcom,sdhci-msm-v4"; + reg = <0xf9824900 0x11c>, <0xf9824000 0x800>; + interrupts = <0 123 0>; + bus-width = <8>; + non-removable; + + vmmc = <&pm8941_l20>; + vqmmc = <&pm8941_s3>; + + pinctrl-names = "default"; + pinctrl-0 = <&sdc1_clk &sdc1_cmd &sdc1_data>; + + clocks = <&gcc GCC_SDCC1_APPS_CLK>, <&gcc GCC_SDCC1_AHB_CLK>; + clock-names = "core", "iface"; + }; + + sdhc_2: sdhci@f98a4900 { + compatible = "qcom,sdhci-msm-v4"; + reg = <0xf98a4900 0x11c>, <0xf98a4000 0x800>; + interrupts = <0 125 0>; + bus-width = <4>; + cd-gpios = <&msmgpio 62 0x1>; + + vmmc = <&pm8941_l21>; + vqmmc = <&pm8941_l13>; + + pinctrl-names = "default"; + pinctrl-0 = <&sdc2_clk &sdc2_cmd &sdc2_data>; + + clocks = <&gcc GCC_SDCC2_APPS_CLK>, <&gcc GCC_SDCC2_AHB_CLK>; + clock-names = "core", "iface"; + }; -- cgit v1.2.3 From 0eb0d9f4de34a7702974807e2107a49c6c6e2daa Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Mon, 10 Mar 2014 17:37:12 +0200 Subject: mmc: sdhci-msm: Initial support for Qualcomm chipsets This platform driver adds the initial support of Secure Digital Host Controller Interface compliant controller found in Qualcomm chipsets. Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan Tested-by: Ivan T. Ivanov Signed-off-by: Georgi Djakov Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/host/Kconfig | 13 +++ drivers/mmc/host/Makefile | 1 + drivers/mmc/host/sdhci-msm.c | 208 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 drivers/mmc/host/sdhci-msm.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 8555a7afa056..8aaf8c1f3f63 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -334,6 +334,19 @@ config MMC_ATMELMCI If unsure, say N. +config MMC_SDHCI_MSM + tristate "Qualcomm SDHCI Controller Support" + depends on ARCH_QCOM + depends on MMC_SDHCI_PLTFM + help + This selects the Secure Digital Host Controller Interface (SDHCI) + support present in Qualcomm SOCs. The controller supports + SD/MMC/SDIO devices. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_MSM tristate "Qualcomm SDCC Controller Support" depends on MMC && (ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50) diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index f162f87a049c..0c8aa5e1e304 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o +obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o ifeq ($(CONFIG_CB710_DEBUG),y) CFLAGS-cb710-mmc += -DDEBUG diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c new file mode 100644 index 000000000000..3b0606fe6891 --- /dev/null +++ b/drivers/mmc/host/sdhci-msm.c @@ -0,0 +1,208 @@ +/* + * drivers/mmc/host/sdhci-msm.c - Qualcomm SDHCI Platform driver + * + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#include +#include +#include +#include + +#include "sdhci-pltfm.h" + +#define CORE_HC_MODE 0x78 +#define HC_MODE_EN 0x1 +#define CORE_POWER 0x0 +#define CORE_SW_RST BIT(7) + + +struct sdhci_msm_host { + struct platform_device *pdev; + void __iomem *core_mem; /* MSM SDCC mapped address */ + struct clk *clk; /* main SD/MMC bus clock */ + struct clk *pclk; /* SDHC peripheral bus clock */ + struct clk *bus_clk; /* SDHC bus voter clock */ + struct mmc_host *mmc; + struct sdhci_pltfm_data sdhci_msm_pdata; +}; + +/* Platform specific tuning */ +static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) +{ + /* + * Tuning is required for SDR104, HS200 and HS400 cards and if the clock + * frequency greater than 100MHz in those modes. The standard tuning + * procedure should not be executed, but a custom implementation will be + * added here instead. + */ + return 0; +} + +static const struct of_device_id sdhci_msm_dt_match[] = { + { .compatible = "qcom,sdhci-msm-v4" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match); + +static struct sdhci_ops sdhci_msm_ops = { + .platform_execute_tuning = sdhci_msm_execute_tuning, +}; + +static int sdhci_msm_probe(struct platform_device *pdev) +{ + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_msm_host *msm_host; + struct resource *core_memres; + int ret; + u16 host_version; + + msm_host = devm_kzalloc(&pdev->dev, sizeof(*msm_host), GFP_KERNEL); + if (!msm_host) + return -ENOMEM; + + msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops; + host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata, 0); + if (IS_ERR(host)) + return PTR_ERR(host); + + pltfm_host = sdhci_priv(host); + pltfm_host->priv = msm_host; + msm_host->mmc = host->mmc; + msm_host->pdev = pdev; + + ret = mmc_of_parse(host->mmc); + if (ret) + goto pltfm_free; + + sdhci_get_of_property(pdev); + + /* Setup SDCC bus voter clock. */ + msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus"); + if (!IS_ERR(msm_host->bus_clk)) { + /* Vote for max. clk rate for max. performance */ + ret = clk_set_rate(msm_host->bus_clk, INT_MAX); + if (ret) + goto pltfm_free; + ret = clk_prepare_enable(msm_host->bus_clk); + if (ret) + goto pltfm_free; + } + + /* Setup main peripheral bus clock */ + msm_host->pclk = devm_clk_get(&pdev->dev, "iface"); + if (IS_ERR(msm_host->pclk)) { + ret = PTR_ERR(msm_host->pclk); + dev_err(&pdev->dev, "Perpheral clk setup failed (%d)\n", ret); + goto bus_clk_disable; + } + + ret = clk_prepare_enable(msm_host->pclk); + if (ret) + goto bus_clk_disable; + + /* Setup SDC MMC clock */ + msm_host->clk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(msm_host->clk)) { + ret = PTR_ERR(msm_host->clk); + dev_err(&pdev->dev, "SDC MMC clk setup failed (%d)\n", ret); + goto pclk_disable; + } + + ret = clk_prepare_enable(msm_host->clk); + if (ret) + goto pclk_disable; + + core_memres = platform_get_resource(pdev, IORESOURCE_MEM, 1); + msm_host->core_mem = devm_ioremap_resource(&pdev->dev, core_memres); + + if (IS_ERR(msm_host->core_mem)) { + dev_err(&pdev->dev, "Failed to remap registers\n"); + ret = PTR_ERR(msm_host->core_mem); + goto clk_disable; + } + + /* Reset the core and Enable SDHC mode */ + writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) | + CORE_SW_RST, msm_host->core_mem + CORE_POWER); + + /* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */ + usleep_range(1000, 5000); + if (readl(msm_host->core_mem + CORE_POWER) & CORE_SW_RST) { + dev_err(&pdev->dev, "Stuck in reset\n"); + ret = -ETIMEDOUT; + goto clk_disable; + } + + /* Set HC_MODE_EN bit in HC_MODE register */ + writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); + + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; + + host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); + dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", + host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >> + SDHCI_VENDOR_VER_SHIFT)); + + ret = sdhci_add_host(host); + if (ret) + goto clk_disable; + + return 0; + +clk_disable: + clk_disable_unprepare(msm_host->clk); +pclk_disable: + clk_disable_unprepare(msm_host->pclk); +bus_clk_disable: + if (!IS_ERR(msm_host->bus_clk)) + clk_disable_unprepare(msm_host->bus_clk); +pltfm_free: + sdhci_pltfm_free(pdev); + return ret; +} + +static int sdhci_msm_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == + 0xffffffff); + + sdhci_remove_host(host, dead); + sdhci_pltfm_free(pdev); + clk_disable_unprepare(msm_host->clk); + clk_disable_unprepare(msm_host->pclk); + if (!IS_ERR(msm_host->bus_clk)) + clk_disable_unprepare(msm_host->bus_clk); + return 0; +} + +static struct platform_driver sdhci_msm_driver = { + .probe = sdhci_msm_probe, + .remove = sdhci_msm_remove, + .driver = { + .name = "sdhci_msm", + .owner = THIS_MODULE, + .of_match_table = sdhci_msm_dt_match, + }, +}; + +module_platform_driver(sdhci_msm_driver); + +MODULE_DESCRIPTION("Qualcomm Secure Digital Host Controller Interface driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 415b5a75da4309fe56c9b37e246faad80a97e525 Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Mon, 10 Mar 2014 17:37:13 +0200 Subject: mmc: sdhci-msm: Add platform_execute_tuning implementation This patch adds implementation for platform specific tuning in order to support HS200 bus speed mode on Qualcomm SDHCI controller. Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan Signed-off-by: Georgi Djakov Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-msm.c | 420 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 415 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 3b0606fe6891..acb0e9eb55f1 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "sdhci-pltfm.h" @@ -26,6 +28,42 @@ #define CORE_POWER 0x0 #define CORE_SW_RST BIT(7) +#define MAX_PHASES 16 +#define CORE_DLL_LOCK BIT(7) +#define CORE_DLL_EN BIT(16) +#define CORE_CDR_EN BIT(17) +#define CORE_CK_OUT_EN BIT(18) +#define CORE_CDR_EXT_EN BIT(19) +#define CORE_DLL_PDN BIT(29) +#define CORE_DLL_RST BIT(30) +#define CORE_DLL_CONFIG 0x100 +#define CORE_DLL_STATUS 0x108 + +#define CORE_VENDOR_SPEC 0x10c +#define CORE_CLK_PWRSAVE BIT(1) + +#define CDR_SELEXT_SHIFT 20 +#define CDR_SELEXT_MASK (0xf << CDR_SELEXT_SHIFT) +#define CMUX_SHIFT_PHASE_SHIFT 24 +#define CMUX_SHIFT_PHASE_MASK (7 << CMUX_SHIFT_PHASE_SHIFT) + +static const u32 tuning_block_64[] = { + 0x00ff0fff, 0xccc3ccff, 0xffcc3cc3, 0xeffefffe, + 0xddffdfff, 0xfbfffbff, 0xff7fffbf, 0xefbdf777, + 0xf0fff0ff, 0x3cccfc0f, 0xcfcc33cc, 0xeeffefff, + 0xfdfffdff, 0xffbfffdf, 0xfff7ffbb, 0xde7b7ff7 +}; + +static const u32 tuning_block_128[] = { + 0xff00ffff, 0x0000ffff, 0xccccffff, 0xcccc33cc, + 0xcc3333cc, 0xffffcccc, 0xffffeeff, 0xffeeeeff, + 0xffddffff, 0xddddffff, 0xbbffffff, 0xbbffffff, + 0xffffffbb, 0xffffff77, 0x77ff7777, 0xffeeddbb, + 0x00ffffff, 0x00ffffff, 0xccffff00, 0xcc33cccc, + 0x3333cccc, 0xffcccccc, 0xffeeffff, 0xeeeeffff, + 0xddffffff, 0xddffffff, 0xffffffdd, 0xffffffbb, + 0xffffbbbb, 0xffff77ff, 0xff7777ff, 0xeeddbb77 +}; struct sdhci_msm_host { struct platform_device *pdev; @@ -38,17 +76,389 @@ struct sdhci_msm_host { }; /* Platform specific tuning */ -static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) +static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host, u8 poll) +{ + u32 wait_cnt = 50; + u8 ck_out_en; + struct mmc_host *mmc = host->mmc; + + /* Poll for CK_OUT_EN bit. max. poll time = 50us */ + ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) & + CORE_CK_OUT_EN); + + while (ck_out_en != poll) { + if (--wait_cnt == 0) { + dev_err(mmc_dev(mmc), "%s: CK_OUT_EN bit is not %d\n", + mmc_hostname(mmc), poll); + return -ETIMEDOUT; + } + udelay(1); + + ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) & + CORE_CK_OUT_EN); + } + + return 0; +} + +static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase) +{ + int rc; + static const u8 grey_coded_phase_table[] = { + 0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4, + 0xc, 0xd, 0xf, 0xe, 0xa, 0xb, 0x9, 0x8 + }; + unsigned long flags; + u32 config; + struct mmc_host *mmc = host->mmc; + + spin_lock_irqsave(&host->lock, flags); + + config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); + config &= ~(CORE_CDR_EN | CORE_CK_OUT_EN); + config |= (CORE_CDR_EXT_EN | CORE_DLL_EN); + writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); + + /* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '0' */ + rc = msm_dll_poll_ck_out_en(host, 0); + if (rc) + goto err_out; + + /* + * Write the selected DLL clock output phase (0 ... 15) + * to CDR_SELEXT bit field of DLL_CONFIG register. + */ + config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); + config &= ~CDR_SELEXT_MASK; + config |= grey_coded_phase_table[phase] << CDR_SELEXT_SHIFT; + writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); + + /* Set CK_OUT_EN bit of DLL_CONFIG register to 1. */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG); + + /* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '1' */ + rc = msm_dll_poll_ck_out_en(host, 1); + if (rc) + goto err_out; + + config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); + config |= CORE_CDR_EN; + config &= ~CORE_CDR_EXT_EN; + writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); + goto out; + +err_out: + dev_err(mmc_dev(mmc), "%s: Failed to set DLL phase: %d\n", + mmc_hostname(mmc), phase); +out: + spin_unlock_irqrestore(&host->lock, flags); + return rc; +} + +/* + * Find out the greatest range of consecuitive selected + * DLL clock output phases that can be used as sampling + * setting for SD3.0 UHS-I card read operation (in SDR104 + * timing mode) or for eMMC4.5 card read operation (in HS200 + * timing mode). + * Select the 3/4 of the range and configure the DLL with the + * selected DLL clock output phase. + */ + +static int msm_find_most_appropriate_phase(struct sdhci_host *host, + u8 *phase_table, u8 total_phases) +{ + int ret; + u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} }; + u8 phases_per_row[MAX_PHASES] = { 0 }; + int row_index = 0, col_index = 0, selected_row_index = 0, curr_max = 0; + int i, cnt, phase_0_raw_index = 0, phase_15_raw_index = 0; + bool phase_0_found = false, phase_15_found = false; + struct mmc_host *mmc = host->mmc; + + if (!total_phases || (total_phases > MAX_PHASES)) { + dev_err(mmc_dev(mmc), "%s: Invalid argument: total_phases=%d\n", + mmc_hostname(mmc), total_phases); + return -EINVAL; + } + + for (cnt = 0; cnt < total_phases; cnt++) { + ranges[row_index][col_index] = phase_table[cnt]; + phases_per_row[row_index] += 1; + col_index++; + + if ((cnt + 1) == total_phases) { + continue; + /* check if next phase in phase_table is consecutive or not */ + } else if ((phase_table[cnt] + 1) != phase_table[cnt + 1]) { + row_index++; + col_index = 0; + } + } + + if (row_index >= MAX_PHASES) + return -EINVAL; + + /* Check if phase-0 is present in first valid window? */ + if (!ranges[0][0]) { + phase_0_found = true; + phase_0_raw_index = 0; + /* Check if cycle exist between 2 valid windows */ + for (cnt = 1; cnt <= row_index; cnt++) { + if (phases_per_row[cnt]) { + for (i = 0; i < phases_per_row[cnt]; i++) { + if (ranges[cnt][i] == 15) { + phase_15_found = true; + phase_15_raw_index = cnt; + break; + } + } + } + } + } + + /* If 2 valid windows form cycle then merge them as single window */ + if (phase_0_found && phase_15_found) { + /* number of phases in raw where phase 0 is present */ + u8 phases_0 = phases_per_row[phase_0_raw_index]; + /* number of phases in raw where phase 15 is present */ + u8 phases_15 = phases_per_row[phase_15_raw_index]; + + if (phases_0 + phases_15 >= MAX_PHASES) + /* + * If there are more than 1 phase windows then total + * number of phases in both the windows should not be + * more than or equal to MAX_PHASES. + */ + return -EINVAL; + + /* Merge 2 cyclic windows */ + i = phases_15; + for (cnt = 0; cnt < phases_0; cnt++) { + ranges[phase_15_raw_index][i] = + ranges[phase_0_raw_index][cnt]; + if (++i >= MAX_PHASES) + break; + } + + phases_per_row[phase_0_raw_index] = 0; + phases_per_row[phase_15_raw_index] = phases_15 + phases_0; + } + + for (cnt = 0; cnt <= row_index; cnt++) { + if (phases_per_row[cnt] > curr_max) { + curr_max = phases_per_row[cnt]; + selected_row_index = cnt; + } + } + + i = (curr_max * 3) / 4; + if (i) + i--; + + ret = ranges[selected_row_index][i]; + + if (ret >= MAX_PHASES) { + ret = -EINVAL; + dev_err(mmc_dev(mmc), "%s: Invalid phase selected=%d\n", + mmc_hostname(mmc), ret); + } + + return ret; +} + +static inline void msm_cm_dll_set_freq(struct sdhci_host *host) +{ + u32 mclk_freq = 0, config; + + /* Program the MCLK value to MCLK_FREQ bit field */ + if (host->clock <= 112000000) + mclk_freq = 0; + else if (host->clock <= 125000000) + mclk_freq = 1; + else if (host->clock <= 137000000) + mclk_freq = 2; + else if (host->clock <= 150000000) + mclk_freq = 3; + else if (host->clock <= 162000000) + mclk_freq = 4; + else if (host->clock <= 175000000) + mclk_freq = 5; + else if (host->clock <= 187000000) + mclk_freq = 6; + else if (host->clock <= 200000000) + mclk_freq = 7; + + config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); + config &= ~CMUX_SHIFT_PHASE_MASK; + config |= mclk_freq << CMUX_SHIFT_PHASE_SHIFT; + writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); +} + +/* Initialize the DLL (Programmable Delay Line) */ +static int msm_init_cm_dll(struct sdhci_host *host) { + struct mmc_host *mmc = host->mmc; + int wait_cnt = 50; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + /* - * Tuning is required for SDR104, HS200 and HS400 cards and if the clock - * frequency greater than 100MHz in those modes. The standard tuning - * procedure should not be executed, but a custom implementation will be - * added here instead. + * Make sure that clock is always enabled when DLL + * tuning is in progress. Keeping PWRSAVE ON may + * turn off the clock. */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_CLK_PWRSAVE), host->ioaddr + CORE_VENDOR_SPEC); + + /* Write 1 to DLL_RST bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); + + /* Write 1 to DLL_PDN bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); + msm_cm_dll_set_freq(host); + + /* Write 0 to DLL_RST bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + & ~CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); + + /* Write 0 to DLL_PDN bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + & ~CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); + + /* Set DLL_EN bit to 1. */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_EN), host->ioaddr + CORE_DLL_CONFIG); + + /* Set CK_OUT_EN bit to 1. */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG); + + /* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */ + while (!(readl_relaxed(host->ioaddr + CORE_DLL_STATUS) & + CORE_DLL_LOCK)) { + /* max. wait for 50us sec for LOCK bit to be set */ + if (--wait_cnt == 0) { + dev_err(mmc_dev(mmc), "%s: DLL failed to LOCK\n", + mmc_hostname(mmc)); + spin_unlock_irqrestore(&host->lock, flags); + return -ETIMEDOUT; + } + udelay(1); + } + + spin_unlock_irqrestore(&host->lock, flags); return 0; } +static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) +{ + int tuning_seq_cnt = 3; + u8 phase, *data_buf, tuned_phases[16], tuned_phase_cnt = 0; + const u32 *tuning_block_pattern = tuning_block_64; + int size = sizeof(tuning_block_64); /* Pattern size in bytes */ + int rc; + struct mmc_host *mmc = host->mmc; + struct mmc_ios ios = host->mmc->ios; + + /* + * Tuning is required for SDR104, HS200 and HS400 cards and + * if clock frequency is greater than 100MHz in these modes. + */ + if (host->clock <= 100 * 1000 * 1000 || + !((ios.timing == MMC_TIMING_MMC_HS200) || + (ios.timing == MMC_TIMING_UHS_SDR104))) + return 0; + + if ((opcode == MMC_SEND_TUNING_BLOCK_HS200) && + (mmc->ios.bus_width == MMC_BUS_WIDTH_8)) { + tuning_block_pattern = tuning_block_128; + size = sizeof(tuning_block_128); + } + + data_buf = kmalloc(size, GFP_KERNEL); + if (!data_buf) + return -ENOMEM; + +retry: + /* First of all reset the tuning block */ + rc = msm_init_cm_dll(host); + if (rc) + goto out; + + phase = 0; + do { + struct mmc_command cmd = { 0 }; + struct mmc_data data = { 0 }; + struct mmc_request mrq = { + .cmd = &cmd, + .data = &data + }; + struct scatterlist sg; + + /* Set the phase in delay line hw block */ + rc = msm_config_cm_dll_phase(host, phase); + if (rc) + goto out; + + cmd.opcode = opcode; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + + data.blksz = size; + data.blocks = 1; + data.flags = MMC_DATA_READ; + data.timeout_ns = NSEC_PER_SEC; /* 1 second */ + + data.sg = &sg; + data.sg_len = 1; + sg_init_one(&sg, data_buf, size); + memset(data_buf, 0, size); + mmc_wait_for_req(mmc, &mrq); + + if (!cmd.error && !data.error && + !memcmp(data_buf, tuning_block_pattern, size)) { + /* Tuning is successful at this tuning point */ + tuned_phases[tuned_phase_cnt++] = phase; + dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n", + mmc_hostname(mmc), phase); + } + } while (++phase < ARRAY_SIZE(tuned_phases)); + + if (tuned_phase_cnt) { + rc = msm_find_most_appropriate_phase(host, tuned_phases, + tuned_phase_cnt); + if (rc < 0) + goto out; + else + phase = rc; + + /* + * Finally set the selected phase in delay + * line hw block. + */ + rc = msm_config_cm_dll_phase(host, phase); + if (rc) + goto out; + dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n", + mmc_hostname(mmc), phase); + } else { + if (--tuning_seq_cnt) + goto retry; + /* Tuning failed */ + dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n", + mmc_hostname(mmc)); + rc = -EIO; + } + +out: + kfree(data_buf); + return rc; +} + static const struct of_device_id sdhci_msm_dt_match[] = { { .compatible = "qcom,sdhci-msm-v4" }, {}, -- cgit v1.2.3 From 5491ce3f79eed79f68fcc2dc4c10aeafe00215a6 Mon Sep 17 00:00:00 2001 From: Marcin Wojtas Date: Tue, 18 Feb 2014 16:08:29 +0100 Subject: mmc: sdhci-pxav3: add support for the Armada 38x SDHCI controller The SDHCI unit used on the Armada 380 and 385 Marvell SoC is similar to the PXAv3 unit. The only difference is that on Armada 38x, the PXAv3 unit accesses memory through MBus windows which must be configured prior to using the device. Without this, DMA would not work. In order to achieve this, the sdhci-pxav3 driver is extended with an additional compatible string "marvell,armada-380-sdhci". When this compatible string is used, the MBus windows are initialized in a way that is identical to what all other DMA-capable drivers for Marvell EBU platforms do. Signed-off-by: Marcin Wojtas Signed-off-by: Thomas Petazzoni Signed-off-by: Chris Ball --- .../devicetree/bindings/mmc/sdhci-pxa.txt | 17 +++++- drivers/mmc/host/sdhci-pxav3.c | 68 ++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-pxa.txt b/Documentation/devicetree/bindings/mmc/sdhci-pxa.txt index dbe98a3c183a..86223c3eda90 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-pxa.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-pxa.txt @@ -4,7 +4,14 @@ This file documents differences between the core properties in mmc.txt and the properties used by the sdhci-pxav2 and sdhci-pxav3 drivers. Required properties: -- compatible: Should be "mrvl,pxav2-mmc" or "mrvl,pxav3-mmc". +- compatible: Should be "mrvl,pxav2-mmc", "mrvl,pxav3-mmc" or + "marvell,armada-380-sdhci". +- reg: + * for "mrvl,pxav2-mmc" and "mrvl,pxav3-mmc", one register area for + the SDHCI registers. + * for "marvell,armada-380-sdhci", two register areas. The first one + for the SDHCI registers themselves, and the second one for the + AXI/Mbus bridge registers of the SDHCI unit. Optional properties: - mrvl,clk-delay-cycles: Specify a number of cycles to delay for tuning. @@ -19,3 +26,11 @@ sdhci@d4280800 { non-removable; mrvl,clk-delay-cycles = <31>; }; + +sdhci@d8000 { + compatible = "marvell,armada-380-sdhci"; + reg = <0xd8000 0x1000>, <0xdc000 0x100>; + interrupts = <0 25 0x4>; + clocks = <&gateclk 17>; + mrvl,clk-delay-cycles = <0x1F>; +}; diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index 793dacd3b841..2fd73b38c303 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "sdhci.h" #include "sdhci-pltfm.h" @@ -57,6 +58,60 @@ #define SDCE_MISC_INT (1<<2) #define SDCE_MISC_INT_EN (1<<1) +/* + * These registers are relative to the second register region, for the + * MBus bridge. + */ +#define SDHCI_WINDOW_CTRL(i) (0x80 + ((i) << 3)) +#define SDHCI_WINDOW_BASE(i) (0x84 + ((i) << 3)) +#define SDHCI_MAX_WIN_NUM 8 + +static int mv_conf_mbus_windows(struct platform_device *pdev, + const struct mbus_dram_target_info *dram) +{ + int i; + void __iomem *regs; + struct resource *res; + + if (!dram) { + dev_err(&pdev->dev, "no mbus dram info\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "cannot get mbus registers\n"); + return -EINVAL; + } + + regs = ioremap(res->start, resource_size(res)); + if (!regs) { + dev_err(&pdev->dev, "cannot map mbus registers\n"); + return -ENOMEM; + } + + for (i = 0; i < SDHCI_MAX_WIN_NUM; i++) { + writel(0, regs + SDHCI_WINDOW_CTRL(i)); + writel(0, regs + SDHCI_WINDOW_BASE(i)); + } + + for (i = 0; i < dram->num_cs; i++) { + const struct mbus_dram_window *cs = dram->cs + i; + + /* Write size, attributes and target id to control register */ + writel(((cs->size - 1) & 0xffff0000) | + (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1, + regs + SDHCI_WINDOW_CTRL(i)); + /* Write base address to base register */ + writel(cs->base, regs + SDHCI_WINDOW_BASE(i)); + } + + iounmap(regs); + + return 0; +} + static void pxav3_set_private_registers(struct sdhci_host *host, u8 mask) { struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc)); @@ -187,6 +242,9 @@ static const struct of_device_id sdhci_pxav3_of_match[] = { { .compatible = "mrvl,pxav3-mmc", }, + { + .compatible = "marvell,armada-380-sdhci", + }, {}, }; MODULE_DEVICE_TABLE(of, sdhci_pxav3_of_match); @@ -219,6 +277,7 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; struct sdhci_host *host = NULL; struct sdhci_pxa *pxa = NULL; const struct of_device_id *match; @@ -235,6 +294,14 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) kfree(pxa); return PTR_ERR(host); } + + if (of_device_is_compatible(np, "marvell,armada-380-sdhci")) { + ret = mv_conf_mbus_windows(pdev, mv_mbus_dram_info()); + if (ret < 0) + goto err_mbus_win; + } + + pltfm_host = sdhci_priv(host); pltfm_host->priv = pxa; @@ -321,6 +388,7 @@ err_add_host: clk_disable_unprepare(clk); clk_put(clk); err_clk_get: +err_mbus_win: sdhci_pltfm_free(pdev); kfree(pxa); return ret; -- cgit v1.2.3 From c67480173f72e883235dd0ad09d90156c8f87600 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Thu, 3 Apr 2014 14:58:39 +0300 Subject: mmc: sdhci-acpi: Intel SDIO has broken card detect Intel SDIO has broken card detect so add a quirk to reflect that. Signed-off-by: Adrian Hunter Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-acpi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-acpi.c b/drivers/mmc/host/sdhci-acpi.c index 0d372f02dc6a..ebb3f392b589 100644 --- a/drivers/mmc/host/sdhci-acpi.c +++ b/drivers/mmc/host/sdhci-acpi.c @@ -122,6 +122,7 @@ static const struct sdhci_acpi_slot sdhci_acpi_slot_int_emmc = { }; static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sdio = { + .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION, .quirks2 = SDHCI_QUIRK2_HOST_OFF_CARD_ON, .caps = MMC_CAP_NONREMOVABLE | MMC_CAP_POWER_OFF_CARD, .flags = SDHCI_ACPI_RUNTIME_PM, -- cgit v1.2.3