| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- /*
- * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Apache-2.0
- */
- #include <stddef.h>
- #include <stdint.h>
- #include <string.h>
- #include "sdkconfig.h"
- #include "hal/usbh_hal.h"
- #include "hal/usbh_ll.h"
- #include "hal/assert.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 -----------------------
- /**
- * 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., ESP32-S2, ESP32-S3):
- * - 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) ----------------------------------------------------
- static void set_defaults(usbh_hal_context_t *hal)
- {
- //GAHBCFG register
- usb_ll_en_dma_mode(hal->dev);
- #ifdef CONFIG_IDF_TARGET_ESP32S2
- usb_ll_set_hbstlen(hal->dev, 1); //Use INCR AHB burst. See the ESP32-S2 and later chip ERRATA.
- #elif CONFIG_IDF_TARGET_ESP32S3
- usb_ll_set_hbstlen(hal->dev, 0); //Do not use USB burst INCR mode for the ESP32-S3, to avoid interference with other peripherals.
- #endif
- //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);
- }
- 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;
- uint32_t core_id = usb_ll_get_controller_core_id(dev);
- HAL_ASSERT(core_id == CORE_REG_GSNPSID);
- (void) core_id; //Suppress unused variable warning if asserts are disabled
- //Initialize HAL context
- memset(hal, 0, sizeof(usbh_hal_context_t));
- hal->dev = dev;
- 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;
- }
- 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->periodic_frame_list = NULL;
- 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);
- }
- void usbh_hal_set_fifo_size(usbh_hal_context_t *hal, const usbh_hal_fifo_config_t *fifo_config)
- {
- HAL_ASSERT((fifo_config->rx_fifo_lines + fifo_config->nptx_fifo_lines + fifo_config->ptx_fifo_lines) <= USBH_HAL_FIFO_TOTAL_USABLE_LINES);
- //Check that none of the channels are active
- for (int i = 0; i < USBH_HAL_NUM_CHAN; i++) {
- if (hal->channels.hdls[i] != NULL) {
- HAL_ASSERT(!hal->channels.hdls[i]->flags.active);
- }
- }
- //Set the new FIFO lengths
- usb_ll_set_rx_fifo_size(hal->dev, fifo_config->rx_fifo_lines);
- usb_ll_set_nptx_fifo_size(hal->dev, fifo_config->rx_fifo_lines, fifo_config->nptx_fifo_lines);
- usbh_ll_set_ptx_fifo_size(hal->dev, fifo_config->rx_fifo_lines + fifo_config->nptx_fifo_lines, fifo_config->ptx_fifo_lines);
- //Flush the FIFOs
- usb_ll_flush_nptx_fifo(hal->dev);
- usb_ll_flush_ptx_fifo(hal->dev);
- usb_ll_flush_rx_fifo(hal->dev);
- hal->flags.fifo_sizes_set = 1;
- }
- // ---------------------------------------------------- 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);
- //Configure HFIR
- usbh_ll_hfir_set_defaults(hal->dev, speed);
- }
- // ----------------------------------------------------- Channel -------------------------------------------------------
- // ----------------- Channel Allocation --------------------
- bool usbh_hal_chan_alloc(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj, void *chan_ctx)
- {
- HAL_ASSERT(hal->flags.fifo_sizes_set); //FIFO sizes should be set befor attempting to allocate a channel
- //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;
- }
- }
- HAL_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;
- }
- void usbh_hal_chan_free(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj)
- {
- if (chan_obj->type == USB_PRIV_XFER_TYPE_INTR || chan_obj->type == USB_PRIV_XFER_TYPE_ISOCHRONOUS) {
- //Unschedule this channel
- for (int i = 0; i < hal->frame_list_len; i++) {
- hal->periodic_frame_list[i] &= ~(1 << chan_obj->flags.chan_idx);
- }
- }
- //Can only free a channel when in the disabled state and descriptor list released
- HAL_ASSERT(!chan_obj->flags.active);
- //Disable channel's interrupt
- usbh_ll_haintmsk_dis_chan_intr(hal->dev, 1 << chan_obj->flags.chan_idx);
- //Deallocate channel
- hal->channels.hdls[chan_obj->flags.chan_idx] = NULL;
- hal->channels.num_allocd--;
- HAL_ASSERT(hal->channels.num_allocd >= 0);
- }
- // ---------------- Channel Configuration ------------------
- void usbh_hal_chan_set_ep_char(usbh_hal_context_t *hal, 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
- HAL_ASSERT(!chan_obj->flags.active);
- //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);
- //Save channel type
- chan_obj->type = ep_char->type;
- //If this is a periodic endpoint/channel, set its schedule in the frame list
- if (ep_char->type == USB_PRIV_XFER_TYPE_ISOCHRONOUS || ep_char->type == USB_PRIV_XFER_TYPE_INTR) {
- HAL_ASSERT((int)ep_char->periodic.interval <= (int)hal->frame_list_len); //Interval cannot exceed the length of the frame list
- //Find the effective offset in the frame list (in case the phase_offset_frames > interval)
- int offset = ep_char->periodic.phase_offset_frames % ep_char->periodic.interval;
- //Schedule the channel in the frame list
- for (int i = offset; i < hal->frame_list_len; i+= ep_char->periodic.interval) {
- hal->periodic_frame_list[i] |= 1 << chan_obj->flags.chan_idx;
- }
- }
- }
- // ------------------- Channel Control ---------------------
- void usbh_hal_chan_activate(usbh_hal_chan_t *chan_obj, void *xfer_desc_list, int desc_list_len, int start_idx)
- {
- //Cannot activate a channel that has already been enabled or is pending error handling
- HAL_ASSERT(!chan_obj->flags.active);
- //Set start address of the QTD list and starting QTD index
- usbh_ll_chan_set_dma_addr_non_iso(chan_obj->regs, xfer_desc_list, start_idx);
- usbh_ll_chan_set_qtd_list_len(chan_obj->regs, desc_list_len);
- usbh_ll_chan_start(chan_obj->regs); //Start the channel
- chan_obj->flags.active = 1;
- }
- bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj)
- {
- //Cannot request halt on a channel that is pending error handling
- if (usbh_ll_chan_is_active(chan_obj->regs)) {
- //If the register indicates that the channel is still active, the active flag must have been previously set
- HAL_ASSERT(chan_obj->flags.active);
- usbh_ll_chan_halt(chan_obj->regs);
- chan_obj->flags.halt_requested = 1;
- return false;
- } else {
- //We just clear the active flag here as it could still be set (if we have a pending channel interrupt)
- chan_obj->flags.active = 0;
- return true;
- }
- }
- // ------------------------------------------------- Event Handling ----------------------------------------------------
- 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 precedence. 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);
- //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;
- } 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;
- }
- } 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 precedence 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;
- //Note: We don't assert on (chan_obj->flags.active) here as it could have been already cleared by usbh_hal_chan_request_halt()
- if (chan_intrs & CHAN_INTRS_ERROR_MSK) { //Note: Errors are uncommon, so we check against the entire interrupt mask to reduce frequency of entering this call path
- HAL_ASSERT(chan_intrs & USBH_LL_INTR_CHAN_CHHLTD); //An error should have halted the channel
- //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.active = 0;
- //Save the error to be handled later
- chan_event = USBH_HAL_CHAN_EVENT_ERROR;
- } else if (chan_intrs & USBH_LL_INTR_CHAN_CHHLTD) {
- if (chan_obj->flags.halt_requested) {
- chan_obj->flags.halt_requested = 0;
- chan_event = USBH_HAL_CHAN_EVENT_HALT_REQ;
- } else {
- //Must have been halted due to QTD HOC
- chan_event = USBH_HAL_CHAN_EVENT_CPLT;
- }
- chan_obj->flags.active = 0;
- } else if (chan_intrs & USBH_LL_INTR_CHAN_XFERCOMPL) {
- /*
- A transfer complete interrupt WITHOUT the channel halting only occurs when receiving a short interrupt IN packet
- and the underlying QTD does not have the HOC bit set. This signifies the last packet of the Interrupt transfer
- as all interrupt packets must MPS sized except the last.
- */
- //The channel isn't halted yet, so we need to halt it manually to stop the execution of the next QTD/packet
- usbh_ll_chan_halt(chan_obj->regs);
- /*
- After setting the halt bit, this will generate another channel halted interrupt. We treat this interrupt as
- a NONE event, then cycle back with the channel halted interrupt to handle the CPLT event.
- */
- chan_event = USBH_HAL_CHAN_EVENT_NONE;
- } else {
- abort(); //Should never reach this point
- }
- return chan_event;
- }
|