/*************************************************************************** * * Copyright 2015-2019 BES. * All rights reserved. All unpublished rights reserved. * * No part of this work may be used or reproduced in any form or by any * means, or stored in a database or retrieval system, without prior written * permission of BES. * * Use of this work is governed by a license granted by BES. * This work contains confidential and proprietary information of * BES. which is protected by copyright, trade secret, * trademark and other intellectual property rights. * ****************************************************************************/ #ifdef CHIP_HAS_USB #include "hal_usb.h" #include "cmsis_nvic.h" #include "hal_chipid.h" #include "hal_cmu.h" #include "hal_sysfreq.h" #include "hal_timer.h" #include "hal_trace.h" #include "hwtimer_list.h" #include "plat_addr_map.h" #include "pmu.h" #include "reg_usb.h" #include "string.h" #include "usb_descriptor.h" #ifdef CHIP_HAS_USBPHY #include "usbphy.h" #endif // TODO List: // 1) Thread-safe issue (race condition) // Hardware configuration: // GHWCFG1 = 0x00000000 // GHWCFG2 = 0x228a5512 // GHWCFG3 = 0x03f404e8 // GHWCFG4 = 0x16108020 #define USB_TRACE(n, mask, str, ...) \ { \ if (usb_trmask & (1 << mask)) { \ TRACE(n, str, ##__VA_ARGS__); \ } \ } #define USB_FUNC_ENTRY_TRACE(mask) \ { \ if (usb_trmask & (1 << mask)) { \ FUNC_ENTRY_TRACE(); \ } \ } #ifndef CHIP_BEST1000 #define CHIP_HAS_USBIF #endif #ifdef USB_HIGH_SPEED #ifndef USB_ISO_INTERVAL #define USB_ISO_INTERVAL 8 // ANY value larger than 1 #endif #if (USB_ISO_INTERVAL <= 1) #error "Invalid USB_ISO_INTERVAL" #endif #else #undef USB_ISO_INTERVAL #define USB_ISO_INTERVAL 1 #endif #ifdef SIMU_UAUD_MAX_PKT // SEND/RECV with mps = USB_MAX_PACKET_SIZE_ISO #define USB_FIFO_MPS_ISO_RECV USB_FIFO_MPS_ISO_SEND #else #ifdef USB_HIGH_SPEED // 384K sample rate, 32 bits, 2 channels = 3072 bytes/ms #define USB_FIFO_MPS_ISO_RECV 1200 #else // 192K sample rate, 16 bits, 2 channels = 768 bytes/ms #define USB_FIFO_MPS_ISO_RECV USB_FIFO_MPS_ISO_SEND #endif #endif #define PIN_CHECK_ENABLE_INTERVAL MS_TO_TICKS(1) #define PIN_CHECK_WAIT_RESUME_INTERVAL MS_TO_TICKS(30) #define LPM_CHECK_INTERVAL MS_TO_TICKS(100) //(US_TO_TICKS(8 + 50) + 2) #define USB_SYS_FREQ HAL_CMU_FREQ_52M enum DEVICE_STATE { ATTACHED, POWERED, DEFAULT, ADDRESS, CONFIGURED, }; struct EPN_OUT_TRANSFER { uint8_t *data; uint32_t length; bool enabled; }; struct EPN_IN_TRANSFER { const uint8_t *data; uint32_t length; uint16_t pkt_cnt; bool zero_len_pkt; bool enabled; }; enum DATA_PID_T { DATA_PID_DATA0 = 0, DATA_PID_DATA1 = 2, DATA_PID_DATA2 = 1, DATA_PID_MDATA = 3, }; enum USB_SLEEP_T { USB_SLEEP_NONE, USB_SLEEP_SUSPEND, USB_SLEEP_L1, }; static const uint32_t usb_trmask = (1 << 0); //(1 << 2) | (1 << 6) | (1 << 8); static struct USBC_T *const usbc = (struct USBC_T *)USB_BASE; #ifdef CHIP_HAS_USBIF static struct USBIF_T *const usbif = (struct USBIF_T *)(USB_BASE + 0x00040000); #endif static uint16_t fifo_addr; static uint32_t ep0_out_buffer[USB_MAX_PACKET_SIZE_CTRL / 4]; static uint32_t ep0_in_buffer[USB_MAX_PACKET_SIZE_CTRL / 4]; static struct EPN_OUT_TRANSFER epn_out_transfer[EPNUM - 1]; static struct EPN_IN_TRANSFER epn_in_transfer[EPNUM - 1]; #ifdef USB_HIGH_SPEED static uint8_t epn_in_mc[EPNUM - 1]; #endif static struct EP0_TRANSFER ep0_transfer; static struct HAL_USB_CALLBACKS callbacks; static volatile enum DEVICE_STATE device_state = ATTACHED; static uint8_t device_cfg = 0; static uint8_t currentAlternate; static uint16_t currentInterface; static enum USB_SLEEP_T device_sleep_status; static uint8_t device_pwr_wkup_status; static uint8_t device_test_mode; #if defined(USB_SUSPEND) && (defined(PMU_USB_PIN_CHECK) || defined(USB_LPM)) #ifdef USB_LPM static bool lpm_entry; #endif static bool usbdev_timer_active; static HWTIMER_ID usbdev_timer; static void hal_usb_stop_usbdev_timer(void); #endif static void hal_usb_irq_handler(void); static void hal_usb_init_ep0_transfer(void) { ep0_transfer.stage = NONE_STAGE; ep0_transfer.data = NULL; ep0_transfer.length = 0; ep0_transfer.trx_len = 0; } static void hal_usb_init_epn_transfer(void) { memset(&epn_out_transfer[0], 0, sizeof(epn_out_transfer)); memset(&epn_in_transfer[0], 0, sizeof(epn_in_transfer)); } static void get_setup_packet(uint8_t *data, struct SETUP_PACKET *packet) { packet->bmRequestType.direction = (data[0] & 0x80) >> 7; packet->bmRequestType.type = (data[0] & 0x60) >> 5; packet->bmRequestType.recipient = data[0] & 0x1f; packet->bRequest = data[1]; packet->wValue = (data[2] | data[3] << 8); packet->wIndex = (data[4] | data[5] << 8); packet->wLength = (data[6] | data[7] << 8); } static void reset_epn_out_transfer(uint8_t ep) { bool enabled; enabled = epn_out_transfer[ep - 1].enabled; // Clear epn_out_transfer[] before invoking the callback, // so that ep can be restarted in the callback memset(&epn_out_transfer[ep - 1], 0, sizeof(epn_out_transfer[0])); if (enabled && callbacks.epn_recv_compl[ep - 1]) { callbacks.epn_recv_compl[ep - 1](NULL, 0, XFER_COMPL_ERROR); } } static void reset_epn_in_transfer(uint8_t ep) { bool enabled; enabled = epn_in_transfer[ep - 1].enabled; // Clear epn_in_transfer[] before invoking the callback, // so that ep can be restarted in the callback memset(&epn_in_transfer[ep - 1], 0, sizeof(epn_in_transfer[0])); if (enabled && callbacks.epn_send_compl[ep - 1]) { callbacks.epn_send_compl[ep - 1](NULL, 0, XFER_COMPL_ERROR); } } static uint32_t get_ep_type(enum EP_DIR dir, uint8_t ep) { uint32_t type; if (ep == 0) { return E_CONTROL; } if (dir == EP_OUT) { type = GET_BITFIELD(usbc->DOEPnCONFIG[ep - 1].DOEPCTL, USBC_EPTYPE); } else { type = GET_BITFIELD(usbc->DIEPnCONFIG[ep - 1].DIEPCTL, USBC_EPTYPE); } return type; } static void _set_global_out_nak(void) { uint32_t start; uint32_t sts; uint32_t ep; usbc->DCTL |= USBC_SGOUTNAK; start = hal_sys_timer_get(); while (((usbc->GINTSTS & USBC_GOUTNAKEFF) == 0) && (hal_sys_timer_get() - start < MS_TO_TICKS(5))) { if (hal_sys_timer_get() - start >= US_TO_TICKS(60)) { if ((usbc->GRSTCTL & USBC_DMAREQ) == 0) { break; } } } if (usbc->GINTSTS & USBC_GOUTNAKEFF) { return; } start = hal_sys_timer_get(); while ((usbc->GINTSTS & USBC_RXFLVL) && (hal_sys_timer_get() - start < MS_TO_TICKS(5))) { sts = usbc->GRXSTSP; ep = GET_BITFIELD(sts, USBC_EPNUM); ASSERT(ep == 0, "Global OUT NAK: Only ep0 packets can be dropped: ep=%u sts=0x%08X", ep, sts); // NOTE: // Global OUT NAK pattern cannot be read out from GRXSTSP -- consumed by // controller automatically when poping? hal_sys_timer_delay(US_TO_TICKS(60)); if (usbc->GINTSTS & USBC_GOUTNAKEFF) { return; } } ASSERT((usbc->GINTSTS & USBC_GOUTNAKEFF), "Global OUT NAK: Failed to recover"); } static void _disable_out_ep(uint8_t ep, uint32_t set, uint32_t clr) { volatile uint32_t *ctrl; volatile uint32_t *intr; uint32_t doepctl; if (ep >= EPNUM) { return; } if (ep == 0) { ctrl = &usbc->DOEPCTL0; intr = &usbc->DOEPINT0; } else { ctrl = &usbc->DOEPnCONFIG[ep - 1].DOEPCTL; intr = &usbc->DOEPnCONFIG[ep - 1].DOEPINT; } doepctl = *ctrl; if ((doepctl & (USBC_EPENA | USBC_USBACTEP)) == 0) { goto _exit; } if ((doepctl & (USBC_EPENA | USBC_NAKSTS)) == USBC_NAKSTS && set == USBC_SNAK && clr == 0) { goto _exit; } _set_global_out_nak(); *ctrl |= USBC_SNAK | USBC_USBACTEP | set; if (clr) { *ctrl &= ~clr; } // EP0 out cannot be disabled, but stalled if (ep != 0) { if (doepctl & USBC_EPENA) { *intr = USBC_EPDISBLD; *ctrl |= USBC_EPDIS; while ((*intr & USBC_EPDISBLD) == 0) ; } if ((doepctl & USBC_USBACTEP) == 0) { *ctrl &= ~USBC_USBACTEP; } } usbc->DCTL |= USBC_CGOUTNAK; _exit: if (ep > 0) { reset_epn_out_transfer(ep); } } static void _disable_in_ep(uint8_t ep, uint32_t set, uint32_t clr) { volatile uint32_t *ctrl; volatile uint32_t *intr; uint32_t diepctl; if (ep >= EPNUM) { return; } if (ep == 0) { ctrl = &usbc->DIEPCTL0; intr = &usbc->DIEPINT0; } else { ctrl = &usbc->DIEPnCONFIG[ep - 1].DIEPCTL; intr = &usbc->DIEPnCONFIG[ep - 1].DIEPINT; } diepctl = *ctrl; if ((diepctl & (USBC_EPENA | USBC_USBACTEP)) == 0) { goto _exit; } if ((diepctl & (USBC_EPENA | USBC_NAKSTS)) == USBC_NAKSTS && set == USBC_SNAK && clr == 0) { goto _exit; } *intr = USBC_INEPNAKEFF; *ctrl |= USBC_SNAK | USBC_USBACTEP | set; if ((diepctl & USBC_EPENA) && (diepctl & USBC_NAKSTS) == 0) { while ((*intr & USBC_INEPNAKEFF) == 0) ; *intr = USBC_INEPNAKEFF; } if (clr) { *ctrl &= ~clr; } if (diepctl & USBC_EPENA) { *intr = USBC_EPDISBLD; *ctrl |= USBC_EPDIS; while ((*intr & USBC_EPDISBLD) == 0) ; } usbc->GRSTCTL = USBC_TXFNUM(ep) | USBC_TXFFLSH; while ((usbc->GRSTCTL & USBC_TXFFLSH) != 0) ; if ((diepctl & USBC_USBACTEP) == 0) { *ctrl &= ~USBC_USBACTEP; } _exit: if (ep > 0) { reset_epn_in_transfer(ep); } } void hal_usb_disable_ep(enum EP_DIR dir, uint8_t ep) { USB_TRACE(3, 14, "%s: %d ep%d", __FUNCTION__, dir, ep); if (dir == EP_OUT) { _disable_out_ep(ep, USBC_SNAK, 0); } else { _disable_in_ep(ep, USBC_SNAK, 0); } } void hal_usb_stall_ep(enum EP_DIR dir, uint8_t ep) { uint32_t set; uint32_t clr; USB_TRACE(3, 13, "%s: %d ep%d", __FUNCTION__, dir, ep); set = USBC_STALL; clr = 0; if (dir == EP_OUT) { _disable_out_ep(ep, set, clr); } else { _disable_in_ep(ep, set, clr); } } void hal_usb_unstall_ep(enum EP_DIR dir, uint8_t ep) { uint32_t set; uint32_t clr; uint8_t type; USB_TRACE(3, 12, "%s: %d ep%d", __FUNCTION__, dir, ep); set = USBC_SNAK; clr = USBC_STALL; type = get_ep_type(dir, ep); if (type == E_INTERRUPT || type == E_BULK) { set |= USBC_SETD0PID; } if (hal_usb_get_ep_stall_state(dir, ep) == 0) { // Ep not in stall state if (ep != 0 && (type == E_INTERRUPT || type == E_BULK)) { if (dir == EP_OUT) { usbc->DOEPnCONFIG[ep - 1].DOEPCTL |= USBC_SETD0PID; } else { usbc->DIEPnCONFIG[ep - 1].DIEPCTL |= USBC_SETD0PID; } } return; } if (dir == EP_OUT) { _disable_out_ep(ep, set, clr); } else { _disable_in_ep(ep, set, clr); } } int hal_usb_get_ep_stall_state(enum EP_DIR dir, uint8_t ep) { volatile uint32_t *ctrl; if (ep >= EPNUM) { return 0; } // Select ctl register if (ep == 0) { if (dir == EP_IN) { ctrl = &usbc->DIEPCTL0; } else { ctrl = &usbc->DOEPCTL0; } } else { if (dir == EP_IN) { ctrl = &usbc->DIEPnCONFIG[ep - 1].DIEPCTL; } else { ctrl = &usbc->DOEPnCONFIG[ep - 1].DOEPCTL; } } return ((*ctrl & USBC_STALL) != 0); } void hal_usb_stop_ep(enum EP_DIR dir, uint8_t ep) { USB_TRACE(3, 11, "%s: %d ep%d", __FUNCTION__, dir, ep); hal_usb_disable_ep(dir, ep); } static void hal_usb_stop_all_out_eps(void) { int i; volatile uint32_t *ctrl; volatile uint32_t *intr; uint32_t doepctl; USB_FUNC_ENTRY_TRACE(11); // Enable global out nak _set_global_out_nak(); for (i = 0; i < EPNUM; i++) { if (i == 0) { ctrl = &usbc->DOEPCTL0; intr = &usbc->DOEPINT0; } else { ctrl = &usbc->DOEPnCONFIG[i - 1].DOEPCTL; intr = &usbc->DOEPnCONFIG[i - 1].DOEPINT; } doepctl = *ctrl; // BUS RESET will clear USBC_USBACTEP but keep USBC_EPENA in ep ctrl if (doepctl & (USBC_EPENA | USBC_USBACTEP)) { *ctrl |= USBC_SNAK | USBC_USBACTEP; // EP0 out cannot be disabled, but stalled if (i != 0) { if (doepctl & USBC_EPENA) { *intr = USBC_EPDISBLD; *ctrl |= USBC_EPDIS; while ((*intr & USBC_EPDISBLD) == 0) ; } if ((doepctl & USBC_USBACTEP) == 0) { *ctrl &= ~USBC_USBACTEP; } } } } // Disable global out nak usbc->DCTL |= USBC_CGOUTNAK; } static void hal_usb_stop_all_in_eps(void) { int i; volatile uint32_t *ctrl; volatile uint32_t *intr; uint32_t diepctl; USB_FUNC_ENTRY_TRACE(11); usbc->DCTL |= USBC_SGNPINNAK; while ((usbc->GINTSTS & USBC_GINNAKEFF) == 0) ; for (i = 0; i < EPNUM; i++) { if (i == 0) { ctrl = &usbc->DIEPCTL0; intr = &usbc->DIEPINT0; } else { ctrl = &usbc->DIEPnCONFIG[i - 1].DIEPCTL; intr = &usbc->DIEPnCONFIG[i - 1].DIEPINT; } diepctl = *ctrl; // BUS RESET will clear USBC_USBACTEP but keep USBC_EPENA in ep ctrl if (diepctl & (USBC_EPENA | USBC_USBACTEP)) { *intr = USBC_INEPNAKEFF; *ctrl |= USBC_SNAK | USBC_USBACTEP; if ((diepctl & USBC_EPENA) && (diepctl & USBC_NAKSTS) == 0) { while ((*intr & USBC_INEPNAKEFF) == 0) ; *intr = USBC_INEPNAKEFF; } if (diepctl & USBC_EPENA) { *intr = USBC_EPDISBLD; *ctrl |= USBC_EPDIS; while ((*intr & USBC_EPDISBLD) == 0) ; } if ((diepctl & USBC_USBACTEP) == 0) { *ctrl &= ~USBC_USBACTEP; } } } // Flush Tx Fifo usbc->GRSTCTL = USBC_TXFNUM(0x10) | USBC_TXFFLSH | USBC_RXFFLSH; while ((usbc->GRSTCTL & (USBC_TXFFLSH | USBC_RXFFLSH)) != 0) ; usbc->DCTL |= USBC_CGNPINNAK; } static void hal_usb_flush_tx_fifo(uint8_t ep) { usbc->DCTL |= USBC_SGNPINNAK; while ((usbc->GINTSTS & USBC_GINNAKEFF) == 0) ; while ((usbc->GRSTCTL & USBC_AHBIDLE) == 0) ; // while ((usbc->GRSTCTL & USBC_TXFFLSH) != 0); usbc->GRSTCTL = USBC_TXFNUM(ep) | USBC_TXFFLSH; while ((usbc->GRSTCTL & USBC_TXFFLSH) != 0) ; usbc->DCTL |= USBC_CGNPINNAK; } static void hal_usb_flush_all_tx_fifos(void) { hal_usb_flush_tx_fifo(0x10); } static void POSSIBLY_UNUSED hal_usb_flush_rx_fifo(void) { _set_global_out_nak(); usbc->GRSTCTL |= USBC_RXFFLSH; while ((usbc->GRSTCTL & USBC_RXFFLSH) != 0) ; usbc->DCTL |= USBC_CGOUTNAK; } static void hal_usb_alloc_ep0_fifo(void) { uint32_t ram_size; uint32_t ram_avail; if ((usbc->GHWCFG2 & USBC_DYNFIFOSIZING) == 0) { return; } // All endpoints have been stopped in hal_usb_irq_reset() // Configure FIFOs // RX FIFO Calculation // ------------------- // SETUP Packets : 4 * n + 6 // Global OUT NAK : 1 // DATA Packets + Status Info : (MPS / 4 + 1) * m // OutEp XFER COMPL : 1 * outEpNum // OutEp Disable : 1 * outEpNum #ifdef USB_ISO #define RXFIFOSIZE \ ((4 * CTRL_EPNUM + 6) + 1 + (2 * (USB_FIFO_MPS_ISO_RECV / 4 + 1)) + \ (EPNUM * 2)) #else #define RXFIFOSIZE \ ((4 * CTRL_EPNUM + 6) + 1 + (2 * (USB_MAX_PACKET_SIZE_BULK / 4 + 1)) + \ (EPNUM * 2)) #endif #define EP0_TXFIFOSIZE (2 * (USB_MAX_PACKET_SIZE_CTRL + 3) / 4) #if (RXFIFOSIZE + EP0_TXFIFOSIZE > SPFIFORAM_SIZE) #error "Invalid FIFO size configuration" #endif ram_size = GET_BITFIELD(usbc->GDFIFOCFG, USBC_GDFIFOCFG); ram_avail = GET_BITFIELD(usbc->GDFIFOCFG, USBC_EPINFOBASEADDR); ASSERT(SPFIFORAM_SIZE == ram_size, "Bad dfifo size: %u (should be %u)", ram_size, SPFIFORAM_SIZE); ASSERT(RXFIFOSIZE + EP0_TXFIFOSIZE <= ram_avail, "Bad dfifo size cfg: rx=%u ep0=%u avail=%u", RXFIFOSIZE, EP0_TXFIFOSIZE, ram_avail); // Rx Fifo Size (and init fifo_addr) usbc->GRXFSIZ = USBC_RXFDEP(RXFIFOSIZE); fifo_addr = RXFIFOSIZE; // EP0 / Non-periodic Tx Fifo Size usbc->GNPTXFSIZ = USBC_NPTXFSTADDR(fifo_addr) | USBC_NPTXFDEPS(EP0_TXFIFOSIZE); fifo_addr += EP0_TXFIFOSIZE; // Flush Tx FIFOs hal_usb_flush_all_tx_fifos(); } static void hal_usb_alloc_epn_fifo(uint8_t ep, uint16_t mps) { uint16_t size; uint32_t ram_avail; if (ep == 0 || ep >= EPNUM) { return; } size = (mps + 3) / 4 * 2; ram_avail = GET_BITFIELD(usbc->GDFIFOCFG, USBC_EPINFOBASEADDR); ASSERT(fifo_addr + size <= ram_avail, "Fifo overflow: fifo_addr=%u, size=%u, avail=%u", fifo_addr, size, ram_avail); usbc->DTXFSIZE[ep - 1].DIEPTXFn = USBC_INEPNTXFSTADDR(fifo_addr) | USBC_INEPNTXFDEP(size); fifo_addr += size; } static void hal_usb_soft_reset(void) { usbc->GRSTCTL |= USBC_CSFTRST; while ((usbc->GRSTCTL & USBC_CSFTRST) != 0) ; while ((usbc->GRSTCTL & USBC_AHBIDLE) == 0) ; } static void hal_usb_init_phy(void) { #ifdef USB_HIGH_SPEED usbc->GUSBCFG |= USBC_FORCEDEVMODE | USBC_ULPIAUTORES | USBC_ULPIFSLS | USBC_ULPI_UTMI_SEL; usbc->GUSBCFG &= ~(USBC_FSINTF | USBC_PHYIF | USBC_PHYSEL | USBC_USBTRDTIM_MASK); #else usbc->GUSBCFG |= USBC_FORCEDEVMODE | USBC_ULPIAUTORES | USBC_ULPIFSLS | USBC_PHYSEL | USBC_ULPI_UTMI_SEL; usbc->GUSBCFG &= ~(USBC_FSINTF | USBC_PHYIF | USBC_USBTRDTIM_MASK); #endif // USB turnaround time = 4 * AHB Clock + 1 PHY Clock, in terms of PHY clocks. // If AHB Clock >= PHY Clock, time can be set to 5. // If AHB Clock * 2 == PHY Clock, time should be set to 9. usbc->GUSBCFG |= USBC_USBTRDTIM(5); } static void hal_usb_device_init(void) { int i; uint8_t speed; #ifdef CHIP_HAS_USBPHY usbphy_open(); #endif #ifdef USB_HIGH_SPEED #ifdef CHIP_HAS_USBIF usbif->USBIF_00 &= ~(USBIF_00_CFG_DR_SUSPEND | USBIF_00_CFG_REG_SUSPEND); usbif->USBIF_08 &= ~USBIF_08_CFG_SEL48M; #endif speed = 0; #else #ifdef CHIP_HAS_USBIF usbif->USBIF_00 |= USBIF_00_CFG_DR_SUSPEND | USBIF_00_CFG_REG_SUSPEND; usbif->USBIF_08 |= USBIF_08_CFG_SEL48M; #endif speed = 3; #endif #ifdef CHIP_BEST2000 if (hal_get_chip_metal_id() >= HAL_CHIP_METAL_ID_1) { // dr_suspend and reg_suspend are inverted since metal 1 usbif->USBIF_00 ^= USBIF_00_CFG_DR_SUSPEND | USBIF_00_CFG_REG_SUSPEND; } #endif #ifdef USB_HIGH_SPEED // Wait until usbphy clock is ready hal_sys_timer_delay(US_TO_TICKS(60)); #endif hal_usb_soft_reset(); hal_usb_init_phy(); // Reset after selecting PHY hal_usb_soft_reset(); // Some core cfg (except for PHY selection) will also be reset during soft // reset hal_usb_init_phy(); usbc->DCFG &= ~(USBC_DEVSPD_MASK | USBC_PERFRINT_MASK); usbc->DCFG |= USBC_DEVSPD(speed) | USBC_NZSTSOUTHSHK | USBC_PERFRINT(0); // Clear previous interrupts usbc->GINTMSK = 0; usbc->GINTSTS = ~0UL; usbc->DAINTMSK = 0; usbc->DOEPMSK = 0; usbc->DIEPMSK = 0; for (i = 0; i < EPNUM; ++i) { if (i == 0) { usbc->DOEPINT0 = ~0UL; usbc->DIEPINT0 = ~0UL; } else { usbc->DOEPnCONFIG[i - 1].DOEPINT = ~0UL; usbc->DIEPnCONFIG[i - 1].DIEPINT = ~0UL; } } usbc->GINTMSK = USBC_USBRST | USBC_ENUMDONE | USBC_ERLYSUSP | USBC_USBSUSP; usbc->DCTL &= ~USBC_SFTDISCON; #if (USB_ISO_INTERVAL > 1) // Not to check frame number and ignore ISO incomplete interrupts usbc->DCTL |= USBC_IGNRFRMNUM; #endif // Enable DMA mode // Burst size 16 words usbc->GAHBCFG = USBC_DMAEN | USBC_HBSTLEN(7); usbc->GAHBCFG |= USBC_GLBLINTRMSK; } static void hal_usb_soft_disconnect(void) { // Disable global interrupt usbc->GAHBCFG &= ~USBC_GLBLINTRMSK; // Soft disconnection usbc->DCTL |= USBC_SFTDISCON; hal_usb_device_init(); } static void enable_usb_irq(void) { NVIC_SetVector(USB_IRQn, (uint32_t)hal_usb_irq_handler); #ifdef USB_ISO NVIC_SetPriority(USB_IRQn, IRQ_PRIORITY_HIGH); #else NVIC_SetPriority(USB_IRQn, IRQ_PRIORITY_NORMAL); #endif NVIC_ClearPendingIRQ(USB_IRQn); NVIC_EnableIRQ(USB_IRQn); } int hal_usb_open(const struct HAL_USB_CALLBACKS *c, enum HAL_USB_API_MODE m) { if (c == NULL) { return 1; } if (c->device_desc == NULL || c->cfg_desc == NULL || c->string_desc == NULL || c->setcfg == NULL) { return 2; } if (device_state != ATTACHED) { return 3; } device_state = POWERED; device_sleep_status = USB_SLEEP_NONE; hal_sysfreq_req(HAL_SYSFREQ_USER_USB, USB_SYS_FREQ); #if defined(USB_SUSPEND) && (defined(PMU_USB_PIN_CHECK) || defined(USB_LPM)) if (usbdev_timer == NULL) { usbdev_timer = hwtimer_alloc(NULL, NULL); ASSERT(usbdev_timer, "Failed to alloc usbdev_timer"); } #endif hal_cmu_usb_set_device_mode(); hal_cmu_usb_clock_enable(); memcpy(&callbacks, c, sizeof(callbacks)); if (usbc->GAHBCFG & USBC_GLBLINTRMSK) { hal_usb_soft_disconnect(); } else { hal_usb_device_init(); } enable_usb_irq(); if (m == HAL_USB_API_BLOCKING) { while (device_state != CONFIGURED) ; } return 0; } int hal_usb_reopen(const struct HAL_USB_CALLBACKS *c, uint8_t dcfg, uint8_t alt, uint16_t itf) { if (c == NULL) { return 1; } if (c->device_desc == NULL || c->cfg_desc == NULL || c->string_desc == NULL || c->setcfg == NULL) { return 2; } hal_sysfreq_req(HAL_SYSFREQ_USER_USB, USB_SYS_FREQ); memcpy(&callbacks, c, sizeof(callbacks)); device_state = CONFIGURED; device_cfg = dcfg; currentAlternate = alt; currentInterface = itf; enable_usb_irq(); return 0; } void hal_usb_close(void) { #ifdef USB_SUSPEND #if defined(PMU_USB_PIN_CHECK) || defined(USB_LPM) // Stop pin check timer hal_usb_stop_usbdev_timer(); #ifdef PMU_USB_PIN_CHECK // Disabe PMU pin status check pmu_usb_disable_pin_status_check(); #endif #endif #endif NVIC_DisableIRQ(USB_IRQn); device_state = ATTACHED; // Disable global interrupt usbc->GAHBCFG &= ~USBC_GLBLINTRMSK; // Soft disconnection usbc->DCTL |= USBC_SFTDISCON; // Soft reset usbc->GRSTCTL |= USBC_CSFTRST; usbc->DCTL |= USBC_SFTDISCON; usbc->GRSTCTL |= USBC_CSFTRST; // while ((usbc->GRSTCTL & USBC_CSFTRST) != 0); // while ((usbc->GRSTCTL & USBC_AHBIDLE) == 0); #ifdef CHIP_HAS_USBPHY usbphy_close(); #endif hal_cmu_usb_clock_disable(); memset(&callbacks, 0, sizeof(callbacks)); hal_sysfreq_req(HAL_SYSFREQ_USER_USB, HAL_CMU_FREQ_32K); } void hal_usb_detect_disconn(void) { // NOTE: // PHY detects the disconnection event by DP/DN voltage level change. // But DP/DN voltages are provided by vusb ldo inside chip, which has nothing // to do with VBUS. That is why USB controller cannot generate the // disconnection interrupt. Meanwhile VBUS detection or charger detection can // help to generate USB disconnection event via this function. USB_FUNC_ENTRY_TRACE(26); if (device_state != ATTACHED && callbacks.state_change) { callbacks.state_change(HAL_USB_EVENT_DISCONNECT, 0); } } int hal_usb_configured(void) { return (device_state == CONFIGURED); } int hal_usb_suspended(void) { return (device_sleep_status == USB_SLEEP_SUSPEND); } uint32_t hal_usb_get_soffn(void) { return GET_BITFIELD(usbc->DSTS, USBC_SOFFN); } #ifdef USB_HIGH_SPEED uint32_t hal_usb_calc_hshb_ep_mps(uint32_t pkt_size) { // For high speed, high bandwidth endpoints if (pkt_size <= USB_MAX_PACKET_SIZE_ISO) { return pkt_size; } else if (pkt_size > USB_MAX_PACKET_SIZE_ISO && pkt_size <= USB_MAX_PACKET_SIZE_ISO * 2) { return ALIGN(pkt_size / 2, 4); } else { // if (pkt_size > USB_MAX_PACKET_SIZE_ISO * 2 && pkt_size <= // USB_MAX_PACKET_SIZE_ISO * 3) return ALIGN(pkt_size / 3, 4); } } #endif int hal_usb_activate_epn(enum EP_DIR dir, uint8_t ep, uint8_t type, uint16_t mps) { uint32_t fifo_mps; USB_TRACE(3, 10, "%s: %d ep%d", __FUNCTION__, dir, ep); if (ep == 0 || ep >= EPNUM) { return 1; } if (dir == EP_OUT) { // Stop ep out if (usbc->DOEPnCONFIG[ep - 1].DOEPCTL & (USBC_EPENA | USBC_USBACTEP)) { hal_usb_stop_ep(dir, ep); } // Config ep out usbc->DOEPnCONFIG[ep - 1].DOEPCTL = USBC_EPN_MPS(mps) | USBC_EPTYPE(type) | USBC_USBACTEP | USBC_SNAK | USBC_SETD0PID; // Unstall ep out usbc->DOEPnCONFIG[ep - 1].DOEPCTL &= ~USBC_STALL; // Unmask ep out interrupt usbc->DAINTMSK |= USBC_OUTEPMSK(1 << ep); } else { fifo_mps = mps; #ifdef USB_HIGH_SPEED if (type == E_ISOCHRONOUS || type == E_INTERRUPT) { if (mps > USB_FIFO_MPS_ISO_SEND) { fifo_mps = USB_FIFO_MPS_ISO_SEND; } epn_in_mc[ep - 1] = 1; } #endif // Stop ep in if (usbc->DIEPnCONFIG[ep - 1].DIEPCTL & (USBC_EPENA | USBC_USBACTEP)) { hal_usb_stop_ep(dir, ep); } // Config ep in usbc->DIEPnCONFIG[ep - 1].DIEPCTL = USBC_EPN_MPS(mps) | USBC_EPTYPE(type) | USBC_USBACTEP | USBC_EPTXFNUM(ep) | USBC_SNAK | USBC_SETD0PID; // Allocate tx fifo hal_usb_alloc_epn_fifo(ep, fifo_mps); // Unstall ep in usbc->DIEPnCONFIG[ep - 1].DIEPCTL &= ~USBC_STALL; // Unmask ep in interrupt usbc->DAINTMSK |= USBC_INEPMSK(1 << ep); } return 0; } int hal_usb_deactivate_epn(enum EP_DIR dir, uint8_t ep) { USB_TRACE(3, 9, "%s: %d ep%d", __FUNCTION__, dir, ep); if (ep == 0 || ep >= EPNUM) { return 1; } hal_usb_stop_ep(dir, ep); if (dir == EP_OUT) { usbc->DOEPnCONFIG[ep - 1].DOEPCTL &= ~USBC_USBACTEP; // Mask ep out interrupt usbc->DAINTMSK &= ~USBC_OUTEPMSK(1 << ep); } else { usbc->DIEPnCONFIG[ep - 1].DIEPCTL &= ~USBC_USBACTEP; // Mask ep in interrupt usbc->DAINTMSK &= ~USBC_INEPMSK(1 << ep); } return 0; } // NOTE: Not support send epn mps change, which will involve tx fifo // reallocation int hal_usb_update_recv_epn_mps(uint8_t ep, uint16_t mps) { uint8_t type; USB_TRACE(3, 9, "%s: ep%d mps=%u", __FUNCTION__, ep, mps); if (ep == 0 || ep >= EPNUM) { return 1; } if ((usbc->DOEPnCONFIG[ep - 1].DOEPCTL & USBC_USBACTEP) == 0) { return 2; } hal_usb_stop_ep(EP_OUT, ep); usbc->DOEPnCONFIG[ep - 1].DOEPCTL &= ~USBC_USBACTEP; // Mask ep out interrupt usbc->DAINTMSK &= ~USBC_OUTEPMSK(1 << ep); // Config ep out type = GET_BITFIELD(usbc->DOEPnCONFIG[ep - 1].DOEPCTL, USBC_EPTYPE); usbc->DOEPnCONFIG[ep - 1].DOEPCTL = USBC_EPN_MPS(mps) | USBC_EPTYPE(type) | USBC_USBACTEP | USBC_SNAK | USBC_SETD0PID; // Unmask ep out interrupt usbc->DAINTMSK |= USBC_OUTEPMSK(1 << ep); return 0; } int hal_usb_update_send_epn_mc(uint8_t ep, uint8_t mc) { #ifdef USB_HIGH_SPEED uint8_t type; USB_TRACE(3, 9, "%s: ep%d mc=%u", __FUNCTION__, ep, mc); if (ep == 0 || ep >= EPNUM) { return 1; } if (mc < 1 || mc > 3) { return 3; } if ((usbc->DIEPnCONFIG[ep - 1].DIEPCTL & USBC_USBACTEP) == 0) { return 2; } type = GET_BITFIELD(usbc->DIEPnCONFIG[ep - 1].DIEPCTL, USBC_EPTYPE); if (type == E_INTERRUPT || type == E_ISOCHRONOUS) { epn_in_mc[ep - 1] = mc; return 0; } #endif return 4; } static void hal_usb_recv_ep0(void) { USB_FUNC_ENTRY_TRACE(8); // Enable EP0 to receive a new setup packet usbc->DOEPTSIZ0 = USBC_SUPCNT(3) | USBC_OEPXFERSIZE0(USB_MAX_PACKET_SIZE_CTRL) | USBC_OEPPKTCNT0; usbc->DOEPDMA0 = (uint32_t)ep0_out_buffer; usbc->DOEPINT0 = usbc->DOEPINT0; usbc->DOEPCTL0 |= USBC_CNAK | USBC_EPENA; } static void hal_usb_send_ep0(const uint8_t *data, uint16_t size) { USB_TRACE(2, 8, "%s: %d", __FUNCTION__, size); ASSERT(size <= USB_MAX_PACKET_SIZE_CTRL, "Invalid ep0 send size: %d", size); if (data && size) { memcpy(ep0_in_buffer, data, size); } // Enable EP0 to send one packet usbc->DIEPTSIZ0 = USBC_IEPXFERSIZE0(size) | USBC_IEPPKTCNT0(1); usbc->DIEPDMA0 = (uint32_t)ep0_in_buffer; usbc->DIEPINT0 = usbc->DIEPINT0; usbc->DIEPCTL0 |= USBC_CNAK | USBC_EPENA; } int hal_usb_recv_epn(uint8_t ep, uint8_t *buffer, uint32_t size) { uint16_t mps; uint32_t pkt; uint32_t xfer; uint32_t fn = 0; #ifdef USB_ISO bool isoEp; uint32_t lock; #endif USB_TRACE(3, 7, "%s: ep%d %d", __FUNCTION__, ep, size); if (device_state != CONFIGURED) { return 1; } if (ep == 0 || ep > EPNUM) { return 2; } if (((uint32_t)buffer & 0x3) != 0) { return 3; } if (epn_out_transfer[ep - 1].data != NULL) { return 4; } if ((usbc->DOEPnCONFIG[ep - 1].DOEPCTL & USBC_USBACTEP) == 0) { return 5; } mps = GET_BITFIELD(usbc->DOEPnCONFIG[ep - 1].DOEPCTL, USBC_EPN_MPS); mps = ALIGN(mps, 4); if (size < mps) { return 6; } if (size > EPN_MAX_XFERSIZE) { return 7; } pkt = size / mps; if (pkt > EPN_MAX_PKTCNT) { return 8; } xfer = pkt * mps; if (size != xfer) { return 9; } usbc->DOEPnCONFIG[ep - 1].DOEPTSIZ = USBC_OEPXFERSIZE(xfer) | USBC_OEPPKTCNT(pkt); usbc->DOEPnCONFIG[ep - 1].DOEPDMA = (uint32_t)buffer; usbc->DOEPnCONFIG[ep - 1].DOEPINT = usbc->DOEPnCONFIG[ep - 1].DOEPINT; epn_out_transfer[ep - 1].data = buffer; epn_out_transfer[ep - 1].length = xfer; epn_out_transfer[ep - 1].enabled = true; #ifdef USB_ISO if (GET_BITFIELD(usbc->DOEPnCONFIG[ep - 1].DOEPCTL, USBC_EPTYPE) == E_ISOCHRONOUS) { isoEp = true; } else { isoEp = false; } if (isoEp) { // Set the frame number in time lock = int_lock(); // Get next frame number if (GET_BITFIELD(usbc->DSTS, USBC_SOFFN) & 0x1) { fn = USBC_SETD0PID; } else { fn = USBC_SETD1PID; } } #endif usbc->DOEPnCONFIG[ep - 1].DOEPCTL |= USBC_EPENA | USBC_CNAK | fn; #ifdef USB_ISO if (isoEp) { int_unlock(lock); } #endif return 0; } int hal_usb_send_epn(uint8_t ep, const uint8_t *buffer, uint32_t size, enum ZLP_STATE zlp) { uint16_t mps; uint8_t type; uint32_t pkt; uint32_t fn = 0; #ifdef USB_ISO bool isoEp; uint32_t lock; #endif USB_TRACE(3, 6, "%s: ep%d %d", __FUNCTION__, ep, size); if (device_state != CONFIGURED) { return 1; } if (ep == 0 || ep > EPNUM) { return 2; } if (((uint32_t)buffer & 0x3) != 0) { return 3; } if (epn_in_transfer[ep - 1].data != NULL) { return 4; } if ((usbc->DIEPnCONFIG[ep - 1].DIEPCTL & USBC_USBACTEP) == 0) { return 5; } if (size > EPN_MAX_XFERSIZE) { return 7; } mps = GET_BITFIELD(usbc->DIEPnCONFIG[ep - 1].DIEPCTL, USBC_EPN_MPS); if (size <= mps) { // Also taking care of 0 size packet pkt = 1; } else { // If mps is not aligned in 4 bytes, application should add padding at the // end of each packet to make sure a new packet always starts at 4-byte // boundary pkt = (size + mps - 1) / mps; if (pkt > EPN_MAX_PKTCNT) { return 8; } } type = GET_BITFIELD(usbc->DIEPnCONFIG[ep - 1].DIEPCTL, USBC_EPTYPE); if (type == E_INTERRUPT || type == E_ISOCHRONOUS) { #ifdef USB_HIGH_SPEED if (pkt != epn_in_mc[ep - 1] && (pkt % epn_in_mc[ep - 1])) { // MC is the pkt cnt must be sent in every (micro)frame. // The total pkt cnt should be integral multiple of MC value. return 9; } #endif // Never send a zero length packet at the end of transfer epn_in_transfer[ep - 1].zero_len_pkt = false; usbc->DIEPnCONFIG[ep - 1].DIEPTSIZ = USBC_IEPXFERSIZE(size) | USBC_IEPPKTCNT(pkt) | #ifdef USB_HIGH_SPEED USBC_MC(epn_in_mc[ep - 1]); #else USBC_MC(1); #endif } else { // Check if a zero length packet is needed at the end of transfer if (zlp == ZLP_AUTO) { epn_in_transfer[ep - 1].zero_len_pkt = ((size % mps) == 0); } else { epn_in_transfer[ep - 1].zero_len_pkt = false; } usbc->DIEPnCONFIG[ep - 1].DIEPTSIZ = USBC_IEPXFERSIZE(size) | USBC_IEPPKTCNT(pkt); } usbc->DIEPnCONFIG[ep - 1].DIEPDMA = (uint32_t)buffer; usbc->DIEPnCONFIG[ep - 1].DIEPINT = usbc->DIEPnCONFIG[ep - 1].DIEPINT; epn_in_transfer[ep - 1].data = buffer; epn_in_transfer[ep - 1].length = size; epn_in_transfer[ep - 1].pkt_cnt = pkt; epn_in_transfer[ep - 1].enabled = true; #ifdef USB_ISO if (GET_BITFIELD(usbc->DIEPnCONFIG[ep - 1].DIEPCTL, USBC_EPTYPE) == E_ISOCHRONOUS) { isoEp = true; } else { isoEp = false; } if (isoEp) { // Set the frame number in time lock = int_lock(); // Get next frame number if (GET_BITFIELD(usbc->DSTS, USBC_SOFFN) & 0x1) { fn = USBC_SETD0PID; } else { fn = USBC_SETD1PID; } } #endif usbc->DIEPnCONFIG[ep - 1].DIEPCTL |= USBC_EPENA | USBC_CNAK | fn; #ifdef USB_ISO if (isoEp) { int_unlock(lock); } #endif return 0; } static void hal_usb_recv_epn_complete(uint8_t ep, uint32_t statusEp) { uint8_t *data; uint32_t size; uint32_t doeptsiz; enum XFER_COMPL_STATE state = XFER_COMPL_SUCCESS; USB_TRACE(3, 5, "%s: ep%d 0x%08x", __FUNCTION__, ep, statusEp); if (!epn_out_transfer[ep - 1].enabled) { return; } doeptsiz = usbc->DOEPnCONFIG[ep - 1].DOEPTSIZ; data = epn_out_transfer[ep - 1].data; size = GET_BITFIELD(doeptsiz, USBC_OEPXFERSIZE); ASSERT(size <= epn_out_transfer[ep - 1].length, "Invalid xfer size: size=%d, len=%d", size, epn_out_transfer[ep - 1].length); size = epn_out_transfer[ep - 1].length - size; #ifdef USB_ISO uint32_t doepctl = usbc->DOEPnCONFIG[ep - 1].DOEPCTL; if (GET_BITFIELD(doepctl, USBC_EPTYPE) == E_ISOCHRONOUS) { #if (USB_ISO_INTERVAL == 1) #if 1 if (GET_BITFIELD(doeptsiz, USBC_OEPPKTCNT) != 0) { state = XFER_COMPL_ERROR; } #else uint32_t rxdpid = GET_BITFIELD(doeptsiz, USBC_RXDPID); uint32_t pkt = epn_out_transfer[ep - 1].length / GET_BITFIELD(doepctl, USBC_EPN_MPS) - GET_BITFIELD(doeptsiz, USBC_OEPPKTCNT); if ((rxdpid == DATA_PID_DATA0 && pkt != 1) || (rxdpid == DATA_PID_DATA1 && pkt != 2) || (rxdpid == DATA_PID_DATA2 && pkt != 3)) { state = XFER_COMPL_ERROR; } #endif #else // USB_ISO_INTERVAL != 1 if (statusEp & USBC_PKTDRPSTS) { state = XFER_COMPL_ERROR; } #endif // USB_ISO_INTERVAL != 1 } #endif // USB_ISO // Clear epn_out_transfer[] before invoking the callback, // so that ep can be restarted in the callback memset(&epn_out_transfer[ep - 1], 0, sizeof(epn_out_transfer[0])); if (callbacks.epn_recv_compl[ep - 1]) { callbacks.epn_recv_compl[ep - 1](data, size, state); } } static void hal_usb_send_epn_complete(uint8_t ep, uint32_t statusEp) { const uint8_t *data; uint32_t size; uint16_t pkt, mps; uint8_t type; uint32_t mc; enum XFER_COMPL_STATE state = XFER_COMPL_SUCCESS; USB_TRACE(3, 4, "%s: ep%d 0x%08x", __FUNCTION__, ep, statusEp); if (!epn_in_transfer[ep - 1].enabled) { return; } if ((statusEp & USBC_XFERCOMPLMSK) == 0) { state = XFER_COMPL_ERROR; } pkt = GET_BITFIELD(usbc->DIEPnCONFIG[ep - 1].DIEPTSIZ, USBC_IEPPKTCNT); if (pkt != 0) { state = XFER_COMPL_ERROR; mps = GET_BITFIELD(usbc->DIEPnCONFIG[ep - 1].DIEPCTL, USBC_EPN_MPS); ASSERT(pkt <= epn_in_transfer[ep - 1].pkt_cnt, "Invalid pkt cnt: pkt=%d, pkt_cnt=%d", pkt, epn_in_transfer[ep - 1].pkt_cnt); size = (epn_in_transfer[ep - 1].pkt_cnt - pkt) * mps; } else { size = epn_in_transfer[ep - 1].length; } if (state == XFER_COMPL_SUCCESS && epn_in_transfer[ep - 1].zero_len_pkt) { epn_in_transfer[ep - 1].zero_len_pkt = false; // Send the last zero length packet, except for isochronous/interrupt // endpoints type = GET_BITFIELD(usbc->DIEPnCONFIG[ep - 1].DIEPCTL, USBC_EPTYPE); if (type == E_INTERRUPT || type == E_ISOCHRONOUS) { #ifdef USB_HIGH_SPEED mc = USBC_MC(epn_in_mc[ep - 1]); #else mc = USBC_MC(1); #endif } else { mc = 0; } usbc->DIEPnCONFIG[ep - 1].DIEPTSIZ = USBC_IEPXFERSIZE(0) | USBC_IEPPKTCNT(1) | mc; usbc->DIEPnCONFIG[ep - 1].DIEPCTL |= USBC_EPENA | USBC_CNAK; } else { data = epn_in_transfer[ep - 1].data; // Clear epn_in_transfer[] before invoking the callback, // so that ep can be restarted in the callback memset(&epn_in_transfer[ep - 1], 0, sizeof(epn_in_transfer[0])); if (state != XFER_COMPL_SUCCESS) { // The callback will not be invoked when stopping ep, // for epn_in_transfer[] has been cleared hal_usb_stop_ep(EP_IN, ep); } if (callbacks.epn_send_compl[ep - 1]) { callbacks.epn_send_compl[ep - 1](data, size, state); } } } static bool requestSetAddress(void) { USB_FUNC_ENTRY_TRACE(25); /* Set the device address */ usbc->DCFG = SET_BITFIELD(usbc->DCFG, USBC_DEVADDR, ep0_transfer.setup_pkt.wValue); ep0_transfer.stage = STATUS_IN_STAGE; if (ep0_transfer.setup_pkt.wValue == 0) { device_state = DEFAULT; } else { device_state = ADDRESS; } return true; } static bool requestSetConfiguration(void) { USB_FUNC_ENTRY_TRACE(24); device_cfg = ep0_transfer.setup_pkt.wValue; /* Set the device configuration */ if (device_cfg == 0) { /* Not configured */ device_state = ADDRESS; } else { if (callbacks.setcfg && callbacks.setcfg(device_cfg)) { /* Valid configuration */ device_state = CONFIGURED; ep0_transfer.stage = STATUS_IN_STAGE; } else { return false; } } return true; } static bool requestGetConfiguration(void) { USB_FUNC_ENTRY_TRACE(23); /* Send the device configuration */ ep0_transfer.data = &device_cfg; ep0_transfer.length = sizeof(device_cfg); ep0_transfer.stage = DATA_IN_STAGE; return true; } static bool requestGetInterface(void) { USB_FUNC_ENTRY_TRACE(22); /* Return the selected alternate setting for an interface */ if (device_state != CONFIGURED) { return false; } /* Send the alternate setting */ ep0_transfer.setup_pkt.wIndex = currentInterface; ep0_transfer.data = ¤tAlternate; ep0_transfer.length = sizeof(currentAlternate); ep0_transfer.stage = DATA_IN_STAGE; return true; } static bool requestSetInterface(void) { bool success = false; USB_FUNC_ENTRY_TRACE(21); if (callbacks.setitf && callbacks.setitf(ep0_transfer.setup_pkt.wIndex, ep0_transfer.setup_pkt.wValue)) { success = true; currentInterface = ep0_transfer.setup_pkt.wIndex; currentAlternate = ep0_transfer.setup_pkt.wValue; ep0_transfer.stage = STATUS_IN_STAGE; } return success; } static bool requestSetFeature(void) { bool success = false; USB_FUNC_ENTRY_TRACE(20); if (device_state != CONFIGURED) { /* Endpoint or interface must be zero */ if (ep0_transfer.setup_pkt.wIndex != 0) { return false; } } switch (ep0_transfer.setup_pkt.bmRequestType.recipient) { case DEVICE_RECIPIENT: if (ep0_transfer.setup_pkt.wValue == TEST_MODE) { // TODO: Check test mode device_test_mode = (ep0_transfer.setup_pkt.wIndex >> 8); success = true; } #ifdef USB_SUSPEND else if (ep0_transfer.setup_pkt.wValue == DEVICE_REMOTE_WAKEUP) { if (callbacks.set_remote_wakeup) { callbacks.set_remote_wakeup(1); device_pwr_wkup_status |= DEVICE_STATUS_REMOTE_WAKEUP; success = true; } } #endif break; case ENDPOINT_RECIPIENT: if (ep0_transfer.setup_pkt.wValue == ENDPOINT_HALT) { // TODO: Check endpoint number hal_usb_stall_ep((ep0_transfer.setup_pkt.wIndex & 0x80) ? EP_IN : EP_OUT, ep0_transfer.setup_pkt.wIndex & 0xF); if (callbacks.state_change) { callbacks.state_change(HAL_USB_EVENT_STALL, ep0_transfer.setup_pkt.wIndex & 0xFF); } success = true; } break; default: break; } ep0_transfer.stage = STATUS_IN_STAGE; return success; } static bool requestClearFeature(void) { bool success = false; USB_FUNC_ENTRY_TRACE(19); if (device_state != CONFIGURED) { /* Endpoint or interface must be zero */ if (ep0_transfer.setup_pkt.wIndex != 0) { return false; } } switch (ep0_transfer.setup_pkt.bmRequestType.recipient) { case DEVICE_RECIPIENT: #ifdef USB_SUSPEND if (ep0_transfer.setup_pkt.wValue == DEVICE_REMOTE_WAKEUP) { if (callbacks.set_remote_wakeup) { callbacks.set_remote_wakeup(0); device_pwr_wkup_status &= ~DEVICE_STATUS_REMOTE_WAKEUP; success = true; } } #endif break; case ENDPOINT_RECIPIENT: /* TODO: We should check that the endpoint number is valid */ if (ep0_transfer.setup_pkt.wValue == ENDPOINT_HALT) { hal_usb_unstall_ep((ep0_transfer.setup_pkt.wIndex & 0x80) ? EP_IN : EP_OUT, ep0_transfer.setup_pkt.wIndex & 0xF); if (callbacks.state_change) { callbacks.state_change(HAL_USB_EVENT_UNSTALL, ep0_transfer.setup_pkt.wIndex & 0xFF); } success = true; } break; default: break; } ep0_transfer.stage = STATUS_IN_STAGE; return success; } static bool requestGetStatus(void) { static uint16_t status; bool success = false; USB_FUNC_ENTRY_TRACE(18); if (device_state != CONFIGURED) { /* Endpoint or interface must be zero */ if (ep0_transfer.setup_pkt.wIndex != 0) { return false; } } switch (ep0_transfer.setup_pkt.bmRequestType.recipient) { case DEVICE_RECIPIENT: status = device_pwr_wkup_status; success = true; break; case INTERFACE_RECIPIENT: status = 0; success = true; break; case ENDPOINT_RECIPIENT: /* TODO: We should check that the endpoint number is valid */ if (hal_usb_get_ep_stall_state( (ep0_transfer.setup_pkt.wIndex & 0x80) ? EP_IN : EP_OUT, ep0_transfer.setup_pkt.wIndex & 0xF)) { status = ENDPOINT_STATUS_HALT; } else { status = 0; } success = true; break; default: break; } if (success) { /* Send the status */ ep0_transfer.data = (uint8_t *)&status; /* Assumes little endian */ ep0_transfer.length = sizeof(status); ep0_transfer.stage = DATA_IN_STAGE; } return success; } static bool requestGetDescriptor(void) { bool success = false; uint8_t type; uint8_t index; uint8_t *desc; type = DESCRIPTOR_TYPE(ep0_transfer.setup_pkt.wValue); index = DESCRIPTOR_INDEX(ep0_transfer.setup_pkt.wValue); USB_TRACE(3, 3, "%s: %d %d", __FUNCTION__, type, index); switch (type) { case DEVICE_DESCRIPTOR: desc = (uint8_t *)callbacks.device_desc(type); if (desc != NULL) { if ((desc[0] == DEVICE_DESCRIPTOR_LENGTH) && (desc[1] == DEVICE_DESCRIPTOR)) { ep0_transfer.length = desc[0]; ep0_transfer.data = desc; success = true; } } break; case CONFIGURATION_DESCRIPTOR: desc = (uint8_t *)callbacks.cfg_desc(index); if (desc != NULL) { if ((desc[0] == CONFIGURATION_DESCRIPTOR_LENGTH) && (desc[1] == CONFIGURATION_DESCRIPTOR)) { /* Get wTotalLength */ ep0_transfer.length = desc[2] | (desc[3] << 8); ep0_transfer.data = desc; // Get self-powered status if ((desc[7] >> 6) & 0x01) { device_pwr_wkup_status |= DEVICE_STATUS_SELF_POWERED; } else { device_pwr_wkup_status &= ~DEVICE_STATUS_SELF_POWERED; } success = true; } } break; case STRING_DESCRIPTOR: ep0_transfer.data = (uint8_t *)callbacks.string_desc(index); if (ep0_transfer.data) { ep0_transfer.length = ep0_transfer.data[0]; success = true; } break; case INTERFACE_DESCRIPTOR: case ENDPOINT_DESCRIPTOR: /* TODO: Support is optional, not implemented here */ break; case QUALIFIER_DESCRIPTOR: #ifdef USB_HIGH_SPEED desc = (uint8_t *)callbacks.device_desc(type); if (desc != NULL) { if (desc[0] == QUALIFIER_DESCRIPTOR_LENGTH && desc[1] == type) { ep0_transfer.length = desc[0]; ep0_transfer.data = desc; success = true; } } #endif break; case BOS_DESCRIPTOR: #ifdef USB_HIGH_SPEED desc = (uint8_t *)callbacks.device_desc(type); if (desc != NULL) { if (desc[0] == BOS_DESCRIPTOR_LENGTH && desc[1] == type) { // Total length ep0_transfer.length = desc[2]; ep0_transfer.data = desc; success = true; } } #endif break; default: // Might be a class or vendor specific descriptor, which // should be handled in setuprecv callback USB_TRACE(1, 0, "*** Error: Unknown desc type: %d", type); break; } if (success) { ep0_transfer.stage = DATA_IN_STAGE; } return success; } static void hal_usb_reset_all_transfers(void) { int ep; USB_FUNC_ENTRY_TRACE(31); for (ep = 1; ep < EPNUM; ep++) { reset_epn_out_transfer(ep); reset_epn_in_transfer(ep); } } static bool hal_usb_handle_setup(void) { bool success = false; enum DEVICE_STATE old_state; USB_FUNC_ENTRY_TRACE(17); old_state = device_state; /* Process standard requests */ if (ep0_transfer.setup_pkt.bmRequestType.type == STANDARD_TYPE) { switch (ep0_transfer.setup_pkt.bRequest) { case GET_STATUS: success = requestGetStatus(); break; case CLEAR_FEATURE: success = requestClearFeature(); break; case SET_FEATURE: success = requestSetFeature(); break; case SET_ADDRESS: success = requestSetAddress(); break; case GET_DESCRIPTOR: success = requestGetDescriptor(); break; case SET_DESCRIPTOR: /* TODO: Support is optional, not implemented here */ success = false; break; case GET_CONFIGURATION: success = requestGetConfiguration(); break; case SET_CONFIGURATION: success = requestSetConfiguration(); break; case GET_INTERFACE: success = requestGetInterface(); break; case SET_INTERFACE: success = requestSetInterface(); break; default: break; } } if (old_state == CONFIGURED && device_state != CONFIGURED) { hal_usb_reset_all_transfers(); } return success; } static int hal_usb_ep0_setup_stage(uint32_t statusEp) { uint8_t *data; uint8_t setup_cnt; uint16_t pkt_len; // Skip the check on IN/OUT tokens usbc->DOEPMSK &= ~USBC_OUTTKNEPDISMSK; usbc->DIEPMSK &= ~USBC_INTKNTXFEMPMSK; if (statusEp & USBC_BACK2BACKSETUP) { data = (uint8_t *)(usbc->DOEPDMA0 - 8); } else { setup_cnt = GET_BITFIELD(usbc->DOEPTSIZ0, USBC_SUPCNT); if (setup_cnt >= 3) { setup_cnt = 2; } data = (uint8_t *)((uint32_t)ep0_out_buffer + 8 * (2 - setup_cnt)); } // Init new transfer hal_usb_init_ep0_transfer(); ep0_transfer.stage = SETUP_STAGE; get_setup_packet(data, &ep0_transfer.setup_pkt); USB_TRACE(5, 2, "Got SETUP type=%d, req=0x%x, val=0x%x, idx=0x%x, len=%d", ep0_transfer.setup_pkt.bmRequestType.type, ep0_transfer.setup_pkt.bRequest, ep0_transfer.setup_pkt.wValue, ep0_transfer.setup_pkt.wIndex, ep0_transfer.setup_pkt.wLength); if (ep0_transfer.setup_pkt.wLength == 0 && ep0_transfer.setup_pkt.bmRequestType.direction != EP_OUT) { USB_TRACE(0, 0, "*** Error: Ep0 dir should be out if wLength=0"); return 1; } if (callbacks.setuprecv == NULL || !callbacks.setuprecv(&ep0_transfer)) { if (!hal_usb_handle_setup()) { return 1; } } #if 0 if (ep0_transfer.setup_pkt.wLength == 0) { ep0_transfer.setup_pkt.bmRequestType.direction = EP_OUT; } #endif if (ep0_transfer.stage == DATA_OUT_STAGE) { if (ep0_transfer.data == NULL) { ep0_transfer.data = (uint8_t *)ep0_out_buffer; } } else if (ep0_transfer.stage == DATA_IN_STAGE) { if (ep0_transfer.length > ep0_transfer.setup_pkt.wLength) { ep0_transfer.length = ep0_transfer.setup_pkt.wLength; } pkt_len = ep0_transfer.length; if (pkt_len > USB_MAX_PACKET_SIZE_CTRL) { pkt_len = USB_MAX_PACKET_SIZE_CTRL; } hal_usb_send_ep0(ep0_transfer.data, pkt_len); } else if (ep0_transfer.stage == STATUS_IN_STAGE) { hal_usb_send_ep0(NULL, 0); } else { USB_TRACE(1, 0, "*** Setup stage switches to invalid stage: %d", ep0_transfer.stage); return 1; } return 0; } static int hal_usb_ep0_data_out_stage(void) { uint16_t pkt_len; uint16_t reg_size; reg_size = GET_BITFIELD(usbc->DOEPTSIZ0, USBC_OEPXFERSIZE0); ASSERT(reg_size <= USB_MAX_PACKET_SIZE_CTRL, "Invalid ep0 recv size"); pkt_len = MIN(USB_MAX_PACKET_SIZE_CTRL - reg_size, (uint16_t)(ep0_transfer.length - ep0_transfer.trx_len)); ASSERT(ep0_transfer.length >= ep0_transfer.trx_len + pkt_len, "Invalid ep0 recv len: length=%u trx_len=%u pkt_len=%u", ep0_transfer.length, ep0_transfer.trx_len, pkt_len); memcpy(ep0_transfer.data + ep0_transfer.trx_len, ep0_out_buffer, pkt_len); ep0_transfer.trx_len += pkt_len; // Always enable setup packet receiving // CAUTION: Start ep0 recv before invoking datarecv callbacks to handle all // kinds of endpoints. // Some USBC events are triggered by patterns in receive FIFO, only // when these patterns are popped by the core (DMA reading) or the // application (usbc->GRXSTSP reading). E.g., Global OUT NAK // (DCTL.SGOUTNak) triggers GINTSTS.GOUTNakEff, but the interrupt // will never raise in data out stage without DMA or usbc->GRXSTSP // reading. hal_usb_recv_ep0(); pkt_len = ep0_transfer.length - ep0_transfer.trx_len; if (pkt_len == 0) { if (callbacks.datarecv == NULL || !callbacks.datarecv(&ep0_transfer)) { // hal_usb_stall_ep(EP_OUT, 0); // hal_usb_stall_ep(EP_IN, 0); // return; } ep0_transfer.stage = STATUS_IN_STAGE; // Check error on IN/OUT tokens usbc->DOEPMSK |= USBC_OUTTKNEPDISMSK; // Send status packet hal_usb_send_ep0(NULL, 0); } else { // Receive next data packet } return 0; } static int hal_usb_ep0_data_in_stage(void) { uint16_t pkt_len; bool zero_len_pkt = false; ASSERT(GET_BITFIELD(usbc->DIEPTSIZ0, USBC_IEPPKTCNT0) == 0, "Invalid ep0 sent pkt cnt"); ASSERT(ep0_transfer.length >= ep0_transfer.trx_len, "Invalid ep0 sent len 1: length=%u trx_len=%u", ep0_transfer.length, ep0_transfer.trx_len); pkt_len = ep0_transfer.length - ep0_transfer.trx_len; if (pkt_len == 0) { // The last zero length packet was sent successfully ep0_transfer.stage = STATUS_OUT_STAGE; // Receive status packet (receiving is always enabled) } else { // Update sent count if (pkt_len == USB_MAX_PACKET_SIZE_CTRL) { zero_len_pkt = true; } else if (pkt_len > USB_MAX_PACKET_SIZE_CTRL) { pkt_len = USB_MAX_PACKET_SIZE_CTRL; } ep0_transfer.trx_len += pkt_len; ASSERT(ep0_transfer.length >= ep0_transfer.trx_len, "Invalid ep0 sent len 2: length=%u trx_len=%u", ep0_transfer.length, ep0_transfer.trx_len); // Send next packet pkt_len = ep0_transfer.length - ep0_transfer.trx_len; if (pkt_len > USB_MAX_PACKET_SIZE_CTRL) { pkt_len = USB_MAX_PACKET_SIZE_CTRL; } if (pkt_len == 0) { if (zero_len_pkt) { // Send the last zero length packet hal_usb_send_ep0(NULL, 0); } else { ep0_transfer.stage = STATUS_OUT_STAGE; // Check error on IN/OUT tokens usbc->DIEPMSK |= USBC_INTKNTXFEMPMSK; // Receive status packet (receiving is always enabled) } } else { hal_usb_send_ep0(ep0_transfer.data + ep0_transfer.trx_len, pkt_len); } } return 0; } static int hal_usb_ep0_status_stage(void) { // Done with status packet ep0_transfer.stage = NONE_STAGE; // Skip the check on IN/OUT tokens usbc->DOEPMSK &= ~USBC_OUTTKNEPDISMSK; usbc->DIEPMSK &= ~USBC_INTKNTXFEMPMSK; return 0; } static void hal_usb_ep0_test_mode_check(void) { if (device_test_mode) { usbc->DCTL = SET_BITFIELD(usbc->DCTL, USBC_TSTCTL, device_test_mode); device_test_mode = 0; } } static void hal_usb_handle_ep0_packet(enum EP_DIR dir, uint32_t statusEp) { int ret; USB_TRACE(3, 16, "%s: dir=%d, statusEp=0x%08x", __FUNCTION__, dir, statusEp); if (dir == EP_OUT) { if ((statusEp & (USBC_XFERCOMPL | USBC_STSPHSERCVD)) == (USBC_XFERCOMPL | USBC_STSPHSERCVD)) { // From Linux driver if (GET_BITFIELD(usbc->DOEPTSIZ0, USBC_OEPXFERSIZE0) == USB_MAX_PACKET_SIZE_CTRL && (usbc->DOEPTSIZ0 & USBC_OEPPKTCNT0)) { // Abnormal case USB_TRACE( 2, 0, "*** EP0 OUT empty compl with stsphsercvd: stage=%d, size=0x%08x", ep0_transfer.stage, usbc->DOEPTSIZ0); // Always enable setup packet receiving hal_usb_recv_ep0(); return; } } if (statusEp & USBC_XFERCOMPL) { // New packet received if (statusEp & USBC_SETUP) { // Clean previous transfer if (ep0_transfer.stage != NONE_STAGE) { USB_TRACE(1, 0, "*** Setup stage breaks previous stage %d", ep0_transfer.stage); hal_usb_stop_ep(EP_IN, 0); } // New setup packet ep0_transfer.stage = SETUP_STAGE; } else if (statusEp & USBC_STUPPKTRCVD) { // Clean previous transfer if (ep0_transfer.stage != NONE_STAGE) { USB_TRACE(1, 0, "*** Wait setup stage breaks previous stage %d", ep0_transfer.stage); hal_usb_stop_ep(EP_IN, 0); } // New setup packet received, and wait for USBC h/w state machine // finished CAUTION: Enabling data receipt before getting USBC_SETUP // interrupt might cause // race condition in h/w, which leads to missing USBC_XFERCOMPL // interrupt after the data has been received and acked ep0_transfer.stage = WAIT_SETUP_STAGE; return; } } else { // No packet received if (statusEp & USBC_SETUP) { if (ep0_transfer.stage == WAIT_SETUP_STAGE) { // Previous packet is setup packet ep0_transfer.stage = SETUP_STAGE; } else { USB_TRACE(1, 0, "*** Setup interrupt occurs in stage %d", ep0_transfer.stage); // The setup packet has been processed return; } } } } if (ep0_transfer.stage == SETUP_STAGE) { ASSERT(dir == EP_OUT, "Invalid dir for setup stage"); ret = hal_usb_ep0_setup_stage(statusEp); if (ret) { // Error ep0_transfer.stage = NONE_STAGE; hal_usb_stall_ep(EP_OUT, 0); hal_usb_stall_ep(EP_IN, 0); // H/w will unstall EP0 automatically when a setup token is received // hal_usb_recv_ep0(); } // Always enable setup packet receiving hal_usb_recv_ep0(); return; } if (statusEp & USBC_XFERCOMPL) { if (dir == EP_OUT) { if (ep0_transfer.stage == DATA_OUT_STAGE) { hal_usb_ep0_data_out_stage(); } else if (ep0_transfer.stage == STATUS_OUT_STAGE) { hal_usb_ep0_status_stage(); // Always enable setup packet receiving hal_usb_recv_ep0(); } else { // Abnormal case USB_TRACE(2, 0, "*** EP0 OUT compl in stage %d with size 0x%08x", ep0_transfer.stage, usbc->DOEPTSIZ0); // Always enable setup packet receiving hal_usb_recv_ep0(); } } else { if (ep0_transfer.stage == DATA_IN_STAGE) { hal_usb_ep0_data_in_stage(); } else if (ep0_transfer.stage == STATUS_IN_STAGE) { hal_usb_ep0_status_stage(); hal_usb_ep0_test_mode_check(); } else { // Abnormal case USB_TRACE(2, 0, "*** EP0 IN compl in stage %d with size 0x%08x", ep0_transfer.stage, usbc->DIEPTSIZ0); } } } } static void hal_usb_irq_reset(void) { USB_FUNC_ENTRY_TRACE(2); device_state = DEFAULT; device_pwr_wkup_status = 0; device_sleep_status = USB_SLEEP_NONE; usbc->PCGCCTL &= ~USBC_STOPPCLK; usbc->DCTL &= ~USBC_RMTWKUPSIG; hal_usb_reset_all_transfers(); hal_usb_stop_all_out_eps(); hal_usb_stop_all_in_eps(); // Unmask ep0 interrupts usbc->DAINTMSK = USBC_INEPMSK(1 << 0) | USBC_OUTEPMSK(1 << 0); usbc->DOEPMSK = USBC_XFERCOMPLMSK | USBC_SETUPMSK; usbc->DIEPMSK = USBC_XFERCOMPLMSK | USBC_TIMEOUTMSK; usbc->GINTMSK |= USBC_OEPINT | USBC_IEPINT #if defined(USB_ISO) && (USB_ISO_INTERVAL == 1) | USBC_INCOMPISOOUT | USBC_INCOMPISOIN #endif ; #ifdef USB_SUSPEND usbc->GINTMSK &= ~USBC_WKUPINT; #endif #ifdef USB_LPM usbc->GINTMSK |= USBC_LPM_INT; usbc->PCGCCTL |= USBC_ENBL_L1GATING; usbc->GLPMCFG = SET_BITFIELD(usbc->GLPMCFG, USBC_HIRD_THRES, USB_L1_LIGHT_SLEEP_BESL) | USBC_HIRD_THRES_BIT4 | USBC_LPMCAP | USBC_APPL1RES | USBC_ENBESL; // | USBC_ENBLSLPM; #ifdef CHIP_HAS_USBIF usbif->USBIF_04 = SET_BITFIELD(usbif->USBIF_04, USBIF_04_CFG_SLEEP_THSD, USB_L1_LIGHT_SLEEP_BESL); #endif #endif // Config ep0 size hal_usb_alloc_ep0_fifo(); // Reset device address usbc->DCFG &= ~USBC_DEVADDR_MASK; hal_usb_init_ep0_transfer(); hal_usb_init_epn_transfer(); if (callbacks.state_change) { callbacks.state_change(HAL_USB_EVENT_RESET, 0); } } static void hal_usb_irq_enum_done(void) { uint8_t speed; uint8_t mps = 0; USB_FUNC_ENTRY_TRACE(2); speed = GET_BITFIELD(usbc->DSTS, USBC_ENUMSPD); if (speed == 0) { // High speed -- ERROR! mps = 0; // 64 bytes } else if (speed == 1 || speed == 3) { // Full speed mps = 0; // 64 bytes } else { // Low speed -- ERROR! mps = 3; // 8 bytes } // Only support 64-byte MPS ! mps = 0; // Config max packet size usbc->DIEPCTL0 = USBC_EP0_MPS(mps) | USBC_USBACTEP | USBC_EPTXFNUM(0) | USBC_SNAK; usbc->DOEPCTL0 = USBC_EP0_MPS(mps) | USBC_USBACTEP | USBC_SNAK; hal_usb_recv_ep0(); } #if defined(USB_ISO) && (USB_ISO_INTERVAL == 1) static void hal_usb_irq_incomp_iso_out(void) { int i; uint32_t ctrl; uint32_t sof_fn; uint32_t statusEp; sof_fn = GET_BITFIELD(usbc->DSTS, USBC_SOFFN); for (i = 0; i < EPNUM - 1; i++) { ctrl = usbc->DOEPnCONFIG[i].DOEPCTL; if ((ctrl & USBC_EPENA) && ((ctrl >> USBC_EPDPID_SHIFT) & 1) == (sof_fn & 1) && GET_BITFIELD(ctrl, USBC_EPTYPE) == E_ISOCHRONOUS) { statusEp = usbc->DOEPnCONFIG[i].DOEPINT; hal_usb_disable_ep(EP_OUT, i + 1); break; } } if (i < EPNUM - 1) { USB_TRACE(4, 17, "%s ep%d: INT=0x%08x, SOF=0x%04x", __FUNCTION__, i + 1, statusEp, sof_fn); } else { USB_TRACE(1, 17, "%s: No valid ISO ep", __FUNCTION__); } } static void hal_usb_irq_incomp_iso_in(void) { int i; uint32_t ctrl; uint32_t sof_fn; uint32_t statusEp; sof_fn = GET_BITFIELD(usbc->DSTS, USBC_SOFFN); for (i = 0; i < EPNUM - 1; i++) { ctrl = usbc->DIEPnCONFIG[i].DIEPCTL; if ((ctrl & USBC_EPENA) && ((ctrl >> USBC_EPDPID_SHIFT) & 1) == (sof_fn & 1) && GET_BITFIELD(ctrl, USBC_EPTYPE) == E_ISOCHRONOUS) { statusEp = usbc->DIEPnCONFIG[i].DIEPINT; hal_usb_disable_ep(EP_IN, i + 1); break; } } if (i < EPNUM - 1) { USB_TRACE(4, 17, "%s ep%d: INT=0x%08x, SOF=0x%04x", __FUNCTION__, i + 1, statusEp, sof_fn); } else { USB_TRACE(1, 17, "%s: No valid ISO ep", __FUNCTION__); } } #endif #ifdef USB_SUSPEND static void hal_usb_restore_clock(void) { hal_sysfreq_req(HAL_SYSFREQ_USER_USB, USB_SYS_FREQ); #ifdef PMU_USB_PIN_CHECK #ifdef CHIP_HAS_USBPHY // Enable dig phy clock usbphy_wakeup(); #endif hal_cmu_clock_enable(HAL_CMU_MOD_H_USBC); hal_cmu_clock_enable(HAL_CMU_MOD_O_USB); #endif usbc->PCGCCTL &= ~USBC_STOPPCLK; #ifdef PMU_USB_PIN_CHECK NVIC_ClearPendingIRQ(USB_IRQn); NVIC_EnableIRQ(USB_IRQn); #endif } static void hal_usb_stop_clock(void) { #ifdef PMU_USB_PIN_CHECK // Disable USB IRQ to avoid errors when RESET/RESUME is detected before // stopping USB clock NVIC_DisableIRQ(USB_IRQn); #endif usbc->PCGCCTL |= USBC_STOPPCLK; #ifdef PMU_USB_PIN_CHECK hal_cmu_clock_disable(HAL_CMU_MOD_O_USB); hal_cmu_clock_disable(HAL_CMU_MOD_H_USBC); #ifdef CHIP_HAS_USBPHY // Disable dig phy clock usbphy_sleep(); #endif #endif hal_sysfreq_req(HAL_SYSFREQ_USER_USB, HAL_CMU_FREQ_32K); } #ifdef PMU_USB_PIN_CHECK static void hal_usb_pin_status_resume(enum PMU_USB_PIN_CHK_STATUS_T status); static void hal_usb_pin_check_enable_check(void *param) { pmu_usb_enable_pin_status_check(); } static void hal_usb_pin_check_cancel_resume(void *param) { USB_TRACE(3, 18, "[%X] %s: DSTS=0x%08x", hal_sys_timer_get(), __FUNCTION__, usbc->DSTS); if (usbc->DSTS & USBC_SUSPSTS) { hal_usb_stop_clock(); pmu_usb_enable_pin_status_check(); } } static void hal_usb_pin_status_resume(enum PMU_USB_PIN_CHK_STATUS_T status) { USB_TRACE(2, 18, "%s: %d", __FUNCTION__, status); // Start timer to check resume status, so as to avoid fake pin resume signal if (usbdev_timer_active) { hwtimer_stop(usbdev_timer); } hwtimer_update_then_start(usbdev_timer, hal_usb_pin_check_cancel_resume, NULL, PIN_CHECK_WAIT_RESUME_INTERVAL); usbdev_timer_active = 1; hal_usb_restore_clock(); } #endif #if defined(PMU_USB_PIN_CHECK) || defined(USB_LPM) static void hal_usb_stop_usbdev_timer(void) { if (usbdev_timer_active) { hwtimer_stop(usbdev_timer); usbdev_timer_active = 0; } } #endif static void hal_usb_sleep(enum USB_SLEEP_T cause) { USB_FUNC_ENTRY_TRACE(18); device_sleep_status = cause; if (callbacks.state_change) { callbacks.state_change((cause == USB_SLEEP_SUSPEND) ? HAL_USB_EVENT_SUSPEND : HAL_USB_EVENT_L1_DEEP_SLEEP, 0); } usbc->GINTMSK |= USBC_WKUPINT; #ifdef USB_LPM usbc->GINTMSK &= ~USBC_LPM_INT; #endif hal_usb_stop_clock(); #ifdef PMU_USB_PIN_CHECK pmu_usb_config_pin_status_check(PMU_USB_PIN_CHK_HOST_RESUME, hal_usb_pin_status_resume, false); // Stat timer to check current pin status if (usbdev_timer_active) { hwtimer_stop(usbdev_timer); } hwtimer_update_then_start(usbdev_timer, hal_usb_pin_check_enable_check, NULL, PIN_CHECK_ENABLE_INTERVAL); usbdev_timer_active = 1; #endif } static void hal_usb_wakeup(void) { USB_FUNC_ENTRY_TRACE(18); device_sleep_status = USB_SLEEP_NONE; #ifndef PMU_USB_PIN_CHECK hal_usb_restore_clock(); #endif usbc->GINTMSK &= ~USBC_WKUPINT; #ifdef USB_LPM usbc->GINTMSK |= USBC_LPM_INT; #endif if (callbacks.state_change) { callbacks.state_change(HAL_USB_EVENT_RESUME, 0); } } static void hal_usb_send_resume_signal(int enable) { if (enable) { #ifdef PMU_USB_PIN_CHECK // Stop pin check timer hal_usb_stop_usbdev_timer(); // Disabe PMU pin status check pmu_usb_disable_pin_status_check(); #endif // Restore USB clock and IRQ hal_usb_restore_clock(); usbc->GINTMSK &= ~USBC_WKUPINT; usbc->DCTL |= USBC_RMTWKUPSIG; } else { usbc->DCTL &= ~USBC_RMTWKUPSIG; } } #endif int hal_usb_remote_wakeup(int signal) { #ifdef USB_SUSPEND USB_TRACE(2, 15, "%s: %d", __FUNCTION__, signal); if (signal) { if (device_sleep_status != USB_SLEEP_SUSPEND) { return 1; } if ((device_pwr_wkup_status & DEVICE_STATUS_REMOTE_WAKEUP) == 0) { return 2; } hal_usb_send_resume_signal(1); } else { hal_usb_send_resume_signal(0); // USBC will NOT generate resume IRQ in case of remote wakeup, so fake one // here hal_usb_wakeup(); } #endif return 0; } void hal_usb_lpm_sleep_enable(void) { #ifdef USB_LPM if (usbc->GLPMCFG & USBC_ENBESL) { // usbc->GLPMCFG &= ~USBC_RSTRSLPSTS; } usbc->GLPMCFG |= (USBC_APPL1RES | USBC_HIRD_THRES_BIT4); usbc->PCGCCTL |= USBC_ENBL_L1GATING; #endif } void hal_usb_lpm_sleep_disable(void) { #ifdef USB_LPM uint32_t cnt; uint32_t lock; uint32_t lpm; bool POSSIBLY_UNUSED rmtWake = false; usbc->PCGCCTL &= ~USBC_ENBL_L1GATING; usbc->GLPMCFG &= ~(USBC_APPL1RES | USBC_HIRD_THRES_BIT4 | USBC_ENBLSLPM); if (usbc->GLPMCFG & USBC_ENBESL) { usbc->GLPMCFG |= USBC_RSTRSLPSTS; } lpm_entry = false; // Resume if in L1 state if (usbc->GLPMCFG & USBC_SLPSTS) { cnt = 0; while ( (usbc->GLPMCFG & (USBC_BREMOTEWAKE | USBC_SLPSTS | USBC_L1RESUMEOK)) == (USBC_BREMOTEWAKE | USBC_SLPSTS)) { hal_sys_timer_delay(US_TO_TICKS(0)); if (++cnt > 3) { break; } } lock = int_lock(); lpm = usbc->GLPMCFG; if (lpm & USBC_SLPSTS) { if ((lpm & (USBC_BREMOTEWAKE | USBC_L1RESUMEOK)) == (USBC_BREMOTEWAKE | USBC_L1RESUMEOK)) { hal_usb_send_resume_signal(1); rmtWake = true; #if 0 // Detect race condition between remote wake and L1 state change if ((usbc->GLPMCFG & USBC_SLPSTS) == 0) { usbc->DCTL &= ~USBC_RMTWKUPSIG; } #endif #ifdef USB_SUSPEND if (device_sleep_status == USB_SLEEP_L1) { // USBC will NOT generate resume IRQ in case of remote wakeup, so fake // one here hal_usb_wakeup(); } #endif } else if ((lpm & (USBC_BREMOTEWAKE | USBC_L1RESUMEOK)) == USBC_BREMOTEWAKE) { TRACE(1, "\n*** ERROR: LPM Disable: Failed to wait L1 resume OK: 0x%08X\n", lpm); } } int_unlock(lock); if (rmtWake) { USB_TRACE(1, 0, "LPM RmtWake: 0x%08X", lpm); } } #endif } #ifdef USB_LPM #ifdef USB_SUSPEND static void hal_usb_lpm_check(void *param) { uint32_t lpm; uint32_t lock; // TODO: Disable USB irq only (might conflict with hal_usb_stop_clock) lock = int_lock(); lpm = usbc->GLPMCFG; if (lpm_entry && GET_BITFIELD(lpm, USBC_HIRD) >= USB_L1_DEEP_SLEEP_BESL) { if ((lpm & USBC_SLPSTS) && (lpm & (USBC_BREMOTEWAKE | USBC_L1RESUMEOK)) != USBC_BREMOTEWAKE) { lpm_entry = false; hal_usb_sleep(USB_SLEEP_L1); } else { hwtimer_update_then_start(usbdev_timer, hal_usb_lpm_check, NULL, LPM_CHECK_INTERVAL); usbdev_timer_active = 1; } } int_unlock(lock); } #endif static void hal_usb_irq_lpm(void) { static const uint32_t lpm_trace_interval = MS_TO_TICKS(2000); static uint32_t last_lpm_irq_time; static uint32_t cnt = 0; uint32_t time; uint32_t lpm; cnt++; lpm = usbc->GLPMCFG; #ifdef USB_SUSPEND if (GET_BITFIELD(lpm, USBC_HIRD) >= USB_L1_DEEP_SLEEP_BESL) { // Stat timer to check L1 sleep state if (usbdev_timer_active) { hwtimer_stop(usbdev_timer); } hwtimer_update_then_start(usbdev_timer, hal_usb_lpm_check, NULL, LPM_CHECK_INTERVAL); usbdev_timer_active = 1; lpm_entry = true; } #endif time = hal_sys_timer_get(); if (time - last_lpm_irq_time >= lpm_trace_interval || GET_BITFIELD(lpm, USBC_HIRD) >= USB_L1_DEEP_SLEEP_BESL) { last_lpm_irq_time = time; USB_TRACE(6, 0, "LPM IRQ: 0x%08X rmtWake=%d hird=0x%x l1Res=%d slpSts=%d cnt=%u", lpm, !!(lpm & USBC_BREMOTEWAKE), GET_BITFIELD(lpm, USBC_HIRD), GET_BITFIELD(lpm, USBC_COREL1RES), !!(lpm & USBC_SLPSTS), cnt); } } #endif static void hal_usb_irq_handler(void) { uint32_t status, rawStatus; uint32_t statusEp, rawStatusEp; uint32_t data; uint8_t i; // Store interrupt flag and reset it rawStatus = usbc->GINTSTS; usbc->GINTSTS = rawStatus; status = rawStatus & (usbc->GINTMSK & (USBC_USBRST | USBC_ENUMDONE #if defined(USB_ISO) && (USB_ISO_INTERVAL == 1) | USBC_INCOMPISOOUT | USBC_INCOMPISOIN #endif | USBC_IEPINT | USBC_OEPINT | USBC_ERLYSUSP | USBC_USBSUSP | USBC_WKUPINT | USBC_LPM_INT)); USB_TRACE(3, 1, "%s: 0x%08x / 0x%08x", __FUNCTION__, status, rawStatus); #if defined(USB_SUSPEND) && (defined(PMU_USB_PIN_CHECK) || defined(USB_LPM)) bool stop_timer = true; #ifdef USB_LPM if (lpm_entry && status == USBC_ERLYSUSP && (usbc->DSTS & USBC_ERRTICERR) == 0) { // LPM TL1TokenRetry (8 us) timer has expired. L1 state transition will be // finished after TL1Residency (50 us) timer from now. stop_timer = false; } else { lpm_entry = false; } #endif if (stop_timer) { hal_usb_stop_usbdev_timer(); } #endif if (status & USBC_USBRST) { // Usb reset hal_usb_irq_reset(); // We got a reset, and reseted the soft state machine: Discard all other // interrupt causes. status &= USBC_ENUMDONE; } if (status & USBC_ENUMDONE) { // Enumeration done hal_usb_irq_enum_done(); } #if defined(USB_ISO) && (USB_ISO_INTERVAL == 1) if (status & USBC_INCOMPISOOUT) { // Incomplete ISO OUT hal_usb_irq_incomp_iso_out(); } if (status & USBC_INCOMPISOIN) { // Incomplete ISO IN hal_usb_irq_incomp_iso_in(); } #endif if (status & USBC_IEPINT) { // Tx data = usbc->DAINT & usbc->DAINTMSK; for (i = 0; i < EPNUM; ++i) { if (data & USBC_INEPMSK(1 << i)) { if (i == 0) { // EP0 rawStatusEp = usbc->DIEPINT0; usbc->DIEPINT0 = rawStatusEp; statusEp = rawStatusEp & usbc->DIEPMSK; if ((statusEp & USBC_TIMEOUT) || (statusEp & USBC_INTKNTXFEMP)) { usbc->DIEPMSK &= ~USBC_INTKNTXFEMPMSK; hal_usb_stall_ep(EP_IN, i); } else if (statusEp & USBC_XFERCOMPL) { // Handle ep0 command hal_usb_handle_ep0_packet(EP_IN, rawStatusEp); } } else { rawStatusEp = usbc->DIEPnCONFIG[i - 1].DIEPINT; usbc->DIEPnCONFIG[i - 1].DIEPINT = rawStatusEp; statusEp = rawStatusEp & usbc->DIEPMSK; if ((statusEp & USBC_TIMEOUT) || (statusEp & USBC_XFERCOMPL)) { hal_usb_send_epn_complete(i, rawStatusEp); } } } } } if (status & USBC_OEPINT) { // Rx data = usbc->DAINT & usbc->DAINTMSK; for (i = 0; i < EPNUM; ++i) { if (data & USBC_OUTEPMSK(1 << i)) { if (i == 0) { rawStatusEp = usbc->DOEPINT0; usbc->DOEPINT0 = rawStatusEp; statusEp = rawStatusEp & usbc->DOEPMSK; if (statusEp & USBC_OUTTKNEPDIS) { usbc->DOEPMSK &= ~USBC_OUTTKNEPDISMSK; hal_usb_stall_ep(EP_OUT, i); if (statusEp & USBC_XFERCOMPL) { // Always enable setup packet receiving hal_usb_recv_ep0(); } } else if (statusEp & (USBC_XFERCOMPL | USBC_SETUP)) { // Handle ep0 command hal_usb_handle_ep0_packet(EP_OUT, rawStatusEp); } } else { rawStatusEp = usbc->DOEPnCONFIG[i - 1].DOEPINT; usbc->DOEPnCONFIG[i - 1].DOEPINT = rawStatusEp; statusEp = rawStatusEp & usbc->DOEPMSK; if (statusEp & USBC_XFERCOMPL) { hal_usb_recv_epn_complete(i, rawStatusEp); } } } } } #ifdef USB_SUSPEND if (status & USBC_ERLYSUSP) { if (usbc->DSTS & USBC_ERRTICERR) { hal_usb_soft_disconnect(); return; } } if (status & USBC_USBSUSP) { hal_usb_sleep(USB_SLEEP_SUSPEND); } if (status & USBC_WKUPINT) { hal_usb_wakeup(); } #endif #ifdef USB_LPM if (status & USBC_LPM_INT) { hal_usb_irq_lpm(); } #endif } #endif // CHIP_HAS_USB