summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorAnish Trivedi <anish@freescale.com>2011-10-19 13:51:30 -0500
committerAnish Trivedi <anish@freescale.com>2011-10-27 15:56:23 -0500
commitec10ca317794cd7e83e11df57e89085046ccd940 (patch)
treeb48157b44d03963573adf2b3fefc1721097a3645 /drivers
parent1b69b48bbaacca9b355a56598ae5e7342442251f (diff)
ENGR00139221 USDHC Add SDXC UHS-I support
Modified MMC library for UHS-I command sequence Added support to USDHC driver for UHS-I Signed-off-by: Anish Trivedi <anish@freescale.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mmc/imx_esdhc.c82
-rw-r--r--drivers/mmc/mmc.c225
2 files changed, 288 insertions, 19 deletions
diff --git a/drivers/mmc/imx_esdhc.c b/drivers/mmc/imx_esdhc.c
index a326f87c4e..ab7f585950 100644
--- a/drivers/mmc/imx_esdhc.c
+++ b/drivers/mmc/imx_esdhc.c
@@ -72,7 +72,8 @@ struct fsl_esdhc {
char reserved2[12];
uint dllctrl;
uint dllstatus;
- char reserved3[88];
+ uint clktunectrlstatus;
+ char reserved3[84];
uint vendorspec;
uint mmcboot;
char reserved4[52];
@@ -165,7 +166,7 @@ static int esdhc_setup_data(struct mmc *mmc, struct mmc_data *data)
static int
esdhc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
{
- uint xfertyp;
+ uint xfertyp, mixctrl;
uint irqstat;
u32 tmp, sysctl_restore;
struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv;
@@ -211,8 +212,13 @@ esdhc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
/* Send the command */
writel(cmd->cmdarg, &regs->cmdarg);
- /* for uSDHC, write lower-half of xfertyp to mixctrl */
- writel((xfertyp & 0xFFFF), &regs->mixctrl);
+
+ /* write lower-half of xfertyp to mixctrl */
+ mixctrl = xfertyp & 0xFFFF;
+ /* Keep the bits 22-25 of the register as is */
+ mixctrl |= (readl(&regs->mixctrl) & (0xF << 22));
+ writel(mixctrl, &regs->mixctrl);
+
writel(xfertyp, &regs->xfertyp);
/* Mask all irqs */
@@ -241,6 +247,11 @@ esdhc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
/* Restore auto-clock gate if error */
if (!cfg->is_usdhc && !data && (cmd->resp_type & MMC_RSP_BUSY))
writel(sysctl_restore, &regs->sysctl);
+
+ /* If this was CMD11, then notify that power cycle is needed */
+ if (cmd->cmdidx == SD_CMD_SWITCH_UHS18V)
+ printf("CMD11 to switch to 1.8V mode failed."
+ "Card requires power cycle\n");
}
if (irqstat & CMD_ERR)
@@ -249,6 +260,27 @@ esdhc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
if (irqstat & IRQSTAT_CTOE)
return TIMEOUT;
+ /* Switch voltage to 1.8V if CMD11 succeeded */
+ if (cmd->cmdidx == SD_CMD_SWITCH_UHS18V) {
+ /* Set SD_VSELECT to switch to 1.8V */
+ u32 reg;
+ reg = readl(&regs->vendorspec);
+ reg |= VENDORSPEC_VSELECT;
+ writel(reg, &regs->vendorspec);
+
+ /* Sleep for 5 ms - max time for card to switch to 1.8V */
+ udelay(5000);
+
+ /* Turn on SD clock */
+ writel(reg | VENDORSPEC_FRC_SDCLK_ON, &regs->vendorspec);
+
+ while (!(readl(&regs->prsstat) & PRSSTAT_DAT0))
+ ;
+
+ /* restore SD clock status */
+ writel(reg, &regs->vendorspec);
+ }
+
/* Workaround for ESDHC errata ENGcm03648 */
if (!cfg->is_usdhc && !data && (cmd->resp_type & MMC_RSP_BUSY)) {
int timeout = 2500;
@@ -355,6 +387,12 @@ void set_sysctl(struct mmc *mmc, uint clock)
} else
pre_div = 2;
+ /* For the case where clock requested is equal to SDHC clock,
+ * the pre_div should be 1.
+ */
+ if (clock == sdhc_clk)
+ pre_div = 1;
+
for (div = 1; div <= 16; div++)
if ((sdhc_clk / (div * pre_div)) <= clock)
break;
@@ -494,6 +532,24 @@ static void esdhc_set_ios(struct mmc *mmc)
esdhc_dll_setup(mmc);
}
+static void esdhc_uhsi_tuning(struct mmc *mmc, uint val)
+{
+ struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv;
+ struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base;
+ u32 mixctrl;
+
+ /* No tuning needed for 50 MHz or lower */
+ if (mmc->card_uhs_mode < SD_UHSI_FUNC_SDR50)
+ return;
+
+ mixctrl = readl(&regs->mixctrl);
+ mixctrl |= USDHC_MIXCTRL_EXE_TUNE | \
+ USDHC_MIXCTRL_SMPCLK_SEL | \
+ USDHC_MIXCTRL_FBCLK_SEL;
+ writel(mixctrl, &regs->mixctrl);
+ writel((val << 8), &regs->clktunectrlstatus);
+}
+
static int esdhc_init(struct mmc *mmc)
{
struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv;
@@ -509,6 +565,12 @@ static int esdhc_init(struct mmc *mmc)
/* RSTA doesn't reset MMC_BOOT register, so manually reset it */
writel(0, &regs->mmcboot);
+ /* Reset MIX_CTRL and CLK_TUNE_CTRL_STATUS regs to 0 */
+ writel(0, &regs->mixctrl);
+ writel(0, &regs->clktunectrlstatus);
+
+ /* Put VEND_SPEC to default value */
+ writel(VENDORSPEC_INIT, &regs->vendorspec);
#ifdef CONFIG_IMX_ESDHC_V1
tmp = readl(&regs->sysctl) | (SYSCTL_HCKEN | SYSCTL_IPGEN);
@@ -560,6 +622,7 @@ int fsl_esdhc_initialize(bd_t *bis, struct fsl_esdhc_cfg *cfg)
mmc->send_cmd = esdhc_send_cmd;
mmc->set_ios = esdhc_set_ios;
mmc->init = esdhc_init;
+ mmc->set_tuning = esdhc_uhsi_tuning;
/* Enable uSDHC if the config is defined (only for i.MX50 in SDR mode) */
#ifdef CONFIG_MX50_ENABLE_USDHC_SDR
@@ -570,6 +633,7 @@ int fsl_esdhc_initialize(bd_t *bis, struct fsl_esdhc_cfg *cfg)
sprintf(mmc->name, "FSL_USDHC");
caps = readl(&regs->hostcapblt);
+
if (caps & ESDHC_HOSTCAPBLT_VS30)
mmc->voltages |= MMC_VDD_29_30 | MMC_VDD_30_31;
if (caps & ESDHC_HOSTCAPBLT_VS33)
@@ -596,6 +660,16 @@ int fsl_esdhc_initialize(bd_t *bis, struct fsl_esdhc_cfg *cfg)
mmc->f_min = 400000;
mmc->f_max = MIN(mxc_get_clock(MXC_ESDHC_CLK), 52000000);
+ if (cfg->is_usdhc) {
+ mmc->f_max = MIN(mxc_get_clock(MXC_ESDHC_CLK), 208000000);
+ mmc->tuning_max = USDHC_TUNE_CTRL_MAX;
+ mmc->tuning_min = USDHC_TUNE_CTRL_MIN;
+ mmc->tuning_step = USDHC_TUNE_CTRL_STEP;
+ }
+
+ if (cfg->port_supports_uhs18v)
+ mmc->host_caps |= SD_UHSI_CAP_ALL_MODES;
+
mmc_register(mmc);
#ifdef CONFIG_MMC_8BIT_PORTS
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index 54e2a21ea6..638edfae5c 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -328,8 +328,9 @@ mmc_bwrite(int dev_num, ulong start, lbaint_t blkcnt, const void *src)
blklen = mmc->write_bl_len;
- if (mmc->card_caps & EMMC_MODE_4BIT_DDR ||
- mmc->card_caps & EMMC_MODE_8BIT_DDR) {
+ if ((mmc->card_caps & EMMC_MODE_4BIT_DDR ||
+ mmc->card_caps & EMMC_MODE_8BIT_DDR) ||
+ (IS_SD(mmc) && mmc->high_capacity)) {
err = 0;
blklen = 512;
} else
@@ -346,7 +347,7 @@ mmc_bwrite(int dev_num, ulong start, lbaint_t blkcnt, const void *src)
return 0;
blocks_todo -= cur;
start += cur;
- src += cur * mmc->write_bl_len;
+ src += cur * blklen;
} while (blocks_todo > 0);
return blkcnt;
@@ -415,8 +416,9 @@ static ulong mmc_bread(int dev_num, ulong start, lbaint_t blkcnt, void *dst)
return 0;
}
- if (mmc->card_caps & EMMC_MODE_4BIT_DDR ||
- mmc->card_caps & EMMC_MODE_8BIT_DDR) {
+ if ((mmc->card_caps & EMMC_MODE_4BIT_DDR ||
+ mmc->card_caps & EMMC_MODE_8BIT_DDR) ||
+ (IS_SD(mmc) && mmc->high_capacity)) {
blklen = 512;
err = 0;
} else {
@@ -435,7 +437,7 @@ static ulong mmc_bread(int dev_num, ulong start, lbaint_t blkcnt, void *dst)
return 0;
blocks_todo -= cur;
start += cur;
- dst += cur * mmc->read_bl_len;
+ dst += cur * blklen;
} while (blocks_todo > 0);
return blkcnt;
@@ -494,9 +496,14 @@ sd_send_op_cond(struct mmc *mmc)
cmd.cmdarg = mmc_host_is_spi(mmc) ? 0 :
(mmc->voltages & 0xff8000);
- if (mmc->version == SD_VERSION_2)
+ /* Check for high capacity or UHS-I 1.8V signalling */
+ if (mmc->version == SD_VERSION_2) {
cmd.cmdarg |= OCR_HCS;
+ if (mmc->host_caps & SD_UHSI_CAP_ALL_MODES)
+ cmd.cmdarg |= SD_SWITCH_18V;
+ }
+
err = mmc_send_cmd(mmc, &cmd, NULL);
if (err)
@@ -526,6 +533,11 @@ sd_send_op_cond(struct mmc *mmc)
mmc->ocr = cmd.response[0];
mmc->high_capacity = ((mmc->ocr & OCR_HCS) == OCR_HCS);
+
+ /* Is card UHS-I compliant? */
+ if (mmc->host_caps & SD_UHSI_CAP_ALL_MODES)
+ mmc->uhs18v = ((mmc->ocr & SD_SWITCH_18V) == SD_SWITCH_18V);
+
mmc->rca = 0;
return 0;
@@ -833,6 +845,27 @@ err_rtn:
return -1;
}
+static int sd_send_switch_uhs18v(struct mmc *mmc)
+{
+ struct mmc_cmd cmd;
+ int err;
+
+ cmd.cmdidx = SD_CMD_SWITCH_UHS18V;
+ cmd.cmdarg = 0;
+ cmd.resp_type = MMC_RSP_R1;
+ cmd.flags = 0;
+
+ err = mmc_send_cmd(mmc, &cmd, NULL);
+
+ if (err) {
+ printf("mmc: CMD11 to switch to 1.8V UHS mode failed. \
+ Card will require power cycle\n");
+ return err;
+ }
+
+ return 0;
+}
+
int sd_switch_boot_part(int dev_num, unsigned int part_num)
{
return 0;
@@ -917,7 +950,9 @@ retry_scr:
mmc->version = SD_VERSION_1_10;
break;
case 2:
- mmc->version = SD_VERSION_2;
+ if ((mmc->scr[0] >> 15) & 0x1)
+ mmc->version = SD_VERSION_3;
+ /* else, it is already initialized as SD_VERSION_2 */
break;
default:
mmc->version = SD_VERSION_1_0;
@@ -948,15 +983,140 @@ retry_scr:
if (!(__be32_to_cpu(switch_status[3]) & SD_HIGHSPEED_SUPPORTED))
return 0;
- err = sd_switch(mmc, SD_SWITCH_SWITCH, 0, 1, (u8 *)&switch_status);
+ if (mmc->uhs18v) {
+ /* which UHS-I modes are supported by card? */
+ if (__be32_to_cpu(switch_status[3]) & SD_UHSI_CAP_SDR104)
+ mmc->card_caps |= SD_UHSI_CAP_SDR104;
+ if (__be32_to_cpu(switch_status[3]) & SD_UHSI_CAP_SDR50)
+ mmc->card_caps |= SD_UHSI_CAP_SDR50;
+ if (__be32_to_cpu(switch_status[3]) & SD_UHSI_CAP_DDR50)
+ mmc->card_caps |= SD_UHSI_CAP_DDR50;
+ if (__be32_to_cpu(switch_status[3]) & SD_UHSI_CAP_SDR25)
+ mmc->card_caps |= SD_UHSI_CAP_SDR25;
+ if (__be32_to_cpu(switch_status[3]) & SD_UHSI_CAP_SDR12)
+ mmc->card_caps |= SD_UHSI_CAP_SDR12;
+ } else {
+ /* Switch non-UHS card to high speed mode (50 MHz) */
+ err = sd_switch(mmc, SD_SWITCH_SWITCH, 0, 1,
+ (u8 *)&switch_status);
+
+ if (err)
+ return err;
+
+ if ((__be32_to_cpu(switch_status[4]) & 0x0f000000) ==
+ 0x01000000)
+ mmc->card_caps |= MMC_MODE_HS;
+ }
+
+ return 0;
+}
+
+/* Put the card in SDR50, DDR50, or SDR104 mode of UHS-I */
+int sd_uhsi_mode_select(struct mmc *mmc)
+{
+ int timeout, err;
+ uint switch_status[16];
+ uint func_num;
+
+ mmc->card_uhs_mode = SD_UHSI_FUNC_SDR12;
+
+ /* Order of checking important to pick fastest mode */
+ if (mmc->card_caps & SD_UHSI_CAP_SDR104)
+ func_num = SD_UHSI_FUNC_SDR104;
+ else if (mmc->card_caps & SD_UHSI_CAP_SDR50)
+ func_num = SD_UHSI_FUNC_SDR50;
+ else if (mmc->card_caps & SD_UHSI_CAP_DDR50)
+ func_num = SD_UHSI_FUNC_DDR50;
+ else if (mmc->card_caps & SD_UHSI_CAP_SDR25)
+ func_num = SD_UHSI_FUNC_SDR25;
+ else
+ return 0;
+
+ timeout = 4;
+ while (timeout--) {
+ err = sd_switch(mmc, SD_SWITCH_CHECK, 0, func_num,
+ (u8 *)&switch_status);
+
+ if (err)
+ return err;
+
+ /* The function is busy if bit is set. Try again */
+ if (!(__be32_to_cpu(switch_status[7]) & (1 << (16 + func_num))))
+ break;
+ }
+
+ err = sd_switch(mmc, SD_SWITCH_SWITCH, 0, func_num,
+ (u8 *)&switch_status);
if (err)
return err;
- if ((__be32_to_cpu(switch_status[4]) & 0x0f000000) == 0x01000000)
- mmc->card_caps |= MMC_MODE_HS;
+ if (((__be32_to_cpu(switch_status[4]) & 0x0f000000) >> 24) ==
+ func_num) {
+ mmc->card_uhs_mode = func_num;
+
+ if (mmc->card_uhs_mode == SD_UHSI_FUNC_DDR50)
+ mmc->card_caps |= EMMC_MODE_4BIT_DDR;
+ }
return 0;
+
+}
+
+int sd_send_tuning_cmd(struct mmc *mmc)
+{
+ struct mmc_cmd cmd;
+ struct mmc_data data;
+ char buff[64]; /* 64 byte tuning block */
+
+ /* Switch the frequency */
+ cmd.cmdidx = SD_CMD_TUNING;
+ cmd.resp_type = MMC_RSP_R1;
+ cmd.cmdarg = 0;
+ cmd.flags = 0;
+
+ data.dest = buff;
+ data.blocksize = 64;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+
+ return mmc_send_cmd(mmc, &cmd, &data);
+}
+
+void sd_uhsi_tuning(struct mmc *mmc)
+{
+ int min, max, avg;
+
+ /* Tuning only required for SDR50 and SDR104 modes */
+ if (mmc->card_uhs_mode != SD_UHSI_FUNC_SDR50 &&
+ mmc->card_uhs_mode != SD_UHSI_FUNC_SDR104)
+ return;
+
+ /* Start with lowest value, increase it until CMD19 succeeds */
+ min = mmc->tuning_min;
+ while (min < mmc->tuning_max) {
+ mmc->set_tuning(mmc, min);
+ if (!sd_send_tuning_cmd(mmc))
+ break;
+ min += mmc->tuning_step;
+ }
+
+ /* Start with last successful value, increase it until CMD19 fails */
+ max = min;
+ while (max < mmc->tuning_max) {
+ mmc->set_tuning(mmc, max);
+ if (sd_send_tuning_cmd(mmc))
+ break;
+ max += mmc->tuning_step;
+ }
+
+ /* Set tuning value to average of
+ * [lowest successful val, highest successful val]
+ */
+ avg = (min + max) / 2;
+ mmc->set_tuning(mmc, avg);
+ sd_send_tuning_cmd(mmc);
+ sd_send_tuning_cmd(mmc);
}
/* frequency bases */
@@ -1037,6 +1197,13 @@ int mmc_startup(struct mmc *mmc)
}
#endif
+ /* If this is a UHS-I compliant SD card, switch to 1.8V for I/O now */
+ if (mmc->uhs18v) {
+ err = sd_send_switch_uhs18v(mmc);
+ if (err)
+ return err;
+ }
+
/* Put the Card in Identify Mode */
cmd.cmdidx = mmc_host_is_spi(mmc) ? MMC_CMD_SEND_CID :
MMC_CMD_ALL_SEND_CID; /* cmd not supported in spi */
@@ -1236,10 +1403,36 @@ int mmc_startup(struct mmc *mmc)
mmc_set_bus_width(mmc, 4);
}
- if (mmc->card_caps & MMC_MODE_HS)
- mmc_set_clock(mmc, 50000000);
- else
- mmc_set_clock(mmc, 25000000);
+ /* Switch the card and host to UHS-I modes, if available */
+ if (mmc->uhs18v) {
+ err = sd_uhsi_mode_select(mmc);
+ if (err)
+ return err;
+
+ switch (mmc->card_uhs_mode) {
+ case SD_UHSI_FUNC_SDR104:
+ mmc_set_clock(mmc, 208000000);
+ break;
+ case SD_UHSI_FUNC_SDR50:
+ mmc_set_clock(mmc, 100000000);
+ break;
+ case SD_UHSI_FUNC_SDR25:
+ case SD_UHSI_FUNC_DDR50:
+ mmc_set_clock(mmc, 50000000);
+ break;
+ case SD_UHSI_FUNC_SDR12:
+ default:
+ mmc_set_clock(mmc, 25000000);
+ break;
+ }
+
+ sd_uhsi_tuning(mmc);
+ } else {
+ if (mmc->card_caps & MMC_MODE_HS)
+ mmc_set_clock(mmc, 50000000);
+ else
+ mmc_set_clock(mmc, 25000000);
+ }
} else {
if (mmc->card_caps & MMC_MODE_4BIT) {
@@ -1363,6 +1556,8 @@ int mmc_init(struct mmc *mmc)
if (mmc->has_init)
return 0;
+ mmc->uhs18v = 0;
+
err = mmc->init(mmc);
if (err)