mmc: sdhci: enhance preset value function

4d55c5a1 ("mmc: sdhci: enable preset value after uhs initialization")
added preset value support and enabled it by default during sd card init.

Below are the enhancements introduced by this patch:

1. In current code, preset value is enabled after setting clock finished,
which means the clock is manually set by driver firstly and then suddenly
switched to preset value at this point. So the first setting is useless
and unnecessary. What's more, the first clock setting may differ from the
preset one.  The better way is enable preset value just after switch to
UHS mode so the preset value can take effect immediately. So move preset
value enable from mmc_sd_init_card to sdhci_set_ios which will be called
during set timing.

2. In current code, preset value is disabled at the beginning of
mmc_attach_sd.  It's too late since low freq (400khz) should be set in
mmc_power_up.  So move preset value disable to sdhci_set_ios which will
be called during power up.

3. host->clock and ios->drv_type should also be updated according to the
preset value if it's enabled. Current code missed this.

4. This patch also introduce a quirk to disable preset value in case
preset value doesn't work.

This patch has been verified on sdhci-pxav3 platform with both preset
enabled and disabled.

Signed-off-by: Kevin Liu <kliu5@marvell.com>
Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Chris Ball <cjb@laptop.org>
This commit is contained in:
Kevin Liu 2013-01-31 11:31:37 +08:00 committed by Chris Ball
parent 073350f7b5
commit 52983382c7
5 changed files with 103 additions and 59 deletions

View File

@ -969,16 +969,6 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
/* Card is an ultra-high-speed card */ /* Card is an ultra-high-speed card */
mmc_card_set_uhs(card); mmc_card_set_uhs(card);
/*
* Since initialization is now complete, enable preset
* value registers for UHS-I cards.
*/
if (host->ops->enable_preset_value) {
mmc_host_clk_hold(card->host);
host->ops->enable_preset_value(host, true);
mmc_host_clk_release(card->host);
}
} else { } else {
/* /*
* Attempt to change to high-speed (if supported) * Attempt to change to high-speed (if supported)
@ -1157,13 +1147,6 @@ int mmc_attach_sd(struct mmc_host *host)
BUG_ON(!host); BUG_ON(!host);
WARN_ON(!host->claimed); WARN_ON(!host->claimed);
/* Disable preset value enable if already set since last time */
if (host->ops->enable_preset_value) {
mmc_host_clk_hold(host);
host->ops->enable_preset_value(host, false);
mmc_host_clk_release(host);
}
err = mmc_send_app_op_cond(host, 0, &ocr); err = mmc_send_app_op_cond(host, 0, &ocr);
if (err) if (err)
return err; return err;

View File

@ -53,6 +53,7 @@ static void sdhci_send_command(struct sdhci_host *, struct mmc_command *);
static void sdhci_finish_command(struct sdhci_host *); static void sdhci_finish_command(struct sdhci_host *);
static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode); static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
static void sdhci_tuning_timer(unsigned long data); static void sdhci_tuning_timer(unsigned long data);
static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
#ifdef CONFIG_PM_RUNTIME #ifdef CONFIG_PM_RUNTIME
static int sdhci_runtime_pm_get(struct sdhci_host *host); static int sdhci_runtime_pm_get(struct sdhci_host *host);
@ -1082,6 +1083,37 @@ static void sdhci_finish_command(struct sdhci_host *host)
} }
} }
static u16 sdhci_get_preset_value(struct sdhci_host *host)
{
u16 ctrl, preset = 0;
ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
switch (ctrl & SDHCI_CTRL_UHS_MASK) {
case SDHCI_CTRL_UHS_SDR12:
preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR12);
break;
case SDHCI_CTRL_UHS_SDR25:
preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR25);
break;
case SDHCI_CTRL_UHS_SDR50:
preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR50);
break;
case SDHCI_CTRL_UHS_SDR104:
preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR104);
break;
case SDHCI_CTRL_UHS_DDR50:
preset = sdhci_readw(host, SDHCI_PRESET_FOR_DDR50);
break;
default:
pr_warn("%s: Invalid UHS-I mode selected\n",
mmc_hostname(host->mmc));
preset = sdhci_readw(host, SDHCI_PRESET_FOR_SDR12);
break;
}
return preset;
}
static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
{ {
int div = 0; /* Initialized for compiler warning */ int div = 0; /* Initialized for compiler warning */
@ -1106,24 +1138,33 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
goto out; goto out;
if (host->version >= SDHCI_SPEC_300) { if (host->version >= SDHCI_SPEC_300) {
if (sdhci_readw(host, SDHCI_HOST_CONTROL2) &
SDHCI_CTRL_PRESET_VAL_ENABLE) {
u16 pre_val;
clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
pre_val = sdhci_get_preset_value(host);
div = (pre_val & SDHCI_PRESET_SDCLK_FREQ_MASK)
>> SDHCI_PRESET_SDCLK_FREQ_SHIFT;
if (host->clk_mul &&
(pre_val & SDHCI_PRESET_CLKGEN_SEL_MASK)) {
clk = SDHCI_PROG_CLOCK_MODE;
real_div = div + 1;
clk_mul = host->clk_mul;
} else {
real_div = max_t(int, 1, div << 1);
}
goto clock_set;
}
/* /*
* Check if the Host Controller supports Programmable Clock * Check if the Host Controller supports Programmable Clock
* Mode. * Mode.
*/ */
if (host->clk_mul) { if (host->clk_mul) {
u16 ctrl;
/*
* We need to figure out whether the Host Driver needs
* to select Programmable Clock Mode, or the value can
* be set automatically by the Host Controller based on
* the Preset Value registers.
*/
ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
if (!(ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
for (div = 1; div <= 1024; div++) { for (div = 1; div <= 1024; div++) {
if (((host->max_clk * host->clk_mul) / if ((host->max_clk * host->clk_mul / div)
div) <= clock) <= clock)
break; break;
} }
/* /*
@ -1134,7 +1175,6 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
real_div = div; real_div = div;
clk_mul = host->clk_mul; clk_mul = host->clk_mul;
div--; div--;
}
} else { } else {
/* Version 3.00 divisors must be a multiple of 2. */ /* Version 3.00 divisors must be a multiple of 2. */
if (host->max_clk <= clock) if (host->max_clk <= clock)
@ -1159,6 +1199,7 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
div >>= 1; div >>= 1;
} }
clock_set:
if (real_div) if (real_div)
host->mmc->actual_clock = (host->max_clk * clk_mul) / real_div; host->mmc->actual_clock = (host->max_clk * clk_mul) / real_div;
@ -1376,6 +1417,10 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
sdhci_reinit(host); sdhci_reinit(host);
} }
if (host->version >= SDHCI_SPEC_300 &&
(ios->power_mode == MMC_POWER_UP))
sdhci_enable_preset_value(host, false);
sdhci_set_clock(host, ios->clock); sdhci_set_clock(host, ios->clock);
if (ios->power_mode == MMC_POWER_OFF) if (ios->power_mode == MMC_POWER_OFF)
@ -1496,6 +1541,20 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
} }
if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN) &&
((ios->timing == MMC_TIMING_UHS_SDR12) ||
(ios->timing == MMC_TIMING_UHS_SDR25) ||
(ios->timing == MMC_TIMING_UHS_SDR50) ||
(ios->timing == MMC_TIMING_UHS_SDR104) ||
(ios->timing == MMC_TIMING_UHS_DDR50))) {
u16 preset;
sdhci_enable_preset_value(host, true);
preset = sdhci_get_preset_value(host);
ios->drv_type = (preset & SDHCI_PRESET_DRV_MASK)
>> SDHCI_PRESET_DRV_SHIFT;
}
/* Re-enable SD Clock */ /* Re-enable SD Clock */
sdhci_update_clock(host); sdhci_update_clock(host);
} else } else
@ -1925,17 +1984,15 @@ out:
return err; return err;
} }
static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable)
static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
{ {
u16 ctrl; u16 ctrl;
unsigned long flags;
/* Host Controller v3.00 defines preset value registers */ /* Host Controller v3.00 defines preset value registers */
if (host->version < SDHCI_SPEC_300) if (host->version < SDHCI_SPEC_300)
return; return;
spin_lock_irqsave(&host->lock, flags);
ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
/* /*
@ -1951,17 +2008,6 @@ static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable)
sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
host->flags &= ~SDHCI_PV_ENABLED; host->flags &= ~SDHCI_PV_ENABLED;
} }
spin_unlock_irqrestore(&host->lock, flags);
}
static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable)
{
struct sdhci_host *host = mmc_priv(mmc);
sdhci_runtime_pm_get(host);
sdhci_do_enable_preset_value(host, enable);
sdhci_runtime_pm_put(host);
} }
static void sdhci_card_event(struct mmc_host *mmc) static void sdhci_card_event(struct mmc_host *mmc)
@ -1997,7 +2043,6 @@ static const struct mmc_host_ops sdhci_ops = {
.enable_sdio_irq = sdhci_enable_sdio_irq, .enable_sdio_irq = sdhci_enable_sdio_irq,
.start_signal_voltage_switch = sdhci_start_signal_voltage_switch, .start_signal_voltage_switch = sdhci_start_signal_voltage_switch,
.execute_tuning = sdhci_execute_tuning, .execute_tuning = sdhci_execute_tuning,
.enable_preset_value = sdhci_enable_preset_value,
.card_event = sdhci_card_event, .card_event = sdhci_card_event,
.card_busy = sdhci_card_busy, .card_busy = sdhci_card_busy,
}; };
@ -2591,8 +2636,12 @@ int sdhci_runtime_resume_host(struct sdhci_host *host)
sdhci_do_set_ios(host, &host->mmc->ios); sdhci_do_set_ios(host, &host->mmc->ios);
sdhci_do_start_signal_voltage_switch(host, &host->mmc->ios); sdhci_do_start_signal_voltage_switch(host, &host->mmc->ios);
if (host_flags & SDHCI_PV_ENABLED) if ((host_flags & SDHCI_PV_ENABLED) &&
sdhci_do_enable_preset_value(host, true); !(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN)) {
spin_lock_irqsave(&host->lock, flags);
sdhci_enable_preset_value(host, true);
spin_unlock_irqrestore(&host->lock, flags);
}
/* Set the re-tuning expiration flag */ /* Set the re-tuning expiration flag */
if (host->flags & SDHCI_USING_RETUNING_TIMER) if (host->flags & SDHCI_USING_RETUNING_TIMER)

View File

@ -229,6 +229,18 @@
/* 60-FB reserved */ /* 60-FB reserved */
#define SDHCI_PRESET_FOR_SDR12 0x66
#define SDHCI_PRESET_FOR_SDR25 0x68
#define SDHCI_PRESET_FOR_SDR50 0x6A
#define SDHCI_PRESET_FOR_SDR104 0x6C
#define SDHCI_PRESET_FOR_DDR50 0x6E
#define SDHCI_PRESET_DRV_MASK 0xC000
#define SDHCI_PRESET_DRV_SHIFT 14
#define SDHCI_PRESET_CLKGEN_SEL_MASK 0x400
#define SDHCI_PRESET_CLKGEN_SEL_SHIFT 10
#define SDHCI_PRESET_SDCLK_FREQ_MASK 0x3FF
#define SDHCI_PRESET_SDCLK_FREQ_SHIFT 0
#define SDHCI_SLOT_INT_STATUS 0xFC #define SDHCI_SLOT_INT_STATUS 0xFC
#define SDHCI_HOST_VERSION 0xFE #define SDHCI_HOST_VERSION 0xFE

View File

@ -136,7 +136,6 @@ struct mmc_host_ops {
/* The tuning command opcode value is different for SD and eMMC cards */ /* The tuning command opcode value is different for SD and eMMC cards */
int (*execute_tuning)(struct mmc_host *host, u32 opcode); int (*execute_tuning)(struct mmc_host *host, u32 opcode);
void (*enable_preset_value)(struct mmc_host *host, bool enable);
int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv); int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv);
void (*hw_reset)(struct mmc_host *host); void (*hw_reset)(struct mmc_host *host);
void (*card_event)(struct mmc_host *host); void (*card_event)(struct mmc_host *host);

View File

@ -94,6 +94,7 @@ struct sdhci_host {
#define SDHCI_QUIRK2_HOST_NO_CMD23 (1<<1) #define SDHCI_QUIRK2_HOST_NO_CMD23 (1<<1)
/* The system physically doesn't support 1.8v, even if the host does */ /* The system physically doesn't support 1.8v, even if the host does */
#define SDHCI_QUIRK2_NO_1_8_V (1<<2) #define SDHCI_QUIRK2_NO_1_8_V (1<<2)
#define SDHCI_QUIRK2_PRESET_VALUE_BROKEN (1<<3)
int irq; /* Device IRQ */ int irq; /* Device IRQ */
void __iomem *ioaddr; /* Mapped address */ void __iomem *ioaddr; /* Mapped address */