Merge pull request #1796 from grandpaul/rpi3-sdhost-driver

RPi3 SDHost driver
diff --git a/drivers/rpi3/sdhost/rpi3_sdhost.c b/drivers/rpi3/sdhost/rpi3_sdhost.c
new file mode 100644
index 0000000..efcd6db
--- /dev/null
+++ b/drivers/rpi3/sdhost/rpi3_sdhost.c
@@ -0,0 +1,689 @@
+/*
+ * Copyright (c) 2019, Linaro Limited
+ * Copyright (c) 2019, Ying-Chun Liu (PaulLiu) <paul.liu@linaro.org>
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch.h>
+#include <arch_helpers.h>
+#include <assert.h>
+#include <common/debug.h>
+#include <lib/mmio.h>
+#include <drivers/delay_timer.h>
+#include <drivers/rpi3/sdhost/rpi3_sdhost.h>
+#include <drivers/mmc.h>
+#include <drivers/rpi3/gpio/rpi3_gpio.h>
+#include <errno.h>
+#include <string.h>
+
+static void rpi3_sdhost_initialize(void);
+static int rpi3_sdhost_send_cmd(struct mmc_cmd *cmd);
+static int rpi3_sdhost_set_ios(unsigned int clk, unsigned int width);
+static int rpi3_sdhost_prepare(int lba, uintptr_t buf, size_t size);
+static int rpi3_sdhost_read(int lba, uintptr_t buf, size_t size);
+static int rpi3_sdhost_write(int lba, uintptr_t buf, size_t size);
+
+static const struct mmc_ops rpi3_sdhost_ops = {
+	.init		= rpi3_sdhost_initialize,
+	.send_cmd	= rpi3_sdhost_send_cmd,
+	.set_ios	= rpi3_sdhost_set_ios,
+	.prepare	= rpi3_sdhost_prepare,
+	.read		= rpi3_sdhost_read,
+	.write		= rpi3_sdhost_write,
+};
+
+static struct rpi3_sdhost_params rpi3_sdhost_params;
+
+/**
+ * Wait for command being processed.
+ *
+ * This function waits the command being processed. It compares
+ * the ENABLE flag of the HC_COMMAND register. When ENABLE flag disappeared
+ * it means the command is processed by the SDHOST.
+ * The timeout is currently 1000*100 us = 100 ms.
+ *
+ * @return 0: command finished. 1: command timed out.
+ */
+static int rpi3_sdhost_waitcommand(void)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+
+	volatile int timeout = 1000;
+
+	while ((mmio_read_32(reg_base + HC_COMMAND) & HC_CMD_ENABLE)
+	       && (--timeout > 0)) {
+		udelay(100);
+	}
+
+	return ((timeout > 0) ? 0 : (-(ETIMEDOUT)));
+}
+
+/**
+ * Send the command and argument to the SDHOST
+ *
+ * This function will wait for the previous command finished. And then
+ * clear any error status of previous command. And then
+ * send out the command and args. The command will be turned on the ENABLE
+ * flag before sending out.
+ */
+static void send_command_raw(unsigned int cmd, unsigned int arg)
+{
+	unsigned int status;
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+
+	/* wait for previous command finish */
+	rpi3_sdhost_waitcommand();
+
+	/* clean error status */
+	status = mmio_read_32(reg_base + HC_HOSTSTATUS);
+	if (status & HC_HSTST_MASK_ERROR_ALL)
+		mmio_write_32(reg_base + HC_HOSTSTATUS, status);
+
+	/* recording the command */
+	rpi3_sdhost_params.current_cmd = cmd & HC_CMD_COMMAND_MASK;
+
+	/* send the argument and command */
+	mmio_write_32(reg_base + HC_ARGUMENT, arg);
+	mmio_write_32(reg_base + HC_COMMAND, cmd | HC_CMD_ENABLE);
+}
+
+/**
+ * Send the command and argument to the SDHOST, decorated with control
+ * flags.
+ *
+ * This function will use send_command_raw to send the commands to SDHOST.
+ * But before sending it will decorate the command with control flags specific
+ * to SDHOST.
+ */
+static void send_command_decorated(unsigned int cmd, unsigned int arg)
+{
+	unsigned int cmd_flags = 0;
+
+	switch (cmd & HC_CMD_COMMAND_MASK) {
+	case MMC_CMD(0):
+		cmd_flags |= HC_CMD_RESPONSE_NONE;
+		break;
+	case MMC_ACMD(51):
+		cmd_flags |= HC_CMD_READ;
+		break;
+	case MMC_CMD(8):
+	case MMC_CMD(11):
+	case MMC_CMD(17):
+	case MMC_CMD(18):
+		cmd_flags |= HC_CMD_READ;
+		break;
+	case MMC_CMD(20):
+	case MMC_CMD(24):
+	case MMC_CMD(25):
+		cmd_flags |= HC_CMD_WRITE;
+		break;
+	case MMC_CMD(12):
+		cmd_flags |= HC_CMD_BUSY;
+		break;
+	default:
+		break;
+	}
+	send_command_raw(cmd | cmd_flags, arg);
+}
+
+/**
+ * drains the FIFO on DATA port
+ *
+ * This function drains any data left in the DATA port.
+ */
+static void rpi3_drain_fifo(void)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+	volatile int timeout = 100000;
+
+	rpi3_sdhost_waitcommand();
+
+	while (mmio_read_32(reg_base + HC_HOSTSTATUS) & HC_HSTST_HAVEDATA) {
+		mmio_read_32(reg_base + HC_DATAPORT);
+		udelay(100);
+	}
+
+	while (1) {
+		uint32_t edm, fsm;
+
+		edm = mmio_read_32(reg_base + HC_DEBUG);
+		fsm = edm & HC_DBG_FSM_MASK;
+
+		if ((fsm == HC_DBG_FSM_IDENTMODE) ||
+		    (fsm == HC_DBG_FSM_DATAMODE))
+			break;
+
+		if ((fsm == HC_DBG_FSM_READWAIT) ||
+		    (fsm == HC_DBG_FSM_WRITESTART1) ||
+		    (fsm == HC_DBG_FSM_READDATA)) {
+			mmio_write_32(reg_base + HC_DEBUG,
+				      edm | HC_DBG_FORCE_DATA_MODE);
+			break;
+		}
+
+		if (--timeout <= 0) {
+			ERROR("rpi3_sdhost: %s cannot recover stat\n",
+			      __func__);
+			return;
+		}
+	}
+}
+
+/**
+ * Dump SDHOST registers
+ */
+static void rpi3_sdhost_print_regs(void)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+
+	INFO("rpi3_sdhost: HC_COMMAND:        0x%08x\n",
+	     mmio_read_32(reg_base + HC_COMMAND));
+	INFO("rpi3_sdhost: HC_ARGUMENT:       0x%08x\n",
+	     mmio_read_32(reg_base + HC_ARGUMENT));
+	INFO("rpi3_sdhost: HC_TIMEOUTCOUNTER: 0x%08x\n",
+	     mmio_read_32(reg_base + HC_TIMEOUTCOUNTER));
+	INFO("rpi3_sdhost: HC_CLOCKDIVISOR:   0x%08x\n",
+	     mmio_read_32(reg_base + HC_CLOCKDIVISOR));
+	INFO("rpi3_sdhost: HC_RESPONSE_0:     0x%08x\n",
+	     mmio_read_32(reg_base + HC_RESPONSE_0));
+	INFO("rpi3_sdhost: HC_RESPONSE_1:     0x%08x\n",
+	     mmio_read_32(reg_base + HC_RESPONSE_1));
+	INFO("rpi3_sdhost: HC_RESPONSE_2:     0x%08x\n",
+	     mmio_read_32(reg_base + HC_RESPONSE_2));
+	INFO("rpi3_sdhost: HC_RESPONSE_3:     0x%08x\n",
+	     mmio_read_32(reg_base + HC_RESPONSE_3));
+	INFO("rpi3_sdhost: HC_HOSTSTATUS:     0x%08x\n",
+	     mmio_read_32(reg_base + HC_HOSTSTATUS));
+	INFO("rpi3_sdhost: HC_POWER:          0x%08x\n",
+	     mmio_read_32(reg_base + HC_POWER));
+	INFO("rpi3_sdhost: HC_DEBUG:          0x%08x\n",
+	     mmio_read_32(reg_base + HC_DEBUG));
+	INFO("rpi3_sdhost: HC_HOSTCONFIG:     0x%08x\n",
+	     mmio_read_32(reg_base + HC_HOSTCONFIG));
+	INFO("rpi3_sdhost: HC_BLOCKSIZE:      0x%08x\n",
+	     mmio_read_32(reg_base + HC_BLOCKSIZE));
+	INFO("rpi3_sdhost: HC_BLOCKCOUNT:     0x%08x\n",
+	     mmio_read_32(reg_base + HC_BLOCKCOUNT));
+}
+
+/**
+ * Reset SDHOST
+ */
+static void rpi3_sdhost_reset(void)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+	unsigned int dbg;
+	uint32_t tmp1;
+
+	mmio_write_32(reg_base + HC_POWER, 0);
+	mmio_write_32(reg_base + HC_COMMAND, 0);
+	mmio_write_32(reg_base + HC_ARGUMENT, 0);
+
+	mmio_write_32(reg_base + HC_TIMEOUTCOUNTER, HC_TIMEOUT_DEFAULT);
+	mmio_write_32(reg_base + HC_CLOCKDIVISOR, 0);
+	mmio_write_32(reg_base + HC_HOSTSTATUS, HC_HSTST_RESET);
+	mmio_write_32(reg_base + HC_HOSTCONFIG, 0);
+	mmio_write_32(reg_base + HC_BLOCKSIZE, 0);
+	mmio_write_32(reg_base + HC_BLOCKCOUNT, 0);
+
+	dbg = mmio_read_32(reg_base + HC_DEBUG);
+	dbg &= ~((HC_DBG_FIFO_THRESH_MASK << HC_DBG_FIFO_THRESH_READ_SHIFT) |
+		 (HC_DBG_FIFO_THRESH_MASK << HC_DBG_FIFO_THRESH_WRITE_SHIFT));
+	dbg |= (HC_FIFO_THRESH_READ << HC_DBG_FIFO_THRESH_READ_SHIFT) |
+		(HC_FIFO_THRESH_WRITE << HC_DBG_FIFO_THRESH_WRITE_SHIFT);
+	mmio_write_32(reg_base + HC_DEBUG, dbg);
+	mdelay(250);
+	mmio_write_32(reg_base + HC_POWER, 1);
+	mdelay(250);
+	rpi3_sdhost_params.clk_rate = 0;
+
+	mmio_write_32(reg_base + HC_CLOCKDIVISOR, HC_CLOCKDIVISOR_MAXVAL);
+	tmp1 = mmio_read_32(reg_base + HC_HOSTCONFIG);
+	mmio_write_32(reg_base + HC_HOSTCONFIG, tmp1 | HC_HSTCF_INT_BUSY);
+}
+
+static void rpi3_sdhost_initialize(void)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+
+	assert((rpi3_sdhost_params.reg_base & MMC_BLOCK_MASK) == 0);
+
+	rpi3_sdhost_reset();
+
+	mmio_write_32(reg_base + HC_CLOCKDIVISOR, HC_CLOCKDIVISOR_PREFERVAL);
+	udelay(300);
+}
+
+static int rpi3_sdhost_send_cmd(struct mmc_cmd *cmd)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+	int err = 0;
+	uint32_t cmd_idx;
+	uint32_t cmd_arg;
+	uint32_t cmd_flags = 0;
+	uint32_t intmask;
+
+	/* Wait for the command done */
+	err = rpi3_sdhost_waitcommand();
+	if (err != 0) {
+		WARN("previous command not done yet\n");
+		return err;
+	}
+
+	cmd_idx = cmd->cmd_idx & HC_CMD_COMMAND_MASK;
+	if (cmd_idx == MMC_CMD(17))
+		cmd_idx = MMC_CMD(18);
+
+	cmd_arg = cmd->cmd_arg;
+	if (cmd_idx == MMC_ACMD(51)) {
+		/* if previous cmd send to SDHOST is not MMC_CMD(55).
+		 * It means this MMC_ACMD(51) is a resend.
+		 * And we must also resend MMC_CMD(55) in this case
+		 */
+		if (rpi3_sdhost_params.current_cmd != MMC_CMD(55)) {
+			send_command_decorated(
+				MMC_CMD(55),
+				rpi3_sdhost_params.sdcard_rca <<
+				RCA_SHIFT_OFFSET);
+			rpi3_sdhost_params.mmc_app_cmd = 1;
+			rpi3_sdhost_waitcommand();
+
+			/* Also we need to call prepare to clean the buffer */
+			rpi3_sdhost_prepare(0, (uintptr_t)NULL, 8);
+		}
+	}
+
+	/* We ignore MMC_CMD(12) sending from the TF-A's MMC driver
+	 * because we send MMC_CMD(12) by ourselves.
+	 */
+	if (cmd_idx == MMC_CMD(12))
+		return 0;
+
+	if ((cmd->resp_type & MMC_RSP_136) &&
+	    (cmd->resp_type & MMC_RSP_BUSY)) {
+		ERROR("rpi3_sdhost: unsupported response type!\n");
+		return -(EOPNOTSUPP);
+	}
+
+	if (cmd->resp_type & MMC_RSP_48 && cmd->resp_type != MMC_RESPONSE_R2) {
+		/* 48-bit command
+		 * We don't need to set any flags here because it is default.
+		 */
+	} else if (cmd->resp_type & MMC_RSP_136) {
+		/* 136-bit command */
+		cmd_flags |= HC_CMD_RESPONSE_LONG;
+	} else {
+		/* no respond command */
+		cmd_flags |= HC_CMD_RESPONSE_NONE;
+	}
+
+	rpi3_sdhost_params.cmdbusy = 0;
+	if (cmd->resp_type & MMC_RSP_BUSY) {
+		cmd_flags |= HC_CMD_BUSY;
+		rpi3_sdhost_params.cmdbusy = 1;
+	}
+
+	if (rpi3_sdhost_params.mmc_app_cmd) {
+		switch (cmd_idx) {
+		case MMC_ACMD(41):
+			if (cmd_arg == OCR_HCS)
+				cmd_arg |= OCR_3_3_3_4;
+			break;
+		default:
+			break;
+		}
+		rpi3_sdhost_params.mmc_app_cmd = 0;
+	}
+
+	if (cmd_idx == MMC_CMD(55))
+		rpi3_sdhost_params.mmc_app_cmd = 1;
+
+	send_command_decorated(cmd_idx | cmd_flags, cmd_arg);
+
+	intmask = mmio_read_32(reg_base + HC_HOSTSTATUS);
+	if (rpi3_sdhost_params.cmdbusy && (intmask & HC_HSTST_INT_BUSY)) {
+		mmio_write_32(reg_base + HC_HOSTSTATUS, HC_HSTST_INT_BUSY);
+		rpi3_sdhost_params.cmdbusy = 0;
+	}
+
+	if (!(cmd_flags & HC_CMD_RESPONSE_NONE)) {
+		err = rpi3_sdhost_waitcommand();
+		if (err != 0)
+			ERROR("rpi3_sdhost: cmd cannot be finished\n");
+	}
+
+	cmd->resp_data[0] = mmio_read_32(reg_base + HC_RESPONSE_0);
+	cmd->resp_data[1] = mmio_read_32(reg_base + HC_RESPONSE_1);
+	cmd->resp_data[2] = mmio_read_32(reg_base + HC_RESPONSE_2);
+	cmd->resp_data[3] = mmio_read_32(reg_base + HC_RESPONSE_3);
+
+	if (mmio_read_32(reg_base + HC_COMMAND) & HC_CMD_FAILED) {
+		uint32_t sdhsts = mmio_read_32(reg_base + HC_HOSTSTATUS);
+
+		mmio_write_32(reg_base + HC_HOSTSTATUS,
+			      HC_HSTST_MASK_ERROR_ALL);
+
+		if (!(sdhsts & HC_HSTST_ERROR_CRC7)
+		    || (cmd_idx != MMC_ACMD(51))) {
+			if (sdhsts & HC_HSTST_TIMEOUT_CMD) {
+				ERROR("rpi3_sdhost: timeout status 0x%x\n",
+				      sdhsts);
+				err = -(ETIMEDOUT);
+			} else {
+				ERROR("rpi3_sdhost: unknown err, cmd = 0x%x\n",
+				      mmio_read_32(reg_base + HC_COMMAND));
+				ERROR("rpi3_sdhost status: 0x%x\n", sdhsts);
+				err = -(EILSEQ);
+			}
+		}
+	}
+
+	if ((!err) && (cmd_idx == MMC_CMD(3))) {
+		/* we keep the RCA in case to send MMC_CMD(55) ourselves */
+		rpi3_sdhost_params.sdcard_rca = (cmd->resp_data[0]
+						 & 0xFFFF0000U) >> 16;
+	}
+
+	return err;
+}
+
+static int rpi3_sdhost_set_clock(unsigned int clk)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+	uint32_t max_clk = 250000000;
+	uint32_t div;
+
+	if (clk < 100000) {
+		mmio_write_32(reg_base + HC_CLOCKDIVISOR,
+			      HC_CLOCKDIVISOR_MAXVAL);
+		return 0;
+	}
+
+	div = max_clk / clk;
+	if (div < 2)
+		div = 2;
+
+	if ((max_clk / div) > clk)
+		div++;
+
+	div -= 2;
+	if (div > HC_CLOCKDIVISOR_MAXVAL)
+		div = HC_CLOCKDIVISOR_MAXVAL;
+
+	rpi3_sdhost_params.clk_rate = max_clk / (div + 2);
+	rpi3_sdhost_params.ns_per_fifo_word = (1000000000 /
+					       rpi3_sdhost_params.clk_rate)
+		* 8;
+
+	mmio_write_32(reg_base + HC_CLOCKDIVISOR, div);
+	return 0;
+}
+
+static int rpi3_sdhost_set_ios(unsigned int clk, unsigned int width)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+	uint32_t tmp1;
+
+	rpi3_sdhost_set_clock(clk);
+	VERBOSE("rpi3_sdhost: Changing clock to %dHz for data mode\n", clk);
+
+	if (width != MMC_BUS_WIDTH_4 && width != MMC_BUS_WIDTH_1) {
+		ERROR("rpi3_sdhost: width %d not supported\n", width);
+		return -(EOPNOTSUPP);
+	}
+	rpi3_sdhost_params.bus_width = width;
+
+	tmp1 = mmio_read_32(reg_base + HC_HOSTCONFIG);
+	tmp1 &= ~(HC_HSTCF_EXTBUS_4BIT);
+	if (rpi3_sdhost_params.bus_width == MMC_BUS_WIDTH_4)
+		tmp1 |= HC_HSTCF_EXTBUS_4BIT;
+
+	mmio_write_32(reg_base + HC_HOSTCONFIG, tmp1);
+	tmp1 = mmio_read_32(reg_base + HC_HOSTCONFIG);
+	mmio_write_32(reg_base + HC_HOSTCONFIG, tmp1 |
+		      HC_HSTCF_SLOW_CARD | HC_HSTCF_INTBUS_WIDE);
+
+	return 0;
+}
+
+static int rpi3_sdhost_prepare(int lba, uintptr_t buf, size_t size)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+	size_t blocks;
+	size_t blocksize;
+
+	if (size < 512) {
+		blocksize = size;
+		blocks = 1;
+	} else {
+		blocksize = 512;
+		blocks = size / blocksize;
+		if (size % blocksize != 0)
+			blocks++;
+	}
+
+	rpi3_drain_fifo();
+
+	mmio_write_32(reg_base + HC_BLOCKSIZE, blocksize);
+	mmio_write_32(reg_base + HC_BLOCKCOUNT, blocks);
+	udelay(100);
+	return 0;
+}
+
+static int rpi3_sdhost_read(int lba, uintptr_t buf, size_t size)
+{
+	int err = 0;
+	uint32_t *buf1 = ((uint32_t *) buf);
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+	int timeout = 100000;
+	int remaining_words = 0;
+
+	for (int i = 0; i < size / 4; i++) {
+		volatile int t = timeout;
+		uint32_t hsts_err;
+
+		while ((mmio_read_32(reg_base + HC_HOSTSTATUS)
+			& HC_HSTST_HAVEDATA) == 0) {
+			if (t == 0) {
+				ERROR("rpi3_sdhost: fifo timeout after %dus\n",
+				      timeout);
+				err = -(ETIMEDOUT);
+				break;
+			}
+			t--;
+			udelay(10);
+		}
+		if (t == 0)
+			break;
+
+		uint32_t data = mmio_read_32(reg_base + HC_DATAPORT);
+
+		hsts_err = mmio_read_32(reg_base + HC_HOSTSTATUS)
+			& HC_HSTST_MASK_ERROR_ALL;
+		if (hsts_err) {
+			ERROR("rpi3_sdhost: transfer FIFO word %d: 0x%x\n",
+			      i,
+			      mmio_read_32(reg_base + HC_HOSTSTATUS));
+			rpi3_sdhost_print_regs();
+
+			err = -(EILSEQ);
+
+			/* clean the error status */
+			mmio_write_32(reg_base + HC_HOSTSTATUS, hsts_err);
+		}
+
+		if (buf1)
+			buf1[i] = data;
+
+		/* speeding up if the remaining words are still a lot */
+		remaining_words = (mmio_read_32(reg_base + HC_DEBUG) >> 4)
+			& HC_DBG_FIFO_THRESH_MASK;
+		if (remaining_words >= 7)
+			continue;
+
+		/* delay. slowing down the read process */
+		udelay(100);
+	}
+
+	/* We decide to stop by ourselves.
+	 * It is because MMC_CMD(18) -> MMC_CMD(13) -> MMC_CMD(12)
+	 * doesn't work for RPi3 SDHost.
+	 */
+	if (rpi3_sdhost_params.current_cmd == MMC_CMD(18))
+		send_command_decorated(MMC_CMD(12), 0);
+
+	if (err == -(EILSEQ)) {
+		const int max_retries = 20;
+		int r;
+
+		rpi3_sdhost_params.crc_err_retries++;
+		if (rpi3_sdhost_params.crc_err_retries < max_retries) {
+			/* retries if there's an CRC error */
+			r = rpi3_sdhost_prepare(lba, buf, size);
+			send_command_decorated(MMC_CMD(18), lba);
+			r = rpi3_sdhost_read(lba, buf, size);
+			if (r == 0)
+				err = 0;
+		}
+	}
+
+	return err;
+}
+
+static int rpi3_sdhost_write(int lba, uintptr_t buf, size_t size)
+{
+	uint32_t *buf1 = ((uint32_t *) buf);
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+	int err = 0;
+	int remaining_words = 0;
+
+	for (int i = 0; i < size / 4; i++) {
+		uint32_t hsts_err;
+		uint32_t data = buf1[i];
+		uint32_t dbg;
+		uint32_t fsm_state;
+
+		mmio_write_32(reg_base + HC_DATAPORT, data);
+
+		dbg = mmio_read_32(reg_base + HC_DEBUG);
+		fsm_state = dbg & HC_DBG_FSM_MASK;
+		if (fsm_state != HC_DBG_FSM_WRITEDATA
+		    && fsm_state != HC_DBG_FSM_WRITESTART1
+		    && fsm_state != HC_DBG_FSM_WRITESTART2
+		    && fsm_state != HC_DBG_FSM_WRITECRC
+		    && fsm_state != HC_DBG_FSM_WRITEWAIT1
+		    && fsm_state != HC_DBG_FSM_WRITEWAIT2) {
+			hsts_err = mmio_read_32(reg_base + HC_HOSTSTATUS)
+				& HC_HSTST_MASK_ERROR_ALL;
+			if (hsts_err)
+				err = -(EILSEQ);
+		}
+
+		/* speeding up if the remaining words are not many */
+		remaining_words = (mmio_read_32(reg_base + HC_DEBUG) >> 4)
+			& HC_DBG_FIFO_THRESH_MASK;
+		if (remaining_words <= 4)
+			continue;
+
+		udelay(100);
+	}
+
+	/* We decide to stop by ourselves.
+	 * It is because MMC_CMD(25) -> MMC_CMD(13) -> MMC_CMD(12)
+	 * doesn't work for RPi3 SDHost.
+	 */
+	if (rpi3_sdhost_params.current_cmd == MMC_CMD(25))
+		send_command_decorated(MMC_CMD(12), 0);
+
+	return err;
+}
+
+void rpi3_sdhost_init(struct rpi3_sdhost_params *params,
+		    struct mmc_device_info *mmc_dev_info)
+{
+	assert((params != 0) &&
+	       ((params->reg_base & MMC_BLOCK_MASK) == 0));
+
+	memcpy(&rpi3_sdhost_params, params, sizeof(struct rpi3_sdhost_params));
+
+	/* backup GPIO 48 to 53 configurations */
+	for (int i = 48; i <= 53; i++) {
+		rpi3_sdhost_params.gpio48_pinselect[i - 48]
+			= rpi3_gpio_get_select(i);
+		VERBOSE("rpi3_sdhost: Original GPIO state %d: %d\n",
+			i,
+			rpi3_sdhost_params.gpio48_pinselect[i - 48]);
+	}
+
+	/* setting pull resistors for 48 to 53.
+	 * GPIO 48 (SD_CLK) to GPIO_PULL_UP
+	 * GPIO 49 (SD_CMD) to GPIO_PULL_NONE
+	 * GPIO 50 (SD_D0)  to GPIO_PULL_NONE
+	 * GPIO 51 (SD_D1)  to GPIO_PULL_NONE
+	 * GPIO 52 (SD_D2)  to GPIO_PULL_NONE
+	 * GPIO 53 (SD_D3)  to GPIO_PULL_NONE
+	 */
+	gpio_set_pull(48, GPIO_PULL_UP);
+	for (int i = 49; i <= 53; i++)
+		gpio_set_pull(i, GPIO_PULL_NONE);
+
+	/* Set pin 48-53 to alt-0. It means route SDHOST to card slot */
+	for (int i = 48; i <= 53; i++)
+		rpi3_gpio_set_select(i, RPI3_GPIO_FUNC_ALT0);
+
+	mmc_init(&rpi3_sdhost_ops, params->clk_rate, params->bus_width,
+		 params->flags, mmc_dev_info);
+}
+
+void rpi3_sdhost_stop(void)
+{
+	uintptr_t reg_base = rpi3_sdhost_params.reg_base;
+
+	VERBOSE("rpi3_sdhost: Shutting down: drain FIFO out\n");
+	rpi3_drain_fifo();
+
+	VERBOSE("rpi3_sdhost: Shutting down: slowing down the clock\n");
+	mmio_write_32(reg_base+HC_CLOCKDIVISOR, HC_CLOCKDIVISOR_SLOWVAL);
+	udelay(500);
+
+	VERBOSE("rpi3_sdhost: Shutting down: put SDHost into idle state\n");
+	send_command_decorated(MMC_CMD(0), 0);
+	udelay(500);
+
+	mmio_write_32(reg_base + HC_COMMAND, 0);
+	mmio_write_32(reg_base + HC_ARGUMENT, 0);
+	mmio_write_32(reg_base + HC_TIMEOUTCOUNTER, HC_TIMEOUT_IDLE);
+	mmio_write_32(reg_base + HC_CLOCKDIVISOR, HC_CLOCKDIVISOR_STOPVAL);
+
+	udelay(100);
+
+	mmio_write_32(reg_base + HC_POWER, 0);
+	mmio_write_32(reg_base + HC_HOSTCONFIG, 0);
+	mmio_write_32(reg_base + HC_BLOCKSIZE, 0x400);
+	mmio_write_32(reg_base + HC_BLOCKCOUNT, 0);
+	mmio_write_32(reg_base + HC_HOSTSTATUS, 0x7f8);
+
+	mmio_write_32(reg_base + HC_COMMAND, 0);
+	mmio_write_32(reg_base + HC_ARGUMENT, 0);
+
+	udelay(100);
+
+	/* Restore the pinmux to original state */
+	for (int i = 48; i <= 53; i++) {
+		rpi3_gpio_set_select(i,
+				     rpi3_sdhost_params.gpio48_pinselect[i-48]);
+	}
+
+	/* Must reset the pull resistors for u-boot to work.
+	 * GPIO 48 (SD_CLK) to GPIO_PULL_NONE
+	 * GPIO 49 (SD_CMD) to GPIO_PULL_UP
+	 * GPIO 50 (SD_D0)  to GPIO_PULL_UP
+	 * GPIO 51 (SD_D1)  to GPIO_PULL_UP
+	 * GPIO 52 (SD_D2)  to GPIO_PULL_UP
+	 * GPIO 53 (SD_D3)  to GPIO_PULL_UP
+	 */
+	gpio_set_pull(48, GPIO_PULL_NONE);
+	for (int i = 49; i <= 53; i++)
+		gpio_set_pull(i, GPIO_PULL_UP);
+}
diff --git a/include/drivers/rpi3/sdhost/rpi3_sdhost.h b/include/drivers/rpi3/sdhost/rpi3_sdhost.h
new file mode 100644
index 0000000..bc906e3
--- /dev/null
+++ b/include/drivers/rpi3/sdhost/rpi3_sdhost.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2019, Linaro Limited
+ * Copyright (c) 2019, Ying-Chun Liu (PaulLiu) <paul.liu@linaro.org>
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef RPI3_SDHOST_H
+#define	RPI3_SDHOST_H
+
+#include <drivers/mmc.h>
+#include <stdint.h>
+#include <platform_def.h>
+
+struct rpi3_sdhost_params {
+	uintptr_t	reg_base;
+	uint32_t	clk_rate;
+	uint32_t	bus_width;
+	uint32_t        flags;
+	uint32_t	current_cmd;
+	uint8_t		cmdbusy;
+	uint8_t		mmc_app_cmd;
+	uint32_t	ns_per_fifo_word;
+	uint32_t	crc_err_retries;
+
+	uint32_t	sdcard_rca;
+	uint32_t	gpio48_pinselect[6];
+};
+
+void rpi3_sdhost_init(struct rpi3_sdhost_params *params,
+		      struct mmc_device_info *mmc_dev_info);
+void rpi3_sdhost_stop(void);
+
+/* Registers */
+#define HC_COMMAND			0x00        /* Command and flags */
+#define HC_ARGUMENT			0x04
+#define HC_TIMEOUTCOUNTER		0x08
+#define HC_CLOCKDIVISOR			0x0c
+#define HC_RESPONSE_0			0x10
+#define HC_RESPONSE_1			0x14
+#define HC_RESPONSE_2			0x18
+#define HC_RESPONSE_3			0x1c
+#define HC_HOSTSTATUS			0x20
+#define HC_POWER			0x30
+#define HC_DEBUG			0x34
+#define HC_HOSTCONFIG			0x38
+#define HC_BLOCKSIZE			0x3c
+#define HC_DATAPORT			0x40
+#define HC_BLOCKCOUNT			0x50
+
+/* Flags for HC_COMMAND register */
+#define HC_CMD_ENABLE			0x8000
+#define HC_CMD_FAILED			0x4000
+#define HC_CMD_BUSY			0x0800
+#define HC_CMD_RESPONSE_NONE		0x0400
+#define HC_CMD_RESPONSE_LONG		0x0200
+#define HC_CMD_WRITE			0x0080
+#define HC_CMD_READ			0x0040
+#define HC_CMD_COMMAND_MASK		0x003f
+
+#define HC_CLOCKDIVISOR_MAXVAL		0x07ff
+#define HC_CLOCKDIVISOR_PREFERVAL	0x027b
+#define HC_CLOCKDIVISOR_SLOWVAL		0x0148
+#define HC_CLOCKDIVISOR_STOPVAL		0x01fb
+
+/* Flags for HC_HOSTSTATUS register */
+#define HC_HSTST_HAVEDATA		0x0001
+#define HC_HSTST_ERROR_FIFO		0x0008
+#define HC_HSTST_ERROR_CRC7		0x0010
+#define HC_HSTST_ERROR_CRC16		0x0020
+#define HC_HSTST_TIMEOUT_CMD		0x0040
+#define HC_HSTST_TIMEOUT_DATA		0x0080
+#define HC_HSTST_INT_BLOCK		0x0200
+#define HC_HSTST_INT_BUSY		0x0400
+
+#define HC_HSTST_RESET			0xffff
+
+#define HC_HSTST_MASK_ERROR_DATA	(HC_HSTST_ERROR_FIFO | \
+					 HC_HSTST_ERROR_CRC7 | \
+					 HC_HSTST_ERROR_CRC16 | \
+					 HC_HSTST_TIMEOUT_DATA)
+
+#define HC_HSTST_MASK_ERROR_ALL		(HC_HSTST_MASK_ERROR_DATA | \
+					 HC_HSTST_TIMEOUT_CMD)
+
+/* Flags for HC_HOSTCONFIG register */
+#define HC_HSTCF_INTBUS_WIDE		0x0002
+#define HC_HSTCF_EXTBUS_4BIT		0x0004
+#define HC_HSTCF_SLOW_CARD		0x0008
+#define HC_HSTCF_INT_DATA		0x0010
+#define HC_HSTCF_INT_BLOCK		0x0100
+#define HC_HSTCF_INT_BUSY		0x0400
+
+/* Flags for HC_DEBUG register */
+#define HC_DBG_FIFO_THRESH_WRITE_SHIFT	9
+#define HC_DBG_FIFO_THRESH_READ_SHIFT	14
+#define HC_DBG_FIFO_THRESH_MASK		0x001f
+#define HC_DBG_FSM_MASK			0xf
+#define HC_DBG_FSM_IDENTMODE		0x0
+#define HC_DBG_FSM_DATAMODE		0x1
+#define HC_DBG_FSM_READDATA		0x2
+#define HC_DBG_FSM_WRITEDATA		0x3
+#define HC_DBG_FSM_READWAIT		0x4
+#define HC_DBG_FSM_READCRC		0x5
+#define HC_DBG_FSM_WRITECRC		0x6
+#define HC_DBG_FSM_WRITEWAIT1		0x7
+#define HC_DBG_FSM_POWERDOWN		0x8
+#define HC_DBG_FSM_POWERUP		0x9
+#define HC_DBG_FSM_WRITESTART1		0xa
+#define HC_DBG_FSM_WRITESTART2		0xb
+#define HC_DBG_FSM_GENPULSES		0xc
+#define HC_DBG_FSM_WRITEWAIT2		0xd
+#define HC_DBG_FSM_STARTPOWDOWN		0xf
+#define HC_DBG_FORCE_DATA_MODE		0x40000
+
+/* Settings */
+#define HC_FIFO_SIZE			16
+#define HC_FIFO_THRESH_READ		4
+#define HC_FIFO_THRESH_WRITE		4
+
+#define HC_TIMEOUT_DEFAULT		0x00f00000
+#define HC_TIMEOUT_IDLE			0x00a00000
+
+#endif  /* RPI3_SDHOST_H */
diff --git a/maintainers.rst b/maintainers.rst
index f53dda5..987b962 100644
--- a/maintainers.rst
+++ b/maintainers.rst
@@ -169,8 +169,12 @@
 ----------------------------
 :M: Antonio Niño Díaz <antonio.ninodiaz@arm.com>
 :G: `antonio-nino-diaz-arm`_
+:M: Ying-Chun Liu (PaulLiu) <paul.liu@linaro.org>
+:G: `grandpaul`_
 :F: docs/plat/rpi3.rst
 :F: plat/rpi3/
+:F: drivers/rpi3/
+:F: include/drivers/rpi3/
 
 Renesas rcar-gen3 platform port
 -------------------------------
diff --git a/plat/rpi3/include/platform_def.h b/plat/rpi3/include/platform_def.h
index 69ebdb3..4d90222 100644
--- a/plat/rpi3/include/platform_def.h
+++ b/plat/rpi3/include/platform_def.h
@@ -244,6 +244,8 @@
 #define MAX_IO_DEVICES			U(3)
 #define MAX_IO_HANDLES			U(4)
 
+#define MAX_IO_BLOCK_DEVICES		U(1)
+
 /*
  * Serial-related constants.
  */
diff --git a/plat/rpi3/platform.mk b/plat/rpi3/platform.mk
index ded92bd..ca638d5 100644
--- a/plat/rpi3/platform.mk
+++ b/plat/rpi3/platform.mk
@@ -31,6 +31,9 @@
 				drivers/delay_timer/delay_timer.c	\
 				drivers/delay_timer/generic_delay_timer.c \
 				drivers/rpi3/gpio/rpi3_gpio.c		\
+				drivers/io/io_block.c			\
+				drivers/mmc/mmc.c			\
+				drivers/rpi3/sdhost/rpi3_sdhost.c	\
 				plat/common/aarch64/platform_mp_stack.S	\
 				plat/rpi3/aarch64/plat_helpers.S	\
 				plat/rpi3/aarch64/rpi3_bl2_mem_params_desc.c \
diff --git a/plat/rpi3/rpi3_bl2_setup.c b/plat/rpi3/rpi3_bl2_setup.c
index 09f0562..3d1f8f9 100644
--- a/plat/rpi3/rpi3_bl2_setup.c
+++ b/plat/rpi3/rpi3_bl2_setup.c
@@ -17,6 +17,7 @@
 #include <lib/xlat_tables/xlat_tables_defs.h>
 #include <drivers/generic_delay_timer.h>
 #include <drivers/rpi3/gpio/rpi3_gpio.h>
+#include <drivers/rpi3/sdhost/rpi3_sdhost.h>
 
 #include "rpi3_private.h"
 
@@ -34,6 +35,21 @@
 	rpi3_gpio_init(&params);
 }
 
+/* Data structure which holds the MMC info */
+static struct mmc_device_info mmc_info;
+
+static void rpi3_sdhost_setup(void)
+{
+	struct rpi3_sdhost_params params;
+
+	memset(&params, 0, sizeof(struct rpi3_sdhost_params));
+	params.reg_base = RPI3_SDHOST_BASE;
+	params.bus_width = MMC_BUS_WIDTH_4;
+	params.clk_rate = 392464;
+	mmc_info.mmc_dev_type = MMC_IS_SD_HC;
+	rpi3_sdhost_init(&params, &mmc_info);
+}
+
 /*******************************************************************************
  * BL1 has passed the extents of the trusted SRAM that should be visible to BL2
  * in x0. This memory layout is sitting at the base of the free trusted SRAM.
@@ -57,6 +73,9 @@
 	/* Setup the BL2 memory layout */
 	bl2_tzram_layout = *mem_layout;
 
+	/* Setup SDHost driver */
+	rpi3_sdhost_setup();
+
 	plat_rpi3_io_setup();
 }
 
@@ -122,6 +141,9 @@
 		/* BL33 expects to receive the primary CPU MPID (through r0) */
 		bl_mem_params->ep_info.args.arg0 = 0xffff & read_mpidr();
 		bl_mem_params->ep_info.spsr = rpi3_get_spsr_for_bl33_entry();
+
+		/* Shutting down the SDHost driver to let BL33 drives SDHost.*/
+		rpi3_sdhost_stop();
 		break;
 
 	default:
diff --git a/plat/rpi3/rpi3_hw.h b/plat/rpi3/rpi3_hw.h
index 61d1837..1a86835 100644
--- a/plat/rpi3/rpi3_hw.h
+++ b/plat/rpi3/rpi3_hw.h
@@ -90,6 +90,12 @@
 #define RPI3_GPIO_BASE			(RPI3_IO_BASE + RPI3_IO_GPIO_OFFSET)
 
 /*
+ * SDHost controller
+ */
+#define RPI3_IO_SDHOST_OFFSET           ULL(0x00202000)
+#define RPI3_SDHOST_BASE                (RPI3_IO_BASE + RPI3_IO_SDHOST_OFFSET)
+
+/*
  * Local interrupt controller
  */
 #define RPI3_INTC_BASE_ADDRESS			ULL(0x40000000)