pinebuds/platform/hal/hal_usb.c

2726 lines
71 KiB
C

/***************************************************************************
*
* 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 = &currentAlternate;
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