diff options
| author | Talel Shenhar <tatias@codeaurora.org> | 2015-05-27 14:20:34 +0300 |
|---|---|---|
| committer | Subhash Jadavani <subhashj@codeaurora.org> | 2016-05-31 15:26:24 -0700 |
| commit | a968943d3557b2b3ef444fe1059c948ddb26b8cd (patch) | |
| tree | 0827733a007e589d22e618ec3fdebf20e940c8cb | |
| parent | ae479fa29fc8cc9fd3ab36dde46a0638da2a4fcc (diff) | |
mmc: fix MMC clock scaling to meet upstream HS400 implementation
On the 3.10 kernel branch we had an implementation
supporting HS400 that was different than that in the
Linux community code base.
As part of the transition to kernel 3.14, the
community's implementation was used.
However, as this implementation does not properly
support up/down clock scaling - this patch adds
the missing functionality.
Change-Id: I096132bc715909b1ff2ac84448ec0adb32ca06ba
Signed-off-by: Talel Shenhar <tatias@codeaurora.org>
| -rw-r--r-- | drivers/mmc/core/core.c | 15 | ||||
| -rw-r--r-- | drivers/mmc/core/mmc.c | 263 | ||||
| -rw-r--r-- | drivers/mmc/core/sd.c | 11 |
3 files changed, 179 insertions, 110 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 0340980c0b77..5f2145cbb7ad 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -298,6 +298,21 @@ int mmc_clk_update_freq(struct mmc_host *host, return -EINVAL; } + /* make sure the card supports the frequency we want */ + if (unlikely(freq > host->card->clk_scaling_highest)) { + freq = host->card->clk_scaling_highest; + pr_warn("%s: %s: frequency was overridden to %lu\n", + mmc_hostname(host), __func__, + host->card->clk_scaling_highest); + } + + if (unlikely(freq < host->card->clk_scaling_lowest)) { + freq = host->card->clk_scaling_lowest; + pr_warn("%s: %s: frequency was overridden to %lu\n", + mmc_hostname(host), __func__, + host->card->clk_scaling_lowest); + } + if (host->ops->notify_load) { err = host->ops->notify_load(host, state); if (err) { diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index dda6600a25d9..16f43e4b1d99 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1366,98 +1366,6 @@ err: return err; } -int mmc_set_clock_bus_speed(struct mmc_card *card, unsigned long freq) -{ - int err; - - if (freq < MMC_HS200_MAX_DTR) { - /* - * Lower the clock and adjust the timing to be able - * to switch to HighSpeed mode - */ - mmc_set_timing(card->host, MMC_TIMING_LEGACY); - mmc_set_clock(card->host, MMC_HIGH_26_MAX_DTR); - - err = mmc_select_hs(card); - } else { - err = mmc_select_hs400(card); - } - - return err; -} - -/** - * mmc_change_bus_speed() - Change MMC card bus frequency at runtime - * @host: pointer to mmc host structure - * @freq: pointer to desired frequency to be set - * - * Change the MMC card bus frequency at runtime after the card is - * initialized. Callers are expected to make sure of the card's - * state (DATA/RCV/TRANSFER) beforing changing the frequency at runtime. - * - * If the frequency to change is greater than max. supported by card, - * *freq is changed to max. supported by card and if it is less than min. - * supported by host, *freq is changed to min. supported by host. - */ -static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq) -{ - int err = 0; - struct mmc_card *card; - - mmc_claim_host(host); - /* - * Assign card pointer after claiming host to avoid race - * conditions that may arise during removal of the card. - */ - card = host->card; - - if (!card || !freq) { - err = -EINVAL; - goto out; - } - - if (mmc_card_hs(card) || mmc_card_hs200(card) - || mmc_card_ddr52(card)) { - if (*freq > card->ext_csd.hs_max_dtr) - *freq = card->ext_csd.hs_max_dtr; - } else if (*freq > card->csd.max_dtr) { - *freq = card->csd.max_dtr; - } - - if (*freq < host->f_min) - *freq = host->f_min; - - if (mmc_card_hs400(card)) { - err = mmc_set_clock_bus_speed(card, *freq); - if (err) - goto out; - } else { - mmc_set_clock(host, (unsigned int) (*freq)); - } - - if (mmc_card_hs200(card) && card->host->ops->execute_tuning) { - /* - * We try to probe host driver for tuning for any - * frequency, it is host driver responsibility to - * perform actual tuning only when required. - */ - mmc_host_clk_hold(card->host); - err = card->host->ops->execute_tuning(card->host, - MMC_SEND_TUNING_BLOCK_HS200); - mmc_host_clk_release(card->host); - - if (err) { - pr_warn("%s: %s: tuning execution failed %d. Restoring to previous clock %lu\n", - mmc_hostname(card->host), __func__, err, - host->clk_scaling.curr_freq); - mmc_set_clock(host, host->clk_scaling.curr_freq); - } - } -out: - mmc_release_host(host); - return err; -} - static int mmc_reboot_notify(struct notifier_block *notify_block, unsigned long event, void *unused) { @@ -1527,6 +1435,163 @@ static int mmc_hs200_tuning(struct mmc_card *card) } /* + * Scale down from HS400 to HS in order to allow frequency change. + * This is needed for cards that doesn't support changing frequency in HS400 + */ +static int mmc_scale_low(struct mmc_host *host, unsigned long freq) +{ + int err = 0; + + mmc_set_timing(host, MMC_TIMING_LEGACY); + mmc_set_clock(host, MMC_HIGH_26_MAX_DTR); + + err = mmc_select_hs(host->card); + if (err) { + pr_err("%s: %s: selecting HS (52Mhz) failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + err = mmc_select_bus_width(host->card); + if (err < 0) { + pr_err("%s: %s: select_bus_width failed(%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + mmc_set_clock(host, freq); + + return 0; +} + +/* + * Scale UP from HS to HS200/H400 + */ +static int mmc_scale_high(struct mmc_host *host) +{ + int err = 0; + + if (!(host->card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200)) { + pr_err("%s: %s: card does not support HS200\n", + mmc_hostname(host), __func__); + WARN_ON(1); + return -EPERM; + } + + err = mmc_select_hs200(host->card); + if (err) { + pr_err("%s: %s: selecting HS200 failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + mmc_set_bus_speed(host->card); + + err = mmc_hs200_tuning(host->card); + if (err) { + pr_err("%s: %s: hs200 tuning failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + if (!(host->card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400)) { + pr_debug("%s: card does not support HS400\n", + mmc_hostname(host)); + return 0; + } + + err = mmc_select_hs400(host->card); + if (err) { + pr_err("%s: %s: select hs400 failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + return 0; +} + +static int mmc_set_clock_bus_speed(struct mmc_card *card, unsigned long freq) +{ + int err = 0; + + if (freq == MMC_HS200_MAX_DTR) + err = mmc_scale_high(card->host); + else + err = mmc_scale_low(card->host, freq); + + return err; +} + +static inline unsigned long mmc_ddr_freq_accommodation(unsigned long freq) +{ + if (freq == MMC_HIGH_DDR_MAX_DTR) + return freq; + + return freq/2; +} + +/** + * mmc_change_bus_speed() - Change MMC card bus frequency at runtime + * @host: pointer to mmc host structure + * @freq: pointer to desired frequency to be set + * + * Change the MMC card bus frequency at runtime after the card is + * initialized. Callers are expected to make sure of the card's + * state (DATA/RCV/TRANSFER) before changing the frequency at runtime. + * + * If the frequency to change is greater than max. supported by card, + * *freq is changed to max. supported by card. If it is less than min. + * supported by host, *freq is changed to min. supported by host. + * Host is assumed to be calimed while calling this funciton. + */ +static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq) +{ + int err = 0; + struct mmc_card *card; + unsigned long actual_freq; + + card = host->card; + + if (!card || !freq) { + err = -EINVAL; + goto out; + } + actual_freq = *freq; + + WARN_ON(!host->claimed); + + /* + * For scaling up/down HS400 we'll need special handling, + * for other timings we can simply do clock frequency change + */ + if (mmc_card_hs400(card) || + (*freq == MMC_HS200_MAX_DTR)) { + err = mmc_set_clock_bus_speed(card, *freq); + if (err) { + pr_err("%s: %s: failed (%d)to set bus and clock speed (freq=%lu)\n", + mmc_hostname(host), __func__, err, *freq); + goto out; + } + } else if (mmc_card_hs200(host->card)) { + mmc_set_clock(host, *freq); + err = mmc_hs200_tuning(host->card); + if (err) { + pr_warn("%s: %s: tuning execution failed %d\n", + mmc_hostname(card->host), + __func__, err); + mmc_set_clock(host, host->clk_scaling.curr_freq); + } + } else { + if (mmc_card_ddr52(host->card)) + actual_freq = mmc_ddr_freq_accommodation(*freq); + mmc_set_clock(host, actual_freq); + } + +out: + return err; +} + +/* * Handle the detection and initialisation of a card. * * In the case of a resume, "oldcard" will contain the card @@ -1810,10 +1875,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } card->clk_scaling_lowest = host->f_min; - if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS400) | + if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS400) || (card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS200)) card->clk_scaling_highest = card->ext_csd.hs200_max_dtr; - else if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS) | + else if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS) || (card->mmc_avail_type | EXT_CSD_CARD_TYPE_DDR_52)) card->clk_scaling_highest = card->ext_csd.hs_max_dtr; else @@ -2111,11 +2176,6 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) BUG_ON(!host); BUG_ON(!host->card); - mmc_claim_host(host); - - if (mmc_card_suspended(host->card)) - goto out; - /* * Disable clock scaling before suspend and enable it after resume so * as to avoid clock scaling decisions kicking in during this window. @@ -2123,6 +2183,11 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) if (mmc_can_scale_clk(host)) mmc_disable_clk_scaling(host); + mmc_claim_host(host); + + if (mmc_card_suspended(host->card)) + goto out; + if (mmc_card_doing_bkops(host->card)) { err = mmc_stop_bkops(host->card); if (err) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index c7aaa9bbb1be..44bc036bf2d0 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -610,17 +610,6 @@ static int mmc_sd_change_bus_speed(struct mmc_host *host, unsigned long *freq) goto out; } - if (mmc_card_uhs(card)) { - if (*freq > card->sw_caps.uhs_max_dtr) - *freq = card->sw_caps.uhs_max_dtr; - } else { - if (*freq > mmc_sd_get_max_clock(card)) - *freq = mmc_sd_get_max_clock(card); - } - - if (*freq < host->f_min) - *freq = host->f_min; - mmc_set_clock(host, (unsigned int) (*freq)); if (!mmc_host_is_spi(card->host) && mmc_card_uhs(card) |
