summaryrefslogtreecommitdiff
path: root/chip/stm32/usb_dwc.c
diff options
context:
space:
mode:
authorNick Sanders <nsanders@chromium.org>2016-08-02 19:35:44 -0700
committerchrome-bot <chrome-bot@chromium.org>2016-09-01 22:56:22 -0700
commita4bfc663a3cd645b43963bb814269efe864f8d1e (patch)
tree185ab7cc413c4b580ea50845a024838aba577732 /chip/stm32/usb_dwc.c
parent54f4612764e07e5e3ccd8a4af04ee83a46454612 (diff)
downloadchrome-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.c1230
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
+