[dev][mt-usb] First cut at USB driver for Mediatek mt8167s

Peripheral role only. Support for host role comming later.
Uses fifos for IO. DMA support not yet implemented.

TEST: netboot zircon over CDC ethernet function

Change-Id: Id52ee06a138412837893e15e2862b82d9b3afaa4
diff --git a/system/dev/board/mt8167s_ref/bind.c b/system/dev/board/mt8167s_ref/bind.c
deleted file mode 100644
index 3b7e400..0000000
--- a/system/dev/board/mt8167s_ref/bind.c
+++ /dev/null
@@ -1,20 +0,0 @@
-// 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/binding.h>
-#include <ddk/driver.h>
-#include <ddk/platform-defs.h>
-
-extern zx_status_t mt8167_bind(void* ctx, zx_device_t* parent);
-
-static zx_driver_ops_t mt8167_driver_ops = {
-    .version = DRIVER_OPS_VERSION,
-    .bind = mt8167_bind,
-};
-
-ZIRCON_DRIVER_BEGIN(mt8167, mt8167_driver_ops, "zircon", "0.1", 3)
-    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PBUS),
-    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_MEDIATEK),
-    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_MEDIATEK_8167S_REF),
-ZIRCON_DRIVER_END(mt8167)
diff --git a/system/dev/board/mt8167s_ref/mt8167-usb.cpp b/system/dev/board/mt8167s_ref/mt8167-usb.cpp
new file mode 100644
index 0000000..f95fc41
--- /dev/null
+++ b/system/dev/board/mt8167s_ref/mt8167-usb.cpp
@@ -0,0 +1,86 @@
+// 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/mmio-buffer.h>
+#include <ddk/platform-defs.h>
+#include <hw/reg.h>
+#include <lib/zx/handle.h>
+
+#include <soc/mt8167/mt8167-hw.h>
+
+#include "mt8167.h"
+
+namespace board_mt8167 {
+
+static const pbus_mmio_t usb_mmios[] = {
+    {
+        .base = MT8167_USB0_BASE,
+        .length = MT8167_USB0_LENGTH,
+    },
+    {
+        .base = MT8167_USBPHY_BASE,
+        .length = MT8167_USBPHY_LENGTH,
+    },
+};
+
+static const pbus_irq_t usb_irqs[] = {
+    {
+        .irq = MT8167_IRQ_USB_MCU,
+        .mode = ZX_INTERRUPT_MODE_LEVEL_HIGH,
+    },
+};
+
+static const pbus_bti_t usb_btis[] = {
+    {
+        .iommu_index = 0,
+        .bti_id = BTI_USB,
+    },
+};
+
+static pbus_dev_t usb_dev = [](){
+    pbus_dev_t dev;
+    dev.name = "mt-usb";
+    dev.vid = PDEV_VID_MEDIATEK;
+    dev.did = PDEV_DID_MEDIATEK_USB;
+    dev.mmio_list = usb_mmios;
+    dev.mmio_count = countof(usb_mmios);
+    dev.irq_list = usb_irqs;
+    dev.irq_count = countof(usb_irqs);
+    dev.bti_list = usb_btis;
+    dev.bti_count = countof(usb_btis);
+    return dev;
+}();
+
+#define CLK_GATING_CTRL1_CLR (0x084 / sizeof(uint32_t))
+#define SET_USB_SW_CG (1U << 13)
+
+zx_status_t Mt8167::UsbInit() {
+    // TODO: move to clock driver when we have one
+    mmio_buffer_t buf;
+    zx_status_t status;
+
+    status = mmio_buffer_init_physical(&buf, MT8167_XO_BASE, MT8167_XO_SIZE,
+                                       get_root_resource(), ZX_CACHE_POLICY_UNCACHED_DEVICE);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    volatile uint32_t* xo_base = static_cast<uint32_t*>(buf.vaddr);
+
+    writel(SET_USB_SW_CG, xo_base + CLK_GATING_CTRL1_CLR);
+
+    mmio_buffer_release(&buf);
+
+    status = pbus_.DeviceAdd(&usb_dev);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: DeviceAdd failed %d\n", __func__, status);
+        return status;
+    }
+
+    return ZX_OK;
+}
+
+} // namespace board_mt8167
diff --git a/system/dev/board/mt8167s_ref/mt8167.cpp b/system/dev/board/mt8167s_ref/mt8167.cpp
index 6da9dbf..5fa2b34 100644
--- a/system/dev/board/mt8167s_ref/mt8167.cpp
+++ b/system/dev/board/mt8167s_ref/mt8167.cpp
@@ -56,6 +56,7 @@
         zxlogf(ERROR, "SocInit() failed\n");
         return -1;
     }
+    // Load protocol implementation drivers first.
     if (GpioInit() != ZX_OK) {
         zxlogf(ERROR, "GpioInit() failed\n");
         return -1;
@@ -64,6 +65,8 @@
         zxlogf(ERROR, "I2cInit() failed\n");
         return -1;
     }
+
+    // Then the platform device drivers.
     if (EmmcInit() != ZX_OK) {
         zxlogf(ERROR, "EmmcInit() failed\n");
         return -1;
@@ -76,6 +79,10 @@
         zxlogf(ERROR, "DisplayInit() failed\n");
         return -1;
     }
+    if (UsbInit() != ZX_OK) {
+        zxlogf(ERROR, "UsbInit() failed\n");
+        return -1;
+    }
 
     return 0;
 }
@@ -97,8 +104,22 @@
     delete this;
 }
 
-} // namespace board_mt8167
-
 zx_status_t mt8167_bind(void* ctx, zx_device_t* parent) {
     return board_mt8167::Mt8167::Create(parent);
 }
+
+static zx_driver_ops_t driver_ops = [](){
+    zx_driver_ops_t ops;
+    ops.version = DRIVER_OPS_VERSION;
+    ops.bind = mt8167_bind;
+    return ops;
+}();
+
+} // namespace board_mt8167
+
+ZIRCON_DRIVER_BEGIN(mt8167, board_mt8167::driver_ops, "zircon", "0.1", 3)
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PBUS),
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_MEDIATEK),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_MEDIATEK_8167S_REF),
+ZIRCON_DRIVER_END(mt8167)
+
diff --git a/system/dev/board/mt8167s_ref/mt8167.h b/system/dev/board/mt8167s_ref/mt8167.h
index c6d8f4c..c2d1174 100644
--- a/system/dev/board/mt8167s_ref/mt8167.h
+++ b/system/dev/board/mt8167s_ref/mt8167.h
@@ -17,8 +17,10 @@
 
 // BTI IDs for our devices
 enum {
+    BTI_BOARD,
     BTI_DISPLAY,
     BTI_EMMC,
+    BTI_USB,
 };
 
 class Mt8167;
@@ -45,6 +47,7 @@
     zx_status_t DisplayInit();
     zx_status_t I2cInit();
     zx_status_t ButtonsInit();
+    zx_status_t UsbInit();
     int Thread();
 
     ddk::PBusProtocolProxy pbus_;
diff --git a/system/dev/board/mt8167s_ref/rules.mk b/system/dev/board/mt8167s_ref/rules.mk
index ac5238b..77d7d29 100644
--- a/system/dev/board/mt8167s_ref/rules.mk
+++ b/system/dev/board/mt8167s_ref/rules.mk
@@ -9,7 +9,6 @@
 MODULE_TYPE := driver
 
 MODULE_SRCS := \
-    $(LOCAL_DIR)/bind.c \
     $(LOCAL_DIR)/mt8167.cpp \
     $(LOCAL_DIR)/mt8167-emmc.cpp \
     $(LOCAL_DIR)/mt8167-soc.cpp \
@@ -17,6 +16,7 @@
     $(LOCAL_DIR)/mt8167-display.cpp \
     $(LOCAL_DIR)/mt8167-i2c.cpp \
     $(LOCAL_DIR)/mt8167-buttons.cpp \
+    $(LOCAL_DIR)/mt8167-usb.cpp \
 
 MODULE_STATIC_LIBS := \
     system/dev/lib/mt8167 \
diff --git a/system/dev/lib/mt8167/include/soc/mt8167/mt8167-hw.h b/system/dev/lib/mt8167/include/soc/mt8167/mt8167-hw.h
index ab703f9..29eced3 100644
--- a/system/dev/lib/mt8167/include/soc/mt8167/mt8167-hw.h
+++ b/system/dev/lib/mt8167/include/soc/mt8167/mt8167-hw.h
@@ -29,6 +29,12 @@
 #define MT8167_I2C2_BASE                                    0x1100b000
 #define MT8167_I2C2_SIZE                                    0x8c
 
+#define MT8167_USB0_BASE                                    0x11100000
+#define MT8167_USB0_LENGTH                                  0x1000
+
+#define MT8167_USBPHY_BASE                                  0x11110800
+#define MT8167_USBPHY_LENGTH                                0x800
+
 #define MT8167_MSDC0_BASE                                   0x11120000
 #define MT8167_MSDC0_SIZE                                   0x22c
 
diff --git a/system/dev/usb/mt-usb/mt-usb-phy-regs.h b/system/dev/usb/mt-usb/mt-usb-phy-regs.h
new file mode 100644
index 0000000..4b15946
--- /dev/null
+++ b/system/dev/usb/mt-usb/mt-usb-phy-regs.h
@@ -0,0 +1,198 @@
+// 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
+
+#include <hwreg/bitfields.h>
+#include <zircon/types.h>
+
+namespace mt_usb {
+
+// USB20 PHYA Common 0 Register
+class USBPHYACR0 : public hwreg::RegisterBase<USBPHYACR0, uint32_t> {
+public:
+    DEF_FIELD(30, 28, mpx_out_sel);
+    DEF_FIELD(26, 24, tx_ph_rot_sel);
+    DEF_FIELD(22, 20, pll_diven);
+    DEF_BIT(18, pll_br);
+    DEF_BIT(17, pll_bp);
+    DEF_BIT(16, pll_blp);
+    DEF_BIT(15, usbpll_force_on);
+    DEF_FIELD(14, 8, pll_fbdiv);
+    DEF_FIELD(7, 6, pll_prediv);
+    DEF_BIT(5, intr_en);
+    DEF_BIT(4, ref_en);
+    DEF_FIELD(3, 2, bgr_div);
+    DEF_BIT(1, chp_en);
+    DEF_BIT(0, bgr_en);
+    static auto Get() { return hwreg::RegisterAddr<USBPHYACR0>(0x00); }
+};
+
+// USB20 PHYA Common 1 Register
+class USBPHYACR1 : public hwreg::RegisterBase<USBPHYACR1, uint32_t> {
+public:public:
+    DEF_FIELD(31, 24, clkref_rev_7_0);
+    DEF_FIELD(23, 19, intr_cal);
+    DEF_FIELD(18, 16, otg_vbusth);
+    DEF_FIELD(14, 12, vrt_vref_sel);
+    DEF_FIELD(10, 8, term_vref_sel);
+    DEF_FIELD(7, 0, mpx_sel);
+    static auto Get() { return hwreg::RegisterAddr<USBPHYACR1>(0x04); }
+};
+
+// USB20 PHYA Common 2 Register
+class USBPHYACR2 : public hwreg::RegisterBase<USBPHYACR2, uint32_t> {
+public:public:
+    DEF_FIELD(7, 0, clkref_rev_18_8);
+    static auto Get() { return hwreg::RegisterAddr<USBPHYACR2>(0x08); }
+};
+
+// USB20 PHYA Common 4 Register
+class USBPHYACR4 : public hwreg::RegisterBase<USBPHYACR4, uint32_t> {
+public:public:
+    DEF_BIT(31, dp_abist_source_en);
+    DEF_FIELD(27, 24, dp_abist_sele);
+    DEF_BIT(16, icusb_en);
+    DEF_FIELD(14, 12, ls_cr);
+    DEF_FIELD(10, 8, fs_cr);
+    DEF_FIELD(6, 4, ls_sr);
+    DEF_FIELD(2, 0, fs_sr);
+    static auto Get() { return hwreg::RegisterAddr<USBPHYACR4>(0x10); }
+};
+
+// USB20 PHYA Common 5 Register
+class USBPHYACR5 : public hwreg::RegisterBase<USBPHYACR5, uint32_t> {
+public:public:
+    DEF_BIT(28, disc_fit_en);
+    DEF_FIELD(27, 26, init_sq_en_dg);
+    DEF_FIELD(25, 24, hstx_tmode_sel);
+    DEF_FIELD(23, 22, sqd);
+    DEF_FIELD(21, 20, discd);
+    DEF_BIT(19, hstx_tmode_en);
+    DEF_BIT(18, phyd_monen);
+    DEF_BIT(17, inlpbk_en);
+    DEF_BIT(16, chirp_en);
+    DEF_BIT(15, hstx_srcal_en);
+    DEF_FIELD(14, 12, hstx_srctrl);
+    DEF_BIT(11, hs_100u_u3_en);
+    DEF_BIT(10, gbias_enb);
+    DEF_BIT(7, dm_abist_source_en);
+    DEF_FIELD(3, 0, dm_abist_sele);
+    static auto Get() { return hwreg::RegisterAddr<USBPHYACR5>(0x14); }
+};
+
+// USB20 PHYA Common 6 Register
+class USBPHYACR6 : public hwreg::RegisterBase<USBPHYACR6, uint32_t> {
+public:public:
+    DEF_FIELD(31, 24, phy_rev_7_0);
+    DEF_BIT(23, bc11_sw_en);
+    DEF_BIT(22, sr_clk_sel);
+    DEF_BIT(20, otg_vbuscmp_en);
+    DEF_BIT(19, otg_abist_en);
+    DEF_FIELD(18, 16, otg_abist_sele);
+    DEF_FIELD(13, 12, hsrx_mmode_sele);
+    DEF_FIELD(10, 9, hsrx_bias_en_sel);
+    DEF_BIT(8, hsrx_tmode_en);
+    DEF_FIELD(7, 4, discth);
+    DEF_FIELD(3, 0, sqth);
+    static auto Get() { return hwreg::RegisterAddr<USBPHYACR6>(0x18); }
+};
+
+// USB20 PHYA Control 3 Register
+class U2PHYACR3 : public hwreg::RegisterBase<U2PHYACR3, uint32_t> {
+public:public:
+    DEF_FIELD(31, 28, hstx_dbist);
+    DEF_BIT(26, hstx_bist_en);
+    DEF_FIELD(25, 24, hstx_i_en_mode);
+    DEF_BIT(19, usb11_tmode_en);
+    DEF_BIT(18, tmode_fs_ls_tx_en);
+    DEF_BIT(17, tmode_fx_ls_rcv_en);
+    DEF_BIT(16, tmode_fs_ls_mode);
+    DEF_FIELD(14, 13, hs_term_en_mode);
+    DEF_BIT(12, pupd_bist_en);
+    DEF_BIT(11, en_pu_dm);
+    DEF_BIT(10, en_pd_dm);
+    DEF_BIT(9, en_pu_dp);
+    DEF_BIT(8, en_pd_dp);
+    static auto Get() { return hwreg::RegisterAddr<U2PHYACR3>(0x1c); }
+};
+
+// USB20 PHYA Control 4 Register
+class U2PHYACR4 : public hwreg::RegisterBase<U2PHYACR4, uint32_t> {
+public:public:
+    DEF_BIT(18, dp_100k_mode);
+    DEF_BIT(17, dm_100k_en);
+    DEF_BIT(16, dp_100k_en);
+    DEF_BIT(15, usb20_gpio_dm_i);
+    DEF_BIT(14, usb20_gpio_dp_i);
+    DEF_BIT(13, usb20_gpio_dm_oe);
+    DEF_BIT(12, usb20_gpio_dp_oe);
+    DEF_BIT(9, usb20_gpio_ctl);
+    DEF_BIT(8, usb20_gpio_mode);
+    DEF_BIT(5, tx_bias_en);
+    DEF_BIT(4, tx_vcmpdn_en);
+    DEF_FIELD(3, 2, hs_sq_en_mode);
+    DEF_FIELD(1, 0, hs_rcv_en_mode);
+    static auto Get() { return hwreg::RegisterAddr<U2PHYACR4>(0x20); }
+};
+
+// USB20 PHYD Control UTMI 0 Register
+class U2PHYDTM0 : public hwreg::RegisterBase<U2PHYDTM0, uint32_t> {
+public:public:
+    DEF_FIELD(31, 30, rg_uart_mode);
+    DEF_BIT(29, force_uart_i);
+    DEF_BIT(28, force_uart_bias_en);
+    DEF_BIT(27, force_uart_tx_oe);
+    DEF_BIT(26, force_uart_en);
+    DEF_BIT(25, force_usb_clken);
+    DEF_BIT(24, force_drvvbus);
+    DEF_BIT(23, force_datain);
+    DEF_BIT(22, force_txvalid);
+    DEF_BIT(21, force_dm_pulldown);
+    DEF_BIT(20, force_dp_pulldown);
+    DEF_BIT(19, force_xcvsel);
+    DEF_BIT(18, force_suspendm);
+    DEF_BIT(17, force_termsel);
+    DEF_BIT(16, force_opmode);
+    DEF_BIT(15, utmi_muxsel);
+    DEF_BIT(14, rg_reset);
+    DEF_FIELD(13, 10, rg_datain);
+    DEF_BIT(9, rg_txvalidh);
+    DEF_BIT(8, rg_txvalid);
+    DEF_BIT(7, rg_dmpulldown);
+    DEF_BIT(6, rg_dppulldown);
+    DEF_FIELD(5, 4, rg_xcvrsel);
+    DEF_BIT(3, rg_suspendm);
+    DEF_BIT(2, rg_termsel);
+    DEF_FIELD(1, 0, rg_opmode);
+    static auto Get() { return hwreg::RegisterAddr<U2PHYDTM0>(0x68); }
+};
+
+// USB20 PHYD Control UTMI 1 Register
+class U2PHYDTM1 : public hwreg::RegisterBase<U2PHYDTM1, uint32_t> {
+public:public:
+    DEF_BIT(31, prbs7_en);
+    DEF_FIELD(29, 24, prbs7_bitcnt);
+    DEF_BIT(23, clk48m_en);
+    DEF_BIT(22, clk60m_en);
+    DEF_BIT(19, rg_uart_i);
+    DEF_BIT(18, rg_uart_bias_en);
+    DEF_BIT(17, rg_uart_tx_oe);
+    DEF_BIT(16, rg_uart_en);
+    DEF_BIT(13, force_vbusvalid);
+    DEF_BIT(12, force_sessend);
+    DEF_BIT(11, force_bvalid);
+    DEF_BIT(10, force_avalid);
+    DEF_BIT(9, force_iddig);
+    DEF_BIT(8, force_idpullup);
+    DEF_BIT(5, rg_vbusvalid);
+    DEF_BIT(4, rg_sessend);
+    DEF_BIT(3, rg_bvalid);
+    DEF_BIT(2, rg_avalid);
+    DEF_BIT(1, rg_iddig);
+    DEF_BIT(0, rg_idpullup);
+    static auto Get() { return hwreg::RegisterAddr<U2PHYDTM1>(0x6c); }
+};
+
+} // namespace mt_usb
diff --git a/system/dev/usb/mt-usb/mt-usb-regs.h b/system/dev/usb/mt-usb/mt-usb-regs.h
new file mode 100644
index 0000000..ab09d4e
--- /dev/null
+++ b/system/dev/usb/mt-usb/mt-usb-regs.h
@@ -0,0 +1,546 @@
+// 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
+
+#include <hwreg/bitfields.h>
+#include <zircon/types.h>
+
+namespace mt_usb {
+
+// Function Address Register
+class FADDR : public hwreg::RegisterBase<FADDR, uint8_t> {
+public:
+    DEF_FIELD(6, 0, function_address);
+    static auto Get() { return hwreg::RegisterAddr<FADDR>(0x00); }
+};
+
+// Power Management Register (peripheral mode)
+class POWER_PERI : public hwreg::RegisterBase<POWER_PERI, uint8_t, hwreg::EnablePrinter> {
+public:
+    DEF_BIT(7, isoupdate);
+    DEF_BIT(6, softconn);
+    DEF_BIT(5, hsenab);
+    DEF_BIT(4, hsmode);
+    DEF_BIT(3, reset);
+    DEF_BIT(2, resume);
+    DEF_BIT(1, suspendmode);
+    DEF_BIT(0, enablesuspendm);
+    static auto Get() { return hwreg::RegisterAddr<POWER_PERI>(0x01); }
+};
+
+// Power Management Register (host mode)
+class POWER_HOST : public hwreg::RegisterBase<POWER_HOST, uint8_t> {
+public:
+    DEF_BIT(5, hsenab);
+    DEF_BIT(4, hsmode);
+    DEF_BIT(3, reset);
+    DEF_BIT(2, resume);
+    DEF_BIT(1, suspendmode);
+    DEF_BIT(0, enablesuspendm);
+    static auto Get() { return hwreg::RegisterAddr<POWER_HOST>(0x01); }
+};
+
+// TX Interrupt Status Register
+class INTRTX : public hwreg::RegisterBase<INTRTX, uint16_t, hwreg::EnablePrinter> {
+public:
+    // bit field, one bit per TX endpoint
+    DEF_FIELD(15, 0, ep_tx);
+    static auto Get() { return hwreg::RegisterAddr<INTRTX>(0x02); }
+};
+
+// RX Interrupt Status Register
+class INTRRX : public hwreg::RegisterBase<INTRRX, uint16_t, hwreg::EnablePrinter> {
+public:
+    // bit field, one bit per RX endpoint (endpoints 1 - 15)
+    DEF_FIELD(15, 0, ep_rx);
+    static auto Get() { return hwreg::RegisterAddr<INTRRX>(0x4); }
+};
+
+// TX Interrupt Enable Register
+class INTRTXE : public hwreg::RegisterBase<INTRTXE, uint16_t, hwreg::EnablePrinter> {
+public:
+    // bit field, one bit per TX endpoint
+    DEF_FIELD(15, 0, ep_tx);
+    static auto Get() { return hwreg::RegisterAddr<INTRTXE>(0x06); }
+};
+
+// RX Interrupt Enable Register
+class INTRRXE : public hwreg::RegisterBase<INTRRXE, uint16_t, hwreg::EnablePrinter> {
+public:
+    // bit field, one bit per RX endpoint (endpoints 1 - 15)
+    DEF_FIELD(15, 0, ep_rx);
+    static auto Get() { return hwreg::RegisterAddr<INTRRXE>(0x8); }
+};
+
+// Common USB Interrupt Register
+class INTRUSB : public hwreg::RegisterBase<INTRUSB, uint8_t, hwreg::EnablePrinter> {
+public:
+    DEF_BIT(7, vbuserror);
+    DEF_BIT(6, sessreq);
+    DEF_BIT(5, discon);
+    DEF_BIT(4, conn);
+    DEF_BIT(3, sof);
+    DEF_BIT(2, reset);
+    DEF_BIT(1, resume);
+    DEF_BIT(0, suspend);
+    static auto Get() { return hwreg::RegisterAddr<INTRUSB>(0x0a); }
+};
+
+// Common USB Interrupt Enable Register
+class INTRUSBE : public hwreg::RegisterBase<INTRUSBE, uint8_t> {
+public:
+    DEF_BIT(7, vbuserror_e);
+    DEF_BIT(6, sessreq_e);
+    DEF_BIT(5, discon_e);
+    DEF_BIT(4, conn_e);
+    DEF_BIT(3, sof_e);
+    DEF_BIT(2, reset_e);
+    DEF_BIT(1, resume_e);
+    DEF_BIT(0, suspend_e);
+    static auto Get() { return hwreg::RegisterAddr<INTRUSBE>(0x0b); }
+};
+
+// Frame Number Register
+class FRAME : public hwreg::RegisterBase<FRAME, uint16_t> {
+public:
+    DEF_FIELD(10, 0, frame_number);
+    static auto Get() { return hwreg::RegisterAddr<FRAME>(0x0c); }
+};
+
+// Endpoint Selection Index Register
+class INDEX : public hwreg::RegisterBase<INDEX, uint8_t> {
+public:
+    DEF_FIELD(3, 0, selected_endpoint);
+    static auto Get() { return hwreg::RegisterAddr<INDEX>(0x0e); }
+};
+
+// Endpoint Selection Index Register
+class TESTMODE : public hwreg::RegisterBase<TESTMODE, uint8_t> {
+public:
+    DEF_BIT(7, force_host);
+    DEF_BIT(6, fifo_access);
+    DEF_BIT(5, force_fs);
+    DEF_BIT(4, force_hs);
+    DEF_BIT(3, test_packet);
+    DEF_BIT(2, test_k);
+    DEF_BIT(1, test_j);
+    DEF_BIT(0, test_se0_nak);
+    static auto Get() { return hwreg::RegisterAddr<TESTMODE>(0x0f); }
+};
+
+// USB Endpoint n FIFO Register (32 bit access)
+class FIFO : public hwreg::RegisterBase<FIFO, uint32_t> {
+public:
+    DEF_FIELD(31, 0, fifo_data);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<FIFO>(0x20 + ep * 4); }
+};
+
+// USB Endpoint n FIFO Register (8 bit access)
+class FIFO_8 : public hwreg::RegisterBase<FIFO_8, uint8_t> {
+public:
+    DEF_FIELD(7, 0, fifo_data);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<FIFO_8>(0x20 + ep * 4); }
+};
+
+// Device Control Register
+class DEVCTL : public hwreg::RegisterBase<DEVCTL, uint8_t> {
+public:
+    DEF_BIT(7, b_device);
+    DEF_BIT(6, fsdev);
+    DEF_BIT(5, lsdev);
+    DEF_FIELD(4, 3, vbus);
+    DEF_BIT(2, hostmode);
+    DEF_BIT(1, hostreq);
+    DEF_BIT(0, session);
+    static auto Get() { return hwreg::RegisterAddr<DEVCTL>(0x60); }
+};
+
+// Power Up Counter Register
+class PWRUPCNT : public hwreg::RegisterBase<PWRUPCNT, uint8_t> {
+public:
+    DEF_FIELD(3, 0, pwrupcnt);
+    static auto Get() { return hwreg::RegisterAddr<PWRUPCNT>(0x61); }
+};
+
+// FIFO sizes for TXFIFOSZ and RXFIFOSZ
+static constexpr uint8_t FIFO_SIZE_8 = 0;
+static constexpr uint8_t FIFO_SIZE_16 = 1;
+static constexpr uint8_t FIFO_SIZE_32 = 2;
+static constexpr uint8_t FIFO_SIZE_64 = 3;
+static constexpr uint8_t FIFO_SIZE_128 = 4;
+static constexpr uint8_t FIFO_SIZE_256 = 5;
+static constexpr uint8_t FIFO_SIZE_512 = 6;
+static constexpr uint8_t FIFO_SIZE_1024 = 7;
+static constexpr uint8_t FIFO_SIZE_2048 = 8;
+static constexpr uint8_t FIFO_SIZE_4095 = 9;
+
+// TX FIFO Size Register
+class TXFIFOSZ : public hwreg::RegisterBase<TXFIFOSZ, uint8_t> {
+public:
+    DEF_BIT(4, txdpb);
+    DEF_FIELD(3, 0, txsz);
+    static auto Get() { return hwreg::RegisterAddr<TXFIFOSZ>(0x62); }
+};
+
+// RX FIFO Size Register
+class RXFIFOSZ : public hwreg::RegisterBase<RXFIFOSZ, uint8_t> {
+public:
+    DEF_BIT(4, rxdpb);
+    DEF_FIELD(3, 0, rxsz);
+    static auto Get() { return hwreg::RegisterAddr<RXFIFOSZ>(0x63); }
+};
+
+// TX FIFO Address Register
+class TXFIFOADD : public hwreg::RegisterBase<TXFIFOADD, uint16_t> {
+public:
+    DEF_FIELD(12, 0, txfifoadd);
+    static auto Get() { return hwreg::RegisterAddr<TXFIFOADD>(0x64); }
+};
+
+// RX FIFO Address Register
+class RXFIFOADD : public hwreg::RegisterBase<RXFIFOADD, uint16_t> {
+public:
+    DEF_BIT(15, data_err_intr_en);
+    DEF_BIT(14, overrun_intr_en);
+    DEF_FIELD(12, 0, rxfifoadd);
+    static auto Get() { return hwreg::RegisterAddr<RXFIFOADD>(0x66); }
+};
+
+// Hardware Capability Register
+class HWCAPS : public hwreg::RegisterBase<HWCAPS, uint16_t> {
+public:
+    DEF_BIT(15, qmu_support);
+    DEF_BIT(14, hub_support);
+    DEF_BIT(13, usb20_support);
+    DEF_BIT(12, usb11_support);
+    DEF_FIELD(11, 10, mstr_wrap_intfx);
+    DEF_FIELD(9, 8, slave_wrap_intfx);
+    DEF_FIELD(5, 0, usb_version_code);
+    static auto Get() { return hwreg::RegisterAddr<HWCAPS>(0x6c); }
+};
+
+// Version Register
+class HWSVERS : public hwreg::RegisterBase<HWSVERS, uint16_t> {
+public:
+    DEF_FIELD(7, 0, usb_sub_version_code);
+    static auto Get() { return hwreg::RegisterAddr<HWSVERS>(0x6e); }
+};
+
+// Bus Performance Register 3
+class BUSPERF3 : public hwreg::RegisterBase<BUSPERF3, uint16_t> {
+public:
+    DEF_BIT(11, vbuserr_mode);
+    DEF_BIT(9, flush_fifo_en);
+    DEF_BIT(7, noise_still_sof);
+    DEF_BIT(6, bab_cl_en);
+    DEF_BIT(3, undo_srpfix);
+    DEF_BIT(2, otg_deglitch_disable);
+    DEF_BIT(1, ep_swrst);
+    DEF_BIT(0, disusbreset);
+    static auto Get() { return hwreg::RegisterAddr<BUSPERF3>(0x74); }
+};
+
+// Number of TX and RX Register
+class EPINFO : public hwreg::RegisterBase<EPINFO, uint8_t, hwreg::EnablePrinter> {
+public:
+    DEF_FIELD(7, 4, rxendpoints);
+    DEF_FIELD(3, 0, txendpoints);
+    static auto Get() { return hwreg::RegisterAddr<EPINFO>(0x78); }
+};
+
+// Version Register
+class RAMINFO : public hwreg::RegisterBase<RAMINFO, uint8_t, hwreg::EnablePrinter> {
+public:
+    DEF_FIELD(7, 4, dmachans);
+    DEF_FIELD(3, 0, rambits);
+    static auto Get() { return hwreg::RegisterAddr<RAMINFO>(0x79); }
+};
+
+// USB Level 1 Interrupt Status Register
+class USB_L1INTS : public hwreg::RegisterBase<USB_L1INTS, uint32_t, hwreg::EnablePrinter> {
+public:
+    DEF_BIT(11, powerdwn);
+    DEF_BIT(10, drvvbus);
+    DEF_BIT(9, iddig);
+    DEF_BIT(8, vbusvalid);
+    DEF_BIT(7, dpdm);
+    DEF_BIT(6, qhif);
+    DEF_BIT(5, qint);
+    DEF_BIT(4, psr);
+    DEF_BIT(3, dma);
+    DEF_BIT(2, usbcom);
+    DEF_BIT(1, rx);
+    DEF_BIT(0, tx);
+    static auto Get() { return hwreg::RegisterAddr<USB_L1INTS>(0xa0); }
+};
+
+// USB Level 1 Interrupt Mask Register
+class USB_L1INTM : public hwreg::RegisterBase<USB_L1INTM, uint32_t> {
+public:
+    DEF_BIT(11, powerdwn);
+    DEF_BIT(10, drvvbus);
+    DEF_BIT(9, iddig);
+    DEF_BIT(8, vbusvalid);
+    DEF_BIT(7, dpdm);
+    DEF_BIT(6, qhif);
+    DEF_BIT(5, qint);
+    DEF_BIT(4, psr);
+    DEF_BIT(3, dma);
+    DEF_BIT(2, usbcom);
+    DEF_BIT(1, rx);
+    DEF_BIT(0, tx);
+    static auto Get() { return hwreg::RegisterAddr<USB_L1INTM>(0xa4); }
+};
+
+// USB Level 1 Interrupt Polarity Register
+class USB_L1INTP : public hwreg::RegisterBase<USB_L1INTP, uint32_t> {
+public:
+    DEF_BIT(11, powerdwn);
+    DEF_BIT(10, drvvbus);
+    DEF_BIT(9, iddig);
+    DEF_BIT(8, vbusvalid);
+    static auto Get() { return hwreg::RegisterAddr<USB_L1INTP>(0xa8); }
+};
+
+// USB Level 1 Interrupt Control Register
+class USB_L1INTC : public hwreg::RegisterBase<USB_L1INTC, uint32_t> {
+public:
+    DEF_BIT(0, usb_int_sync);
+    static auto Get() { return hwreg::RegisterAddr<USB_L1INTC>(0xac); }
+};
+
+// EP0 Control Status Register (peripheral mode)
+class CSR0_PERI : public hwreg::RegisterBase<CSR0_PERI, uint16_t, hwreg::EnablePrinter> {
+public:
+    DEF_BIT(8, flushfifo);
+    DEF_BIT(7, serviced_setupend);
+    DEF_BIT(6, serviced_rxpktrdy);
+    DEF_BIT(5, sendstall);
+    DEF_BIT(4, setupend);
+    DEF_BIT(3, dataend);
+    DEF_BIT(2, sentstall);
+    DEF_BIT(1, txpktrdy);
+    DEF_BIT(0, rxpktrdy);
+    static auto Get() { return hwreg::RegisterAddr<CSR0_PERI>(0x102); }
+};
+
+//EP0 Control Status Register (host mode)
+class CSR0_HOST : public hwreg::RegisterBase<CSR0_HOST, uint16_t> {
+public:
+    DEF_BIT(11, disping);
+    DEF_BIT(8, flushfifo);
+    DEF_BIT(7, naktimeout);
+    DEF_BIT(6, statuspkt);
+    DEF_BIT(5, reqpkt);
+    DEF_BIT(4, error);
+    DEF_BIT(3, setuppkt);
+    DEF_BIT(2, rxstall);
+    DEF_BIT(1, txpktrdy);
+    DEF_BIT(0, rxpktrdy);
+    static auto Get() { return hwreg::RegisterAddr<CSR0_HOST>(0x102); }
+};
+
+// TXMAP Register
+class TXMAP : public hwreg::RegisterBase<TXMAP, uint16_t> {
+public:
+    DEF_FIELD(12, 11, m_1);
+    DEF_FIELD(10, 0, maximum_payload_transaction);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<TXMAP>(0x100 + ep * 0x10); }
+};
+
+// TX CSR Register (peripheral mode)
+class TXCSR_PERI : public hwreg::RegisterBase<TXCSR_PERI, uint16_t, hwreg::EnablePrinter> {
+public:
+    DEF_BIT(15, autoset);
+    DEF_BIT(14, iso);
+    DEF_BIT(12, dmareqen);
+    DEF_BIT(11, frcdatatog);
+    DEF_BIT(10, dmareqmode);
+    DEF_BIT(8, settxpktrdy_twice);
+    DEF_BIT(7, incomptx);
+    DEF_BIT(6, clrdatatog);
+    DEF_BIT(5, sentstall);
+    DEF_BIT(4, sendstall);
+    DEF_BIT(3, flushfifo);
+    DEF_BIT(2, underrun);
+    DEF_BIT(1, fifo_not_empty);
+    DEF_BIT(0, txpktrdy);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<TXCSR_PERI>(0x102 + ep * 0x10); }
+};
+
+// TX CSR Register (host mode)
+class TXCSR_HOST : public hwreg::RegisterBase<TXCSR_HOST, uint16_t> {
+public:
+    DEF_BIT(15, autoset);
+    DEF_BIT(12, dmareqen);
+    DEF_BIT(11, frcdatatog);
+    DEF_BIT(10, dmareqmode);
+    DEF_BIT(8, settxpktrdy_twice);
+    DEF_BIT(7, naktimeout_incomptx);
+    DEF_BIT(6, clrdatatog);
+    DEF_BIT(5, rxstall);
+    DEF_BIT(3, flushfifo);
+    DEF_BIT(2, error);
+    DEF_BIT(1, fifonotempty);
+    DEF_BIT(0, txpktrdy);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<TXCSR_HOST>(0x102 + ep * 0x10); }
+};
+
+// RXMAP Register
+class RXMAP : public hwreg::RegisterBase<RXMAP, uint16_t> {
+public:
+    DEF_FIELD(12, 11, m_1);
+    DEF_FIELD(10, 0, maximum_payload_transaction);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<RXMAP>(0x104 + ep * 0x10); }
+};
+
+// RX CSR Register (peripheral mode)
+class RXCSR_PERI : public hwreg::RegisterBase<RXCSR_PERI, uint16_t, hwreg::EnablePrinter> {
+public:
+    DEF_BIT(15, autoclear);
+    DEF_BIT(14, iso);
+    DEF_BIT(13, dmareqen);
+    DEF_BIT(12, disnyet_piderr);
+    DEF_BIT(11, dmareqmode);
+    DEF_BIT(9, keeperrstatus);
+    DEF_BIT(8, incomprx);
+    DEF_BIT(7, clrdatatog);
+    DEF_BIT(6, sentstall);
+    DEF_BIT(5, sendstall);
+    DEF_BIT(4, flushfifo);
+    DEF_BIT(3, dataerr);
+    DEF_BIT(2, overrun);
+    DEF_BIT(1, fifofull);
+    DEF_BIT(0, rxpktrdy);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<RXCSR_PERI>(0x106 + ep * 0x10); }
+};
+
+// RX CSR Register (host mode)
+class RXCSR_HOST : public hwreg::RegisterBase<RXCSR_HOST, uint16_t> {
+public:
+    DEF_BIT(15, autoclear);
+    DEF_BIT(14, autoreq);
+    DEF_BIT(13, dmareqenab);
+    DEF_BIT(12, piderror);
+    DEF_BIT(11, dmareqmode);
+    DEF_BIT(10, setreqpkt_twice);
+    DEF_BIT(9, keeperrstatus);
+    DEF_BIT(8, incomprx);
+    DEF_BIT(7, clrdatatog);
+    DEF_BIT(6, rxstall);
+    DEF_BIT(5, reqpkt);
+    DEF_BIT(4, flushfifo);
+    DEF_BIT(3, dataerr_naktimeout);
+    DEF_BIT(2, error);
+    DEF_BIT(1, fifofull);
+    DEF_BIT(0, rxpktrdy);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<RXCSR_HOST>(0x106 + ep * 0x10); }
+};
+
+// RX Count Register
+class RXCOUNT : public hwreg::RegisterBase<RXCOUNT, uint16_t> {
+public:
+    DEF_FIELD(13, 0, rxcount);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<RXCOUNT>(0x108 + ep * 0x10); }
+};
+
+// TX Type Register
+class TXTYPE : public hwreg::RegisterBase<TXTYPE, uint8_t> {
+public:
+    DEF_FIELD(7, 6, tx_speed);
+    DEF_FIELD(5, 4, tx_protocol);
+    DEF_FIELD(3, 0, tx_target_ep_number);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<TXTYPE>(0x10a + ep * 0x10); }
+};
+
+// TX Interval Register
+class TXINTERVAL : public hwreg::RegisterBase<TXINTERVAL, uint8_t> {
+public:
+    DEF_FIELD(7, 0, tx_polling_interval_nak_limit_m);
+    static auto Get() { return hwreg::RegisterAddr<TXINTERVAL>(0x1b); }
+};
+
+// RX Type Register
+class RXTYPE : public hwreg::RegisterBase<RXTYPE, uint8_t> {
+public:
+    DEF_FIELD(7, 6, rx_speed);
+    DEF_FIELD(5, 4, rx_protocol);
+    DEF_FIELD(3, 0, rx_target_ep_number);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<RXTYPE>(0x10c + ep * 0x10); }
+};
+
+// RX Interval Register
+class RXINTERVAL : public hwreg::RegisterBase<RXINTERVAL, uint8_t> {
+public:
+    DEF_FIELD(7, 0, rx_polling_interval_nak_limit_m);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<RXINTERVAL>(0x10d + ep * 0x10); }
+};
+
+// Configured FIFO Size Register
+class FIFOSIZE : public hwreg::RegisterBase<FIFOSIZE, uint8_t> {
+public:
+    DEF_FIELD(7, 4, rxfifosize);
+    DEF_FIELD(3, 0, txfifosize);
+    static auto Get(uint32_t ep) { return hwreg::RegisterAddr<FIFOSIZE>(0x10f + ep * 0x10); }
+};
+
+// DMA Interrupt Status Register
+class DMA_INTR : public hwreg::RegisterBase<DMA_INTR, uint32_t, hwreg::EnablePrinter> {
+public:
+    DEF_FIELD(31, 24, unmask_set);
+    DEF_FIELD(23, 16, unmask_clear);
+    DEF_FIELD(15, 8, unmask);
+    DEF_FIELD(7, 0, status);
+    static auto Get() { return hwreg::RegisterAddr<DMA_INTR>(0x200); }
+};
+
+// DMA Channel n Control Register
+class DMA_CNTL : public hwreg::RegisterBase<DMA_CNTL, uint16_t, hwreg::EnablePrinter> {
+public:
+    DEF_BIT(13, dma_abort);
+    DEF_BIT(11, dma_chan);
+    DEF_FIELD(10, 9, burst_mode);
+    DEF_BIT(8, buserr);
+    DEF_FIELD(7, 4, endpoint);
+    DEF_BIT(3, inten);
+    DEF_BIT(2, dmamode);
+    DEF_BIT(1, dir);
+    DEF_BIT(0, enable);
+    static auto Get(uint32_t n) { return hwreg::RegisterAddr<DMA_CNTL>(0x204 + n * 0x10); }
+};
+
+// DMA Channel n Address Register
+class DMA_ADDR : public hwreg::RegisterBase<DMA_ADDR, uint32_t> {
+public:
+    DEF_FIELD(31, 0, addr);
+    static auto Get(uint32_t n) { return hwreg::RegisterAddr<DMA_ADDR>(0x208 + n * 0x10); }
+};
+
+// DMA Channel n Address Register
+class DMA_COUNT : public hwreg::RegisterBase<DMA_COUNT, uint32_t> {
+public:
+    DEF_FIELD(23, 0, count);
+    static auto Get(uint32_t n) { return hwreg::RegisterAddr<DMA_COUNT>(0x20C + n * 0x10); }
+};
+
+// DMA Limiter Register
+class DMA_LIMITER : public hwreg::RegisterBase<DMA_LIMITER, uint32_t> {
+public:
+    DEF_FIELD(7, 0, limiter);
+    static auto Get() { return hwreg::RegisterAddr<DMA_LIMITER>(0x210); }
+};
+
+// DMA Configuration Register
+class DMA_CONFIG : public hwreg::RegisterBase<DMA_CONFIG, uint32_t> {
+public:
+    DEF_FIELD(11, 10, dma_active_en);
+    DEF_FIELD(9, 8, ahb_hprot_2_en);
+    DEF_FIELD(6, 4, dmaq_chan_sel);
+    DEF_BIT(1, ahbwait_sel);
+    DEF_BIT(0, boundary_1k_cross_en);
+    static auto Get() { return hwreg::RegisterAddr<DMA_CONFIG>(0x220); }
+};
+
+} // namespace mt_usb
diff --git a/system/dev/usb/mt-usb/mt-usb.cpp b/system/dev/usb/mt-usb/mt-usb.cpp
new file mode 100644
index 0000000..0181481
--- /dev/null
+++ b/system/dev/usb/mt-usb/mt-usb.cpp
@@ -0,0 +1,941 @@
+// 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 "mt-usb.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/driver.h>
+#include <ddk/platform-defs.h>
+#include <ddk/protocol/gpio.h>
+#include <ddk/protocol/platform/device.h>
+#include <ddk/protocol/platform-device-lib.h>
+#include <hw/reg.h>
+#include <fbl/algorithm.h>
+#include <fbl/auto_lock.h>
+#include <fbl/unique_ptr.h>
+#include <usb/usb-request.h>
+#include <zircon/assert.h>
+
+#include "mt-usb-regs.h"
+#include "mt-usb-phy-regs.h"
+
+namespace mt_usb {
+
+MtUsb::Endpoint* MtUsb::EndpointFromAddress(uint8_t addr) {
+    size_t ep_num = addr & USB_ENDPOINT_NUM_MASK;
+    if (ep_num == 0 || ep_num > NUM_EPS) {
+        zxlogf(ERROR, "%s: invalid endpoint address %02x\n", __func__, addr);
+        return nullptr;
+    }
+
+    if ((addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) {
+        return &in_eps_[ep_num - 1];
+    } else {
+        return &out_eps_[ep_num - 1];
+    }
+}
+
+zx_status_t MtUsb::Create(zx_device_t* parent) {
+    pdev_protocol_t pdev;
+
+    auto status = device_get_protocol(parent, ZX_PROTOCOL_PDEV, &pdev);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    fbl::AllocChecker ac;
+    auto mt_usb = fbl::make_unique_checked<MtUsb>(&ac, parent, &pdev);
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    status = mt_usb->Init();
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    // devmgr is now in charge of the device.
+    __UNUSED auto* dummy = mt_usb.release();
+    return ZX_OK;
+}
+
+void MtUsb::InitEndpoint(Endpoint* ep, uint8_t ep_num, EpDirection direction) {
+    ep->ep_num = ep_num;
+    ep->direction = direction;
+
+    fbl::AutoLock lock(&ep->lock);
+
+    list_initialize(&ep->queued_reqs);
+    list_initialize(&ep->complete_reqs);
+    ep->current_req = nullptr;
+}
+
+void MtUsb::InitEndpoints() {
+    for (uint8_t i = 0; i < countof(out_eps_); i++) {
+        InitEndpoint(&out_eps_[i], static_cast<uint8_t>(i + 1), EP_OUT);
+    }
+    for (uint8_t i = 0; i < countof(in_eps_); i++) {
+        InitEndpoint(&in_eps_[i], static_cast<uint8_t>(i + 1), EP_IN);
+    }
+}
+
+zx_status_t MtUsb::Init() {
+    InitEndpoints();
+
+    auto status = pdev_.GetBti(0, &bti_);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    status = pdev_.MapMmio(0, &usb_mmio_);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    status = pdev_.MapMmio(1, &phy_mmio_);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    status = pdev_.GetInterrupt(0, &irq_);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    status = DdkAdd("mt-usb");
+    if (status != ZX_OK) {
+        return status;
+    }
+    return ZX_OK;
+}
+
+// Initializes PHY in peripheral role, based on bootloader's configuration.
+// TODO(voydanoff) Add OTG support, consider moving this to a separate driver.
+void MtUsb::InitPhy() {
+    auto* mmio = phy_mmio();
+    auto usbphyacr6 = USBPHYACR6::Get();
+    auto u2phyacr3 = U2PHYACR3::Get();
+    auto u2phyacr4 = U2PHYACR4::Get();
+    auto u2phydtm0 = U2PHYDTM0::Get();
+    auto u2phydtm1 = U2PHYDTM1::Get();
+
+    u2phydtm0.ReadFrom(mmio).set_force_uart_en(0).WriteTo(mmio);
+    u2phydtm1.ReadFrom(mmio).set_rg_uart_en(0).WriteTo(mmio);
+    u2phyacr4.ReadFrom(mmio).set_tx_vcmpdn_en(0).set_tx_bias_en(0).WriteTo(mmio);
+    u2phyacr4.ReadFrom(mmio).set_dp_100k_mode(1).WriteTo(mmio);
+    usbphyacr6.ReadFrom(mmio).set_bc11_sw_en(0).WriteTo(mmio);
+    u2phyacr4.ReadFrom(mmio).set_dp_100k_en(0).set_dm_100k_en(0).WriteTo(mmio);
+    u2phyacr4.ReadFrom(mmio).set_tx_vcmpdn_en(1).WriteTo(mmio);
+    u2phydtm0.ReadFrom(mmio).set_force_suspendm(0).WriteTo(mmio);
+
+    usleep(800);
+
+    u2phydtm1.ReadFrom(mmio).set_rg_sessend(0).WriteTo(mmio);
+    u2phydtm1
+        .ReadFrom(mmio)
+        .set_rg_iddig(1)
+        .set_rg_avalid(1)
+        .set_rg_bvalid(1)
+        .set_rg_vbusvalid(1)
+        .set_rg_uart_en(1)
+        .set_rg_uart_tx_oe(1)
+        .set_rg_uart_i(1)
+        .set_clk60m_en(1)
+        .set_clk48m_en(1)
+        .WriteTo(mmio);
+    u2phyacr3.ReadFrom(mmio).set_pupd_bist_en(0).WriteTo(mmio);
+    u2phydtm0.ReadFrom(mmio).set_force_uart_en(0).WriteTo(mmio);
+    u2phydtm1.ReadFrom(mmio).set_rg_uart_en(0).WriteTo(mmio);
+    u2phydtm0.ReadFrom(mmio).set_force_suspendm(0).WriteTo(mmio);
+    u2phyacr4.ReadFrom(mmio).set_tx_vcmpdn_en(0).set_tx_bias_en(0).WriteTo(mmio);
+    u2phydtm0
+        .ReadFrom(mmio)
+        .set_rg_dmpulldown(0)
+        .set_rg_dppulldown(0)
+        .set_rg_xcvrsel(0)
+        .set_rg_termsel(0)
+        .WriteTo(mmio);
+    u2phydtm0.ReadFrom(mmio).set_rg_datain(0).WriteTo(mmio);
+    u2phydtm0
+        .ReadFrom(mmio)
+        .set_force_termsel(0)
+        .set_force_xcvsel(0)
+        .set_force_dp_pulldown(0)
+        .set_force_dm_pulldown(0)
+        .set_force_datain(0)
+        .WriteTo(mmio);
+    usbphyacr6.ReadFrom(mmio).set_bc11_sw_en(0).WriteTo(mmio);
+    usbphyacr6.ReadFrom(mmio).set_otg_abist_sele(1).WriteTo(mmio);
+
+    usleep(800);
+}
+
+void MtUsb::HandleSuspend() {
+    // TODO - is this the best place to do this?
+    dci_intf_->SetConnected(false);
+}
+
+void MtUsb::HandleReset() {
+    auto* mmio = usb_mmio();
+
+    FADDR::Get()
+        .FromValue(0)
+        .set_function_address(0)
+        .WriteTo(mmio);
+    address_ = 0;
+    set_address_ = false;
+    configuration_ = 0;
+
+    INTRTXE::Get()
+        .FromValue(0)
+        .WriteTo(mmio);
+    INTRRXE::Get()
+        .FromValue(0)
+        .WriteTo(mmio);
+
+    BUSPERF3::Get()
+        .FromValue(0)
+        .set_ep_swrst(1)
+        .set_disusbreset(1)
+        .WriteTo(mmio);
+
+    // TODO flush fifos
+
+//    POWER_PERI::Get().ReadFrom(mmio).Print();
+
+    if (POWER_PERI::Get().ReadFrom(mmio).hsmode()) {
+        dci_intf_->SetSpeed(USB_SPEED_HIGH);
+        ep0_max_packet_ = 64;
+    } else {
+        dci_intf_->SetSpeed(USB_SPEED_FULL);
+        ep0_max_packet_ = 8;
+    }
+
+//    INDEX::Get().FromValue(0).WriteTo(mmio);
+
+    TXMAP::Get(0)
+        .FromValue(0)
+        .set_maximum_payload_transaction(ep0_max_packet_)
+        .WriteTo(mmio);
+    RXMAP::Get(0)
+        .FromValue(0)
+        .set_maximum_payload_transaction(ep0_max_packet_)
+        .WriteTo(mmio);
+
+    // TODO mt_udc_rxtxmap_recover()
+}
+
+zx_status_t MtUsb::HandleEp0() {
+    auto* mmio = usb_mmio();
+
+    // Loop until we explicitly return from this function.
+    // This allows us to handle multiple state transitions at once when appropriate.
+    while (true) {
+        auto csr0 = CSR0_PERI::Get().ReadFrom(mmio);
+
+        if (csr0.setupend()) {
+            csr0.set_serviced_setupend(1);
+            csr0.WriteTo(mmio);
+            csr0.ReadFrom(mmio);
+            ep0_state_ = EP0_IDLE;
+        }
+
+        switch (ep0_state_) {
+        case EP0_IDLE: {
+            if (set_address_) {
+                // Set our new address to the FADDR register.
+                FADDR::Get()
+                    .FromValue(0)
+                    .set_function_address(address_)
+                    .WriteTo(mmio);
+                set_address_ = false;
+                dci_intf_->SetConnected(true);
+            }
+
+            if (!csr0.rxpktrdy()) {
+                return ZX_OK;
+            }
+
+            usb_setup_t* setup = &cur_setup_;
+            size_t actual;
+            FifoRead(0, setup, sizeof(*setup), &actual);
+            if (actual != sizeof(cur_setup_)) {
+                return ZX_ERR_IO_INVALID;
+            }
+            zxlogf(TRACE, "SETUP bmRequestType %x bRequest %u wValue %u wIndex %u wLength %u\n",
+                   setup->bmRequestType, setup->bRequest, setup->wValue, setup->wIndex,
+                   setup->wLength);
+
+            if (setup->wLength > 0 && (setup->bmRequestType & USB_DIR_MASK) == USB_DIR_OUT) {
+                ep0_state_ = EP0_READ;
+                ep0_data_offset_ = 0;
+                ep0_data_length_ = setup->wLength;
+                csr0.ReadFrom(mmio).set_serviced_rxpktrdy(1).set_dataend(0).WriteTo(mmio);
+                break;
+            } else {
+                size_t actual = 0;
+
+                // Handle some special setup requests in this driver.
+                if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) &&
+                    setup->bRequest == USB_REQ_SET_ADDRESS) {
+                    // We save our new address and set it to the FADDR register
+                    // when we get our next interrupt.
+                    // We must defer it until after this setup request has completed.
+                    address_ = static_cast<uint8_t>(setup->wValue);
+                    set_address_ = true;
+                } else if (setup->bmRequestType ==
+                                        (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) &&
+                           setup->bRequest == USB_REQ_SET_CONFIGURATION) {
+                    configuration_ = 0;
+                    auto status = dci_intf_->Control(setup, nullptr, 0, nullptr, 0, &actual);
+                    if (status != ZX_OK) {
+                        zxlogf(ERROR, "%s: USB_REQ_SET_CONFIGURATION Control returned %d\n",
+                               __func__, status);
+                        return status;
+                    }
+                    configuration_ = static_cast<uint8_t>(setup->wValue);
+                    if (configuration_) {
+                        StartEndpoints();
+                    }
+                } else {
+                    auto status = dci_intf_->Control(setup, nullptr, 0, ep0_data_, sizeof(ep0_data_),
+                                                     &actual);
+                    if (status != ZX_OK) {
+                        return status;
+                    }
+                }
+
+                if (actual > 0) {
+                    ep0_state_ = EP0_WRITE;
+                    ep0_data_offset_ = 0;
+                    ep0_data_length_ = actual;
+                } else {
+                    ep0_state_ = EP0_IDLE;
+                }
+
+                csr0.ReadFrom(mmio);
+                csr0.set_serviced_rxpktrdy(1);
+                if (actual == 0) {
+                    csr0.set_dataend(1);
+                }
+                csr0.WriteTo(mmio);
+
+                if (ep0_state_ == EP0_IDLE) {
+                    return ZX_OK;
+                }
+            }
+            break;
+        }
+        case EP0_READ: {
+            if (!csr0.rxpktrdy()) {
+                return ZX_OK;
+            }
+
+            size_t count = ep0_data_length_ - ep0_data_offset_;
+            if (count > ep0_max_packet_) {
+                count = ep0_max_packet_;
+            }
+
+            size_t actual;
+            FifoRead(0, ep0_data_ + ep0_data_offset_, count, &actual);
+            ep0_data_offset_ += actual;
+
+            bool complete = (ep0_data_offset_ == ep0_data_length_);
+            csr0.ReadFrom(mmio).set_serviced_rxpktrdy(1).set_dataend(complete).WriteTo(mmio);
+
+            if (complete) {
+                auto status = dci_intf_->Control(&cur_setup_, ep0_data_, ep0_data_length_, nullptr,
+                                                 0, nullptr);
+                ep0_state_ = EP0_IDLE;
+                if (status != ZX_OK) {
+                    zxlogf(ERROR, "%s: Control returned %d\n", __func__, status);
+                    return status;
+                }
+            }
+            break;
+        }
+        case EP0_WRITE: {
+            if (csr0.txpktrdy()) {
+                return ZX_OK;
+            }
+            size_t count = ep0_data_length_ - ep0_data_offset_;
+            if (count > ep0_max_packet_) {
+                count = ep0_max_packet_;
+            }
+            FifoWrite(0, ep0_data_ + ep0_data_offset_, count);
+            ep0_data_offset_ += count;
+            if (ep0_data_offset_ == ep0_data_length_) {
+                csr0.set_dataend(1)
+                    .set_txpktrdy(1)
+                    .WriteTo(mmio);
+                ep0_state_ = EP0_IDLE;
+            } else {
+                csr0.set_txpktrdy(1)
+                    .WriteTo(mmio);
+            }
+            break;
+        }
+        }
+    }
+}
+
+void MtUsb::HandleEndpointTxLocked(Endpoint* ep) {
+    auto* mmio = usb_mmio();
+    auto ep_num = ep->ep_num;
+
+// TODO check errors, clear bits in CSR?
+
+    ZX_DEBUG_ASSERT(ep->direction == EP_IN);
+
+    auto txcsr = TXCSR_PERI::Get(ep_num);
+
+    if (txcsr.ReadFrom(mmio).txpktrdy()) {
+       return;
+    }
+
+    usb_request_t* req = ep->current_req;
+    if (req) {
+        auto write_length = req->header.length - ep->cur_offset;
+
+        if (write_length > 0) {
+            void* vaddr;
+            auto status = usb_request_mmap(req, &vaddr);
+            if (status != ZX_OK) {
+                zxlogf(ERROR, "%s: usb_request_mmap failed %d\n", __func__, status);
+                req->response.status = status;
+                req->response.actual = 0;
+                ep->current_req = nullptr;
+                list_add_tail(&ep->complete_reqs, &req->node);
+            } else {
+                auto buffer = static_cast<uint8_t*>(vaddr);
+                if (write_length > ep->max_packet_size) {
+                    write_length = ep->max_packet_size;
+                }
+                FifoWrite(ep_num, buffer + ep->cur_offset, write_length);
+                ep->cur_offset += write_length;
+
+                txcsr.ReadFrom(mmio)
+                    .set_txpktrdy(1)
+                    .WriteTo(mmio);
+            }
+        } else {
+            req->response.status = ZX_OK;
+            req->response.actual = req->header.length;
+            ep->current_req = nullptr;
+            list_add_tail(&ep->complete_reqs, &req->node);
+        }
+    }
+
+    if (ep->enabled && ep->current_req == nullptr) {
+        EpQueueNextLocked(ep);
+    }
+}
+
+void MtUsb::HandleEndpointRxLocked(Endpoint* ep) {
+    auto* mmio = usb_mmio();
+    auto ep_num = ep->ep_num;
+
+    ZX_DEBUG_ASSERT(ep->direction == EP_OUT);
+
+// TODO check errors, clear bits in CSR?
+
+    auto rxcsr = RXCSR_PERI::Get(ep_num).ReadFrom(mmio);
+
+    if (!rxcsr.rxpktrdy()) {
+        return;
+    }
+
+    usb_request_t* req = ep->current_req;
+    if (req) {
+        size_t length = req->header.length;
+        void* vaddr;
+        auto status = usb_request_mmap(req, &vaddr);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: usb_request_mmap failed %d\n", __func__, status);
+            req->response.status = status;
+            req->response.actual = 0;
+            ep->current_req = nullptr;
+            list_add_tail(&ep->complete_reqs, &req->node);
+        } else {
+            auto buffer = static_cast<uint8_t*>(vaddr);
+            length -= ep->cur_offset;
+            if (length > ep->max_packet_size) {
+                length = ep->max_packet_size;
+            }
+
+            size_t actual = 0;
+            if (length > 0) {
+                FifoRead(ep_num, buffer + ep->cur_offset, length, &actual);
+                ep->cur_offset += actual;
+                // signal that we read the packet
+                rxcsr.ReadFrom(mmio).set_rxpktrdy(0).WriteTo(mmio);
+            }
+
+            if (actual < length || ep->cur_offset == req->header.length) {
+                req->response.status = ZX_OK;
+                req->response.actual = ep->cur_offset;
+                ep->current_req = nullptr;
+                list_add_tail(&ep->complete_reqs, &req->node);
+            }
+        }
+    }
+
+    if (ep->enabled && ep->current_req == nullptr) {
+        EpQueueNextLocked(ep);
+    }
+}
+
+void MtUsb::EpQueueNextLocked(Endpoint* ep) {
+    __UNUSED auto* mmio = usb_mmio();
+    usb_request_t* req;
+
+    if (ep->current_req == nullptr &&
+        (req = list_remove_head_type(&ep->queued_reqs, usb_request_t, node)) != nullptr) {
+        ep->current_req = req;
+        ep->cur_offset = 0;
+
+        if (ep->direction == EP_IN) {
+            HandleEndpointTxLocked(ep);
+        } else {
+            HandleEndpointRxLocked(ep);
+        }
+    }
+}
+
+void MtUsb::StartEndpoint(Endpoint* ep) {
+    fbl::AutoLock lock(&ep->lock);
+
+    if (ep->enabled) {
+        EpQueueNextLocked(ep);
+    }
+}
+
+void MtUsb::StartEndpoints() {
+    for (uint8_t i = 0; i < countof(out_eps_); i++) {
+        StartEndpoint(&out_eps_[i]);
+    }
+    for (uint8_t i = 0; i < countof(in_eps_); i++) {
+        StartEndpoint(&in_eps_[i]);
+    }
+}
+
+void MtUsb::SetStall(Endpoint* ep, bool stall) {
+    auto* mmio = usb_mmio();
+
+    if (ep->direction == EP_IN) {
+        TXCSR_PERI::Get(ep->ep_num)
+            .ReadFrom(mmio)
+            .set_sendstall(stall ? 1 : 0)
+            .WriteTo(mmio);
+    } else {
+        RXCSR_PERI::Get(ep->ep_num)
+            .ReadFrom(mmio)
+            .set_sendstall(stall ? 1 : 0)
+            .WriteTo(mmio);
+    }
+}
+
+void MtUsb::FifoRead(uint8_t ep_index, void* buf, size_t buflen, size_t* actual) {
+    auto* mmio = usb_mmio();
+
+    size_t count = RXCOUNT::Get(ep_index).ReadFrom(mmio).rxcount();
+    if (count > buflen) {
+        zxlogf(ERROR, "%s: buffer too small: buflen %zu rxcount %zu\n", __func__, buflen, count);
+        count = buflen;
+    }
+
+    auto remaining = count;
+    auto dest = static_cast<uint32_t*>(buf);
+
+// needed?
+    INDEX::Get().FromValue(0).set_selected_endpoint(ep_index).WriteTo(mmio);
+
+    while (remaining >= 4) {
+        *dest++ = FIFO::Get(ep_index).ReadFrom(mmio).fifo_data();
+        remaining -= 4;
+    }
+    auto dest_8 = reinterpret_cast<uint8_t*>(dest);
+    while (remaining > 0) {
+        *dest_8++ = FIFO_8::Get(ep_index).ReadFrom(mmio).fifo_data();
+        remaining--;
+    }
+
+    *actual = count;
+}
+
+void MtUsb::FifoWrite(uint8_t ep_index, const void* buf, size_t length) {
+    auto* mmio = usb_mmio();
+
+//    INDEX::Get().FromValue(ep_index).WriteTo(mmio);
+
+    auto remaining = length;
+    auto src = static_cast<const uint8_t*>(buf);
+
+// needed?
+    INDEX::Get().FromValue(0).set_selected_endpoint(ep_index).WriteTo(mmio);
+
+    auto fifo = FIFO_8::Get(ep_index).FromValue(0);
+
+    while (remaining-- > 0) {
+        fifo.set_fifo_data(*src++).WriteTo(mmio);
+    }
+}
+
+int MtUsb::IrqThread() {
+    auto* mmio = usb_mmio();
+
+    // Turn off power first
+    POWER_PERI::Get()
+        .ReadFrom(mmio)
+        .set_softconn(0)
+        .WriteTo(mmio);
+
+    InitPhy();
+
+    // Turn power back on
+    POWER_PERI::Get()
+        .ReadFrom(mmio)
+        .set_softconn(1)
+        .set_enablesuspendm(1)
+        .set_hsenab(1)
+        .WriteTo(mmio);
+
+    // Clear interrupts first
+    INTRTX::Get()
+        .FromValue(0xffff)
+        .WriteTo(mmio);
+    INTRRX::Get()
+        .FromValue(0xffff)
+        .WriteTo(mmio);
+    INTRUSB::Get()
+        .FromValue(0xff)
+        .WriteTo(mmio);
+
+    // Enable TX and RX interrupts for endpoint zero
+    INTRTXE::Get()
+        .FromValue(0)
+        .set_ep_tx(1 << 0)
+        .WriteTo(mmio);
+
+    // Enable USB interrupts
+    INTRUSBE::Get()
+        .FromValue(0)
+        .set_discon_e(1)
+        .set_reset_e(1)
+        .set_resume_e(1)
+        .set_suspend_e(1)
+        .WriteTo(mmio);
+
+    // Enable USB level 1 interrupts
+    USB_L1INTM::Get()
+        .FromValue(0)
+        .set_tx(1)
+        .set_rx(1)
+        .set_usbcom(1)
+        .WriteTo(mmio);
+
+    for (uint8_t i = 1; i <= NUM_EPS; i++) { 
+        INDEX::Get().FromValue(0).set_selected_endpoint(i).WriteTo(mmio);
+        uint32_t fifo_addr = ((1024 * i) >> 3);
+        ZX_DEBUG_ASSERT(fifo_addr < UINT16_MAX);
+        TXFIFOADD::Get().FromValue(0).set_txfifoadd(static_cast<uint16_t>(fifo_addr)).WriteTo(mmio);
+        RXFIFOADD::Get().FromValue(0).set_rxfifoadd(static_cast<uint16_t>(fifo_addr)).WriteTo(mmio);
+        TXFIFOSZ::Get().FromValue(0).set_txdpb(1).set_txsz(FIFO_SIZE_1024).WriteTo(mmio);
+        RXFIFOSZ::Get().FromValue(0).set_rxdpb(1).set_rxsz(FIFO_SIZE_1024).WriteTo(mmio);
+    }
+
+    while (true) {
+        auto status = irq_.wait(nullptr);
+        if (status == ZX_ERR_CANCELED) {
+            return 0;
+        } else if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: irq_.wait failed: %d\n", __func__, status);
+            return -1;
+        }
+        zxlogf(TRACE, " \n%s: got interrupt!\n", __func__);
+
+        // Write back these registers to acknowledge the interrupts
+        auto intrtx = INTRTX::Get().ReadFrom(mmio).WriteTo(mmio);
+        auto intrrx = INTRRX::Get().ReadFrom(mmio).WriteTo(mmio);
+        auto intrusb = INTRUSB::Get().ReadFrom(mmio).WriteTo(mmio);
+
+        if (intrusb.suspend()) {
+            HandleSuspend();
+        }
+        if (intrusb.reset()) {
+            HandleReset();
+        }
+
+        auto ep_tx = intrtx.ep_tx();
+        auto ep_rx = intrrx.ep_rx();
+
+        if (ep_tx) {
+            if (ep_tx & (1 << 0)) {
+                auto status = HandleEp0();
+                if (status != ZX_OK) {
+                    // Stall
+                    CSR0_PERI::Get().ReadFrom(mmio).set_sendstall(1).WriteTo(mmio);
+                }
+            }
+
+            for (unsigned i = 0; i < countof(in_eps_); i++) {
+                if (ep_tx & (1 << (i + 1))) {
+                    Endpoint* ep = &in_eps_[i];
+                    // requests to complete outside of the lock
+                    list_node_t complete_reqs;
+
+                    {
+                        fbl::AutoLock lock(&ep->lock);
+                        HandleEndpointTxLocked(ep);
+                        list_move(&ep->complete_reqs, &complete_reqs);
+                    }
+                    // Requests must be completed outside of the lock.
+                    usb_request_t* req;
+                    while ((req = list_remove_head_type(&complete_reqs, usb_request_t, node))) {
+                        usb_request_complete(req, req->response.status, req->response.actual);
+                    }
+                }
+            }
+        }
+
+        if (ep_rx) {
+            for (unsigned i = 0; i <=countof(out_eps_); i++) {
+                if (ep_rx & (1 << (i + 1))) {
+                    Endpoint* ep = &out_eps_[i];
+                    list_node_t complete_reqs;
+
+                    {
+                        fbl::AutoLock lock(&ep->lock);
+                        HandleEndpointRxLocked(ep);
+                        list_move(&ep->complete_reqs, &complete_reqs);
+                    }
+                    // Requests must be completed outside of the lock.
+                    usb_request_t* req;
+                    while ((req = list_remove_head_type(&complete_reqs, usb_request_t, node))) {
+                        usb_request_complete(req, req->response.status, req->response.actual);
+                    }
+                }
+            }
+        }
+    }
+}
+
+void MtUsb::DdkUnbind() {
+    irq_.destroy();
+    thrd_join(irq_thread_, nullptr);
+}
+
+void MtUsb::DdkRelease() {
+    delete this;
+}
+
+void MtUsb::UsbDciRequestQueue(usb_request_t* req) {
+    auto* ep = EndpointFromAddress(req->header.ep_address);
+    if (ep == nullptr) {
+        usb_request_complete(req, ZX_ERR_INVALID_ARGS, 0);
+        return;
+    }
+
+    fbl::AutoLock lock(&ep->lock);
+
+    if (!ep->enabled) {
+        usb_request_complete(req, ZX_ERR_BAD_STATE, 0);
+        return;
+    }
+
+    list_add_tail(&ep->queued_reqs, &req->node);
+    EpQueueNextLocked(ep);
+}
+
+zx_status_t MtUsb::UsbDciSetInterface(const usb_dci_interface_t* interface) {
+    // TODO - handle interface == nullptr for tear down path?
+
+    if (dci_intf_.has_value()) {
+        zxlogf(ERROR, "%s: dci_intf_ already set\n", __func__);
+        return ZX_ERR_BAD_STATE;
+    }
+
+    dci_intf_ = ddk::UsbDciInterfaceProxy(interface);
+
+    // Now that the usb-peripheral driver has bound, we can start things up.
+    int rc = thrd_create_with_name(&irq_thread_,
+                                   [](void* arg) -> int {
+                                       return reinterpret_cast<MtUsb*>(arg)->IrqThread();
+                                   },
+                                   reinterpret_cast<void*>(this),
+                                   "mt-usb-irq-thread");
+    if (rc != thrd_success) {
+        return ZX_ERR_INTERNAL;
+    }
+
+    return ZX_OK;
+}
+
+ zx_status_t MtUsb::UsbDciConfigEp(const usb_endpoint_descriptor_t* ep_desc,
+                                   const usb_ss_ep_comp_descriptor_t* ss_comp_desc) {
+    auto* mmio = usb_mmio();
+    auto ep_address = ep_desc->bEndpointAddress;
+    auto* ep = EndpointFromAddress(ep_address);
+    if (ep == nullptr) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+    auto ep_num = ep->ep_num;
+
+    zxlogf(TRACE, "%s address %02x ep_num %u direction %u\n", __func__, ep_address, ep_num,
+           ep->direction);
+
+    fbl::AutoLock lock(&ep->lock);
+
+    if (ep->enabled) {
+        return ZX_ERR_BAD_STATE;
+    }
+
+    ep->address = ep_address;
+
+    if (ep->direction == EP_IN) {
+        auto intrtxe = INTRTXE::Get().ReadFrom(mmio);
+        uint16_t mask = intrtxe.ep_tx();
+        mask |= static_cast<uint16_t>(1 << ep_num);
+        intrtxe.set_ep_tx(mask).WriteTo(mmio);
+    } else {
+        auto intrrxe = INTRRXE::Get().ReadFrom(mmio);
+        uint16_t mask = intrrxe.ep_rx();
+        mask |= static_cast<uint16_t>(1 << ep_num);
+        intrrxe.set_ep_rx(mask).WriteTo(mmio);
+    }
+
+    uint16_t max_packet_size = usb_ep_max_packet(ep_desc);
+    if (ep->direction == EP_IN) {
+        TXCSR_PERI::Get(ep_num)
+            .ReadFrom(mmio)
+            .set_clrdatatog(1)
+            .set_flushfifo(1)
+            .WriteTo(mmio);
+
+//        if (usb_ep_type(ep_desc) == USB_ENDPOINT_BULK) {
+//            TXMAP::Get(ep_num)
+//                .FromValue(0)
+//                .set_m_1(3)
+//                .set_maximum_payload_transaction(1024)
+//                .WriteTo(mmio);
+//        } else {
+            TXMAP::Get(ep_num)
+                .FromValue(0)
+                .set_maximum_payload_transaction(max_packet_size)
+                .WriteTo(mmio);
+//        }
+    } else {
+        RXCSR_PERI::Get(ep_num)
+            .ReadFrom(mmio)
+            .set_clrdatatog(1)
+            .set_flushfifo(1)
+            .WriteTo(mmio);
+
+        RXMAP::Get(ep_num)
+            .FromValue(0)
+            .set_maximum_payload_transaction(max_packet_size)
+            .WriteTo(mmio);
+    }
+
+    ep->max_packet_size = max_packet_size;
+    ep->enabled = true;
+
+    if (configuration_) {
+        EpQueueNextLocked(ep);
+    }
+
+    return ZX_OK;
+}
+
+zx_status_t MtUsb::UsbDciDisableEp(uint8_t ep_address) {
+
+    auto* mmio = usb_mmio();
+    auto* ep = EndpointFromAddress(ep_address);
+    if (ep == nullptr) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    auto ep_num = ep->ep_num;
+
+    zxlogf(TRACE, "%s address %02x ep_num %u direction %u\n", __func__, ep_address, ep_num,
+           ep->direction);
+
+    fbl::AutoLock lock(&ep->lock);
+
+    if (!ep->enabled) {
+        return ZX_ERR_BAD_STATE;
+    }
+
+    if (ep->direction == EP_IN) {
+        auto intrtxe = INTRTXE::Get().ReadFrom(mmio);
+        uint16_t mask = intrtxe.ep_tx();
+        mask &= static_cast<uint16_t>(~(1 << ep_num));
+        intrtxe.set_ep_tx(mask).WriteTo(mmio);
+    } else {
+        auto intrrxe = INTRRXE::Get().ReadFrom(mmio);
+        uint16_t mask = intrrxe.ep_rx();
+        mask &= static_cast<uint16_t>(~(1 << ep_num));
+        intrrxe.set_ep_rx(mask).WriteTo(mmio);
+    }
+
+    ep->enabled = false;
+
+    return ZX_OK;
+}
+
+zx_status_t MtUsb::UsbDciEpSetStall(uint8_t ep_address) {
+    auto* ep = EndpointFromAddress(ep_address);
+    if (ep == nullptr) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+    SetStall(ep, true);
+    return ZX_OK;
+}
+
+zx_status_t MtUsb::UsbDciEpClearStall(uint8_t ep_address) {
+    auto* ep = EndpointFromAddress(ep_address);
+    if (ep == nullptr) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+    SetStall(ep, false);
+    return ZX_OK;
+}
+
+zx_status_t MtUsb::UsbDciGetBti(zx_handle_t* out_bti) {
+    *out_bti = bti_.get();
+    return ZX_OK;
+}
+
+size_t MtUsb::UsbDciGetRequestSize() {
+    return sizeof(usb_request_t);
+}
+
+
+zx_status_t mt_usb_bind(void* ctx, zx_device_t* parent) {
+    return MtUsb::Create(parent);
+}
+
+static zx_driver_ops_t driver_ops = [](){
+    zx_driver_ops_t ops;
+    ops.version = DRIVER_OPS_VERSION;
+    ops.bind = mt_usb_bind;
+    return ops;
+}();
+
+} // namespace mt_usb
+
+ZIRCON_DRIVER_BEGIN(mt_usb, mt_usb::driver_ops, "zircon", "0.1", 3)
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV),
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_MEDIATEK),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_MEDIATEK_USB),
+ZIRCON_DRIVER_END(mt_usb)
diff --git a/system/dev/usb/mt-usb/mt-usb.h b/system/dev/usb/mt-usb/mt-usb.h
new file mode 100644
index 0000000..54e2925
--- /dev/null
+++ b/system/dev/usb/mt-usb/mt-usb.h
@@ -0,0 +1,154 @@
+// 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
+
+#include <ddktl/device.h>
+#include <ddktl/mmio.h>
+#include <ddktl/pdev.h>
+#include <ddktl/protocol/usb-dci.h>
+#include <fbl/macros.h>
+#include <fbl/mutex.h>
+#include <fbl/optional.h>
+#include <fbl/unique_ptr.h>
+#include <lib/zx/handle.h>
+#include <lib/zx/interrupt.h>
+#include <zircon/listnode.h>
+#include <zircon/hw/usb.h>
+
+#include <threads.h>
+
+namespace mt_usb {
+
+class MtUsb;
+using MtUsbType = ddk::Device<MtUsb, ddk::Unbindable>;
+
+class MtUsb : public MtUsbType, public ddk::UsbDciProtocol<MtUsb> {
+public:
+    explicit MtUsb(zx_device_t* parent, pdev_protocol_t* pdev)
+        : MtUsbType(parent), pdev_(pdev) {}
+
+    static zx_status_t Create(zx_device_t* parent);
+
+    // Device protocol implementation.
+    void DdkUnbind();
+    void DdkRelease();
+
+    // USB DCI protocol implementation.
+     void UsbDciRequestQueue(usb_request_t* req);
+     zx_status_t UsbDciSetInterface(const usb_dci_interface_t* interface);
+     zx_status_t UsbDciConfigEp(const usb_endpoint_descriptor_t* ep_desc, const
+                                usb_ss_ep_comp_descriptor_t* ss_comp_desc);
+     zx_status_t UsbDciDisableEp(uint8_t ep_address);
+     zx_status_t UsbDciEpSetStall(uint8_t ep_address);
+     zx_status_t UsbDciEpClearStall(uint8_t ep_address);
+     zx_status_t UsbDciGetBti(zx_handle_t* out_bti);
+     size_t UsbDciGetRequestSize();
+
+private:
+    DISALLOW_COPY_ASSIGN_AND_MOVE(MtUsb);
+
+    enum Ep0State {
+        // Waiting for next setup request.
+        EP0_IDLE,
+        // Reading data for setup request.
+        EP0_READ,
+        // Writing data for setup request.
+        EP0_WRITE,
+    };
+
+    enum EpDirection {
+        EP_OUT,
+        EP_IN,
+    };
+
+    struct Endpoint {
+        // Endpoint number to use when indexing into hardware registers.
+        uint8_t ep_num;
+        EpDirection direction;
+        uint8_t address;
+
+        bool enabled  __TA_GUARDED(lock) = false;
+        uint16_t max_packet_size;
+
+        // Requests waiting to be processed.
+        list_node_t queued_reqs __TA_GUARDED(lock);
+        // request currently being processed.
+        usb_request_t* current_req __TA_GUARDED(lock) = nullptr;
+        list_node_t complete_reqs __TA_GUARDED(lock);
+
+        // Offset into current_req during read and write.
+        size_t cur_offset;
+
+        fbl::Mutex lock;
+    };
+
+    zx_status_t Init();
+    void InitEndpoint(Endpoint* ep, uint8_t ep_num, EpDirection direction);
+    void InitEndpoints();
+    void InitPhy();
+    int IrqThread();
+
+    void HandleSuspend();
+    void HandleReset();
+    zx_status_t HandleEp0();
+    void HandleEndpointTxLocked(Endpoint* ep) __TA_REQUIRES(ep->lock);
+    void HandleEndpointRxLocked(Endpoint* ep) __TA_REQUIRES(ep->lock);
+
+    void FifoRead(uint8_t ep_index, void* buf, size_t buflen, size_t* actual);
+    void FifoWrite(uint8_t ep_index, const void* buf, size_t length);
+    void EpQueueNextLocked(Endpoint* ep) __TA_REQUIRES(ep->lock);
+    void StartEndpoint(Endpoint* ep);
+    void StartEndpoints();
+    void SetStall(Endpoint* ep, bool stall);
+
+    Endpoint* EndpointFromAddress(uint8_t addr);
+
+    inline ddk::MmioBuffer* usb_mmio() {
+        return &*usb_mmio_;
+    }
+    inline ddk::MmioBuffer* phy_mmio() {
+        return &*phy_mmio_;
+    }
+
+    ddk::PDev pdev_;
+    fbl::optional<ddk::UsbDciInterfaceProxy> dci_intf_;
+    zx::bti bti_;
+
+    fbl::optional<ddk::MmioBuffer> usb_mmio_;
+    fbl::optional<ddk::MmioBuffer> phy_mmio_;
+
+    zx::interrupt irq_;
+    thrd_t irq_thread_;
+
+    // Number of endpoints we support, not counting ep0.
+    static constexpr size_t NUM_EPS = 15;
+
+    Endpoint out_eps_[NUM_EPS];
+    Endpoint in_eps_[NUM_EPS];
+
+    // Address assigned to us by the host.
+    uint8_t address_ = 0;
+    bool set_address_ = false;
+
+    // Current USB configuration. TODO this needs a lock.
+    uint8_t configuration_ = 0;
+
+    Ep0State ep0_state_ = EP0_IDLE;
+    usb_setup_t cur_setup_;
+
+    uint8_t ep0_data_[UINT16_MAX];
+    // Current read/write location in ep0_buffer_
+    size_t ep0_data_offset_ = 0;
+    // Total length to read or write
+    size_t ep0_data_length_ = 0;
+
+    uint8_t ep0_max_packet_;
+};
+
+} // namespace mt_usb
+
+__BEGIN_CDECLS
+zx_status_t mt_usb_bind(void* ctx, zx_device_t* parent);
+__END_CDECLS
diff --git a/system/dev/usb/mt-usb/rules.mk b/system/dev/usb/mt-usb/rules.mk
new file mode 100644
index 0000000..f59e72b
--- /dev/null
+++ b/system/dev/usb/mt-usb/rules.mk
@@ -0,0 +1,34 @@
+# 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)/mt-usb.cpp \
+
+MODULE_STATIC_LIBS := \
+    system/dev/lib/usb \
+    system/ulib/ddk \
+    system/ulib/ddktl \
+    system/ulib/fbl \
+    system/ulib/hwreg \
+    system/ulib/sync \
+    system/ulib/zx \
+    system/ulib/zxcpp \
+
+MODULE_LIBS := \
+    system/ulib/driver \
+    system/ulib/zircon \
+    system/ulib/c \
+
+MODULE_BANJO_LIBS := \
+    system/banjo/ddk-protocol-gpio \
+    system/banjo/ddk-protocol-i2c \
+    system/banjo/ddk-protocol-platform-device \
+
+include make/module.mk
diff --git a/system/public/zircon/hw/usb.h b/system/public/zircon/hw/usb.h
index bf9755f..697f7f0 100644
--- a/system/public/zircon/hw/usb.h
+++ b/system/public/zircon/hw/usb.h
@@ -96,6 +96,7 @@
 #define USB_ENDPOINT_IN                    0x80
 #define USB_ENDPOINT_OUT                   0x00
 #define USB_ENDPOINT_DIR_MASK              0x80
+#define USB_ENDPOINT_NUM_MASK              0x1F
 
 /* Endpoint types (bmAttributes) */
 #define USB_ENDPOINT_CONTROL               0x00
diff --git a/system/ulib/ddk/include/ddk/platform-defs.h b/system/ulib/ddk/include/ddk/platform-defs.h
index 5bfae6a..2d1871d 100644
--- a/system/ulib/ddk/include/ddk/platform-defs.h
+++ b/system/ulib/ddk/include/ddk/platform-defs.h
@@ -143,6 +143,7 @@
 #define PDEV_DID_MEDIATEK_EMMC      2
 #define PDEV_DID_MEDIATEK_DISPLAY   3
 #define PDEV_DID_MEDIATEK_I2C       4
+#define PDEV_DID_MEDIATEK_USB       5
 
 // Sony
 #define PDEV_VID_SONY               14
diff --git a/system/ulib/ddktl/include/ddktl/protocol/usb-dci-internal.h b/system/ulib/ddktl/include/ddktl/protocol/usb-dci-internal.h
index 0572c35..95801e3 100644
--- a/system/ulib/ddktl/include/ddktl/protocol/usb-dci-internal.h
+++ b/system/ulib/ddktl/include/ddktl/protocol/usb-dci-internal.h
@@ -53,6 +53,8 @@
                                      zx_status_t (C::*)(uint8_t ep_address));
 DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(has_usb_dci_protocol_get_bti, UsbDciGetBti,
                                      zx_status_t (C::*)(zx_handle_t* out_bti));
+DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(has_usb_dci_protocol_get_request_size, UsbDciGetRequestSize,
+                                     size_t (C::*)());
 
 template <typename D>
 constexpr void CheckUsbDciProtocolSubclass() {
@@ -78,6 +80,9 @@
     static_assert(internal::has_usb_dci_protocol_get_bti<D>::value,
                   "UsbDciProtocol subclasses must implement "
                   "zx_status_t UsbDciGetBti(zx_handle_t* out_bti");
+    static_assert(internal::has_usb_dci_protocol_get_request_size<D>::value,
+                  "UsbDciProtocol subclasses must implement "
+                  "size_t UsbDciGetRequestSize()");
 }
 
 } // namespace internal
diff --git a/system/ulib/ddktl/include/ddktl/protocol/usb-dci.h b/system/ulib/ddktl/include/ddktl/protocol/usb-dci.h
index a439543..8e313dc 100644
--- a/system/ulib/ddktl/include/ddktl/protocol/usb-dci.h
+++ b/system/ulib/ddktl/include/ddktl/protocol/usb-dci.h
@@ -135,7 +135,7 @@
         ops_.get_request_size = UsbDciGetRequestSize;
 
         // Can only inherit from one base_protocol implementation.
-        ZX_ASSERT(ddk_proto_id_ = 0);
+        ZX_ASSERT(ddk_proto_id_ == 0);
         ddk_proto_id_ = ZX_PROTOCOL_USB_DCI;
         ddk_proto_ops_ = &ops_;
     }