diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index 28e0bc3213..617dbe14bb 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -65,7 +65,8 @@ if(NOT BOOTLOADER_BUILD) "esp32s2/touch_sensor_hal.c" "esp32s2/dac_hal.c" "esp32s2/interrupt_descriptor_table.c" - "esp32s2/usb_hal.c") + "esp32s2/usb_hal.c" + "esp32s2/usbh_hal.c") endif() if(${target} STREQUAL "esp32s3") diff --git a/components/hal/esp32s2/include/hal/usbh_hal.h b/components/hal/esp32s2/include/hal/usbh_hal.h new file mode 100644 index 0000000000..5dce919232 --- /dev/null +++ b/components/hal/esp32s2/include/hal/usbh_hal.h @@ -0,0 +1,789 @@ +// 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. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* +NOTE: Thread safety is the responsibility fo the HAL user. All USB Host HAL + functions should be called from critical sections unless specified otherwise +*/ + +#include +#include +#include "soc/usbh_struct.h" +#include "soc/usb_wrap_struct.h" +#include "hal/usbh_ll.h" +#include "hal/usb_types.h" + +/* ----------------------------------------------------------------------------- +------------------------------- Macros and Types ------------------------------- +----------------------------------------------------------------------------- */ + +// ---------------------------- Constants/Configs ------------------------------ + +#define USBH_HAL_DMA_MEM_ALIGN 512 +#define USBH_HAL_NUM_CHAN 8 +#define USBH_HAL_XFER_DESC_SIZE (sizeof(usbh_ll_dma_qtd_t)) + +// ------------------------------- HAL States ---------------------------------- + +/** + * @brief Channel states + */ +typedef enum { + USBH_HAL_CHAN_STATE_HALTED = 0, /**< The channel is halted. No transfer descriptor list is being executed */ + USBH_HAL_CHAN_STATE_ACTIVE, /**< The channel is active. A transfer descriptor list is being executed */ + USBH_HAL_CHAN_STATE_ERROR, /**< The channel is in the error state */ +} usbh_hal_chan_state_t; + +// ------------------------------- HAL Events ---------------------------------- + +/** + * @brief Host port HAL events + */ +typedef enum { + USBH_HAL_PORT_EVENT_CHAN, /**< A channel event has occurred. Call the the channel event handler instead */ + USBH_HAL_PORT_EVENT_CONN, /**< The host port has detected a connection */ + USBH_HAL_PORT_EVENT_DISCONN, /**< The host port has been disconnected */ + USBH_HAL_PORT_EVENT_ENABLED, /**< The host port has been enabled (i.e., connected to a device that has been reset. Started sending SOFs) */ + USBH_HAL_PORT_EVENT_DISABLED, /**< The host port has been disabled (no more SOFs). Could be due to disable/reset request, or a port error (e.g. port babble condition. See 11.8.1 of USB2.0 spec) */ + USBH_HAL_PORT_EVENT_OVRCUR, /**< The host port has encountered an overcurrent condition */ + USBH_HAL_PORT_EVENT_OVRCUR_CLR, /**< The host port has been cleared of the overcurrent condition */ +} usbh_hal_port_event_t; + +/** + * @brief Channel events + */ +typedef enum { + USBH_HAL_CHAN_EVENT_SLOT_DONE, /**< The channel has completed execution of an entire transfer descriptor list. Channel is now halted */ + USBH_HAL_CHAN_EVENT_SLOT_HALT, /**< The channel as completed execution of a single transfer descriptor in a list. Channel is now halted */ + USBH_HAL_CHAN_EVENT_ERROR, /**< The channel has encountered an error. Channel is now halted. */ + USBH_HAL_CHAN_EVENT_HALT_REQ, /**< The channel has been successfully halted as requested */ + USBH_HAL_CHAN_EVENT_SUDDEN_HLT, /**< The channel was suddenly halted (e.g. due to a disconnect). */ +} usbh_hal_chan_event_t; + +// ------------------------------- HAL Errors ---------------------------------- + +/** + * @brief Channel errors + */ +typedef enum { + USBH_HAL_CHAN_ERROR_XCS_XACT = 0, /**< Excessive (three consecutive) transaction errors (e.g., no response, bad CRC etc */ + USBH_HAL_CHAN_ERROR_BNA, /**< Buffer Not Available error (i.e., transfer slot is unfilled */ + USBH_HAL_CHAN_ERROR_PKT_BBL, /**< Packet babbler error (packet exceeded MPS) */ + USBH_HAL_CHAN_ERROR_STALL, /**< STALL response received */ + USBH_HAL_CHAN_ERROR_AHB, /**< AHB error */ +} usbh_hal_chan_error_t; + +// ----------------------- Transfer Descriptor Related ------------------------- + +/** + * @brief Flags used to describe the type of transfer descriptor to fill + */ +#define USBH_HAL_XFER_DESC_FLAG_IN 0x01 +#define USBH_HAL_XFER_DESC_FLAG_SETUP 0x02 +#define USBH_HAL_XFER_DESC_FLAG_NULL 0x04 +#define USBH_HAL_XFER_DESC_FLAG_HALT 0x08 + +/** + * @brief Status value of a transfer descriptor + */ +#define USBH_HAL_XFER_DESC_STS_SUCCESS USBH_LL_QTD_STATUS_SUCCESS +#define USBH_HAL_XFER_DESC_STS_PKTERR USBH_LL_QTD_STATUS_PKTERR +#define USBH_HAL_XFER_DESC_STS_BUFFER_ERR USBH_LL_QTD_STATUS_BUFFER +#define USBH_HAL_XFER_DESC_STS_NOT_EXECUTED USBH_LL_QTD_STATUS_NOT_EXECUTED + +// ------------------------------ Object Types --------------------------------- + +/** + * @brief Endpoint characteristics structure + */ +typedef struct { + union { + struct { + usb_xfer_type_t type: 2; /**< The type of endpoint */ + uint32_t bEndpointAddress: 8; /**< Endpoint address (containing endpoint number and direction) */ + uint32_t mps: 11; /**< Maximum Packet Size */ + uint32_t dev_addr: 8; /**< Device Address */ + uint32_t reserved3: 3; + }; + uint32_t val; + }; +} usbh_hal_ep_char_t; + +/** + * @brief Channel object + */ +typedef struct { + //Channel control, status, and information + union { + struct { + uint32_t active: 1; /**< The channel is enabled */ + uint32_t halt_requested: 1; /**< A halt has been requested */ + uint32_t error_pending: 1; /**< The channel is waiting for the error to be handled */ + uint32_t chan_idx: 4; /**< The index number of the channel */ + uint32_t reserved25: 25; + }; + uint32_t val; + } flags; /**< Flags regarding channel's status and information */ + usb_host_chan_regs_t *regs; /**< Pointer to the channel's register set */ + usbh_hal_chan_error_t error; /**< The last error that occurred on the channel */ + void *chan_ctx; /**< Context variable for the owner of the channel */ + //Transfer Descriptor List Slot + struct { + union { + struct { + bool slot_acquired: 1; /**< The transfer descriptor list slot has been acquired */ + uint32_t reserved7: 7; + uint32_t cur_qtd_idx: 8; /**< Index of the first QTD in chain of QTDs being executed */ + uint32_t qtd_list_len: 8; /**< Length of QTD list in number of QTDs */ + uint32_t reserved8: 8; + }; + uint32_t val; + } flags; + void *owner_ctx; /**< Context variable for the owner of the slot */ + usbh_ll_dma_qtd_t *xfer_desc_list; /**< Pointer to transfer descriptor list */ + } slot; +} usbh_hal_chan_t; + +/** + * @brief HAL context structure + */ +typedef struct { + //Context + usbh_dev_t *dev; /**< Pointer to base address of DWC_OTG registers */ + usb_wrap_dev_t *wrap_dev; /**< Pointer to base address of USB Wrapper registers */ + //Host Port related + union { + struct { + uint32_t dbnc_lock_enabled: 1; /**< Debounce lock enabled */ + uint32_t reserved31: 31; + }; + uint32_t val; + } flags; + //Channel related + struct { + int num_allocd; /**< Number of channels currently allocated */ + int chan_pend_intrs_msk; /**< Bit mask of channels with pending interrupts */ + usbh_hal_chan_t *hdls[USBH_HAL_NUM_CHAN]; /**< Handles of each channel. Set to NULL if channel has not been allocated */ + } channels; +} usbh_hal_context_t; + +/* ----------------------------------------------------------------------------- +--------------------------------- Core (Global) -------------------------------- +----------------------------------------------------------------------------- */ + +/** + * @brief Initialize the HAL context and check if DWC_OTG is alive + * + * Entry: + * - The peripheral must have been reset and clock un-gated + * - GPIO pins configured + * - Interrupt allocated but DISABLED (in case of an unknown interupt state) + * Exit: + * - Checks to see if DWC_OTG is alive, and if HW version/config is correct + * - HAl context initialized + * - Sets default values to some global and OTG registers (GAHBCFG and GUSBCFG) + * - Umask global interrupt signal + * - Put DWC_OTG into host mode. Require 25ms delay before this takes effect. + * - State -> USBH_HAL_PORT_STATE_OTG + * - Interrupts cleared. Users can now enable their ISR + * + * @param[inout] hal Context of the HAL layer + */ +void usbh_hal_init(usbh_hal_context_t *hal); + +/** + * @brief Deinitialize the HAL context + * + * Entry: + * - All channels should be properly disabled, and any pending events handled + * Exit: + * - DWC_OTG global interrupt disabled + * - HAL context deinitialized + * + * @param hal Context of the HAL layer + */ +void usbh_hal_deinit(usbh_hal_context_t *hal); + +/** + * @brief Issue a soft reset to the controller + * + * This should be called when the host port encounters an error event or has + * been disconnected. Before calling this, users are responsible for safely + * freeing all channels as a soft reset will wipe all host port nd channel + * registers. + * + * This function will result in the host port being put back into same state as + * after calling usbh_hal_init(). + * + * @note This has nothing to do with a USB bus reset. It simply resets the peripheral + * + * @param hal Context of the HAL layer + */ +void usbh_hal_core_soft_reset(usbh_hal_context_t *hal); + +/* ----------------------------------------------------------------------------- +---------------------------------- Host Port ---------------------------------- +----------------------------------------------------------------------------- */ + +// ---------------------------- Host Port Control ------------------------------ + +/** + * @brief Enable the host port's interrupt allowing port and channel events to occur + * + * @param hal Context of the HAL layer + */ +static inline void usbh_hal_port_start(usbh_hal_context_t *hal) +{ + //Configure Host related interrupts + usbh_ll_haintmsk_dis_chan_intr(hal->dev, 0xFFFFFFFF); //Disable interrupts for all channels + usb_ll_en_intrs(hal->dev, USB_LL_INTR_CORE_PRTINT | USB_LL_INTR_CORE_HCHINT); +} + +/** + * @brief Disable the host port's interrupt preventing any further port or channel events + * + * @param hal Context of the HAL layer + */ +static inline void usbh_hal_port_stop(usbh_hal_context_t *hal) +{ + //Disable Host port and channel interrupts + usb_ll_dis_intrs(hal->dev, USB_LL_INTR_CORE_PRTINT | USB_LL_INTR_CORE_HCHINT); +} + +/** + * @brief Toggle the host port's power + * + * @param hal Context of the HAL layer + * @param power_on Whether to power ON or OFF the port + */ +static inline void usbh_hal_port_toggle_power(usbh_hal_context_t *hal, bool power_on) +{ + if (power_on) { + usbh_ll_hprt_en_pwr(hal->dev); + } else { + usbh_ll_hprt_dis_pwr(hal->dev); + } +} + +/** + * @brief Toggle reset signal on the bus + * + * The reset signal should be held for at least 10ms + * Entry: + * - Host port detects a device connection or Host port is already enabled + * Exit: + * - On release of the reset signal, a USBH_HAL_PORT_EVENT_ENABLED will be generated + * + * @note If the host port is already enabled, then issuing a reset will cause + * it be disabled and generate a USBH_HAL_PORT_EVENT_DISABLED event. The + * host port will not be enabled until the reset signal is released (thus + * generating the USBH_HAL_PORT_EVENT_ENABLED event) + * + * @param hal Context of the HAL layer + * @param enable Enable/disable reset signal + */ +static inline void usbh_hal_port_toggle_reset(usbh_hal_context_t *hal, bool enable) +{ + assert(hal->channels.num_allocd == 0); //Cannot reset if there are still allocated channels + usbh_ll_hprt_set_port_reset(hal->dev, enable); +} + +/** + * @brief Enable the host port + * + * Entry: + * - Host port enabled event triggered following a reset + * Exit: + * - Host port enabled to operate in scatter/gather DMA mode + * - DMA fifo sizes configured + * + * @param hal Context of the HAL layer + */ +void usbh_hal_port_enable(usbh_hal_context_t *hal); + +/** + * @brief Disable the host port + * + * Exit: + * - Host port disabled event triggered + * + * @param hal Context of the HAL layer + */ +static inline void usbh_hal_port_disable(usbh_hal_context_t *hal) +{ + usbh_ll_hprt_port_dis(hal->dev); +} + +/** + * @brief Suspend the host port + * + * @param hal Context of the HAL layers + */ +static inline void usbh_hal_port_suspend(usbh_hal_context_t *hal) +{ + usbh_ll_hprt_set_port_suspend(hal->dev); +} + +/** + * @brief Toggle resume signal on the bus + * + * Hosts should hold the resume signal for at least 20ms + * + * @note If a remote wakeup event occurs, the resume signal is driven + * and cleared automatically. + * + * @param hal Context of the HAL layer + * @param enable Enable/disable resume signal + */ +static inline void usbh_hal_port_toggle_resume(usbh_hal_context_t *hal, bool enable) +{ + if (enable) { + usbh_ll_hprt_set_port_resume(hal->dev); + } else { + usbh_ll_hprt_clr_port_resume(hal->dev); + } +} + +/** + * @brief Check whether the resume signal is being driven + * + * If a remote wakeup event occurs, the core will automatically drive and clear + * the resume signal for the required amount of time. Call this function to + * check whether the resume signal has completed. + * + * @param hal Context of the HAL layer + * @return true Resume signal is still being driven + * @return false Resume signal is no longer driven + */ +static inline bool usbh_hal_port_check_resume(usbh_hal_context_t *hal) +{ + return usbh_ll_hprt_get_port_resume(hal->dev); +} + +// -------------------------- Host Port Status/State --------------------------- + +/** + * @brief Check if a device is currently connected to the host port + * + * This function is intended to be called after one of the following events + * followed by an adequate debounce delay + * - USBH_HAL_PORT_EVENT_CONN + * - USBH_HAL_PORT_EVENT_DISCONN + * + * @note No other connection/disconnection event will occur again until the + * debounce lock is disabled via usbh_hal_disable_debounce_lock() + * + * @param hal Context of the HAL layer + * @return true A device is connected to the host port + * @return false A device is not connected to the host port + */ +static inline bool usbh_hal_port_check_if_connected(usbh_hal_context_t *hal) +{ + return usbh_ll_hprt_get_conn_status(hal->dev); +} + +/** + * @brief Check the speed (LS/FS) of the device connected to the host port + * + * @note This function should only be called after confirming that a device is + * connected to the host port + * + * @param hal Context of the HAL layer + * @return usb_speed_t Speed of the connected device + */ +static inline usb_speed_t usbh_hal_port_get_conn_speed(usbh_hal_context_t *hal) +{ + return usbh_ll_hprt_get_speed(hal->dev); +} + +/** + * @brief Disable the debounce lock + * + * This function should be called after calling usbh_hal_port_check_if_connected() + * and will allow connection/disconnection events to occur again. + * + * @param hal Context of the HAL layer + */ +static inline void usbh_hal_disable_debounce_lock(usbh_hal_context_t *hal) +{ + hal->flags.dbnc_lock_enabled = 0; + //Reenable the hprt (connection) and disconnection interrupts + usb_ll_en_intrs(hal->dev, USB_LL_INTR_CORE_PRTINT | USB_LL_INTR_CORE_DISCONNINT); +} + +/* ----------------------------------------------------------------------------- +----------------------------------- Channel ------------------------------------ +------------------------------------------------------------------------------*/ + +// --------------------------- Channel Allocation ------------------------------ + +/** + * @brief Allocate a channel + * + * @param[in] hal Context of the HAL layer + * @param[inout] chan_obj Empty channel object + * @param[in] chan_ctx Context variable for the allocator of the channel + * @return true Channel successfully allocated + * @return false Failed to allocate channel + */ +bool usbh_hal_chan_alloc(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj, void *chan_ctx); + +/** + * @brief Free a channel + * + * @param[in] hal Context of the HAL layer + * @param[in] chan_obj Channel object + */ +void usbh_hal_chan_free(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj); + +/** + * @brief Get the context variable of the channel + * + * @param[in] chan_obj Channel object + * @return void* The context variable of the channel + */ +static inline void *usbh_hal_chan_get_context(usbh_hal_chan_t *chan_obj) +{ + return chan_obj->chan_ctx; +} + +// ---------------------------- Channel Control -------------------------------- + +/** + * @brief Get the current state of a channel + * + * @param chan_obj Channel object + * @return usbh_hal_chan_state_t State of the channel + */ +static inline usbh_hal_chan_state_t usbh_hal_chan_get_state(usbh_hal_chan_t *chan_obj) +{ + if (chan_obj->flags.error_pending) { + return USBH_HAL_CHAN_STATE_ERROR; + } else if (chan_obj->flags.active) { + return USBH_HAL_CHAN_STATE_ACTIVE; + } else { + return USBH_HAL_CHAN_STATE_HALTED; + } +} + +/** + * @brief Set the endpoint information for a particular channel + * + * This should be called when a channel switches target from one EP to another + * + * @note the channel must be in the disabled state in order to change its EP + * information + * + * @param chan_obj Channel object + * @param ep_char Endpoint characteristics + */ +void usbh_hal_chan_set_ep_char(usbh_hal_chan_t *chan_obj, usbh_hal_ep_char_t *ep_char); + +/** + * @brief Set the direction of the channel + * + * This is a convenience function to flip the direction of a channel without + * needing to reconfigure all of the channel's EP info. This is used primarily + * for control transfers. + * + * @note This function should only be called when the channel is in the disabled + * state or is halted from a USBH_HAL_CHAN_EVENT_SLOT_HALT event + * + * @param chan_obj Channel object + * @param is_in Whether the direction is IN + */ +static inline void usbh_hal_chan_set_dir(usbh_hal_chan_t *chan_obj, bool is_in) +{ + //Cannot change direction whilst channel is still active or in error + assert(!chan_obj->flags.active && !chan_obj->flags.error_pending); + usbh_ll_chan_set_dir(chan_obj->regs, is_in); +} + +/** + * @brief Set the next Packet ID of the channel (e.g., DATA0/DATA1) + * + * This should be called when a channel switches target from one EP to another + * or when change stages for a control transfer + * + * @note The channel should only be called when the channel is in the + * halted state. + * + * @param chan_obj Channel object + * @param pid PID of the next DATA packet (DATA0 or DATA1) + */ +static inline void usbh_hal_chan_set_pid(usbh_hal_chan_t *chan_obj, int pid) +{ + //Cannot change pid whilst channel is still active or in error + assert(!chan_obj->flags.active && !chan_obj->flags.error_pending); + //Update channel object and set the register + usbh_ll_chan_set_pid(chan_obj->regs, pid); +} + +/** + * @brief Get the next PID of a channel + * + * Returns the next PID (DATA0 or DATA1) of the channel. This function should be + * used when the next PID of a pipe needs to be saved (e.g., when switching pipes + * on a channel) + * + * @param chan_obj Channel object + * @return uint32_t Starting PID of the next transfer (DATA0 or DATA1) + */ +static inline uint32_t usbh_hal_chan_get_pid(usbh_hal_chan_t *chan_obj) +{ + assert(!chan_obj->flags.active && !chan_obj->flags.error_pending); + return usbh_ll_chan_get_pid(chan_obj->regs); +} + +/** + * @brief Get a channel's error + * + * @param chan_obj Channel object + * @return usbh_hal_chan_error_t The type of error the channel has encountered + */ +static inline usbh_hal_chan_error_t usbh_hal_chan_get_error(usbh_hal_chan_t *chan_obj) +{ + assert(chan_obj->flags.error_pending); + return chan_obj->error; +} + +/** + * @brief Clear a channel of it's error + * + * @param chan_obj Channel object + */ +static inline void usbh_hal_chan_clear_error(usbh_hal_chan_t *chan_obj) +{ + //Can only clear error when an error has occurred + assert(chan_obj->flags.error_pending); + chan_obj->flags.error_pending = 0; +} + +/* ----------------------------------------------------------------------------- +-------------------------- Transfer Descriptor List ---------------------------- +------------------------------------------------------------------------------*/ + +/** + * @brief Fill a single entry in a transfer descriptor list + * + * - A single entry corresponds to a USB transfer in a particular direction + * (e.g., a BULK OUT). + * - The channel will automatically split the transfer into multiple MPS sized + * packets of the endpoint. + * - For multi direction transfers (such as the various stages of a control transfer), + * the direction and PID of channel must be managed manually. Set the + * USBH_HAL_XFER_DESC_FLAG_HALT flag to halt on each entry to flip the direction + * and PID of the channel. + * - For IN transfer entries, set the USBH_HAL_XFER_DESC_FLAG_IN. The transfer + * size must also be an integer multiple of the endpoint's MPS + * + * @note The USBH_HAL_XFER_DESC_FLAG_HALT must be set on the last descriptor of + * the list so that an interrupt is generated at the end of the list + * @note The USBH_HAL_XFER_DESC_FLAG_HALT can be set on every descriptor if users + * prefer to manually step through the list (such as change EP directions in between) + * @note Critical section is not required for this function + * + * @param xfer_desc_list Transfer descriptor list + * @param xfer_desc_idx Transfer descriptor index + * @param xfer_data_buff Transfer data buffer + * @param xfer_len Transfer length + * @param flags Transfer flags + */ +static inline void usbh_hal_xfer_desc_fill(void *xfer_desc_list, int xfer_desc_idx, uint8_t *xfer_data_buff, int xfer_len, uint32_t flags) +{ + //Check if the channel should be halted on completion of this xfer descriptor + bool halt_on_xfer_cplt = flags & USBH_HAL_XFER_DESC_FLAG_HALT; + usbh_ll_dma_qtd_t *qtd_list = (usbh_ll_dma_qtd_t *)xfer_desc_list; + if (flags & USBH_HAL_XFER_DESC_FLAG_NULL) { + usbh_ll_set_qtd_null(&qtd_list[xfer_desc_idx]); + } else if (flags & USBH_HAL_XFER_DESC_FLAG_IN) { + usbh_ll_set_qtd_in(&qtd_list[xfer_desc_idx], xfer_data_buff, xfer_len, halt_on_xfer_cplt); + } else { + usbh_ll_set_qtd_out(&qtd_list[xfer_desc_idx], xfer_data_buff, xfer_len, halt_on_xfer_cplt, (flags & USBH_HAL_XFER_DESC_FLAG_SETUP)); + } +} + +/** + * @brief Parse a transfer decriptors results + * + * @param xfer_desc_list Transfer descriptor list + * @param xfer_desc_idx Transfer descriptor index + * @param[out] xfer_rem_len Remaining length of the transfer in bytes + * @param[out] xfer_status Status of the transfer + * + * @note Critical section is not required for this function + */ +static inline void usbh_hal_xfer_desc_parse(void *xfer_desc_list, int xfer_desc_idx, int *xfer_rem_len, int *xfer_status) +{ + usbh_ll_dma_qtd_t *qtd_list = (usbh_ll_dma_qtd_t *)xfer_desc_list; + usbh_ll_get_qtd_status(&qtd_list[xfer_desc_idx], xfer_rem_len, xfer_status); +} + +/* ----------------------------------------------------------------------------- +-------------------------------- Channel Slot ---------------------------------- +------------------------------------------------------------------------------*/ + +/** + * @brief Acquire a slot + * + * Acquiring a channel's transfer descriptor list slot will cause a give ownership + * of the channel to the acquirer. The transfer descriptor list to be executed + * when the channel is activated. + * + * @param chan_obj Channel object + * @param xfer_desc_list A filled transfer descriptor list + * @param desc_list_len Length of the descriptor list + * @param owner_ctx Context variable of the owner + */ +static inline void usbh_hal_chan_slot_acquire(usbh_hal_chan_t *chan_obj, void *xfer_desc_list, int desc_list_len, void *owner_ctx) +{ + assert(!chan_obj->slot.flags.slot_acquired); + chan_obj->slot.xfer_desc_list = (usbh_ll_dma_qtd_t *)xfer_desc_list; + chan_obj->slot.owner_ctx = owner_ctx; + chan_obj->slot.flags.cur_qtd_idx = 0; //Start from the first descriptor + chan_obj->slot.flags.qtd_list_len = desc_list_len; + chan_obj->slot.flags.slot_acquired = true; + //Store the descriptor list length in the HCTSIZ register. Address of desc list is set when channel is activated + usbh_ll_chan_set_qtd_list_len(chan_obj->regs, desc_list_len); +} + +/** + * @brief Get current owner of a slot + * + * This function reqturns a slot's context variable that was set when the slot + * was acquired + * + * @param chan_obj Channel object + * @return void* Context variable of the owner of the slot + */ +static inline void *usbh_hal_chan_slot_get_owner(usbh_hal_chan_t *chan_obj) +{ + assert(chan_obj->slot.flags.slot_acquired); + return chan_obj->slot.owner_ctx; +} + +/** + * @brief Release a slot + * + * @note This should only be called after confirming that the transfer descriptor + * list has completed execution. + * @note Users should parse the completed transfer descriptor list to check the + * results of each transfer. + * + * @param[in] chan_obj Channel object + * @param[out] xfer_desc_list A completed transfer descriptor list + * @param[out] desc_list_len Length of the descriptor list + */ +static inline void usbh_hal_chan_slot_release(usbh_hal_chan_t *chan_obj, void **xfer_desc_list, int *desc_list_len) +{ + assert(chan_obj->slot.flags.slot_acquired); + *xfer_desc_list = (void *)chan_obj->slot.xfer_desc_list; + *desc_list_len = chan_obj->slot.flags.qtd_list_len; + chan_obj->slot.flags.slot_acquired = false; +} + +/** + * @brief Activate a channel + * + * Activating a channel will cause it to start executing the transfer descriptor + * list in its slot starting from its next descriptor index. When a transfer + * descriptor completes execution and has the HALT flag set, an event will be + * generated. + * + * @param chan_obj Channel object + * @param num_to_skip Number of transfer descriptors to skip over + */ +void usbh_hal_chan_activate(usbh_hal_chan_t *chan_obj, int num_to_skip); + +/** + * @brief Get next transfer descriptor index + * + * This function returns the index of the next descriptor that will be executed + * in the transfer descriptor list. + * + * @param chan_obj Channel object + * @return int Descriptor index + */ +static inline int usbh_hal_chan_get_next_desc_index(usbh_hal_chan_t *chan_obj) +{ + return chan_obj->slot.flags.cur_qtd_idx; +} + +/** + * @brief Request to halt a channel + * + * This function should be called in order to halt a channel. If the channel is + * already halted, this function will return true. If the channel is still + * active, this function will return false and users must wait for the + * USBH_HAL_CHAN_EVENT_HALT_REQ event before treating the channel as halted. + * + * @param chan_obj Channel object + * @return true The channel is already halted + * @return false The halt was requested, wait for USBH_HAL_CHAN_EVENT_HALT_REQ + */ +bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj); + +/* ----------------------------------------------------------------------------- +-------------------------------- Event Handling -------------------------------- +----------------------------------------------------------------------------- */ + +/** + * @brief Decode global and host port interrupts + * + * - Reads and clears global and host port interrupt registers + * - Decodes the interrupt bits to determine what host port event occurred + * + * @note This should be the first interrupt decode function to be run + * + * @param hal Context of the HAL layer + * @return usbh_hal_port_event_t Host port event + */ +usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal); + +/** + * @brief Gets the next channel with a pending interrupt + * + * If no channel is pending an interrupt, this function will return NULL. If one + * or more channels are pending an interrupt, this function returns one of the + * channel's objects. Call this function repeatedly until it returns NULL. + * + * @note If a channel error event occurs, or a Slot halt/done event occurs, the + * channel is immediately halted and no further channel interrupt or errors + * can occur until it is reactivated. + * + * @param hal Context of the HAL layer + * @return usbh_hal_chan_t* Channel object. NULL if no channel are pending an interrupt. + */ +usbh_hal_chan_t *usbh_hal_get_chan_pending_intr(usbh_hal_context_t *hal); + +/** + * @brief Decode a particular channel's interrupt + * + * - Reads and clears the interrupt register of the channel + * - Returns the corresponding event for that channel + * + * @param chan_obj Channel object + * @return usbh_hal_chan_event_t Channel event + */ +usbh_hal_chan_event_t usbh_hal_chan_decode_intr(usbh_hal_chan_t *chan_obj); + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/esp32s2/include/hal/usbh_ll.h b/components/hal/esp32s2/include/hal/usbh_ll.h index 0d080e5964..aa26cb6fba 100644 --- a/components/hal/esp32s2/include/hal/usbh_ll.h +++ b/components/hal/esp32s2/include/hal/usbh_ll.h @@ -156,7 +156,7 @@ typedef struct { } out_iso; uint32_t buffer_status_val; }; - void *buffer; + uint8_t *buffer; } usbh_ll_dma_qtd_t; /* @@ -268,6 +268,16 @@ static inline void usb_ll_reset_frame_counter(usbh_dev_t *hw) hw->grstctl_reg.frmcntrrst = 1; } +static inline void usb_ll_core_soft_reset(usbh_dev_t *hw) +{ + hw->grstctl_reg.csftrst = 1; +} + +static inline bool usb_ll_check_core_soft_reset(usbh_dev_t *hw) +{ + return hw->grstctl_reg.csftrst; +} + // --------------------------- GINTSTS Register -------------------------------- /** @@ -412,7 +422,7 @@ static inline void usbh_ll_hfir_set_defaults(usbh_dev_t *hw) usb_hfir_reg_t hfir; hfir.val = hw->hfir_reg.val; hfir.hfirrldctrl = 0; //Disable dynamic loading - hfir.frint = 48000; //Set frame interval to 48000 cycles of 48KHz clock (1ms) + hfir.frint = 48000; //Set frame interval to 48000 cycles of 48MHz clock (i.e. equals to 1ms) hw->hfir_reg.val = hfir.val; } @@ -713,6 +723,14 @@ static inline void usbh_ll_chan_set_pid(volatile usb_host_chan_regs_t *chan, uin } } +static inline uint32_t usbh_ll_chan_get_pid(volatile usb_host_chan_regs_t *chan) { + if (chan->hctsiz_reg.pid == 0) { + return 0; //DATA0 + } else { + return 1; //DATA1 + } +} + static inline void usbh_ll_chan_set_dma_addr_non_iso(volatile usb_host_chan_regs_t *chan, void *dmaaddr, uint32_t qtd_idx) @@ -755,15 +773,17 @@ static inline int usbh_ll_chan_get_ctd(usb_host_chan_regs_t *chan) return chan->hcdma_reg.non_iso.ctd; } -static inline void usbh_ll_chan_hctsiz_init(volatile usb_host_chan_regs_t *chan, int qtd_list_len) +static inline void usbh_ll_chan_hctsiz_init(volatile usb_host_chan_regs_t *chan) { - //HCTSIZi - chan->hctsiz_reg.dopng = 0; //Don't do ping - chan->hctsiz_reg.pid = 0; //Reset PID to Data0 - chan->hctsiz_reg.ntd = qtd_list_len - 1; //Set the length of the descriptor list + chan->hctsiz_reg.dopng = 0; //Don't do ping chan->hctsiz_reg.sched_info = 0xFF; //Schedinfo is always 0xFF for fullspeed. Not used in Bulk/Ctrl channels } +static inline void usbh_ll_chan_set_qtd_list_len(volatile usb_host_chan_regs_t *chan, int qtd_list_len) +{ + chan->hctsiz_reg.ntd = qtd_list_len - 1; //Set the length of the descriptor list +} + // ---------------------------- HCDMABi Register ------------------------------- static inline void *usbh_ll_chan_get_cur_buff_addr(volatile usb_host_chan_regs_t *chan) @@ -806,7 +826,7 @@ static inline usb_host_chan_regs_t *usbh_ll_get_chan_regs(usbh_dev_t *dev, int c * Non zero length must be mulitple of the endpoint's MPS. * @param halt_on_cplt Generate a channel halted interrupt on completion of QTD */ -static inline void usbh_ll_set_qtd_in(usbh_ll_dma_qtd_t *qtd, void *data_buff, int xfer_len, bool halt_on_cplt) +static inline void usbh_ll_set_qtd_in(usbh_ll_dma_qtd_t *qtd, uint8_t *data_buff, int xfer_len, bool halt_on_cplt) { qtd->buffer = data_buff; //Set pointer to data buffer qtd->buffer_status_val = 0; //Reset all flags to zero @@ -829,7 +849,7 @@ static inline void usbh_ll_set_qtd_in(usbh_ll_dma_qtd_t *qtd, void *data_buff, i * @param is_setup Indicates whether this is a control transfer setup packet or a normal OUT Data transfer. * (As per the USB protocol, setup packets cannot be STALLd or NAKd by the device) */ -static inline void usbh_ll_set_qtd_out(usbh_ll_dma_qtd_t *qtd, void *data_buff, int xfer_len, bool halt_on_cplt, bool is_setup) +static inline void usbh_ll_set_qtd_out(usbh_ll_dma_qtd_t *qtd, uint8_t *data_buff, int xfer_len, bool halt_on_cplt, bool is_setup) { qtd->buffer = data_buff; //Set pointer to data buffer qtd->buffer_status_val = 0; //Reset all flags to zero diff --git a/components/hal/esp32s2/usbh_hal.c b/components/hal/esp32s2/usbh_hal.c new file mode 100644 index 0000000000..98f1ee92c8 --- /dev/null +++ b/components/hal/esp32s2/usbh_hal.c @@ -0,0 +1,400 @@ +// 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 +#include +#include +#include +#include "hal/usbh_hal.h" +#include "hal/usbh_ll.h" + +/* ----------------------------------------------------------------------------- +------------------------------- Macros and Types ------------------------------- +----------------------------------------------------------------------------- */ + +// -------------------------------- Constants ---------------------------------- + +#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_AHBERR + * - USBH_LL_INTR_CHAN_STALL + * - USBH_LL_INTR_CHAN_BBLEER + * - USBH_LL_INTR_CHAN_BNAINTR + * - USBH_LL_INTR_CHAN_XCS_XACT_ERR + * + * - 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 + */ +#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_AHBERR | \ + 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; + uint32_t core_id = usb_ll_get_controller_core_id(dev); + assert(core_id == CORE_REG_GSNPSID); + //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 + hal->flags.val = 0; +} + +/* ----------------------------------------------------------------------------- +---------------------------------- 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) +{ + //Host Configuration + usbh_ll_hcfg_set_defaults(hal->dev); + //Todo: Set frame list entries and ena per sched + //Configure HFIR + usbh_ll_hfir_set_defaults(hal->dev); + //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 & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK, + ep_char->mps, + ep_char->type, + ep_char->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK); +} + +/* ----------------------------------------------------------------------------- +------------------------------- 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_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)) { + usbh_ll_chan_halt(chan_obj->regs); + chan_obj->flags.halt_requested = 1; + return false; + } + 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 precendance. ENABLED < DISABLED < CONN < DISCONN < OVRCUR + usbh_hal_port_event_t event = -1; + + //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); + } + } + if (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 + assert(chan_intrs & USBH_LL_INTR_CHAN_CHHLTD); + 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_AHBERR) { + error = USBH_HAL_CHAN_ERROR_AHB; + } else 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 { + //Channel halted suddenly (i.e,, a disconnect) + chan_event = USBH_HAL_CHAN_EVENT_SUDDEN_HLT; + } + return chan_event; +}