feat(mmc): manage SD Switch Function for high speed mode
On SD-cards, Switch Function Command (CMD6) is used to switch
functions, like setting High Speed mode. It is useful for high capacity
cards to double frequency (from 25MHz by default to 50MHz).
If the SD-card is High Capacity, a CMD6 is issued after filling the
device information. If High Speed mode is supported and the switch is
OK, then the max_bus_freq can be set to 50MHz. The driver set_ios()
function should then be called to update peripheral configuration,
especially clock prescaler.
Change-Id: I2d6807aa7f9440d2b2f907a747cd3b47a2ba1545
Signed-off-by: Yann Gautier <yann.gautier@st.com>
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index 116afda..1b6e96b 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -25,6 +25,7 @@
static const struct mmc_ops *ops;
static unsigned int mmc_ocr_value;
static struct mmc_csd_emmc mmc_csd;
+static struct sd_switch_status sd_switch_func_status;
static unsigned char mmc_ext_csd[512] __aligned(16);
static unsigned int mmc_flags;
static struct mmc_device_info *mmc_dev_info;
@@ -44,6 +45,11 @@
return ((mmc_flags & MMC_FLAG_CMD23) != 0U);
}
+static bool is_sd_cmd6_enabled(void)
+{
+ return ((mmc_flags & MMC_FLAG_SD_CMD6) != 0U);
+}
+
static int mmc_send_cmd(unsigned int idx, unsigned int arg,
unsigned int r_type, unsigned int *r_data)
{
@@ -357,6 +363,33 @@
return 0;
}
+static int sd_switch(unsigned int mode, unsigned char group,
+ unsigned char func)
+{
+ unsigned int group_shift = (group - 1U) * 4U;
+ unsigned int group_mask = GENMASK(group_shift + 3U, group_shift);
+ unsigned int arg;
+ int ret;
+
+ ret = ops->prepare(0, (uintptr_t)&sd_switch_func_status,
+ sizeof(sd_switch_func_status));
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* MMC CMD6: SWITCH_FUNC */
+ arg = mode | SD_SWITCH_ALL_GROUPS_MASK;
+ arg &= ~group_mask;
+ arg |= func << group_shift;
+ ret = mmc_send_cmd(MMC_CMD(6), arg, MMC_RESPONSE_R1, NULL);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return ops->read(0, (uintptr_t)&sd_switch_func_status,
+ sizeof(sd_switch_func_status));
+}
+
static int sd_send_op_cond(void)
{
int n;
@@ -524,7 +557,39 @@
return ret;
}
- return mmc_fill_device_info();
+ ret = mmc_fill_device_info();
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (is_sd_cmd6_enabled() &&
+ (mmc_dev_info->mmc_dev_type == MMC_IS_SD_HC)) {
+ /* Try to switch to High Speed Mode */
+ ret = sd_switch(SD_SWITCH_FUNC_CHECK, 1U, 1U);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if ((sd_switch_func_status.support_g1 & BIT(9)) == 0U) {
+ /* High speed not supported, keep default speed */
+ return 0;
+ }
+
+ ret = sd_switch(SD_SWITCH_FUNC_SWITCH, 1U, 1U);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if ((sd_switch_func_status.sel_g2_g1 & 0x1U) == 0U) {
+ /* Cannot switch to high speed, keep default speed */
+ return 0;
+ }
+
+ mmc_dev_info->max_bus_freq = 50000000U;
+ ret = ops->set_ios(clk, bus_width);
+ }
+
+ return ret;
}
size_t mmc_read_blocks(int lba, uintptr_t buf, size_t size)
diff --git a/include/drivers/mmc.h b/include/drivers/mmc.h
index c154ea5..e973248 100644
--- a/include/drivers/mmc.h
+++ b/include/drivers/mmc.h
@@ -111,6 +111,7 @@
#define MMC_STATE_SLP 10
#define MMC_FLAG_CMD23 (U(1) << 0)
+#define MMC_FLAG_SD_CMD6 (U(1) << 1)
#define CMD8_CHECK_PATTERN U(0xAA)
#define VHS_2_7_3_6_V BIT(8)
@@ -118,6 +119,10 @@
#define SD_SCR_BUS_WIDTH_1 BIT(8)
#define SD_SCR_BUS_WIDTH_4 BIT(10)
+#define SD_SWITCH_FUNC_CHECK 0U
+#define SD_SWITCH_FUNC_SWITCH BIT(31)
+#define SD_SWITCH_ALL_GROUPS_MASK GENMASK(23, 0)
+
struct mmc_cmd {
unsigned int cmd_idx;
unsigned int cmd_arg;
@@ -217,6 +222,27 @@
unsigned int csd_structure: 2;
};
+struct sd_switch_status {
+ unsigned short max_current;
+ unsigned short support_g6;
+ unsigned short support_g5;
+ unsigned short support_g4;
+ unsigned short support_g3;
+ unsigned short support_g2;
+ unsigned short support_g1;
+ unsigned char sel_g6_g5;
+ unsigned char sel_g4_g3;
+ unsigned char sel_g2_g1;
+ unsigned char data_struct_ver;
+ unsigned short busy_g6;
+ unsigned short busy_g5;
+ unsigned short busy_g4;
+ unsigned short busy_g3;
+ unsigned short busy_g2;
+ unsigned short busy_g1;
+ unsigned short reserved[17];
+};
+
enum mmc_device_type {
MMC_IS_EMMC,
MMC_IS_SD,