From 6231f3de1332b2a8a90e0c598ab6acc8f1eff7c1 Mon Sep 17 00:00:00 2001 From: Philip Rakity Date: Mon, 23 Jul 2012 15:56:23 -0700 Subject: mmc: sdhci: Add regulator support for vccq (voltage regualor) On some systems the host controller does not support vccq signaling. This is supplied by a dedicated regulator (vqmmc). Add support for this regulator. Signed-off-by: Philip Rakity Signed-off-by: Chris Ball --- include/linux/mmc/sdhci.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index ac83b105bedd..fa8529a859b8 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -97,7 +97,8 @@ struct sdhci_host { const struct sdhci_ops *ops; /* Low level hw interface */ - struct regulator *vmmc; /* Power regulator */ + struct regulator *vmmc; /* Power regulator (vmmc) */ + struct regulator *vqmmc; /* Signaling regulator (vccq) */ /* Internal data */ struct mmc_host *mmc; /* MMC structure */ -- cgit v1.2.3 From 3339d1e33185798a45dbdb5ea6c0bec1c27ca5fd Mon Sep 17 00:00:00 2001 From: Johan Rudholm Date: Thu, 23 Aug 2012 13:40:55 +0200 Subject: mmc: core: Do not rescan non-removable devices If MMC_CAP_NONREMOVABLE is set, only issue a detect job on init. Signed-off-by: Johan Rudholm Acked-by: Ulf Hansson Acked-by: Linus Walleij Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 5 +++++ include/linux/mmc/host.h | 1 + 2 files changed, 6 insertions(+) (limited to 'include/linux') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 835c9f001a10..af2c4d2fd69e 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2053,6 +2053,11 @@ void mmc_rescan(struct work_struct *work) if (host->rescan_disable) return; + /* If there is a non-removable card registered, only scan once */ + if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered) + return; + host->rescan_entered = 1; + mmc_bus_get(host); /* diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index f578a71d82a6..d5d9bd4c5aa8 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -300,6 +300,7 @@ struct mmc_host { #endif int rescan_disable; /* disable card detection */ + int rescan_entered; /* used with nonremovable devices */ struct mmc_card *card; /* device attached to this host */ -- cgit v1.2.3 From 8f63795c60ef5bc3dbfcbf19c1ac64ed79d23c62 Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Wed, 19 Sep 2012 16:29:12 +0800 Subject: mmc: sdhci-pxav3: dt: Support "cd-gpios" property Tested on OLPC XO-4/MMP3, where the card detection pin for one of the controllers is a sideband GPIO. The third cell in the cd-gpios property controls whether the GPIO is active high/active low. (Also, pass host_caps2 through from platdata to the mmc host.) Acked-by: Haojian Zhuang [kliu5@marvell.com: Compile fix] Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-pxav3.c | 25 +++++++++++++++++++++++++ include/linux/platform_data/pxa_sdhci.h | 1 + 2 files changed, 26 insertions(+) (limited to 'include/linux') diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index a553b180d340..c5ff124f8f60 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -24,12 +24,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include "sdhci.h" #include "sdhci-pltfm.h" @@ -182,6 +184,7 @@ static struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev) struct device_node *np = dev->of_node; u32 bus_width; u32 clk_delay_cycles; + enum of_gpio_flags gpio_flags; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) @@ -198,6 +201,10 @@ static struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev) if (clk_delay_cycles > 0) pdata->clk_delay_cycles = clk_delay_cycles; + pdata->ext_cd_gpio = of_get_named_gpio_flags(np, "cd-gpios", 0, &gpio_flags); + if (gpio_flags != OF_GPIO_ACTIVE_LOW) + pdata->host_caps2 |= MMC_CAP2_CD_ACTIVE_HIGH; + return pdata; } #else @@ -266,8 +273,19 @@ static int __devinit sdhci_pxav3_probe(struct platform_device *pdev) host->quirks |= pdata->quirks; if (pdata->host_caps) host->mmc->caps |= pdata->host_caps; + if (pdata->host_caps2) + host->mmc->caps2 |= pdata->host_caps2; if (pdata->pm_caps) host->mmc->pm_caps |= pdata->pm_caps; + + if (gpio_is_valid(pdata->ext_cd_gpio)) { + ret = mmc_gpio_request_cd(host->mmc, pdata->ext_cd_gpio); + if (ret) { + dev_err(mmc_dev(host->mmc), + "failed to allocate card detect gpio\n"); + goto err_cd_req; + } + } } host->ops = &pxav3_sdhci_ops; @@ -285,6 +303,8 @@ static int __devinit sdhci_pxav3_probe(struct platform_device *pdev) err_add_host: clk_disable_unprepare(clk); clk_put(clk); + mmc_gpio_free_cd(host->mmc); +err_cd_req: err_clk_get: sdhci_pltfm_free(pdev); kfree(pxa); @@ -296,11 +316,16 @@ static int __devexit sdhci_pxav3_remove(struct platform_device *pdev) struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_pxa *pxa = pltfm_host->priv; + struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; sdhci_remove_host(host, 1); clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); + + if (gpio_is_valid(pdata->ext_cd_gpio)) + mmc_gpio_free_cd(host->mmc); + sdhci_pltfm_free(pdev); kfree(pxa); diff --git a/include/linux/platform_data/pxa_sdhci.h b/include/linux/platform_data/pxa_sdhci.h index 51ad0995abac..59acd987ed34 100644 --- a/include/linux/platform_data/pxa_sdhci.h +++ b/include/linux/platform_data/pxa_sdhci.h @@ -49,6 +49,7 @@ struct sdhci_pxa_platdata { bool ext_cd_gpio_invert; unsigned int max_speed; unsigned int host_caps; + unsigned int host_caps2; unsigned int quirks; unsigned int pm_caps; }; -- cgit v1.2.3 From 950d56acce5d401f477b91d0177605b543d63d07 Mon Sep 17 00:00:00 2001 From: Jaehoon Chung Date: Mon, 17 Sep 2012 08:42:02 +0000 Subject: mmc: support BKOPS feature for eMMC Enable eMMC background operations (BKOPS) feature. If URGENT_BKOPS is set after a response, note that BKOPS are required. Immediately run BKOPS if required. Read/write operations should be requested during BKOPS(LEVEL-1), then issue HPI to interrupt the ongoing BKOPS and service the foreground operation. (This patch only controls the LEVEL2/3.) When repeating the writing 1GB data, at a certain time, performance is decreased. At that time, card triggers the Level-3 or Level-2. After running bkops, performance is recovered. Future considerations: * Check BKOPS_LEVEL=1 and start BKOPS in a preventive manner. * Interrupt ongoing BKOPS before powering off the card. * How do we get BKOPS_STATUS value (periodically send ext_csd command)? * If using periodic bkops, also consider runtime_pm control. Signed-off-by: Jaehoon Chung Signed-off-by: Kyungmin Park Signed-off-by: Konstantin Dorfman Reviewed-by: Maya Erez Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 160 +++++++++++++++++++++++++++++++++++++++++++-- drivers/mmc/core/mmc.c | 11 ++++ drivers/mmc/core/mmc_ops.c | 26 ++++++-- include/linux/mmc/card.h | 8 +++ include/linux/mmc/core.h | 4 ++ include/linux/mmc/mmc.h | 19 ++++++ 6 files changed, 220 insertions(+), 8 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index af2c4d2fd69e..044cd016320e 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,12 @@ #include "sd_ops.h" #include "sdio_ops.h" +/* + * Background operations can take a long time, depending on the housekeeping + * operations the card has to perform. + */ +#define MMC_BKOPS_MAX_TIMEOUT (4 * 60 * 1000) /* max time to wait in ms */ + static struct workqueue_struct *workqueue; static const unsigned freqs[] = { 400000, 300000, 200000, 100000 }; @@ -245,6 +252,70 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) host->ops->request(host, mrq); } +/** + * mmc_start_bkops - start BKOPS for supported cards + * @card: MMC card to start BKOPS + * @form_exception: A flag to indicate if this function was + * called due to an exception raised by the card + * + * Start background operations whenever requested. + * When the urgent BKOPS bit is set in a R1 command response + * then background operations should be started immediately. +*/ +void mmc_start_bkops(struct mmc_card *card, bool from_exception) +{ + int err; + int timeout; + bool use_busy_signal; + + BUG_ON(!card); + + if (!card->ext_csd.bkops_en || mmc_card_doing_bkops(card)) + return; + + err = mmc_read_bkops_status(card); + if (err) { + pr_err("%s: Failed to read bkops status: %d\n", + mmc_hostname(card->host), err); + return; + } + + if (!card->ext_csd.raw_bkops_status) + return; + + if (card->ext_csd.raw_bkops_status < EXT_CSD_BKOPS_LEVEL_2 && + from_exception) + return; + + mmc_claim_host(card->host); + if (card->ext_csd.raw_bkops_status >= EXT_CSD_BKOPS_LEVEL_2) { + timeout = MMC_BKOPS_MAX_TIMEOUT; + use_busy_signal = true; + } else { + timeout = 0; + use_busy_signal = false; + } + + err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BKOPS_START, 1, timeout, use_busy_signal); + if (err) { + pr_warn("%s: Error %d starting bkops\n", + mmc_hostname(card->host), err); + goto out; + } + + /* + * For urgent bkops status (LEVEL_2 and more) + * bkops executed synchronously, otherwise + * the operation is in progress + */ + if (!use_busy_signal) + mmc_card_set_doing_bkops(card); +out: + mmc_release_host(card->host); +} +EXPORT_SYMBOL(mmc_start_bkops); + static void mmc_wait_done(struct mmc_request *mrq) { complete(&mrq->completion); @@ -354,6 +425,14 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, if (host->areq) { mmc_wait_for_req_done(host, host->areq->mrq); err = host->areq->err_check(host->card, host->areq); + /* + * Check BKOPS urgency for each R1 response + */ + if (host->card && mmc_card_mmc(host->card) && + ((mmc_resp_type(host->areq->mrq->cmd) == MMC_RSP_R1) || + (mmc_resp_type(host->areq->mrq->cmd) == MMC_RSP_R1B)) && + (host->areq->mrq->cmd->resp[0] & R1_EXCEPTION_EVENT)) + mmc_start_bkops(host->card, true); } if (!err && areq) @@ -398,7 +477,7 @@ EXPORT_SYMBOL(mmc_wait_for_req); * @card: the MMC card associated with the HPI transfer * * Issued High Priority Interrupt, and check for card status - * util out-of prg-state. + * until out-of prg-state. */ int mmc_interrupt_hpi(struct mmc_card *card) { @@ -489,6 +568,64 @@ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries EXPORT_SYMBOL(mmc_wait_for_cmd); +/** + * mmc_stop_bkops - stop ongoing BKOPS + * @card: MMC card to check BKOPS + * + * Send HPI command to stop ongoing background operations to + * allow rapid servicing of foreground operations, e.g. read/ + * writes. Wait until the card comes out of the programming state + * to avoid errors in servicing read/write requests. + */ +int mmc_stop_bkops(struct mmc_card *card) +{ + int err = 0; + + BUG_ON(!card); + err = mmc_interrupt_hpi(card); + + /* + * If err is EINVAL, we can't issue an HPI. + * It should complete the BKOPS. + */ + if (!err || (err == -EINVAL)) { + mmc_card_clr_doing_bkops(card); + err = 0; + } + + return err; +} +EXPORT_SYMBOL(mmc_stop_bkops); + +int mmc_read_bkops_status(struct mmc_card *card) +{ + int err; + u8 *ext_csd; + + /* + * In future work, we should consider storing the entire ext_csd. + */ + ext_csd = kmalloc(512, GFP_KERNEL); + if (!ext_csd) { + pr_err("%s: could not allocate buffer to receive the ext_csd.\n", + mmc_hostname(card->host)); + return -ENOMEM; + } + + mmc_claim_host(card->host); + err = mmc_send_ext_csd(card, ext_csd); + mmc_release_host(card->host); + if (err) + goto out; + + card->ext_csd.raw_bkops_status = ext_csd[EXT_CSD_BKOPS_STATUS]; + card->ext_csd.raw_exception_status = ext_csd[EXT_CSD_EXP_EVENTS_STATUS]; +out: + kfree(ext_csd); + return err; +} +EXPORT_SYMBOL(mmc_read_bkops_status); + /** * mmc_set_data_timeout - set the timeout for a data command * @data: data phase for command @@ -2333,9 +2470,14 @@ int mmc_suspend_host(struct mmc_host *host) mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { - - if (host->bus_ops->suspend) + if (host->bus_ops->suspend) { + if (mmc_card_doing_bkops(host->card)) { + err = mmc_stop_bkops(host->card); + if (err) + goto out; + } err = host->bus_ops->suspend(host); + } if (err == -ENOSYS || !host->bus_ops->resume) { /* @@ -2417,11 +2559,21 @@ int mmc_pm_notify(struct notifier_block *notify_block, struct mmc_host *host = container_of( notify_block, struct mmc_host, pm_notify); unsigned long flags; - + int err = 0; switch (mode) { case PM_HIBERNATION_PREPARE: case PM_SUSPEND_PREPARE: + if (host->card && mmc_card_mmc(host->card) && + mmc_card_doing_bkops(host->card)) { + err = mmc_stop_bkops(host->card); + if (err) { + pr_err("%s: didn't stop bkops\n", + mmc_hostname(host)); + return err; + } + mmc_card_clr_doing_bkops(host->card); + } spin_lock_irqsave(&host->lock, flags); host->rescan_disable = 1; diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 396b25891bb9..7509de14aa78 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -463,6 +463,17 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) } if (card->ext_csd.rev >= 5) { + /* check whether the eMMC card supports BKOPS */ + if (ext_csd[EXT_CSD_BKOPS_SUPPORT] & 0x1) { + card->ext_csd.bkops = 1; + card->ext_csd.bkops_en = ext_csd[EXT_CSD_BKOPS_EN]; + card->ext_csd.raw_bkops_status = + ext_csd[EXT_CSD_BKOPS_STATUS]; + if (!card->ext_csd.bkops_en) + pr_info("%s: BKOPS_EN bit is not set\n", + mmc_hostname(card->host)); + } + /* check whether the eMMC card supports HPI */ if (ext_csd[EXT_CSD_HPI_FEATURES] & 0x1) { card->ext_csd.hpi = 1; diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 225371a28861..a0e172042e65 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -393,18 +393,19 @@ int mmc_spi_set_crc(struct mmc_host *host, int use_crc) } /** - * mmc_switch - modify EXT_CSD register + * __mmc_switch - modify EXT_CSD register * @card: the MMC card associated with the data transfer * @set: cmd set values * @index: EXT_CSD register index * @value: value to program into EXT_CSD register * @timeout_ms: timeout (ms) for operation performed by register write, * timeout of zero implies maximum possible timeout + * @use_busy_signal: use the busy signal as response type * * 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) +int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, + unsigned int timeout_ms, bool use_busy_signal) { int err; struct mmc_command cmd = {0}; @@ -418,13 +419,23 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, (index << 16) | (value << 8) | set; - cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; + cmd.flags = MMC_CMD_AC; + if (use_busy_signal) + cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B; + else + cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1; + + cmd.cmd_timeout_ms = timeout_ms; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); if (err) return err; + /* No need to check card status in case of unblocking command */ + if (!use_busy_signal) + return 0; + /* Must check status to be sure of no errors */ do { err = mmc_send_status(card, &status); @@ -449,6 +460,13 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, return 0; } +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); +} EXPORT_SYMBOL_GPL(mmc_switch); int mmc_send_status(struct mmc_card *card, u32 *status) diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 4b27f9f503e4..78cc3be85391 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -76,10 +76,13 @@ struct mmc_ext_csd { bool hpi_en; /* HPI enablebit */ bool hpi; /* HPI support bit */ unsigned int hpi_cmd; /* cmd used as HPI */ + bool bkops; /* background support bit */ + bool bkops_en; /* background enable bit */ unsigned int data_sector_size; /* 512 bytes or 4KB */ unsigned int data_tag_unit_size; /* DATA TAG UNIT size */ unsigned int boot_ro_lock; /* ro lock support */ bool boot_ro_lockable; + u8 raw_exception_status; /* 53 */ u8 raw_partition_support; /* 160 */ u8 raw_erased_mem_count; /* 181 */ u8 raw_ext_csd_structure; /* 194 */ @@ -93,6 +96,7 @@ struct mmc_ext_csd { u8 raw_sec_erase_mult; /* 230 */ u8 raw_sec_feature_support;/* 231 */ u8 raw_trim_mult; /* 232 */ + u8 raw_bkops_status; /* 246 */ u8 raw_sectors[4]; /* 212 - 4 bytes */ unsigned int feature_support; @@ -226,6 +230,7 @@ struct mmc_card { #define MMC_CARD_REMOVED (1<<7) /* card has been removed */ #define MMC_STATE_HIGHSPEED_200 (1<<8) /* card is in HS200 mode */ #define MMC_STATE_SLEEP (1<<9) /* card is in sleep state */ +#define MMC_STATE_DOING_BKOPS (1<<10) /* card is doing BKOPS */ unsigned int quirks; /* card quirks */ #define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */ #define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1) /* use func->cur_blksize */ @@ -393,6 +398,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_ext_capacity(c) ((c)->state & MMC_CARD_SDXC) #define mmc_card_removed(c) ((c) && ((c)->state & MMC_CARD_REMOVED)) #define mmc_card_is_sleep(c) ((c)->state & MMC_STATE_SLEEP) +#define mmc_card_doing_bkops(c) ((c)->state & MMC_STATE_DOING_BKOPS) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY) @@ -405,7 +411,9 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_set_ext_capacity(c) ((c)->state |= MMC_CARD_SDXC) #define mmc_card_set_removed(c) ((c)->state |= MMC_CARD_REMOVED) #define mmc_card_set_sleep(c) ((c)->state |= MMC_STATE_SLEEP) +#define mmc_card_set_doing_bkops(c) ((c)->state |= MMC_STATE_DOING_BKOPS) +#define mmc_card_clr_doing_bkops(c) ((c)->state &= ~MMC_STATE_DOING_BKOPS) #define mmc_card_clr_sleep(c) ((c)->state &= ~MMC_STATE_SLEEP) /* * Quirk add/remove for MMC products. diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 1b431c728b9a..9b9cdafc7737 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -134,6 +134,8 @@ struct mmc_host; struct mmc_card; struct mmc_async_req; +extern int mmc_stop_bkops(struct mmc_card *); +extern int mmc_read_bkops_status(struct mmc_card *); extern struct mmc_async_req *mmc_start_req(struct mmc_host *, struct mmc_async_req *, int *); extern int mmc_interrupt_hpi(struct mmc_card *); @@ -142,6 +144,8 @@ extern int mmc_wait_for_cmd(struct mmc_host *, struct mmc_command *, int); extern int mmc_app_cmd(struct mmc_host *, struct mmc_card *); 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); extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int); #define MMC_ERASE_ARG 0x00000000 diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index d425cab144d9..01e4b394029b 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -139,6 +139,7 @@ static inline bool mmc_op_multi(u32 opcode) #define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ #define R1_READY_FOR_DATA (1 << 8) /* sx, a */ #define R1_SWITCH_ERROR (1 << 7) /* sx, c */ +#define R1_EXCEPTION_EVENT (1 << 6) /* sx, a */ #define R1_APP_CMD (1 << 5) /* sr, c */ #define R1_STATE_IDLE 0 @@ -274,12 +275,15 @@ struct _mmc_csd { #define EXT_CSD_FLUSH_CACHE 32 /* W */ #define EXT_CSD_CACHE_CTRL 33 /* R/W */ #define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ +#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO */ #define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */ #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ #define EXT_CSD_HPI_MGMT 161 /* R/W */ #define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ +#define EXT_CSD_BKOPS_EN 163 /* R/W */ +#define EXT_CSD_BKOPS_START 164 /* W */ #define EXT_CSD_SANITIZE_START 165 /* W */ #define EXT_CSD_WR_REL_PARAM 166 /* RO */ #define EXT_CSD_BOOT_WP 173 /* R/W */ @@ -313,11 +317,13 @@ struct _mmc_csd { #define EXT_CSD_PWR_CL_200_360 237 /* RO */ #define EXT_CSD_PWR_CL_DDR_52_195 238 /* RO */ #define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */ +#define EXT_CSD_BKOPS_STATUS 246 /* RO */ #define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ #define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ #define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ #define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ #define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */ +#define EXT_CSD_BKOPS_SUPPORT 502 /* RO */ #define EXT_CSD_HPI_FEATURES 503 /* RO */ /* @@ -377,6 +383,19 @@ struct _mmc_csd { #define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */ #define EXT_CSD_PWR_CL_8BIT_SHIFT 4 #define EXT_CSD_PWR_CL_4BIT_SHIFT 0 +/* + * EXCEPTION_EVENT_STATUS field + */ +#define EXT_CSD_URGENT_BKOPS BIT(0) +#define EXT_CSD_DYNCAP_NEEDED BIT(1) +#define EXT_CSD_SYSPOOL_EXHAUSTED BIT(2) +#define EXT_CSD_PACKED_FAILURE BIT(3) + +/* + * BKOPS status level + */ +#define EXT_CSD_BKOPS_LEVEL_2 0x2 + /* * MMC_SWITCH access modes */ -- cgit v1.2.3 From 4a90920c6b39a3af26470cfc26b8e5ec9c4e7f3c Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:35 +0000 Subject: mmc: dw_mmc: convert copy of struct device in struct dw_mci to a reference The 'struct dw_mci' maintains a copy of the pdev->dev instance instead of maintaining a reference to that 'struct device' instance. Any resource allocated using the device resource management kernel API with the instance of 'struct device' in 'struct dw_mci' is then incorrect. Fix this by converting the copy of 'struct device' in 'struct dw_mci' to a reference. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc-pci.c | 2 +- drivers/mmc/host/dw_mmc-pltfm.c | 2 +- drivers/mmc/host/dw_mmc.c | 56 ++++++++++++++++++++--------------------- include/linux/mmc/dw_mmc.h | 2 +- 4 files changed, 31 insertions(+), 31 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/host/dw_mmc-pci.c b/drivers/mmc/host/dw_mmc-pci.c index f5ab03d32fb9..edb37e9135ae 100644 --- a/drivers/mmc/host/dw_mmc-pci.c +++ b/drivers/mmc/host/dw_mmc-pci.c @@ -59,7 +59,7 @@ static int __devinit dw_mci_pci_probe(struct pci_dev *pdev, host->irq = pdev->irq; host->irq_flags = IRQF_SHARED; - host->dev = pdev->dev; + host->dev = &pdev->dev; host->pdata = &pci_board_data; host->regs = pci_iomap(pdev, PCI_BAR_NO, COMPLETE_BAR); diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index a54d3a359ccd..528d37b93651 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -43,7 +43,7 @@ static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) goto err_free; } - host->dev = pdev->dev; + host->dev = &pdev->dev; host->irq_flags = 0; host->pdata = pdev->dev.platform_data; ret = -ENOMEM; diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 36f98c09715e..a18e73c7305e 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -266,7 +266,7 @@ static void dw_mci_start_command(struct dw_mci *host, struct mmc_command *cmd, u32 cmd_flags) { host->cmd = cmd; - dev_vdbg(&host->dev, + dev_vdbg(host->dev, "start command: ARGR=0x%08x CMDR=0x%08x\n", cmd->arg, cmd_flags); @@ -308,7 +308,7 @@ static void dw_mci_dma_cleanup(struct dw_mci *host) if (data) if (!data->host_cookie) - dma_unmap_sg(&host->dev, + dma_unmap_sg(host->dev, data->sg, data->sg_len, dw_mci_get_dma_dir(data)); @@ -334,7 +334,7 @@ static void dw_mci_idmac_complete_dma(struct dw_mci *host) { struct mmc_data *data = host->data; - dev_vdbg(&host->dev, "DMA complete\n"); + dev_vdbg(host->dev, "DMA complete\n"); host->dma_ops->cleanup(host); @@ -414,13 +414,13 @@ static int dw_mci_idmac_init(struct dw_mci *host) dma_support = (mci_readl(host, HCON) >> 16) & 0x3; if (!dma_support || dma_support > 2) { - dev_err(&host->dev, + dev_err(host->dev, "Host Controller does not support IDMA Tx.\n"); host->dma_ops = NULL; return -ENODEV; } - dev_info(&host->dev, "Using internal DMA controller.\n"); + dev_info(host->dev, "Using internal DMA controller.\n"); /* Forward link the descriptor list */ for (i = 0, p = host->sg_cpu; i < host->ring_size - 1; i++, p++) @@ -476,7 +476,7 @@ static int dw_mci_pre_dma_transfer(struct dw_mci *host, return -EINVAL; } - sg_len = dma_map_sg(&host->dev, + sg_len = dma_map_sg(host->dev, data->sg, data->sg_len, dw_mci_get_dma_dir(data)); @@ -519,7 +519,7 @@ static void dw_mci_post_req(struct mmc_host *mmc, return; if (data->host_cookie) - dma_unmap_sg(&slot->host->dev, + dma_unmap_sg(slot->host->dev, data->sg, data->sg_len, dw_mci_get_dma_dir(data)); @@ -545,7 +545,7 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) host->using_dma = 1; - dev_vdbg(&host->dev, + dev_vdbg(host->dev, "sd sg_cpu: %#lx sg_dma: %#lx sg_len: %d\n", (unsigned long)host->sg_cpu, (unsigned long)host->sg_dma, sg_len); @@ -939,12 +939,12 @@ static void dw_mci_request_end(struct dw_mci *host, struct mmc_request *mrq) slot = list_entry(host->queue.next, struct dw_mci_slot, queue_node); list_del(&slot->queue_node); - dev_vdbg(&host->dev, "list not empty: %s is next\n", + dev_vdbg(host->dev, "list not empty: %s is next\n", mmc_hostname(slot->mmc)); host->state = STATE_SENDING_CMD; dw_mci_start_request(host, slot); } else { - dev_vdbg(&host->dev, "list empty\n"); + dev_vdbg(host->dev, "list empty\n"); host->state = STATE_IDLE; } @@ -1083,7 +1083,7 @@ static void dw_mci_tasklet_func(unsigned long priv) data->bytes_xfered = 0; data->error = -ETIMEDOUT; } else { - dev_err(&host->dev, + dev_err(host->dev, "data FIFO error " "(status=%08x)\n", status); @@ -1772,7 +1772,7 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) struct mmc_host *mmc; struct dw_mci_slot *slot; - mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), &host->dev); + mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev); if (!mmc) return -ENOMEM; @@ -1884,10 +1884,10 @@ static void dw_mci_cleanup_slot(struct dw_mci_slot *slot, unsigned int id) static void dw_mci_init_dma(struct dw_mci *host) { /* Alloc memory for sg translation */ - host->sg_cpu = dma_alloc_coherent(&host->dev, PAGE_SIZE, + host->sg_cpu = dma_alloc_coherent(host->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL); if (!host->sg_cpu) { - dev_err(&host->dev, "%s: could not alloc DMA memory\n", + dev_err(host->dev, "%s: could not alloc DMA memory\n", __func__); goto no_dma; } @@ -1903,12 +1903,12 @@ static void dw_mci_init_dma(struct dw_mci *host) if (host->dma_ops->init && host->dma_ops->start && host->dma_ops->stop && host->dma_ops->cleanup) { if (host->dma_ops->init(host)) { - dev_err(&host->dev, "%s: Unable to initialize " + dev_err(host->dev, "%s: Unable to initialize " "DMA Controller.\n", __func__); goto no_dma; } } else { - dev_err(&host->dev, "DMA initialization not found.\n"); + dev_err(host->dev, "DMA initialization not found.\n"); goto no_dma; } @@ -1916,7 +1916,7 @@ static void dw_mci_init_dma(struct dw_mci *host) return; no_dma: - dev_info(&host->dev, "Using PIO mode.\n"); + dev_info(host->dev, "Using PIO mode.\n"); host->use_dma = 0; return; } @@ -1948,19 +1948,19 @@ int dw_mci_probe(struct dw_mci *host) u32 fifo_size; if (!host->pdata || !host->pdata->init) { - dev_err(&host->dev, + dev_err(host->dev, "Platform data must supply init function\n"); return -ENODEV; } if (!host->pdata->select_slot && host->pdata->num_slots > 1) { - dev_err(&host->dev, + dev_err(host->dev, "Platform data must supply select_slot function\n"); return -ENODEV; } if (!host->pdata->bus_hz) { - dev_err(&host->dev, + dev_err(host->dev, "Platform data must supply bus speed\n"); return -ENODEV; } @@ -1998,7 +1998,7 @@ int dw_mci_probe(struct dw_mci *host) } /* Reset all blocks */ - if (!mci_wait_reset(&host->dev, host)) + if (!mci_wait_reset(host->dev, host)) return -ENODEV; host->dma_ops = host->pdata->dma_ops; @@ -2065,7 +2065,7 @@ int dw_mci_probe(struct dw_mci *host) * Need to check the version-id and set data-offset for DATA register. */ host->verid = SDMMC_GET_VERID(mci_readl(host, VERID)); - dev_info(&host->dev, "Version ID is %04x\n", host->verid); + dev_info(host->dev, "Version ID is %04x\n", host->verid); if (host->verid < DW_MMC_240A) host->data_offset = DATA_OFFSET; @@ -2082,12 +2082,12 @@ int dw_mci_probe(struct dw_mci *host) DW_MCI_ERROR_FLAGS | SDMMC_INT_CD); mci_writel(host, CTRL, SDMMC_CTRL_INT_ENABLE); /* Enable mci interrupt */ - dev_info(&host->dev, "DW MMC controller at irq %d, " + dev_info(host->dev, "DW MMC controller at irq %d, " "%d bit host data width, " "%u deep fifo\n", host->irq, width, fifo_size); if (host->quirks & DW_MCI_QUIRK_IDMAC_DTO) - dev_info(&host->dev, "Internal DMAC interrupt fix enabled.\n"); + dev_info(host->dev, "Internal DMAC interrupt fix enabled.\n"); return 0; @@ -2106,7 +2106,7 @@ err_workqueue: err_dmaunmap: if (host->use_dma && host->dma_ops->exit) host->dma_ops->exit(host); - dma_free_coherent(&host->dev, PAGE_SIZE, + dma_free_coherent(host->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); if (host->vmmc) { @@ -2125,7 +2125,7 @@ void dw_mci_remove(struct dw_mci *host) mci_writel(host, INTMASK, 0); /* disable all mmc interrupt first */ for (i = 0; i < host->num_slots; i++) { - dev_dbg(&host->dev, "remove slot %d\n", i); + dev_dbg(host->dev, "remove slot %d\n", i); if (host->slot[i]) dw_mci_cleanup_slot(host->slot[i], i); } @@ -2136,7 +2136,7 @@ void dw_mci_remove(struct dw_mci *host) free_irq(host->irq, host); destroy_workqueue(host->card_workqueue); - dma_free_coherent(&host->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); + dma_free_coherent(host->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); if (host->use_dma && host->dma_ops->exit) host->dma_ops->exit(host); @@ -2188,7 +2188,7 @@ int dw_mci_resume(struct dw_mci *host) if (host->vmmc) regulator_enable(host->vmmc); - if (!mci_wait_reset(&host->dev, host)) { + if (!mci_wait_reset(host->dev, host)) { ret = -ENODEV; return ret; } diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index 7a7ebd367cfd..a37a573fa13c 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -156,7 +156,7 @@ struct dw_mci { u32 fifoth_val; u16 verid; u16 data_offset; - struct device dev; + struct device *dev; struct dw_mci_board *pdata; struct dw_mci_slot *slot[MAX_MCI_SLOTS]; -- cgit v1.2.3 From f90a0612f0e110a8af976835273124dff4fa8b3d Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:38 +0000 Subject: mmc: dw_mmc: lookup for optional biu and ciu clocks Some platforms allow for clock gating and control of bus interface unit clock and card interface unit clock. Add support for clock lookup of optional biu and ciu clocks for clock gating and clock speed determination. Signed-off-by: Abhilash Kesavan Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc.c | 52 +++++++++++++++++++++++++++++++++++++++++++--- include/linux/mmc/dw_mmc.h | 4 ++++ 2 files changed, 53 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 227c42ef18c5..de45ad24becb 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1960,13 +1960,42 @@ int dw_mci_probe(struct dw_mci *host) return -ENODEV; } - if (!host->pdata->bus_hz) { + host->biu_clk = clk_get(host->dev, "biu"); + if (IS_ERR(host->biu_clk)) { + dev_dbg(host->dev, "biu clock not available\n"); + } else { + ret = clk_prepare_enable(host->biu_clk); + if (ret) { + dev_err(host->dev, "failed to enable biu clock\n"); + clk_put(host->biu_clk); + return ret; + } + } + + host->ciu_clk = clk_get(host->dev, "ciu"); + if (IS_ERR(host->ciu_clk)) { + dev_dbg(host->dev, "ciu clock not available\n"); + } else { + ret = clk_prepare_enable(host->ciu_clk); + if (ret) { + dev_err(host->dev, "failed to enable ciu clock\n"); + clk_put(host->ciu_clk); + goto err_clk_biu; + } + } + + if (IS_ERR(host->ciu_clk)) + host->bus_hz = host->pdata->bus_hz; + else + host->bus_hz = clk_get_rate(host->ciu_clk); + + if (!host->bus_hz) { dev_err(host->dev, "Platform data must supply bus speed\n"); - return -ENODEV; + ret = -ENODEV; + goto err_clk_ciu; } - host->bus_hz = host->pdata->bus_hz; host->quirks = host->pdata->quirks; spin_lock_init(&host->lock); @@ -2116,6 +2145,17 @@ err_dmaunmap: regulator_disable(host->vmmc); regulator_put(host->vmmc); } + +err_clk_ciu: + if (!IS_ERR(host->ciu_clk)) { + clk_disable_unprepare(host->ciu_clk); + clk_put(host->ciu_clk); + } +err_clk_biu: + if (!IS_ERR(host->biu_clk)) { + clk_disable_unprepare(host->biu_clk); + clk_put(host->biu_clk); + } return ret; } EXPORT_SYMBOL(dw_mci_probe); @@ -2149,6 +2189,12 @@ void dw_mci_remove(struct dw_mci *host) regulator_put(host->vmmc); } + if (!IS_ERR(host->ciu_clk)) + clk_disable_unprepare(host->ciu_clk); + if (!IS_ERR(host->biu_clk)) + clk_disable_unprepare(host->biu_clk); + clk_put(host->ciu_clk); + clk_put(host->biu_clk); } EXPORT_SYMBOL(dw_mci_remove); diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index a37a573fa13c..787ad569c99b 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -78,6 +78,8 @@ struct mmc_data; * @data_offset: Set the offset of DATA register according to VERID. * @dev: Device associated with the MMC controller. * @pdata: Platform data associated with the MMC controller. + * @biu_clk: Pointer to bus interface unit clock instance. + * @ciu_clk: Pointer to card interface unit clock instance. * @slot: Slots sharing this MMC controller. * @fifo_depth: depth of FIFO. * @data_shift: log2 of FIFO item size. @@ -158,6 +160,8 @@ struct dw_mci { u16 data_offset; struct device *dev; struct dw_mci_board *pdata; + struct clk *biu_clk; + struct clk *ciu_clk; struct dw_mci_slot *slot[MAX_MCI_SLOTS]; /* FIFO push and pull */ -- cgit v1.2.3 From b4967aa58e2bbafbb280dd4f0c5a777181500e41 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:39 +0000 Subject: mmc: dw_mmc: add quirk to indicate missing write protect line If the write protect pad of the controller is not connected to the write protect pin of the slot, the driver should be notified of this condition so that incorrect check for write protection by reading the WRTORT register can avoided. The get_ro platform callback can be used for in such cases, but with device tree support enabled, such platform callbacks cannot be supported. Add a new quirk for notifying the driver about the missing write protect line so the driver can assume that the card write protection is disabled. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc.c | 4 +++- include/linux/mmc/dw_mmc.h | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index de45ad24becb..455a155406bc 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -830,7 +830,9 @@ static int dw_mci_get_ro(struct mmc_host *mmc) struct dw_mci_board *brd = slot->host->pdata; /* Use platform get_ro function, else try on board write protect */ - if (brd->get_ro) + if (brd->quirks & DW_MCI_QUIRK_NO_WRITE_PROTECT) + read_only = 0; + else if (brd->get_ro) read_only = brd->get_ro(slot->id); else read_only = diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index 787ad569c99b..b72e4aa91c11 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -205,7 +205,8 @@ struct dw_mci_dma_ops { #define DW_MCI_QUIRK_HIGHSPEED BIT(2) /* Unreliable card detection */ #define DW_MCI_QUIRK_BROKEN_CARD_DETECTION BIT(3) - +/* Write Protect detection not available */ +#define DW_MCI_QUIRK_NO_WRITE_PROTECT BIT(4) struct dma_pdata; -- cgit v1.2.3 From 800d78bfccb3d38116abfda2a5b9c8afdbd5ea21 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:42 +0000 Subject: mmc: dw_mmc: add support for implementation specific callbacks The core dw-mshc controller driver can let platform specific implementations of the dw-mshc controller to control the hardware as required by such implementations. This is acheived by invoking implementation specific (optional) callbacks. Define the list of callbacks supported the add invocation points for the same. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc-pltfm.c | 12 +++++++++-- drivers/mmc/host/dw_mmc-pltfm.h | 3 ++- drivers/mmc/host/dw_mmc.c | 47 ++++++++++++++++++++++++++++++++++++++++- drivers/mmc/host/dw_mmc.h | 24 +++++++++++++++++++++ include/linux/mmc/dw_mmc.h | 4 ++++ 5 files changed, 86 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index e17da912efff..c960ca7ffbe6 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -23,7 +23,8 @@ #include "dw_mmc.h" -int dw_mci_pltfm_register(struct platform_device *pdev) +int dw_mci_pltfm_register(struct platform_device *pdev, + struct dw_mci_drv_data *drv_data) { struct dw_mci *host; struct resource *regs; @@ -41,6 +42,7 @@ int dw_mci_pltfm_register(struct platform_device *pdev) if (host->irq < 0) return host->irq; + host->drv_data = drv_data; host->dev = &pdev->dev; host->irq_flags = 0; host->pdata = pdev->dev.platform_data; @@ -48,6 +50,12 @@ int dw_mci_pltfm_register(struct platform_device *pdev) if (!host->regs) return -ENOMEM; + if (host->drv_data->init) { + ret = host->drv_data->init(host); + if (ret) + return ret; + } + platform_set_drvdata(pdev, host); ret = dw_mci_probe(host); return ret; @@ -56,7 +64,7 @@ EXPORT_SYMBOL_GPL(dw_mci_pltfm_register); static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) { - return dw_mci_pltfm_register(pdev); + return dw_mci_pltfm_register(pdev, NULL); } static int __devexit dw_mci_pltfm_remove(struct platform_device *pdev) diff --git a/drivers/mmc/host/dw_mmc-pltfm.h b/drivers/mmc/host/dw_mmc-pltfm.h index 6c065d9c2dd6..301f24541fc2 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.h +++ b/drivers/mmc/host/dw_mmc-pltfm.h @@ -12,7 +12,8 @@ #ifndef _DW_MMC_PLTFM_H_ #define _DW_MMC_PLTFM_H_ -extern int dw_mci_pltfm_register(struct platform_device *pdev); +extern int dw_mci_pltfm_register(struct platform_device *pdev, + struct dw_mci_drv_data *drv_data); extern int __devexit dw_mci_pltfm_remove(struct platform_device *pdev); extern const struct dev_pm_ops dw_mci_pltfm_pmops; diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index c792466fb8ac..9f8e487bfa97 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -231,6 +231,7 @@ static void dw_mci_set_timeout(struct dw_mci *host) static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd) { struct mmc_data *data; + struct dw_mci_slot *slot = mmc_priv(mmc); u32 cmdr; cmd->error = -EINPROGRESS; @@ -260,6 +261,9 @@ static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd) cmdr |= SDMMC_CMD_DAT_WR; } + if (slot->host->drv_data->prepare_command) + slot->host->drv_data->prepare_command(slot->host, &cmdr); + return cmdr; } @@ -815,6 +819,9 @@ static void dw_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) slot->clock = ios->clock; } + if (slot->host->drv_data->set_ios) + slot->host->drv_data->set_ios(slot->host, ios); + switch (ios->power_mode) { case MMC_POWER_UP: set_bit(DW_MMC_CARD_NEED_INIT, &slot->flags); @@ -1820,6 +1827,7 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) { struct mmc_host *mmc; struct dw_mci_slot *slot; + int ctrl_id, ret; u8 bus_width; mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev); @@ -1851,6 +1859,16 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) if (host->pdata->caps) mmc->caps = host->pdata->caps; + if (host->dev->of_node) { + ctrl_id = of_alias_get_id(host->dev->of_node, "mshc"); + if (ctrl_id < 0) + ctrl_id = 0; + } else { + ctrl_id = to_platform_device(host->dev)->id; + } + if (host->drv_data && host->drv_data->caps) + mmc->caps |= host->drv_data->caps[ctrl_id]; + if (host->pdata->caps2) mmc->caps2 = host->pdata->caps2; @@ -1861,6 +1879,14 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) else bus_width = 1; + if (host->drv_data->setup_bus) { + struct device_node *slot_np; + slot_np = dw_mci_of_find_slot_node(host->dev, slot->id); + ret = host->drv_data->setup_bus(host, slot_np, bus_width); + if (ret) + goto err_setup_bus; + } + switch (bus_width) { case 8: mmc->caps |= MMC_CAP_8_BIT_DATA; @@ -1927,6 +1953,10 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) queue_work(host->card_workqueue, &host->card_work); return 0; + +err_setup_bus: + mmc_free_host(mmc); + return -EINVAL; } static void dw_mci_cleanup_slot(struct dw_mci_slot *slot, unsigned int id) @@ -2021,7 +2051,7 @@ static struct dw_mci_board *dw_mci_parse_dt(struct dw_mci *host) struct dw_mci_board *pdata; struct device *dev = host->dev; struct device_node *np = dev->of_node; - int idx; + int idx, ret; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { @@ -2048,6 +2078,12 @@ static struct dw_mci_board *dw_mci_parse_dt(struct dw_mci *host) of_property_read_u32(np, "card-detect-delay", &pdata->detect_delay_ms); + if (host->drv_data->parse_dt) { + ret = host->drv_data->parse_dt(host); + if (ret) + return ERR_PTR(ret); + } + return pdata; } @@ -2107,6 +2143,15 @@ int dw_mci_probe(struct dw_mci *host) else host->bus_hz = clk_get_rate(host->ciu_clk); + if (host->drv_data->setup_clock) { + ret = host->drv_data->setup_clock(host); + if (ret) { + dev_err(host->dev, + "implementation specific clock setup failed\n"); + goto err_clk_ciu; + } + } + if (!host->bus_hz) { dev_err(host->dev, "Platform data must supply bus speed\n"); diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 15c27e17c23f..53b8fd987e47 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -182,4 +182,28 @@ extern int dw_mci_suspend(struct dw_mci *host); extern int dw_mci_resume(struct dw_mci *host); #endif +/** + * dw_mci driver data - dw-mshc implementation specific driver data. + * @caps: mmc subsystem specified capabilities of the controller(s). + * @init: early implementation specific initialization. + * @setup_clock: implementation specific clock configuration. + * @prepare_command: handle CMD register extensions. + * @set_ios: handle bus specific extensions. + * @parse_dt: parse implementation specific device tree properties. + * @setup_bus: initialize io-interface + * + * Provide controller implementation specific extensions. The usage of this + * data structure is fully optional and usage of each member in this structure + * is optional as well. + */ +struct dw_mci_drv_data { + unsigned long *caps; + int (*init)(struct dw_mci *host); + int (*setup_clock)(struct dw_mci *host); + void (*prepare_command)(struct dw_mci *host, u32 *cmdr); + void (*set_ios)(struct dw_mci *host, struct mmc_ios *ios); + int (*parse_dt)(struct dw_mci *host); + int (*setup_bus)(struct dw_mci *host, + struct device_node *slot_np, u8 bus_width); +}; #endif /* _DW_MMC_H_ */ diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index b72e4aa91c11..6cb043e48953 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -78,6 +78,8 @@ struct mmc_data; * @data_offset: Set the offset of DATA register according to VERID. * @dev: Device associated with the MMC controller. * @pdata: Platform data associated with the MMC controller. + * @drv_data: Driver specific data for identified variant of the controller + * @priv: Implementation defined private data. * @biu_clk: Pointer to bus interface unit clock instance. * @ciu_clk: Pointer to card interface unit clock instance. * @slot: Slots sharing this MMC controller. @@ -160,6 +162,8 @@ struct dw_mci { u16 data_offset; struct device *dev; struct dw_mci_board *pdata; + struct dw_mci_drv_data *drv_data; + void *priv; struct clk *biu_clk; struct clk *ciu_clk; struct dw_mci_slot *slot[MAX_MCI_SLOTS]; -- cgit v1.2.3 From c3665006eccd7d2326b7dc2c1d5ff2f545dee6c5 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:43 +0000 Subject: mmc: dw_mmc: add support for exynos specific implementation of dw-mshc Samsung Exynos SoC's extend the dw-mshc controller for additional clock and bus control. Add support for these extensions and include provide device tree based discovery suppory as well. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball --- .../devicetree/bindings/mmc/exynos-dw-mshc.txt | 87 +++++++ drivers/mmc/host/Kconfig | 9 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/dw_mmc-exynos.c | 253 +++++++++++++++++++++ include/linux/mmc/dw_mmc.h | 2 +- 5 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt create mode 100644 drivers/mmc/host/dw_mmc-exynos.c (limited to 'include/linux') diff --git a/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt b/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt new file mode 100644 index 000000000000..792768953330 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt @@ -0,0 +1,87 @@ +* Samsung Exynos specific extensions to the Synopsis Designware Mobile + Storage Host Controller + +The Synopsis 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 Synopsis dw mshc controller properties described +by synposis-dw-mshc.txt and the properties used by the Samsung Exynos specific +extensions to the Synopsis Designware Mobile Storage Host Controller. + +Required Properties: + +* compatible: should be + - "samsung,exynos4210-dw-mshc": for controllers with Samsung Exynos4210 + specific extentions. + - "samsung,exynos4412-dw-mshc": for controllers with Samsung Exynos4412 + specific extentions. + - "samsung,exynos5250-dw-mshc": for controllers with Samsung Exynos5250 + specific extentions. + +* samsung,dw-mshc-ciu-div: Specifies the divider value for the card interface + unit (ciu) clock. This property is applicable only for Exynos5 SoC's and + ignored for Exynos4 SoC's. The valid range of divider value is 0 to 7. + +* samsung,dw-mshc-sdr-timing: Specifies the value of CIU clock phase shift value + in transmit mode and CIU clock phase shift value in receive mode for single + data rate mode operation. Refer notes below for the order of the cells and the + valid values. + +* samsung,dw-mshc-ddr-timing: Specifies the value of CUI clock phase shift value + in transmit mode and CIU clock phase shift value in receive mode for double + data rate mode operation. Refer notes below for the order of the cells and the + valid values. + + Notes for the sdr-timing and ddr-timing values: + + The order of the cells should be + - First Cell: CIU clock phase shift value for tx mode. + - Second Cell: CIU clock phase shift value for rx mode. + + Valid values for SDR and DDR CIU clock timing for Exynos5250: + - valid value for tx phase shift and rx phase shift is 0 to 7. + - when CIU clock divider value is set to 3, all possible 8 phase shift + values can be used. + - if CIU clock divider value is 0 (that is divide by 1), both tx and rx + phase shift clocks should be 0. + +Required properties for a slot: + +* gpios: specifies a list of gpios used for command, clock and data bus. The + first gpio is the command line and the second gpio is the clock line. The + rest of the gpios (depending on the bus-width property) are the data lines in + no particular order. The format of the gpio specifier depends on the gpio + controller. + +Example: + + The MSHC controller node can be split into two portions, SoC specific and + board specific portions as listed below. + + dwmmc0@12200000 { + compatible = "samsung,exynos5250-dw-mshc"; + reg = <0x12200000 0x1000>; + interrupts = <0 75 0>; + #address-cells = <1>; + #size-cells = <0>; + }; + + dwmmc0@12200000 { + num-slots = <1>; + supports-highspeed; + broken-cd; + fifo-depth = <0x80>; + card-detect-delay = <200>; + samsung,dw-mshc-ciu-div = <3>; + samsung,dw-mshc-sdr-timing = <2 3>; + samsung,dw-mshc-ddr-timing = <1 2>; + + slot@0 { + reg = <0>; + bus-width = <8>; + gpios = <&gpc0 0 2 0 3>, <&gpc0 1 2 0 3>, + <&gpc1 0 2 3 3>, <&gpc1 1 2 3 3>, + <&gpc1 2 2 3 3>, <&gpc1 3 2 3 3>, + <&gpc0 3 2 3 3>, <&gpc0 4 2 3 3>, + <&gpc0 5 2 3 3>, <&gpc0 6 2 3 3>; + }; + }; diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index aa131b32e3b2..9bf10e7bbfaf 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -540,6 +540,15 @@ config MMC_DW_PLTFM If unsure, say Y. +config MMC_DW_EXYNOS + tristate "Exynos specific extentions for Synopsys DW Memory Card Interface" + depends on MMC_DW + select MMC_DW_PLTFM + help + This selects support for Samsung Exynos SoC specific extensions to the + Synopsys DesignWare Memory Card Interface driver. Select this option + for platforms based on Exynos4 and Exynos5 SoC's. + config MMC_DW_PCI tristate "Synopsys Designware MCI support on PCI bus" depends on MMC_DW && PCI diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 8922b06be925..17ad0a7ba40b 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_DW) += dw_mmc.o obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o +obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o diff --git a/drivers/mmc/host/dw_mmc-exynos.c b/drivers/mmc/host/dw_mmc-exynos.c new file mode 100644 index 000000000000..660bbc528862 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-exynos.c @@ -0,0 +1,253 @@ +/* + * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver + * + * Copyright (C) 2012, Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dw_mmc.h" +#include "dw_mmc-pltfm.h" + +#define NUM_PINS(x) (x + 2) + +#define SDMMC_CLKSEL 0x09C +#define SDMMC_CLKSEL_CCLK_SAMPLE(x) (((x) & 7) << 0) +#define SDMMC_CLKSEL_CCLK_DRIVE(x) (((x) & 7) << 16) +#define SDMMC_CLKSEL_CCLK_DIVIDER(x) (((x) & 7) << 24) +#define SDMMC_CLKSEL_GET_DRV_WD3(x) (((x) >> 16) & 0x7) +#define SDMMC_CLKSEL_TIMING(x, y, z) (SDMMC_CLKSEL_CCLK_SAMPLE(x) | \ + SDMMC_CLKSEL_CCLK_DRIVE(y) | \ + SDMMC_CLKSEL_CCLK_DIVIDER(z)) + +#define SDMMC_CMD_USE_HOLD_REG BIT(29) + +#define EXYNOS4210_FIXED_CIU_CLK_DIV 2 +#define EXYNOS4412_FIXED_CIU_CLK_DIV 4 + +/* Variations in Exynos specific dw-mshc controller */ +enum dw_mci_exynos_type { + DW_MCI_TYPE_EXYNOS4210, + DW_MCI_TYPE_EXYNOS4412, + DW_MCI_TYPE_EXYNOS5250, +}; + +/* Exynos implementation specific driver private data */ +struct dw_mci_exynos_priv_data { + enum dw_mci_exynos_type ctrl_type; + u8 ciu_div; + u32 sdr_timing; + u32 ddr_timing; +}; + +static struct dw_mci_exynos_compatible { + char *compatible; + enum dw_mci_exynos_type ctrl_type; +} exynos_compat[] = { + { + .compatible = "samsung,exynos4210-dw-mshc", + .ctrl_type = DW_MCI_TYPE_EXYNOS4210, + }, { + .compatible = "samsung,exynos4412-dw-mshc", + .ctrl_type = DW_MCI_TYPE_EXYNOS4412, + }, { + .compatible = "samsung,exynos5250-dw-mshc", + .ctrl_type = DW_MCI_TYPE_EXYNOS5250, + }, +}; + +static int dw_mci_exynos_priv_init(struct dw_mci *host) +{ + struct dw_mci_exynos_priv_data *priv; + int idx; + + priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(host->dev, "mem alloc failed for private data\n"); + return -ENOMEM; + } + + for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) { + if (of_device_is_compatible(host->dev->of_node, + exynos_compat[idx].compatible)) + priv->ctrl_type = exynos_compat[idx].ctrl_type; + } + + host->priv = priv; + return 0; +} + +static int dw_mci_exynos_setup_clock(struct dw_mci *host) +{ + struct dw_mci_exynos_priv_data *priv = host->priv; + + if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5250) + host->bus_hz /= (priv->ciu_div + 1); + else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412) + host->bus_hz /= EXYNOS4412_FIXED_CIU_CLK_DIV; + else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210) + host->bus_hz /= EXYNOS4210_FIXED_CIU_CLK_DIV; + + return 0; +} + +static void dw_mci_exynos_prepare_command(struct dw_mci *host, u32 *cmdr) +{ + /* + * Exynos4412 and Exynos5250 extends the use of CMD register with the + * use of bit 29 (which is reserved on standard MSHC controllers) for + * optionally bypassing the HOLD register for command and data. The + * HOLD register should be bypassed in case there is no phase shift + * applied on CMD/DATA that is sent to the card. + */ + if (SDMMC_CLKSEL_GET_DRV_WD3(mci_readl(host, CLKSEL))) + *cmdr |= SDMMC_CMD_USE_HOLD_REG; +} + +static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios) +{ + struct dw_mci_exynos_priv_data *priv = host->priv; + + if (ios->timing == MMC_TIMING_UHS_DDR50) + mci_writel(host, CLKSEL, priv->ddr_timing); + else + mci_writel(host, CLKSEL, priv->sdr_timing); +} + +static int dw_mci_exynos_parse_dt(struct dw_mci *host) +{ + struct dw_mci_exynos_priv_data *priv = host->priv; + struct device_node *np = host->dev->of_node; + u32 timing[2]; + u32 div = 0; + int ret; + + of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div); + priv->ciu_div = div; + + ret = of_property_read_u32_array(np, + "samsung,dw-mshc-sdr-timing", timing, 2); + if (ret) + return ret; + + priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div); + + ret = of_property_read_u32_array(np, + "samsung,dw-mshc-ddr-timing", timing, 2); + if (ret) + return ret; + + priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div); + return 0; +} + +static int dw_mci_exynos_setup_bus(struct dw_mci *host, + struct device_node *slot_np, u8 bus_width) +{ + int idx, gpio, ret; + + if (!slot_np) + return -EINVAL; + + /* cmd + clock + bus-width pins */ + for (idx = 0; idx < NUM_PINS(bus_width); idx++) { + gpio = of_get_gpio(slot_np, idx); + if (!gpio_is_valid(gpio)) { + dev_err(host->dev, "invalid gpio: %d\n", gpio); + return -EINVAL; + } + + ret = devm_gpio_request(host->dev, gpio, "dw-mci-bus"); + if (ret) { + dev_err(host->dev, "gpio [%d] request failed\n", gpio); + return -EBUSY; + } + } + + gpio = of_get_named_gpio(slot_np, "wp-gpios", 0); + if (gpio_is_valid(gpio)) { + if (devm_gpio_request(host->dev, gpio, "dw-mci-wp")) + dev_info(host->dev, "gpio [%d] request failed\n", + gpio); + } else { + dev_info(host->dev, "wp gpio not available"); + host->pdata->quirks |= DW_MCI_QUIRK_NO_WRITE_PROTECT; + } + + if (host->pdata->quirks & DW_MCI_QUIRK_BROKEN_CARD_DETECTION) + return 0; + + gpio = of_get_named_gpio(slot_np, "samsung,cd-pinmux-gpio", 0); + if (gpio_is_valid(gpio)) { + if (devm_gpio_request(host->dev, gpio, "dw-mci-cd")) + dev_err(host->dev, "gpio [%d] request failed\n", gpio); + } else { + dev_info(host->dev, "cd gpio not available"); + } + + return 0; +} + +/* Exynos5250 controller specific capabilities */ +static unsigned long exynos5250_dwmmc_caps[4] = { + MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR | + MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23, + MMC_CAP_CMD23, + MMC_CAP_CMD23, + MMC_CAP_CMD23, +}; + +static struct dw_mci_drv_data exynos5250_drv_data = { + .caps = exynos5250_dwmmc_caps, + .init = dw_mci_exynos_priv_init, + .setup_clock = dw_mci_exynos_setup_clock, + .prepare_command = dw_mci_exynos_prepare_command, + .set_ios = dw_mci_exynos_set_ios, + .parse_dt = dw_mci_exynos_parse_dt, + .setup_bus = dw_mci_exynos_setup_bus, +}; + +static const struct of_device_id dw_mci_exynos_match[] = { + { .compatible = "samsung,exynos5250-dw-mshc", + .data = (void *)&exynos5250_drv_data, }, + {}, +}; +MODULE_DEVICE_TABLE(of, dw_mci_pltfm_match); + +int dw_mci_exynos_probe(struct platform_device *pdev) +{ + struct dw_mci_drv_data *drv_data; + const struct of_device_id *match; + + match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node); + drv_data = match->data; + return dw_mci_pltfm_register(pdev, drv_data); +} + +static struct platform_driver dw_mci_exynos_pltfm_driver = { + .probe = dw_mci_exynos_probe, + .remove = __exit_p(dw_mci_pltfm_remove), + .driver = { + .name = "dwmmc_exynos", + .of_match_table = of_match_ptr(dw_mci_exynos_match), + .pm = &dw_mci_pltfm_pmops, + }, +}; + +module_platform_driver(dw_mci_exynos_pltfm_driver); + +MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension"); +MODULE_AUTHOR("Thomas Abraham Date: Fri, 5 Oct 2012 12:45:39 -0400 Subject: mmc: core: Fixup broken suspend and eMMC4.5 power off notify This patch fixes up the broken suspend sequence for eMMC with sleep support. Additionally it reworks the eMMC4.5 Power Off Notification feature so it fits together with the existing sleep feature. The CMD0 based re-initialization of the eMMC at resume is re-introduced to maintain compatiblity for devices using sleep. A host shall use MMC_CAP2_POWEROFF_NOTIFY to enable the Power Off Notification feature. We might be able to remove this cap later on, if we think that Power Off Notification always is preferred over sleep, even if the host is not able to cut the eMMC VCCQ power. Signed-off-by: Ulf Hansson Signed-off-by: Saugata Das Acked-by: Linus Walleij Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 62 ----------------------------------------------- drivers/mmc/core/mmc.c | 46 ++++++++++++++++++++++++++--------- drivers/mmc/host/dw_mmc.c | 5 ---- drivers/mmc/host/sdhci.c | 9 ------- include/linux/mmc/card.h | 12 ++------- include/linux/mmc/host.h | 4 --- 6 files changed, 37 insertions(+), 101 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 66121633c9cd..06c42cfb7c34 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1273,48 +1273,6 @@ void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type) mmc_host_clk_release(host); } -static void mmc_poweroff_notify(struct mmc_host *host) -{ - struct mmc_card *card; - unsigned int timeout; - unsigned int notify_type = EXT_CSD_NO_POWER_NOTIFICATION; - int err = 0; - - card = host->card; - mmc_claim_host(host); - - /* - * Send power notify command only if card - * is mmc and notify state is powered ON - */ - if (card && mmc_card_mmc(card) && - (card->poweroff_notify_state == MMC_POWERED_ON)) { - - if (host->power_notify_type == MMC_HOST_PW_NOTIFY_SHORT) { - notify_type = EXT_CSD_POWER_OFF_SHORT; - timeout = card->ext_csd.generic_cmd6_time; - card->poweroff_notify_state = MMC_POWEROFF_SHORT; - } else { - notify_type = EXT_CSD_POWER_OFF_LONG; - timeout = card->ext_csd.power_off_longtime; - card->poweroff_notify_state = MMC_POWEROFF_LONG; - } - - err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_POWER_OFF_NOTIFICATION, - notify_type, timeout); - - if (err && err != -EBADMSG) - pr_err("Device failed to respond within %d poweroff " - "time. Forcefully powering down the device\n", - timeout); - - /* Set the card state to no notification after the poweroff */ - card->poweroff_notify_state = MMC_NO_POWER_NOTIFICATION; - } - mmc_release_host(host); -} - /* * Apply power to the MMC stack. This is a two-stage process. * First, we enable power to the card without the clock running. @@ -1377,8 +1335,6 @@ static void mmc_power_up(struct mmc_host *host) void mmc_power_off(struct mmc_host *host) { - int err = 0; - if (host->ios.power_mode == MMC_POWER_OFF) return; @@ -1387,22 +1343,6 @@ void mmc_power_off(struct mmc_host *host) host->ios.clock = 0; host->ios.vdd = 0; - /* - * For eMMC 4.5 device send AWAKE command before - * POWER_OFF_NOTIFY command, because in sleep state - * eMMC 4.5 devices respond to only RESET and AWAKE cmd - */ - if (host->card && mmc_card_is_sleep(host->card) && - host->bus_ops->resume) { - err = host->bus_ops->resume(host); - - if (!err) - mmc_poweroff_notify(host); - else - pr_warning("%s: error %d during resume " - "(continue with poweroff sequence)\n", - mmc_hostname(host), err); - } /* * Reset ocr mask to be the highest possible voltage supported for @@ -2579,7 +2519,6 @@ int mmc_pm_notify(struct notifier_block *notify_block, spin_lock_irqsave(&host->lock, flags); host->rescan_disable = 1; - host->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; spin_unlock_irqrestore(&host->lock, flags); cancel_delayed_work_sync(&host->detect); @@ -2603,7 +2542,6 @@ int mmc_pm_notify(struct notifier_block *notify_block, spin_lock_irqsave(&host->lock, flags); host->rescan_disable = 0; - host->power_notify_type = MMC_HOST_PW_NOTIFY_LONG; spin_unlock_irqrestore(&host->lock, flags); mmc_detect_change(host, 0); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 7509de14aa78..7cc46382fd64 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1007,7 +1007,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * so check for success and update the flag */ if (!err) - card->poweroff_notify_state = MMC_POWERED_ON; + card->ext_csd.power_off_notification = EXT_CSD_POWER_ON; } /* @@ -1273,6 +1273,35 @@ err: return err; } +static int mmc_can_poweroff_notify(const struct mmc_card *card) +{ + return card && + mmc_card_mmc(card) && + (card->ext_csd.power_off_notification == EXT_CSD_POWER_ON); +} + +static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type) +{ + unsigned int timeout = card->ext_csd.generic_cmd6_time; + int err; + + /* Use EXT_CSD_POWER_OFF_SHORT as default notification type. */ + if (notify_type == EXT_CSD_POWER_OFF_LONG) + timeout = card->ext_csd.power_off_longtime; + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_OFF_NOTIFICATION, + notify_type, timeout); + if (err) + pr_err("%s: Power Off Notification timed out, %u\n", + mmc_hostname(card->host), timeout); + + /* Disable the power off notification after the switch operation. */ + card->ext_csd.power_off_notification = EXT_CSD_NO_POWER_NOTIFICATION; + + return err; +} + /* * Host is being removed. Free up the current card. */ @@ -1333,11 +1362,11 @@ static int mmc_suspend(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); - if (mmc_card_can_sleep(host)) { + if (mmc_can_poweroff_notify(host->card)) + err = mmc_poweroff_notify(host->card, EXT_CSD_POWER_OFF_SHORT); + else if (mmc_card_can_sleep(host)) err = mmc_card_sleep(host); - if (!err) - mmc_card_set_sleep(host->card); - } else if (!mmc_host_is_spi(host)) + else if (!mmc_host_is_spi(host)) err = mmc_deselect_cards(host); host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200); mmc_release_host(host); @@ -1359,11 +1388,7 @@ static int mmc_resume(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); - if (mmc_card_is_sleep(host->card)) { - err = mmc_card_awake(host); - mmc_card_clr_sleep(host->card); - } else - err = mmc_init_card(host, host->ocr, host->card); + err = mmc_init_card(host, host->ocr, host->card); mmc_release_host(host); return err; @@ -1374,7 +1399,6 @@ static int mmc_power_restore(struct mmc_host *host) int ret; host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200); - mmc_card_clr_sleep(host->card); mmc_claim_host(host); ret = mmc_init_card(host, host->ocr, host->card); mmc_release_host(host); diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index a23af77de4ce..c2828f35c3b8 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1885,11 +1885,6 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) if (host->pdata->quirks & DW_MCI_QUIRK_HIGHSPEED) mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED; - if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY) - mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; - else - mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE; - if (host->pdata->blk_settings) { mmc->max_segs = host->pdata->blk_settings->max_segs; mmc->max_blk_size = host->pdata->blk_settings->max_blk_size; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0e15c79014fb..7922adb42386 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2886,15 +2886,6 @@ int sdhci_add_host(struct sdhci_host *host) if (caps[1] & SDHCI_DRIVER_TYPE_D) mmc->caps |= MMC_CAP_DRIVER_TYPE_D; - /* - * If Power Off Notify capability is enabled by the host, - * set notify to short power off notify timeout value. - */ - if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY) - mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; - else - mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE; - /* Initial value for re-tuning timer count */ host->tuning_count = (caps[1] & SDHCI_RETUNING_TIMER_COUNT_MASK) >> SDHCI_RETUNING_TIMER_COUNT_SHIFT; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 78cc3be85391..943550dfe9ea 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -57,6 +57,7 @@ struct mmc_ext_csd { unsigned int sa_timeout; /* Units: 100ns */ unsigned int generic_cmd6_time; /* Units: 10ms */ unsigned int power_off_longtime; /* Units: ms */ + u8 power_off_notification; /* state */ unsigned int hs_max_dtr; #define MMC_HIGH_26_MAX_DTR 26000000 #define MMC_HIGH_52_MAX_DTR 52000000 @@ -229,7 +230,6 @@ struct mmc_card { #define MMC_CARD_SDXC (1<<6) /* card is SDXC */ #define MMC_CARD_REMOVED (1<<7) /* card has been removed */ #define MMC_STATE_HIGHSPEED_200 (1<<8) /* card is in HS200 mode */ -#define MMC_STATE_SLEEP (1<<9) /* card is in sleep state */ #define MMC_STATE_DOING_BKOPS (1<<10) /* card is doing BKOPS */ unsigned int quirks; /* card quirks */ #define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */ @@ -246,11 +246,6 @@ struct mmc_card { #define MMC_QUIRK_LONG_READ_TIME (1<<9) /* Data read time > CSD says */ #define MMC_QUIRK_SEC_ERASE_TRIM_BROKEN (1<<10) /* Skip secure for erase/trim */ /* byte mode */ - unsigned int poweroff_notify_state; /* eMMC4.5 notify feature */ -#define MMC_NO_POWER_NOTIFICATION 0 -#define MMC_POWERED_ON 1 -#define MMC_POWEROFF_SHORT 2 -#define MMC_POWEROFF_LONG 3 unsigned int erase_size; /* erase size in sectors */ unsigned int erase_shift; /* if erase unit is power 2 */ @@ -397,7 +392,6 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_sd_card_uhs(c) ((c)->state & MMC_STATE_ULTRAHIGHSPEED) #define mmc_card_ext_capacity(c) ((c)->state & MMC_CARD_SDXC) #define mmc_card_removed(c) ((c) && ((c)->state & MMC_CARD_REMOVED)) -#define mmc_card_is_sleep(c) ((c)->state & MMC_STATE_SLEEP) #define mmc_card_doing_bkops(c) ((c)->state & MMC_STATE_DOING_BKOPS) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) @@ -410,11 +404,9 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_sd_card_set_uhs(c) ((c)->state |= MMC_STATE_ULTRAHIGHSPEED) #define mmc_card_set_ext_capacity(c) ((c)->state |= MMC_CARD_SDXC) #define mmc_card_set_removed(c) ((c)->state |= MMC_CARD_REMOVED) -#define mmc_card_set_sleep(c) ((c)->state |= MMC_STATE_SLEEP) #define mmc_card_set_doing_bkops(c) ((c)->state |= MMC_STATE_DOING_BKOPS) - #define mmc_card_clr_doing_bkops(c) ((c)->state &= ~MMC_STATE_DOING_BKOPS) -#define mmc_card_clr_sleep(c) ((c)->state &= ~MMC_STATE_SLEEP) + /* * Quirk add/remove for MMC products. */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index d5d9bd4c5aa8..7abb0e1f7bda 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -259,10 +259,6 @@ struct mmc_host { #define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */ mmc_pm_flag_t pm_caps; /* supported pm features */ - unsigned int power_notify_type; -#define MMC_HOST_PW_NOTIFY_NONE 0 -#define MMC_HOST_PW_NOTIFY_SHORT 1 -#define MMC_HOST_PW_NOTIFY_LONG 2 #ifdef CONFIG_MMC_CLKGATE int clk_requests; /* internal reference counter */ -- cgit v1.2.3