Hosts which support re-tuning mode 2 has the capability to indicate the re-tuning timing by issuing re-tuning request during data transfers. During non-data transfers, re-tuning timing is determined by either re-tuning timer or re-tuning request. Since there is no way to determine the host's capability to generate re-tuning request during non-data transfers, we will start the re-tuning timer by default for re-tuning mode 2. If we ever received a re-tuning request during non-data transfers, that means the host is also capable of generating re-tuning request for non-data transfers, we will then deactivate the re-tuning timer altogether. The SDHCI_RETUNING_TIMER flag is added to indicate the fact that the host requires re-tuning timer to trigger the re-tuning timing. The SDHCI_NEEDS_RETUNING, SDHCI_RETUNING_TIMER flags and the max block count of the host will be restored to their default values since they are affected by different cards inserted too. Signed-off-by: Aaron Lu <aaron.lu@xxxxxxx> --- drivers/mmc/host/sdhci.c | 128 +++++++++++++++++++++++++++++++++------------ drivers/mmc/host/sdhci.h | 3 + include/linux/mmc/sdhci.h | 4 ++ 3 files changed, 102 insertions(+), 33 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0e02cc1..f620c72 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -222,6 +222,22 @@ static void sdhci_reinit(struct sdhci_host *host) { sdhci_init(host, 0); sdhci_enable_card_detection(host); + + if (host->version >= SDHCI_SPEC_300) { + /* + * Clear re-tuning related flags, since these flags + * are also affected by different cards inserted + */ + host->flags &= ~(SDHCI_NEEDS_RETUNING | + SDHCI_RETUNING_TIMER); + + /* + * restore max block count, since it might be + * reduced due to re-tuning mode 1 and 2 + */ + host->mmc->max_blk_count = + (host->quirks & SDHCI_QUIRK_NO_MULTIBLOCK) ? 1 : 65535; + } } static void sdhci_activate_led(struct sdhci_host *host) @@ -1245,7 +1261,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) present_state = sdhci_readl(host, SDHCI_PRESENT_STATE); /* - * Check if the re-tuning timer has already expired and there + * Check if the re-tuning timing has already arrived and there * is no on-going data transfer. If so, we need to execute * tuning procedure before sending command. */ @@ -1733,34 +1749,41 @@ static int sdhci_execute_tuning(struct mmc_host *mmc) out: /* - * If this is the very first time we are here, we start the retuning - * timer. Since only during the first time, SDHCI_NEEDS_RETUNING - * flag won't be set, we check this condition before actually starting - * the timer. + * If this is the very first time we are here, we setup the + * corresponding re-tuning condition. Since only during the + * first time, SDHCI_NEEDS_RETUNING flag won't be set. */ - if (!(host->flags & SDHCI_NEEDS_RETUNING) && host->tuning_count && - (host->tuning_mode == SDHCI_TUNING_MODE_1)) { - mod_timer(&host->tuning_timer, jiffies + - 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; + if (!(host->flags & SDHCI_NEEDS_RETUNING)) { + if (host->tuning_count && + host->tuning_mode != SDHCI_TUNING_MODE_RSV) { + host->flags |= SDHCI_RETUNING_TIMER; + mod_timer(&host->tuning_timer, jiffies + + host->tuning_count * HZ); + } + /* Enable re-tuning event for tuning mode 2 */ + if (host->tuning_mode == SDHCI_TUNING_MODE_2) + ier |= SDHCI_INT_RETUNING; + /* Tuning mode 1 and 2 limits the maximum data length to 4MB */ + if (host->tuning_mode == SDHCI_TUNING_MODE_1 || + host->tuning_mode == SDHCI_TUNING_MODE_2) + mmc->max_blk_count = (4 * 1024 * 1024) / + mmc->max_blk_size; } else { host->flags &= ~SDHCI_NEEDS_RETUNING; - /* Reload the new initial value for timer */ - if (host->tuning_mode == SDHCI_TUNING_MODE_1) + /* Reload the new initial value for re-tuning timer */ + if (host->flags & SDHCI_RETUNING_TIMER) mod_timer(&host->tuning_timer, jiffies + host->tuning_count * HZ); } /* * In case tuning fails, host controllers which support re-tuning can - * try tuning again at a later time, when the re-tuning timer expires. + * try tuning again at a later time, when the re-tuning timing arrives. * So for these controllers, we return 0. Since there might be other * controllers who do not have this capability, we return error for * them. */ - if (err && host->tuning_count && - host->tuning_mode == SDHCI_TUNING_MODE_1) + if (err && (host->tuning_mode != SDHCI_TUNING_MODE_RSV)) err = 0; sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier); @@ -2050,6 +2073,18 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) } } + /* + * Reload re-tuning timer if transfer complete + * occured for re-tuning mode 2 + */ + if ((host->flags & SDHCI_RETUNING_TIMER) && + (host->tuning_mode == SDHCI_TUNING_MODE_2) && + (intmask & SDHCI_INT_DATA_END)) { + mod_timer(&host->tuning_timer, jiffies + + host->tuning_count * HZ); + host->flags &= ~SDHCI_NEEDS_RETUNING; + } + if (!host->data) { /* * The "data complete" interrupt is also used to @@ -2206,6 +2241,23 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) intmask &= ~SDHCI_INT_CARD_INT; + if (intmask & SDHCI_INT_RETUNING) { + host->flags |= SDHCI_NEEDS_RETUNING; + if ((host->flags & SDHCI_RETUNING_TIMER) && + (host->tuning_mode == SDHCI_TUNING_MODE_2)) { + u32 state = sdhci_readl(host, SDHCI_PRESENT_STATE); + /* + * If host is capable of generating re-tuning + * request during non-data transfers, there is + * no need to use re-tuning timer + */ + if (!(state & SDHCI_DATA_ACTIVE)) + host->flags &= ~SDHCI_RETUNING_TIMER; + } + } + + intmask &= ~SDHCI_INT_RETUNING; + if (intmask) { printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n", mmc_hostname(host->mmc), intmask); @@ -2243,12 +2295,14 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state) sdhci_disable_card_detection(host); - /* Disable tuning since we are suspending */ - if (host->version >= SDHCI_SPEC_300 && host->tuning_count && - host->tuning_mode == SDHCI_TUNING_MODE_1) { + /* Disable re-tuning since we are suspending */ + if ((host->version >= SDHCI_SPEC_300) && + (host->tuning_mode != SDHCI_TUNING_MODE_RSV)) { host->flags &= ~SDHCI_NEEDS_RETUNING; - mod_timer(&host->tuning_timer, jiffies + - host->tuning_count * HZ); + if (host->flags & SDHCI_RETUNING_TIMER) + del_timer_sync(&host->tuning_timer); + if (host->tuning_mode == SDHCI_TUNING_MODE_2) + sdhci_mask_irqs(host, SDHCI_INT_RETUNING); } ret = mmc_suspend_host(host->mmc); @@ -2292,11 +2346,6 @@ int sdhci_resume_host(struct sdhci_host *host) ret = mmc_resume_host(host->mmc); sdhci_enable_card_detection(host); - /* Set the re-tuning expiration flag */ - if ((host->version >= SDHCI_SPEC_300) && host->tuning_count && - (host->tuning_mode == SDHCI_TUNING_MODE_1)) - host->flags |= SDHCI_NEEDS_RETUNING; - return ret; } @@ -2570,12 +2619,22 @@ int sdhci_add_host(struct sdhci_host *host) host->tuning_count = (caps[1] & SDHCI_RETUNING_TIMER_COUNT_MASK) >> SDHCI_RETUNING_TIMER_COUNT_SHIFT; - /* - * In case Re-tuning Timer is not disabled, the actual value of - * re-tuning timer will be 2 ^ (n - 1). - */ - if (host->tuning_count) - host->tuning_count = 1 << (host->tuning_count - 1); + if (host->tuning_count) { + /* + * In case re-tuning timer is not disabled, + * the actual value of re-tuning timer will be: + * 0x1 - 0xb: 2 ^ (n - 1) + * 0xc - 0xe: reserved + * 0xf: get the value from other source + */ + if (host->tuning_count <= 0xb) + host->tuning_count = 1 << (host->tuning_count - 1); + else if (host->tuning_count == 0xf) + host->tuning_count = host->ops->get_tuning_count ? + host->ops->get_tuning_count(host) : 0; + else + host->tuning_count = 0; + } /* Re-tuning mode supported by the Host Controller */ host->tuning_mode = (caps[1] & SDHCI_RETUNING_MODE_MASK) >> @@ -2816,6 +2875,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) sdhci_disable_card_detection(host); + if (host->tuning_mode == SDHCI_TUNING_MODE_2) + sdhci_mask_irqs(host, SDHCI_INT_RETUNING); + mmc_remove_host(host->mmc); #ifdef SDHCI_USE_LEDS_CLASS @@ -2828,7 +2890,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) free_irq(host->irq, host); del_timer_sync(&host->timer); - if (host->version >= SDHCI_SPEC_300) + if (host->flags & SDHCI_RETUNING_TIMER) del_timer_sync(&host->tuning_timer); tasklet_kill(&host->card_tasklet); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 745c42f..ef4a0a3 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -64,6 +64,7 @@ #define SDHCI_PRESENT_STATE 0x24 #define SDHCI_CMD_INHIBIT 0x00000001 #define SDHCI_DATA_INHIBIT 0x00000002 +#define SDHCI_DATA_ACTIVE 0x00000004 #define SDHCI_DOING_WRITE 0x00000100 #define SDHCI_DOING_READ 0x00000200 #define SDHCI_SPACE_AVAILABLE 0x00000400 @@ -126,6 +127,7 @@ #define SDHCI_INT_CARD_INSERT 0x00000040 #define SDHCI_INT_CARD_REMOVE 0x00000080 #define SDHCI_INT_CARD_INT 0x00000100 +#define SDHCI_INT_RETUNING 0x00001000 #define SDHCI_INT_ERROR 0x00008000 #define SDHCI_INT_TIMEOUT 0x00010000 #define SDHCI_INT_CRC 0x00020000 @@ -273,6 +275,7 @@ struct sdhci_ops { void (*platform_reset_enter)(struct sdhci_host *host, u8 mask); void (*platform_reset_exit)(struct sdhci_host *host, u8 mask); int (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs); + int (*get_tuning_count)(struct sdhci_host *host); }; diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index 5666f3a..938a4f5 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -115,6 +115,7 @@ struct sdhci_host { #define SDHCI_NEEDS_RETUNING (1<<5) /* Host needs retuning */ #define SDHCI_AUTO_CMD12 (1<<6) /* Auto CMD12 support */ #define SDHCI_AUTO_CMD23 (1<<7) /* Auto CMD23 support */ +#define SDHCI_RETUNING_TIMER (1<<8) /* Host uses re-tuning timer */ unsigned int version; /* SDHCI spec. version */ @@ -158,6 +159,9 @@ struct sdhci_host { unsigned int tuning_count; /* Timer count for re-tuning */ unsigned int tuning_mode; /* Re-tuning mode supported by host */ #define SDHCI_TUNING_MODE_1 0 +#define SDHCI_TUNING_MODE_2 1 +#define SDHCI_TUNING_MODE_3 2 +#define SDHCI_TUNING_MODE_RSV 3 struct timer_list tuning_timer; /* Timer for tuning */ unsigned long private[0] ____cacheline_aligned; -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html