blob: c316e9d146cb1e5dd2c42f9c2fb59c062137e830 [file] [log] [blame]
// 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)