| // 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 { |
| |
| uint8_t MtUsb::EpAddressToIndex(uint8_t addr) { |
| // map 0x01 -> 0, 0x81 -> 1, 0x02 -> 2, 0x82 -> 3, ... |
| if (addr & USB_ENDPOINT_DIR_MASK) { |
| return static_cast<uint8_t>(2 * (addr & USB_ENDPOINT_NUM_MASK) - 1); |
| } else { |
| return static_cast<uint8_t>(2 * (addr & USB_ENDPOINT_NUM_MASK) - 2); |
| } |
| } |
| |
| #ifdef USE_DMA |
| zx_status_t MtUsb::AllocDmaChannel(Endpoint* ep) { |
| for (uint8_t i = 0; i < DMA_CHANNEL_COUNT; i++) { |
| if (dma_eps_[i] == nullptr) { |
| ep->dma_channel = i; |
| dma_eps_[i] = ep; |
| return ZX_OK; |
| } |
| } |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| void MtUsb::ReleaseDmaChannel(Endpoint* ep) { |
| if (ep->dma_channel < DMA_CHANNEL_COUNT) { |
| dma_eps_[ep->dma_channel] = nullptr; |
| } |
| ep->dma_channel = DMA_CHANNEL_INVALID; |
| } |
| #endif |
| |
| 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::InitEndpoints() { |
| for (uint8_t i = 0; i < countof(eps_); i++) { |
| auto* ep = &eps_[i]; |
| ep->direction = (i & 1 ? EP_IN : EP_OUT); |
| ep->ep_num = static_cast<uint8_t>(i / 2 + 1); |
| |
| list_initialize(&ep->queued_reqs); |
| list_initialize(&ep->complete_reqs); |
| |
| fbl::AutoLock lock(&ep->lock); |
| ep->current_req = nullptr; |
| #ifdef USE_DMA |
| ep->dma_channel = DMA_CHANNEL_INVALID; |
| #endif |
| } |
| } |
| |
| 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() |
| } |
| |
| void 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_) { |
| FADDR::Get() |
| .FromValue(0) |
| .set_function_address(address_) |
| .WriteTo(mmio); |
| set_address_ = false; |
| dci_intf_->SetConnected(true); |
| } |
| |
| if (!csr0.rxpktrdy()) { |
| return; |
| } |
| |
| usb_setup_t* setup = &cur_setup_; |
| size_t actual; |
| FifoRead(0, setup, sizeof(*setup), &actual); |
| if (actual != sizeof(cur_setup_)) { |
| zxlogf(ERROR, "%s: setup read only read %zu bytes\n", __func__, actual); |
| return; |
| } |
| 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; |
| 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) { |
| zxlogf(INFO, "SET_ADDRESS %u\n", setup->wValue); |
| 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) { |
| // TODO error handling |
| zxlogf(ERROR, "%s: USB_REQ_SET_CONFIGURATION Control returned %d\n", __func__, status); |
| return; |
| } |
| 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) { |
| // TODO error handling |
| zxlogf(ERROR, "%s: Control returned %d\n", __func__, status); |
| return; |
| } |
| } |
| |
| 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); |
| csr0.WriteTo(mmio); // ??? |
| |
| // ???? |
| if (ep0_state_ == EP0_IDLE) { |
| return; |
| } |
| } |
| break; |
| } |
| case EP0_READ: |
| printf("case EP0_READ\n"); |
| break; |
| case EP0_WRITE: { |
| if (csr0.txpktrdy()) { |
| return; |
| } |
| 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; |
| |
| printf("req->header.length %zu ep->cur_offset %zu write_length %zu\n", req->header.length, ep->cur_offset, write_length); |
| if (write_length > 0) { |
| #ifdef USE_DMA |
| uint32_t dma_channel = ep->dma_channel; |
| |
| phys_iter_t iter; |
| zx_paddr_t phys; |
| usb_request_physmap(req, bti_.get()); |
| usb_request_phys_iter_init(&iter, req, PAGE_SIZE); |
| usb_request_phys_iter_next(&iter, &phys); |
| // This controller only supports 32-bit addresses |
| ZX_DEBUG_ASSERT(phys < UINT32_MAX); |
| |
| phys += ep->cur_offset; |
| ep->dma_phys = phys; |
| |
| DMA_ADDR::Get(dma_channel) |
| .FromValue(0) |
| .set_addr(static_cast<uint32_t>(phys)) |
| .WriteTo(mmio); |
| |
| DMA_COUNT::Get(dma_channel) |
| .FromValue(0) |
| .set_count(static_cast<uint32_t>(write_length)) |
| .WriteTo(mmio); |
| printf("Set DMA_COUNT %zu\n", write_length); |
| ep->cur_offset += write_length; |
| |
| DMA_CNTL::Get(dma_channel) |
| .FromValue(0) |
| .set_burst_mode(3) |
| .set_endpoint(ep_num) |
| .set_inten(1) |
| .set_dir(1) |
| .set_enable(1) |
| .WriteTo(mmio); |
| |
| // wait for dma interrupt |
| /* |
| txcsr.ReadFrom(mmio) |
| .set_dmareqen(0) |
| .set_dmareqmode(0) |
| .set_txpktrdy(1) |
| .WriteTo(mmio); |
| */ |
| |
| #else |
| 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); |
| } |
| #endif |
| } 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); |
| // rxcsr.Print(); |
| |
| if (!rxcsr.rxpktrdy()) { |
| return; |
| } |
| |
| usb_request_t* req = ep->current_req; |
| if (req) { |
| __UNUSED size_t length = req->header.length; |
| #ifdef USE_DMA |
| size_t count = RXCOUNT::Get(ep->ep_num).ReadFrom(mmio).rxcount(); |
| printf("RXCOUNT %zu\n", count); |
| |
| |
| uint32_t dma_channel = ep->dma_channel; |
| |
| phys_iter_t iter; |
| zx_paddr_t phys; |
| usb_request_physmap(req, bti_.get()); |
| usb_request_phys_iter_init(&iter, req, PAGE_SIZE); |
| usb_request_phys_iter_next(&iter, &phys); |
| // This controller only supports 32-bit addresses |
| ZX_DEBUG_ASSERT(phys < UINT32_MAX); |
| |
| phys += ep->cur_offset; |
| ep->dma_phys = phys; |
| |
| DMA_ADDR::Get(dma_channel) |
| .FromValue(0) |
| .set_addr(static_cast<uint32_t>(phys)) |
| .WriteTo(mmio); |
| |
| DMA_COUNT::Get(dma_channel) |
| .FromValue(0) |
| .set_count(static_cast<uint32_t>(count)) |
| .WriteTo(mmio); |
| |
| DMA_CNTL::Get(dma_channel) |
| .FromValue(0) |
| .set_burst_mode(3) |
| .set_endpoint(ep_num) |
| .set_inten(1) |
| .set_dir(0) |
| .set_enable(1) |
| .WriteTo(mmio); |
| |
| // rxcsr.ReadFrom(mmio).set_dmareqen(0).set_dmareqmode(0).set_rxpktrdy(0).WriteTo(mmio); |
| |
| #else |
| 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 (length == 0 || actual < length) { |
| req->response.status = ZX_OK; |
| req->response.actual = ep->cur_offset; |
| ep->current_req = nullptr; |
| list_add_tail(&ep->complete_reqs, &req->node); |
| } |
| } |
| #endif |
| } |
| |
| if (ep->enabled && ep->current_req == nullptr) { |
| EpQueueNextLocked(ep); |
| } |
| } |
| |
| #ifdef USE_DMA |
| void MtUsb::HandleDma() { |
| auto* mmio = usb_mmio(); |
| |
| auto dma_intr = DMA_INTR::Get().ReadFrom(mmio).WriteTo(mmio); |
| auto status = dma_intr.status(); |
| |
| for (uint32_t channel = 0; channel < DMA_CHANNEL_COUNT; channel++) { |
| if (status & (1 << channel)) { |
| auto dma_cntl = DMA_CNTL::Get(channel).ReadFrom(mmio); |
| if (dma_cntl.dma_abort()) { |
| printf("XXXXX dma_abort for channel %u\n", channel); |
| } |
| |
| Endpoint* ep = dma_eps_[channel]; |
| if (!ep) { |
| zxlogf(ERROR, "DMA interrupt for channel %u with no endpoint\n", channel); |
| continue; |
| } |
| |
| // requests to complete outside of the lock |
| list_node_t complete_reqs; |
| list_initialize(&complete_reqs); |
| { |
| fbl::AutoLock lock(&ep->lock); |
| |
| zx_paddr_t new_phys = DMA_ADDR::Get(channel).ReadFrom(mmio).addr(); |
| size_t count = DMA_COUNT::Get(channel).ReadFrom(mmio).count(); |
| |
| // if (ep->dma_phys && new_phys > ep->dma_phys) { |
| // ep->cur_offset += (new_phys - ep->dma_phys); |
| // } |
| |
| |
| if (ep->direction == EP_IN) { |
| // HandleEndpointTxLocked(ep); |
| printf("HandleDma count %zu ep->dma_phys %zu new_phys %zu\n", count, ep->dma_phys, new_phys); |
| |
| printf("HandleDma set txpktrdy\n"); |
| TXCSR_PERI::Get(ep->ep_num).ReadFrom(mmio) |
| .set_dmareqen(1) |
| .set_dmareqmode(0) |
| .set_txpktrdy(1) |
| .WriteTo(mmio); |
| |
| |
| } else { |
| // HandleEndpointRxLocked(ep); |
| printf("clear rxpktrdy\n"); |
| RXCSR_PERI::Get(ep->ep_num).ReadFrom(mmio).set_rxpktrdy(0).WriteTo(mmio); |
| } |
| |
| 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); |
| } |
| } |
| } |
| } |
| #endif |
| |
| 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; |
| #ifdef USE_DMA |
| ep->dma_phys = 0; |
| if (ep->direction == EP_IN) { |
| usb_request_cache_flush(req, 0, req->header.length); |
| } else { |
| usb_request_cache_flush_invalidate(req, 0, req->header.length); |
| } |
| #endif |
| |
| 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 (size_t i = 0; i < countof(eps_); i++) { |
| StartEndpoint(&eps_[i]); |
| } |
| } |
| |
| void MtUsb::FifoRead(uint8_t ep_index, void* buf, size_t buflen, size_t* actual) { |
| auto* mmio = usb_mmio(); |
| |
| // INDEX::Get().FromValue(ep_index).WriteTo(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) |
| #ifdef USE_DMA |
| .set_dma(1) |
| #endif |
| .WriteTo(mmio); |
| |
| #ifdef USE_DMA |
| DMA_INTR::Get() |
| .ReadFrom(mmio) |
| .set_unmask_set(0xff) |
| .WriteTo(mmio); |
| #endif |
| |
| for (uint8_t i = 1; i <= countof(eps_) / 2; 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); |
| __UNUSED auto l1ints = USB_L1INTS::Get().ReadFrom(mmio).WriteTo(mmio); |
| |
| // intrtx.Print(); |
| // intrrx.Print(); |
| // intrusb.Print(); |
| // l1ints.Print(); |
| |
| if (intrusb.suspend()) { |
| printf(" SUSPEND\n"); |
| HandleSuspend(); |
| } |
| if (intrusb.reset()) { |
| printf(" RESET\n"); |
| HandleReset(); |
| } |
| |
| auto ep_tx = intrtx.ep_tx(); |
| auto ep_rx = intrrx.ep_rx(); |
| |
| if (ep_tx) { |
| if (ep_tx & (1 << 0)) { |
| HandleEp0(); |
| } |
| |
| for (unsigned i = 1; i <= 8 /* TODO constant? */; i++) { |
| if (ep_tx & (1 << i)) { |
| Endpoint* ep = &eps_[(i - 1) * 2 + 1]; |
| // 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 = 1; i <= 8 /* TODO constant? */; i++) { |
| if (ep_rx & (1 << i)) { |
| Endpoint* ep = &eps_[(i - 1) * 2]; |
| 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); |
| } |
| } |
| } |
| } |
| |
| #ifdef USE_DMA |
| if (l1ints.dma()) { |
| HandleDma(); |
| } |
| #endif |
| |
| if (intrusb.discon()) printf(" DISCONNECT\n"); |
| if (intrusb.conn()) printf(" CONNECT\n"); |
| if (intrusb.resume()) printf(" RESUME\n"); |
| } |
| } |
| |
| void MtUsb::DdkUnbind() { |
| irq_.destroy(); |
| thrd_join(irq_thread_, nullptr); |
| } |
| |
| void MtUsb::DdkRelease() { |
| delete this; |
| } |
| |
| void MtUsb::UsbDciRequestQueue(usb_request_t* req) { |
| |
| uint8_t ep_index = EpAddressToIndex(req->header.ep_address); |
| |
| if (ep_index >= countof(eps_)) { |
| zxlogf(ERROR, "%s: invalid endpoint address %02x\n", __func__, req->header.ep_address); |
| return; |
| } |
| Endpoint* ep = &eps_[ep_index]; |
| |
| 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_index = EpAddressToIndex(ep_address); |
| |
| if (ep_index >= countof(eps_)) { |
| zxlogf(ERROR, "%s: endpoint address %02x too large\n", __func__, ep_address); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| Endpoint* ep = &eps_[ep_index]; |
| zxlogf(TRACE, "%s address %02x ep_num %u direction %u\n", __func__, ep_address, ep->ep_num, |
| ep->direction); |
| |
| fbl::AutoLock lock(&ep->lock); |
| |
| if (ep->enabled) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| ep->address = ep_address; |
| |
| #ifdef USE_DMA |
| auto status = AllocDmaChannel(ep); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: AllocDmaChannel failed for endpoint %02x: %d\n", __func__, |
| ep_desc->bEndpointAddress, status); |
| return status; |
| } |
| #endif |
| |
| // TODO synchronize here? |
| if (ep->direction == EP_IN) { |
| auto intrtxe = INTRTXE::Get().ReadFrom(mmio); |
| uint16_t mask = intrtxe.ep_tx(); |
| mask |= static_cast<uint16_t>(1 << ep->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->ep_num); |
| intrrxe.set_ep_rx(mask).WriteTo(mmio); |
| } |
| |
| uint16_t max_packet_size = usb_ep_max_packet(ep_desc); |
| if ((ep_address & USB_DIR_MASK) == USB_DIR_IN) { |
| TXCSR_PERI::Get(ep_index) |
| .ReadFrom(mmio) |
| .set_clrdatatog(1) |
| .set_flushfifo(1) |
| .WriteTo(mmio); |
| |
| // if (usb_ep_type(ep_desc) == USB_ENDPOINT_BULK) { |
| // TXMAP::Get(ep_index) |
| // .FromValue(0) |
| // .set_m_1(3) |
| // .set_maximum_payload_transaction(1024) |
| // .WriteTo(mmio); |
| // } else { |
| TXMAP::Get(ep_index) |
| .FromValue(0) |
| .set_maximum_payload_transaction(max_packet_size) |
| .WriteTo(mmio); |
| // } |
| } else { |
| RXCSR_PERI::Get(ep_index) |
| .ReadFrom(mmio) |
| .set_clrdatatog(1) |
| .set_flushfifo(1) |
| .WriteTo(mmio); |
| |
| RXMAP::Get(ep_index) |
| .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) { |
| return ZX_OK; |
| } |
| |
| zx_status_t MtUsb::UsbDciEpSetStall(uint8_t ep_address) { |
| return ZX_OK; |
| } |
| |
| zx_status_t MtUsb::UsbDciEpClearStall(uint8_t ep_address) { |
| 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 0; |
| } |
| |
| |
| 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) |