imx8 sdhci support. Onhold for now until we have a working ARM-based SDHCI driver.

Change-Id: I4c002660652ebc33cc9cb98fd26e8167273d92d6
diff --git a/system/dev/block/imx-sdhci/imx-sdhci.c b/system/dev/block/imx-sdhci/imx-sdhci.c
new file mode 100644
index 0000000..c73f5ab
--- /dev/null
+++ b/system/dev/block/imx-sdhci/imx-sdhci.c
@@ -0,0 +1,773 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Standard Includes
+#include <assert.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <threads.h>
+
+// DDK Includes
+#include <ddk/binding.h>
+#include <ddk/device.h>
+#include <ddk/debug.h>
+#include <ddk/io-buffer.h>
+#include <ddk/phys-iter.h>
+#include <ddk/protocol/platform-defs.h>
+#include <ddk/protocol/platform-device.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/sdmmc.h>
+#include <ddk/protocol/sdhci.h>
+#include <hw/sdmmc.h>
+#include <zircon/types.h>
+
+
+// Zircon Includes
+#include <fdio/watcher.h>
+#include <zircon/threads.h>
+#include <zircon/assert.h>
+#include <sync/completion.h>
+#include <pretty/hexdump.h>
+
+#include "imx-sdhci.h"
+
+#define SDHCI_ERROR(fmt, ...)       zxlogf(ERROR, "[%s %d]" fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define SDHCI_INFO(fmt, ...)        zxlogf(ERROR, "[%s %d]" fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define SDHCI_TRACE(fmt, ...)       zxlogf(ERROR, "[%s %d]" fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define SDHCI_FUNC_ENTRY_LOG        zxlogf(ERROR, "[%s %d]\n", __func__, __LINE__)
+
+#define SD_FREQ_SETUP_HZ  400000
+
+#define MAX_TUNING_COUNT 40
+
+#define PAGE_MASK   (PAGE_SIZE - 1ull)
+
+#define HI32(val)   (((val) >> 32) & 0xffffffff)
+#define LO32(val)   ((val) & 0xffffffff)
+#define SDMMC_COMMAND(c) ((c) << 24)
+
+typedef struct sdhci_adma64_desc {
+    union {
+        struct {
+            uint8_t valid : 1;
+            uint8_t end   : 1;
+            uint8_t intr  : 1;
+            uint8_t rsvd0 : 1;
+            uint8_t act1  : 1;
+            uint8_t act2  : 1;
+            uint8_t rsvd1 : 2;
+            uint8_t rsvd2;
+        } __PACKED;
+        uint16_t attr;
+    } __PACKED;
+    uint16_t length;
+    uint64_t address;
+} __PACKED sdhci_adma64_desc_t;
+
+static_assert(sizeof(sdhci_adma64_desc_t) == 12, "unexpected ADMA2 descriptor size");
+
+// 64k max per descriptor
+#define ADMA2_DESC_MAX_LENGTH   0x10000 // 64k
+// for 2M max transfer size for fully discontiguous
+// also see SDMMC_PAGES_COUNT in ddk/protocol/sdmmc.h
+#define DMA_DESC_COUNT          512
+
+typedef struct imx_sdhci_device {
+    platform_device_protocol_t  pdev;
+    platform_bus_protocol_t     pbus;
+    zx_device_t*                zxdev;
+    io_buffer_t                 mmios;
+    zx_handle_t                 irq_handle;
+
+    volatile imx_sdhci_regs_t*  regs;
+    uint64_t                    regs_size;
+    zx_handle_t                 regs_handle;
+    zx_handle_t                 bti_handle;
+
+    // DMA descriptors
+    io_buffer_t iobuf;
+    sdhci_adma64_desc_t* descs;
+
+    // Held when a command or action is in progress.
+    mtx_t mtx;
+
+    // Current command request
+    sdmmc_req_t* cmd_req;
+    // Current data line request
+    sdmmc_req_t* data_req;
+    // Current block id to transfer (PIO)
+    uint16_t data_blockid;
+    uint16_t reserved;
+    // Set to true if the data stage completed before the command stage
+    bool data_done;
+    // used to signal request complete
+    completion_t req_completion;
+
+    // Controller info
+    sdmmc_host_info_t info;
+
+    // Controller specific quirks
+    uint64_t quirks;
+
+    // Base clock rate
+    uint32_t base_clock;
+
+} imx_sdhci_device_t;
+
+static const uint32_t error_interrupts = (
+    IMX_SDHC_INT_STAT_DMAE  |
+    IMX_SDHC_INT_STAT_TNE   |
+    IMX_SDHC_INT_STAT_AC12E |
+    IMX_SDHC_INT_STAT_DEBE  |
+    IMX_SDHC_INT_STAT_DCE   |
+    IMX_SDHC_INT_STAT_DTOE  |
+    IMX_SDHC_INT_STAT_CIE   |
+    IMX_SDHC_INT_STAT_CEBE  |
+    IMX_SDHC_INT_STAT_CCE   |
+    IMX_SDHC_INT_STAT_CTOE
+);
+
+static const uint32_t normal_interrupts = (
+    IMX_SDHC_INT_STAT_BRR   |
+    IMX_SDHC_INT_STAT_BWR   |
+    IMX_SDHC_INT_STAT_DINT  |
+    IMX_SDHC_INT_STAT_BGE   |
+    IMX_SDHC_INT_STAT_TC    |
+    IMX_SDHC_INT_STAT_CC
+);
+
+static bool imx_sdmmc_cmd_rsp_busy(uint32_t cmd) {
+    uint32_t resp = cmd & SDMMC_RESP_MASK;
+    return resp == SDMMC_RESP_LEN_48B;
+}
+
+static bool imx_sdmmc_has_data(uint32_t resp_type) {
+    return resp_type & SDMMC_RESP_DATA_PRESENT;
+}
+
+
+static bool imx_sdmmc_supports_adma2_64bit(imx_sdhci_device_t* dev) {
+    return (0); // TODO: iMX8 doesn't support 64bit ADMA2
+}
+
+static zx_status_t imx_sdhci_wait_for_reset(imx_sdhci_device_t* dev,
+                                            const uint32_t mask, zx_time_t timeout) {
+    zx_time_t deadline = zx_clock_get(ZX_CLOCK_MONOTONIC) + timeout;
+    while (true) {
+        if (((dev->regs->sys_ctrl) & mask) == 0) {
+            break;
+        }
+        if (zx_clock_get(ZX_CLOCK_MONOTONIC) > deadline) {
+            SDHCI_ERROR("time out while waiting for reset\n");
+            return ZX_ERR_TIMED_OUT;
+        }
+    }
+    return ZX_OK;
+}
+
+static void imx_sdhci_complete_request_locked(imx_sdhci_device_t* dev, sdmmc_req_t* req,
+                                                zx_status_t status) {
+    SDHCI_TRACE("complete cmd 0x%08x status %d\n", req->cmd, status);
+
+    // Disable interrupts when no pending transfer
+    dev->regs->int_signal_en = 0;
+
+    dev->cmd_req = NULL;
+    dev->data_req = NULL;
+    dev->data_blockid = 0;
+    dev->data_done = false;
+
+    req->status = status;
+    completion_signal(&dev->req_completion);
+}
+
+static void imx_sdhci_cmd_stage_complete_locked(imx_sdhci_device_t* dev) {
+    SDHCI_TRACE("Got CC interrupt\n");
+
+    if (!dev->cmd_req) {
+        SDHCI_TRACE("Spurious CC interupt\n");
+        return;
+    }
+
+    sdmmc_req_t* req = dev->cmd_req;
+    volatile struct imx_sdhci_regs* regs = dev->regs;
+    uint32_t cmd = SDMMC_COMMAND(req->cmd) | req->resp_type;
+
+    // Read the response data
+    if (cmd & SDMMC_RESP_LEN_136) {
+        // TODO: need any quirks??
+        req->response[0] = (regs->cmd_rsp0 << 8);
+        req->response[1] = (regs->cmd_rsp1 << 8) | ((regs->cmd_rsp0 >> 24) & 0xFF);
+        req->response[2] = (regs->cmd_rsp2 << 8) | ((regs->cmd_rsp1 >> 24) & 0xFF);
+        req->response[3] = (regs->cmd_rsp3 << 8) | ((regs->cmd_rsp2 >> 24) & 0xFF);
+
+    } else if (cmd & (SDMMC_RESP_LEN_48 | SDMMC_RESP_LEN_48B)) {
+        req->response[0] = regs->cmd_rsp0;
+        req->response[1] = regs->cmd_rsp1;
+    }
+
+    // We're done if the command has no data stage or if the data stage completed early
+    if (!dev->data_req || dev->data_done) {
+        imx_sdhci_complete_request_locked(dev, dev->cmd_req, ZX_OK);
+    } else {
+        dev->cmd_req = NULL;
+    }
+}
+
+static void imx_sdhci_data_stage_read_ready_locked(imx_sdhci_device_t* dev) {
+    SDHCI_TRACE("Got BRR Interrupts\n");
+
+    if (!dev->data_req || !imx_sdmmc_has_data(dev->data_req->resp_type)) {
+        SDHCI_TRACE("Spurious BRR Interrupt\n");
+        return;
+    }
+
+    sdmmc_req_t* req = dev->data_req;
+
+    if (dev->data_req->cmd == MMC_SEND_TUNING_BLOCK) {
+        // tuing commnad is done here
+        imx_sdhci_complete_request_locked(dev, dev->data_req, ZX_OK);
+    } else {
+        // Sequentially read each block
+        for (size_t byteid = 0; byteid < req->blocksize; byteid += 4) {
+            const size_t offset = dev->data_blockid * req->blocksize + byteid;
+            uint32_t* wrd = req->virt + offset;
+            *wrd = dev->regs->data_buff_acc_port; //TODO: Can't read this if DMA is enabled!
+        }
+        dev->data_blockid += 1;
+    }
+}
+
+static void imx_sdhci_data_stage_write_ready_locked(imx_sdhci_device_t* dev) {
+    SDHCI_TRACE("Got BWR Interrupt\n");
+
+    if (!dev->data_req || !imx_sdmmc_has_data(dev->data_req->resp_type)) {
+        SDHCI_TRACE("Spurious BWR Interrupt\n");
+        return;
+    }
+
+    sdmmc_req_t* req = dev->data_req;
+
+    // Sequentially write each block
+    for (size_t byteid = 0; byteid < req->blocksize; byteid += 4) {
+        const size_t offset = dev->data_blockid * req->blocksize + byteid;
+        uint32_t* wrd = req->virt + offset;
+        dev->regs->data_buff_acc_port = *wrd; //TODO: Can't write if DMA is enabled
+    }
+    dev->data_blockid += 1;
+}
+
+static void imx_sdhci_transfer_complete_locked(imx_sdhci_device_t* dev) {
+    SDHCI_TRACE("Got TC Interrupt\n");
+    if (!dev->data_req) {
+        SDHCI_TRACE("Spurious TC Interrupt\n");
+        return;
+    }
+
+    if (dev->cmd_req) {
+        dev->data_done = true;
+    } else {
+        imx_sdhci_complete_request_locked(dev, dev->data_req, ZX_OK);
+    }
+}
+
+static void imx_sdhci_error_recovery_locked(imx_sdhci_device_t* dev) {
+    // Reset internal state machines
+    dev->regs->sys_ctrl |= IMX_SDHC_SYS_CTRL_RSTC;
+    imx_sdhci_wait_for_reset(dev, IMX_SDHC_SYS_CTRL_RSTC, ZX_SEC(1));
+    dev->regs->sys_ctrl |= IMX_SDHC_SYS_CTRL_RSTD;
+    imx_sdhci_wait_for_reset(dev, IMX_SDHC_SYS_CTRL_RSTD, ZX_SEC(1));
+
+    // Complete any pending txn with error status
+    if (dev->cmd_req != NULL) {
+        imx_sdhci_complete_request_locked(dev, dev->cmd_req, ZX_ERR_IO);
+    } else if (dev->data_req != NULL) {
+        imx_sdhci_complete_request_locked(dev, dev->data_req, ZX_ERR_IO);
+    }
+}
+
+static uint32_t get_clock_divider(const uint32_t base_clock, const uint32_t target_rate) {
+    uint32_t pre_div = 1;
+    uint32_t div = 1;
+
+    if (target_rate >= base_clock) {
+        // A clock divider of 0 means "don't divide the clock"
+        // If the base clock is already slow enough to use as the SD clock then
+        // we don't need to divide it any further.
+        return 0;
+    }
+
+    SDHCI_TRACE("base %d, pre_div %d, div = %d, target_rate %d\n",
+        base_clock, pre_div, div, target_rate);
+    while (base_clock / pre_div / 16 > target_rate && pre_div < 256) {
+        SDHCI_TRACE("base %d, pre_div %d, div = %d, target_rate %d\n",
+            base_clock, pre_div, div, target_rate);
+        pre_div *= 2;
+    }
+
+    while (base_clock / pre_div / div > target_rate && div < 16) {
+        SDHCI_TRACE("base %d, pre_div %d, div = %d, target_rate %d\n",
+            base_clock, pre_div, div, target_rate);
+        div++;
+    }
+
+    SDHCI_TRACE("base %d, pre_div %d, div = %d, target_rate %d\n",
+        base_clock, pre_div, div, target_rate);
+
+    pre_div >>= 1;
+    div -= 1;
+
+    return (((pre_div & 0xFF) << 16)| (div & 0xF));
+}
+
+
+static int imx_sdhci_irq_thread(void *args) {
+    zx_status_t wait_res;
+    imx_sdhci_device_t* dev = (imx_sdhci_device_t*)args;
+    volatile struct imx_sdhci_regs* regs = dev->regs;
+    zx_handle_t irq_handle = dev->irq_handle;
+
+    while(true) {
+        uint64_t slots;
+        wait_res = zx_interrupt_wait(irq_handle, &slots);
+        if (wait_res != ZX_OK) {
+            SDHCI_ERROR("Interrupt_wait failed with retcode %d\n", wait_res);
+            break;
+        }
+
+        const uint32_t irq = regs->int_status;
+        SDHCI_TRACE("got irq 0x%08x 0x%08x en 0x%08x sig 0x%08x\n", regs->int_status, irq,
+                                                    regs->int_status_en, regs->int_signal_en);
+
+        // Acknowledge the IRQs that we stashed.
+        regs->int_status = irq;
+
+        mtx_lock(&dev->mtx);
+        if (irq & IMX_SDHC_INT_STAT_CC) {
+            imx_sdhci_cmd_stage_complete_locked(dev);
+        }
+        if (irq & IMX_SDHC_INT_STAT_BRR) {
+            imx_sdhci_data_stage_read_ready_locked(dev);
+        }
+        if (irq & IMX_SDHC_INT_STAT_BWR) {
+            imx_sdhci_data_stage_write_ready_locked(dev);
+        }
+        if (irq & IMX_SDHC_INT_STAT_TC) {
+            imx_sdhci_transfer_complete_locked(dev);
+        }
+        if (irq & error_interrupts) {
+            if (irq & IMX_SDHC_INT_STAT_DMAE) {
+                SDHCI_TRACE("ADMA error 0x%x ADMAADDR0 0x%x\n",
+                regs->adma_err_status, regs->adma_sys_addr);
+            }
+            imx_sdhci_error_recovery_locked(dev);
+        }
+        mtx_unlock(&dev->mtx);
+    }
+    return ZX_OK;
+}
+
+static zx_status_t imx_sdhci_build_dma_desc(imx_sdhci_device_t* dev, sdmmc_req_t* req) {
+    SDHCI_FUNC_ENTRY_LOG;
+    return ZX_OK;
+}
+
+static zx_status_t imx_sdhci_start_req_locked(imx_sdhci_device_t* dev, sdmmc_req_t* req) {
+    volatile struct imx_sdhci_regs* regs = dev->regs;
+    const uint32_t arg = req->arg;
+    const uint16_t blkcnt = req->blockcount;
+    const uint16_t blksiz = req->blocksize;
+    uint32_t cmd = SDMMC_COMMAND(req->cmd) | req->resp_type;
+    bool has_data = imx_sdmmc_has_data(req->resp_type);
+
+    if (req->use_dma && !imx_sdmmc_supports_adma2_64bit(dev)) {
+        SDHCI_INFO("Host does not support 64BIT DMA\n");
+        // iMX8 support ADMA2 32bit!! why not use that!
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+
+    if (req->use_dma) {
+        SDHCI_INFO("we don't support dma yet\t");
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+
+    SDHCI_TRACE("start_req cmd=0x%08x (data %d dma %d bsy %d) blkcnt %u blksiz %u\n",
+                  cmd, has_data, req->use_dma, imx_sdmmc_cmd_rsp_busy(cmd), blkcnt, blksiz);
+
+    // Every command requires that the Commnad Inhibit bit is unset
+    uint32_t inhibit_mask = IMX_SDHC_PRES_STATE_CIHB;
+
+    // Busy type commands must also wait for the DATA Inhibit to be 0 unless it's an abort
+    // command which can be issued with the data lines active
+    if (((cmd & SDMMC_RESP_LEN_48B) == SDMMC_RESP_LEN_48B) &&
+        ((cmd & SDMMC_CMD_TYPE_ABORT) == 0)) {
+        inhibit_mask |= IMX_SDHC_PRES_STATE_CDIHB;
+    }
+
+    // Wait for the inhibit masks from above to become 0 before issueing the command
+    while(regs->pres_state & inhibit_mask) {
+        zx_nanosleep(zx_deadline_after(ZX_MSEC(1)));
+    }
+
+    // zx_status_t status = ZX_OK;
+    if (has_data) {
+        if (cmd & SDMMC_CMD_MULTI_BLK) {
+            cmd |= SDMMC_CMD_AUTO12;
+        }
+    }
+
+    regs->blk_att = (blksiz | (blkcnt << 16));
+
+    regs->cmd_arg = arg;
+
+    // Clear any pending interrupts before starting the transaction
+    regs->int_status = regs->int_signal_en;
+
+    // Unmask and enable interrupts
+    regs->int_signal_en = error_interrupts | normal_interrupts;
+    regs->int_status_en = error_interrupts | normal_interrupts;
+
+    // Start command
+    regs->cmd_xfr_typ = cmd;
+
+    dev->cmd_req = req;
+
+    if (has_data || imx_sdmmc_cmd_rsp_busy(cmd)) {
+        dev->data_req = req;
+    } else {
+        dev->data_req = NULL;
+    }
+    dev->data_blockid = 0;
+    dev->data_done = false;
+    return ZX_OK;
+// err:
+//     return status;
+}
+
+static zx_status_t imx_sdhci_finish_req(imx_sdhci_device_t* dev, sdmmc_req_t* req) {
+    zx_status_t status = ZX_OK;
+
+    if (req->use_dma && req->pmt != ZX_HANDLE_INVALID) {
+        status = zx_pmt_unpin(req->pmt);
+        if (status != ZX_OK) {
+            SDHCI_ERROR("error %d in pmt_unpin\n", status);
+        }
+        req->pmt = ZX_HANDLE_INVALID;
+    }
+    return status;
+}
+
+/* SDMMC PROTOCOL Implementations: host_info */
+static zx_status_t imx_sdhci_host_info(void* ctx, sdmmc_host_info_t* info) {
+    SDHCI_FUNC_ENTRY_LOG;
+    return ZX_OK;
+}
+
+/* SDMMC PROTOCOL Implementations: set_signal_voltage */
+static zx_status_t imx_sdhci_set_signal_voltage(void* ctx, sdmmc_voltage_t voltage) {
+    SDHCI_FUNC_ENTRY_LOG;
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+/* SDMMC PROTOCOL Implementations: set_bus_width */
+static zx_status_t imx_sdhci_set_bus_width(void* ctx, uint32_t bus_width) {
+    SDHCI_FUNC_ENTRY_LOG;
+    if (bus_width >= SDMMC_BUS_WIDTH_MAX) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+    zx_status_t status = ZX_OK;
+    imx_sdhci_device_t* dev = ctx;
+
+    mtx_lock(&dev->mtx);
+
+    if ((bus_width == SDMMC_BUS_WIDTH_8) && !(dev->info.caps & SDMMC_HOST_CAP_BUS_WIDTH_8)) {
+        SDHCI_TRACE("8-bit bus width not supported\n");
+        status = ZX_ERR_NOT_SUPPORTED;
+        goto unlock;
+    }
+
+    switch (bus_width) {
+        case SDMMC_BUS_WIDTH_1:
+            dev->regs->prot_ctrl &= ~IMX_SDHC_PROT_CTRL_DTW_MASK;
+            dev->regs->prot_ctrl |= IMX_SDHC_PROT_CTRL_DTW_1;
+            break;
+        case SDMMC_BUS_WIDTH_4:
+            dev->regs->prot_ctrl &= ~IMX_SDHC_PROT_CTRL_DTW_MASK;
+            dev->regs->prot_ctrl |= IMX_SDHC_PROT_CTRL_DTW_4;
+            break;
+        case SDMMC_BUS_WIDTH_8:
+            dev->regs->prot_ctrl &= ~IMX_SDHC_PROT_CTRL_DTW_MASK;
+            dev->regs->prot_ctrl |= IMX_SDHC_PROT_CTRL_DTW_8;
+            break;
+        default:
+            break;
+    }
+
+    SDHCI_TRACE("set bus width to %d\n", bus_width);
+
+unlock:
+    mtx_unlock(&dev->mtx);
+    return status;
+}
+
+/* SDMMC PROTOCOL Implementations: set_bus_freq */
+static zx_status_t imx_sdhci_set_bus_freq(void* ctx, uint32_t bus_freq) {
+    SDHCI_FUNC_ENTRY_LOG;
+    zx_status_t status = ZX_OK;
+    imx_sdhci_device_t* dev = ctx;
+
+    mtx_lock(&dev->mtx);
+
+    const uint32_t divider = get_clock_divider(dev->base_clock, bus_freq);
+    const uint8_t pre_div = (divider >> 16) & 0xFF;
+    const uint8_t div = (divider & 0xF);
+    SDHCI_TRACE("divider %d, pre_div %d, div = %d\n",
+        divider, pre_div, div);
+
+    volatile struct imx_sdhci_regs* regs = dev->regs;
+
+    uint32_t iterations = 0;
+    while (regs->pres_state & (IMX_SDHC_PRES_STATE_CIHB | IMX_SDHC_PRES_STATE_CDIHB)) {
+        if (++iterations > 1000) {
+            status = ZX_ERR_TIMED_OUT;
+            goto unlock;
+        }
+        zx_nanosleep(zx_deadline_after(ZX_MSEC(1)));
+    }
+
+    regs->vend_spec &= ~(IMX_SDHC_VEND_SPEC_FRC_SDCLK_ON);
+
+    // turn off clocks
+    regs->sys_ctrl &= ~(IMX_SDHC_SYS_CTRL_CLOCK_PEREN   |
+                        IMX_SDHC_SYS_CTRL_CLOCK_HCKEN   |
+                        IMX_SDHC_SYS_CTRL_CLOCK_IPGEN   |
+                        IMX_SDHC_SYS_CTRL_CLOCK_MASK);
+
+    regs->sys_ctrl |= (IMX_SDHC_SYS_CTRL_CLOCK_PEREN    |
+                        IMX_SDHC_SYS_CTRL_CLOCK_HCKEN   |
+                        IMX_SDHC_SYS_CTRL_CLOCK_IPGEN   |
+                        (pre_div << IMX_SDHC_SYS_CTRL_PREDIV_SHIFT) |
+                        (div << IMX_SDHC_SYS_CTRL_DIVIDER_SHIFT));
+
+    regs->vend_spec |= (IMX_SDHC_VEND_SPEC_FRC_SDCLK_ON);
+    zx_nanosleep(zx_deadline_after(ZX_MSEC(2)));
+
+    SDHCI_TRACE("desired freq = %d, actual = %d, (%d, %d. %d)\n",
+        bus_freq, dev->base_clock / (pre_div<<1) / (div+1), dev->base_clock, pre_div, div);
+
+unlock:
+    mtx_unlock(&dev->mtx);
+    return status;
+}
+
+/* SDMMC PROTOCOL Implementations: set_timing */
+static zx_status_t imx_sdhci_set_timing(void* ctx, sdmmc_timing_t timing) {
+    SDHCI_FUNC_ENTRY_LOG;
+    return ZX_OK;
+}
+
+/* SDMMC PROTOCOL Implementations: hw_reset */
+static void imx_sdhci_hw_reset(void* ctx) {
+    SDHCI_FUNC_ENTRY_LOG;
+}
+
+/* SDMMC PROTOCOL Implementations: perform_tuning */
+static zx_status_t imx_sdhci_perform_tuning(void* ctx) {
+    SDHCI_FUNC_ENTRY_LOG;
+    return ZX_OK;
+}
+
+/* SDMMC PROTOCOL Implementations: request */
+static zx_status_t imx_sdhci_request(void* ctx, sdmmc_req_t* req) {
+    SDHCI_FUNC_ENTRY_LOG;
+    zx_status_t status = ZX_OK;
+    imx_sdhci_device_t* dev = ctx;
+
+    mtx_lock(&dev->mtx);
+
+    // one command at a time
+    if ((dev->cmd_req != NULL) || (dev->data_req != NULL)) {
+        status = ZX_ERR_SHOULD_WAIT;
+        goto unlock_out;
+    }
+
+    status = imx_sdhci_start_req_locked(dev, req);
+    if (status != ZX_OK) {
+        goto unlock_out;
+    }
+
+    mtx_unlock(&dev->mtx);
+
+    completion_wait(&dev->req_completion, ZX_TIME_INFINITE);
+
+    imx_sdhci_finish_req(dev, req);
+
+    completion_reset(&dev->req_completion);
+
+    return req->status;
+
+unlock_out:
+    mtx_unlock(&dev->mtx);
+    imx_sdhci_finish_req(dev, req);
+    return status;
+}
+
+
+static sdmmc_protocol_ops_t sdmmc_proto = {
+    .host_info = imx_sdhci_host_info,
+    .set_signal_voltage = imx_sdhci_set_signal_voltage,
+    .set_bus_width = imx_sdhci_set_bus_width,
+    .set_bus_freq = imx_sdhci_set_bus_freq,
+    .set_timing = imx_sdhci_set_timing,
+    .hw_reset = imx_sdhci_hw_reset,
+    .perform_tuning = imx_sdhci_perform_tuning,
+    .request = imx_sdhci_request,
+};
+
+static void imx_sdhci_unbind(void* ctx) {
+    imx_sdhci_device_t* dev = ctx;
+    device_remove(dev->zxdev);
+}
+
+static void imx_sdhci_release(void* ctx) {
+    imx_sdhci_device_t* dev = ctx;
+    if (dev->regs != NULL) {
+        zx_handle_close(dev->regs_handle);
+    }
+    zx_handle_close(dev->bti_handle);
+    free(dev);
+}
+
+static zx_protocol_device_t imx_sdhci_device_proto = {
+    .version = DEVICE_OPS_VERSION,
+    .unbind = imx_sdhci_unbind,
+    .release = imx_sdhci_release,
+
+};
+
+
+static zx_status_t imx_sdhci_bind(void* ctx, zx_device_t* parent) {
+    zx_status_t status;
+
+    imx_sdhci_device_t* dev = calloc(1, sizeof(imx_sdhci_device_t));
+    if (!dev) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &dev->pdev);
+    if (status != ZX_OK) {
+        SDHCI_ERROR("ZX_PROTOCOL_PLATFORM_DEV not available %d \n", status);
+        goto fail;
+    }
+
+    status = pdev_map_mmio_buffer(&dev->pdev, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE,
+                                    &dev->mmios);
+    if (status != ZX_OK) {
+        SDHCI_ERROR("pdev_map_mmio_buffer failed %d\n", status);
+        goto fail;
+    }
+
+    // hook up mmio to dev->regs
+    dev->regs = io_buffer_virt(&dev->mmios);
+
+    status = pdev_get_bti(&dev->pdev, 0, &dev->bti_handle);
+    if (status != ZX_OK) {
+        SDHCI_ERROR("Could not get BTI handle %d\n", status);
+        goto fail;
+    }
+
+    status = pdev_map_interrupt(&dev->pdev, 0, &dev->irq_handle);
+    if (status != ZX_OK) {
+        SDHCI_ERROR("pdev_map_interrupt failed %d\n", status);
+        goto fail;
+    }
+
+    thrd_t irq_thread;
+    if (thrd_create_with_name(&irq_thread, imx_sdhci_irq_thread,
+                                        dev, "imx_sdhci_irq_thread") != thrd_success) {
+        SDHCI_ERROR("Failed to create irq thread\n");
+    }
+    thrd_detach(irq_thread);
+
+    dev->base_clock = 200000000; // TODO: Better way of doing this obviously
+
+    uint32_t caps0 = dev->regs->host_ctrl_cap;
+    SDHCI_ERROR("caps = 0x%x\n", caps0);
+
+    dev->info.caps |= SDMMC_HOST_CAP_BUS_WIDTH_8;
+    if (caps0 & SDHCI_CORECFG_3P3_VOLT_SUPPORT) {
+        dev->info.caps |= SDMMC_HOST_CAP_VOLTAGE_330;
+    }
+
+
+    // Reset host controller
+    dev->regs->sys_ctrl |= IMX_SDHC_SYS_CTRL_RSTA;
+    if (imx_sdhci_wait_for_reset(dev, IMX_SDHC_SYS_CTRL_RSTA, ZX_SEC(1)) != ZX_OK) {
+        SDHCI_ERROR("Did not recover from reset 0x%x\n", dev->regs->sys_ctrl);
+        goto fail;
+    }
+
+    // RSTA does not reset MMC_BOOT register. so do it manually
+    dev->regs->mmc_boot = 0;
+
+    // Reset MIX_CTRL and CLK_TUNE_CTRL regs to 0 as well
+    dev->regs->mix_ctrl = 0;
+    dev->regs->clk_tune_ctrl_status = 0;
+
+    // disable DLL_CTRL delay lie
+    dev->regs->dll_ctrl = 0;
+
+    // dev->regs->prot_ctrl = 0x00000020
+
+    // no dma for now
+    dev->info.max_transfer_size = 0;
+
+    // enable clocks
+    imx_sdhci_set_bus_freq(dev, SD_FREQ_SETUP_HZ);
+
+    dev->regs->sys_ctrl |= (0xe << 16);
+
+    zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
+
+    // Disable all interrupts
+    dev->regs->int_signal_en = 0;
+    dev->regs->int_status = 0xffffffff;
+
+
+    device_add_args_t args = {
+        .version = DEVICE_ADD_ARGS_VERSION,
+        .name = "imx-sdhci",
+        .ctx = dev,
+        .ops = &imx_sdhci_device_proto,
+        .proto_id = ZX_PROTOCOL_SDMMC,
+        .proto_ops = &sdmmc_proto,
+    };
+
+    status = device_add(parent, &args, &dev->zxdev);
+    if (status != ZX_OK) {
+        SDHCI_ERROR("device_add failed %d\n", status);
+        goto fail;
+    }
+
+    return ZX_OK;
+
+fail:
+    imx_sdhci_release(dev);
+    return status;
+}
+
+static zx_driver_ops_t imx_sdhci_driver_ops = {
+    .version = DRIVER_OPS_VERSION,
+    .bind = imx_sdhci_bind,
+};
+
+ZIRCON_DRIVER_BEGIN(imx_sdhci, imx_sdhci_driver_ops, "zircon", "0.1", 4)
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV),
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_NXP),
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_DID, PDEV_DID_IMX_SDHCI),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_IMX8MEVK),
+ZIRCON_DRIVER_END(imx_sdhci)
diff --git a/system/dev/block/imx-sdhci/imx-sdhci.h b/system/dev/block/imx-sdhci/imx-sdhci.h
new file mode 100644
index 0000000..d6703a7
--- /dev/null
+++ b/system/dev/block/imx-sdhci/imx-sdhci.h
@@ -0,0 +1,203 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+typedef struct imx_sdhci_regs {
+    uint32_t ds_addr;                       // 0x00
+    uint32_t blk_att;                       // 0x04
+    uint32_t cmd_arg;                       // 0x08
+    uint32_t cmd_xfr_typ;                   // 0x0C
+    uint32_t cmd_rsp0;                      // 0x10
+    uint32_t cmd_rsp1;                      // 0x14
+    uint32_t cmd_rsp2;                      // 0x18
+    uint32_t cmd_rsp3;                      // 0x1C
+    uint32_t data_buff_acc_port;            // 0x20
+    uint32_t pres_state;                    // 0x24
+    uint32_t prot_ctrl;                     // 0x28
+    uint32_t sys_ctrl;                      // 0x2C
+    uint32_t int_status;                    // 0x30
+    uint32_t int_status_en;                 // 0x34
+    uint32_t int_signal_en;                 // 0x38
+    uint32_t autocmd12_err_status;          // 0x3C
+    uint32_t host_ctrl_cap;                 // 0x40
+    uint32_t wtmk_lvl;                      // 0x44
+    uint32_t mix_ctrl;                      // 0x48
+    uint8_t rsv1[4];                        // xxxxxxxx
+    uint32_t force_event;                   // 0x50
+    uint32_t adma_err_status;               // 0x54
+    uint32_t adma_sys_addr;                 // 0x58
+    uint8_t rsv2[4];                        // xxxxxxxx
+    uint32_t dll_ctrl;                      // 0x60
+    uint32_t dll_status;                    // 0x64
+    uint32_t clk_tune_ctrl_status;          // 0x68
+    uint8_t rsv3[4];                        // xxxxxxxx
+    uint32_t strobe_dll_ctrl;               // 0x70
+    uint32_t strobe_dll_status;             // 0x74
+    uint8_t rsv4[72];                       // xxxxxxxx
+    uint32_t vend_spec;                     // 0xC0
+    uint32_t mmc_boot;                      // 0xC4
+    uint32_t vend_spec2;                    // 0xC8
+    uint32_t tuning_ctrl;                   // 0xCC
+} __PACKED imx_sdhci_regs_t;
+
+
+#define IMX_SDHC_DS_ADDR(x)                         (x << 2)
+
+#define IMX_SDHC_BLK_ATT_BLKSIZE(x)                 ( x & 0x1FFF)
+#define IMX_SDHC_BLK_ATT_BLKCNT(x)                  ((x & 0xFFFF) << 16)
+
+
+#define IMX_SDHC_CMD_XFER_TYPE_CMDINX(x)            ((x & 0x3f) << 24)
+#define IMX_SDHC_CMD_XFER_TYPE_CMDTYP_ABORT         (3 << 22)
+#define IMX_SDHC_CMD_XFER_TYPE_CMDTYP_RESUME        (2 << 22)
+#define IMX_SDHC_CMD_XFER_TYPE_CMDTYP_SUSPEND       (1 << 22)
+#define IMX_SDHC_CMD_XFER_TYPE_CMDTYP_NORM          (0 << 22)
+#define IMX_SDHC_CMD_XFER_TYPE_DPSEL                (1 << 21)
+#define IMX_SDHC_CMD_XFER_TYPE_CICEN                (1 << 20)
+#define IMX_SDHC_CMD_XFER_TYPE_CCCEN                (1 << 19)
+#define IMX_SDHC_CMD_XFER_TYPE_RSP_TYPE_48_BSY      (3 << 16)
+#define IMX_SDHC_CMD_XFER_TYPE_RSP_TYPE_48          (2 << 16)
+#define IMX_SDHC_CMD_XFER_TYPE_RSP_TYPE_136         (1 << 16)
+#define IMX_SDHC_CMD_XFER_TYPE_RSP_TYPE_NO_RESP     (0 << 16)
+
+#define IMX_SDHC_PRES_STATE_DLSL(x)                 (x << 24)
+#define IMX_SDHC_PRES_STATE_CLSL                    (1 << 23)
+#define IMX_SDHC_PRES_STATE_WPSPL                   (1 << 19)
+#define IMX_SDHC_PRES_STATE_CDPL                    (1 << 18)
+#define IMX_SDHC_PRES_STATE_CINST                   (1 << 16)
+#define IMX_SDHC_PRES_STATE_TSCD                    (1 << 15)
+#define IMX_SDHC_PRES_STATE_RTR                     (1 << 12)
+#define IMX_SDHC_PRES_STATE_BREN                    (1 << 11)
+#define IMX_SDHC_PRES_STATE_BWEN                    (1 << 10)
+#define IMX_SDHC_PRES_STATE_RTA                     (1 << 9)
+#define IMX_SDHC_PRES_STATE_WTA                     (1 << 8)
+#define IMX_SDHC_PRES_STATE_SDOFF                   (1 << 7)
+#define IMX_SDHC_PRES_STATE_PEROFF                  (1 << 6)
+#define IMX_SDHC_PRES_STATE_HCKOFF                  (1 << 5)
+#define IMX_SDHC_PRES_STATE_IPGOFF                  (1 << 4)
+#define IMX_SDHC_PRES_STATE_SDSTB                   (1 << 3)
+#define IMX_SDHC_PRES_STATE_DLA                     (1 << 2)
+#define IMX_SDHC_PRES_STATE_CDIHB                   (1 << 1)
+#define IMX_SDHC_PRES_STATE_CIHB                    (1 << 0)
+
+
+#define IMX_SDHC_PROT_CTRL_DMASEL_MASK              (3 << 8)
+#define IMX_SDHC_PROT_CTRL_DMASEL_ADMA2             (2 << 8)
+#define IMX_SDHC_PROT_CTRL_DMASEL_ADMA1             (1 << 8)
+#define IMX_SDHC_PROT_CTRL_DMASEL_NODMA             (0 << 8)
+#define IMX_SDHC_PROT_CTRL_CDSS                     (1 << 7)
+#define IMX_SDHC_PROT_CTRL_CDTL                     (1 << 6)
+#define IMX_SDHC_PROT_CTRL_INIT                     (1 << 5)
+#define IMX_SDHC_PROT_CTRL_DTW_MASK                 (3 << 1)
+#define IMX_SDHC_PROT_CTRL_DTW_8                    (2 << 1)
+#define IMX_SDHC_PROT_CTRL_DTW_4                    (1 << 1)
+#define IMX_SDHC_PROT_CTRL_DTW_1                    (0 << 1)
+
+#define IMX_SDHC_SYS_CTRL_RSTT                      (1 << 28)
+#define IMX_SDHC_SYS_CTRL_INTA                      (1 << 27)
+#define IMX_SDHC_SYS_CTRL_RSTD                      (1 << 26)
+#define IMX_SDHC_SYS_CTRL_RSTC                      (1 << 25)
+#define IMX_SDHC_SYS_CTRL_RSTA                      (1 << 24)
+#define IMX_SDHC_SYS_CTRL_DTOCV(x)                  ((x & 0xF) << 16)
+#define IMX_SDHC_SYS_CTRL_SDCLKFS(x)                ((x & 0xFF) << 8)
+#define IMX_SDHC_SYS_CTRL_DVS(x)                    ((x & 0xF) << 4)
+
+// Undocumented sysctl bits (clock related)
+#define IMX_SDHC_SYS_CTRL_CLOCK_MASK                (0x0000fff0)
+#define IMX_SDHC_SYS_CTRL_PREDIV_SHIFT              (8)
+#define IMX_SDHC_SYS_CTRL_DIVIDER_SHIFT             (4)
+#define IMX_SDHC_SYS_CTRL_CLOCK_PEREN               (1 << 2)
+#define IMX_SDHC_SYS_CTRL_CLOCK_HCKEN               (1 << 1)
+#define IMX_SDHC_SYS_CTRL_CLOCK_IPGEN               (1 << 0)
+
+#define IMX_SDHC_INT_STAT_DMAE                      (1 << 28)
+#define IMX_SDHC_INT_STAT_TNE                       (1 << 26)
+#define IMX_SDHC_INT_STAT_AC12E                     (1 << 24)
+#define IMX_SDHC_INT_STAT_DEBE                      (1 << 22)
+#define IMX_SDHC_INT_STAT_DCE                       (1 << 21)
+#define IMX_SDHC_INT_STAT_DTOE                      (1 << 20)
+#define IMX_SDHC_INT_STAT_CIE                       (1 << 19)
+#define IMX_SDHC_INT_STAT_CEBE                      (1 << 18)
+#define IMX_SDHC_INT_STAT_CCE                       (1 << 17)
+#define IMX_SDHC_INT_STAT_CTOE                      (1 << 16)
+#define IMX_SDHC_INT_STAT_TP                        (1 << 14)
+#define IMX_SDHC_INT_STAT_RTE                       (1 << 12)
+#define IMX_SDHC_INT_STAT_CINT                      (1 << 8)
+#define IMX_SDHC_INT_STAT_CRM                       (1 << 7)
+#define IMX_SDHC_INT_STAT_CINS                      (1 << 6)
+#define IMX_SDHC_INT_STAT_BRR                       (1 << 5)
+#define IMX_SDHC_INT_STAT_BWR                       (1 << 4)
+#define IMX_SDHC_INT_STAT_DINT                      (1 << 3)
+#define IMX_SDHC_INT_STAT_BGE                       (1 << 2)
+#define IMX_SDHC_INT_STAT_TC                        (1 << 1)
+#define IMX_SDHC_INT_STAT_CC                        (1 << 0)
+
+#define IMX_SDHC_INT_EN_DMAEN                       (1 << 28)
+#define IMX_SDHC_INT_EN_TNE                         (1 << 26)
+#define IMX_SDHC_INT_EN_AC12E                       (1 << 24)
+#define IMX_SDHC_INT_EN_DEBE                        (1 << 22)
+#define IMX_SDHC_INT_EN_DCE                         (1 << 21)
+#define IMX_SDHC_INT_EN_DTOE                        (1 << 20)
+#define IMX_SDHC_INT_EN_CIE                         (1 << 19)
+#define IMX_SDHC_INT_EN_CEBE                        (1 << 18)
+#define IMX_SDHC_INT_EN_CCE                         (1 << 17)
+#define IMX_SDHC_INT_EN_CTOE                        (1 << 16)
+#define IMX_SDHC_INT_EN_TP                          (1 << 14)
+#define IMX_SDHC_INT_EN_RTE                         (1 << 12)
+#define IMX_SDHC_INT_EN_CINT                        (1 << 8)
+#define IMX_SDHC_INT_EN_CRM                         (1 << 7)
+#define IMX_SDHC_INT_EN_CINS                        (1 << 6)
+#define IMX_SDHC_INT_EN_BRR                         (1 << 5)
+#define IMX_SDHC_INT_EN_BWR                         (1 << 4)
+#define IMX_SDHC_INT_EN_DINT                        (1 << 3)
+#define IMX_SDHC_INT_EN_BGE                         (1 << 2)
+#define IMX_SDHC_INT_EN_TC                          (1 << 1)
+#define IMX_SDHC_INT_EN_CC                          (1 << 0)
+
+#define IMX_SDHC_INT_SIG_DMAEN                     (1 << 28)
+#define IMX_SDHC_INT_SIG_TNE                       (1 << 26)
+#define IMX_SDHC_INT_SIG_AC12E                     (1 << 24)
+#define IMX_SDHC_INT_SIG_DEBE                      (1 << 22)
+#define IMX_SDHC_INT_SIG_DCE                       (1 << 21)
+#define IMX_SDHC_INT_SIG_DTOE                      (1 << 20)
+#define IMX_SDHC_INT_SIG_CIE                       (1 << 19)
+#define IMX_SDHC_INT_SIG_CEBE                      (1 << 18)
+#define IMX_SDHC_INT_SIG_CCE                       (1 << 17)
+#define IMX_SDHC_INT_SIG_CTOE                      (1 << 16)
+#define IMX_SDHC_INT_SIG_TP                        (1 << 14)
+#define IMX_SDHC_INT_SIG_RTE                       (1 << 12)
+#define IMX_SDHC_INT_SIG_CINT                      (1 << 8)
+#define IMX_SDHC_INT_SIG_CRM                       (1 << 7)
+#define IMX_SDHC_INT_SIG_CINS                      (1 << 6)
+#define IMX_SDHC_INT_SIG_BRR                       (1 << 5)
+#define IMX_SDHC_INT_SIG_BWR                       (1 << 4)
+#define IMX_SDHC_INT_SIG_DINT                      (1 << 3)
+#define IMX_SDHC_INT_SIG_BGE                       (1 << 2)
+#define IMX_SDHC_INT_SIG_TC                        (1 << 1)
+#define IMX_SDHC_INT_SIG_CC                        (1 << 0)
+
+#define IMX_SDHC_HOST_CTRL_CAP_VS18                 (1 << 26)
+#define IMX_SDHC_HOST_CTRL_CAP_VS30                 (1 << 25)
+#define IMX_SDHC_HOST_CTRL_CAP_VS33                 (1 << 24)
+#define IMX_SDHC_HOST_CTRL_CAP_SRS                  (1 << 23)
+#define IMX_SDHC_HOST_CTRL_CAP_DMAS                 (1 << 22)
+#define IMX_SDHC_HOST_CTRL_CAP_HSS                  (1 << 21)
+#define IMX_SDHC_HOST_CTRL_CAP_ADMAS                (1 << 20)
+
+#define IMX_SDHC_MIX_CTRL_HS400                     (1 << 26)
+#define IMX_SDHC_MIX_CTRL_FBCLK_SEL                 (1 << 25)
+#define IMX_SDHC_MIX_CTRL_AUTO_TUNE                 (1 << 24)
+#define IMX_SDHC_MIX_CTRL_SMP_CLK_SEL               (1 << 23)
+#define IMX_SDHC_MIX_CTRL_EXE_TUNE                  (1 << 22)
+#define IMX_SDHC_MIX_CTRL_AC23EN                    (1 << 7)
+#define IMX_SDHC_MIX_CTRL_NIBBLE_POS                (1 << 6)
+#define IMX_SDHC_MIX_CTRL_MSBSEL                    (1 << 5)
+#define IMX_SDHC_MIX_CTRL_DTDSEL                    (1 << 4)
+#define IMX_SDHC_MIX_CTRL_DDR_EN                    (1 << 3)
+#define IMX_SDHC_MIX_CTRL_AC12EN                    (1 << 2)
+#define IMX_SDHC_MIX_CTRL_BCEN                      (1 << 1)
+#define IMX_SDHC_MIX_CTRL_DMAEN                     (1 << 0)
+
+#define IMX_SDHC_VEND_SPEC_FRC_SDCLK_ON             (1 << 8)
\ No newline at end of file
diff --git a/system/dev/block/imx-sdhci/rules.mk b/system/dev/block/imx-sdhci/rules.mk
new file mode 100644
index 0000000..ac5c6ab
--- /dev/null
+++ b/system/dev/block/imx-sdhci/rules.mk
@@ -0,0 +1,20 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_TYPE := driver
+
+MODULE_SRCS := \
+    $(LOCAL_DIR)/imx-sdhci.c
+
+MODULE_STATIC_LIBS := system/ulib/ddk system/ulib/sync system/ulib/pretty
+
+MODULE_LIBS := system/ulib/driver system/ulib/zircon system/ulib/c system/ulib/fdio
+
+MODULE_HEADER_DEPS := system/dev/soc/imx8m
+
+include make/module.mk
diff --git a/system/dev/block/sdmmc/mmc.c b/system/dev/block/sdmmc/mmc.c
index 41d189c..9c11df9 100644
--- a/system/dev/block/sdmmc/mmc.c
+++ b/system/dev/block/sdmmc/mmc.c
@@ -27,7 +27,7 @@
     // Send the MMC_SWITCH command
     zx_status_t st = mmc_switch(dev, index, value);
     if (st != ZX_OK) {
-        zxlogf(ERROR, "mmc: failed to MMC_SWITCH (0x%x=%d), retcode = %d\n", index, value, st);
+        printf("mmc: failed to MMC_SWITCH (0x%x=%d), retcode = %d\n", index, value, st);
         return st;
     }
 
@@ -36,12 +36,12 @@
     st = sdmmc_send_status(dev, &resp);
     if (st == ZX_OK) {
         if (resp & MMC_STATUS_SWITCH_ERR) {
-            zxlogf(ERROR, "mmc: mmc status error after MMC_SWITCH (0x%x=%d), status = 0x%08x\n",
+            printf("mmc: mmc status error after MMC_SWITCH (0x%x=%d), status = 0x%08x\n",
                    index, value, resp);
             st = ZX_ERR_INTERNAL;
         }
     } else {
-        zxlogf(ERROR, "mmc: failed to MMC_SEND_STATUS (%x=%d), retcode = %d\n", index, value, st);
+        printf("mmc: failed to MMC_SEND_STATUS (%x=%d), retcode = %d\n", index, value, st);
     }
 
     return ZX_OK;
@@ -52,7 +52,7 @@
     // Switch the card to the new bus width
     zx_status_t st = mmc_do_switch(dev, MMC_EXT_CSD_BUS_WIDTH, mmc_ext_csd_bus_width);
     if (st != ZX_OK) {
-        zxlogf(ERROR, "mmc: failed to switch bus width to EXT_CSD %d, retcode = %d\n",
+        printf("mmc: failed to switch bus width to EXT_CSD %d, retcode = %d\n",
                mmc_ext_csd_bus_width, st);
         return ZX_ERR_INTERNAL;
     }
@@ -60,7 +60,7 @@
     if (bus_width != dev->bus_width) {
         // Switch the host to the new bus width
         if ((st = sdmmc_set_bus_width(&dev->host, bus_width)) != ZX_OK) {
-            zxlogf(ERROR, "mmc: failed to switch the host bus width to %d, retcode = %d\n",
+            printf("mmc: failed to switch the host bus width to %d, retcode = %d\n",
                    bus_width, st);
             return ZX_ERR_INTERNAL;
         }
@@ -109,13 +109,13 @@
 
     zx_status_t st = mmc_do_switch(dev, MMC_EXT_CSD_HS_TIMING, ext_csd_timing);
     if (st != ZX_OK) {
-        zxlogf(ERROR, "mmc: failed to switch device timing to %d\n", new_timing);
+        printf("mmc: failed to switch device timing to %d\n", new_timing);
         return st;
     }
 
     // Switch the host timing
     if ((st = sdmmc_set_timing(&dev->host, new_timing)) != ZX_OK) {
-        zxlogf(ERROR, "mmc: failed to switch host timing to %d\n", new_timing);
+        printf("mmc: failed to switch host timing to %d\n", new_timing);
         return st;
     }
 
@@ -126,7 +126,7 @@
 static zx_status_t mmc_switch_freq(sdmmc_device_t* dev, uint32_t new_freq) {
     zx_status_t st;
     if ((st = sdmmc_set_bus_freq(&dev->host, new_freq)) != ZX_OK) {
-        zxlogf(ERROR, "mmc: failed to set host bus frequency, retcode = %d\n", st);
+        printf("mmc: failed to set host bus frequency, retcode = %d\n", st);
         return st;
     }
     dev->clock_rate = new_freq;
@@ -148,13 +148,14 @@
     uint8_t spec_vrsn = (raw_csd[MMC_CSD_SPEC_VERSION] >> 2) & 0xf;
     // Only support spec version > 4.0
     if (spec_vrsn < MMC_CID_SPEC_VRSN_40) {
+        printf("%s: unsupported version %d\n", __FUNCTION__, spec_vrsn);
         return ZX_ERR_NOT_SUPPORTED;
     }
 
-    zxlogf(SPEW, "mmc: CSD version %u spec version %u\n",
+    printf("mmc: CSD version %u spec version %u\n",
            (raw_csd[MMC_CSD_SPEC_VERSION] >> 6) & 0x3, spec_vrsn);
     if (driver_get_log_flags() & DDK_LOG_SPEW) {
-        zxlogf(SPEW, "CSD:\n");
+        printf("CSD:\n");
         hexdump8_ex(raw_csd, 16, 0);
     }
 
@@ -163,14 +164,14 @@
                       (raw_csd[MMC_CSD_SIZE_START + 1] << 2) |
                       ((raw_csd[MMC_CSD_SIZE_START + 2] & 0x3) << 10);
     if (c_size != 0xfff) {
-        zxlogf(ERROR, "mmc: unsupported C_SIZE 0x%04x\n", c_size);
+        printf("mmc: unsupported C_SIZE 0x%04x\n", c_size);
         return ZX_ERR_NOT_SUPPORTED;
     }
     return ZX_OK;
 }
 
 static zx_status_t mmc_decode_ext_csd(sdmmc_device_t* dev, const uint8_t* raw_ext_csd) {
-    zxlogf(SPEW, "mmc: EXT_CSD version %u CSD version %u\n", raw_ext_csd[192], raw_ext_csd[194]);
+    printf("mmc: EXT_CSD version %u CSD version %u\n", raw_ext_csd[192], raw_ext_csd[194]);
 
     // Get the capacity for the card
     uint32_t sectors = (raw_ext_csd[212] << 0) | (raw_ext_csd[213] << 8) |
@@ -178,7 +179,7 @@
     dev->block_info.block_count = sectors * MMC_SECTOR_SIZE / MMC_BLOCK_SIZE;
     dev->block_info.block_size = (uint32_t)MMC_BLOCK_SIZE;
 
-    zxlogf(TRACE, "mmc: found card with capacity = %" PRIu64 "B\n",
+    printf("mmc: found card with capacity = %" PRIu64 "B\n",
            dev->block_info.block_count * dev->block_info.block_size);
 
     return ZX_OK;
@@ -213,23 +214,23 @@
     // Query OCR
     uint32_t ocr = 0;
     if ((st = mmc_send_op_cond(dev, ocr, &ocr)) != ZX_OK) {
-        zxlogf(ERROR, "mmc: MMC_SEND_OP_COND failed, retcode = %d\n", st);
+        printf("mmc: MMC_SEND_OP_COND failed, retcode = %d\n", st);
         goto err;
     }
 
     // Indicate sector mode
     if ((st = mmc_send_op_cond(dev, ocr, &ocr)) != ZX_OK) {
-        zxlogf(ERROR, "mmc: MMC_SEND_OP_COND failed, retcode = %d\n", st);
+        printf("mmc: MMC_SEND_OP_COND failed, retcode = %d\n", st);
         goto err;
     }
 
     // Get CID from card
     // Only supports 1 card currently so no need to loop
     if ((st = mmc_all_send_cid(dev, dev->raw_cid)) != ZX_OK) {
-        zxlogf(ERROR, "mmc: MMC_ALL_SEND_CID failed, retcode = %d\n", st);
+        printf("mmc: MMC_ALL_SEND_CID failed, retcode = %d\n", st);
         goto err;
     }
-    zxlogf(SPEW, "mmc: MMC_ALL_SEND_CID cid 0x%08x 0x%08x 0x%08x 0x%08x\n",
+    printf("mmc: MMC_ALL_SEND_CID cid 0x%08x 0x%08x 0x%08x 0x%08x\n",
         dev->raw_cid[0],
         dev->raw_cid[1],
         dev->raw_cid[2],
@@ -239,34 +240,39 @@
 
     // Set relative card address
     if ((st = mmc_set_relative_addr(dev, 1)) != ZX_OK) {
-        zxlogf(ERROR, "mmc: MMC_SET_RELATIVE_ADDR failed, retcode = %d\n", st);
+        printf("mmc: MMC_SET_RELATIVE_ADDR failed, retcode = %d\n", st);
         goto err;
     }
     dev->rca = 1;
 
     // Read CSD register
     if ((st = mmc_send_csd(dev, dev->raw_csd)) != ZX_OK) {
-        zxlogf(ERROR, "mmc: MMC_SEND_CSD failed, retcode = %d\n", st);
+        printf("mmc: MMC_SEND_CSD failed, retcode = %d\n", st);
         goto err;
     }
 
+    printf("mmc: 0x%x 0x%x 0x%x 0x%x\n",
+        dev->raw_csd[0], dev->raw_csd[1], dev->raw_csd[2], dev->raw_csd[3]);
+
     if ((st = mmc_decode_csd(dev, (const uint8_t*)dev->raw_csd)) != ZX_OK) {
+        printf("%s %d\n", __FUNCTION__, __LINE__);
         goto err;
     }
 
     // Select the card
     if ((st = mmc_select_card(dev)) != ZX_OK) {
-        zxlogf(ERROR, "mmc: MMC_SELECT_CARD failed, retcode = %d\n", st);
+        printf("mmc: MMC_SELECT_CARD failed, retcode = %d\n", st);
         goto err;
     }
 
     // Read extended CSD register
     if ((st = mmc_send_ext_csd(dev, dev->raw_ext_csd)) != ZX_OK) {
-        zxlogf(ERROR, "mmc: MMC_SEND_EXT_CSD failed, retcode = %d\n", st);
+        printf("mmc: MMC_SEND_EXT_CSD failed, retcode = %d\n", st);
         goto err;
     }
 
     if ((st = mmc_decode_ext_csd(dev, (const uint8_t*)dev->raw_ext_csd)) != ZX_OK) {
+        printf("%s %d\n", __FUNCTION__, __LINE__);
         goto err;
     }
 
@@ -279,7 +285,7 @@
         // Switch to 1.8V signal voltage
         sdmmc_voltage_t new_voltage = SDMMC_VOLTAGE_180;
         if ((st = sdmmc_set_signal_voltage(&dev->host, new_voltage)) != ZX_OK) {
-            zxlogf(ERROR, "mmc: failed to switch to 1.8V signalling, retcode = %d\n", st);
+            printf("mmc: failed to switch to 1.8V signalling, retcode = %d\n", st);
             goto err;
         }
         dev->signal_voltage = new_voltage;
@@ -289,47 +295,56 @@
         // Must perform tuning at HS200 first if HS400 is supported
         if (mmc_supports_hs200(dev) && dev->bus_width != SDMMC_BUS_WIDTH_1) {
             if ((st = mmc_switch_timing(dev, SDMMC_TIMING_HS200)) != ZX_OK) {
+                printf("%s %d\n", __FUNCTION__, __LINE__);
                 goto err;
             }
 
             if ((st = mmc_switch_freq(dev, FREQ_200MHZ)) != ZX_OK) {
+                printf("%s %d\n", __FUNCTION__, __LINE__);
                 goto err;
             }
 
             if ((st = sdmmc_perform_tuning(&dev->host)) != ZX_OK) {
-                zxlogf(ERROR, "mmc: tuning failed %d\n", st);
+                printf("mmc: tuning failed %d\n", st);
                 goto err;
             }
 
             if (mmc_supports_hs400(dev) && dev->bus_width == SDMMC_BUS_WIDTH_8) {
                 if ((st = mmc_switch_timing(dev, SDMMC_TIMING_HS)) != ZX_OK) {
+                    printf("%s %d\n", __FUNCTION__, __LINE__);
                     goto err;
                 }
 
                 if ((st = mmc_switch_freq(dev, FREQ_52MHZ)) != ZX_OK) {
+                    printf("%s %d\n", __FUNCTION__, __LINE__);
                     goto err;
                 }
 
                 if ((st = mmc_set_bus_width(dev, SDMMC_BUS_WIDTH_8,
                                             MMC_EXT_CSD_BUS_WIDTH_8_DDR)) != ZX_OK) {
+                    printf("%s %d\n", __FUNCTION__, __LINE__);
                     goto err;
                 }
 
                 if ((st = mmc_switch_timing(dev, SDMMC_TIMING_HS400)) != ZX_OK) {
+                    printf("%s %d\n", __FUNCTION__, __LINE__);
                     goto err;
                 }
 
                 if ((st = mmc_switch_freq(dev, FREQ_200MHZ)) != ZX_OK) {
+                    printf("%s %d\n", __FUNCTION__, __LINE__);
                     goto err;
                 }
             }
         } else {
             if ((st = mmc_switch_timing(dev, SDMMC_TIMING_HS)) != ZX_OK) {
+                printf("%s %d\n", __FUNCTION__, __LINE__);
                 goto err;
             }
 
             if (mmc_supports_hsddr(dev) && (dev->bus_width != SDMMC_BUS_WIDTH_1)) {
                 if ((st = mmc_switch_timing(dev, SDMMC_TIMING_HSDDR)) != ZX_OK) {
+                    printf("%s %d\n", __FUNCTION__, __LINE__);
                     goto err;
                 }
 
@@ -337,23 +352,26 @@
                                             MMC_EXT_CSD_BUS_WIDTH_4_DDR :
                                             MMC_EXT_CSD_BUS_WIDTH_8_DDR;
                 if ((st = mmc_set_bus_width(dev, dev->bus_width, mmc_bus_width)) != ZX_OK) {
+                    printf("%s %d\n", __FUNCTION__, __LINE__);
                     goto err;
                 }
             }
 
             if ((st = mmc_switch_freq(dev, FREQ_52MHZ)) != ZX_OK) {
+                printf("%s %d\n", __FUNCTION__, __LINE__);
                 goto err;
             }
         }
     } else {
         // Set the bus frequency to legacy timing
         if ((st = mmc_switch_freq(dev, FREQ_25MHZ)) != ZX_OK) {
+            printf("%s %d\n", __FUNCTION__, __LINE__);
             goto err;
         }
         dev->timing = SDMMC_TIMING_LEGACY;
     }
 
-    zxlogf(INFO, "mmc: initialized mmc @ %u mhz, bus width %d, timing %d\n",
+    printf("mmc: initialized mmc @ %u mhz, bus width %d, timing %d\n",
             dev->clock_rate, dev->bus_width, dev->timing);
 
 err:
diff --git a/system/dev/board/imx8mevk/imx8mevk-sdhci.c b/system/dev/board/imx8mevk/imx8mevk-sdhci.c
new file mode 100644
index 0000000..728de7e
--- /dev/null
+++ b/system/dev/board/imx8mevk/imx8mevk-sdhci.c
@@ -0,0 +1,69 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/platform-defs.h>
+
+#include <soc/imx8m/imx8m.h>
+#include <soc/imx8m/imx8m-hw.h>
+#include <soc/imx8m/imx8m-iomux.h>
+#include <soc/imx8m/imx8m-gpio.h>
+#include <limits.h>
+
+#include "imx8mevk.h"
+
+static const pbus_mmio_t sdhci_mmios[] = {
+    {
+        // eMMC Interface
+        .base = IMX8M_AIPS_USDHC1_BASE,
+        .length = IMX8M_AIPS_LENGTH,
+    },
+    {
+        // uSD Interface
+        .base = IMX8M_AIPS_USDHC2_BASE,
+        .length = IMX8M_AIPS_LENGTH,
+    },
+};
+
+static const pbus_irq_t sdhci_irqs[] = {
+    {
+        .irq = IMX8M_A53_INTR_USDHC1,
+    },
+    {
+        .irq = IMX8M_A53_INTR_USDHC2,
+    },
+};
+
+static const pbus_bti_t sdhci_btis[] = {
+    {
+        .iommu_index = 0,
+        .bti_id = BTI_SDHCI,
+    },
+};
+
+static pbus_dev_t sdhci_dev = {
+    .name = "sdhci",
+    .vid = PDEV_VID_NXP,
+    .pid = PDEV_PID_IMX8MEVK,
+    .did = PDEV_DID_IMX_SDHCI,
+    .mmios = sdhci_mmios,
+    .mmio_count = countof(sdhci_mmios),
+    .irqs = sdhci_irqs,
+    .irq_count = countof(sdhci_irqs),
+    .btis = sdhci_btis,
+    .bti_count = countof(sdhci_btis),
+};
+
+
+zx_status_t imx8m_sdhci_init(imx8mevk_bus_t* bus) {
+    zx_status_t status = pbus_device_add(&bus->pbus, &sdhci_dev, 0);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: pbus_device_add failed %d\n", __FUNCTION__, status);
+        return status;
+    }
+
+    return ZX_OK;
+}
\ No newline at end of file
diff --git a/system/dev/board/imx8mevk/imx8mevk.c b/system/dev/board/imx8mevk/imx8mevk.c
index 7a6805a..51f5dec 100644
--- a/system/dev/board/imx8mevk/imx8mevk.c
+++ b/system/dev/board/imx8mevk/imx8mevk.c
@@ -76,6 +76,11 @@
         goto fail;
     }
 
+    if ((status = imx8m_sdhci_init(bus)) != ZX_OK) {
+        zxlogf(ERROR, "%s: failed %d\n", __FUNCTION__, status);
+        goto fail;
+    }
+
     return ZX_OK;
 
 fail:
diff --git a/system/dev/board/imx8mevk/imx8mevk.h b/system/dev/board/imx8mevk/imx8mevk.h
index 2389eb8..f75141e 100644
--- a/system/dev/board/imx8mevk/imx8mevk.h
+++ b/system/dev/board/imx8mevk/imx8mevk.h
@@ -17,6 +17,7 @@
 {
     BTI_BOARD,
     BTI_USB_XHCI,
+    BTI_SDHCI,
 };
 
 typedef struct {
@@ -33,4 +34,4 @@
 
 zx_status_t imx8m_gpio_init(imx8mevk_bus_t* bus);
 zx_status_t imx_usb_init(imx8mevk_bus_t* bus);
-
+zx_status_t imx8m_sdhci_init(imx8mevk_bus_t* bus);
diff --git a/system/dev/board/imx8mevk/rules.mk b/system/dev/board/imx8mevk/rules.mk
index 24f8500..98e7bc2 100644
--- a/system/dev/board/imx8mevk/rules.mk
+++ b/system/dev/board/imx8mevk/rules.mk
@@ -12,6 +12,7 @@
     $(LOCAL_DIR)/imx8mevk.c \
     $(LOCAL_DIR)/imx8mevk-gpio.c \
     $(LOCAL_DIR)/imx8mevk-usb.c \
+    $(LOCAL_DIR)/imx8mevk-sdhci.c
 
 
 MODULE_STATIC_LIBS := \
diff --git a/system/ulib/ddk/include/ddk/protocol/platform-defs.h b/system/ulib/ddk/include/ddk/protocol/platform-defs.h
index 7a578cf..4f43494 100644
--- a/system/ulib/ddk/include/ddk/protocol/platform-defs.h
+++ b/system/ulib/ddk/include/ddk/protocol/platform-defs.h
@@ -80,5 +80,6 @@
 #define PDEV_PID_IMX8MEVK           1
 
 #define PDEV_DID_IMX_GPIO           1
+#define PDEV_DID_IMX_SDHCI          2
 
 __END_CDECLS;