diff options
Diffstat (limited to 'drivers/mmc/core/core.c')
-rw-r--r-- | drivers/mmc/core/core.c | 190 |
1 files changed, 175 insertions, 15 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index d637982b035..ec769490300 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -24,6 +24,8 @@ #include <linux/regulator/consumer.h> #include <linux/pm_runtime.h> #include <linux/suspend.h> +#include <linux/fault-inject.h> +#include <linux/random.h> #include <linux/mmc/card.h> #include <linux/mmc/host.h> @@ -83,6 +85,43 @@ static void mmc_flush_scheduled_work(void) flush_workqueue(workqueue); } +#ifdef CONFIG_FAIL_MMC_REQUEST + +/* + * Internal function. Inject random data errors. + * If mmc_data is NULL no errors are injected. + */ +static void mmc_should_fail_request(struct mmc_host *host, + struct mmc_request *mrq) +{ + struct mmc_command *cmd = mrq->cmd; + struct mmc_data *data = mrq->data; + static const int data_errors[] = { + -ETIMEDOUT, + -EILSEQ, + -EIO, + }; + + if (!data) + return; + + if (cmd->error || data->error || + !should_fail(&host->fail_mmc_request, data->blksz * data->blocks)) + return; + + data->error = data_errors[random32() % ARRAY_SIZE(data_errors)]; + data->bytes_xfered = (random32() % (data->bytes_xfered >> 9)) << 9; +} + +#else /* CONFIG_FAIL_MMC_REQUEST */ + +static inline void mmc_should_fail_request(struct mmc_host *host, + struct mmc_request *mrq) +{ +} + +#endif /* CONFIG_FAIL_MMC_REQUEST */ + /** * mmc_request_done - finish processing an MMC request * @host: MMC host which completed request @@ -102,13 +141,15 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) } if (err && cmd->retries) { - pr_debug("%s: req failed (CMD%u): %d, retrying...\n", - mmc_hostname(host), cmd->opcode, err); - - cmd->retries--; - cmd->error = 0; - host->ops->request(host, mrq); + /* + * Request starter must handle retries - see + * mmc_wait_for_req_done(). + */ + if (mrq->done) + mrq->done(mrq); } else { + mmc_should_fail_request(host, mrq); + led_trigger_event(host->led, LED_OFF); pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n", @@ -212,7 +253,21 @@ static void __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) static void mmc_wait_for_req_done(struct mmc_host *host, struct mmc_request *mrq) { - wait_for_completion(&mrq->completion); + struct mmc_command *cmd; + + while (1) { + wait_for_completion(&mrq->completion); + + cmd = mrq->cmd; + if (!cmd->error || !cmd->retries) + break; + + pr_debug("%s: req failed (CMD%u): %d, retrying...\n", + mmc_hostname(host), cmd->opcode, cmd->error); + cmd->retries--; + cmd->error = 0; + host->ops->request(host, mrq); + } } /** @@ -279,8 +334,14 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, mmc_wait_for_req_done(host, host->areq->mrq); err = host->areq->err_check(host->card, host->areq); if (err) { + /* post process the completed failed request */ mmc_post_req(host, host->areq->mrq, 0); if (areq) + /* + * Cancel the new prepared request, because + * it can't run until the failed + * request has been properly handled. + */ mmc_post_req(host, areq->mrq, -EINVAL); host->areq = NULL; @@ -330,7 +391,7 @@ EXPORT_SYMBOL(mmc_wait_for_req); */ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries) { - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; WARN_ON(!host->claimed); @@ -1119,13 +1180,11 @@ static void mmc_power_up(struct mmc_host *host) bit = fls(host->ocr_avail) - 1; host->ios.vdd = bit; - if (mmc_host_is_spi(host)) { + if (mmc_host_is_spi(host)) host->ios.chip_select = MMC_CS_HIGH; - host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; - } else { + else host->ios.chip_select = MMC_CS_DONTCARE; - host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; - } + host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; host->ios.power_mode = MMC_POWER_UP; host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; @@ -1173,6 +1232,13 @@ void mmc_power_off(struct mmc_host *host) host->ios.timing = MMC_TIMING_LEGACY; mmc_set_ios(host); + /* + * Some configurations, such as the 802.11 SDIO card in the OLPC + * XO-1.5, require a short delay after poweroff before the card + * can be successfully turned on again. + */ + mmc_delay(1); + mmc_host_clk_release(host); } @@ -1477,7 +1543,7 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from, if (err) { printk(KERN_ERR "mmc_erase: group start error %d, " "status %#x\n", err, cmd.resp[0]); - err = -EINVAL; + err = -EIO; goto out; } @@ -1492,7 +1558,7 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from, if (err) { printk(KERN_ERR "mmc_erase: group end error %d, status %#x\n", err, cmd.resp[0]); - err = -EINVAL; + err = -EIO; goto out; } @@ -1724,6 +1790,94 @@ int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen) } EXPORT_SYMBOL(mmc_set_blocklen); +static void mmc_hw_reset_for_init(struct mmc_host *host) +{ + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) + return; + mmc_host_clk_hold(host); + host->ops->hw_reset(host); + mmc_host_clk_release(host); +} + +int mmc_can_reset(struct mmc_card *card) +{ + u8 rst_n_function; + + if (!mmc_card_mmc(card)) + return 0; + rst_n_function = card->ext_csd.rst_n_function; + if ((rst_n_function & EXT_CSD_RST_N_EN_MASK) != EXT_CSD_RST_N_ENABLED) + return 0; + return 1; +} +EXPORT_SYMBOL(mmc_can_reset); + +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; + + if (!card) + return -EINVAL; + + if (!mmc_can_reset(card)) + return -EOPNOTSUPP; + + mmc_host_clk_hold(host); + mmc_set_clock(host, host->f_init); + + host->ops->hw_reset(host); + + /* If the reset has happened, then a status command will fail */ + if (check) { + struct mmc_command cmd = {0}; + int err; + + cmd.opcode = MMC_SEND_STATUS; + if (!mmc_host_is_spi(card->host)) + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC; + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (!err) { + mmc_host_clk_release(host); + return -ENOSYS; + } + } + + host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_DDR); + if (mmc_host_is_spi(host)) { + host->ios.chip_select = MMC_CS_HIGH; + host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; + } else { + host->ios.chip_select = MMC_CS_DONTCARE; + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + } + host->ios.bus_width = MMC_BUS_WIDTH_1; + host->ios.timing = MMC_TIMING_LEGACY; + mmc_set_ios(host); + + mmc_host_clk_release(host); + + return host->bus_ops->power_restore(host); +} + +int mmc_hw_reset(struct mmc_host *host) +{ + return mmc_do_hw_reset(host, 0); +} +EXPORT_SYMBOL(mmc_hw_reset); + +int mmc_hw_reset_check(struct mmc_host *host) +{ + return mmc_do_hw_reset(host, 1); +} +EXPORT_SYMBOL(mmc_hw_reset_check); + static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { host->f_init = freq; @@ -1735,6 +1889,12 @@ static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) mmc_power_up(host); /* + * Some eMMCs (with VCCQ always on) may not be reset after power up, so + * do a hardware reset if possible. + */ + mmc_hw_reset_for_init(host); + + /* * sdio_reset sends CMD52 to reset card. Since we do not know * if the card is being re-initialized, just send it. CMD52 * should be ignored by SD/eMMC cards. |