esp-idf/components/hal/esp32s2/usbh_hal.c
Darian Leung 2906a25988 Separate USB HAL and common USB types
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.
2021-04-22 19:24:48 +08:00

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;
}