mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
2906a25988
This commit separates out the common USB types used throughout most of the stack into its own header file inside the USB component. The types used in the USB HAL are now exclusive to the HAL.
430 lines
18 KiB
C
430 lines
18 KiB
C
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include "hal/usbh_hal.h"
|
|
#include "hal/usbh_ll.h"
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
------------------------------- Macros and Types -------------------------------
|
|
----------------------------------------------------------------------------- */
|
|
|
|
// -------------------------------- Constants ----------------------------------
|
|
|
|
#define BENDPOINTADDRESS_NUM_MSK 0x0F //Endpoint number mask of the bEndpointAddress field of an endpoint descriptor
|
|
#define BENDPOINTADDRESS_DIR_MSK 0x80 //Endpoint direction mask of the bEndpointAddress field of an endpoint descriptor
|
|
|
|
#define CORE_REG_GSNPSID 0x4F54400A
|
|
#define CORE_REG_GHWCFG1 0x00000000
|
|
#define CORE_REG_GHWCFG2 0x224DD930
|
|
#define CORE_REG_GHWCFG3 0x00C804B5
|
|
#define CORE_REG_GHWCFG4 0xD3F0A030
|
|
|
|
// ------------------------------ Configurable ---------------------------------
|
|
|
|
#define CHAN_MAX_SLOTS 16
|
|
|
|
/*
|
|
FIFO lengths configured as follows:
|
|
|
|
RXFIFO (Receive FIFO)
|
|
- Recommended: (((LPS/4) + 2) * NUM_PACKETS) + (NUM_CHAN * 2) + (NUM_BULK_CTRL * 1)
|
|
- Actual: Assume (LPS = 64), (NUM_CHAN = 8), (NUM_BULK_CTRL = 8):
|
|
NPTXFIFO (Non-periodic TX FIFO)
|
|
- Recommended: (((LPS/4) + 2) * 2) Fit two largest packet sizes (and each packets overhead info)
|
|
- Actual: Assume LPS is 64 (is the MPS for CTRL/BULK/INTR in FS)
|
|
PTXFIFO (Periodic TX FIFO)
|
|
- Recommended: ((LPS/4) + 2) * NUM_PACKETS
|
|
- Actual: Assume a single LPS of 64 (quarter of ISO MPS), then 2 packets worth of overhead
|
|
REGFIFO (Register storage)
|
|
- Recommended: 4 * NUM_CHAN
|
|
- Actual: Assume NUM_CHAN is 8
|
|
*/
|
|
#define HW_FIFO_LEN 256
|
|
#define RX_FIFO_LEN 92
|
|
#define NPTX_FIFO_LEN 36
|
|
#define PTX_FIFO_LEN 72
|
|
#define REG_FIFO_LEN 32
|
|
_Static_assert((RX_FIFO_LEN + NPTX_FIFO_LEN + PTX_FIFO_LEN + REG_FIFO_LEN) <= HW_FIFO_LEN, "Sum of FIFO lengths not equal to HW_FIFO_LEN");
|
|
|
|
/**
|
|
* The following core interrupts will be enabled (listed LSB to MSB). Some of these
|
|
* interrupts are enabled later than others.
|
|
* - USB_LL_INTR_CORE_PRTINT
|
|
* - USB_LL_INTR_CORE_HCHINT
|
|
* - USB_LL_INTR_CORE_DISCONNINT
|
|
* The following PORT interrupts cannot be masked, listed LSB to MSB
|
|
* - USBH_LL_INTR_HPRT_PRTCONNDET
|
|
* - USBH_LL_INTR_HPRT_PRTENCHNG
|
|
* - USBH_LL_INTR_HPRT_PRTOVRCURRCHNG
|
|
*/
|
|
#define CORE_INTRS_EN_MSK (USB_LL_INTR_CORE_DISCONNINT)
|
|
|
|
//Interrupts that pertain to core events
|
|
#define CORE_EVENTS_INTRS_MSK (USB_LL_INTR_CORE_DISCONNINT | \
|
|
USB_LL_INTR_CORE_HCHINT)
|
|
|
|
//Interrupt that pertain to host port events
|
|
#define PORT_EVENTS_INTRS_MSK (USBH_LL_INTR_HPRT_PRTCONNDET | \
|
|
USBH_LL_INTR_HPRT_PRTENCHNG | \
|
|
USBH_LL_INTR_HPRT_PRTOVRCURRCHNG)
|
|
|
|
/**
|
|
* The following channel interrupt bits are currently checked (in order LSB to MSB)
|
|
* - USBH_LL_INTR_CHAN_XFERCOMPL
|
|
* - USBH_LL_INTR_CHAN_CHHLTD
|
|
* - USBH_LL_INTR_CHAN_STALL
|
|
* - USBH_LL_INTR_CHAN_BBLEER
|
|
* - USBH_LL_INTR_CHAN_BNAINTR
|
|
* - USBH_LL_INTR_CHAN_XCS_XACT_ERR
|
|
*
|
|
* Note the following points about channel interrupts:
|
|
* - Not all bits are unmaskable under scatter/gather
|
|
* - Those bits proxy their interrupt through the USBH_LL_INTR_CHAN_CHHLTD bit
|
|
* - USBH_LL_INTR_CHAN_XCS_XACT_ERR is always unmasked
|
|
* - When USBH_LL_INTR_CHAN_BNAINTR occurs, USBH_LL_INTR_CHAN_CHHLTD will NOT.
|
|
* - USBH_LL_INTR_CHAN_AHBERR doesn't actually ever happen on our system )i.e., ESP32S2 and later):
|
|
* - If the QTD list's starting address is an invalid address (e.g., NULL), the core will attempt to fetch that
|
|
* address for a transfer descriptor and probably gets all zeroes. It will interpret the zero as a bad QTD and
|
|
* return a USBH_LL_INTR_CHAN_BNAINTR instead.
|
|
* - If the QTD's buffer pointer is an invalid address, the core will attempt to read/write data to/from that
|
|
* invalid buffer address with NO INDICATION OF ERROR. The transfer will be acknowledged and treated as
|
|
* successful. Bad buffer pointers MUST BE CHECKED FROM HIGHER LAYERS INSTEAD.
|
|
*/
|
|
#define CHAN_INTRS_EN_MSK (USBH_LL_INTR_CHAN_XFERCOMPL | \
|
|
USBH_LL_INTR_CHAN_CHHLTD | \
|
|
USBH_LL_INTR_CHAN_BNAINTR)
|
|
|
|
#define CHAN_INTRS_ERROR_MSK (USBH_LL_INTR_CHAN_STALL | \
|
|
USBH_LL_INTR_CHAN_BBLEER | \
|
|
USBH_LL_INTR_CHAN_BNAINTR | \
|
|
USBH_LL_INTR_CHAN_XCS_XACT_ERR)
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
--------------------------------- Core (Global) --------------------------------
|
|
----------------------------------------------------------------------------- */
|
|
|
|
// ---------------------------- Private Functions ------------------------------
|
|
|
|
static void set_defaults(usbh_hal_context_t *hal)
|
|
{
|
|
usbh_ll_internal_phy_conf(hal->wrap_dev); //Enable and configure internal PHY
|
|
//GAHBCFG register
|
|
usb_ll_en_dma_mode(hal->dev);
|
|
usb_ll_set_hbstlen(hal->dev, 0); //INCR16 AHB burst length
|
|
//GUSBCFG register
|
|
usb_ll_dis_hnp_cap(hal->dev); //Disable HNP
|
|
usb_ll_dis_srp_cap(hal->dev); //Disable SRP
|
|
//Enable interruts
|
|
usb_ll_dis_intrs(hal->dev, 0xFFFFFFFF); //Mask all interrupts first
|
|
usb_ll_en_intrs(hal->dev, CORE_INTRS_EN_MSK); //Unmask global interrupts
|
|
usb_ll_intr_read_and_clear(hal->dev); //Clear interrupts
|
|
usb_ll_en_global_intr(hal->dev); //Enable interrupt signal
|
|
//Enable host mode
|
|
usb_ll_set_host_mode(hal->dev);
|
|
}
|
|
|
|
// ---------------------------- Public Functions -------------------------------
|
|
|
|
void usbh_hal_init(usbh_hal_context_t *hal)
|
|
{
|
|
//Check if a peripheral is alive by reading the core ID registers
|
|
usbh_dev_t *dev = &USBH;
|
|
#ifndef NDEBUG
|
|
uint32_t core_id = usb_ll_get_controller_core_id(dev);
|
|
assert(core_id == CORE_REG_GSNPSID);
|
|
#endif
|
|
//Initialize HAL context
|
|
memset(hal, 0, sizeof(usbh_hal_context_t));
|
|
hal->dev = dev;
|
|
hal->wrap_dev = &USB_WRAP;
|
|
set_defaults(hal);
|
|
}
|
|
|
|
void usbh_hal_deinit(usbh_hal_context_t *hal)
|
|
{
|
|
//Disable and clear global interrupt
|
|
usb_ll_dis_intrs(hal->dev, 0xFFFFFFFF); //Disable all interrupts
|
|
usb_ll_intr_read_and_clear(hal->dev); //Clear interrupts
|
|
usb_ll_dis_global_intr(hal->dev); //Disable interrupt signal
|
|
hal->dev = NULL;
|
|
hal->wrap_dev = NULL;
|
|
}
|
|
|
|
void usbh_hal_core_soft_reset(usbh_hal_context_t *hal)
|
|
{
|
|
usb_ll_core_soft_reset(hal->dev);
|
|
while (usb_ll_check_core_soft_reset(hal->dev)) {
|
|
; //Wait until core reset is done
|
|
}
|
|
while (!usb_ll_check_ahb_idle(hal->dev)) {
|
|
; //Wait until AHB Master bus is idle before doing any other operations
|
|
}
|
|
//Set the default bits
|
|
set_defaults(hal);
|
|
//Clear all the flags and channels
|
|
hal->flags.val = 0;
|
|
hal->channels.num_allocd = 0;
|
|
hal->channels.chan_pend_intrs_msk = 0;
|
|
memset(hal->channels.hdls, 0, sizeof(usbh_hal_chan_t *) * USBH_HAL_NUM_CHAN);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
---------------------------------- Host Port ----------------------------------
|
|
----------------------------------------------------------------------------- */
|
|
|
|
static inline void debounce_lock_enable(usbh_hal_context_t *hal)
|
|
{
|
|
//Disable the hprt (connection) and disconnection interrupts to prevent repeated triggerings
|
|
usb_ll_dis_intrs(hal->dev, USB_LL_INTR_CORE_PRTINT | USB_LL_INTR_CORE_DISCONNINT);
|
|
hal->flags.dbnc_lock_enabled = 1;
|
|
}
|
|
|
|
void usbh_hal_port_enable(usbh_hal_context_t *hal)
|
|
{
|
|
usb_priv_speed_t speed = usbh_ll_hprt_get_speed(hal->dev);
|
|
//Host Configuration
|
|
usbh_ll_hcfg_set_defaults(hal->dev, speed);
|
|
//Todo: Set frame list entries and ena per sched
|
|
//Configure HFIR
|
|
usbh_ll_hfir_set_defaults(hal->dev, speed);
|
|
//Config FIFO sizes
|
|
usb_ll_set_rx_fifo_size(hal->dev, RX_FIFO_LEN);
|
|
usb_ll_set_nptx_fifo_size(hal->dev, RX_FIFO_LEN, NPTX_FIFO_LEN);
|
|
usbh_ll_set_ptx_fifo_size(hal->dev, RX_FIFO_LEN + NPTX_FIFO_LEN, PTX_FIFO_LEN);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
----------------------------------- Channel ------------------------------------
|
|
------------------------------------------------------------------------------*/
|
|
|
|
// --------------------------- Channel Allocation ------------------------------
|
|
|
|
//Allocate a channel
|
|
bool usbh_hal_chan_alloc(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj, void *chan_ctx)
|
|
{
|
|
//Attempt to allocate channel
|
|
if (hal->channels.num_allocd == USBH_HAL_NUM_CHAN) {
|
|
return false; //Out of free channels
|
|
}
|
|
int chan_idx = -1;
|
|
for (int i = 0; i < USBH_HAL_NUM_CHAN; i++) {
|
|
if (hal->channels.hdls[i] == NULL) {
|
|
hal->channels.hdls[i] = chan_obj;
|
|
chan_idx = i;
|
|
hal->channels.num_allocd++;
|
|
break;
|
|
}
|
|
}
|
|
assert(chan_idx != -1);
|
|
//Initialize channel object
|
|
memset(chan_obj, 0, sizeof(usbh_hal_chan_t));
|
|
chan_obj->flags.chan_idx = chan_idx;
|
|
chan_obj->regs = usbh_ll_get_chan_regs(hal->dev, chan_idx);
|
|
chan_obj->chan_ctx = chan_ctx;
|
|
//Note: EP characteristics configured separately
|
|
//Clean and unmask the channel's interrupt
|
|
usbh_ll_chan_intr_read_and_clear(chan_obj->regs); //Clear the interrupt bits for that channel
|
|
usbh_ll_haintmsk_en_chan_intr(hal->dev, 1 << chan_obj->flags.chan_idx);
|
|
usbh_ll_chan_set_intr_mask(chan_obj->regs, CHAN_INTRS_EN_MSK); //Unmask interrupts for this channel
|
|
usbh_ll_chan_set_pid(chan_obj->regs, 0); //Set the initial PID to zero
|
|
usbh_ll_chan_hctsiz_init(chan_obj->regs); //Set the non changing parts of the HCTSIZ registers (e.g., do_ping and sched info)
|
|
return true;
|
|
}
|
|
|
|
//Returns object memory
|
|
void usbh_hal_chan_free(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj)
|
|
{
|
|
//Can only free a channel when in the disabled state and descriptor list released
|
|
assert(!chan_obj->slot.flags.slot_acquired
|
|
&& !chan_obj->flags.active
|
|
&& !chan_obj->flags.error_pending);
|
|
//Deallocate channel
|
|
hal->channels.hdls[chan_obj->flags.chan_idx] = NULL;
|
|
hal->channels.num_allocd--;
|
|
assert(hal->channels.num_allocd >= 0);
|
|
}
|
|
|
|
// ---------------------------- Channel Control --------------------------------
|
|
|
|
void usbh_hal_chan_set_ep_char(usbh_hal_chan_t *chan_obj, usbh_hal_ep_char_t *ep_char)
|
|
{
|
|
//Cannot change ep_char whilst channel is still active or in error
|
|
assert(!chan_obj->flags.active && !chan_obj->flags.error_pending);
|
|
//Set the endpoint characteristics of the pipe
|
|
usbh_ll_chan_hcchar_init(chan_obj->regs,
|
|
ep_char->dev_addr,
|
|
ep_char->bEndpointAddress & BENDPOINTADDRESS_NUM_MSK,
|
|
ep_char->mps,
|
|
ep_char->type,
|
|
ep_char->bEndpointAddress & BENDPOINTADDRESS_DIR_MSK,
|
|
ep_char->ls_via_fs_hub);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
------------------------------- Transfers Slots --------------------------------
|
|
------------------------------------------------------------------------------*/
|
|
|
|
void usbh_hal_chan_activate(usbh_hal_chan_t *chan_obj, int num_to_skip)
|
|
{
|
|
//Cannot enable a channel that has already been enabled or is pending error handling
|
|
assert(!chan_obj->flags.active && !chan_obj->flags.error_pending);
|
|
assert(chan_obj->slot.flags.slot_acquired);
|
|
//Update the descriptor list index and check if it's within bounds
|
|
chan_obj->slot.flags.cur_qtd_idx += num_to_skip;
|
|
assert(chan_obj->slot.flags.cur_qtd_idx < chan_obj->slot.flags.qtd_list_len);
|
|
chan_obj->flags.active = 1;
|
|
|
|
//Set start address of the QTD list and starting QTD index
|
|
usbh_ll_chan_set_dma_addr_non_iso(chan_obj->regs, chan_obj->slot.xfer_desc_list, chan_obj->slot.flags.cur_qtd_idx);
|
|
//Start the channel
|
|
usbh_ll_chan_start(chan_obj->regs);
|
|
}
|
|
|
|
bool usbh_hal_chan_slot_request_halt(usbh_hal_chan_t *chan_obj)
|
|
{
|
|
//Cannot request halt on a channel that is pending error handling
|
|
assert(!chan_obj->flags.error_pending);
|
|
if (usbh_ll_chan_is_active(chan_obj->regs) || chan_obj->flags.active) {
|
|
usbh_ll_chan_halt(chan_obj->regs);
|
|
chan_obj->flags.halt_requested = 1;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
-------------------------------- Event Handling --------------------------------
|
|
----------------------------------------------------------------------------- */
|
|
|
|
//When a device on the port is no longer valid (e.g., disconnect, port error). All channels are no longer valid
|
|
static void chan_all_halt(usbh_hal_context_t *hal)
|
|
{
|
|
for (int i = 0; i < USBH_HAL_NUM_CHAN; i++) {
|
|
if (hal->channels.hdls[i] != NULL) {
|
|
hal->channels.hdls[i]->flags.active = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal)
|
|
{
|
|
uint32_t intrs_core = usb_ll_intr_read_and_clear(hal->dev); //Read and clear core interrupts
|
|
uint32_t intrs_port = 0;
|
|
if (intrs_core & USB_LL_INTR_CORE_PRTINT) {
|
|
//There are host port interrupts. Read and clear those as well.
|
|
intrs_port = usbh_ll_hprt_intr_read_and_clear(hal->dev);
|
|
}
|
|
//Note: Do not change order of checks. Regressing events (e.g. enable -> disabled, connected -> connected)
|
|
//always take precendance. ENABLED < DISABLED < CONN < DISCONN < OVRCUR
|
|
usbh_hal_port_event_t event = USBH_HAL_PORT_EVENT_NONE;
|
|
|
|
//Check if this is a core or port event
|
|
if ((intrs_core & CORE_EVENTS_INTRS_MSK) || (intrs_port & PORT_EVENTS_INTRS_MSK)) {
|
|
//Do not change the order of the following checks. Some events/interrupts take precedence over others
|
|
if (intrs_core & USB_LL_INTR_CORE_DISCONNINT) {
|
|
event = USBH_HAL_PORT_EVENT_DISCONN;
|
|
debounce_lock_enable(hal);
|
|
chan_all_halt(hal); //All channels are halted on a disconnect
|
|
//Mask the port connection and disconnection interrupts to prevent repeated triggering
|
|
} else if (intrs_port & USBH_LL_INTR_HPRT_PRTOVRCURRCHNG) {
|
|
//Check if this is an overcurrent or an overcurrent cleared
|
|
if (usbh_ll_hprt_get_port_overcur(hal->dev)) {
|
|
event = USBH_HAL_PORT_EVENT_OVRCUR;
|
|
chan_all_halt(hal); //All channels are halted on an overcurrent
|
|
} else {
|
|
event = USBH_HAL_PORT_EVENT_OVRCUR_CLR;
|
|
}
|
|
} else if (intrs_port & USBH_LL_INTR_HPRT_PRTENCHNG) {
|
|
if (usbh_ll_hprt_get_port_en(hal->dev)) { //Host port was enabled
|
|
event = USBH_HAL_PORT_EVENT_ENABLED;
|
|
} else { //Host port has been disabled
|
|
event = USBH_HAL_PORT_EVENT_DISABLED;
|
|
chan_all_halt(hal); //All channels are halted when the port is disabled
|
|
}
|
|
} else if (intrs_port & USBH_LL_INTR_HPRT_PRTCONNDET && !hal->flags.dbnc_lock_enabled) {
|
|
event = USBH_HAL_PORT_EVENT_CONN;
|
|
debounce_lock_enable(hal);
|
|
}
|
|
}
|
|
//Port events always take precendance over channel events
|
|
if (event == USBH_HAL_PORT_EVENT_NONE && (intrs_core & USB_LL_INTR_CORE_HCHINT)) {
|
|
//One or more channels have pending interrupts. Store the mask of those channels
|
|
hal->channels.chan_pend_intrs_msk = usbh_ll_get_chan_intrs_msk(hal->dev);
|
|
event = USBH_HAL_PORT_EVENT_CHAN;
|
|
}
|
|
|
|
return event;
|
|
}
|
|
|
|
usbh_hal_chan_t *usbh_hal_get_chan_pending_intr(usbh_hal_context_t *hal)
|
|
{
|
|
int chan_num = __builtin_ffs(hal->channels.chan_pend_intrs_msk);
|
|
if (chan_num) {
|
|
hal->channels.chan_pend_intrs_msk &= ~(1 << (chan_num - 1)); //Clear the pending bit for that channel
|
|
return hal->channels.hdls[chan_num - 1];
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
usbh_hal_chan_event_t usbh_hal_chan_decode_intr(usbh_hal_chan_t *chan_obj)
|
|
{
|
|
uint32_t chan_intrs = usbh_ll_chan_intr_read_and_clear(chan_obj->regs);
|
|
usbh_hal_chan_event_t chan_event;
|
|
//Currently, all cases where channel interrupts occur will also halt the channel, except for BNA
|
|
assert(chan_intrs & (USBH_LL_INTR_CHAN_CHHLTD | USBH_LL_INTR_CHAN_BNAINTR));
|
|
chan_obj->flags.active = 0;
|
|
//Note: Do not change the current checking order of checks. Certain interrupts (e.g., errors) have precedence over others
|
|
if (chan_intrs & CHAN_INTRS_ERROR_MSK) { //One of the error interrupts has occurred.
|
|
//Note: Errors are uncommon, so we check against the entire interrupt mask to reduce frequency of entering this call path
|
|
//Store the error in hal context
|
|
usbh_hal_chan_error_t error;
|
|
if (chan_intrs & USBH_LL_INTR_CHAN_STALL) {
|
|
error = USBH_HAL_CHAN_ERROR_STALL;
|
|
} else if (chan_intrs & USBH_LL_INTR_CHAN_BBLEER) {
|
|
error = USBH_HAL_CHAN_ERROR_PKT_BBL;
|
|
} else if (chan_intrs & USBH_LL_INTR_CHAN_BNAINTR) {
|
|
error = USBH_HAL_CHAN_ERROR_BNA;
|
|
} else { //USBH_LL_INTR_CHAN_XCS_XACT_ERR
|
|
error = USBH_HAL_CHAN_ERROR_XCS_XACT;
|
|
}
|
|
//Update flags
|
|
chan_obj->error = error;
|
|
chan_obj->flags.error_pending = 1;
|
|
//Save the error to be handled later
|
|
chan_event = USBH_HAL_CHAN_EVENT_ERROR;
|
|
} else if (chan_obj->flags.halt_requested) { //A halt was previously requested and has not been fulfilled
|
|
chan_obj->flags.halt_requested = 0;
|
|
chan_event = USBH_HAL_CHAN_EVENT_HALT_REQ;
|
|
} else if (chan_intrs & USBH_LL_INTR_CHAN_XFERCOMPL) {
|
|
int cur_qtd_idx = usbh_ll_chan_get_ctd(chan_obj->regs);
|
|
//Store current qtd index
|
|
chan_obj->slot.flags.cur_qtd_idx = cur_qtd_idx;
|
|
if (cur_qtd_idx == 0) {
|
|
//If the transfer descriptor list has completed, the CTD index should be 0 (wrapped around)
|
|
chan_event = USBH_HAL_CHAN_EVENT_SLOT_DONE;
|
|
} else {
|
|
chan_event = USBH_HAL_CHAN_EVENT_SLOT_HALT;
|
|
}
|
|
} else {
|
|
//Should never reach this point
|
|
abort();
|
|
}
|
|
return chan_event;
|
|
}
|