// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mt_usb { using namespace board_mt8167; // Hardware registers. // Internal context for USB requests typedef struct { // callback to the upper layer usb_request_complete_t complete_cb; // for queueing requests internally list_node_t node; } mt_usb_req_internal_t; #define USB_REQ_TO_INTERNAL(req) \ ((mt_usb_req_internal_t *)((uintptr_t)(req) + sizeof(usb_request_t))) #define INTERNAL_TO_USB_REQ(ctx) ((usb_request_t *)((uintptr_t)(ctx) - sizeof(usb_request_t))) 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(&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(i + 1), EP_OUT); } for (uint8_t i = 0; i < countof(in_eps_); i++) { InitEndpoint(&in_eps_[i], static_cast(i + 1), EP_IN); } } zx_status_t MtUsb::Init() { InitEndpoints(); auto 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 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; } 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); } 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(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(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; auto* req_int = USB_REQ_TO_INTERNAL(req); list_add_tail(&ep->complete_reqs, &req_int->node); } else { auto buffer = static_cast(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; auto* req_int = USB_REQ_TO_INTERNAL(req); list_add_tail(&ep->complete_reqs, &req_int->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; auto* req_int = USB_REQ_TO_INTERNAL(req); list_add_tail(&ep->complete_reqs, &req_int->node); } else { auto buffer = static_cast(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; auto* req_int = USB_REQ_TO_INTERNAL(req); list_add_tail(&ep->complete_reqs, &req_int->node); } } } if (ep->enabled && ep->current_req == nullptr) { EpQueueNextLocked(ep); } } void MtUsb::EpQueueNextLocked(Endpoint* ep) { __UNUSED auto* mmio = usb_mmio(); mt_usb_req_internal_t* req_int; if (ep->current_req == nullptr && (req_int = list_remove_head_type(&ep->queued_reqs, mt_usb_req_internal_t, node)) != nullptr) { usb_request_t* req = INTERNAL_TO_USB_REQ(req_int); 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(buf); while (remaining >= 4) { *dest++ = FIFO::Get(ep_index).ReadFrom(mmio).fifo_data(); remaining -= 4; } auto dest_8 = reinterpret_cast(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(); auto remaining = length; auto src = static_cast(buf); 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); // Configure all endpoints other than endpoint zero to use 1024 byte FIFOs. constexpr uint32_t fifo_size = 1024 >> 3; // FIFO size is measured in 8 byte units. uint32_t fifo_addr = (64 >> 3); // First 64 bytes used for endpoint zero. for (uint8_t i = 1; i <= NUM_EPS; i++) { INDEX::Get().FromValue(0).set_selected_endpoint(i).WriteTo(mmio); ZX_DEBUG_ASSERT(fifo_addr < UINT16_MAX); TXFIFOADD::Get().FromValue(0).set_txfifoadd(static_cast(fifo_addr)).WriteTo(mmio); fifo_addr += fifo_size; ZX_DEBUG_ASSERT(fifo_addr < UINT16_MAX); RXFIFOADD::Get().FromValue(0).set_rxfifoadd(static_cast(fifo_addr)).WriteTo(mmio); fifo_addr += fifo_size; 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. mt_usb_req_internal_t* req_int; while ((req_int = list_remove_head_type(&complete_reqs, mt_usb_req_internal_t, node))) { usb_request_t* req = INTERNAL_TO_USB_REQ(req_int); usb_request_complete(req, req->response.status, req->response.actual, &req_int->complete_cb); } } } } 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. mt_usb_req_internal_t* req_int; while ((req_int = list_remove_head_type(&complete_reqs, mt_usb_req_internal_t, node))) { usb_request_t* req = INTERNAL_TO_USB_REQ(req_int); usb_request_complete(req, req->response.status, req->response.actual, &req_int->complete_cb); } } } } } } void MtUsb::DdkUnbind() { irq_.destroy(); thrd_join(irq_thread_, nullptr); } void MtUsb::DdkRelease() { delete this; } void MtUsb::UsbDciRequestQueue(usb_request_t* req, const usb_request_complete_t* cb) { auto* ep = EndpointFromAddress(req->header.ep_address); if (ep == nullptr) { usb_request_complete(req, ZX_ERR_INVALID_ARGS, 0, cb); return; } fbl::AutoLock lock(&ep->lock); if (!ep->enabled) { usb_request_complete(req, ZX_ERR_BAD_STATE, 0, cb); return; } auto* req_int = USB_REQ_TO_INTERNAL(req); req_int->complete_cb = *cb; list_add_tail(&ep->queued_reqs, &req_int->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::UsbDciInterfaceClient(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(arg)->IrqThread(); }, reinterpret_cast(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(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(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) // FIFO must be flushed twice when using double buffering .WriteTo(mmio); 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) // FIFO must be flushed twice when using double buffering .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(~(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(~(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; } size_t MtUsb::UsbDciGetRequestSize() { return sizeof(usb_request_t) + sizeof(mt_usb_req_internal_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_MUSB_PERIPHERAL), ZIRCON_DRIVER_END(mt_usb)