[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_;
}