blob: 88804ec1e05b91de8a820bb4fe5204f4273c58fc [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 "dwc2.h"
static zx_status_t usb_dwc_softreset_core(dwc_usb_t* dwc) {
dwc_regs_t* regs = dwc->regs;
/* do we need this? */
while (regs->grstctl.ahbidle == 0) {
usleep(1000);
}
dwc_grstctl_t grstctl = {0};
grstctl.csftrst = 1;
regs->grstctl = grstctl;
for (int i = 0; i < 1000; i++) {
if (regs->grstctl.csftrst == 0) {
usleep(10 * 1000);
return ZX_OK;
}
usleep(1000);
}
return ZX_ERR_TIMED_OUT;
}
static zx_status_t usb_dwc_setupcontroller(dwc_usb_t* dwc) {
dwc_regs_t* regs = dwc->regs;
regs->gusbcfg.force_dev_mode = 1;
regs->gahbcfg.dmaenable = 0;
#if 1 // astro
regs->gusbcfg.usbtrdtim = 9;
#else
regs->gusbcfg.usbtrdtim = 5;
#endif
regs->dctl.sftdiscon = 1;
regs->dctl.sftdiscon = 0;
// reset phy clock
regs->pcgcctl.val = 0;
regs->grxfsiz = 256; //???
regs->gnptxfsiz.depth = 256;
regs->gnptxfsiz.startaddr = 256;
dwc_flush_fifo(dwc, 0x10);
regs->grstctl.intknqflsh = 1;
/* Clear all pending Device Interrupts */
regs->diepmsk.val = 0;
regs->doepmsk.val = 0;
regs->daint = 0xffffffff;
regs->daintmsk = 0;
for (int i = 0; i < MAX_EPS_CHANNELS; i++) {
regs->depin[i].diepctl.val = 0;
regs->depout[i].doepctl.val = 0;
regs->depin[i].dieptsiz.val = 0;
regs->depout[i].doeptsiz.val = 0;
}
dwc_interrupts_t gintmsk = {0};
gintmsk.rxstsqlvl = 1;
gintmsk.usbreset = 1;
gintmsk.enumdone = 1;
gintmsk.inepintr = 1;
gintmsk.outepintr = 1;
// gintmsk.sof_intr = 1;
gintmsk.usbsuspend = 1;
gintmsk.ginnakeff = 1;
gintmsk.goutnakeff = 1;
/*
gintmsk.modemismatch = 1;
gintmsk.otgintr = 1;
gintmsk.conidstschng = 1;
gintmsk.wkupintr = 1;
gintmsk.disconnect = 0;
gintmsk.sessreqintr = 1;
*/
printf("ghwcfg1 %08x ghwcfg2 %08x ghwcfg3 %08x\n", regs->ghwcfg1, regs->ghwcfg2, regs->ghwcfg3);
regs->gotgint = 0xFFFFFFF;
regs->gintsts.val = 0xFFFFFFF;
zxlogf(LINFO, "enabling interrupts %08x\n", gintmsk.val);
regs->gintmsk = gintmsk;
regs->gahbcfg.glblintrmsk = 1;
return ZX_OK;
}
static void dwc_request_queue(void* ctx, usb_request_t* req) {
dwc_usb_t* dwc = ctx;
zxlogf(INFO, "XXXXXXX dwc_request_queue ep: 0x%02x length %zu\n", req->header.ep_address, req->header.length);
unsigned ep_num = DWC_ADDR_TO_INDEX(req->header.ep_address);
if (ep_num == 0 || ep_num >= countof(dwc->eps)) {
zxlogf(ERROR, "dwc_request_queue: bad ep address 0x%02X\n", req->header.ep_address);
usb_request_complete(req, ZX_ERR_INVALID_ARGS, 0);
return;
}
dwc_ep_queue(dwc, ep_num, req);
}
static zx_status_t dwc_set_interface(void* ctx, usb_dci_interface_t* dci_intf) {
dwc_usb_t* dwc = ctx;
memcpy(&dwc->dci_intf, dci_intf, sizeof(dwc->dci_intf));
return ZX_OK;
}
static zx_status_t dwc_config_ep(void* ctx, usb_endpoint_descriptor_t* ep_desc,
usb_ss_ep_comp_descriptor_t* ss_comp_desc) {
dwc_usb_t* dwc = ctx;
return dwc_ep_config(dwc, ep_desc, ss_comp_desc);
}
static zx_status_t dwc_disable_ep(void* ctx, uint8_t ep_addr) {
dwc_usb_t* dwc = ctx;
return dwc_ep_disable(dwc, ep_addr);
}
static zx_status_t dwc_set_stall(void* ctx, uint8_t ep_address) {
dwc_usb_t* dwc = ctx;
return dwc_ep_set_stall(dwc, DWC_ADDR_TO_INDEX(ep_address), true);
}
static zx_status_t dwc_clear_stall(void* ctx, uint8_t ep_address) {
dwc_usb_t* dwc = ctx;
return dwc_ep_set_stall(dwc, DWC_ADDR_TO_INDEX(ep_address), false);
}
static zx_status_t dwc_get_bti(void* ctx, zx_handle_t* out_handle) {
dwc_usb_t* dwc = ctx;
*out_handle = dwc->bti_handle;
return ZX_OK;
}
static usb_dci_protocol_ops_t dwc_dci_protocol = {
.request_queue = dwc_request_queue,
.set_interface = dwc_set_interface,
.config_ep = dwc_config_ep,
.disable_ep = dwc_disable_ep,
.ep_set_stall = dwc_set_stall,
.ep_clear_stall = dwc_clear_stall,
.get_bti = dwc_get_bti,
};
static zx_status_t dwc_get_initial_mode(void* ctx, usb_mode_t* out_mode) {
dwc_usb_t* dwc = ctx;
zx_status_t status = usb_mode_switch_get_initial_mode(&dwc->ums, out_mode);
if (status == ZX_ERR_NOT_SUPPORTED) {
*out_mode = USB_MODE_DEVICE;
status = ZX_OK;
}
return status;
}
static zx_status_t dwc_set_mode(void* ctx, usb_mode_t mode) {
dwc_usb_t* dwc = ctx;
zx_status_t status = ZX_OK;
if (mode != USB_MODE_DEVICE && mode != USB_MODE_NONE) {
return ZX_ERR_NOT_SUPPORTED;
}
if (dwc->usb_mode == mode) {
return ZX_OK;
}
// Shutdown if we are in device mode
if (dwc->usb_mode == USB_MODE_DEVICE) {
dwc_irq_stop(dwc);
}
/* may be unsupported
status = usb_mode_switch_set_mode(&dwc->ums, mode);
if (status != ZX_OK) {
goto fail;
}
*/
if (mode == USB_MODE_DEVICE) {
status = dwc_irq_start(dwc);
if (status != ZX_OK) {
zxlogf(ERROR, "dwc3_set_mode: pdev_map_interrupt failed\n");
goto fail;
}
}
dwc->usb_mode = mode;
return ZX_OK;
fail:
usb_mode_switch_set_mode(&dwc->ums, USB_MODE_NONE);
dwc->usb_mode = USB_MODE_NONE;
return status;
}
usb_mode_switch_protocol_ops_t dwc_ums_protocol = {
.get_initial_mode = dwc_get_initial_mode,
.set_mode = dwc_set_mode,
};
static zx_status_t dwc_get_protocol(void* ctx, uint32_t proto_id, void* out) {
switch (proto_id) {
case ZX_PROTOCOL_USB_DCI: {
usb_dci_protocol_t* proto = out;
proto->ops = &dwc_dci_protocol;
proto->ctx = ctx;
return ZX_OK;
}
case ZX_PROTOCOL_USB_MODE_SWITCH: {
usb_mode_switch_protocol_t* proto = out;
proto->ops = &dwc_ums_protocol;
proto->ctx = ctx;
return ZX_OK;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
static void dwc_unbind(void* ctx) {
zxlogf(ERROR, "dwc_usb: dwc_unbind not implemented\n");
}
static void dwc_release(void* ctx) {
zxlogf(ERROR, "dwc_usb: dwc_release not implemented\n");
}
static zx_protocol_device_t dwc_device_proto = {
.version = DEVICE_OPS_VERSION,
.get_protocol = dwc_get_protocol,
.unbind = dwc_unbind,
.release = dwc_release,
};
// Bind is the entry point for this driver.
static zx_status_t dwc_bind(void* ctx, zx_device_t* dev) {
zxlogf(TRACE, "dwc_bind: dev = %p\n", dev);
// Allocate a new device object for the bus.
dwc_usb_t* dwc = calloc(1, sizeof(*dwc));
if (!dwc) {
zxlogf(ERROR, "dwc_bind: bind failed to allocate usb_dwc struct\n");
return ZX_ERR_NO_MEMORY;
}
#if SINGLE_EP_IN_QUEUE
list_initialize(&dwc->queued_in_reqs);
#endif
zx_status_t status = device_get_protocol(dev, ZX_PROTOCOL_PLATFORM_DEV, &dwc->pdev);
if (status != ZX_OK) {
free(dwc);
return status;
}
status = device_get_protocol(dev, ZX_PROTOCOL_USB_MODE_SWITCH, &dwc->ums);
if (status != ZX_OK) {
free(dwc);
return status;
}
// hack for astro USB tuning (also optional)
device_get_protocol(dev, ZX_PROTOCOL_ASTRO_USB, &dwc->astro_usb);
for (unsigned i = 0; i < countof(dwc->eps); i++) {
dwc_endpoint_t* ep = &dwc->eps[i];
ep->ep_num = i;
mtx_init(&ep->lock, mtx_plain);
list_initialize(&ep->queued_reqs);
}
dwc->eps[0].req_buffer = dwc->ep0_buffer;
// Carve out some address space for this device.
size_t mmio_size;
zx_handle_t mmio_handle = ZX_HANDLE_INVALID;
status = pdev_map_mmio(&dwc->pdev, MMIO_INDEX, ZX_CACHE_POLICY_UNCACHED_DEVICE, (void **)&dwc->regs,
&mmio_size, &mmio_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "usb_dwc: bind failed to pdev_map_mmio.\n");
goto error_return;
}
status = pdev_get_bti(&dwc->pdev, 0, &dwc->bti_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "usb_dwc: bind failed to get bti handle.\n");
goto error_return;
}
dwc->parent = dev;
dwc->usb_mode = USB_MODE_NONE;
if (dwc->astro_usb.ops) {
astro_usb_do_usb_tuning(&dwc->astro_usb, false, true);
}
if ((status = usb_dwc_softreset_core(dwc)) != ZX_OK) {
zxlogf(ERROR, "usb_dwc: failed to reset core.\n");
goto error_return;
}
if ((status = usb_dwc_setupcontroller(dwc)) != ZX_OK) {
zxlogf(ERROR, "usb_dwc: failed setup controller.\n");
goto error_return;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "dwc2",
.ctx = dwc,
.ops = &dwc_device_proto,
.proto_id = ZX_PROTOCOL_USB_DCI,
.proto_ops = &dwc_dci_protocol,
};
if ((status = device_add(dev, &args, &dwc->zxdev)) != ZX_OK) {
free(dwc);
return status;
}
zxlogf(TRACE, "usb_dwc: bind success!\n");
return ZX_OK;
error_return:
if (dwc) {
if (dwc->regs) {
zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)dwc->regs, mmio_size);
}
zx_handle_close(mmio_handle);
zx_handle_close(dwc->irq_handle);
zx_handle_close(dwc->bti_handle);
free(dwc);
}
return status;
}
static zx_driver_ops_t usb_dwc_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = dwc_bind,
};
// The formatter does not play nice with these macros.
// clang-format off
ZIRCON_DRIVER_BEGIN(dwc2, usb_dwc_driver_ops, "zircon", "0.1", 3)
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_DEVICE),
ZIRCON_DRIVER_END(dwc2)
// clang-format on