| // Copyright 2019 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 "dwc2.h" |
| |
| #include <ddk/metadata.h> |
| #include <ddk/platform-defs.h> |
| #include <ddktl/protocol/composite.h> |
| #include <fbl/algorithm.h> |
| #include <lib/zx/time.h> |
| |
| namespace dwc2 { |
| |
| // Handler for usbreset interrupt. |
| void Dwc2::HandleReset() { |
| auto* mmio = get_mmio(); |
| |
| zxlogf(LTRACE, "\nRESET\n"); |
| |
| ep0_state_ = Ep0State::DISCONNECTED; |
| configured_ = false; |
| |
| // Clear remote wakeup signalling |
| DCTL::Get() |
| .ReadFrom(mmio) |
| .set_rmtwkupsig(0) |
| .WriteTo(mmio); |
| |
| for (uint32_t i = 0; i < MAX_EPS_CHANNELS; i++) { |
| auto diepctl = DEPCTL::Get(i).ReadFrom(mmio); |
| |
| // Disable IN endpoints |
| if (diepctl.epena()) { |
| diepctl.set_snak(1); |
| diepctl.set_epdis(1); |
| diepctl.WriteTo(mmio); |
| } |
| |
| // Clear snak on OUT endpoints |
| DEPCTL::Get(i + DWC_EP_OUT_SHIFT) |
| .ReadFrom(mmio) |
| .set_snak(1) |
| .WriteTo(mmio); |
| } |
| |
| // Flush endpoint zero TX FIFO |
| FlushTxFifo(0); |
| |
| // Flush the learning queue |
| GRSTCTL::Get() |
| .FromValue(0) |
| .set_intknqflsh(1) |
| .WriteTo(mmio); |
| |
| // Enable interrupts for only EPO IN and OUT |
| DAINTMSK::Get() |
| .FromValue((1 << DWC_EP0_IN) | (1 << DWC_EP0_OUT)) |
| .WriteTo(mmio); |
| |
| // Enable various endpoint specific interrupts |
| DOEPMSK::Get().FromValue(0). |
| set_setup(1). |
| set_stsphsercvd(1). |
| set_xfercompl(1). |
| set_ahberr(1). |
| set_epdisabled(1). |
| WriteTo(mmio); |
| DIEPMSK::Get().FromValue(0). |
| set_xfercompl(1). |
| set_timeout(1). |
| set_ahberr(1). |
| set_epdisabled(1). |
| WriteTo(mmio); |
| |
| // Clear device address |
| DCFG::Get() |
| .ReadFrom(mmio) |
| .set_devaddr(0) |
| .WriteTo(mmio); |
| |
| zxlogf(INFO, "%s\n", __func__); |
| SetConnected(false); |
| } |
| |
| // Handler for usbsuspend interrupt. |
| void Dwc2::HandleSuspend() { |
| zxlogf(INFO, "%s\n", __func__); |
| SetConnected(false); |
| } |
| |
| // Handler for enumdone interrupt. |
| void Dwc2::HandleEnumDone() { |
| SetConnected(true); |
| |
| auto* mmio = get_mmio(); |
| |
| ep0_state_ = Ep0State::IDLE; |
| |
| endpoints_[DWC_EP0_IN].max_packet_size = 64; |
| endpoints_[DWC_EP0_OUT].max_packet_size = 64; |
| endpoints_[DWC_EP0_IN].phys = static_cast<uint32_t>(ep0_buffer_.phys()); |
| endpoints_[DWC_EP0_OUT].phys = static_cast<uint32_t>(ep0_buffer_.phys()); |
| |
| DEPCTL0::Get(DWC_EP0_IN) |
| .ReadFrom(mmio) |
| .set_mps(DEPCTL0::MPS_64) |
| .WriteTo(mmio); |
| DEPCTL0::Get(DWC_EP0_OUT) |
| .ReadFrom(mmio) |
| .set_mps(DEPCTL0::MPS_64) |
| .WriteTo(mmio); |
| |
| DCTL::Get() |
| .ReadFrom(mmio) |
| .set_cgnpinnak(1) |
| .WriteTo(mmio); |
| |
| GUSBCFG::Get() |
| .ReadFrom(mmio) |
| .set_usbtrdtim(metadata_.usb_turnaround_time) |
| .WriteTo(mmio); |
| |
| if (dci_intf_) { |
| dci_intf_->SetSpeed(USB_SPEED_HIGH); |
| } |
| StartEp0(); |
| } |
| |
| // Handler for inepintr interrupt. |
| void Dwc2::HandleInEpInterrupt() { |
| auto* mmio = get_mmio(); |
| uint8_t ep_num = 0; |
| |
| // Read bits indicating which endpoints have inepintr active |
| uint32_t ep_bits = DAINT::Get().ReadFrom(mmio).reg_value(); |
| ep_bits &= DAINTMSK::Get().ReadFrom(mmio).reg_value(); |
| ep_bits &= DWC_EP_IN_MASK; |
| |
| // Acknowledge the endpoint bits |
| DAINT::Get() |
| .FromValue(DWC_EP_IN_MASK) |
| .WriteTo(mmio); |
| |
| // Loop through IN endpoints and handle those with interrupt raised |
| while (ep_bits) { |
| if (ep_bits & 1) { |
| auto diepint = DIEPINT::Get(ep_num).ReadFrom(mmio); |
| diepint.set_reg_value(diepint.reg_value() & DIEPMSK::Get().ReadFrom(mmio).reg_value()); |
| |
| if (diepint.xfercompl()) { |
| DIEPINT::Get(ep_num) |
| .FromValue(0) |
| .set_xfercompl(1) |
| .WriteTo(mmio); |
| |
| if (ep_num == DWC_EP0_IN) { |
| HandleEp0TransferComplete(); |
| } else { |
| HandleTransferComplete(ep_num); |
| if (diepint.nak()) { |
| zxlogf(ERROR, "Unandled interrupt diepint.nak ep_num %u\n", ep_num); |
| DIEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_nak(1) |
| .WriteTo(mmio); |
| } |
| } |
| } |
| |
| // TODO(voydanoff) Implement error recovery for these interrupts |
| if (diepint.epdisabled()) { |
| zxlogf(ERROR, "Unandled interrupt diepint.epdisabled for ep_num %u\n", ep_num); |
| DIEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_epdisabled(1) |
| .WriteTo(mmio); |
| } |
| if (diepint.ahberr()) { |
| zxlogf(ERROR, "Unandled interrupt diepint.ahberr for ep_num %u\n", ep_num); |
| DIEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_ahberr(1) |
| .WriteTo(mmio); |
| } |
| if (diepint.timeout()) { |
| zxlogf(ERROR, "Unandled interrupt diepint.timeout for ep_num %u\n", ep_num); |
| DIEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_timeout(1) |
| .WriteTo(mmio); |
| } |
| if (diepint.intktxfemp()) { |
| zxlogf(ERROR, "Unandled interrupt diepint.intktxfemp for ep_num %u\n", ep_num); |
| DIEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_intktxfemp(1) |
| .WriteTo(mmio); |
| } |
| if (diepint.intknepmis()) { |
| zxlogf(ERROR, "Unhandled interrupt diepint.intknepmis for ep_num %u\n", ep_num); |
| DIEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_intknepmis(1) |
| .WriteTo(mmio); |
| } |
| if (diepint.inepnakeff()) { |
| printf("Unandled interrupt diepint.inepnakeff for ep_num %u\n", ep_num); |
| DIEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_inepnakeff(1) |
| .WriteTo(mmio); |
| } |
| } |
| ep_num++; |
| ep_bits >>= 1; |
| } |
| } |
| |
| // Handler for outepintr interrupt. |
| void Dwc2::HandleOutEpInterrupt() { |
| auto* mmio = get_mmio(); |
| |
| uint8_t ep_num = DWC_EP0_OUT; |
| |
| // Read bits indicating which endpoints have inepintr active |
| auto ep_bits = DAINT::Get().ReadFrom(mmio).reg_value(); |
| auto ep_mask = DAINTMSK::Get().ReadFrom(mmio).reg_value(); |
| ep_bits &= ep_mask; |
| ep_bits &= DWC_EP_OUT_MASK; |
| ep_bits >>= DWC_EP_OUT_SHIFT; |
| |
| // Acknowledge the endpoint bits |
| DAINT::Get() |
| .FromValue(DWC_EP_OUT_MASK) |
| .WriteTo(mmio); |
| |
| // Loop through OUT endpoints and handle those with interrupt raised |
| while (ep_bits) { |
| if (ep_bits & 1) { |
| auto doepint = DOEPINT::Get(ep_num).ReadFrom(mmio); |
| doepint.set_reg_value(doepint.reg_value() & DOEPMSK::Get().ReadFrom(mmio).reg_value()); |
| |
| if (doepint.sr()) { |
| DOEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_sr(1) |
| .WriteTo(mmio); |
| } |
| |
| if (doepint.stsphsercvd()) { |
| DOEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_stsphsercvd(1) |
| .WriteTo(mmio); |
| } |
| |
| if (doepint.setup()) { |
| // TODO(voydanoff): On this interrupt, the application must read the DOEPTSIZn |
| // register to determine the number of SETUP packets received and process the last |
| // received SETUP packet. |
| DOEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_setup(1) |
| .WriteTo(mmio); |
| |
| memcpy(&cur_setup_, ep0_buffer_.virt(), sizeof(cur_setup_)); |
| zxlogf(LINFO, "SETUP bmRequestType: 0x%02x bRequest: %u wValue: %u wIndex: %u " |
| "wLength: %u\n", cur_setup_.bmRequestType, cur_setup_.bRequest, |
| cur_setup_.wValue, cur_setup_.wIndex, cur_setup_.wLength); |
| |
| HandleEp0Setup(); |
| } |
| if (doepint.xfercompl()) { |
| DOEPINT::Get(ep_num) |
| .FromValue(0) |
| .set_xfercompl(1) |
| .WriteTo(mmio); |
| |
| if (ep_num == DWC_EP0_OUT) { |
| if (!doepint.setup()) { |
| HandleEp0TransferComplete(); |
| } |
| } else { |
| HandleTransferComplete(ep_num); |
| } |
| } |
| // TODO(voydanoff) Implement error recovery for these interrupts |
| if (doepint.epdisabled()) { |
| zxlogf(ERROR, "Unhandled interrupt doepint.epdisabled for ep_num %u\n", ep_num); |
| DOEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_epdisabled(1) |
| .WriteTo(mmio); |
| } |
| if (doepint.ahberr()) { |
| zxlogf(ERROR, "Unhandled interrupt doepint.ahberr for ep_num %u\n", ep_num); |
| DOEPINT::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_ahberr(1) |
| .WriteTo(mmio); |
| } |
| } |
| ep_num++; |
| ep_bits >>= 1; |
| } |
| } |
| |
| // Handles setup requests from the host. |
| zx_status_t Dwc2::HandleSetupRequest(size_t* out_actual) { |
| zx_status_t status; |
| |
| auto* setup = &cur_setup_; |
| auto* buffer = ep0_buffer_.virt(); |
| |
| if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) { |
| // Handle some special setup requests in this driver |
| switch (setup->bRequest) { |
| case USB_REQ_SET_ADDRESS: |
| zxlogf(LINFO, "SET_ADDRESS %d\n", setup->wValue); |
| SetAddress(static_cast<uint8_t>(setup->wValue)); |
| *out_actual = 0; |
| return ZX_OK; |
| case USB_REQ_SET_CONFIGURATION: |
| zxlogf(LINFO, "SET_CONFIGURATION %d\n", setup->wValue); |
| configured_ = true; |
| if (dci_intf_) { |
| status = dci_intf_->Control(setup, nullptr, 0, nullptr, 0, out_actual); |
| } else { |
| status = ZX_ERR_NOT_SUPPORTED; |
| } |
| if (status == ZX_OK && setup->wValue) { |
| StartEndpoints(); |
| } else { |
| configured_ = false; |
| } |
| return status; |
| default: |
| // fall through to dci_intf_->Control() |
| break; |
| } |
| } |
| |
| bool is_in = ((setup->bmRequestType & USB_DIR_MASK) == USB_DIR_IN); |
| auto length = le16toh(setup->wLength); |
| |
| if (dci_intf_) { |
| if (length == 0) { |
| status = dci_intf_->Control(setup, nullptr, 0, nullptr, 0, out_actual); |
| } else if (is_in) { |
| status = dci_intf_->Control(setup, nullptr, 0, buffer, length, out_actual); |
| } else { |
| status = ZX_ERR_NOT_SUPPORTED; |
| } |
| } else { |
| status = ZX_ERR_NOT_SUPPORTED; |
| } |
| if (status == ZX_OK) { |
| auto* ep = &endpoints_[DWC_EP0_OUT]; |
| ep->req_offset = 0; |
| if (is_in) { |
| ep->req_length = static_cast<uint32_t>(*out_actual); |
| } |
| } |
| return status; |
| } |
| |
| // Programs the device address received from the SET_ADDRESS command from the host |
| void Dwc2::SetAddress(uint8_t address) { |
| auto* mmio = get_mmio(); |
| |
| DCFG::Get() |
| .ReadFrom(mmio) |
| .set_devaddr(address) |
| .WriteTo(mmio); |
| } |
| |
| // Reads number of bytes transfered on specified endpoint |
| uint32_t Dwc2::ReadTransfered(Endpoint* ep) { |
| auto* mmio = get_mmio(); |
| return ep->req_xfersize - DEPTSIZ::Get(ep->ep_num).ReadFrom(mmio).xfersize(); |
| } |
| |
| // Prepares to receive next control request on endpoint zero. |
| void Dwc2::StartEp0() { |
| auto* mmio = get_mmio(); |
| auto* ep = &endpoints_[DWC_EP0_OUT]; |
| ep->req_offset = 0; |
| ep->req_xfersize = 3 * sizeof(usb_setup_t); |
| |
| ep0_buffer_.CacheFlushInvalidate(0, sizeof(cur_setup_)); |
| |
| DEPDMA::Get(DWC_EP0_OUT) |
| .FromValue(0) |
| .set_addr(static_cast<uint32_t>(ep0_buffer_.phys())) |
| .WriteTo(get_mmio()); |
| |
| DEPTSIZ0::Get(DWC_EP0_OUT) |
| .FromValue(0) |
| .set_supcnt(3) |
| .set_pktcnt(1) |
| .set_xfersize(ep->req_xfersize) |
| .WriteTo(mmio); |
| hw_wmb(); |
| |
| DEPCTL::Get(DWC_EP0_OUT) |
| .ReadFrom(mmio) |
| .set_epena(1) |
| .WriteTo(mmio); |
| hw_wmb(); |
| } |
| |
| // Queues the next USB request for the specified endpoint |
| void Dwc2::QueueNextRequest(Endpoint* ep) { |
| std::optional<Request> req; |
| |
| if (ep->current_req == nullptr) { |
| req = ep->queued_reqs.pop(); |
| } |
| |
| if (req.has_value()) { |
| auto* usb_req = req->take(); |
| ep->current_req = usb_req; |
| |
| phys_iter_t iter; |
| zx_paddr_t phys; |
| usb_request_physmap(usb_req, bti_.get()); |
| usb_request_phys_iter_init(&iter, usb_req, PAGE_SIZE); |
| usb_request_phys_iter_next(&iter, &phys); |
| ep->phys = static_cast<uint32_t>(phys); |
| |
| ep->req_offset = 0; |
| ep->req_length = static_cast<uint32_t>(usb_req->header.length); |
| StartTransfer(ep, ep->req_length); |
| } |
| } |
| |
| void Dwc2::StartTransfer(Endpoint* ep, uint32_t length) { |
| auto ep_num = ep->ep_num; |
| auto* mmio = get_mmio(); |
| bool is_in = DWC_EP_IS_IN(ep_num); |
| |
| if (length > 0) { |
| if (is_in) { |
| if (ep_num == DWC_EP0_IN) { |
| ep0_buffer_.CacheFlush(ep->req_offset, length); |
| } else { |
| usb_request_cache_flush(ep->current_req, ep->req_offset, length); |
| } |
| } else { |
| if (ep_num == DWC_EP0_OUT) { |
| ep0_buffer_.CacheFlushInvalidate(ep->req_offset, length); |
| } else { |
| usb_request_cache_flush_invalidate(ep->current_req, ep->req_offset, length); |
| } |
| } |
| } |
| |
| // Program DMA address |
| DEPDMA::Get(ep_num) |
| .FromValue(0) |
| .set_addr(ep->phys + ep->req_offset) |
| .WriteTo(mmio); |
| |
| uint32_t ep_mps = ep->max_packet_size; |
| auto deptsiz = DEPTSIZ::Get(ep_num).FromValue(0); |
| |
| if (length == 0) { |
| deptsiz.set_xfersize(is_in ? 0 : ep_mps); |
| deptsiz.set_pktcnt(1); |
| } else { |
| deptsiz.set_pktcnt((length + (ep_mps - 1)) / ep_mps); |
| deptsiz.set_xfersize(length); |
| } |
| deptsiz.set_mc(is_in ? 1 : 0); |
| ep->req_xfersize = deptsiz.xfersize(); |
| deptsiz.WriteTo(mmio); |
| hw_wmb(); |
| |
| DEPCTL::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_cnak(1) |
| .set_epena(1) |
| .WriteTo(mmio); |
| hw_wmb(); |
| } |
| |
| void Dwc2::FlushTxFifo(uint32_t fifo_num) { |
| auto* mmio = get_mmio(); |
| |
| auto grstctl = GRSTCTL::Get() |
| .FromValue(0) |
| .set_txfflsh(1) |
| .set_txfnum(fifo_num) |
| .WriteTo(mmio); |
| |
| uint32_t count = 0; |
| do { |
| grstctl.ReadFrom(mmio); |
| // Retry count of 10000 comes from Amlogic bootloader driver. |
| if (++count > 10000) |
| break; |
| } while (grstctl.txfflsh() == 1); |
| |
| zx::nanosleep(zx::deadline_after(zx::usec(1))); |
| |
| grstctl.set_reg_value(0) |
| .set_rxfflsh(1) |
| .WriteTo(mmio); |
| |
| count = 0; |
| do { |
| grstctl.ReadFrom(mmio); |
| if (++count > 10000) |
| break; |
| } while (grstctl.rxfflsh() == 1); |
| |
| zx::nanosleep(zx::deadline_after(zx::usec(1))); |
| } |
| |
| void Dwc2::FlushRxFifo() { |
| auto* mmio = get_mmio(); |
| auto grstctl = GRSTCTL::Get() |
| .FromValue(0) |
| .set_rxfflsh(1) |
| .WriteTo(mmio); |
| |
| uint32_t count = 0; |
| do { |
| grstctl.ReadFrom(mmio); |
| if (++count > 10000) |
| break; |
| } while (grstctl.rxfflsh() == 1); |
| |
| zx::nanosleep(zx::deadline_after(zx::usec(1))); |
| |
| grstctl.set_reg_value(0) |
| .set_rxfflsh(1) |
| .WriteTo(mmio); |
| |
| count = 0; |
| do { |
| grstctl.ReadFrom(mmio); |
| if (++count > 10000) |
| break; |
| } while (grstctl.rxfflsh() == 1); |
| |
| zx::nanosleep(zx::deadline_after(zx::usec(1))); |
| } |
| |
| void Dwc2::StartEndpoints() { |
| for (uint8_t ep_num = 1; ep_num < fbl::count_of(endpoints_); ep_num++) { |
| auto* ep = &endpoints_[ep_num]; |
| if (ep->enabled) { |
| EnableEp(ep_num, true); |
| |
| fbl::AutoLock lock(&ep->lock); |
| QueueNextRequest(ep); |
| } |
| } |
| } |
| |
| void Dwc2::EnableEp(uint8_t ep_num, bool enable) { |
| auto* mmio = get_mmio(); |
| |
| fbl::AutoLock lock(&lock_); |
| |
| uint32_t bit = 1 << ep_num; |
| |
| auto mask = DAINTMSK::Get().ReadFrom(mmio).reg_value(); |
| if (enable) { |
| auto daint = DAINT::Get().ReadFrom(mmio).reg_value(); |
| daint |= bit; |
| DAINT::Get().FromValue(daint).WriteTo(mmio); |
| mask |= bit; |
| } else { |
| mask &= ~bit; |
| } |
| DAINTMSK::Get().FromValue(mask).WriteTo(mmio); |
| } |
| |
| void Dwc2::HandleEp0Setup() { |
| auto* setup = &cur_setup_; |
| |
| auto length = letoh16(setup->wLength); |
| bool is_in = ((setup->bmRequestType & USB_DIR_MASK) == USB_DIR_IN); |
| size_t actual = 0; |
| |
| // No data to read, can handle setup now |
| if (length == 0 || is_in) { |
| // TODO(voydanoff) stall if this fails (after we implement stalling) |
| __UNUSED zx_status_t _ = HandleSetupRequest(&actual); |
| } |
| |
| if (length > 0) { |
| if (is_in) { |
| ep0_state_ = Ep0State::DATA_IN; |
| // send data in |
| auto* ep = &endpoints_[DWC_EP0_IN]; |
| ep->req_offset = 0; |
| ep->req_length = static_cast<uint32_t>(actual); |
| fbl::AutoLock al(&ep->lock); |
| StartTransfer(ep, (ep->req_length > 127 ? ep->max_packet_size : ep->req_length)); |
| } else { |
| ep0_state_ = Ep0State::DATA_OUT; |
| // queue a read for the data phase |
| ep0_state_ = Ep0State::DATA_OUT; |
| auto* ep = &endpoints_[DWC_EP0_OUT]; |
| ep->req_offset = 0; |
| ep->req_length = length; |
| fbl::AutoLock al(&ep->lock); |
| StartTransfer(ep, (length > 127 ? ep->max_packet_size : length)); |
| } |
| } else { |
| // no data phase |
| // status in IN direction |
| HandleEp0Status(true); |
| } |
| } |
| |
| // Handles status phase of a setup request |
| void Dwc2::HandleEp0Status(bool is_in) { |
| ep0_state_ = (is_in ? Ep0State::STATUS_IN : Ep0State::STATUS_OUT); |
| uint8_t ep_num = (is_in ? DWC_EP0_IN : DWC_EP0_OUT); |
| auto* ep = &endpoints_[ep_num]; |
| fbl::AutoLock al(&ep->lock); |
| StartTransfer(ep, 0); |
| |
| if (is_in) { |
| StartEp0(); |
| } |
| } |
| |
| // Handles transfer complete events for endpoint zero |
| void Dwc2::HandleEp0TransferComplete() { |
| switch (ep0_state_) { |
| case Ep0State::IDLE: { |
| StartEp0(); |
| break; |
| } |
| case Ep0State::DATA_IN: { |
| auto* ep = &endpoints_[DWC_EP0_IN]; |
| auto transfered = ReadTransfered(ep); |
| ep->req_offset += transfered; |
| |
| if (ep->req_offset == ep->req_length) { |
| HandleEp0Status(false); |
| } else { |
| auto length = ep->req_length - ep->req_offset; |
| if (length > 64) { |
| length = 64; |
| } |
| fbl::AutoLock al(&ep->lock); |
| StartTransfer(ep, length); |
| } |
| break; |
| } |
| case Ep0State::DATA_OUT: { |
| auto* ep = &endpoints_[DWC_EP0_OUT]; |
| auto transfered = ReadTransfered(ep); |
| ep->req_offset += transfered; |
| |
| if (ep->req_offset == ep->req_length) { |
| if (dci_intf_) { |
| size_t actual; |
| dci_intf_->Control(&cur_setup_, (uint8_t*)ep0_buffer_.virt(), ep->req_length, |
| nullptr, 0, &actual); |
| } |
| HandleEp0Status(true); |
| } else { |
| auto length = ep->req_length - ep->req_offset; |
| // Strangely, the controller can transfer up to 127 bytes in a single transaction. |
| // But if length is > 127, the transfer must be done in multiple chunks, and those |
| // chunks must be 64 bytes long. |
| if (length > 127) { |
| length = 64; |
| } |
| fbl::AutoLock al(&ep->lock); |
| StartTransfer(ep, length); |
| } |
| break; |
| } |
| case Ep0State::STATUS_OUT: |
| ep0_state_ = Ep0State::IDLE; |
| StartEp0(); |
| break; |
| case Ep0State::STATUS_IN: |
| ep0_state_ = Ep0State::IDLE; |
| break; |
| case Ep0State::STALL: |
| default: |
| zxlogf(ERROR, "EP0 state is %d, should not get here\n", static_cast<int>(ep0_state_)); |
| break; |
| } |
| } |
| |
| // Handles transfer complete events for endpoints other than endpoint zero |
| void Dwc2::HandleTransferComplete(uint8_t ep_num) { |
| ZX_DEBUG_ASSERT(ep_num != DWC_EP0_IN && ep_num != DWC_EP0_OUT); |
| auto* ep = &endpoints_[ep_num]; |
| |
| ep->lock.Acquire(); |
| |
| auto transfered = ReadTransfered(ep); |
| |
| ep->req_offset += transfered; |
| |
| usb_request_t* req = ep->current_req; |
| if (req) { |
| Request request(req, sizeof(usb_request_t)); |
| ep->lock.Release(); |
| request.Complete(ZX_OK, ep->req_offset); |
| ep->lock.Acquire(); |
| |
| ep->current_req = nullptr; |
| QueueNextRequest(ep); |
| } |
| ep->lock.Release(); |
| } |
| |
| zx_status_t Dwc2::InitController() { |
| auto* mmio = get_mmio(); |
| |
| auto gsnpsid = GSNPSID::Get().ReadFrom(mmio).reg_value(); |
| if (gsnpsid != 0x4f54400a && gsnpsid != 0x4f54330a) { |
| zxlogf(WARN, "DWC2 driver has not been tested with IP version 0x%08x. " |
| "The IP has quirks, so things may not work as expected\n", |
| gsnpsid); |
| } |
| |
| auto ghwcfg2 = GHWCFG2::Get().ReadFrom(mmio); |
| if (!ghwcfg2.dynamic_fifo()) { |
| zxlogf(ERROR, "DWC2 driver requires dynamic FIFO support\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| auto grstctl = GRSTCTL::Get(); |
| while (grstctl.ReadFrom(mmio).ahbidle() == 0) { |
| zx::nanosleep(zx::deadline_after(zx::msec(1))); |
| } |
| |
| // Reset the controller |
| grstctl.FromValue(0) |
| .set_csftrst(1) |
| .WriteTo(mmio); |
| |
| // Wait for reset to complete |
| bool done = false; |
| for (int i = 0; i < 1000; i++) { |
| if (grstctl.ReadFrom(mmio).csftrst() == 0) { |
| zx::nanosleep(zx::deadline_after(zx::msec(10))); |
| done = true; |
| break; |
| } |
| zx::nanosleep(zx::deadline_after(zx::msec(1))); |
| } |
| if (!done) { |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| zx::nanosleep(zx::deadline_after(zx::msec(10))); |
| |
| // Configure controller for device mode, enable DMA |
| GUSBCFG::Get() |
| .ReadFrom(mmio) |
| .set_force_dev_mode(0) |
| .set_srpcap(1) |
| .set_hnpcap(1) |
| .set_ulpi_utmi_sel(1) |
| .set_phyif(0) |
| .set_ddrsel(0) |
| .WriteTo(mmio); |
| GAHBCFG::Get() |
| .FromValue(0) |
| .set_dmaenable(1) |
| .set_hburstlen(metadata_.dma_burst_len) |
| .set_nptxfemplvl_txfemplvl(1) |
| .WriteTo(mmio); |
| |
| // Set turnaround time based on metadata |
| GUSBCFG::Get() |
| .ReadFrom(mmio) |
| .set_usbtrdtim(metadata_.usb_turnaround_time) |
| .WriteTo(mmio); |
| DCFG::Get() |
| .ReadFrom(mmio) |
| .set_devaddr(0) |
| .set_epmscnt(2) |
| .set_descdma(0) |
| .set_devspd(0) |
| .set_perfrint(DCFG::PERCENT_80) |
| .WriteTo(mmio); |
| |
| DCTL::Get() |
| .ReadFrom(mmio) |
| .set_sftdiscon(1) |
| .WriteTo(mmio); |
| DCTL::Get() |
| .ReadFrom(mmio) |
| .set_sftdiscon(0) |
| .WriteTo(mmio); |
| |
| // Reset phy clock |
| PCGCCTL::Get() |
| .FromValue(0) |
| .WriteTo(mmio); |
| |
| // Set RX and non-periodic TX fifo sizes based on metadata. |
| GRXFSIZ::Get() |
| .FromValue(0) |
| .set_size(metadata_.rx_fifo_size) |
| .WriteTo(mmio); |
| GNPTXFSIZ::Get() |
| .FromValue(0) |
| .set_depth(metadata_.nptx_fifo_size) |
| .set_startaddr(metadata_.rx_fifo_size) |
| .WriteTo(mmio); |
| |
| |
| // Reset dynamic FIFO values for periodic endpoints. |
| next_dfifo_ = 1; |
| dfifo_base_ = metadata_.rx_fifo_size + metadata_.nptx_fifo_size; |
| dfifo_end_ = GHWCFG3::Get().ReadFrom(mmio).dfifo_depth(); |
| |
| GDFIFOCFG::Get() |
| .FromValue(0) |
| .set_gdfifocfg(dfifo_end_) |
| .set_epinfobase(dfifo_base_) |
| .WriteTo(mmio); |
| |
| // Flush all FIFOs |
| FlushTxFifo(0x10); |
| FlushRxFifo(); |
| |
| GRSTCTL::Get() |
| .FromValue(0) |
| .set_intknqflsh(1) |
| .WriteTo(mmio); |
| |
| // Clear all pending device interrupts |
| DIEPMSK::Get() |
| .FromValue(0) |
| .WriteTo(mmio); |
| DOEPMSK::Get() |
| .FromValue(0) |
| .WriteTo(mmio); |
| DAINT::Get() |
| .FromValue(0xFFFFFFFF) |
| .WriteTo(mmio); |
| DAINTMSK::Get() |
| .FromValue(0) |
| .WriteTo(mmio); |
| |
| for (uint32_t i = 0; i < DWC_MAX_EPS; i++) { |
| DEPCTL::Get(i) |
| .FromValue(0) |
| .WriteTo(mmio); |
| DEPTSIZ::Get(i) |
| .FromValue(0) |
| .WriteTo(mmio); |
| } |
| |
| // Clear all pending OTG and global interrupts |
| GOTGINT::Get() |
| .FromValue(0xFFFFFFFF) |
| .WriteTo(mmio); |
| GINTSTS::Get() |
| .FromValue(0xFFFFFFFF) |
| .WriteTo(mmio); |
| |
| // Enable selected global interrupts |
| GINTMSK::Get() |
| .FromValue(0) |
| .set_usbreset(1) |
| .set_enumdone(1) |
| .set_inepintr(1) |
| .set_outepintr(1) |
| .set_usbsuspend(1) |
| .set_erlysuspend(1) |
| .WriteTo(mmio); |
| |
| // Enable global interrupts |
| GAHBCFG::Get() |
| .ReadFrom(mmio) |
| .set_glblintrmsk(1) |
| .WriteTo(mmio); |
| |
| return ZX_OK; |
| } |
| |
| void Dwc2::SetConnected(bool connected) { |
| // if (connected == connected_) { |
| // return; |
| // } |
| |
| if (dci_intf_) { |
| dci_intf_->SetConnected(connected); |
| } |
| if (usb_phy_) { |
| usb_phy_->ConnectStatusChanged(connected); |
| } |
| |
| // Complete any pending requests |
| if (!connected) { |
| for (uint8_t i = 0; i < fbl::count_of(endpoints_); i++) { |
| auto* ep = &endpoints_[i]; |
| RequestQueue complete_reqs; |
| |
| { |
| fbl::AutoLock lock(&ep->lock); |
| if (ep->current_req) { |
| complete_reqs.push(Request(ep->current_req, sizeof(usb_request_t))); |
| ep->current_req = nullptr; |
| } |
| for (auto req = ep->queued_reqs.pop(); req; req = ep->queued_reqs.pop()) { |
| complete_reqs.push(std::move(*req)); |
| } |
| |
| } |
| // Requests must be completed outside of the lock. |
| for (auto req = complete_reqs.pop(); req; req = complete_reqs.pop()) { |
| req->Complete(ZX_ERR_IO_NOT_PRESENT, 0); |
| } |
| } |
| } |
| |
| connected_ = connected; |
| } |
| |
| zx_status_t Dwc2::Create(void* ctx, zx_device_t* parent) { |
| auto dev = std::make_unique<Dwc2>(parent); |
| auto status = dev->Init(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // devmgr is now in charge of the device. |
| __UNUSED auto* _ = dev.release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t Dwc2::Init() { |
| ddk::CompositeProtocolClient composite(parent()); |
| if (!composite.is_valid()) { |
| zxlogf(ERROR, "Dwc2::Create could not get composite protocol\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_device_t* components[2]; |
| size_t actual; |
| |
| // Retrieve platform device protocol from our first component. |
| composite.GetComponents(components, fbl::count_of(components), &actual); |
| if (actual < fbl::count_of(components)) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| pdev_ = components[0]; |
| if (!pdev_.is_valid()) { |
| zxlogf(ERROR, "Dwc2::Create: could not get platform device protocol\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // USB PHY protocol is optional. |
| usb_phy_ = components[1]; |
| if (!usb_phy_->is_valid()) { |
| usb_phy_.reset(); |
| } |
| |
| for (uint8_t i = 0; i < fbl::count_of(endpoints_); i++) { |
| auto* ep = &endpoints_[i]; |
| ep->ep_num = i; |
| } |
| |
| auto status = DdkGetMetadata(DEVICE_METADATA_PRIVATE, &metadata_, sizeof(metadata_), &actual); |
| if (status != ZX_OK || actual != sizeof(metadata_)) { |
| zxlogf(ERROR, "Dwc2::Init can't get driver metadata\n"); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| status = pdev_.MapMmio(0, &mmio_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Dwc2::Init MapMmio failed: %d\n", status); |
| return status; |
| } |
| |
| status = pdev_.GetInterrupt(0, &irq_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Dwc2::Init GetInterrupt failed: %d\n", status); |
| return status; |
| } |
| |
| status = pdev_.GetBti(0, &bti_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Dwc2::Init GetBti failed: %d\n", status); |
| return status; |
| } |
| |
| status = ep0_buffer_.Init(bti_.get(), UINT16_MAX, IO_BUFFER_RW | IO_BUFFER_CONTIG); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Dwc2::Init ep0_buffer_.Init failed: %d\n", status); |
| return status; |
| } |
| |
| status = ep0_buffer_.PhysMap(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Dwc2::Init ep0_buffer_.PhysMap failed: %d\n", status); |
| return status; |
| } |
| |
| if ((status = InitController()) != ZX_OK) { |
| zxlogf(ERROR, "Dwc2::Init InitController failed: %d\n", status); |
| return status; |
| } |
| |
| status = DdkAdd("dwc2"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Dwc2::Init DdkAdd failed: %d\n", status); |
| return status; |
| } |
| |
| int rc = thrd_create_with_name(&irq_thread_, |
| [](void* arg) -> int { |
| return reinterpret_cast<Dwc2*>(arg)->IrqThread(); |
| }, |
| reinterpret_cast<void*>(this), |
| "dwc2-interrupt-thread"); |
| if (rc != thrd_success) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| return ZX_OK; |
| } |
| |
| int Dwc2::IrqThread() { |
| auto* mmio = get_mmio(); |
| |
| while (1) { |
| auto wait_res = irq_.wait(nullptr); |
| if (wait_res != ZX_OK) { |
| zxlogf(ERROR, "dwc_usb: irq wait failed, retcode = %d\n", wait_res); |
| } |
| |
| // It doesn't seem that this inner loop should be necessary, |
| // but without it we miss interrupts on some versions of the IP. |
| while (1) { |
| auto gintsts = GINTSTS::Get().ReadFrom(mmio); |
| auto gintmsk = GINTMSK::Get().ReadFrom(mmio); |
| gintsts.WriteTo(mmio); |
| gintsts.set_reg_value(gintsts.reg_value() & gintmsk.reg_value()); |
| |
| if (gintsts.reg_value() == 0) { |
| break; |
| } |
| |
| if (gintsts.usbreset()) { |
| HandleReset(); |
| } |
| if (gintsts.usbsuspend()) { |
| printf("usbsuspend\n"); |
| HandleSuspend(); |
| } |
| if (gintsts.erlysuspend()) { |
| printf("erlysuspend\n"); |
| HandleSuspend(); |
| } |
| if (gintsts.enumdone()) { |
| HandleEnumDone(); |
| } |
| if (gintsts.inepintr()) { |
| HandleInEpInterrupt(); |
| } |
| if (gintsts.outepintr()) { |
| HandleOutEpInterrupt(); |
| } |
| } |
| } |
| |
| zxlogf(INFO, "dwc_usb: irq thread finished\n"); |
| return 0; |
| } |
| |
| void Dwc2::DdkUnbind() { |
| irq_.destroy(); |
| thrd_join(irq_thread_, nullptr); |
| } |
| |
| void Dwc2::DdkRelease() { |
| delete this; |
| } |
| |
| void Dwc2::UsbDciRequestQueue(usb_request_t* req, const usb_request_complete_t* cb) { |
| uint8_t ep_num = DWC_ADDR_TO_INDEX(req->header.ep_address); |
| if (ep_num == DWC_EP0_IN || ep_num == DWC_EP0_OUT || ep_num >= fbl::count_of(endpoints_)) { |
| zxlogf(ERROR, "Dwc2::UsbDciRequestQueue: bad ep address 0x%02X\n", req->header.ep_address); |
| usb_request_complete(req, ZX_ERR_INVALID_ARGS, 0, cb); |
| return; |
| } |
| zxlogf(LTRACE, "UsbDciRequestQueue ep %u length %zu\n", ep_num, req->header.length); |
| |
| auto* ep = &endpoints_[ep_num]; |
| |
| if (!ep->enabled) { |
| zxlogf(ERROR, "Dwc2::UsbDciRequestQueue: endpoint 0x%02X not enabled\n", |
| req->header.ep_address); |
| return; |
| } |
| |
| // OUT transactions must have length > 0 and multiple of max packet size |
| if (DWC_EP_IS_OUT(ep_num)) { |
| if (req->header.length == 0 || req->header.length % ep->max_packet_size != 0) { |
| zxlogf(ERROR, "dwc_ep_queue: OUT transfers must be multiple of max packet size\n"); |
| usb_request_complete(req, ZX_ERR_INVALID_ARGS, 0, cb); |
| return; |
| } |
| } |
| |
| fbl::AutoLock lock(&ep->lock); |
| |
| if (!ep->enabled) { |
| zxlogf(ERROR, "dwc_ep_queue ep not enabled!\n"); |
| usb_request_complete(req, ZX_ERR_BAD_STATE, 0, cb); |
| return; |
| } |
| |
| if (!configured_) { |
| zxlogf(ERROR, "dwc_ep_queue not configured!\n"); |
| usb_request_complete(req, ZX_ERR_BAD_STATE, 0, cb); |
| return; |
| } |
| |
| ep->queued_reqs.push(Request(req, *cb, sizeof(usb_request_t))); |
| QueueNextRequest(ep); |
| } |
| |
| zx_status_t Dwc2::UsbDciSetInterface(const usb_dci_interface_protocol_t* interface) { |
| if (dci_intf_) { |
| zxlogf(ERROR, "%s: dci_intf_ already set\n", __func__); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| dci_intf_ = ddk::UsbDciInterfaceProtocolClient(interface); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Dwc2::UsbDciConfigEp(const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_comp_desc) { |
| auto* mmio = get_mmio(); |
| |
| uint8_t ep_num = DWC_ADDR_TO_INDEX(ep_desc->bEndpointAddress); |
| if (ep_num == DWC_EP0_IN || ep_num == DWC_EP0_OUT || ep_num >= fbl::count_of(endpoints_)) { |
| zxlogf(ERROR, "Dwc2::UsbDciConfigEp: bad ep address 0x%02X\n", ep_desc->bEndpointAddress); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| bool is_in = (ep_desc->bEndpointAddress & USB_DIR_MASK) == USB_DIR_IN; |
| uint8_t ep_type = usb_ep_type(ep_desc); |
| uint16_t max_packet_size = usb_ep_max_packet(ep_desc); |
| |
| if (ep_type == USB_ENDPOINT_ISOCHRONOUS) { |
| zxlogf(ERROR, "Dwc2::UsbDciConfigEp: isochronous endpoints are not supported\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| uint32_t txfnum = 0; |
| // Allocate FIFO space for periodic IN endpoints |
| if (is_in && ep_type != USB_ENDPOINT_BULK) { |
| fbl::AutoLock lock(&lock_); |
| |
| // FIFO is in 4 byte word units |
| uint32_t fifo_size = max_packet_size / 4; |
| if (fifo_size > dfifo_end_ - dfifo_base_) { |
| zxlogf(ERROR, "Dwc2::UsbDciConfigEp: out of fifo memory\n"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| txfnum = next_dfifo_++; |
| |
| DTXFSIZ::Get(txfnum) |
| .FromValue(0) |
| .set_startaddr(dfifo_base_) |
| .set_depth(fifo_size) |
| .WriteTo(mmio); |
| dfifo_base_ += fifo_size; |
| GDFIFOCFG::Get() |
| .FromValue(0) |
| .set_gdfifocfg(dfifo_end_) |
| .set_epinfobase(dfifo_base_) |
| .WriteTo(mmio); |
| |
| // Flush all TX fifos |
| FlushTxFifo(0x10); |
| } |
| |
| auto* ep = &endpoints_[ep_num]; |
| fbl::AutoLock lock(&ep->lock); |
| |
| ep->type = ep_type; |
| ep->max_packet_size = max_packet_size; |
| ep->enabled = true; |
| |
| DEPCTL::Get(ep_num) |
| .FromValue(0) |
| .set_mps(ep->max_packet_size) |
| .set_eptype(ep_type) |
| .set_setd0pid(1) |
| .set_txfnum(txfnum) |
| .set_usbactep(1) |
| .WriteTo(mmio); |
| |
| EnableEp(ep_num, true); |
| |
| if (configured_) { |
| QueueNextRequest(ep); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Dwc2::UsbDciDisableEp(uint8_t ep_address) { |
| auto* mmio = get_mmio(); |
| |
| unsigned ep_num = DWC_ADDR_TO_INDEX(ep_address); |
| if (ep_num == DWC_EP0_IN || ep_num == DWC_EP0_OUT || ep_num >= fbl::count_of(endpoints_)) { |
| zxlogf(ERROR, "Dwc2::UsbDciConfigEp: bad ep address 0x%02X\n", ep_address); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| auto* ep = &endpoints_[ep_num]; |
| |
| fbl::AutoLock lock(&ep->lock); |
| |
| DEPCTL::Get(ep_num) |
| .ReadFrom(mmio) |
| .set_usbactep(0) |
| .WriteTo(mmio); |
| ep->enabled = false; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Dwc2::UsbDciEpSetStall(uint8_t ep_address) { |
| // TODO(voydanoff) implement this |
| return ZX_OK; |
| } |
| |
| zx_status_t Dwc2::UsbDciEpClearStall(uint8_t ep_address) { |
| // TODO(voydanoff) implement this |
| return ZX_OK; |
| } |
| |
| size_t Dwc2::UsbDciGetRequestSize() { |
| return Request::RequestSize(sizeof(usb_request_t)); |
| } |
| |
| zx_status_t Dwc2::UsbDciCancelAll(uint8_t ep) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_driver_ops_t driver_ops = [](){ |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = Dwc2::Create; |
| return ops; |
| }(); |
| |
| } // namespace dwc2 |
| |
| // The formatter does not play nice with these macros. |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(dwc2, dwc2::driver_ops, "zircon", "0.1", 4) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GENERIC), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_USB_DWC2), |
| ZIRCON_DRIVER_END(dwc2) |
| // clang-format on |