| /* 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 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 = 0x80 /* Reserved bit */ |
| #ifdef CONFIG_USB_SELF_POWERED /* bus or self powered */ |
| | 0x40 |
| #endif |
| #ifdef CONFIG_USB_REMOTE_WAKEUP |
| | 0x20 |
| #endif |
| , |
| .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_expected = 0, |
| .out_data = 0, |
| .out_databuffer = ep0_setup_buf, |
| .out_databuffer_max = sizeof(ep0_setup_buf), |
| .rx_deferred = 0, |
| .in_packets = 0, |
| .in_pending = 0, |
| .in_data = 0, |
| .in_databuffer = ep0_in_buf, |
| .in_databuffer_max = sizeof(ep0_in_buf), |
| .tx_deferred = 0, |
| }; |
| |
| /* 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; |
| |
| |
| /* True if the HW Rx/OUT FIFO is currently listening. */ |
| int rx_ep_is_active(uint32_t ep_num) |
| { |
| return (GR_USB_DOEPCTL(ep_num) & DXEPCTL_EPENA) ? 1 : 0; |
| } |
| |
| /* Number of bytes the HW Rx/OUT FIFO has for us. |
| * |
| * @param ep_num USB endpoint |
| * |
| * @returns number of bytes ready, zero if none. |
| */ |
| int rx_ep_pending(uint32_t ep_num) |
| { |
| struct dwc_usb_ep *ep = usb_ctl.ep[ep_num]; |
| |
| return ep->out_pending; |
| } |
| |
| /* True if the Tx/IN FIFO can take some bytes from us. */ |
| int tx_ep_is_ready(uint32_t ep_num) |
| { |
| struct dwc_usb_ep *ep = usb_ctl.ep[ep_num]; |
| int ready; |
| |
| /* Is the tx hw idle? */ |
| ready = !(GR_USB_DIEPCTL(ep_num) & DXEPCTL_EPENA); |
| |
| /* Is there no pending data? */ |
| ready &= (ep->in_pending == 0); |
| return ready; |
| } |
| |
| /* Write packets of data IN to the host. |
| * |
| * This function uses DMA, so the *data write buffer |
| * must persist until the write completion event. |
| * |
| * @param ep_num USB endpoint to write |
| * @param len number of bytes to write |
| * @param data pointer of data to write |
| * |
| * @return bytes written |
| */ |
| int usb_write_ep(uint32_t ep_num, int len, void *data) |
| { |
| struct dwc_usb_ep *ep = usb_ctl.ep[ep_num]; |
| |
| if (GR_USB_DIEPCTL(ep_num) & DXEPCTL_EPENA) { |
| CPRINTS("usb_write_ep ep%d: FAIL: tx already in progress!"); |
| return 0; |
| } |
| |
| /* 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 - 1) / USB_MAX_PACKET_SIZE; |
| ep->in_pending = len; |
| ep->in_data = data; |
| |
| GR_USB_DIEPTSIZ(ep_num) = 0; |
| |
| GR_USB_DIEPTSIZ(ep_num) |= DXEPTSIZ_PKTCNT(ep->in_packets); |
| GR_USB_DIEPTSIZ(ep_num) |= DXEPTSIZ_XFERSIZE(len); |
| GR_USB_DIEPDMA(ep_num) = (uint32_t)(ep->in_data); |
| |
| /* We could support longer multi-dma transfers here. */ |
| ep->in_pending -= len; |
| ep->in_packets -= ep->in_packets; |
| ep->in_data += len; |
| |
| /* We are ready to enable this endpoint to start transferring data. */ |
| GR_USB_DIEPCTL(ep_num) |= DXEPCTL_CNAK | DXEPCTL_EPENA; |
| return len; |
| } |
| |
| /* Tx/IN interrupt handler */ |
| void usb_epN_tx(uint32_t ep_num) |
| { |
| struct dwc_usb_ep *ep = usb_ctl.ep[ep_num]; |
| uint32_t dieptsiz = GR_USB_DIEPTSIZ(ep_num); |
| |
| if (GR_USB_DIEPCTL(ep_num) & DXEPCTL_EPENA) { |
| CPRINTS("usb_epN_tx ep%d: tx still active.", ep_num); |
| return; |
| } |
| |
| /* clear the Tx/IN interrupts */ |
| GR_USB_DIEPINT(ep_num) = 0xffffffff; |
| |
| /* |
| * Let's assume this is actually true. |
| * We could support multi-dma transfers here. |
| */ |
| ep->in_packets = 0; |
| ep->in_pending = dieptsiz & GC_USB_DIEPTSIZ1_XFERSIZE_MASK; |
| |
| if (ep->tx_deferred) |
| hook_call_deferred(ep->tx_deferred, 0); |
| } |
| |
| /* Read a packet of data OUT from the host. |
| * |
| * This function uses DMA, so the *data write buffer |
| * must persist until the read completion event. |
| * |
| * @param ep_num USB endpoint to read |
| * @param len number of bytes to read |
| * @param data pointer of data to read |
| * |
| * @return EC_SUCCESS on success |
| */ |
| int usb_read_ep(uint32_t ep_num, int len, void *data) |
| { |
| struct dwc_usb_ep *ep = usb_ctl.ep[ep_num]; |
| int packets = (len + USB_MAX_PACKET_SIZE - 1) / USB_MAX_PACKET_SIZE; |
| |
| ep->out_data = data; |
| ep->out_pending = 0; |
| ep->out_expected = len; |
| |
| GR_USB_DOEPTSIZ(ep_num) = 0; |
| GR_USB_DOEPTSIZ(ep_num) |= DXEPTSIZ_PKTCNT(packets); |
| GR_USB_DOEPTSIZ(ep_num) |= DXEPTSIZ_XFERSIZE(len); |
| GR_USB_DOEPDMA(ep_num) = (uint32_t)ep->out_data; |
| |
| GR_USB_DOEPCTL(ep_num) |= DXEPCTL_CNAK | DXEPCTL_EPENA; |
| return EC_SUCCESS; |
| } |
| |
| /* Rx/OUT endpoint interrupt handler */ |
| void usb_epN_rx(uint32_t ep_num) |
| { |
| struct dwc_usb_ep *ep = usb_ctl.ep[ep_num]; |
| |
| /* Still receiving data. Let's wait. */ |
| if (rx_ep_is_active(ep_num)) |
| return; |
| |
| /* Bytes received decrement DOEPTSIZ XFERSIZE */ |
| if (GR_USB_DOEPINT(ep_num) & DOEPINT_XFERCOMPL) { |
| if (ep->out_expected > 0) { |
| ep->out_pending = |
| ep->out_expected - |
| (GR_USB_DOEPTSIZ(ep_num) & |
| GC_USB_DOEPTSIZ1_XFERSIZE_MASK); |
| } else { |
| CPRINTF("usb_ep%d_rx: unexpected RX DOEPTSIZ %08x\n", |
| ep_num, GR_USB_DOEPTSIZ(ep_num)); |
| ep->out_pending = 0; |
| } |
| ep->out_expected = 0; |
| GR_USB_DOEPTSIZ(ep_num) = 0; |
| } |
| |
| /* clear the RX/OUT interrupts */ |
| GR_USB_DOEPINT(ep_num) = 0xffffffff; |
| |
| if (ep->rx_deferred) |
| hook_call_deferred(ep->rx_deferred, 0); |
| } |
| |
| /* Reset endpoint HW block. */ |
| void epN_reset(uint32_t ep_num) |
| { |
| GR_USB_DOEPCTL(ep_num) = DXEPCTL_MPS(USB_MAX_PACKET_SIZE) | |
| DXEPCTL_USBACTEP | DXEPCTL_EPTYPE_BULK; |
| GR_USB_DIEPCTL(ep_num) = DXEPCTL_MPS(USB_MAX_PACKET_SIZE) | |
| DXEPCTL_USBACTEP | DXEPCTL_EPTYPE_BULK | |
| DXEPCTL_TXFNUM(ep_num); |
| GR_USB_DAINTMSK |= DAINT_INEP(ep_num) | |
| DAINT_OUTEP(ep_num); |
| } |
| |
| |
| /****************************************************************************** |
| * Internal and EP0 functions. |
| */ |
| |
| |
| 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(ep_num) = 0; |
| |
| GR_USB_DIEPTSIZ(ep_num) |= 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; |
| case USB_DT_DEBUG: |
| /* Not supported */ |
| 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); |
| } |
| |
| /* Print USB info and stats */ |
| static void usb_info(void) |
| { |
| struct dwc_usb *usb = &usb_ctl; |
| int i; |
| |
| CPRINTF("USB settings: %s%s%s\n", |
| usb->speed == USB_SPEED_FS ? "FS " : "HS ", |
| usb->phy_type == USB_PHY_INTERNAL ? "Internal Phy " : "ULPI ", |
| usb->dma_en ? "DMA " : ""); |
| |
| for (i = 0; i < USB_EP_COUNT; i++) { |
| CPRINTF("Endpoint %d activity: %s%s\n", i, |
| rx_ep_is_active(i) ? "RX " : "", |
| tx_ep_is_ready(i) ? "" : "TX "); |
| } |
| } |
| |
| 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(); |
| else if (!strcasecmp("info", argv[1])) |
| usb_info(); |
| return EC_SUCCESS; |
| } |
| |
| return EC_ERROR_PARAM1; |
| } |
| DECLARE_CONSOLE_COMMAND(usb, command_usb, |
| "[on|off|info]", |
| "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 |
| |