esp-idf/components/hal/esp32s2/usbh_hal.c

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