feat(drivers/mmc): boot partition read support

Added a public function to read blocks from a current boot partition.
switch between partitions has to respect eMMC partition switch timing.

Signed-off-by: Vyacheslav Yurkov <uvv.mail@gmail.com>
Change-Id: I55b0c910314253e5647486609583fd290dadd30a
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index e288d47..c327e71 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -105,6 +105,36 @@
 	return MMC_GET_STATE(resp_data[0]);
 }
 
+static int mmc_send_part_switch_cmd(unsigned int part_config)
+{
+	int ret;
+	unsigned int part_time = 0;
+
+	ret = mmc_send_cmd(MMC_CMD(6),
+			   EXTCSD_WRITE_BYTES |
+			   EXTCSD_CMD(CMD_EXTCSD_PARTITION_CONFIG) |
+			   EXTCSD_VALUE(part_config) |
+			   EXTCSD_CMD_SET_NORMAL,
+			   MMC_RESPONSE_R1B, NULL);
+	if (ret != 0) {
+		return ret;
+	}
+
+	/* Partition switch timing is in 10ms units */
+	part_time = mmc_ext_csd[CMD_EXTCSD_PART_SWITCH_TIME] * 10;
+
+	mdelay(part_time);
+
+	do {
+		ret = mmc_device_state();
+		if (ret < 0) {
+			return ret;
+		}
+	} while (ret == MMC_STATE_PRG);
+
+	return 0;
+}
+
 static int mmc_set_ext_csd(unsigned int ext_cmd, unsigned int value)
 {
 	int ret;
@@ -668,7 +698,7 @@
 {
 	mmc_set_ext_csd(CMD_EXTCSD_PARTITION_CONFIG,
 			PART_CFG_BOOT_PARTITION1_ENABLE |
-			PART_CFG_PARTITION1_ACCESS);
+			PART_CFG_BOOT_PARTITION1_ACCESS);
 }
 
 static inline void mmc_rpmb_disable(void)
@@ -710,6 +740,50 @@
 	return size_erased;
 }
 
+static int mmc_part_switch(unsigned int part_type)
+{
+	uint8_t part_config = mmc_ext_csd[CMD_EXTCSD_PARTITION_CONFIG];
+
+	part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK;
+	part_config |= part_type;
+
+	return mmc_send_part_switch_cmd(part_config);
+}
+
+static unsigned char mmc_current_boot_part(void)
+{
+	return PART_CFG_CURRENT_BOOT_PARTITION(mmc_ext_csd[CMD_EXTCSD_PARTITION_CONFIG]);
+}
+
+size_t mmc_boot_part_read_blocks(int lba, uintptr_t buf, size_t size)
+{
+	size_t size_read;
+	int ret;
+	unsigned char current_boot_part = mmc_current_boot_part();
+
+	if (current_boot_part != 1U &&
+	    current_boot_part != 2U) {
+		ERROR("Got unexpected value for active boot partition, %u\n", current_boot_part);
+		return 0;
+	}
+
+	ret = mmc_part_switch(current_boot_part);
+	if (ret < 0) {
+		ERROR("Failed to switch to boot partition, %d\n", ret);
+		return 0;
+	}
+
+	size_read = mmc_read_blocks(lba, buf, size);
+
+	ret = mmc_part_switch(0);
+	if (ret < 0) {
+		ERROR("Failed to switch back to user partition, %d\n", ret);
+		return 0;
+	}
+
+	return size_read;
+}
+
 int mmc_init(const struct mmc_ops *ops_ptr, unsigned int clk,
 	     unsigned int width, unsigned int flags,
 	     struct mmc_device_info *device_info)
diff --git a/include/drivers/mmc.h b/include/drivers/mmc.h
index 7611f01..834a80f 100644
--- a/include/drivers/mmc.h
+++ b/include/drivers/mmc.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2021, ARM Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -60,10 +60,16 @@
 #define CMD_EXTCSD_PARTITION_CONFIG	179
 #define CMD_EXTCSD_BUS_WIDTH		183
 #define CMD_EXTCSD_HS_TIMING		185
+#define CMD_EXTCSD_PART_SWITCH_TIME	199
 #define CMD_EXTCSD_SEC_CNT		212
 
+#define EXT_CSD_PART_CONFIG_ACC_MASK	GENMASK(2, 0)
 #define PART_CFG_BOOT_PARTITION1_ENABLE	(U(1) << 3)
-#define PART_CFG_PARTITION1_ACCESS	(U(1) << 0)
+#define PART_CFG_BOOT_PARTITION1_ACCESS (U(1) << 0)
+#define PART_CFG_BOOT_PART_EN_MASK		GENMASK(5, 3)
+#define PART_CFG_BOOT_PART_EN_SHIFT		3
+#define PART_CFG_CURRENT_BOOT_PARTITION(x)	(((x) & PART_CFG_BOOT_PART_EN_MASK) >> \
+	PART_CFG_BOOT_PART_EN_SHIFT)
 
 /* Values in EXT CSD register */
 #define MMC_BUS_WIDTH_1			U(0)
@@ -230,6 +236,7 @@
 size_t mmc_rpmb_read_blocks(int lba, uintptr_t buf, size_t size);
 size_t mmc_rpmb_write_blocks(int lba, const uintptr_t buf, size_t size);
 size_t mmc_rpmb_erase_blocks(int lba, size_t size);
+size_t mmc_boot_part_read_blocks(int lba, uintptr_t buf, size_t size);
 int mmc_init(const struct mmc_ops *ops_ptr, unsigned int clk,
 	     unsigned int width, unsigned int flags,
 	     struct mmc_device_info *device_info);