diff options
author | Nick Sanders <nsanders@chromium.org> | 2016-08-02 19:35:44 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2016-09-01 22:56:22 -0700 |
commit | a4bfc663a3cd645b43963bb814269efe864f8d1e (patch) | |
tree | 185ab7cc413c4b580ea50845a024838aba577732 /chip/stm32/usb_dwc.c | |
parent | 54f4612764e07e5e3ccd8a4af04ee83a46454612 (diff) | |
download | chrome-ec-a4bfc663a3cd645b43963bb814269efe864f8d1e.tar.gz |
sweetberry: add dwc usb support
stm32f446 uses a synopsys designware USB block
rather than the typical ST one. This change adds driver support
for the new block, including usb console support.
BUG=chromium:608039
TEST=usb console works
BRANCH=None
Change-Id: I0e143758ae0b5285f1c94ea2ec5aee159e22e00c
Signed-off-by: Nick Sanders <nsanders@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/365448
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Diffstat (limited to 'chip/stm32/usb_dwc.c')
-rw-r--r-- | chip/stm32/usb_dwc.c | 1230 |
1 files changed, 1230 insertions, 0 deletions
diff --git a/chip/stm32/usb_dwc.c b/chip/stm32/usb_dwc.c new file mode 100644 index 0000000000..628a0c1406 --- /dev/null +++ b/chip/stm32/usb_dwc.c @@ -0,0 +1,1230 @@ +/* Copyright 2016 The Chromium OS 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 "clock.h" +#include "common.h" +#include "config.h" +#include "console.h" +#include "flash.h" +#include "gpio.h" +#include "hooks.h" +#include "link_defs.h" +#include "registers.h" +#include "usb_dwc_hw.h" +#include "system.h" +#include "task.h" +#include "timer.h" +#include "util.h" +#include "usb_descriptor.h" +#include "watchdog.h" + + +/****************************************************************************/ +/* Debug output */ + +/* Console output macro */ +#define CPRINTS(format, args...) cprints(CC_USB, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_USB, format, ## args) + +/* TODO: Something unexpected happened. Figure out how to report & fix it. */ +#define report_error(val) \ + CPRINTS("Unhandled USB event at %s line %d: 0x%x", \ + __FILE__, __LINE__, val) + + +/****************************************************************************/ +/* Standard USB stuff */ + +#ifdef CONFIG_USB_BOS +/* v2.01 (vs 2.00) BOS Descriptor provided */ +#define USB_DEV_BCDUSB 0x0201 +#else +#define USB_DEV_BCDUSB 0x0200 +#endif + +#ifndef USB_DEV_CLASS +#define USB_DEV_CLASS USB_CLASS_PER_INTERFACE +#endif + +#ifndef CONFIG_USB_BCD_DEV +#define CONFIG_USB_BCD_DEV 0x0100 /* 1.00 */ +#endif + +#ifndef USB_BMATTRIBUTES +#ifdef CONFIG_USB_SELF_POWERED +#define USB_BMATTRIBUTES 0xc0 /* Self powered. */ +#else +#define USB_BMATTRIBUTES 0x80 /* Bus powered. */ +#endif +#endif + +#ifndef CONFIG_USB_SERIALNO +#define USB_STR_SERIALNO 0 +#else +static int usb_load_serial(void); +#endif + + +/* USB Standard Device Descriptor */ +static const struct usb_device_descriptor dev_desc = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = USB_DEV_BCDUSB, + .bDeviceClass = USB_DEV_CLASS, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = USB_MAX_PACKET_SIZE, + .idVendor = USB_VID_GOOGLE, + .idProduct = CONFIG_USB_PID, + .bcdDevice = CONFIG_USB_BCD_DEV, + .iManufacturer = USB_STR_VENDOR, + .iProduct = USB_STR_PRODUCT, + .iSerialNumber = USB_STR_SERIALNO, + .bNumConfigurations = 1 +}; + +/* USB Configuration Descriptor */ +const struct usb_config_descriptor USB_CONF_DESC(conf) = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0x0BAD, /* number of returned bytes, set at runtime */ + .bNumInterfaces = USB_IFACE_COUNT, + .bConfigurationValue = 1, /* Caution: hard-coded value */ + .iConfiguration = USB_STR_VERSION, + .bmAttributes = USB_BMATTRIBUTES, /* bus or self powered */ + .bMaxPower = (CONFIG_USB_MAXPOWER_MA / 2), +}; + +/* Qualifier Descriptor */ +static const struct usb_qualifier_descriptor qualifier_desc = { + .bLength = USB_DT_QUALIFIER_SIZE, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + .bcdUSB = USB_DEV_BCDUSB, + .bDeviceClass = USB_DEV_CLASS, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = USB_MAX_PACKET_SIZE, + .bNumConfigurations = 1, + .bReserved = 0, +}; + +const uint8_t usb_string_desc[] = { + 4, /* Descriptor size */ + USB_DT_STRING, + 0x09, 0x04 /* LangID = 0x0409: U.S. English */ +}; + +/****************************************************************************/ +/* Packet-handling stuff, specific to this SoC */ + +/* Some internal state to keep track of what's going on */ +static enum { + WAITING_FOR_SETUP_PACKET, + DATA_STAGE_IN, + NO_DATA_STAGE, +} what_am_i_doing; + +#ifdef DEBUG_ME +static const char * const wat[3] = { + [WAITING_FOR_SETUP_PACKET] = "wait_for_setup", + [DATA_STAGE_IN] = "data_in", + [NO_DATA_STAGE] = "no_data", +}; +#endif + +/* Programmer's Guide, Table 10-7 */ +enum table_case { + BAD_0, + TABLE_CASE_COMPLETE, + TABLE_CASE_SETUP, + TABLE_CASE_WTF, + TABLE_CASE_D, + TABLE_CASE_E, + BAD_6, + BAD_7, +}; + +static enum table_case decode_table_10_7(uint32_t doepint) +{ + enum table_case val = BAD_0; + + /* Bits: SI, SPD, IOC */ + if (doepint & DOEPINT_XFERCOMPL) + val += 1; + if (doepint & DOEPINT_SETUP) + val += 2; + return val; +} + +/* For STATUS/OUT: Use two DMA descriptors, each with one-packet buffers */ +#define NUM_OUT_BUFFERS 2 +static uint8_t ep0_setup_buf[USB_MAX_PACKET_SIZE]; + +/* For IN: Several DMA descriptors, all pointing into one large buffer, so that + * we can return the configuration descriptor as one big blob. + */ +#define NUM_IN_PACKETS_AT_ONCE 4 +#define IN_BUF_SIZE (NUM_IN_PACKETS_AT_ONCE * USB_MAX_PACKET_SIZE) +static uint8_t ep0_in_buf[IN_BUF_SIZE]; + +struct dwc_usb_ep ep0_ctl = { + .max_packet = USB_MAX_PACKET_SIZE, + .tx_fifo = 0, + .out_pending = 0, + .out_data = 0, + .out_databuffer = ep0_setup_buf, + .out_databuffer_max = sizeof(ep0_setup_buf), + .in_packets = 0, + .in_pending = 0, + .in_data = 0, + .in_databuffer = ep0_in_buf, + .in_databuffer_max = sizeof(ep0_in_buf), +}; + +/* Overall device state (USB 2.0 spec, section 9.1.1). + * We only need a few, though. + */ +static enum { + DS_DEFAULT, + DS_ADDRESS, + DS_CONFIGURED, +} device_state; +static uint8_t configuration_value; + +static void flush_all_fifos(void) +{ + /* Flush all FIFOs according to Section 2.1.1.2 */ + GR_USB_GRSTCTL = GRSTCTL_TXFNUM(0x10) | GRSTCTL_TXFFLSH + | GRSTCTL_RXFFLSH; + while (GR_USB_GRSTCTL & (GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH)) + ; +} + +int send_in_packet(uint32_t ep_num) +{ + struct dwc_usb *usb = &usb_ctl; + struct dwc_usb_ep *ep = usb->ep[ep_num]; + int len = MIN(USB_MAX_PACKET_SIZE, ep->in_pending); + + if (ep->in_packets == 0) { + report_error(ep_num); + return -1; + } + + GR_USB_DIEPTSIZ(0) = 0; + + GR_USB_DIEPTSIZ(0) |= DXEPTSIZ_PKTCNT(1); + GR_USB_DIEPTSIZ(0) |= DXEPTSIZ_XFERSIZE(len); + GR_USB_DIEPDMA(0) = (uint32_t)ep->in_data; + + + /* We're sending this much. */ + ep->in_pending -= len; + ep->in_packets -= 1; + ep->in_data += len; + + /* We are ready to enable this endpoint to start transferring data. */ + return len; +} + + +/* Load the EP0 IN FIFO buffer with some data (zero-length works too). Returns + * len, or negative on error. + */ +int initialize_in_transfer(const void *source, uint32_t len) +{ + struct dwc_usb *usb = &usb_ctl; + struct dwc_usb_ep *ep = usb->ep[0]; + +#ifdef CONFIG_USB_DWC_FS + /* FS OTG port does not support DMA or external phy */ + ASSERT(!(usb->dma_en)); + ASSERT(usb->phy_type == USB_PHY_INTERNAL); + ASSERT(usb->speed == USB_SPEED_FS); + ASSERT(usb->irq == STM32_IRQ_OTG_FS); +#else + /* HS OTG port requires an external phy to support HS */ + ASSERT(!((usb->phy_type == USB_PHY_INTERNAL) && + (usb->speed == USB_SPEED_HS))); + ASSERT(usb->irq == STM32_IRQ_OTG_HS); +#endif + + /* Copy the data into our FIFO buffer */ + if (len >= IN_BUF_SIZE) { + report_error(len); + return -1; + } + + /* Stage data in DMA buffer. */ + memcpy(ep->in_databuffer, source, len); + ep->in_data = ep->in_databuffer; + + /* We will send as many packets as necessary, including a final + * packet of < USB_MAX_PACKET_SIZE (maybe zero length) + */ + ep->in_packets = (len + USB_MAX_PACKET_SIZE)/USB_MAX_PACKET_SIZE; + ep->in_pending = len; + + send_in_packet(0); + return len; +} + +/* Prepare the EP0 OUT FIFO buffer to accept some data. Returns len, or + * negative on error. + */ +int accept_out_fifo(uint32_t len) +{ + /* TODO: This is not yet implemented */ + report_error(len); + return -1; +} + +/* The next packet from the host should be a Setup packet. Get ready for it. */ +static void expect_setup_packet(void) +{ + struct dwc_usb *usb = &usb_ctl; + struct dwc_usb_ep *ep = usb->ep[0]; + + what_am_i_doing = WAITING_FOR_SETUP_PACKET; + ep->out_data = ep->out_databuffer; + + /* We don't care about IN packets right now, only OUT. */ + GR_USB_DAINTMSK |= DAINT_OUTEP(0); + GR_USB_DAINTMSK &= ~DAINT_INEP(0); + + GR_USB_DOEPTSIZ(0) = 0; + GR_USB_DOEPTSIZ(0) |= DXEPTSIZ_PKTCNT(1); + GR_USB_DOEPTSIZ(0) |= DXEPTSIZ_XFERSIZE(0x18); + GR_USB_DOEPTSIZ(0) |= DXEPTSIZ_SUPCNT(1); + GR_USB_DOEPCTL(0) = DXEPCTL_USBACTEP | DXEPCTL_EPENA; + GR_USB_DOEPDMA(0) = (uint32_t)ep->out_data; +} + +/* We're complaining about something by stalling both IN and OUT packets, + * but a SETUP packet will get through anyway, so prepare for it. + */ +static void stall_both_fifos(void) +{ + what_am_i_doing = WAITING_FOR_SETUP_PACKET; + /* We don't care about IN packets right now, only OUT. */ + GR_USB_DAINTMSK |= DAINT_OUTEP(0); + GR_USB_DAINTMSK &= ~DAINT_INEP(0); + + GR_USB_DOEPCTL(0) |= DXEPCTL_STALL; + GR_USB_DIEPCTL(0) |= DXEPCTL_STALL; + expect_setup_packet(); +} + +/* The TX FIFO buffer is loaded. Start the Data phase. */ +static void expect_data_phase_in(enum table_case tc) +{ + what_am_i_doing = DATA_STAGE_IN; + + /* Send the reply (data phase in) */ + if (tc == TABLE_CASE_SETUP) + GR_USB_DIEPCTL(0) |= DXEPCTL_USBACTEP | + DXEPCTL_CNAK | DXEPCTL_EPENA; + else + GR_USB_DIEPCTL(0) |= DXEPCTL_EPENA; + + /* We'll receive an empty packet back as a ack, I guess. */ + if (tc == TABLE_CASE_SETUP) + GR_USB_DOEPCTL(0) |= DXEPCTL_CNAK | DXEPCTL_EPENA; + else + GR_USB_DOEPCTL(0) |= DXEPCTL_EPENA; + + /* Get an interrupt when either IN or OUT arrives */ + GR_USB_DAINTMSK |= (DAINT_OUTEP(0) | DAINT_INEP(0)); + +} + +static void expect_data_phase_out(enum table_case tc) +{ + /* TODO: This is not yet supported */ + report_error(tc); + expect_setup_packet(); +} + +/* No Data phase, just Status phase (which is IN, since Setup is OUT) */ +static void expect_status_phase_in(enum table_case tc) +{ + what_am_i_doing = NO_DATA_STAGE; + + /* Expect a zero-length IN for the Status phase */ + (void) initialize_in_transfer(0, 0); + + /* Blindly following instructions here, too. */ + if (tc == TABLE_CASE_SETUP) + GR_USB_DIEPCTL(0) |= DXEPCTL_USBACTEP + | DXEPCTL_CNAK | DXEPCTL_EPENA; + else + GR_USB_DIEPCTL(0) |= DXEPCTL_EPENA; + + /* Get an interrupt when either IN or OUT arrives */ + GR_USB_DAINTMSK |= (DAINT_OUTEP(0) | DAINT_INEP(0)); +} + +/* Handle a Setup packet that expects us to send back data in reply. Return the + * length of the data we're returning, or negative to indicate an error. + */ +static int handle_setup_with_in_stage(enum table_case tc, + struct usb_setup_packet *req) +{ + struct dwc_usb *usb = &usb_ctl; + struct dwc_usb_ep *ep = usb->ep[0]; + + const void *data = 0; + uint32_t len = 0; + int ugly_hack = 0; + static const uint16_t zero; /* == 0 */ + + switch (req->bRequest) { + case USB_REQ_GET_DESCRIPTOR: { + uint8_t type = req->wValue >> 8; + uint8_t idx = req->wValue & 0xff; + + switch (type) { + case USB_DT_DEVICE: + data = &dev_desc; + len = sizeof(dev_desc); + break; + case USB_DT_CONFIGURATION: + data = __usb_desc; + len = USB_DESC_SIZE; + ugly_hack = 1; /* see below */ + break; +#ifdef CONFIG_USB_BOS + case USB_DT_BOS: + data = bos_ctx.descp; + len = bos_ctx.size; + break; +#endif + case USB_DT_STRING: + if (idx >= USB_STR_COUNT) + return -1; +#ifdef CONFIG_USB_SERIALNO + if (idx == USB_STR_SERIALNO) + data = (uint8_t *)usb_serialno_desc; + else +#endif + data = usb_strings[idx]; + len = *(uint8_t *)data; + break; + case USB_DT_DEVICE_QUALIFIER: + /* We're not high speed */ + return -1; + default: + report_error(type); + return -1; + } + break; + } + case USB_REQ_GET_STATUS: { + /* TODO: Device Status: Remote Wakeup? Self Powered? */ + data = &zero; + len = sizeof(zero); + break; + } + case USB_REQ_GET_CONFIGURATION: + data = &configuration_value; + len = sizeof(configuration_value); + break; + + case USB_REQ_SYNCH_FRAME: + /* Unimplemented */ + return -1; + + default: + report_error(req->bRequest); + return -1; + } + + /* Don't send back more than we were asked for. */ + len = MIN(req->wLength, len); + + /* Prepare the TX FIFO. If we haven't preallocated enough room in the + * TX FIFO for the largest reply, we'll have to stall. This is a bug in + * our code, but detecting it easily at compile time is related to the + * ugly_hack directly below. + */ + if (initialize_in_transfer(data, len) < 0) + return -1; + + if (ugly_hack) { + /* + * TODO: Somebody figure out how to fix this, please. + * + * The USB configuration descriptor request is unique in that + * it not only returns the configuration descriptor, but also + * all the interface descriptors and all their endpoint + * descriptors as one enormous blob. We've set up some macros + * so we can declare and implement separate interfaces in + * separate files just by compiling them, and all the relevant + * descriptors are sorted and bundled up by the linker. But the + * total length of the entire blob needs to appear in the first + * configuration descriptor struct and because we don't know + * that value until after linking, it can't be initialized as a + * constant. So we have to compute it at run-time and shove it + * in here, which also means that we have to copy the whole + * blob into our TX FIFO buffer so that it's mutable. Otherwise + * we could just point at it (or pretty much any other constant + * struct that we wanted to send to the host). Bah. + */ + struct usb_config_descriptor *cfg = + (struct usb_config_descriptor *)ep->in_databuffer; + /* set the real descriptor size */ + cfg->wTotalLength = USB_DESC_SIZE; + } + + return len; +} + +/* Handle a Setup that comes with additional data for us. */ +static int handle_setup_with_out_stage(enum table_case tc, + struct usb_setup_packet *req) +{ + /* TODO: We don't support any of these. We should. */ + report_error(-1); + return -1; +} + +/* Some Setup packets don't have a data stage at all. */ +static int handle_setup_with_no_data_stage(enum table_case tc, + struct usb_setup_packet *req) +{ + uint8_t set_addr; + + switch (req->bRequest) { + case USB_REQ_SET_ADDRESS: + /* + * Set the address after the IN packet handshake. + * + * From the USB 2.0 spec, section 9.4.6: + * + * As noted elsewhere, requests actually may result in + * up to three stages. In the first stage, the Setup + * packet is sent to the device. In the optional second + * stage, data is transferred between the host and the + * device. In the final stage, status is transferred + * between the host and the device. The direction of + * data and status transfer depends on whether the host + * is sending data to the device or the device is + * sending data to the host. The Status stage transfer + * is always in the opposite direction of the Data + * stage. If there is no Data stage, the Status stage + * is from the device to the host. + * + * Stages after the initial Setup packet assume the + * same device address as the Setup packet. The USB + * device does not change its device address until + * after the Status stage of this request is completed + * successfully. Note that this is a difference between + * this request and all other requests. For all other + * requests, the operation indicated must be completed + * before the Status stage + */ + set_addr = req->wValue & 0xff; + /* + * NOTE: Now that we've said that, we don't do it. The + * hardware for this SoC knows that an IN packet will + * be following the SET ADDRESS, so it waits until it + * sees that happen before the address change takes + * effect. If we wait until after the IN packet to + * change the register, the hardware gets confused and + * doesn't respond to anything. + */ + GWRITE_FIELD(USB, DCFG, DEVADDR, set_addr); + CPRINTS("SETAD 0x%02x (%d)", set_addr, set_addr); + device_state = DS_ADDRESS; + break; + + case USB_REQ_SET_CONFIGURATION: + switch (req->wValue) { + case 0: + configuration_value = req->wValue; + device_state = DS_ADDRESS; + break; + case 1: /* Caution: Only one config descriptor TODAY */ + /* TODO: All endpoints set to DATA0 toggle state */ + configuration_value = req->wValue; + device_state = DS_CONFIGURED; + break; + default: + /* Nope. That's a paddlin. */ + report_error(-1); + return -1; + } + break; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* TODO: Handle DEVICE_REMOTE_WAKEUP, ENDPOINT_HALT? */ + break; + + default: + /* Anything else is unsupported */ + report_error(-1); + return -1; + } + + /* No data to transfer, go straight to the Status phase. */ + return 0; +} + +/* Dispatch an incoming Setup packet according to its type */ +static void handle_setup(enum table_case tc) +{ + struct dwc_usb *usb = &usb_ctl; + struct dwc_usb_ep *ep = usb->ep[0]; + struct usb_setup_packet *req = + (struct usb_setup_packet *)ep->out_databuffer; + int data_phase_in = req->bmRequestType & USB_DIR_IN; + int data_phase_out = !data_phase_in && req->wLength; + int bytes = -1; /* default is to stall */ + + if (0 == (req->bmRequestType & (USB_TYPE_MASK | USB_RECIP_MASK))) { + /* Standard Device requests */ + if (data_phase_in) + bytes = handle_setup_with_in_stage(tc, req); + else if (data_phase_out) + bytes = handle_setup_with_out_stage(tc, req); + else + bytes = handle_setup_with_no_data_stage(tc, req); + } else if (USB_RECIP_INTERFACE == + (req->bmRequestType & USB_RECIP_MASK)) { + /* Interface-specific requests */ + uint8_t iface = req->wIndex & 0xff; + + if (iface < USB_IFACE_COUNT) + bytes = usb_iface_request[iface](req); + } else { + /* Something we need to add support for? */ + report_error(-1); + } + + /* We say "no" to unsupported and intentionally unhandled requests by + * stalling the Data and/or Status stage. + */ + if (bytes < 0) { + /* Stall both IN and OUT. SETUP will come through anyway. */ + stall_both_fifos(); + } else { + if (data_phase_in) + expect_data_phase_in(tc); + else if (data_phase_out) + expect_data_phase_out(tc); + else + expect_status_phase_in(tc); + } +} + +/* This handles both IN and OUT interrupts for EP0 */ +static void ep0_interrupt(uint32_t intr_on_out, uint32_t intr_on_in) +{ + struct dwc_usb *usb = &usb_ctl; + struct dwc_usb_ep *ep = usb->ep[0]; + uint32_t doepint, diepint; + enum table_case tc; + int out_complete, out_setup, in_complete; + + /* Determine the interrupt cause and clear the bits quickly, but only + * if they really apply. I don't think they're trustworthy if we didn't + * actually get an interrupt. + */ + doepint = GR_USB_DOEPINT(0) & GR_USB_DOEPMSK; + if (intr_on_out) + GR_USB_DOEPINT(0) = doepint; + diepint = GR_USB_DIEPINT(0) & GR_USB_DIEPMSK; + if (intr_on_in) + GR_USB_DIEPINT(0) = diepint; + + out_complete = doepint & DOEPINT_XFERCOMPL; + out_setup = doepint & DOEPINT_SETUP; + in_complete = diepint & DIEPINT_XFERCOMPL; + + /* Decode the situation according to Table 10-7 */ + tc = decode_table_10_7(doepint); + + switch (what_am_i_doing) { + case WAITING_FOR_SETUP_PACKET: + if (out_setup) + handle_setup(tc); + else + report_error(-1); + break; + + case DATA_STAGE_IN: + if (intr_on_in && in_complete) { + /* A packet is sent. Should we send another? */ + if (ep->in_packets > 0) { + /* Send another packet. */ + send_in_packet(0); + expect_data_phase_in(tc); + } + } + + /* But we should ignore the OUT endpoint if we didn't actually + * get an OUT interrupt. + */ + if (!intr_on_out) + break; + + if (out_setup) { + /* The first IN packet has been seen. Keep going. */ + break; + } + if (out_complete) { + /* We've handled the Status phase. All done. */ + expect_setup_packet(); + break; + } + + /* Anything else should be ignorable. Right? */ + break; + + case NO_DATA_STAGE: + if (intr_on_in && in_complete) { + /* We are not expecting an empty packet in + * return for our empty packet. + */ + expect_setup_packet(); + } + + /* Done unless we got an OUT interrupt */ + if (!intr_on_out) + break; + + if (out_setup) { + report_error(-1); + break; + } + + /* Anything else means get ready for a Setup packet */ + report_error(-1); + expect_setup_packet(); + break; + } +} + +/****************************************************************************/ +/* USB device initialization and shutdown routines */ + +/* + * DATA FIFO Setup. There is an internal SPRAM used to buffer the IN/OUT + * packets and track related state without hammering the AHB and system RAM + * during USB transactions. We have to specify where and how much of that SPRAM + * to use for what. + * + * See Programmer's Guide chapter 2, "Calculating FIFO Size". + * We're using Dedicated TxFIFO Operation, without enabling thresholding. + * + * Section 2.1.1.2, page 30: RXFIFO size is the same as for Shared FIFO, which + * is Section 2.1.1.1, page 28. This is also the same as Method 2 on page 45. + * + * We support up to 3 control EPs, no periodic IN EPs, up to 16 TX EPs. Max + * data packet size is 64 bytes. Total SPRAM available is 1024 slots. + */ +#define MAX_CONTROL_EPS 3 +#define MAX_NORMAL_EPS 16 +#define FIFO_RAM_DEPTH 1024 +/* + * Device RX FIFO size is thus: + * (4 * 3 + 6) + 2 * ((64 / 4) + 1) + (2 * 16) + 1 == 85 + */ +#define RXFIFO_SIZE ((4 * MAX_CONTROL_EPS + 6) + \ + 2 * ((USB_MAX_PACKET_SIZE / 4) + 1) + \ + (2 * MAX_NORMAL_EPS) + 1) +/* + * Device TX FIFO size is 2 * (64 / 4) == 32 for each IN EP (Page 46). + */ +#define TXFIFO_SIZE (2 * (USB_MAX_PACKET_SIZE / 4)) +/* + * We need 4 slots per endpoint direction for endpoint status stuff (Table 2-1, + * unconfigurable). + */ +#define EP_STATUS_SIZE (4 * MAX_NORMAL_EPS * 2) +/* + * Make sure all that fits. + */ +BUILD_ASSERT(RXFIFO_SIZE + TXFIFO_SIZE * MAX_NORMAL_EPS + EP_STATUS_SIZE < + FIFO_RAM_DEPTH); + + +/* Now put those constants into the correct registers */ +static void setup_data_fifos(void) +{ + int i; + + /* Programmer's Guide, p31 */ + GR_USB_GRXFSIZ = RXFIFO_SIZE; /* RXFIFO */ + GR_USB_GNPTXFSIZ = (TXFIFO_SIZE << 16) | RXFIFO_SIZE; /* TXFIFO 0 */ + + /* TXFIFO 1..15 */ + for (i = 1; i < MAX_NORMAL_EPS; i++) + GR_USB_DIEPTXF(i) = ((TXFIFO_SIZE << 16) | + (RXFIFO_SIZE + i * TXFIFO_SIZE)); + + /* + * TODO: The Programmer's Guide is confusing about when or whether to + * flush the FIFOs. Section 2.1.1.2 (p31) just says to flush. Section + * 2.2.2 (p55) says to stop all the FIFOs first, then flush. Section + * 7.5.4 (p162) says that flushing the RXFIFO at reset is not + * recommended at all. + * + * I'm also unclear on whether or not the individual EPs are expected + * to be disabled already (DIEPCTLn/DOEPCTLn.EPENA == 0), and if so, + * whether by firmware or hardware. + */ + + /* Flush all FIFOs according to Section 2.1.1.2 */ + GR_USB_GRSTCTL = GRSTCTL_TXFNUM(0x10) | GRSTCTL_TXFFLSH + | GRSTCTL_RXFFLSH; + while (GR_USB_GRSTCTL & (GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH)) + ; /* TODO: timeout 100ms */ +} + +static void usb_init_endpoints(void) +{ + int ep; + + /* Prepare to receive packets on EP0 */ + expect_setup_packet(); + + /* Reset the other endpoints */ + for (ep = 1; ep < USB_EP_COUNT; ep++) + usb_ep_reset[ep](); +} + +static void usb_reset(void) +{ + /* Clear our internal state */ + device_state = DS_DEFAULT; + configuration_value = 0; + + /* Clear the device address */ + GWRITE_FIELD(USB, DCFG, DEVADDR, 0); + + /* Reinitialize all the endpoints */ + usb_init_endpoints(); +} + +static void usb_resetdet(void) +{ + /* TODO: Same as normal reset, right? I think we only get this if we're + * suspended (sleeping) and the host resets us. Try it and see. + */ + usb_reset(); +} + +static void usb_enumdone(void) +{ + /* We can change to HS here. We will not go to HS today */ + GR_USB_DCTL |= DCTL_CGOUTNAK; +} + + +void usb_interrupt(void) +{ + uint32_t status = GR_USB_GINTSTS & GR_USB_GINTMSK; + uint32_t oepint = status & GINTSTS(OEPINT); + uint32_t iepint = status & GINTSTS(IEPINT); + int ep; + + if (status & GINTSTS(ENUMDONE)) + usb_enumdone(); + + if (status & GINTSTS(RESETDET)) + usb_resetdet(); + + if (status & GINTSTS(USBRST)) + usb_reset(); + + /* Endpoint interrupts */ + if (oepint || iepint) { + /* Note: It seems that the DAINT bits are only trustworthy for + * identifying interrupts when selected by the corresponding + * OEPINT and IEPINT bits from GINTSTS. + */ + uint32_t daint = GR_USB_DAINT; + + /* EP0 has a combined IN/OUT handler. Only call it once, but + * let it know which direction(s) had an interrupt. + */ + if (daint & (DAINT_OUTEP(0) | DAINT_INEP(0))) { + uint32_t intr_on_out = (oepint && + (daint & DAINT_OUTEP(0))); + uint32_t intr_on_in = (iepint && + (daint & DAINT_INEP(0))); + ep0_interrupt(intr_on_out, intr_on_in); + } + + /* Invoke the unidirectional IN and OUT functions for the other + * endpoints. Each handler must clear their own bits in + * DIEPINTn/DOEPINTn. + */ + for (ep = 1; ep < USB_EP_COUNT; ep++) { + if (oepint && (daint & DAINT_OUTEP(ep))) + usb_ep_rx[ep](); + if (iepint && (daint & DAINT_INEP(ep))) + usb_ep_tx[ep](); + } + } + + GR_USB_GINTSTS = status; +} +DECLARE_IRQ(STM32_IRQ_OTG_FS, usb_interrupt, 1); +DECLARE_IRQ(STM32_IRQ_OTG_HS, usb_interrupt, 1); + +static void usb_softreset(void) +{ + int timeout; + + CPRINTS("%s", __func__); + + /* Wait for bus idle */ + timeout = 10000; + while (!(GR_USB_GRSTCTL & GRSTCTL_AHBIDLE) && timeout-- > 0) + ; + + /* Reset and wait for clear */ + GR_USB_GRSTCTL = GRSTCTL_CSFTRST; + timeout = 10000; + while ((GR_USB_GRSTCTL & GRSTCTL_CSFTRST) && timeout-- > 0) + ; + if (GR_USB_GRSTCTL & GRSTCTL_CSFTRST) { + CPRINTF("USB: reset failed\n"); + return; + } + + /* Some more idle? */ + timeout = 10000; + while (!(GR_USB_GRSTCTL & GRSTCTL_AHBIDLE) && timeout-- > 0) + ; + + if (!timeout) { + CPRINTF("USB: reset timeout\n"); + return; + } + /* TODO: Wait 3 PHY clocks before returning */ +} + +void usb_connect(void) +{ + GR_USB_DCTL &= ~DCTL_SFTDISCON; +} + +void usb_disconnect(void) +{ + GR_USB_DCTL |= DCTL_SFTDISCON; + + device_state = DS_DEFAULT; + configuration_value = 0; +} + +void usb_reset_init_phy(void) +{ + struct dwc_usb *usb = &usb_ctl; + + if (usb->phy_type == USB_PHY_ULPI) { + GR_USB_GCCFG &= ~GCCFG_PWRDWN; + GR_USB_GUSBCFG &= ~(GUSBCFG_TSDPS | + GUSBCFG_ULPIFSLS | GUSBCFG_PHYSEL); + GR_USB_GUSBCFG &= ~(GUSBCFG_ULPIEVBUSD | GUSBCFG_ULPIEVBUSI); + /* No suspend */ + GR_USB_GUSBCFG |= GUSBCFG_ULPICSM | GUSBCFG_ULPIAR; + + usb_softreset(); + } else { + GR_USB_GUSBCFG |= GUSBCFG_PHYSEL; + usb_softreset(); + GR_USB_GCCFG |= GCCFG_PWRDWN; + } +} + +void usb_init(void) +{ + int i; + struct dwc_usb *usb = &usb_ctl; + + CPRINTS("%s", __func__); + +#ifdef CONFIG_USB_SERIALNO + usb_load_serial(); +#endif + + /* USB is in use */ + disable_sleep(SLEEP_MASK_USB_DEVICE); + + /* Enable clocks */ + clock_enable_module(MODULE_USB, 0); + clock_enable_module(MODULE_USB, 1); + + /* TODO(crbug.com/496888): set up pinmux */ + gpio_config_module(MODULE_USB, 1); + + /* Make sure interrupts are disabled */ + GR_USB_GINTMSK = 0; + GR_USB_DAINTMSK = 0; + GR_USB_DIEPMSK = 0; + GR_USB_DOEPMSK = 0; + + /* Full-Speed Serial PHY */ + usb_reset_init_phy(); + + /* Global + DMA configuration */ + GR_USB_GAHBCFG = GAHBCFG_GLB_INTR_EN; + GR_USB_GAHBCFG |= GAHBCFG_HBSTLEN_INCR4; + if (usb->dma_en) + GR_USB_GAHBCFG |= GAHBCFG_DMA_EN; + + /* Device only, no SRP */ + GR_USB_GUSBCFG |= GUSBCFG_FDMOD; + GR_USB_GUSBCFG |= GUSBCFG_SRPCAP | GUSBCFG_HNPCAP; + + GR_USB_GCCFG &= ~GCCFG_VBDEN; + GR_USB_GOTGCTL |= GOTGCTL_BVALOEN; + GR_USB_GOTGCTL |= GOTGCTL_BVALOVAL; + + GR_USB_PCGCCTL = 0; + + if (usb->phy_type == USB_PHY_ULPI) { + /* TODO(nsanders): add HS support like so. + * GR_USB_DCFG = (GR_USB_DCFG & ~GC_USB_DCFG_DEVSPD_MASK) + * | DCFG_DEVSPD_HSULPI; + */ + GR_USB_DCFG = (GR_USB_DCFG & ~GC_USB_DCFG_DEVSPD_MASK) + | DCFG_DEVSPD_FSULPI; + } else { + GR_USB_DCFG = (GR_USB_DCFG & ~GC_USB_DCFG_DEVSPD_MASK) + | DCFG_DEVSPD_FS48; + } + + GR_USB_DCFG |= DCFG_NZLSOHSK; + + flush_all_fifos(); + + /* Clear pending interrupts again */ + GR_USB_GINTMSK = 0; + GR_USB_DIEPMSK = 0; + GR_USB_DOEPMSK = 0; + GR_USB_DAINT = 0xffffffff; + GR_USB_DAINTMSK = 0; + + /* TODO: What about the AHB Burst Length Field? It's 0 now. */ + GR_USB_GAHBCFG |= GAHBCFG_TXFELVL | GAHBCFG_PTXFELVL; + + /* Device only, no SRP */ + GR_USB_GUSBCFG |= GUSBCFG_FDMOD + | GUSBCFG_TOUTCAL(7) + /* FIXME: Magic number! 14 is for 15MHz! Use 9 for 30MHz */ + | GUSBCFG_USBTRDTIM(14); + + /* Be in disconnected state until we are ready */ + usb_disconnect(); + + /* If we've restored a nonzero device address, update our state. */ + if (GR_USB_DCFG & GC_USB_DCFG_DEVADDR_MASK) { + /* Caution: We only have one config TODAY, so there's no real + * difference between DS_CONFIGURED and DS_ADDRESS. + */ + device_state = DS_CONFIGURED; + configuration_value = 1; + } else { + device_state = DS_DEFAULT; + configuration_value = 0; + } + + /* Now that DCFG.DesDMA is accurate, prepare the FIFOs */ + setup_data_fifos(); + + usb_init_endpoints(); + + /* Clear any pending interrupts */ + for (i = 0; i < 16; i++) { + GR_USB_DIEPINT(i) = 0xffffffff; + GR_USB_DIEPTSIZ(i) = 0; + GR_USB_DOEPINT(i) = 0xffffffff; + GR_USB_DOEPTSIZ(i) = 0; + } + + if (usb->dma_en) { + GR_USB_DTHRCTL = DTHRCTL_TXTHRLEN_6 | DTHRCTL_RXTHRLEN_6; + GR_USB_DTHRCTL |= DTHRCTL_RXTHREN | DTHRCTL_ISOTHREN + | DTHRCTL_NONISOTHREN; + i = GR_USB_DTHRCTL; + } + + GR_USB_GINTSTS = 0xFFFFFFFF; + + GR_USB_GAHBCFG |= GAHBCFG_GLB_INTR_EN | GAHBCFG_TXFELVL + | GAHBCFG_PTXFELVL; + + if (!(usb->dma_en)) + GR_USB_GINTMSK |= GINTMSK(RXFLVL); + + /* Unmask some endpoint interrupt causes */ + GR_USB_DIEPMSK = DIEPMSK_EPDISBLDMSK | DIEPMSK_XFERCOMPLMSK; + GR_USB_DOEPMSK = DOEPMSK_EPDISBLDMSK | DOEPMSK_XFERCOMPLMSK | + DOEPMSK_SETUPMSK; + + /* Enable interrupt handlers */ + task_enable_irq(usb->irq); + + /* Allow USB interrupts to come in */ + GR_USB_GINTMSK |= + /* NAK bits that must be cleared by the DCTL register */ + GINTMSK(GOUTNAKEFF) | GINTMSK(GINNAKEFF) | + /* Initialization events */ + GINTMSK(USBRST) | GINTMSK(ENUMDONE) | + /* Reset detected while suspended. Need to wake up. */ + GINTMSK(RESETDET) | /* TODO: Do we need this? */ + /* Idle, Suspend detected. Should go to sleep. */ + GINTMSK(ERLYSUSP) | GINTMSK(USBSUSP); + + GR_USB_GINTMSK |= + /* Endpoint activity, cleared by the DOEPINT/DIEPINT regs */ + GINTMSK(OEPINT) | GINTMSK(IEPINT); + + /* Device registers have been setup */ + GR_USB_DCTL |= DCTL_PWRONPRGDONE; + udelay(10); + GR_USB_DCTL &= ~DCTL_PWRONPRGDONE; + + /* Clear global NAKs */ + GR_USB_DCTL |= DCTL_CGOUTNAK | DCTL_CGNPINNAK; + +#ifndef CONFIG_USB_INHIBIT_CONNECT + /* Indicate our presence to the USB host */ + usb_connect(); +#endif +} +#ifndef CONFIG_USB_INHIBIT_INIT +DECLARE_HOOK(HOOK_INIT, usb_init, HOOK_PRIO_DEFAULT); +#endif + +void usb_release(void) +{ + struct dwc_usb *usb = &usb_ctl; + + /* signal disconnect to host */ + usb_disconnect(); + + /* disable interrupt handlers */ + task_disable_irq(usb->irq); + + /* disable clocks */ + clock_enable_module(MODULE_USB, 0); + /* TODO: pin-mux */ + + /* USB is off, so sleep whenever */ + enable_sleep(SLEEP_MASK_USB_DEVICE); +} + +static int command_usb(int argc, char **argv) +{ + if (argc > 1) { + if (!strcasecmp("on", argv[1])) + usb_init(); + else if (!strcasecmp("off", argv[1])) + usb_release(); + } + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(usb, command_usb, + "[on|off|a|b]", + "Get/set the USB connection state and PHY selection"); + +#ifdef CONFIG_USB_SERIALNO +/* This will be subbed into USB_STR_SERIALNO. */ +struct usb_string_desc *usb_serialno_desc = + USB_WR_STRING_DESC(DEFAULT_SERIALNO); + +/* Update serial number */ +static int usb_set_serial(const char *serialno) +{ + struct usb_string_desc *sd = usb_serialno_desc; + int i; + + if (!serialno) + return EC_ERROR_INVAL; + + /* Convert into unicode usb string desc. */ + for (i = 0; i < USB_STRING_LEN; i++) { + sd->_data[i] = serialno[i]; + if (serialno[i] == 0) + break; + } + /* Count wchars (w/o null terminator) plus size & type bytes. */ + sd->_len = (i * 2) + 2; + sd->_type = USB_DT_STRING; + + return EC_SUCCESS; +} + +/* Retrieve serial number from pstate flash. */ +static int usb_load_serial(void) +{ + const char *serialno; + int rv; + + serialno = flash_read_serial(); + if (!serialno) + return EC_ERROR_ACCESS_DENIED; + + rv = usb_set_serial(serialno); + return rv; +} + + +/* Save serial number into pstate region. */ +static int usb_save_serial(const char *serialno) +{ + int rv; + + if (!serialno) + return EC_ERROR_INVAL; + + /* Save this new serial number to flash. */ + rv = flash_write_serial(serialno); + if (rv) + return rv; + + /* Load this new serial number to memory. */ + rv = usb_load_serial(); + return rv; +} + +static int command_serialno(int argc, char **argv) +{ + struct usb_string_desc *sd = usb_serialno_desc; + char buf[USB_STRING_LEN]; + int rv = EC_SUCCESS; + int i; + + if (argc != 1) { + if ((strcasecmp(argv[1], "set") == 0) && + (argc == 3)) { + ccprintf("Saving serial number\n"); + rv = usb_save_serial(argv[2]); + } else if ((strcasecmp(argv[1], "load") == 0) && + (argc == 2)) { + ccprintf("Loading serial number\n"); + rv = usb_load_serial(); + } else + return EC_ERROR_INVAL; + } + + for (i = 0; i < USB_STRING_LEN; i++) + buf[i] = sd->_data[i]; + ccprintf("Serial number: %s\n", buf); + return rv; +} + +DECLARE_CONSOLE_COMMAND(serialno, command_serialno, + "load/set [value]", + "Read and write USB serial number"); +#endif + |