diff options
author | Tony Lin <tony.lin@freescale.com> | 2011-07-12 11:08:57 +0800 |
---|---|---|
committer | Tony Lin <tony.lin@freescale.com> | 2011-07-13 19:08:41 +0800 |
commit | 63657c69e7957e8d077cb97bee4910536ff91600 (patch) | |
tree | 4f0be1a176c778609805678497359872e8138cb6 /drivers/mmc | |
parent | af5ae3ea01b0e9ff814c72db794ddf39ffe0a0a6 (diff) |
ENGR00152547-03 [MX6Q]add SDHC3.0 support on uSDHC controller
add voltage switch function due to SDHC3.0 spec requirement
add tuning function due to SDHC3.0 spec requirement
extend some functions to support SDR50 & SDR104 speed mode
Signed-off-by: Tony Lin <tony.lin@freescale.com>
Diffstat (limited to 'drivers/mmc')
-rw-r--r-- | drivers/mmc/core/core.c | 13 | ||||
-rw-r--r-- | drivers/mmc/core/core.h | 1 | ||||
-rw-r--r-- | drivers/mmc/core/sd.c | 159 | ||||
-rw-r--r-- | drivers/mmc/core/sd_ops.c | 43 | ||||
-rw-r--r-- | drivers/mmc/core/sd_ops.h | 2 |
5 files changed, 211 insertions, 7 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 150b5f3cd401..cf237f148001 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -648,6 +648,18 @@ void mmc_set_clock(struct mmc_host *host, unsigned int hz) mmc_set_ios(host); } +void mmc_set_tuning(struct mmc_host *host, unsigned int tuning) +{ + WARN_ON(tuning < host->tuning_min); + if (tuning > host->tuning_max) + tuning = host->tuning_max; + + host->ios.tuning = tuning; + host->ios.tuning_flag = 1; + mmc_set_ios(host); + host->ios.tuning_flag = 0; +} + #ifdef CONFIG_MMC_CLKGATE /* * This gates the clock by setting it to 0 Hz. @@ -1002,6 +1014,7 @@ static void mmc_power_off(struct mmc_host *host) { host->ios.clock = 0; host->ios.vdd = 0; + host->ocr = 0; if (!mmc_host_is_spi(host)) { host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; host->ios.chip_select = MMC_CS_DONTCARE; diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index ca1fdde29df6..1e46b5838c9e 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -33,6 +33,7 @@ void mmc_init_erase(struct mmc_card *card); void mmc_set_chip_select(struct mmc_host *host, int mode); void mmc_set_clock(struct mmc_host *host, unsigned int hz); +void mmc_set_tuning(struct mmc_host *host, unsigned int tuning); void mmc_gate_clock(struct mmc_host *host); void mmc_ungate_clock(struct mmc_host *host); void mmc_set_ungated(struct mmc_host *host); diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index d18c32bca99b..35d082e43dc7 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -42,6 +42,8 @@ static const unsigned int tacc_mant[] = { 35, 40, 45, 50, 55, 60, 70, 80, }; +#define CCS_BIT (1 << 30) +#define S18A_BIT (1 << 24) #define UNSTUFF_BITS(resp,start,size) \ ({ \ const int __size = size; \ @@ -188,7 +190,7 @@ static int mmc_decode_scr(struct mmc_card *card) scr->sda_vsn = UNSTUFF_BITS(resp, 56, 4); scr->bus_widths = UNSTUFF_BITS(resp, 48, 4); - + scr->sda_vsn3 = UNSTUFF_BITS(resp, 47, 1); if (UNSTUFF_BITS(resp, 55, 1)) card->erased_byte = 0xFF; else @@ -296,7 +298,10 @@ static int mmc_read_switch(struct mmc_card *card) if (status[13] & 0x02) card->sw_caps.hs_max_dtr = 50000000; - + if (status[13] & 0x04) + card->sw_caps.hs_max_dtr = 100000000; + if (status[13] & 0x08) + card->sw_caps.hs_max_dtr = 200000000; out: kfree(status); @@ -351,6 +356,69 @@ out: return err; } +/* + * Test if the card supports SDR mode and, if so, switch to it. + */ +int mmc_sd_switch_sdr_mode(struct mmc_card *card, int mode) +{ + int err; + u8 *status; + u8 function; + + if (card->scr.sda_vsn < SCR_SPEC_VER_1 || \ + card->scr.sda_vsn3 == 0) + return 0; + + if (!(card->csd.cmdclass & CCC_SWITCH)) + return 0; + + if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED)) + return 0; + + if (card->sw_caps.hs_max_dtr == 0) + return 0; + + err = -EIO; + + status = kmalloc(64, GFP_KERNEL); + if (!status) { + printk(KERN_ERR "%s: could not allocate a buffer for " + "switch capabilities.\n", mmc_hostname(card->host)); + return -ENOMEM; + } + + switch (mode) { + case MMC_STATE_SD_SDR50: + function = 2; + break; + case MMC_STATE_SD_SDR104: + function = 3; + break; + case MMC_STATE_SD_DDR50: + function = 4; + break; + default: + function = 1; + break; + } + err = mmc_sd_switch(card, 1, 0, function, status); + if (err) + goto out; + + if ((status[16] & 0xF) != function) { + printk(KERN_WARNING "%s: Problem switching card " + "into sdr mode!function: %d\n", + mmc_hostname(card->host), function); + err = 0; + } else { + err = 1; + } + +out: + kfree(status); + + return err; +} MMC_DEV_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1], card->raw_cid[2], card->raw_cid[3]); MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1], @@ -396,13 +464,30 @@ struct device_type sd_type = { .groups = sd_attr_groups, }; +static int mmc_vol_switch(struct mmc_host *host) +{ + struct mmc_command cmd; + int err; + + cmd.opcode = SD_VOLTAGE_SWITCH; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + + err = mmc_wait_for_cmd(host, &cmd, 0); + if (err) + return err; + msleep(10); + + return 0; +} + /* * Fetch CID from card. */ int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid) { int err; - + int new_ocr; /* * Since we're changing the OCR value, we seem to * need to tell some cards to go back to the idle @@ -421,10 +506,22 @@ int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid) if (!err) ocr |= 1 << 30; - err = mmc_send_app_op_cond(host, ocr, NULL); + ocr |= S18A_BIT | CCS_BIT; + err = mmc_send_app_op_cond(host, ocr, &new_ocr); if (err) return err; - + else { + if ((new_ocr & S18A_BIT) && \ + (host->ocr_avail_sd & MMC_VDD_165_195)) { + new_ocr = MMC_VDD_165_195; + mmc_vol_switch(host); + } + host->ocr = mmc_select_voltage(host, new_ocr); + if (!host->ocr) { + err = -EINVAL; + return err; + } + } if (mmc_host_is_spi(host)) err = mmc_send_cid(host, cid); else @@ -552,6 +649,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *card; int err; u32 cid[4]; + u32 clock = 50000000; BUG_ON(!host); WARN_ON(!host->claimed); @@ -621,7 +719,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, /* * Set bus speed. */ - mmc_set_clock(host, mmc_sd_get_max_clock(card)); + mmc_set_clock(host, min(mmc_sd_get_max_clock(card), clock)); /* * Switch to wider bus (if supported). @@ -635,6 +733,55 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, mmc_set_bus_width(host, MMC_BUS_WIDTH_4); } + if (mmc_sd_get_max_clock(card) <= clock) + goto skip_sdr_tuning; + + + /* + * Attempt to change to sdr-speed (if supported) + */ + err = mmc_sd_switch_sdr_mode(card, MMC_STATE_SD_SDR104); + if (err > 0) { + clock = 200000000; + mmc_sd_go_highspeed(card); + goto skip_sdr_mode; + } + err = mmc_sd_switch_sdr_mode(card, MMC_STATE_SD_SDR50); + if (err > 0) { + clock = 100000000; + mmc_sd_go_highspeed(card); + } else + goto skip_sdr_tuning; +skip_sdr_mode: + mmc_set_clock(host, min(mmc_sd_get_max_clock(card), clock)); + + { + int min, max, avg; + + min = host->tuning_min; + while (min < host->tuning_max) { + mmc_set_tuning(host, min); + if (!mmc_send_tuning_cmd(card)) + break; + min += host->tuning_step; + } + + max = min; + while (max < host->tuning_max) { + mmc_set_tuning(host, max); + if (mmc_send_tuning_cmd(card)) + break; + max += host->tuning_step; + } + + avg = (min + max) / 2; + mmc_set_tuning(host, avg); + mmc_send_tuning_cmd(card); + mmc_send_tuning_cmd(card); + } + +skip_sdr_tuning: + host->card = card; return 0; diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index 797cdb5887fd..183861eadf6f 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -346,6 +346,49 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, return 0; } +int mmc_send_tuning_cmd(struct mmc_card *card) +{ + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + struct scatterlist sg; + char scr[64]; + + BUG_ON(!card); + BUG_ON(!card->host); + + memset(&mrq, 0, sizeof(struct mmc_request)); + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + memset(scr, 0, 64); + + mrq.cmd = &cmd; + mrq.data = &data; + + cmd.opcode = SD_TUNING_CMD; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + + data.blksz = 64; + data.blocks = 1; + data.flags = MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + sg_init_one(&sg, scr, 64); + + mmc_set_data_timeout(&data, card); + + mmc_wait_for_req(card->host, &mrq); + + if (cmd.error) + return cmd.error; + if (data.error) + return data.error; + + return 0; +} + int mmc_app_sd_status(struct mmc_card *card, void *ssr) { int err; diff --git a/drivers/mmc/core/sd_ops.h b/drivers/mmc/core/sd_ops.h index ffc2305d905f..c336c3e44a86 100644 --- a/drivers/mmc/core/sd_ops.h +++ b/drivers/mmc/core/sd_ops.h @@ -20,6 +20,6 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr); int mmc_sd_switch(struct mmc_card *card, int mode, int group, u8 value, u8 *resp); int mmc_app_sd_status(struct mmc_card *card, void *ssr); - +int mmc_send_tuning_cmd(struct mmc_card *card); #endif |