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)