[U-Boot] [PATCH 10/33] mmc: add support for HS200 mode of eMMC4.5
Ziyuan Xu
xzy.xu at rock-chips.com
Mon May 15 06:07:04 UTC 2017
Add the support of the HS200 mode for eMMC 4.5 devices. The eMMC 4.5
device has support up to 200MHz bus speed, it can speed up the boot speed.
We can enable this feature via MMC_MODE_HS200 if the host controller has
the ability to support HS200 timing. Also the tuning feature required
when the HS200 mode is selected.
By the way, mmc card can only switch to high speed mode in SPL stage.
Signed-off-by: Ziyuan Xu <xzy.xu at rock-chips.com>
---
drivers/mmc/mmc.c | 395 ++++++++++++++++++++++++++++++++++++------------------
include/mmc.h | 27 ++++
2 files changed, 289 insertions(+), 133 deletions(-)
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index 9aee6ff..bebf8f3 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -318,6 +318,26 @@ ulong mmc_bread(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt,
return blkcnt;
}
+void mmc_set_clock(struct mmc *mmc, uint clock)
+{
+ if (clock > mmc->cfg->f_max)
+ clock = mmc->cfg->f_max;
+
+ if (clock < mmc->cfg->f_min)
+ clock = mmc->cfg->f_min;
+
+ mmc->clock = clock;
+
+ mmc_set_ios(mmc);
+}
+
+static void mmc_set_bus_width(struct mmc *mmc, uint width)
+{
+ mmc->bus_width = width;
+
+ mmc_set_ios(mmc);
+}
+
static void mmc_set_timing(struct mmc *mmc, uint timing)
{
mmc->timing = timing;
@@ -587,6 +607,181 @@ int mmc_switch(struct mmc *mmc, u8 set, u8 index, u8 value)
return __mmc_switch(mmc, set, index, value, true);
}
+static int mmc_select_bus_width(struct mmc *mmc)
+{
+ u32 ext_csd_bits[] = {
+ EXT_CSD_BUS_WIDTH_8,
+ EXT_CSD_BUS_WIDTH_4,
+ };
+ u32 bus_widths[] = {
+ MMC_BUS_WIDTH_8BIT,
+ MMC_BUS_WIDTH_4BIT,
+ };
+ ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
+ ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN);
+ u32 idx, bus_width = 0;
+ int err = 0;
+
+ if (mmc->version < MMC_VERSION_4 ||
+ !(mmc->cfg->host_caps & (MMC_MODE_4BIT | MMC_MODE_8BIT)))
+ return 0;
+
+ err = mmc_send_ext_csd(mmc, ext_csd);
+
+ if (err)
+ return err;
+
+ idx = (mmc->cfg->host_caps & MMC_MODE_8BIT) ? 0 : 1;
+
+ /*
+ * Unlike SD, MMC cards dont have a configuration register to notify
+ * supported bus width. So bus test command should be run to identify
+ * the supported bus width or compare the ext csd values of current
+ * bus width and ext csd values of 1 bit mode read earlier.
+ */
+ for (; idx < ARRAY_SIZE(bus_widths); idx++) {
+ /*
+ * Host is capable of 8bit transfer, then switch
+ * the device to work in 8bit transfer mode. If the
+ * mmc switch command returns error then switch to
+ * 4bit transfer mode. On success set the corresponding
+ * bus width on the host.
+ */
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BUS_WIDTH, ext_csd_bits[idx]);
+ if (err)
+ continue;
+
+ bus_width = bus_widths[idx];
+ mmc_set_bus_width(mmc, bus_width);
+
+ err = mmc_send_ext_csd(mmc, test_csd);
+
+ if (err)
+ continue;
+
+ /* Only compare read only fields */
+ if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] ==
+ test_csd[EXT_CSD_PARTITIONING_SUPPORT]) &&
+ (ext_csd[EXT_CSD_HC_WP_GRP_SIZE] ==
+ test_csd[EXT_CSD_HC_WP_GRP_SIZE]) &&
+ (ext_csd[EXT_CSD_REV] == test_csd[EXT_CSD_REV]) &&
+ (ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] ==
+ test_csd[EXT_CSD_HC_ERASE_GRP_SIZE]) &&
+ !memcmp(&ext_csd[EXT_CSD_SEC_CNT],
+ &test_csd[EXT_CSD_SEC_CNT], 4)) {
+ err = bus_width;
+ break;
+ } else {
+ err = -EBADMSG;
+ }
+ }
+
+ return err;
+}
+
+static const u8 tuning_blk_pattern_4bit[] = {
+ 0xff, 0x0f, 0xff, 0x00, 0xff, 0xcc, 0xc3, 0xcc,
+ 0xc3, 0x3c, 0xcc, 0xff, 0xfe, 0xff, 0xfe, 0xef,
+ 0xff, 0xdf, 0xff, 0xdd, 0xff, 0xfb, 0xff, 0xfb,
+ 0xbf, 0xff, 0x7f, 0xff, 0x77, 0xf7, 0xbd, 0xef,
+ 0xff, 0xf0, 0xff, 0xf0, 0x0f, 0xfc, 0xcc, 0x3c,
+ 0xcc, 0x33, 0xcc, 0xcf, 0xff, 0xef, 0xff, 0xee,
+ 0xff, 0xfd, 0xff, 0xfd, 0xdf, 0xff, 0xbf, 0xff,
+ 0xbb, 0xff, 0xf7, 0xff, 0xf7, 0x7f, 0x7b, 0xde,
+};
+
+static const u8 tuning_blk_pattern_8bit[] = {
+ 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc,
+ 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff,
+ 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff,
+ 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd,
+ 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb,
+ 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff,
+ 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff,
+ 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc,
+ 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff,
+ 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee,
+ 0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd,
+ 0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff,
+ 0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff,
+ 0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee,
+};
+
+int mmc_send_tuning(struct mmc *mmc, u32 opcode)
+{
+ struct mmc_cmd cmd;
+ struct mmc_data data;
+ const u8 *tuning_block_pattern;
+ int size, err = 0;
+ u8 *data_buf;
+
+ if (mmc->bus_width == MMC_BUS_WIDTH_8BIT) {
+ tuning_block_pattern = tuning_blk_pattern_8bit;
+ size = sizeof(tuning_blk_pattern_8bit);
+ } else if (mmc->bus_width == MMC_BUS_WIDTH_4BIT) {
+ tuning_block_pattern = tuning_blk_pattern_4bit;
+ size = sizeof(tuning_blk_pattern_4bit);
+ } else {
+ return -EINVAL;
+ }
+
+ data_buf = calloc(1, size);
+ if (!data_buf)
+ return -ENOMEM;
+
+ cmd.cmdidx = opcode;
+ cmd.resp_type = MMC_RSP_R1;
+ cmd.cmdarg = 0;
+
+ data.dest = (char *)data_buf;
+ data.blocksize = size;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+
+ err = mmc_send_cmd(mmc, &cmd, &data);
+ if (err)
+ goto out;
+
+ if (memcmp(data_buf, tuning_block_pattern, size))
+ err = -EIO;
+out:
+ free(data_buf);
+ return err;
+}
+
+static int mmc_execute_tuning(struct mmc *mmc)
+{
+#ifdef CONFIG_DM_MMC_OPS
+ struct dm_mmc_ops *ops = mmc_get_ops(mmc->dev);
+#endif
+ u32 opcode;
+
+ if (IS_SD(mmc))
+ opcode = MMC_SEND_TUNING_BLOCK;
+ else
+ opcode = MMC_SEND_TUNING_BLOCK_HS200;
+
+#ifndef CONFIG_DM_MMC_OPS
+ if (mmc->cfg->ops->execute_tuning) {
+ return mmc->cfg->ops->execute_tuning(mmc, opcode);
+#else
+ if (ops->execute_tuning) {
+ return ops->execute_tuning(mmc->dev, opcode);
+#endif
+ } else {
+ debug("Tuning feature required for HS200 mode.\n");
+ return -EIO;
+ }
+}
+
+static int mmc_hs200_tuning(struct mmc *mmc)
+{
+ return mmc_execute_tuning(mmc);
+}
+
static int mmc_select_hs(struct mmc *mmc)
{
int ret;
@@ -600,6 +795,45 @@ static int mmc_select_hs(struct mmc *mmc)
return ret;
}
+#ifndef CONFIG_SPL_BUILD
+static int mmc_select_hs200(struct mmc *mmc)
+{
+ int ret;
+ struct mmc_cmd cmd;
+
+ /*
+ * Set the bus width(4 or 8) with host's support and
+ * switch to HS200 mode if bus width is set successfully.
+ */
+ ret = mmc_select_bus_width(mmc);
+
+ if (ret > 0) {
+ ret = __mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_HS_TIMING,
+ EXT_CSD_TIMING_HS200, false);
+
+ if (ret)
+ return ret;
+
+ mmc_set_timing(mmc, MMC_TIMING_MMC_HS200);
+
+ cmd.cmdidx = MMC_CMD_SEND_STATUS;
+ cmd.resp_type = MMC_RSP_R1;
+ cmd.cmdarg = mmc->rca << 16;
+
+ ret = mmc_send_cmd(mmc, &cmd, NULL);
+
+ if (ret)
+ return ret;
+
+ if (cmd.response[0] & MMC_STATUS_SWITCH_ERROR)
+ return -EBADMSG;
+ }
+
+ return ret;
+}
+#endif
+
static u32 mmc_select_card_type(struct mmc *mmc, u8 *ext_csd)
{
u8 card_type;
@@ -651,10 +885,24 @@ static u32 mmc_select_card_type(struct mmc *mmc, u8 *ext_csd)
return avail_type;
}
+static void mmc_set_bus_speed(struct mmc *mmc, u8 avail_type)
+{
+ int clock = 0;
+
+ if (mmc_card_hs(mmc))
+ clock = (avail_type & EXT_CSD_CARD_TYPE_52) ?
+ MMC_HIGH_52_MAX_DTR : MMC_HIGH_26_MAX_DTR;
+ else if (mmc_card_hs200(mmc) ||
+ mmc_card_hs400(mmc) ||
+ mmc_card_hs400es(mmc))
+ clock = MMC_HS200_MAX_DTR;
+
+ mmc_set_clock(mmc, clock);
+}
+
static int mmc_change_freq(struct mmc *mmc)
{
ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
- char cardtype;
u32 avail_type;
int err;
@@ -674,9 +922,13 @@ static int mmc_change_freq(struct mmc *mmc)
if (err)
return err;
- cardtype = ext_csd[EXT_CSD_CARD_TYPE] & 0xf;
avail_type = mmc_select_card_type(mmc, ext_csd);
+#ifndef CONFIG_SPL_BUILD
+ if (avail_type & EXT_CSD_CARD_TYPE_HS200)
+ err = mmc_select_hs200(mmc);
+ else
+#endif
if (avail_type & EXT_CSD_CARD_TYPE_HS)
err = mmc_select_hs(mmc);
else
@@ -685,26 +937,14 @@ static int mmc_change_freq(struct mmc *mmc)
if (err)
return err;
- /* Now check to see that it worked */
- err = mmc_send_ext_csd(mmc, ext_csd);
-
- if (err)
- return err;
+ mmc_set_bus_speed(mmc, avail_type);
- /* No high-speed support */
- if (!ext_csd[EXT_CSD_HS_TIMING])
- return 0;
-
- /* High Speed is set, there are two types: 52MHz and 26MHz */
- if (cardtype & EXT_CSD_CARD_TYPE_52) {
- if (cardtype & EXT_CSD_CARD_TYPE_DDR_1_8V)
- mmc->card_caps |= MMC_MODE_DDR_52MHz;
- mmc->card_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
- } else {
- mmc->card_caps |= MMC_MODE_HS;
- }
+ if (mmc_card_hs200(mmc))
+ err = mmc_hs200_tuning(mmc);
+ else
+ err = mmc_select_bus_width(mmc) > 0 ? 0 : err;
- return 0;
+ return err;
}
static int mmc_set_capacity(struct mmc *mmc, int part_num)
@@ -1206,26 +1446,6 @@ static bool mmc_can_card_busy(struct mmc *)
}
#endif
-void mmc_set_clock(struct mmc *mmc, uint clock)
-{
- if (clock > mmc->cfg->f_max)
- clock = mmc->cfg->f_max;
-
- if (clock < mmc->cfg->f_min)
- clock = mmc->cfg->f_min;
-
- mmc->clock = clock;
-
- mmc_set_ios(mmc);
-}
-
-static void mmc_set_bus_width(struct mmc *mmc, uint width)
-{
- mmc->bus_width = width;
-
- mmc_set_ios(mmc);
-}
-
static int mmc_startup(struct mmc *mmc)
{
int err, i;
@@ -1233,7 +1453,6 @@ static int mmc_startup(struct mmc *mmc)
u64 cmult, csize, capacity;
struct mmc_cmd cmd;
ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
- ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN);
bool has_parts = false;
bool part_completed;
struct blk_desc *bdesc;
@@ -1576,102 +1795,12 @@ static int mmc_startup(struct mmc *mmc)
mmc->tran_speed = 50000000;
else
mmc->tran_speed = 25000000;
- } else if (mmc->version >= MMC_VERSION_4) {
- /* Only version 4 of MMC supports wider bus widths */
- int idx;
-
- /* An array of possible bus widths in order of preference */
- static unsigned ext_csd_bits[] = {
- EXT_CSD_DDR_BUS_WIDTH_8,
- EXT_CSD_DDR_BUS_WIDTH_4,
- EXT_CSD_BUS_WIDTH_8,
- EXT_CSD_BUS_WIDTH_4,
- EXT_CSD_BUS_WIDTH_1,
- };
-
- /* An array to map CSD bus widths to host cap bits */
- static unsigned ext_to_hostcaps[] = {
- [EXT_CSD_DDR_BUS_WIDTH_4] =
- MMC_MODE_DDR_52MHz | MMC_MODE_4BIT,
- [EXT_CSD_DDR_BUS_WIDTH_8] =
- MMC_MODE_DDR_52MHz | MMC_MODE_8BIT,
- [EXT_CSD_BUS_WIDTH_4] = MMC_MODE_4BIT,
- [EXT_CSD_BUS_WIDTH_8] = MMC_MODE_8BIT,
- };
-
- /* An array to map chosen bus width to an integer */
- static unsigned widths[] = {
- 8, 4, 8, 4, 1,
- };
-
- for (idx=0; idx < ARRAY_SIZE(ext_csd_bits); idx++) {
- unsigned int extw = ext_csd_bits[idx];
- unsigned int caps = ext_to_hostcaps[extw];
-
- /*
- * If the bus width is still not changed,
- * don't try to set the default again.
- * Otherwise, recover from switch attempts
- * by switching to 1-bit bus width.
- */
- if (extw == EXT_CSD_BUS_WIDTH_1 &&
- mmc->bus_width == 1) {
- err = 0;
- break;
- }
-
- /*
- * Check to make sure the card and controller support
- * these capabilities
- */
- if ((mmc->card_caps & caps) != caps)
- continue;
-
- err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
- EXT_CSD_BUS_WIDTH, extw);
-
- if (err)
- continue;
-
- mmc->ddr_mode = (caps & MMC_MODE_DDR_52MHz) ? 1 : 0;
- mmc_set_bus_width(mmc, widths[idx]);
- err = mmc_send_ext_csd(mmc, test_csd);
-
- if (err)
- continue;
-
- /* Only compare read only fields */
- if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT]
- == test_csd[EXT_CSD_PARTITIONING_SUPPORT] &&
- ext_csd[EXT_CSD_HC_WP_GRP_SIZE]
- == test_csd[EXT_CSD_HC_WP_GRP_SIZE] &&
- ext_csd[EXT_CSD_REV]
- == test_csd[EXT_CSD_REV] &&
- ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]
- == test_csd[EXT_CSD_HC_ERASE_GRP_SIZE] &&
- memcmp(&ext_csd[EXT_CSD_SEC_CNT],
- &test_csd[EXT_CSD_SEC_CNT], 4) == 0)
- break;
- else
- err = -EBADMSG;
- }
-
- if (err)
- return err;
-
- if (mmc->card_caps & MMC_MODE_HS) {
- if (mmc->card_caps & MMC_MODE_HS_52MHz)
- mmc->tran_speed = 52000000;
- else
- mmc->tran_speed = 26000000;
- }
+ mmc_set_clock(mmc, mmc->tran_speed);
}
- mmc_set_clock(mmc, mmc->tran_speed);
-
/* Fix the block length for DDR mode */
- if (mmc->ddr_mode) {
+ if (mmc_card_ddr(mmc)) {
mmc->read_bl_len = MMC_MAX_BLOCK_LEN;
mmc->write_bl_len = MMC_MAX_BLOCK_LEN;
}
diff --git a/include/mmc.h b/include/mmc.h
index 9bed935..a2ef986 100644
--- a/include/mmc.h
+++ b/include/mmc.h
@@ -85,6 +85,8 @@
#define MMC_CMD_SET_BLOCKLEN 16
#define MMC_CMD_READ_SINGLE_BLOCK 17
#define MMC_CMD_READ_MULTIPLE_BLOCK 18
+#define MMC_SEND_TUNING_BLOCK 19
+#define MMC_SEND_TUNING_BLOCK_HS200 21
#define MMC_CMD_SET_BLOCK_COUNT 23
#define MMC_CMD_WRITE_SINGLE_BLOCK 24
#define MMC_CMD_WRITE_MULTIPLE_BLOCK 25
@@ -387,6 +389,17 @@ struct dm_mmc_ops {
* @return 0 if write-enabled, 1 if write-protected, -ve on error
*/
int (*get_wp)(struct udevice *dev);
+
+ /**
+ * execute_tuning() - Find the optimal sampling point of a data
+ * input signals.
+ *
+ * @dev: Device to check
+ * @opcode: The tuning command opcode value is different
+ * for SD and eMMC cards
+ * @return 0 if write-enabled, 1 if write-protected, -ve on error
+ */
+ int (*execute_tuning)(struct udevice *dev, u32 opcode);
};
#define mmc_get_ops(dev) ((struct dm_mmc_ops *)(dev)->driver->ops)
@@ -413,6 +426,7 @@ struct mmc_ops {
int (*init)(struct mmc *mmc);
int (*getcd)(struct mmc *mmc);
int (*getwp)(struct mmc *mmc);
+ int (*execute_tuning)(struct udevice *dev, u32 opcode);
};
#endif
@@ -451,6 +465,11 @@ struct mmc {
uint has_init;
int high_capacity;
uint bus_width;
+
+#define MMC_BUS_WIDTH_1BIT 1
+#define MMC_BUS_WIDTH_4BIT 4
+#define MMC_BUS_WIDTH_8BIT 8
+
uint timing;
#define MMC_TIMING_LEGACY 0
@@ -467,6 +486,12 @@ struct mmc {
#define MMC_TIMING_MMC_HS400ES 11
uint clock;
+
+#define MMC_HIGH_26_MAX_DTR 26000000
+#define MMC_HIGH_52_MAX_DTR 52000000
+#define MMC_HIGH_DDR_MAX_DTR 52000000
+#define MMC_HS200_MAX_DTR 200000000
+
uint card_caps;
uint ocr;
uint dsr;
@@ -559,6 +584,8 @@ static inline bool mmc_card_hs400es(struct mmc *mmc)
return mmc->timing == MMC_TIMING_MMC_HS400ES;
}
+int mmc_send_tuning(struct mmc *mmc, u32 opcode);
+
struct mmc *mmc_create(const struct mmc_config *cfg, void *priv);
/**
--
2.7.4
More information about the U-Boot
mailing list