Merge branch 'refactor/make_usb_hal_independent_backport_v6.0' into 'release/v6.0'

refactor(usb): Make usb hal layer independent (backport v6.0)

See merge request espressif/esp-idf!43249
This commit is contained in:
morris
2025-11-13 10:20:10 +08:00
29 changed files with 167 additions and 115 deletions
-10
View File
@@ -235,16 +235,6 @@ elseif(NOT BOOTLOADER_BUILD)
list(APPEND srcs "usb_serial_jtag_hal.c")
endif()
if(CONFIG_SOC_USB_UTMI_PHY_NUM GREATER 0)
list(APPEND srcs "usb_utmi_hal.c")
endif()
if(CONFIG_SOC_USB_OTG_SUPPORTED)
list(APPEND srcs
"usb_dwc_hal.c"
"usb_wrap_hal.c")
endif()
if(CONFIG_SOC_TOUCH_SENSOR_SUPPORTED)
# Source files for the legacy touch hal driver
if(CONFIG_SOC_TOUCH_SENSOR_VERSION LESS 3)
@@ -1,983 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "soc/usb_dwc_struct.h"
#include "soc/usb_dwc_cfg.h"
#include "hal/usb_dwc_types.h"
#include "hal/misc.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ----------------------------- Helper Macros ------------------------------ */
// Get USB hardware instance
#define USB_DWC_LL_GET_HW(num) (&USB_DWC)
/* -----------------------------------------------------------------------------
--------------------------------- DWC Constants --------------------------------
----------------------------------------------------------------------------- */
#define USB_DWC_QTD_LIST_MEM_ALIGN 512
#define USB_DWC_FRAME_LIST_MEM_ALIGN 512 // The frame list needs to be 512 bytes aligned (contrary to the databook)
/* -----------------------------------------------------------------------------
------------------------------- Global Registers -------------------------------
----------------------------------------------------------------------------- */
/*
* Interrupt bit masks of the GINTSTS and GINTMSK registers
*/
#define USB_DWC_LL_INTR_CORE_WKUPINT (1 << 31)
#define USB_DWC_LL_INTR_CORE_SESSREQINT (1 << 30)
#define USB_DWC_LL_INTR_CORE_DISCONNINT (1 << 29)
#define USB_DWC_LL_INTR_CORE_CONIDSTSCHNG (1 << 28)
#define USB_DWC_LL_INTR_CORE_PTXFEMP (1 << 26)
#define USB_DWC_LL_INTR_CORE_HCHINT (1 << 25)
#define USB_DWC_LL_INTR_CORE_PRTINT (1 << 24)
#define USB_DWC_LL_INTR_CORE_RESETDET (1 << 23)
#define USB_DWC_LL_INTR_CORE_FETSUSP (1 << 22)
#define USB_DWC_LL_INTR_CORE_INCOMPIP (1 << 21)
#define USB_DWC_LL_INTR_CORE_INCOMPISOIN (1 << 20)
#define USB_DWC_LL_INTR_CORE_OEPINT (1 << 19)
#define USB_DWC_LL_INTR_CORE_IEPINT (1 << 18)
#define USB_DWC_LL_INTR_CORE_EPMIS (1 << 17)
#define USB_DWC_LL_INTR_CORE_EOPF (1 << 15)
#define USB_DWC_LL_INTR_CORE_ISOOUTDROP (1 << 14)
#define USB_DWC_LL_INTR_CORE_ENUMDONE (1 << 13)
#define USB_DWC_LL_INTR_CORE_USBRST (1 << 12)
#define USB_DWC_LL_INTR_CORE_USBSUSP (1 << 11)
#define USB_DWC_LL_INTR_CORE_ERLYSUSP (1 << 10)
#define USB_DWC_LL_INTR_CORE_GOUTNAKEFF (1 << 7)
#define USB_DWC_LL_INTR_CORE_GINNAKEFF (1 << 6)
#define USB_DWC_LL_INTR_CORE_NPTXFEMP (1 << 5)
#define USB_DWC_LL_INTR_CORE_RXFLVL (1 << 4)
#define USB_DWC_LL_INTR_CORE_SOF (1 << 3)
#define USB_DWC_LL_INTR_CORE_OTGINT (1 << 2)
#define USB_DWC_LL_INTR_CORE_MODEMIS (1 << 1)
#define USB_DWC_LL_INTR_CORE_CURMOD (1 << 0)
/*
* Bit mask of interrupt generating bits of the the HPRT register. These bits
* are ORd into the USB_DWC_LL_INTR_CORE_PRTINT interrupt.
*
* Note: Some fields of the HPRT are W1C (write 1 clear), this we cannot do a
* simple read and write-back to clear the HPRT interrupt bits. Instead we need
* a W1C mask the non-interrupt related bits
*/
#define USB_DWC_LL_HPRT_W1C_MSK (0x2E)
#define USB_DWC_LL_HPRT_ENA_MSK (0x04)
#define USB_DWC_LL_INTR_HPRT_PRTOVRCURRCHNG (1 << 5)
#define USB_DWC_LL_INTR_HPRT_PRTENCHNG (1 << 3)
#define USB_DWC_LL_INTR_HPRT_PRTCONNDET (1 << 1)
/*
* Bit mask of channel interrupts (HCINTi and HCINTMSKi registers)
*
* Note: Under Scatter/Gather DMA mode, only the following interrupts can be unmasked
* - DESC_LS_ROLL
* - XCS_XACT_ERR (always unmasked)
* - BNAINTR
* - CHHLTD
* - XFERCOMPL
* The remaining interrupt bits will still be set (when the corresponding event occurs)
* but will not generate an interrupt. Therefore we must proxy through the
* USB_DWC_LL_INTR_CHAN_CHHLTD interrupt to check the other interrupt bits.
*/
#define USB_DWC_LL_INTR_CHAN_DESC_LS_ROLL (1 << 13)
#define USB_DWC_LL_INTR_CHAN_XCS_XACT_ERR (1 << 12)
#define USB_DWC_LL_INTR_CHAN_BNAINTR (1 << 11)
#define USB_DWC_LL_INTR_CHAN_DATATGLERR (1 << 10)
#define USB_DWC_LL_INTR_CHAN_FRMOVRUN (1 << 9)
#define USB_DWC_LL_INTR_CHAN_BBLEER (1 << 8)
#define USB_DWC_LL_INTR_CHAN_XACTERR (1 << 7)
#define USB_DWC_LL_INTR_CHAN_NYET (1 << 6)
#define USB_DWC_LL_INTR_CHAN_ACK (1 << 5)
#define USB_DWC_LL_INTR_CHAN_NAK (1 << 4)
#define USB_DWC_LL_INTR_CHAN_STALL (1 << 3)
#define USB_DWC_LL_INTR_CHAN_AHBERR (1 << 2)
#define USB_DWC_LL_INTR_CHAN_CHHLTD (1 << 1)
#define USB_DWC_LL_INTR_CHAN_XFERCOMPL (1 << 0)
/*
* QTD (Queue Transfer Descriptor) structure used in Scatter/Gather DMA mode.
* Each QTD describes one transfer. Scatter gather mode will automatically split
* a transfer into multiple MPS packets. Each QTD is 64bits in size
*
* Note: The status information part of the QTD is interpreted differently depending
* on IN or OUT, and ISO or non-ISO
*/
typedef struct {
union {
struct {
uint32_t xfer_size: 17;
uint32_t aqtd_offset: 6;
uint32_t aqtd_valid: 1;
uint32_t reserved_24: 1;
uint32_t intr_cplt: 1;
uint32_t eol: 1;
uint32_t reserved_27: 1;
uint32_t rx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} in_non_iso;
struct {
uint32_t xfer_size: 12;
uint32_t reserved_12_24: 13;
uint32_t intr_cplt: 1;
uint32_t reserved_26_27: 2;
uint32_t rx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} in_iso;
struct {
uint32_t xfer_size: 17;
uint32_t reserved_17_23: 7;
uint32_t is_setup: 1;
uint32_t intr_cplt: 1;
uint32_t eol: 1;
uint32_t reserved_27: 1;
uint32_t tx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} out_non_iso;
struct {
uint32_t xfer_size: 12;
uint32_t reserved_12_24: 13;
uint32_t intr_cplt: 1;
uint32_t eol: 1;
uint32_t reserved_27: 1;
uint32_t tx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} out_iso;
uint32_t buffer_status_val;
};
uint8_t *buffer;
} usb_dwc_ll_dma_qtd_t;
/* -----------------------------------------------------------------------------
------------------------------- Global Registers -------------------------------
----------------------------------------------------------------------------- */
// --------------------------- GAHBCFG Register --------------------------------
static inline void usb_dwc_ll_gahbcfg_en_dma_mode(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.dmaen = 1;
}
static inline void usb_dwc_ll_gahbcfg_en_slave_mode(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.dmaen = 0;
}
static inline void usb_dwc_ll_gahbcfg_set_hbstlen(usb_dwc_dev_t *hw, uint32_t burst_len)
{
hw->gahbcfg_reg.hbstlen = burst_len;
}
static inline void usb_dwc_ll_gahbcfg_en_global_intr(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.glbllntrmsk = 1;
}
static inline void usb_dwc_ll_gahbcfg_dis_global_intr(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.glbllntrmsk = 0;
}
// --------------------------- GUSBCFG Register --------------------------------
static inline void usb_dwc_ll_gusbcfg_force_host_mode(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.forcehstmode = 1;
}
static inline void usb_dwc_ll_gusbcfg_dis_hnp_cap(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.hnpcap = 0;
}
static inline void usb_dwc_ll_gusbcfg_dis_srp_cap(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.srpcap = 0;
}
static inline void usb_dwc_ll_gusbcfg_set_timeout_cal(usb_dwc_dev_t *hw, uint8_t tout_cal)
{
hw->gusbcfg_reg.toutcal = tout_cal;
}
static inline void usb_dwc_ll_gusbcfg_set_utmi_phy(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.phyif = 1; // 16 bits interface
hw->gusbcfg_reg.ulpiutmisel = 0; // UTMI+
hw->gusbcfg_reg.physel = 0; // HS PHY
}
// --------------------------- GRSTCTL Register --------------------------------
static inline bool usb_dwc_ll_grstctl_is_ahb_idle(usb_dwc_dev_t *hw)
{
return hw->grstctl_reg.ahbidle;
}
static inline bool usb_dwc_ll_grstctl_is_dma_req_in_progress(usb_dwc_dev_t *hw)
{
return hw->grstctl_reg.dmareq;
}
static inline void usb_dwc_ll_grstctl_flush_nptx_fifo(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.txfnum = 0; //Set the TX FIFO number to 0 to select the non-periodic TX FIFO
hw->grstctl_reg.txfflsh = 1; //Flush the selected TX FIFO
//Wait for the flushing to complete
while (hw->grstctl_reg.txfflsh) {
;
}
}
static inline void usb_dwc_ll_grstctl_flush_ptx_fifo(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.txfnum = 1; //Set the TX FIFO number to 1 to select the periodic TX FIFO
hw->grstctl_reg.txfflsh = 1; //FLush the select TX FIFO
//Wait for the flushing to complete
while (hw->grstctl_reg.txfflsh) {
;
}
}
static inline void usb_dwc_ll_grstctl_flush_rx_fifo(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.rxfflsh = 1;
//Wait for the flushing to complete
while (hw->grstctl_reg.rxfflsh) {
;
}
}
static inline void usb_dwc_ll_grstctl_reset_frame_counter(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.frmcntrrst = 1;
}
static inline void usb_dwc_ll_grstctl_core_soft_reset(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.csftrst = 1;
}
static inline bool usb_dwc_ll_grstctl_is_core_soft_reset_in_progress(usb_dwc_dev_t *hw)
{
return hw->grstctl_reg.csftrst;
}
// --------------------------- GINTSTS Register --------------------------------
/**
* @brief Reads and clears the global interrupt register
*
* @param hw Start address of the DWC_OTG registers
* @return uint32_t Mask of interrupts
*/
static inline uint32_t usb_dwc_ll_gintsts_read_and_clear_intrs(usb_dwc_dev_t *hw)
{
usb_dwc_gintsts_reg_t gintsts;
gintsts.val = hw->gintsts_reg.val;
hw->gintsts_reg.val = gintsts.val; //Write back to clear
return gintsts.val;
}
/**
* @brief Clear specific interrupts
*
* @param hw Start address of the DWC_OTG registers
* @param intr_msk Mask of interrupts to clear
*/
static inline void usb_dwc_ll_gintsts_clear_intrs(usb_dwc_dev_t *hw, uint32_t intr_msk)
{
//All GINTSTS fields are either W1C or read only. So safe to write directly
hw->gintsts_reg.val = intr_msk;
}
// --------------------------- GINTMSK Register --------------------------------
static inline void usb_dwc_ll_gintmsk_en_intrs(usb_dwc_dev_t *hw, uint32_t intr_mask)
{
hw->gintmsk_reg.val |= intr_mask;
}
static inline void usb_dwc_ll_gintmsk_dis_intrs(usb_dwc_dev_t *hw, uint32_t intr_mask)
{
hw->gintmsk_reg.val &= ~intr_mask;
}
// --------------------------- GRXFSIZ Register --------------------------------
static inline void usb_dwc_ll_grxfsiz_set_fifo_size(usb_dwc_dev_t *hw, uint32_t num_lines)
{
//Set size in words
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->grxfsiz_reg, rxfdep, num_lines);
}
// -------------------------- GNPTXFSIZ Register -------------------------------
static inline void usb_dwc_ll_gnptxfsiz_set_fifo_size(usb_dwc_dev_t *hw, uint32_t addr, uint32_t num_lines)
{
usb_dwc_gnptxfsiz_reg_t gnptxfsiz;
gnptxfsiz.val = hw->gnptxfsiz_reg.val;
HAL_FORCE_MODIFY_U32_REG_FIELD(gnptxfsiz, nptxfstaddr, addr);
HAL_FORCE_MODIFY_U32_REG_FIELD(gnptxfsiz, nptxfdep, num_lines);
hw->gnptxfsiz_reg.val = gnptxfsiz.val;
}
// --------------------------- GSNPSID Register --------------------------------
static inline uint32_t usb_dwc_ll_gsnpsid_get_id(usb_dwc_dev_t *hw)
{
return hw->gsnpsid_reg.val;
}
// --------------------------- GHWCFGx Register --------------------------------
static inline unsigned usb_dwc_ll_ghwcfg_get_fifo_depth(usb_dwc_dev_t *hw)
{
return hw->ghwcfg3_reg.dfifodepth;
}
static inline unsigned usb_dwc_ll_ghwcfg_get_hsphy_type(usb_dwc_dev_t *hw)
{
return hw->ghwcfg2_reg.hsphytype;
}
static inline unsigned usb_dwc_ll_ghwcfg_get_channel_num(usb_dwc_dev_t *hw)
{
return hw->ghwcfg2_reg.numhstchnl + 1;
}
// --------------------------- HPTXFSIZ Register -------------------------------
static inline void usb_dwc_ll_hptxfsiz_set_ptx_fifo_size(usb_dwc_dev_t *hw, uint32_t addr, uint32_t num_lines)
{
usb_dwc_hptxfsiz_reg_t hptxfsiz;
hptxfsiz.val = hw->hptxfsiz_reg.val;
HAL_FORCE_MODIFY_U32_REG_FIELD(hptxfsiz, ptxfstaddr, addr);
HAL_FORCE_MODIFY_U32_REG_FIELD(hptxfsiz, ptxfsize, num_lines);
hw->hptxfsiz_reg.val = hptxfsiz.val;
}
/* -----------------------------------------------------------------------------
-------------------------------- Host Registers --------------------------------
----------------------------------------------------------------------------- */
// ----------------------------- HCFG Register ---------------------------------
static inline void usb_dwc_ll_hcfg_en_perio_sched(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.perschedena = 1;
}
static inline void usb_dwc_ll_hcfg_dis_perio_sched(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.perschedena = 0;
}
/**
* Sets the length of the frame list
*
* @param num_entires Number of entries in the frame list
*/
static inline void usb_dwc_ll_hcfg_set_num_frame_list_entries(usb_dwc_dev_t *hw, usb_hal_frame_list_len_t num_entries)
{
uint32_t frlisten;
switch (num_entries) {
case USB_HAL_FRAME_LIST_LEN_8:
frlisten = 0;
break;
case USB_HAL_FRAME_LIST_LEN_16:
frlisten = 1;
break;
case USB_HAL_FRAME_LIST_LEN_32:
frlisten = 2;
break;
default: //USB_HAL_FRAME_LIST_LEN_64
frlisten = 3;
break;
}
hw->hcfg_reg.frlisten = frlisten;
}
static inline void usb_dwc_ll_hcfg_en_scatt_gatt_dma(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.descdma = 1;
}
static inline void usb_dwc_ll_hcfg_set_fsls_supp_only(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.fslssupp = 1;
}
/**
* @brief Set FSLS PHY clock
*
* @attention This function should only be called if FSLS PHY is selected
* @param[in] hw Start address of the DWC_OTG registers
*/
static inline void usb_dwc_ll_hcfg_set_fsls_phy_clock(usb_dwc_dev_t *hw)
{
/*
Indicate to the OTG core what speed the PHY clock is at
Note: FSLS PHY has an implicit 8 divider applied when in LS mode,
so the values of FSLSPclkSel and FrInt have to be adjusted accordingly.
*/
usb_dwc_speed_t speed = (usb_dwc_speed_t)hw->hprt_reg.prtspd;
hw->hcfg_reg.fslspclksel = (speed == USB_DWC_SPEED_FULL) ? 1 : 2;
}
// ----------------------------- HFIR Register ---------------------------------
/**
* @brief Set Frame Interval
*
* @attention This function should only be called if FSLS PHY is selected
* @param[in] hw Start address of the DWC_OTG registers
*/
static inline void usb_dwc_ll_hfir_set_frame_interval(usb_dwc_dev_t *hw)
{
usb_dwc_hfir_reg_t hfir;
hfir.val = hw->hfir_reg.val;
hfir.hfirrldctrl = 0; // Disable dynamic loading
/*
Set frame interval to be equal to 1ms
Note: FSLS PHY has an implicit 8 divider applied when in LS mode,
so the values of FSLSPclkSel and FrInt have to be adjusted accordingly.
*/
usb_dwc_speed_t speed = (usb_dwc_speed_t)hw->hprt_reg.prtspd;
hfir.frint = (speed == USB_DWC_SPEED_FULL) ? 48000 : 6000;
hw->hfir_reg.val = hfir.val;
}
// ----------------------------- HFNUM Register --------------------------------
static inline uint32_t usb_dwc_ll_hfnum_get_frame_time_rem(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->hfnum_reg, frrem);
}
static inline uint32_t usb_dwc_ll_hfnum_get_frame_num(usb_dwc_dev_t *hw)
{
return hw->hfnum_reg.frnum;
}
// ---------------------------- HPTXSTS Register -------------------------------
static inline uint32_t usb_dwc_ll_hptxsts_get_ptxq_top(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->hptxsts_reg, ptxqtop);
}
static inline uint32_t usb_dwc_ll_hptxsts_get_ptxq_space_avail(usb_dwc_dev_t *hw)
{
return hw->hptxsts_reg.ptxqspcavail;
}
static inline uint32_t usb_dwc_ll_ptxsts_get_ptxf_space_avail(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->hptxsts_reg, ptxfspcavail);
}
// ----------------------------- HAINT Register --------------------------------
static inline uint32_t usb_dwc_ll_haint_get_chan_intrs(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->haint_reg, haint);
}
// --------------------------- HAINTMSK Register -------------------------------
static inline void usb_dwc_ll_haintmsk_en_chan_intr(usb_dwc_dev_t *hw, uint32_t mask)
{
hw->haintmsk_reg.val |= mask;
}
static inline void usb_dwc_ll_haintmsk_dis_chan_intr(usb_dwc_dev_t *hw, uint32_t mask)
{
hw->haintmsk_reg.val &= ~mask;
}
// --------------------------- HFLBAddr Register -------------------------------
/**
* @brief Set the base address of the scheduling frame list
*
* @note For some reason, this address must be 512 bytes aligned or else a bunch of frames will not be scheduled when
* the frame list rolls over. However, according to the databook, there is no mention of the HFLBAddr needing to
* be aligned.
*
* @param hw Start address of the DWC_OTG registers
* @param addr Base address of the scheduling frame list
*/
static inline void usb_dwc_ll_hflbaddr_set_base_addr(usb_dwc_dev_t *hw, uint32_t addr)
{
hw->hflbaddr_reg.hflbaddr = addr;
}
/**
* @brief Get the base address of the scheduling frame list
*
* @param hw Start address of the DWC_OTG registers
* @return uint32_t Base address of the scheduling frame list
*/
static inline uint32_t usb_dwc_ll_hflbaddr_get_base_addr(usb_dwc_dev_t *hw)
{
return hw->hflbaddr_reg.hflbaddr;
}
// ----------------------------- HPRT Register ---------------------------------
static inline usb_dwc_speed_t usb_dwc_ll_hprt_get_speed(usb_dwc_dev_t *hw)
{
return (usb_dwc_speed_t)hw->hprt_reg.prtspd;
}
static inline uint32_t usb_dwc_ll_hprt_get_test_ctl(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prttstctl;
}
static inline void usb_dwc_ll_hprt_set_test_ctl(usb_dwc_dev_t *hw, uint32_t test_mode)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prttstctl = test_mode;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline void usb_dwc_ll_hprt_en_pwr(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtpwr = 1;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline void usb_dwc_ll_hprt_dis_pwr(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtpwr = 0;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline uint32_t usb_dwc_ll_hprt_get_pwr_line_status(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtlnsts;
}
static inline void usb_dwc_ll_hprt_set_port_reset(usb_dwc_dev_t *hw, bool reset)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtrst = reset;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline bool usb_dwc_ll_hprt_get_port_reset(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtrst;
}
static inline void usb_dwc_ll_hprt_set_port_suspend(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtsusp = 1;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline bool usb_dwc_ll_hprt_get_port_suspend(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtsusp;
}
static inline void usb_dwc_ll_hprt_set_port_resume(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtres = 1;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline void usb_dwc_ll_hprt_clr_port_resume(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtres = 0;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline bool usb_dwc_ll_hprt_get_port_resume(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtres;
}
static inline bool usb_dwc_ll_hprt_get_port_overcur(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtovrcurract;
}
static inline bool usb_dwc_ll_hprt_get_port_en(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtena;
}
static inline void usb_dwc_ll_hprt_port_dis(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtena = 1; //W1C to disable
//we want to W1C ENA but not W1C the interrupt bits
hw->hprt_reg.val = hprt.val & ((~USB_DWC_LL_HPRT_W1C_MSK) | USB_DWC_LL_HPRT_ENA_MSK);
}
static inline bool usb_dwc_ll_hprt_get_conn_status(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtconnsts;
}
static inline uint32_t usb_dwc_ll_hprt_intr_read_and_clear(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
//We want to W1C the interrupt bits but not that ENA
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_ENA_MSK);
//Return only the interrupt bits
return (hprt.val & (USB_DWC_LL_HPRT_W1C_MSK & ~(USB_DWC_LL_HPRT_ENA_MSK)));
}
static inline void usb_dwc_ll_hprt_intr_clear(usb_dwc_dev_t *hw, uint32_t intr_mask)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hw->hprt_reg.val = ((hprt.val & ~USB_DWC_LL_HPRT_ENA_MSK) & ~USB_DWC_LL_HPRT_W1C_MSK) | intr_mask;
}
//Per Channel registers
// --------------------------- HCCHARi Register --------------------------------
static inline void usb_dwc_ll_hcchar_enable_chan(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.chena = 1;
}
static inline bool usb_dwc_ll_hcchar_chan_is_enabled(volatile usb_dwc_host_chan_regs_t *chan)
{
return chan->hcchar_reg.chena;
}
static inline void usb_dwc_ll_hcchar_disable_chan(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.chdis = 1;
}
static inline void usb_dwc_ll_hcchar_set_odd_frame(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.oddfrm = 1;
}
static inline void usb_dwc_ll_hcchar_set_even_frame(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.oddfrm = 0;
}
static inline void usb_dwc_ll_hcchar_set_dev_addr(volatile usb_dwc_host_chan_regs_t *chan, uint32_t addr)
{
chan->hcchar_reg.devaddr = addr;
}
static inline void usb_dwc_ll_hcchar_set_ep_type(volatile usb_dwc_host_chan_regs_t *chan, usb_dwc_xfer_type_t type)
{
chan->hcchar_reg.eptype = (uint32_t)type;
}
//Indicates whether channel is commuunicating with a LS device connected via a FS hub. Setting this bit to 1 will cause
//each packet to be preceded by a PREamble packet
static inline void usb_dwc_ll_hcchar_set_lspddev(volatile usb_dwc_host_chan_regs_t *chan, bool is_ls)
{
chan->hcchar_reg.lspddev = is_ls;
}
static inline void usb_dwc_ll_hcchar_set_dir(volatile usb_dwc_host_chan_regs_t *chan, bool is_in)
{
chan->hcchar_reg.epdir = is_in;
}
static inline void usb_dwc_ll_hcchar_set_ep_num(volatile usb_dwc_host_chan_regs_t *chan, uint32_t num)
{
chan->hcchar_reg.epnum = num;
}
static inline void usb_dwc_ll_hcchar_set_mps(volatile usb_dwc_host_chan_regs_t *chan, uint32_t mps)
{
chan->hcchar_reg.mps = mps;
}
static inline void usb_dwc_ll_hcchar_init(volatile usb_dwc_host_chan_regs_t *chan, int dev_addr, int ep_num, int mps, usb_dwc_xfer_type_t type, bool is_in, bool is_ls)
{
//Sets all persistent fields of the channel over its lifetimez
usb_dwc_ll_hcchar_set_dev_addr(chan, dev_addr);
usb_dwc_ll_hcchar_set_ep_type(chan, type);
usb_dwc_ll_hcchar_set_lspddev(chan, is_ls);
usb_dwc_ll_hcchar_set_dir(chan, is_in);
usb_dwc_ll_hcchar_set_ep_num(chan, ep_num);
usb_dwc_ll_hcchar_set_mps(chan, mps);
}
// ---------------------------- HCINTi Register --------------------------------
static inline uint32_t usb_dwc_ll_hcint_read_and_clear_intrs(volatile usb_dwc_host_chan_regs_t *chan)
{
usb_dwc_hcint_reg_t hcint;
hcint.val = chan->hcint_reg.val;
chan->hcint_reg.val = hcint.val;
return hcint.val;
}
// --------------------------- HCINTMSKi Register ------------------------------
static inline void usb_dwc_ll_hcintmsk_set_intr_mask(volatile usb_dwc_host_chan_regs_t *chan, uint32_t mask)
{
chan->hcintmsk_reg.val = mask;
}
// ---------------------------- HCTSIZi Register -------------------------------
static inline void usb_dwc_ll_hctsiz_init(volatile usb_dwc_host_chan_regs_t *chan)
{
usb_dwc_hctsiz_reg_t hctsiz;
hctsiz.val = chan->hctsiz_reg.val;
hctsiz.dopng = 0; // Don't do ping
hctsiz.pid = 0; // Set PID to DATA0
/*
* Set SCHED_INFO which occupies xfersize[7:0]
*
* Although the hardware documentation suggests that SCHED_INFO is only used for periodic channels,
* empirical evidence shows that omitting this configuration on non-periodic channels can cause them to freeze.
* Therefore, we set this field for all channels to ensure reliable operation.
*/
hctsiz.xfersize |= 0xFF;
chan->hctsiz_reg.val = hctsiz.val;
}
static inline void usb_dwc_ll_hctsiz_set_pid(volatile usb_dwc_host_chan_regs_t *chan, uint32_t data_pid)
{
if (data_pid == 0) {
chan->hctsiz_reg.pid = 0;
} else {
chan->hctsiz_reg.pid = 2;
}
}
static inline uint32_t usb_dwc_ll_hctsiz_get_pid(volatile usb_dwc_host_chan_regs_t *chan)
{
if (chan->hctsiz_reg.pid == 0) {
return 0; //DATA0
} else {
return 1; //DATA1
}
}
static inline void usb_dwc_ll_hctsiz_set_qtd_list_len(volatile usb_dwc_host_chan_regs_t *chan, int qtd_list_len)
{
usb_dwc_hctsiz_reg_t hctsiz;
hctsiz.val = chan->hctsiz_reg.val;
//Set the length of the descriptor list. NTD occupies xfersize[15:8]
hctsiz.xfersize &= ~(0xFF << 8);
hctsiz.xfersize |= ((qtd_list_len - 1) & 0xFF) << 8;
chan->hctsiz_reg.val = hctsiz.val;
}
/**
* @brief Perform PING protocol
*
* @note This function is here only for compatibility reasons. PING is not relevant on FS only targets
* @param[in] chan Channel registers
* @param[in] enable true: Enable PING, false: Disable PING
*/
static inline void usb_dwc_ll_hctsiz_set_dopng(volatile usb_dwc_host_chan_regs_t *chan, bool enable)
{
}
/**
* @brief Set scheduling info for Periodic channel
*
* @note ESP32-H4 is Full-Speed only, so SCHED_INFO is always set to 0xFF
* @attention This function must be called for each periodic channel!
* @see USB-OTG databook: Table 5-47
*
* @param[in] chan Channel registers
* @param[in] tokens_per_frame Ignored
* @param[in] offset Ignored
*/
static inline void usb_dwc_ll_hctsiz_set_sched_info(volatile usb_dwc_host_chan_regs_t *chan, int tokens_per_frame, int offset)
{
usb_dwc_hctsiz_reg_t hctsiz;
hctsiz.val = chan->hctsiz_reg.val;
hctsiz.xfersize |= 0xFF;
chan->hctsiz_reg.val = hctsiz.val;
}
// ---------------------------- HCDMAi Register --------------------------------
static inline void usb_dwc_ll_hcdma_set_qtd_list_addr(volatile usb_dwc_host_chan_regs_t *chan, void *dmaaddr, uint32_t qtd_idx)
{
usb_dwc_hcdma_reg_t hcdma;
/*
Set the base address portion of the field which is dmaaddr[31:9]. This is
the based address of the QTD list and must be 512 bytes aligned
*/
hcdma.dmaaddr = ((uint32_t)dmaaddr) & 0xFFFFFE00;
//Set the current QTD index in the QTD list which is dmaaddr[8:3]
hcdma.dmaaddr |= (qtd_idx & 0x3F) << 3;
//dmaaddr[2:0] is reserved thus doesn't not need to be set
chan->hcdma_reg.val = hcdma.val;
}
static inline int usb_dwc_ll_hcdam_get_cur_qtd_idx(usb_dwc_host_chan_regs_t *chan)
{
//The current QTD index is dmaaddr[8:3]
return (chan->hcdma_reg.dmaaddr >> 3) & 0x3F;
}
// ---------------------------- HCDMABi Register -------------------------------
static inline void *usb_dwc_ll_hcdmab_get_buff_addr(volatile usb_dwc_host_chan_regs_t *chan)
{
return (void *)chan->hcdmab_reg.hcdmab;
}
/* -----------------------------------------------------------------------------
---------------------------- Scatter/Gather DMA QTDs ---------------------------
----------------------------------------------------------------------------- */
// ---------------------------- Helper Functions -------------------------------
/**
* @brief Get the base address of a channel's register based on the channel's index
*
* @param dev Start address of the DWC_OTG registers
* @param chan_idx The channel's index
* @return usb_dwc_host_chan_regs_t* Pointer to channel's registers
*/
static inline usb_dwc_host_chan_regs_t *usb_dwc_ll_chan_get_regs(usb_dwc_dev_t *dev, int chan_idx)
{
return &dev->host_chans[chan_idx];
}
// ------------------------------ QTD related ----------------------------------
#define USB_DWC_LL_QTD_STATUS_SUCCESS 0x0 //If QTD was processed, it indicates the data was transmitted/received successfully
#define USB_DWC_LL_QTD_STATUS_PKTERR 0x1 //Data transmitted/received with errors (CRC/Timeout/Stuff/False EOP/Excessive NAK).
//Note: 0x2 is reserved
#define USB_DWC_LL_QTD_STATUS_BUFFER 0x3 //AHB error occurred.
#define USB_DWC_LL_QTD_STATUS_NOT_EXECUTED 0x4 //QTD as never processed
/**
* @brief Set a QTD for a non isochronous IN transfer
*
* @param qtd Pointer to the QTD
* @param data_buff Pointer to buffer containing the data to transfer
* @param xfer_len Number of bytes in transfer. Setting 0 will do a zero length IN transfer.
* Non zero length must be multiple of the endpoint's MPS.
* @param hoc Halt on complete (will generate an interrupt and halt the channel)
*/
static inline void usb_dwc_ll_qtd_set_in(usb_dwc_ll_dma_qtd_t *qtd, uint8_t *data_buff, int xfer_len, bool hoc)
{
qtd->buffer = data_buff; //Set pointer to data buffer
qtd->buffer_status_val = 0; //Reset all flags to zero
qtd->in_non_iso.xfer_size = xfer_len;
if (hoc) {
qtd->in_non_iso.intr_cplt = 1; //We need to set this to distinguish between a halt due to a QTD
qtd->in_non_iso.eol = 1; //Used to halt the channel at this qtd
}
qtd->in_non_iso.active = 1;
}
/**
* @brief Set a QTD for a non isochronous OUT transfer
*
* @param qtd Pointer to the QTD
* @param data_buff Pointer to buffer containing the data to transfer
* @param xfer_len Number of bytes to transfer. Setting 0 will do a zero length transfer.
* For ctrl setup packets, this should be set to 8.
* @param hoc Halt on complete (will generate an interrupt)
* @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 usb_dwc_ll_qtd_set_out(usb_dwc_ll_dma_qtd_t *qtd, uint8_t *data_buff, int xfer_len, bool hoc, bool is_setup)
{
qtd->buffer = data_buff; //Set pointer to data buffer
qtd->buffer_status_val = 0; //Reset all flags to zero
qtd->out_non_iso.xfer_size = xfer_len;
if (is_setup) {
qtd->out_non_iso.is_setup = 1;
}
if (hoc) {
qtd->in_non_iso.intr_cplt = 1; //We need to set this to distinguish between a halt due to a QTD
qtd->in_non_iso.eol = 1; //Used to halt the channel at this qtd
}
qtd->out_non_iso.active = 1;
}
/**
* @brief Set a QTD as NULL
*
* This sets the QTD to a value of 0. This is only useful when you need to insert
* blank QTDs into a list of QTDs
*
* @param qtd Pointer to the QTD
*/
static inline void usb_dwc_ll_qtd_set_null(usb_dwc_ll_dma_qtd_t *qtd)
{
qtd->buffer = NULL;
qtd->buffer_status_val = 0; //Disable qtd by clearing it to zero. Used by interrupt/isoc as an unscheudled frame
}
/**
* @brief Get the status of a QTD
*
* When a channel gets halted, call this to check whether each QTD was executed successfully
*
* @param qtd Pointer to the QTD
* @param[out] rem_len Number of bytes ramining in the QTD
* @param[out] status Status of the QTD
*/
static inline void usb_dwc_ll_qtd_get_status(usb_dwc_ll_dma_qtd_t *qtd, int *rem_len, int *status)
{
//Status is the same regardless of IN or OUT
if (qtd->in_non_iso.active) {
//QTD was never processed
*status = USB_DWC_LL_QTD_STATUS_NOT_EXECUTED;
} else {
*status = qtd->in_non_iso.rx_status;
}
*rem_len = qtd->in_non_iso.xfer_size;
//Clear the QTD just for safety
qtd->buffer_status_val = 0;
}
#ifdef __cplusplus
}
#endif
@@ -1,236 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_attr.h"
#include "soc/soc.h"
#include "register/soc/pcr_struct.h"
#include "register/soc/usb_wrap_struct.h"
#include "hal/usb_wrap_types.h"
/* ----------------------------- Macros & Types ----------------------------- */
#define USB_WRAP_LL_EXT_PHY_SUPPORTED 0 // Cannot route to an external FSLS PHY
#ifdef __cplusplus
extern "C" {
#endif
/* ---------------------------- USB PHY Control ---------------------------- */
/**
* @brief Sets default
*
* Some register fields and features of the USB WRAP are redundant on the ESP32-H4.
* This function sets those fields to their appropriate default values.
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_defaults(usb_wrap_dev_t *hw)
{
// Always select internal PHY for H4
hw->wrap_otg_conf.wrap_phy_sel = 0;
hw->wrap_otg_conf.wrap_usb_pad_enable = 1;
}
/**
* @brief Enables and sets the override value for the session end signal
*
* @param hw Start address of the USB Wrap registers
* @param sessend Session end override value. True means VBus < 0.2V, false means VBus > 0.8V
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_srp_sessend_override(usb_wrap_dev_t *hw, bool sessend)
{
hw->wrap_otg_conf.wrap_srp_sessend_value = sessend;
hw->wrap_otg_conf.wrap_srp_sessend_override = 1;
}
/**
* @brief Disable session end override
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_srp_sessend_override(usb_wrap_dev_t *hw)
{
hw->wrap_otg_conf.wrap_srp_sessend_override = 0;
}
/**
* @brief Enables/disables exchanging of the D+/D- pins USB PHY
*
* @param hw Start address of the USB Wrap registers
* @param enable Enables pin exchange, disabled otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pin_exchg(usb_wrap_dev_t *hw, bool enable)
{
if (enable) {
hw->wrap_otg_conf.wrap_exchg_pins = 1;
hw->wrap_otg_conf.wrap_exchg_pins_override = 1;
} else {
hw->wrap_otg_conf.wrap_exchg_pins_override = 0;
hw->wrap_otg_conf.wrap_exchg_pins = 0;
}
}
/**
* @brief Enables and sets voltage threshold overrides for USB FSLS PHY single-ended inputs
*
* @param hw Start address of the USB Wrap registers
* @param vrefh_step High voltage threshold. 0 to 3 indicating 80mV steps from 1.76V to 2V.
* @param vrefl_step Low voltage threshold. 0 to 3 indicating 80mV steps from 0.8V to 1.04V.
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_vref_override(usb_wrap_dev_t *hw, unsigned int vrefh_step, unsigned int vrefl_step)
{
hw->wrap_otg_conf.wrap_vrefh = vrefh_step;
hw->wrap_otg_conf.wrap_vrefl = vrefl_step;
hw->wrap_otg_conf.wrap_vref_override = 1;
}
/**
* @brief Disables voltage threshold overrides for USB FSLS PHY single-ended inputs
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_vref_override(usb_wrap_dev_t *hw)
{
hw->wrap_otg_conf.wrap_vref_override = 0;
}
/**
* @brief Enable override of USB FSLS PHY's pull up/down resistors
*
* @param hw Start address of the USB Wrap registers
* @param vals Override values to set
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pull_override(usb_wrap_dev_t *hw, const usb_wrap_pull_override_vals_t *vals)
{
hw->wrap_otg_conf.wrap_dp_pullup = vals->dp_pu;
hw->wrap_otg_conf.wrap_dp_pulldown = vals->dp_pd;
hw->wrap_otg_conf.wrap_dm_pullup = vals->dm_pu;
hw->wrap_otg_conf.wrap_dm_pulldown = vals->dm_pd;
hw->wrap_otg_conf.wrap_pad_pull_override = 1;
}
/**
* @brief Disable override of USB FSLS PHY pull up/down resistors
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_pull_override(usb_wrap_dev_t *hw)
{
hw->wrap_otg_conf.wrap_pad_pull_override = 0;
}
/**
* @brief Sets the strength of the pullup resistor
*
* @param hw Start address of the USB Wrap registers
* @param strong True is a ~1.4K pullup, false is a ~2.4K pullup
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_pullup_strength(usb_wrap_dev_t *hw, bool strong)
{
hw->wrap_otg_conf.wrap_pullup_value = strong;
}
/**
* @brief Check if USB FSLS PHY pads are enabled
*
* @param hw Start address of the USB Wrap registers
* @return True if enabled, false otherwise
*/
FORCE_INLINE_ATTR bool usb_wrap_ll_phy_is_pad_enabled(usb_wrap_dev_t *hw)
{
return hw->wrap_otg_conf.wrap_usb_pad_enable;
}
/**
* @brief Enable the USB FSLS PHY pads
*
* @param hw Start address of the USB Wrap registers
* @param enable Whether to enable the USB FSLS PHY pads
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pad(usb_wrap_dev_t *hw, bool enable)
{
hw->wrap_otg_conf.wrap_usb_pad_enable = enable;
}
/**
* @brief Set USB FSLS PHY TX output clock edge
*
* @param hw Start address of the USB Wrap registers
* @param clk_neg_edge True if TX output at negedge, posedge otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_tx_edge(usb_wrap_dev_t *hw, bool clk_neg_edge)
{
// Not supported on ESP32-H4: no wrap_phy_tx_edge_sel field
(void)hw;
(void)clk_neg_edge;
}
/* ------------------------------ USB PHY Test ------------------------------ */
/**
* @brief Enable the USB FSLS PHY's test mode
*
* @param hw Start address of the USB Wrap registers
* @param enable Whether to enable the USB FSLS PHY's test mode
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_test_mode(usb_wrap_dev_t *hw, bool enable)
{
/* Not supported on H4: test_conf not present in usb_wrap_dev_t */
(void)hw; (void)enable; // Corrected to indicate test_conf is not present
}
/**
* @brief Set the USB FSLS PHY's signal test values
*
* @param hw Start address of the USB Wrap registers
* @param vals Test values to set
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw, const usb_wrap_test_mode_vals_t *vals)
{
/* Not supported on H4: test_conf not present in usb_wrap_dev_t */
(void)hw; (void)vals; // Corrected to indicate test_conf is not present
}
/* ----------------------------- RCC Functions ----------------------------- */
/**
* Enable the bus clock for USB Wrap module
* @param clk_en True if enable the clock of USB Wrap module
*/
FORCE_INLINE_ATTR void _usb_wrap_ll_enable_bus_clock(bool clk_en)
{
PCR.usb_device_conf.usb_device_clk_en = clk_en;
}
// SYSTEM.perip_clk_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_wrap_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* @brief Reset the USB Wrap module
*/
FORCE_INLINE_ATTR void _usb_wrap_ll_reset_register(void)
{
PCR.usb_device_conf.usb_device_rst_en = 1;
PCR.usb_device_conf.usb_device_rst_en = 0;
}
// SYSTEM.perip_rst_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_reset_register(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_wrap_ll_reset_register(__VA_ARGS__); \
} while(0)
#ifdef __cplusplus
}
#endif
File diff suppressed because it is too large Load Diff
@@ -1,97 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_attr.h"
#include "soc/lp_clkrst_struct.h"
#include "soc/hp_sys_clkrst_struct.h"
#include "soc/hp_system_struct.h"
#include "soc/usb_utmi_struct.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ---------------------------- USB PHY Control ---------------------------- */
/**
* @brief Configure Low-Speed mode
*
* @param[in] hw Beginning address of the peripheral registers
* @param[in] parallel Parallel or serial LS mode
* @return FORCE_INLINE_ATTR
*/
FORCE_INLINE_ATTR void usb_utmi_ll_configure_ls(usb_utmi_dev_t *hw, bool parallel)
{
hw->fc_06.ls_par_en = parallel;
hw->fc_06.ls_kpalv_en = 1;
}
/* ----------------------------- RCC Functions ----------------------------- */
/**
* @brief Enable the bus clock for the USB UTMI PHY and USB_DWC_HS controller
*
* @param[in] clk_en True to enable, false to disable
*/
FORCE_INLINE_ATTR void _usb_utmi_ll_enable_bus_clock(bool clk_en)
{
// Enable/disable system clock for USB_UTMI and USB_DWC_HS
HP_SYS_CLKRST.soc_clk_ctrl1.reg_usb_otg20_sys_clk_en = clk_en;
// Enable PHY ref clock (48MHz) for USB UTMI PHY
LP_AON_CLKRST.hp_usb_clkrst_ctrl1.usb_otg20_phyref_clk_en = clk_en;
}
// HP_SYS_CLKRST.soc_clk_ctrlx and LP_AON_CLKRST.hp_usb_clkrst_ctrlx are shared registers, so this function must be used in an atomic way
#define usb_utmi_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_utmi_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* Get the enable status of the USB UTMI PHY bus clock
*
* @return Return true if USB UTMI PHY bus clock is enabled
*/
FORCE_INLINE_ATTR bool _usb_utmi_ll_bus_clock_is_enabled(void)
{
return (HP_SYS_CLKRST.soc_clk_ctrl1.reg_usb_otg20_sys_clk_en && LP_AON_CLKRST.hp_usb_clkrst_ctrl1.usb_otg20_phyref_clk_en);
}
/**
* @brief Reset the USB UTMI PHY and USB_DWC_HS controller
*/
FORCE_INLINE_ATTR void _usb_utmi_ll_reset_register(void)
{
// Reset the USB_UTMI and USB_DWC_HS
LP_AON_CLKRST.hp_usb_clkrst_ctrl1.rst_en_usb_otg20 = 1;
LP_AON_CLKRST.hp_usb_clkrst_ctrl1.rst_en_usb_otg20_phy = 1;
LP_AON_CLKRST.hp_usb_clkrst_ctrl1.rst_en_usb_otg20_phy = 0;
LP_AON_CLKRST.hp_usb_clkrst_ctrl1.rst_en_usb_otg20 = 0;
}
// P_AON_CLKRST.hp_usb_clkrst_ctrlx is shared register, so this function must be used in an atomic way
#define usb_utmi_ll_reset_register(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_utmi_ll_reset_register(__VA_ARGS__); \
} while(0)
/**
* @brief Enable precise detection of VBUS
*
* @param[in] enable Enable/Disable precise detection
*/
FORCE_INLINE_ATTR void usb_utmi_ll_enable_precise_detection(bool enable)
{
// Enable VBUS precise detection
HP_SYSTEM.sys_usbotg20_ctrl.sys_otg_suspendm = enable;
}
#ifdef __cplusplus
}
#endif
@@ -1,273 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_attr.h"
#include "soc/soc.h"
#include "soc/lp_system_struct.h"
#include "soc/lp_clkrst_struct.h"
#include "soc/hp_sys_clkrst_struct.h"
#include "soc/usb_wrap_struct.h"
#include "hal/usb_wrap_types.h"
/* ----------------------------- Macros & Types ----------------------------- */
#define USB_WRAP_LL_SELECT_PHY_SUPPORTED 1 // Can swap to another internal FSLS PHY
#ifdef __cplusplus
extern "C" {
#endif
/* ---------------------------- USB PHY Control ---------------------------- */
/**
* @brief Sets default
*
* Some register fields/features of the USB WRAP are redundant on the ESP32-P4.
* This function those fields are set to the appropriate default values.
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_defaults(usb_wrap_dev_t *hw)
{
// External FSLS PHY is not supported
hw->otg_conf.phy_sel = 0;
hw->otg_conf.usb_pad_enable = 1;
}
/**
* @brief Select the internal USB FSLS PHY for the USB WRAP
*
* @param hw Start address of the USB Wrap registers
* @param phy_idx Selected PHY's index
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_select(usb_wrap_dev_t *hw, unsigned int phy_idx)
{
// Enable SW control mapping USB_WRAP and USJ to USB FSLS PHY 0 and 1
LP_SYS.usb_ctrl.sw_hw_usb_phy_sel = 1;
/*
For 'sw_usb_phy_sel':
False - USJ mapped to USB FSLS PHY 0, USB_WRAP mapped to USB FSLS PHY 1 (default)
True - USJ mapped to USB FSLS PHY 1, USB_WRAP mapped to USB FSLS PHY 0
*/
switch (phy_idx) {
case 0:
LP_SYS.usb_ctrl.sw_usb_phy_sel = true;
case 1:
LP_SYS.usb_ctrl.sw_usb_phy_sel = false;
default:
break;
}
}
/**
* @brief Enables and sets the override value for the session end signal
*
* @param hw Start address of the USB Wrap registers
* @param sessend Session end override value. True means VBus < 0.2V, false means VBus > 0.8V
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_srp_sessend_override(usb_wrap_dev_t *hw, bool sessend)
{
hw->otg_conf.srp_sessend_value = sessend;
hw->otg_conf.srp_sessend_override = 1;
}
/**
* @brief Disable session end override
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_srp_sessend_override(usb_wrap_dev_t *hw)
{
hw->otg_conf.srp_sessend_override = 0;
}
/**
* @brief Enables/disables exchanging of the D+/D- pins USB PHY
*
* @param hw Start address of the USB Wrap registers
* @param enable Enables pin exchange, disabled otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pin_exchg(usb_wrap_dev_t *hw, bool enable)
{
if (enable) {
hw->otg_conf.exchg_pins = 1;
hw->otg_conf.exchg_pins_override = 1;
} else {
hw->otg_conf.exchg_pins_override = 0;
hw->otg_conf.exchg_pins = 0;
}
}
/**
* @brief Enables and sets voltage threshold overrides for USB FSLS PHY single-ended inputs
*
* @param hw Start address of the USB Wrap registers
* @param vrefh_step High voltage threshold. 0 to 3 indicating 80mV steps from 1.76V to 2V.
* @param vrefl_step Low voltage threshold. 0 to 3 indicating 80mV steps from 0.8V to 1.04V.
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_vref_override(usb_wrap_dev_t *hw, unsigned int vrefh_step, unsigned int vrefl_step)
{
hw->otg_conf.vrefh = vrefh_step;
hw->otg_conf.vrefl = vrefl_step;
hw->otg_conf.vref_override = 1;
}
/**
* @brief Disables voltage threshold overrides for USB FSLS PHY single-ended inputs
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_vref_override(usb_wrap_dev_t *hw)
{
hw->otg_conf.vref_override = 0;
}
/**
* @brief Enable override of USB FSLS PHY's pull up/down resistors
*
* @param hw Start address of the USB Wrap registers
* @param vals Override values to set
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pull_override(usb_wrap_dev_t *hw, const usb_wrap_pull_override_vals_t *vals)
{
hw->otg_conf.dp_pullup = vals->dp_pu;
hw->otg_conf.dp_pulldown = vals->dp_pd;
hw->otg_conf.dm_pullup = vals->dm_pu;
hw->otg_conf.dm_pulldown = vals->dm_pd;
hw->otg_conf.pad_pull_override = 1;
}
/**
* @brief Disable override of USB FSLS PHY pull up/down resistors
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_pull_override(usb_wrap_dev_t *hw)
{
hw->otg_conf.pad_pull_override = 0;
}
/**
* @brief Sets the strength of the pullup resistor
*
* @param hw Start address of the USB Wrap registers
* @param strong True is a ~1.4K pullup, false is a ~2.4K pullup
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_pullup_strength(usb_wrap_dev_t *hw, bool strong)
{
hw->otg_conf.pullup_value = strong;
}
/**
* @brief Check if USB FSLS PHY pads are enabled
*
* @param hw Start address of the USB Wrap registers
* @return True if enabled, false otherwise
*/
FORCE_INLINE_ATTR bool usb_wrap_ll_phy_is_pad_enabled(usb_wrap_dev_t *hw)
{
return hw->otg_conf.usb_pad_enable;
}
/**
* @brief Enable the USB FSLS PHY pads
*
* @param hw Start address of the USB Wrap registers
* @param enable Whether to enable the USB FSLS PHY pads
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pad(usb_wrap_dev_t *hw, bool enable)
{
hw->otg_conf.usb_pad_enable = enable;
}
/**
* @brief Set USB FSLS PHY TX output clock edge
*
* @param hw Start address of the USB Wrap registers
* @param clk_neg_edge True if TX output at negedge, posedge otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_tx_edge(usb_wrap_dev_t *hw, bool clk_neg_edge)
{
hw->otg_conf.phy_tx_edge_sel = clk_neg_edge;
}
/* ------------------------------ USB PHY Test ------------------------------ */
/**
* @brief Enable the USB FSLS PHY's test mode
*
* @param hw Start address of the USB Wrap registers
* @param enable Whether to enable the USB FSLS PHY's test mode
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_test_mode(usb_wrap_dev_t *hw, bool enable)
{
hw->test_conf.test_enable = enable;
}
/**
* @brief Set the USB FSLS PHY's signal test values
*
* @param hw Start address of the USB Wrap registers
* @param vals Test values to set
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw, const usb_wrap_test_mode_vals_t *vals)
{
usb_wrap_test_conf_reg_t test_conf;
test_conf.val = hw->test_conf.val;
test_conf.test_usb_wrap_oe = vals->tx_enable_n;
test_conf.test_tx_dp = vals->tx_dp;
test_conf.test_tx_dm = vals->tx_dm;
test_conf.test_rx_rcv = vals->rx_rcv;
test_conf.test_rx_dp = vals->rx_dp;
test_conf.test_rx_dm = vals->rx_dm;
hw->test_conf.val = test_conf.val;
}
/* ----------------------------- RCC Functions ----------------------------- */
/**
* Enable the bus clock for USB Wrap module and USB_DWC_FS controller
* @param clk_en True if enable the clock of USB Wrap module
*/
FORCE_INLINE_ATTR void _usb_wrap_ll_enable_bus_clock(bool clk_en)
{
// Enable/disable system clock for USB_WRAP and USB_DWC_FS
HP_SYS_CLKRST.soc_clk_ctrl1.reg_usb_otg11_sys_clk_en = clk_en;
// Enable PHY clock (48MHz) for USB FSLS PHY 1
LP_AON_CLKRST.hp_usb_clkrst_ctrl0.usb_otg11_48m_clk_en = clk_en;
}
// HP_SYS_CLKRST.soc_clk_ctrlx and LP_AON_CLKRST.hp_usb_clkrst_ctrlx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_wrap_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* @brief Reset the USB Wrap module and USB_DWC_FS controller
*/
FORCE_INLINE_ATTR void _usb_wrap_ll_reset_register(void)
{
// Reset the USB_WRAP and USB_DWC_FS
LP_AON_CLKRST.hp_usb_clkrst_ctrl1.rst_en_usb_otg11 = 1;
LP_AON_CLKRST.hp_usb_clkrst_ctrl1.rst_en_usb_otg11 = 0;
}
// P_AON_CLKRST.hp_usb_clkrst_ctrlx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_reset_register(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_wrap_ll_reset_register(__VA_ARGS__); \
} while(0)
#ifdef __cplusplus
}
#endif
@@ -1,981 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "soc/usb_dwc_struct.h"
#include "soc/usb_dwc_cfg.h"
#include "hal/usb_dwc_types.h"
#include "hal/misc.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ----------------------------- Helper Macros ------------------------------ */
// Get USB hardware instance
#define USB_DWC_LL_GET_HW(num) (&USB_DWC)
/* -----------------------------------------------------------------------------
--------------------------------- DWC Constants --------------------------------
----------------------------------------------------------------------------- */
#define USB_DWC_QTD_LIST_MEM_ALIGN 512
#define USB_DWC_FRAME_LIST_MEM_ALIGN 512 // The frame list needs to be 512 bytes aligned (contrary to the databook)
/* -----------------------------------------------------------------------------
------------------------------- Global Registers -------------------------------
----------------------------------------------------------------------------- */
/*
* Interrupt bit masks of the GINTSTS and GINTMSK registers
*/
#define USB_DWC_LL_INTR_CORE_WKUPINT (1 << 31)
#define USB_DWC_LL_INTR_CORE_SESSREQINT (1 << 30)
#define USB_DWC_LL_INTR_CORE_DISCONNINT (1 << 29)
#define USB_DWC_LL_INTR_CORE_CONIDSTSCHNG (1 << 28)
#define USB_DWC_LL_INTR_CORE_PTXFEMP (1 << 26)
#define USB_DWC_LL_INTR_CORE_HCHINT (1 << 25)
#define USB_DWC_LL_INTR_CORE_PRTINT (1 << 24)
#define USB_DWC_LL_INTR_CORE_RESETDET (1 << 23)
#define USB_DWC_LL_INTR_CORE_FETSUSP (1 << 22)
#define USB_DWC_LL_INTR_CORE_INCOMPIP (1 << 21)
#define USB_DWC_LL_INTR_CORE_INCOMPISOIN (1 << 20)
#define USB_DWC_LL_INTR_CORE_OEPINT (1 << 19)
#define USB_DWC_LL_INTR_CORE_IEPINT (1 << 18)
#define USB_DWC_LL_INTR_CORE_EPMIS (1 << 17)
#define USB_DWC_LL_INTR_CORE_EOPF (1 << 15)
#define USB_DWC_LL_INTR_CORE_ISOOUTDROP (1 << 14)
#define USB_DWC_LL_INTR_CORE_ENUMDONE (1 << 13)
#define USB_DWC_LL_INTR_CORE_USBRST (1 << 12)
#define USB_DWC_LL_INTR_CORE_USBSUSP (1 << 11)
#define USB_DWC_LL_INTR_CORE_ERLYSUSP (1 << 10)
#define USB_DWC_LL_INTR_CORE_GOUTNAKEFF (1 << 7)
#define USB_DWC_LL_INTR_CORE_GINNAKEFF (1 << 6)
#define USB_DWC_LL_INTR_CORE_NPTXFEMP (1 << 5)
#define USB_DWC_LL_INTR_CORE_RXFLVL (1 << 4)
#define USB_DWC_LL_INTR_CORE_SOF (1 << 3)
#define USB_DWC_LL_INTR_CORE_OTGINT (1 << 2)
#define USB_DWC_LL_INTR_CORE_MODEMIS (1 << 1)
#define USB_DWC_LL_INTR_CORE_CURMOD (1 << 0)
/*
* Bit mask of interrupt generating bits of the the HPRT register. These bits
* are ORd into the USB_DWC_LL_INTR_CORE_PRTINT interrupt.
*
* Note: Some fields of the HPRT are W1C (write 1 clear), this we cannot do a
* simple read and write-back to clear the HPRT interrupt bits. Instead we need
* a W1C mask the non-interrupt related bits
*/
#define USB_DWC_LL_HPRT_W1C_MSK (0x2E)
#define USB_DWC_LL_HPRT_ENA_MSK (0x04)
#define USB_DWC_LL_INTR_HPRT_PRTOVRCURRCHNG (1 << 5)
#define USB_DWC_LL_INTR_HPRT_PRTENCHNG (1 << 3)
#define USB_DWC_LL_INTR_HPRT_PRTCONNDET (1 << 1)
/*
* Bit mask of channel interrupts (HCINTi and HCINTMSKi registers)
*
* Note: Under Scatter/Gather DMA mode, only the following interrupts can be unmasked
* - DESC_LS_ROLL
* - XCS_XACT_ERR (always unmasked)
* - BNAINTR
* - CHHLTD
* - XFERCOMPL
* The remaining interrupt bits will still be set (when the corresponding event occurs)
* but will not generate an interrupt. Therefore we must proxy through the
* USB_DWC_LL_INTR_CHAN_CHHLTD interrupt to check the other interrupt bits.
*/
#define USB_DWC_LL_INTR_CHAN_DESC_LS_ROLL (1 << 13)
#define USB_DWC_LL_INTR_CHAN_XCS_XACT_ERR (1 << 12)
#define USB_DWC_LL_INTR_CHAN_BNAINTR (1 << 11)
#define USB_DWC_LL_INTR_CHAN_DATATGLERR (1 << 10)
#define USB_DWC_LL_INTR_CHAN_FRMOVRUN (1 << 9)
#define USB_DWC_LL_INTR_CHAN_BBLEER (1 << 8)
#define USB_DWC_LL_INTR_CHAN_XACTERR (1 << 7)
#define USB_DWC_LL_INTR_CHAN_NYET (1 << 6)
#define USB_DWC_LL_INTR_CHAN_ACK (1 << 5)
#define USB_DWC_LL_INTR_CHAN_NAK (1 << 4)
#define USB_DWC_LL_INTR_CHAN_STALL (1 << 3)
#define USB_DWC_LL_INTR_CHAN_AHBERR (1 << 2)
#define USB_DWC_LL_INTR_CHAN_CHHLTD (1 << 1)
#define USB_DWC_LL_INTR_CHAN_XFERCOMPL (1 << 0)
/*
* QTD (Queue Transfer Descriptor) structure used in Scatter/Gather DMA mode.
* Each QTD describes one transfer. Scatter gather mode will automatically split
* a transfer into multiple MPS packets. Each QTD is 64bits in size
*
* Note: The status information part of the QTD is interpreted differently depending
* on IN or OUT, and ISO or non-ISO
*/
typedef struct {
union {
struct {
uint32_t xfer_size: 17;
uint32_t aqtd_offset: 6;
uint32_t aqtd_valid: 1;
uint32_t reserved_24: 1;
uint32_t intr_cplt: 1;
uint32_t eol: 1;
uint32_t reserved_27: 1;
uint32_t rx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} in_non_iso;
struct {
uint32_t xfer_size: 12;
uint32_t reserved_12_24: 13;
uint32_t intr_cplt: 1;
uint32_t reserved_26_27: 2;
uint32_t rx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} in_iso;
struct {
uint32_t xfer_size: 17;
uint32_t reserved_17_23: 7;
uint32_t is_setup: 1;
uint32_t intr_cplt: 1;
uint32_t eol: 1;
uint32_t reserved_27: 1;
uint32_t tx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} out_non_iso;
struct {
uint32_t xfer_size: 12;
uint32_t reserved_12_24: 13;
uint32_t intr_cplt: 1;
uint32_t eol: 1;
uint32_t reserved_27: 1;
uint32_t tx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} out_iso;
uint32_t buffer_status_val;
};
uint8_t *buffer;
} usb_dwc_ll_dma_qtd_t;
/* -----------------------------------------------------------------------------
------------------------------- Global Registers -------------------------------
----------------------------------------------------------------------------- */
// --------------------------- GAHBCFG Register --------------------------------
static inline void usb_dwc_ll_gahbcfg_en_dma_mode(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.dmaen = 1;
}
static inline void usb_dwc_ll_gahbcfg_en_slave_mode(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.dmaen = 0;
}
static inline void usb_dwc_ll_gahbcfg_set_hbstlen(usb_dwc_dev_t *hw, uint32_t burst_len)
{
hw->gahbcfg_reg.hbstlen = burst_len;
}
static inline void usb_dwc_ll_gahbcfg_en_global_intr(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.glbllntrmsk = 1;
}
static inline void usb_dwc_ll_gahbcfg_dis_global_intr(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.glbllntrmsk = 0;
}
// --------------------------- GUSBCFG Register --------------------------------
static inline void usb_dwc_ll_gusbcfg_force_host_mode(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.forcehstmode = 1;
}
static inline void usb_dwc_ll_gusbcfg_dis_hnp_cap(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.hnpcap = 0;
}
static inline void usb_dwc_ll_gusbcfg_dis_srp_cap(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.srpcap = 0;
}
static inline void usb_dwc_ll_gusbcfg_set_timeout_cal(usb_dwc_dev_t *hw, uint8_t tout_cal)
{
hw->gusbcfg_reg.toutcal = tout_cal;
}
static inline void usb_dwc_ll_gusbcfg_set_utmi_phy(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.phyif = 1; // 16 bits interface
hw->gusbcfg_reg.ulpiutmisel = 0; // UTMI+
hw->gusbcfg_reg.physel = 0; // HS PHY
}
// --------------------------- GRSTCTL Register --------------------------------
static inline bool usb_dwc_ll_grstctl_is_ahb_idle(usb_dwc_dev_t *hw)
{
return hw->grstctl_reg.ahbidle;
}
static inline bool usb_dwc_ll_grstctl_is_dma_req_in_progress(usb_dwc_dev_t *hw)
{
return hw->grstctl_reg.dmareq;
}
static inline void usb_dwc_ll_grstctl_flush_nptx_fifo(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.txfnum = 0; //Set the TX FIFO number to 0 to select the non-periodic TX FIFO
hw->grstctl_reg.txfflsh = 1; //Flush the selected TX FIFO
//Wait for the flushing to complete
while (hw->grstctl_reg.txfflsh) {
;
}
}
static inline void usb_dwc_ll_grstctl_flush_ptx_fifo(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.txfnum = 1; //Set the TX FIFO number to 1 to select the periodic TX FIFO
hw->grstctl_reg.txfflsh = 1; //FLush the select TX FIFO
//Wait for the flushing to complete
while (hw->grstctl_reg.txfflsh) {
;
}
}
static inline void usb_dwc_ll_grstctl_flush_rx_fifo(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.rxfflsh = 1;
//Wait for the flushing to complete
while (hw->grstctl_reg.rxfflsh) {
;
}
}
static inline void usb_dwc_ll_grstctl_reset_frame_counter(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.frmcntrrst = 1;
}
static inline void usb_dwc_ll_grstctl_core_soft_reset(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.csftrst = 1;
while (hw->grstctl_reg.csftrst) {
;
}
}
// --------------------------- GINTSTS Register --------------------------------
/**
* @brief Reads and clears the global interrupt register
*
* @param hw Start address of the DWC_OTG registers
* @return uint32_t Mask of interrupts
*/
static inline uint32_t usb_dwc_ll_gintsts_read_and_clear_intrs(usb_dwc_dev_t *hw)
{
usb_dwc_gintsts_reg_t gintsts;
gintsts.val = hw->gintsts_reg.val;
hw->gintsts_reg.val = gintsts.val; //Write back to clear
return gintsts.val;
}
/**
* @brief Clear specific interrupts
*
* @param hw Start address of the DWC_OTG registers
* @param intr_msk Mask of interrupts to clear
*/
static inline void usb_dwc_ll_gintsts_clear_intrs(usb_dwc_dev_t *hw, uint32_t intr_msk)
{
//All GINTSTS fields are either W1C or read only. So safe to write directly
hw->gintsts_reg.val = intr_msk;
}
// --------------------------- GINTMSK Register --------------------------------
static inline void usb_dwc_ll_gintmsk_en_intrs(usb_dwc_dev_t *hw, uint32_t intr_mask)
{
hw->gintmsk_reg.val |= intr_mask;
}
static inline void usb_dwc_ll_gintmsk_dis_intrs(usb_dwc_dev_t *hw, uint32_t intr_mask)
{
hw->gintmsk_reg.val &= ~intr_mask;
}
// --------------------------- GRXFSIZ Register --------------------------------
static inline void usb_dwc_ll_grxfsiz_set_fifo_size(usb_dwc_dev_t *hw, uint32_t num_lines)
{
//Set size in words
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->grxfsiz_reg, rxfdep, num_lines);
}
// -------------------------- GNPTXFSIZ Register -------------------------------
static inline void usb_dwc_ll_gnptxfsiz_set_fifo_size(usb_dwc_dev_t *hw, uint32_t addr, uint32_t num_lines)
{
usb_dwc_gnptxfsiz_reg_t gnptxfsiz;
gnptxfsiz.val = hw->gnptxfsiz_reg.val;
HAL_FORCE_MODIFY_U32_REG_FIELD(gnptxfsiz, nptxfstaddr, addr);
HAL_FORCE_MODIFY_U32_REG_FIELD(gnptxfsiz, nptxfdep, num_lines);
hw->gnptxfsiz_reg.val = gnptxfsiz.val;
}
// --------------------------- GSNPSID Register --------------------------------
static inline uint32_t usb_dwc_ll_gsnpsid_get_id(usb_dwc_dev_t *hw)
{
return hw->gsnpsid_reg.val;
}
// --------------------------- GHWCFGx Register --------------------------------
static inline unsigned usb_dwc_ll_ghwcfg_get_fifo_depth(usb_dwc_dev_t *hw)
{
return hw->ghwcfg3_reg.dfifodepth;
}
static inline unsigned usb_dwc_ll_ghwcfg_get_hsphy_type(usb_dwc_dev_t *hw)
{
return hw->ghwcfg2_reg.hsphytype;
}
static inline unsigned usb_dwc_ll_ghwcfg_get_channel_num(usb_dwc_dev_t *hw)
{
return hw->ghwcfg2_reg.numhstchnl + 1;
}
// --------------------------- HPTXFSIZ Register -------------------------------
static inline void usb_dwc_ll_hptxfsiz_set_ptx_fifo_size(usb_dwc_dev_t *hw, uint32_t addr, uint32_t num_lines)
{
usb_dwc_hptxfsiz_reg_t hptxfsiz;
hptxfsiz.val = hw->hptxfsiz_reg.val;
HAL_FORCE_MODIFY_U32_REG_FIELD(hptxfsiz, ptxfstaddr, addr);
HAL_FORCE_MODIFY_U32_REG_FIELD(hptxfsiz, ptxfsize, num_lines);
hw->hptxfsiz_reg.val = hptxfsiz.val;
}
/* -----------------------------------------------------------------------------
-------------------------------- Host Registers --------------------------------
----------------------------------------------------------------------------- */
// ----------------------------- HCFG Register ---------------------------------
static inline void usb_dwc_ll_hcfg_en_perio_sched(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.perschedena = 1;
}
static inline void usb_dwc_ll_hcfg_dis_perio_sched(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.perschedena = 0;
}
/**
* Sets the length of the frame list
*
* @param num_entires Number of entries in the frame list
*/
static inline void usb_dwc_ll_hcfg_set_num_frame_list_entries(usb_dwc_dev_t *hw, usb_hal_frame_list_len_t num_entries)
{
uint32_t frlisten;
switch (num_entries) {
case USB_HAL_FRAME_LIST_LEN_8:
frlisten = 0;
break;
case USB_HAL_FRAME_LIST_LEN_16:
frlisten = 1;
break;
case USB_HAL_FRAME_LIST_LEN_32:
frlisten = 2;
break;
default: //USB_HAL_FRAME_LIST_LEN_64
frlisten = 3;
break;
}
hw->hcfg_reg.frlisten = frlisten;
}
static inline void usb_dwc_ll_hcfg_en_scatt_gatt_dma(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.descdma = 1;
}
static inline void usb_dwc_ll_hcfg_set_fsls_supp_only(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.fslssupp = 1;
}
/**
* @brief Set FSLS PHY clock
*
* @attention This function should only be called if FSLS PHY is selected
* @param[in] hw Start address of the DWC_OTG registers
*/
static inline void usb_dwc_ll_hcfg_set_fsls_phy_clock(usb_dwc_dev_t *hw)
{
/*
Indicate to the OTG core what speed the PHY clock is at
Note: FSLS PHY has an implicit 8 divider applied when in LS mode,
so the values of FSLSPclkSel and FrInt have to be adjusted accordingly.
*/
usb_dwc_speed_t speed = (usb_dwc_speed_t)hw->hprt_reg.prtspd;
hw->hcfg_reg.fslspclksel = (speed == USB_DWC_SPEED_FULL) ? 1 : 2;
}
// ----------------------------- HFIR Register ---------------------------------
/**
* @brief Set Frame Interval
*
* @attention This function should only be called if FSLS PHY is selected
* @param[in] hw Start address of the DWC_OTG registers
*/
static inline void usb_dwc_ll_hfir_set_frame_interval(usb_dwc_dev_t *hw)
{
usb_dwc_hfir_reg_t hfir;
hfir.val = hw->hfir_reg.val;
hfir.hfirrldctrl = 0; // Disable dynamic loading
/*
Set frame interval to be equal to 1ms
Note: FSLS PHY has an implicit 8 divider applied when in LS mode,
so the values of FSLSPclkSel and FrInt have to be adjusted accordingly.
*/
usb_dwc_speed_t speed = (usb_dwc_speed_t)hw->hprt_reg.prtspd;
hfir.frint = (speed == USB_DWC_SPEED_FULL) ? 48000 : 6000;
hw->hfir_reg.val = hfir.val;
}
// ----------------------------- HFNUM Register --------------------------------
static inline uint32_t usb_dwc_ll_hfnum_get_frame_time_rem(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->hfnum_reg, frrem);
}
static inline uint32_t usb_dwc_ll_hfnum_get_frame_num(usb_dwc_dev_t *hw)
{
return hw->hfnum_reg.frnum;
}
// ---------------------------- HPTXSTS Register -------------------------------
static inline uint32_t usb_dwc_ll_hptxsts_get_ptxq_top(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->hptxsts_reg, ptxqtop);
}
static inline uint32_t usb_dwc_ll_hptxsts_get_ptxq_space_avail(usb_dwc_dev_t *hw)
{
return hw->hptxsts_reg.ptxqspcavail;
}
static inline uint32_t usb_dwc_ll_ptxsts_get_ptxf_space_avail(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->hptxsts_reg, ptxfspcavail);
}
// ----------------------------- HAINT Register --------------------------------
static inline uint32_t usb_dwc_ll_haint_get_chan_intrs(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->haint_reg, haint);
}
// --------------------------- HAINTMSK Register -------------------------------
static inline void usb_dwc_ll_haintmsk_en_chan_intr(usb_dwc_dev_t *hw, uint32_t mask)
{
hw->haintmsk_reg.val |= mask;
}
static inline void usb_dwc_ll_haintmsk_dis_chan_intr(usb_dwc_dev_t *hw, uint32_t mask)
{
hw->haintmsk_reg.val &= ~mask;
}
// --------------------------- HFLBAddr Register -------------------------------
/**
* @brief Set the base address of the scheduling frame list
*
* @note For some reason, this address must be 512 bytes aligned or else a bunch of frames will not be scheduled when
* the frame list rolls over. However, according to the databook, there is no mention of the HFLBAddr needing to
* be aligned.
*
* @param hw Start address of the DWC_OTG registers
* @param addr Base address of the scheduling frame list
*/
static inline void usb_dwc_ll_hflbaddr_set_base_addr(usb_dwc_dev_t *hw, uint32_t addr)
{
hw->hflbaddr_reg.hflbaddr = addr;
}
/**
* @brief Get the base address of the scheduling frame list
*
* @param hw Start address of the DWC_OTG registers
* @return uint32_t Base address of the scheduling frame list
*/
static inline uint32_t usb_dwc_ll_hflbaddr_get_base_addr(usb_dwc_dev_t *hw)
{
return hw->hflbaddr_reg.hflbaddr;
}
// ----------------------------- HPRT Register ---------------------------------
static inline usb_dwc_speed_t usb_dwc_ll_hprt_get_speed(usb_dwc_dev_t *hw)
{
return (usb_dwc_speed_t)hw->hprt_reg.prtspd;
}
static inline uint32_t usb_dwc_ll_hprt_get_test_ctl(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prttstctl;
}
static inline void usb_dwc_ll_hprt_set_test_ctl(usb_dwc_dev_t *hw, uint32_t test_mode)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prttstctl = test_mode;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline void usb_dwc_ll_hprt_en_pwr(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtpwr = 1;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline void usb_dwc_ll_hprt_dis_pwr(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtpwr = 0;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline uint32_t usb_dwc_ll_hprt_get_pwr_line_status(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtlnsts;
}
static inline void usb_dwc_ll_hprt_set_port_reset(usb_dwc_dev_t *hw, bool reset)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtrst = reset;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline bool usb_dwc_ll_hprt_get_port_reset(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtrst;
}
static inline void usb_dwc_ll_hprt_set_port_suspend(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtsusp = 1;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline bool usb_dwc_ll_hprt_get_port_suspend(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtsusp;
}
static inline void usb_dwc_ll_hprt_set_port_resume(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtres = 1;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline void usb_dwc_ll_hprt_clr_port_resume(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtres = 0;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline bool usb_dwc_ll_hprt_get_port_resume(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtres;
}
static inline bool usb_dwc_ll_hprt_get_port_overcur(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtovrcurract;
}
static inline bool usb_dwc_ll_hprt_get_port_en(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtena;
}
static inline void usb_dwc_ll_hprt_port_dis(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtena = 1; //W1C to disable
//we want to W1C ENA but not W1C the interrupt bits
hw->hprt_reg.val = hprt.val & ((~USB_DWC_LL_HPRT_W1C_MSK) | USB_DWC_LL_HPRT_ENA_MSK);
}
static inline bool usb_dwc_ll_hprt_get_conn_status(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtconnsts;
}
static inline uint32_t usb_dwc_ll_hprt_intr_read_and_clear(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
//We want to W1C the interrupt bits but not that ENA
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_ENA_MSK);
//Return only the interrupt bits
return (hprt.val & (USB_DWC_LL_HPRT_W1C_MSK & ~(USB_DWC_LL_HPRT_ENA_MSK)));
}
static inline void usb_dwc_ll_hprt_intr_clear(usb_dwc_dev_t *hw, uint32_t intr_mask)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hw->hprt_reg.val = ((hprt.val & ~USB_DWC_LL_HPRT_ENA_MSK) & ~USB_DWC_LL_HPRT_W1C_MSK) | intr_mask;
}
//Per Channel registers
// --------------------------- HCCHARi Register --------------------------------
static inline void usb_dwc_ll_hcchar_enable_chan(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.chena = 1;
}
static inline bool usb_dwc_ll_hcchar_chan_is_enabled(volatile usb_dwc_host_chan_regs_t *chan)
{
return chan->hcchar_reg.chena;
}
static inline void usb_dwc_ll_hcchar_disable_chan(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.chdis = 1;
}
static inline void usb_dwc_ll_hcchar_set_odd_frame(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.oddfrm = 1;
}
static inline void usb_dwc_ll_hcchar_set_even_frame(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.oddfrm = 0;
}
static inline void usb_dwc_ll_hcchar_set_dev_addr(volatile usb_dwc_host_chan_regs_t *chan, uint32_t addr)
{
chan->hcchar_reg.devaddr = addr;
}
static inline void usb_dwc_ll_hcchar_set_ep_type(volatile usb_dwc_host_chan_regs_t *chan, usb_dwc_xfer_type_t type)
{
chan->hcchar_reg.eptype = (uint32_t)type;
}
//Indicates whether channel is commuunicating with a LS device connected via a FS hub. Setting this bit to 1 will cause
//each packet to be preceded by a PREamble packet
static inline void usb_dwc_ll_hcchar_set_lspddev(volatile usb_dwc_host_chan_regs_t *chan, bool is_ls)
{
chan->hcchar_reg.lspddev = is_ls;
}
static inline void usb_dwc_ll_hcchar_set_dir(volatile usb_dwc_host_chan_regs_t *chan, bool is_in)
{
chan->hcchar_reg.epdir = is_in;
}
static inline void usb_dwc_ll_hcchar_set_ep_num(volatile usb_dwc_host_chan_regs_t *chan, uint32_t num)
{
chan->hcchar_reg.epnum = num;
}
static inline void usb_dwc_ll_hcchar_set_mps(volatile usb_dwc_host_chan_regs_t *chan, uint32_t mps)
{
chan->hcchar_reg.mps = mps;
}
static inline void usb_dwc_ll_hcchar_init(volatile usb_dwc_host_chan_regs_t *chan, int dev_addr, int ep_num, int mps, usb_dwc_xfer_type_t type, bool is_in, bool is_ls)
{
//Sets all persistent fields of the channel over its lifetimez
usb_dwc_ll_hcchar_set_dev_addr(chan, dev_addr);
usb_dwc_ll_hcchar_set_ep_type(chan, type);
usb_dwc_ll_hcchar_set_lspddev(chan, is_ls);
usb_dwc_ll_hcchar_set_dir(chan, is_in);
usb_dwc_ll_hcchar_set_ep_num(chan, ep_num);
usb_dwc_ll_hcchar_set_mps(chan, mps);
}
// ---------------------------- HCINTi Register --------------------------------
static inline uint32_t usb_dwc_ll_hcint_read_and_clear_intrs(volatile usb_dwc_host_chan_regs_t *chan)
{
usb_dwc_hcint_reg_t hcint;
hcint.val = chan->hcint_reg.val;
chan->hcint_reg.val = hcint.val;
return hcint.val;
}
// --------------------------- HCINTMSKi Register ------------------------------
static inline void usb_dwc_ll_hcintmsk_set_intr_mask(volatile usb_dwc_host_chan_regs_t *chan, uint32_t mask)
{
chan->hcintmsk_reg.val = mask;
}
// ---------------------------- HCTSIZi Register -------------------------------
static inline void usb_dwc_ll_hctsiz_init(volatile usb_dwc_host_chan_regs_t *chan)
{
usb_dwc_hctsiz_reg_t hctsiz;
hctsiz.val = chan->hctsiz_reg.val;
hctsiz.dopng = 0; // Don't do ping
hctsiz.pid = 0; // Set PID to DATA0
/*
* Set SCHED_INFO which occupies xfersize[7:0]
*
* Although the hardware documentation suggests that SCHED_INFO is only used for periodic channels,
* empirical evidence shows that omitting this configuration on non-periodic channels can cause them to freeze.
* Therefore, we set this field for all channels to ensure reliable operation.
*/
hctsiz.xfersize |= 0xFF;
chan->hctsiz_reg.val = hctsiz.val;
}
static inline void usb_dwc_ll_hctsiz_set_pid(volatile usb_dwc_host_chan_regs_t *chan, uint32_t data_pid)
{
if (data_pid == 0) {
chan->hctsiz_reg.pid = 0;
} else {
chan->hctsiz_reg.pid = 2;
}
}
static inline uint32_t usb_dwc_ll_hctsiz_get_pid(volatile usb_dwc_host_chan_regs_t *chan)
{
if (chan->hctsiz_reg.pid == 0) {
return 0; //DATA0
} else {
return 1; //DATA1
}
}
static inline void usb_dwc_ll_hctsiz_set_qtd_list_len(volatile usb_dwc_host_chan_regs_t *chan, int qtd_list_len)
{
usb_dwc_hctsiz_reg_t hctsiz;
hctsiz.val = chan->hctsiz_reg.val;
//Set the length of the descriptor list. NTD occupies xfersize[15:8]
hctsiz.xfersize &= ~(0xFF << 8);
hctsiz.xfersize |= ((qtd_list_len - 1) & 0xFF) << 8;
chan->hctsiz_reg.val = hctsiz.val;
}
/**
* @brief Perform PING protocol
*
* @note This function is here only for compatibility reasons. PING is not relevant on FS only targets
* @param[in] chan Channel registers
* @param[in] enable true: Enable PING, false: Disable PING
*/
static inline void usb_dwc_ll_hctsiz_set_dopng(volatile usb_dwc_host_chan_regs_t *chan, bool enable)
{
}
/**
* @brief Set scheduling info for Periodic channel
*
* @note ESP32-S2 is Full-Speed only, so SCHED_INFO is always set to 0xFF
* @attention This function must be called for each periodic channel!
* @see USB-OTG databook: Table 5-47
*
* @param[in] chan Channel registers
* @param[in] tokens_per_frame Ignored
* @param[in] offset Ignored
*/
static inline void usb_dwc_ll_hctsiz_set_sched_info(volatile usb_dwc_host_chan_regs_t *chan, int tokens_per_frame, int offset)
{
usb_dwc_hctsiz_reg_t hctsiz;
hctsiz.val = chan->hctsiz_reg.val;
hctsiz.xfersize |= 0xFF;
chan->hctsiz_reg.val = hctsiz.val;
}
// ---------------------------- HCDMAi Register --------------------------------
static inline void usb_dwc_ll_hcdma_set_qtd_list_addr(volatile usb_dwc_host_chan_regs_t *chan, void *dmaaddr, uint32_t qtd_idx)
{
usb_dwc_hcdma_reg_t hcdma;
/*
Set the base address portion of the field which is dmaaddr[31:9]. This is
the based address of the QTD list and must be 512 bytes aligned
*/
hcdma.dmaaddr = ((uint32_t)dmaaddr) & 0xFFFFFE00;
//Set the current QTD index in the QTD list which is dmaaddr[8:3]
hcdma.dmaaddr |= (qtd_idx & 0x3F) << 3;
//dmaaddr[2:0] is reserved thus doesn't not need to be set
chan->hcdma_reg.val = hcdma.val;
}
static inline int usb_dwc_ll_hcdam_get_cur_qtd_idx(usb_dwc_host_chan_regs_t *chan)
{
//The current QTD index is dmaaddr[8:3]
return (chan->hcdma_reg.dmaaddr >> 3) & 0x3F;
}
// ---------------------------- HCDMABi Register -------------------------------
static inline void *usb_dwc_ll_hcdmab_get_buff_addr(volatile usb_dwc_host_chan_regs_t *chan)
{
return (void *)chan->hcdmab_reg.hcdmab;
}
/* -----------------------------------------------------------------------------
---------------------------- Scatter/Gather DMA QTDs ---------------------------
----------------------------------------------------------------------------- */
// ---------------------------- Helper Functions -------------------------------
/**
* @brief Get the base address of a channel's register based on the channel's index
*
* @param dev Start address of the DWC_OTG registers
* @param chan_idx The channel's index
* @return usb_dwc_host_chan_regs_t* Pointer to channel's registers
*/
static inline usb_dwc_host_chan_regs_t *usb_dwc_ll_chan_get_regs(usb_dwc_dev_t *dev, int chan_idx)
{
return &dev->host_chans[chan_idx];
}
// ------------------------------ QTD related ----------------------------------
#define USB_DWC_LL_QTD_STATUS_SUCCESS 0x0 //If QTD was processed, it indicates the data was transmitted/received successfully
#define USB_DWC_LL_QTD_STATUS_PKTERR 0x1 //Data transmitted/received with errors (CRC/Timeout/Stuff/False EOP/Excessive NAK).
//Note: 0x2 is reserved
#define USB_DWC_LL_QTD_STATUS_BUFFER 0x3 //AHB error occurred.
#define USB_DWC_LL_QTD_STATUS_NOT_EXECUTED 0x4 //QTD as never processed
/**
* @brief Set a QTD for a non isochronous IN transfer
*
* @param qtd Pointer to the QTD
* @param data_buff Pointer to buffer containing the data to transfer
* @param xfer_len Number of bytes in transfer. Setting 0 will do a zero length IN transfer.
* Non zero length must be multiple of the endpoint's MPS.
* @param hoc Halt on complete (will generate an interrupt and halt the channel)
*/
static inline void usb_dwc_ll_qtd_set_in(usb_dwc_ll_dma_qtd_t *qtd, uint8_t *data_buff, int xfer_len, bool hoc)
{
qtd->buffer = data_buff; //Set pointer to data buffer
qtd->buffer_status_val = 0; //Reset all flags to zero
qtd->in_non_iso.xfer_size = xfer_len;
if (hoc) {
qtd->in_non_iso.intr_cplt = 1; //We need to set this to distinguish between a halt due to a QTD
qtd->in_non_iso.eol = 1; //Used to halt the channel at this qtd
}
qtd->in_non_iso.active = 1;
}
/**
* @brief Set a QTD for a non isochronous OUT transfer
*
* @param qtd Pointer to the QTD
* @param data_buff Pointer to buffer containing the data to transfer
* @param xfer_len Number of bytes to transfer. Setting 0 will do a zero length transfer.
* For ctrl setup packets, this should be set to 8.
* @param hoc Halt on complete (will generate an interrupt)
* @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 usb_dwc_ll_qtd_set_out(usb_dwc_ll_dma_qtd_t *qtd, uint8_t *data_buff, int xfer_len, bool hoc, bool is_setup)
{
qtd->buffer = data_buff; //Set pointer to data buffer
qtd->buffer_status_val = 0; //Reset all flags to zero
qtd->out_non_iso.xfer_size = xfer_len;
if (is_setup) {
qtd->out_non_iso.is_setup = 1;
}
if (hoc) {
qtd->in_non_iso.intr_cplt = 1; //We need to set this to distinguish between a halt due to a QTD
qtd->in_non_iso.eol = 1; //Used to halt the channel at this qtd
}
qtd->out_non_iso.active = 1;
}
/**
* @brief Set a QTD as NULL
*
* This sets the QTD to a value of 0. This is only useful when you need to insert
* blank QTDs into a list of QTDs
*
* @param qtd Pointer to the QTD
*/
static inline void usb_dwc_ll_qtd_set_null(usb_dwc_ll_dma_qtd_t *qtd)
{
qtd->buffer = NULL;
qtd->buffer_status_val = 0; //Disable qtd by clearing it to zero. Used by interrupt/isoc as an unscheudled frame
}
/**
* @brief Get the status of a QTD
*
* When a channel gets halted, call this to check whether each QTD was executed successfully
*
* @param qtd Pointer to the QTD
* @param[out] rem_len Number of bytes ramining in the QTD
* @param[out] status Status of the QTD
*/
static inline void usb_dwc_ll_qtd_get_status(usb_dwc_ll_dma_qtd_t *qtd, int *rem_len, int *status)
{
//Status is the same regardless of IN or OUT
if (qtd->in_non_iso.active) {
//QTD was never processed
*status = USB_DWC_LL_QTD_STATUS_NOT_EXECUTED;
} else {
*status = qtd->in_non_iso.rx_status;
}
*rem_len = qtd->in_non_iso.xfer_size;
//Clear the QTD just for safety
qtd->buffer_status_val = 0;
}
#ifdef __cplusplus
}
#endif
@@ -1,238 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_attr.h"
#include "soc/soc.h"
#include "soc/system_reg.h"
#include "soc/usb_wrap_struct.h"
#include "hal/usb_wrap_types.h"
/* ----------------------------- Macros & Types ----------------------------- */
#define USB_WRAP_LL_EXT_PHY_SUPPORTED 1 // Can route to an external FSLS PHY
#ifdef __cplusplus
extern "C" {
#endif
/* ---------------------------- USB PHY Control ---------------------------- */
/**
* @brief Enables and sets the override value for the session end signal
*
* @param hw Start address of the USB Wrap registers
* @param sessend Session end override value. True means VBus < 0.2V, false means VBus > 0.8V
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_srp_sessend_override(usb_wrap_dev_t *hw, bool sessend)
{
hw->otg_conf.srp_sessend_value = sessend;
hw->otg_conf.srp_sessend_override = 1;
}
/**
* @brief Disable session end override
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_srp_sessend_override(usb_wrap_dev_t *hw)
{
hw->otg_conf.srp_sessend_override = 0;
}
/**
* @brief Sets whether the USB Wrap's FSLS PHY interface routes to an internal or external PHY
*
* @param hw Start address of the USB Wrap registers
* @param enable Enables external PHY, internal otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_external(usb_wrap_dev_t *hw, bool enable)
{
hw->otg_conf.phy_sel = enable;
}
/**
* @brief Enables/disables exchanging of the D+/D- pins USB PHY
*
* @param hw Start address of the USB Wrap registers
* @param enable Enables pin exchange, disabled otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pin_exchg(usb_wrap_dev_t *hw, bool enable)
{
if (enable) {
hw->otg_conf.exchg_pins = 1;
hw->otg_conf.exchg_pins_override = 1;
} else {
hw->otg_conf.exchg_pins_override = 0;
hw->otg_conf.exchg_pins = 0;
}
}
/**
* @brief Enables and sets voltage threshold overrides for USB FSLS PHY single-ended inputs
*
* @param hw Start address of the USB Wrap registers
* @param vrefh_step High voltage threshold. 0 to 3 indicating 80mV steps from 1.76V to 2V.
* @param vrefl_step Low voltage threshold. 0 to 3 indicating 80mV steps from 0.8V to 1.04V.
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_vref_override(usb_wrap_dev_t *hw, unsigned int vrefh_step, unsigned int vrefl_step)
{
hw->otg_conf.vrefh = vrefh_step;
hw->otg_conf.vrefl = vrefl_step;
hw->otg_conf.vref_override = 1;
}
/**
* @brief Disables voltage threshold overrides for USB FSLS PHY single-ended inputs
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_vref_override(usb_wrap_dev_t *hw)
{
hw->otg_conf.vref_override = 0;
}
/**
* @brief Enable override of USB FSLS PHY's pull up/down resistors
*
* @param hw Start address of the USB Wrap registers
* @param vals Override values to set
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pull_override(usb_wrap_dev_t *hw, const usb_wrap_pull_override_vals_t *vals)
{
hw->otg_conf.dp_pullup = vals->dp_pu;
hw->otg_conf.dp_pulldown = vals->dp_pd;
hw->otg_conf.dm_pullup = vals->dm_pu;
hw->otg_conf.dm_pulldown = vals->dm_pd;
hw->otg_conf.pad_pull_override = 1;
}
/**
* @brief Disable override of USB FSLS PHY pull up/down resistors
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_pull_override(usb_wrap_dev_t *hw)
{
hw->otg_conf.pad_pull_override = 0;
}
/**
* @brief Sets the strength of the pullup resistor
*
* @param hw Start address of the USB Wrap registers
* @param strong True is a ~1.4K pullup, false is a ~2.4K pullup
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_pullup_strength(usb_wrap_dev_t *hw, bool strong)
{
hw->otg_conf.pullup_value = strong;
}
/**
* @brief Check if USB FSLS PHY pads are enabled
*
* @param hw Start address of the USB Wrap registers
* @return True if enabled, false otherwise
*/
FORCE_INLINE_ATTR bool usb_wrap_ll_phy_is_pad_enabled(usb_wrap_dev_t *hw)
{
return hw->otg_conf.pad_enable;
}
/**
* @brief Enable the USB FSLS PHY pads
*
* @param hw Start address of the USB Wrap registers
* @param enable Whether to enable the USB FSLS PHY pads
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pad(usb_wrap_dev_t *hw, bool enable)
{
hw->otg_conf.pad_enable = enable;
}
/**
* @brief Set USB FSLS PHY TX output clock edge
*
* @param hw Start address of the USB Wrap registers
* @param clk_neg_edge True if TX output at negedge, posedge otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_tx_edge(usb_wrap_dev_t *hw, bool clk_neg_edge)
{
hw->otg_conf.phy_tx_edge_sel = clk_neg_edge;
}
/* ------------------------------ USB PHY Test ------------------------------ */
/**
* @brief Enable the USB FSLS PHY's test mode
*
* @param hw Start address of the USB Wrap registers
* @param enable Whether to enable the USB FSLS PHY's test mode
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_test_mode(usb_wrap_dev_t *hw, bool enable)
{
hw->test_conf.test_enable = enable;
}
/**
* @brief Set the USB FSLS PHY's signal test values
*
* @param hw Start address of the USB Wrap registers
* @param vals Test values to set
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw, const usb_wrap_test_mode_vals_t *vals)
{
usb_wrap_test_conf_reg_t test_conf;
test_conf.val = hw->test_conf.val;
test_conf.test_usb_wrap_oe = vals->tx_enable_n;
test_conf.test_tx_dp = vals->tx_dp;
test_conf.test_tx_dm = vals->tx_dm;
test_conf.test_rx_rcv = vals->rx_rcv;
test_conf.test_rx_dp = vals->rx_dp;
test_conf.test_rx_dm = vals->rx_dm;
hw->test_conf.val = test_conf.val;
}
/* ----------------------------- RCC Functions ----------------------------- */
/**
* Enable the bus clock for USB Wrap module
* @param clk_en True if enable the clock of USB Wrap module
*/
FORCE_INLINE_ATTR void _usb_wrap_ll_enable_bus_clock(bool clk_en)
{
REG_SET_FIELD(DPORT_PERIP_CLK_EN0_REG, DPORT_USB_CLK_EN, clk_en);
}
// SYSTEM.perip_clk_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_wrap_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* @brief Reset the USB Wrap module
*/
FORCE_INLINE_ATTR void _usb_wrap_ll_reset_register(void)
{
REG_SET_FIELD(DPORT_PERIP_RST_EN0_REG, DPORT_USB_RST, 1);
REG_SET_FIELD(DPORT_PERIP_RST_EN0_REG, DPORT_USB_RST, 0);
}
// SYSTEM.perip_rst_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_reset_register(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_wrap_ll_reset_register(__VA_ARGS__); \
} while(0)
#ifdef __cplusplus
}
#endif
@@ -1,981 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "soc/usb_dwc_struct.h"
#include "soc/usb_dwc_cfg.h"
#include "hal/usb_dwc_types.h"
#include "hal/misc.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ----------------------------- Helper Macros ------------------------------ */
// Get USB hardware instance
#define USB_DWC_LL_GET_HW(num) (&USB_DWC)
/* -----------------------------------------------------------------------------
--------------------------------- DWC Constants --------------------------------
----------------------------------------------------------------------------- */
#define USB_DWC_QTD_LIST_MEM_ALIGN 512
#define USB_DWC_FRAME_LIST_MEM_ALIGN 512 // The frame list needs to be 512 bytes aligned (contrary to the databook)
/* -----------------------------------------------------------------------------
------------------------------- Global Registers -------------------------------
----------------------------------------------------------------------------- */
/*
* Interrupt bit masks of the GINTSTS and GINTMSK registers
*/
#define USB_DWC_LL_INTR_CORE_WKUPINT (1 << 31)
#define USB_DWC_LL_INTR_CORE_SESSREQINT (1 << 30)
#define USB_DWC_LL_INTR_CORE_DISCONNINT (1 << 29)
#define USB_DWC_LL_INTR_CORE_CONIDSTSCHNG (1 << 28)
#define USB_DWC_LL_INTR_CORE_PTXFEMP (1 << 26)
#define USB_DWC_LL_INTR_CORE_HCHINT (1 << 25)
#define USB_DWC_LL_INTR_CORE_PRTINT (1 << 24)
#define USB_DWC_LL_INTR_CORE_RESETDET (1 << 23)
#define USB_DWC_LL_INTR_CORE_FETSUSP (1 << 22)
#define USB_DWC_LL_INTR_CORE_INCOMPIP (1 << 21)
#define USB_DWC_LL_INTR_CORE_INCOMPISOIN (1 << 20)
#define USB_DWC_LL_INTR_CORE_OEPINT (1 << 19)
#define USB_DWC_LL_INTR_CORE_IEPINT (1 << 18)
#define USB_DWC_LL_INTR_CORE_EPMIS (1 << 17)
#define USB_DWC_LL_INTR_CORE_EOPF (1 << 15)
#define USB_DWC_LL_INTR_CORE_ISOOUTDROP (1 << 14)
#define USB_DWC_LL_INTR_CORE_ENUMDONE (1 << 13)
#define USB_DWC_LL_INTR_CORE_USBRST (1 << 12)
#define USB_DWC_LL_INTR_CORE_USBSUSP (1 << 11)
#define USB_DWC_LL_INTR_CORE_ERLYSUSP (1 << 10)
#define USB_DWC_LL_INTR_CORE_GOUTNAKEFF (1 << 7)
#define USB_DWC_LL_INTR_CORE_GINNAKEFF (1 << 6)
#define USB_DWC_LL_INTR_CORE_NPTXFEMP (1 << 5)
#define USB_DWC_LL_INTR_CORE_RXFLVL (1 << 4)
#define USB_DWC_LL_INTR_CORE_SOF (1 << 3)
#define USB_DWC_LL_INTR_CORE_OTGINT (1 << 2)
#define USB_DWC_LL_INTR_CORE_MODEMIS (1 << 1)
#define USB_DWC_LL_INTR_CORE_CURMOD (1 << 0)
/*
* Bit mask of interrupt generating bits of the the HPRT register. These bits
* are ORd into the USB_DWC_LL_INTR_CORE_PRTINT interrupt.
*
* Note: Some fields of the HPRT are W1C (write 1 clear), this we cannot do a
* simple read and write-back to clear the HPRT interrupt bits. Instead we need
* a W1C mask the non-interrupt related bits
*/
#define USB_DWC_LL_HPRT_W1C_MSK (0x2E)
#define USB_DWC_LL_HPRT_ENA_MSK (0x04)
#define USB_DWC_LL_INTR_HPRT_PRTOVRCURRCHNG (1 << 5)
#define USB_DWC_LL_INTR_HPRT_PRTENCHNG (1 << 3)
#define USB_DWC_LL_INTR_HPRT_PRTCONNDET (1 << 1)
/*
* Bit mask of channel interrupts (HCINTi and HCINTMSKi registers)
*
* Note: Under Scatter/Gather DMA mode, only the following interrupts can be unmasked
* - DESC_LS_ROLL
* - XCS_XACT_ERR (always unmasked)
* - BNAINTR
* - CHHLTD
* - XFERCOMPL
* The remaining interrupt bits will still be set (when the corresponding event occurs)
* but will not generate an interrupt. Therefore we must proxy through the
* USB_DWC_LL_INTR_CHAN_CHHLTD interrupt to check the other interrupt bits.
*/
#define USB_DWC_LL_INTR_CHAN_DESC_LS_ROLL (1 << 13)
#define USB_DWC_LL_INTR_CHAN_XCS_XACT_ERR (1 << 12)
#define USB_DWC_LL_INTR_CHAN_BNAINTR (1 << 11)
#define USB_DWC_LL_INTR_CHAN_DATATGLERR (1 << 10)
#define USB_DWC_LL_INTR_CHAN_FRMOVRUN (1 << 9)
#define USB_DWC_LL_INTR_CHAN_BBLEER (1 << 8)
#define USB_DWC_LL_INTR_CHAN_XACTERR (1 << 7)
#define USB_DWC_LL_INTR_CHAN_NYET (1 << 6)
#define USB_DWC_LL_INTR_CHAN_ACK (1 << 5)
#define USB_DWC_LL_INTR_CHAN_NAK (1 << 4)
#define USB_DWC_LL_INTR_CHAN_STALL (1 << 3)
#define USB_DWC_LL_INTR_CHAN_AHBERR (1 << 2)
#define USB_DWC_LL_INTR_CHAN_CHHLTD (1 << 1)
#define USB_DWC_LL_INTR_CHAN_XFERCOMPL (1 << 0)
/*
* QTD (Queue Transfer Descriptor) structure used in Scatter/Gather DMA mode.
* Each QTD describes one transfer. Scatter gather mode will automatically split
* a transfer into multiple MPS packets. Each QTD is 64bits in size
*
* Note: The status information part of the QTD is interpreted differently depending
* on IN or OUT, and ISO or non-ISO
*/
typedef struct {
union {
struct {
uint32_t xfer_size: 17;
uint32_t aqtd_offset: 6;
uint32_t aqtd_valid: 1;
uint32_t reserved_24: 1;
uint32_t intr_cplt: 1;
uint32_t eol: 1;
uint32_t reserved_27: 1;
uint32_t rx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} in_non_iso;
struct {
uint32_t xfer_size: 12;
uint32_t reserved_12_24: 13;
uint32_t intr_cplt: 1;
uint32_t reserved_26_27: 2;
uint32_t rx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} in_iso;
struct {
uint32_t xfer_size: 17;
uint32_t reserved_17_23: 7;
uint32_t is_setup: 1;
uint32_t intr_cplt: 1;
uint32_t eol: 1;
uint32_t reserved_27: 1;
uint32_t tx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} out_non_iso;
struct {
uint32_t xfer_size: 12;
uint32_t reserved_12_24: 13;
uint32_t intr_cplt: 1;
uint32_t eol: 1;
uint32_t reserved_27: 1;
uint32_t tx_status: 2;
uint32_t reserved_30: 1;
uint32_t active: 1;
} out_iso;
uint32_t buffer_status_val;
};
uint8_t *buffer;
} usb_dwc_ll_dma_qtd_t;
/* -----------------------------------------------------------------------------
------------------------------- Global Registers -------------------------------
----------------------------------------------------------------------------- */
// --------------------------- GAHBCFG Register --------------------------------
static inline void usb_dwc_ll_gahbcfg_en_dma_mode(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.dmaen = 1;
}
static inline void usb_dwc_ll_gahbcfg_en_slave_mode(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.dmaen = 0;
}
static inline void usb_dwc_ll_gahbcfg_set_hbstlen(usb_dwc_dev_t *hw, uint32_t burst_len)
{
hw->gahbcfg_reg.hbstlen = burst_len;
}
static inline void usb_dwc_ll_gahbcfg_en_global_intr(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.glbllntrmsk = 1;
}
static inline void usb_dwc_ll_gahbcfg_dis_global_intr(usb_dwc_dev_t *hw)
{
hw->gahbcfg_reg.glbllntrmsk = 0;
}
// --------------------------- GUSBCFG Register --------------------------------
static inline void usb_dwc_ll_gusbcfg_force_host_mode(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.forcehstmode = 1;
}
static inline void usb_dwc_ll_gusbcfg_dis_hnp_cap(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.hnpcap = 0;
}
static inline void usb_dwc_ll_gusbcfg_dis_srp_cap(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.srpcap = 0;
}
static inline void usb_dwc_ll_gusbcfg_set_timeout_cal(usb_dwc_dev_t *hw, uint8_t tout_cal)
{
hw->gusbcfg_reg.toutcal = tout_cal;
}
static inline void usb_dwc_ll_gusbcfg_set_utmi_phy(usb_dwc_dev_t *hw)
{
hw->gusbcfg_reg.phyif = 1; // 16 bits interface
hw->gusbcfg_reg.ulpiutmisel = 0; // UTMI+
hw->gusbcfg_reg.physel = 0; // HS PHY
}
// --------------------------- GRSTCTL Register --------------------------------
static inline bool usb_dwc_ll_grstctl_is_ahb_idle(usb_dwc_dev_t *hw)
{
return hw->grstctl_reg.ahbidle;
}
static inline bool usb_dwc_ll_grstctl_is_dma_req_in_progress(usb_dwc_dev_t *hw)
{
return hw->grstctl_reg.dmareq;
}
static inline void usb_dwc_ll_grstctl_flush_nptx_fifo(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.txfnum = 0; //Set the TX FIFO number to 0 to select the non-periodic TX FIFO
hw->grstctl_reg.txfflsh = 1; //Flush the selected TX FIFO
//Wait for the flushing to complete
while (hw->grstctl_reg.txfflsh) {
;
}
}
static inline void usb_dwc_ll_grstctl_flush_ptx_fifo(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.txfnum = 1; //Set the TX FIFO number to 1 to select the periodic TX FIFO
hw->grstctl_reg.txfflsh = 1; //FLush the select TX FIFO
//Wait for the flushing to complete
while (hw->grstctl_reg.txfflsh) {
;
}
}
static inline void usb_dwc_ll_grstctl_flush_rx_fifo(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.rxfflsh = 1;
//Wait for the flushing to complete
while (hw->grstctl_reg.rxfflsh) {
;
}
}
static inline void usb_dwc_ll_grstctl_reset_frame_counter(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.frmcntrrst = 1;
}
static inline void usb_dwc_ll_grstctl_core_soft_reset(usb_dwc_dev_t *hw)
{
hw->grstctl_reg.csftrst = 1;
while (hw->grstctl_reg.csftrst) {
;
}
}
// --------------------------- GINTSTS Register --------------------------------
/**
* @brief Reads and clears the global interrupt register
*
* @param hw Start address of the DWC_OTG registers
* @return uint32_t Mask of interrupts
*/
static inline uint32_t usb_dwc_ll_gintsts_read_and_clear_intrs(usb_dwc_dev_t *hw)
{
usb_dwc_gintsts_reg_t gintsts;
gintsts.val = hw->gintsts_reg.val;
hw->gintsts_reg.val = gintsts.val; //Write back to clear
return gintsts.val;
}
/**
* @brief Clear specific interrupts
*
* @param hw Start address of the DWC_OTG registers
* @param intr_msk Mask of interrupts to clear
*/
static inline void usb_dwc_ll_gintsts_clear_intrs(usb_dwc_dev_t *hw, uint32_t intr_msk)
{
//All GINTSTS fields are either W1C or read only. So safe to write directly
hw->gintsts_reg.val = intr_msk;
}
// --------------------------- GINTMSK Register --------------------------------
static inline void usb_dwc_ll_gintmsk_en_intrs(usb_dwc_dev_t *hw, uint32_t intr_mask)
{
hw->gintmsk_reg.val |= intr_mask;
}
static inline void usb_dwc_ll_gintmsk_dis_intrs(usb_dwc_dev_t *hw, uint32_t intr_mask)
{
hw->gintmsk_reg.val &= ~intr_mask;
}
// --------------------------- GRXFSIZ Register --------------------------------
static inline void usb_dwc_ll_grxfsiz_set_fifo_size(usb_dwc_dev_t *hw, uint32_t num_lines)
{
//Set size in words
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->grxfsiz_reg, rxfdep, num_lines);
}
// -------------------------- GNPTXFSIZ Register -------------------------------
static inline void usb_dwc_ll_gnptxfsiz_set_fifo_size(usb_dwc_dev_t *hw, uint32_t addr, uint32_t num_lines)
{
usb_dwc_gnptxfsiz_reg_t gnptxfsiz;
gnptxfsiz.val = hw->gnptxfsiz_reg.val;
HAL_FORCE_MODIFY_U32_REG_FIELD(gnptxfsiz, nptxfstaddr, addr);
HAL_FORCE_MODIFY_U32_REG_FIELD(gnptxfsiz, nptxfdep, num_lines);
hw->gnptxfsiz_reg.val = gnptxfsiz.val;
}
// --------------------------- GSNPSID Register --------------------------------
static inline uint32_t usb_dwc_ll_gsnpsid_get_id(usb_dwc_dev_t *hw)
{
return hw->gsnpsid_reg.val;
}
// --------------------------- GHWCFGx Register --------------------------------
static inline unsigned usb_dwc_ll_ghwcfg_get_fifo_depth(usb_dwc_dev_t *hw)
{
return hw->ghwcfg3_reg.dfifodepth;
}
static inline unsigned usb_dwc_ll_ghwcfg_get_hsphy_type(usb_dwc_dev_t *hw)
{
return hw->ghwcfg2_reg.hsphytype;
}
static inline unsigned usb_dwc_ll_ghwcfg_get_channel_num(usb_dwc_dev_t *hw)
{
return hw->ghwcfg2_reg.numhstchnl + 1;
}
// --------------------------- HPTXFSIZ Register -------------------------------
static inline void usb_dwc_ll_hptxfsiz_set_ptx_fifo_size(usb_dwc_dev_t *hw, uint32_t addr, uint32_t num_lines)
{
usb_dwc_hptxfsiz_reg_t hptxfsiz;
hptxfsiz.val = hw->hptxfsiz_reg.val;
HAL_FORCE_MODIFY_U32_REG_FIELD(hptxfsiz, ptxfstaddr, addr);
HAL_FORCE_MODIFY_U32_REG_FIELD(hptxfsiz, ptxfsize, num_lines);
hw->hptxfsiz_reg.val = hptxfsiz.val;
}
/* -----------------------------------------------------------------------------
-------------------------------- Host Registers --------------------------------
----------------------------------------------------------------------------- */
// ----------------------------- HCFG Register ---------------------------------
static inline void usb_dwc_ll_hcfg_en_perio_sched(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.perschedena = 1;
}
static inline void usb_dwc_ll_hcfg_dis_perio_sched(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.perschedena = 0;
}
/**
* Sets the length of the frame list
*
* @param num_entires Number of entries in the frame list
*/
static inline void usb_dwc_ll_hcfg_set_num_frame_list_entries(usb_dwc_dev_t *hw, usb_hal_frame_list_len_t num_entries)
{
uint32_t frlisten;
switch (num_entries) {
case USB_HAL_FRAME_LIST_LEN_8:
frlisten = 0;
break;
case USB_HAL_FRAME_LIST_LEN_16:
frlisten = 1;
break;
case USB_HAL_FRAME_LIST_LEN_32:
frlisten = 2;
break;
default: //USB_HAL_FRAME_LIST_LEN_64
frlisten = 3;
break;
}
hw->hcfg_reg.frlisten = frlisten;
}
static inline void usb_dwc_ll_hcfg_en_scatt_gatt_dma(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.descdma = 1;
}
static inline void usb_dwc_ll_hcfg_set_fsls_supp_only(usb_dwc_dev_t *hw)
{
hw->hcfg_reg.fslssupp = 1;
}
/**
* @brief Set FSLS PHY clock
*
* @attention This function should only be called if FSLS PHY is selected
* @param[in] hw Start address of the DWC_OTG registers
*/
static inline void usb_dwc_ll_hcfg_set_fsls_phy_clock(usb_dwc_dev_t *hw)
{
/*
Indicate to the OTG core what speed the PHY clock is at
Note: FSLS PHY has an implicit 8 divider applied when in LS mode,
so the values of FSLSPclkSel and FrInt have to be adjusted accordingly.
*/
usb_dwc_speed_t speed = (usb_dwc_speed_t)hw->hprt_reg.prtspd;
hw->hcfg_reg.fslspclksel = (speed == USB_DWC_SPEED_FULL) ? 1 : 2;
}
// ----------------------------- HFIR Register ---------------------------------
/**
* @brief Set Frame Interval
*
* @attention This function should only be called if FSLS PHY is selected
* @param[in] hw Start address of the DWC_OTG registers
*/
static inline void usb_dwc_ll_hfir_set_frame_interval(usb_dwc_dev_t *hw)
{
usb_dwc_hfir_reg_t hfir;
hfir.val = hw->hfir_reg.val;
hfir.hfirrldctrl = 0; // Disable dynamic loading
/*
Set frame interval to be equal to 1ms
Note: FSLS PHY has an implicit 8 divider applied when in LS mode,
so the values of FSLSPclkSel and FrInt have to be adjusted accordingly.
*/
usb_dwc_speed_t speed = (usb_dwc_speed_t)hw->hprt_reg.prtspd;
hfir.frint = (speed == USB_DWC_SPEED_FULL) ? 48000 : 6000;
hw->hfir_reg.val = hfir.val;
}
// ----------------------------- HFNUM Register --------------------------------
static inline uint32_t usb_dwc_ll_hfnum_get_frame_time_rem(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->hfnum_reg, frrem);
}
static inline uint32_t usb_dwc_ll_hfnum_get_frame_num(usb_dwc_dev_t *hw)
{
return hw->hfnum_reg.frnum;
}
// ---------------------------- HPTXSTS Register -------------------------------
static inline uint32_t usb_dwc_ll_hptxsts_get_ptxq_top(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->hptxsts_reg, ptxqtop);
}
static inline uint32_t usb_dwc_ll_hptxsts_get_ptxq_space_avail(usb_dwc_dev_t *hw)
{
return hw->hptxsts_reg.ptxqspcavail;
}
static inline uint32_t usb_dwc_ll_ptxsts_get_ptxf_space_avail(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->hptxsts_reg, ptxfspcavail);
}
// ----------------------------- HAINT Register --------------------------------
static inline uint32_t usb_dwc_ll_haint_get_chan_intrs(usb_dwc_dev_t *hw)
{
return HAL_FORCE_READ_U32_REG_FIELD(hw->haint_reg, haint);
}
// --------------------------- HAINTMSK Register -------------------------------
static inline void usb_dwc_ll_haintmsk_en_chan_intr(usb_dwc_dev_t *hw, uint32_t mask)
{
hw->haintmsk_reg.val |= mask;
}
static inline void usb_dwc_ll_haintmsk_dis_chan_intr(usb_dwc_dev_t *hw, uint32_t mask)
{
hw->haintmsk_reg.val &= ~mask;
}
// --------------------------- HFLBAddr Register -------------------------------
/**
* @brief Set the base address of the scheduling frame list
*
* @note For some reason, this address must be 512 bytes aligned or else a bunch of frames will not be scheduled when
* the frame list rolls over. However, according to the databook, there is no mention of the HFLBAddr needing to
* be aligned.
*
* @param hw Start address of the DWC_OTG registers
* @param addr Base address of the scheduling frame list
*/
static inline void usb_dwc_ll_hflbaddr_set_base_addr(usb_dwc_dev_t *hw, uint32_t addr)
{
hw->hflbaddr_reg.hflbaddr = addr;
}
/**
* @brief Get the base address of the scheduling frame list
*
* @param hw Start address of the DWC_OTG registers
* @return uint32_t Base address of the scheduling frame list
*/
static inline uint32_t usb_dwc_ll_hflbaddr_get_base_addr(usb_dwc_dev_t *hw)
{
return hw->hflbaddr_reg.hflbaddr;
}
// ----------------------------- HPRT Register ---------------------------------
static inline usb_dwc_speed_t usb_dwc_ll_hprt_get_speed(usb_dwc_dev_t *hw)
{
return (usb_dwc_speed_t)hw->hprt_reg.prtspd;
}
static inline uint32_t usb_dwc_ll_hprt_get_test_ctl(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prttstctl;
}
static inline void usb_dwc_ll_hprt_set_test_ctl(usb_dwc_dev_t *hw, uint32_t test_mode)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prttstctl = test_mode;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline void usb_dwc_ll_hprt_en_pwr(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtpwr = 1;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline void usb_dwc_ll_hprt_dis_pwr(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtpwr = 0;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline uint32_t usb_dwc_ll_hprt_get_pwr_line_status(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtlnsts;
}
static inline void usb_dwc_ll_hprt_set_port_reset(usb_dwc_dev_t *hw, bool reset)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtrst = reset;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline bool usb_dwc_ll_hprt_get_port_reset(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtrst;
}
static inline void usb_dwc_ll_hprt_set_port_suspend(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtsusp = 1;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline bool usb_dwc_ll_hprt_get_port_suspend(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtsusp;
}
static inline void usb_dwc_ll_hprt_set_port_resume(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtres = 1;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline void usb_dwc_ll_hprt_clr_port_resume(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtres = 0;
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_W1C_MSK);
}
static inline bool usb_dwc_ll_hprt_get_port_resume(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtres;
}
static inline bool usb_dwc_ll_hprt_get_port_overcur(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtovrcurract;
}
static inline bool usb_dwc_ll_hprt_get_port_en(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtena;
}
static inline void usb_dwc_ll_hprt_port_dis(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hprt.prtena = 1; //W1C to disable
//we want to W1C ENA but not W1C the interrupt bits
hw->hprt_reg.val = hprt.val & ((~USB_DWC_LL_HPRT_W1C_MSK) | USB_DWC_LL_HPRT_ENA_MSK);
}
static inline bool usb_dwc_ll_hprt_get_conn_status(usb_dwc_dev_t *hw)
{
return hw->hprt_reg.prtconnsts;
}
static inline uint32_t usb_dwc_ll_hprt_intr_read_and_clear(usb_dwc_dev_t *hw)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
//We want to W1C the interrupt bits but not that ENA
hw->hprt_reg.val = hprt.val & (~USB_DWC_LL_HPRT_ENA_MSK);
//Return only the interrupt bits
return (hprt.val & (USB_DWC_LL_HPRT_W1C_MSK & ~(USB_DWC_LL_HPRT_ENA_MSK)));
}
static inline void usb_dwc_ll_hprt_intr_clear(usb_dwc_dev_t *hw, uint32_t intr_mask)
{
usb_dwc_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hw->hprt_reg.val = ((hprt.val & ~USB_DWC_LL_HPRT_ENA_MSK) & ~USB_DWC_LL_HPRT_W1C_MSK) | intr_mask;
}
//Per Channel registers
// --------------------------- HCCHARi Register --------------------------------
static inline void usb_dwc_ll_hcchar_enable_chan(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.chena = 1;
}
static inline bool usb_dwc_ll_hcchar_chan_is_enabled(volatile usb_dwc_host_chan_regs_t *chan)
{
return chan->hcchar_reg.chena;
}
static inline void usb_dwc_ll_hcchar_disable_chan(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.chdis = 1;
}
static inline void usb_dwc_ll_hcchar_set_odd_frame(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.oddfrm = 1;
}
static inline void usb_dwc_ll_hcchar_set_even_frame(volatile usb_dwc_host_chan_regs_t *chan)
{
chan->hcchar_reg.oddfrm = 0;
}
static inline void usb_dwc_ll_hcchar_set_dev_addr(volatile usb_dwc_host_chan_regs_t *chan, uint32_t addr)
{
chan->hcchar_reg.devaddr = addr;
}
static inline void usb_dwc_ll_hcchar_set_ep_type(volatile usb_dwc_host_chan_regs_t *chan, usb_dwc_xfer_type_t type)
{
chan->hcchar_reg.eptype = (uint32_t)type;
}
//Indicates whether channel is commuunicating with a LS device connected via a FS hub. Setting this bit to 1 will cause
//each packet to be preceded by a PREamble packet
static inline void usb_dwc_ll_hcchar_set_lspddev(volatile usb_dwc_host_chan_regs_t *chan, bool is_ls)
{
chan->hcchar_reg.lspddev = is_ls;
}
static inline void usb_dwc_ll_hcchar_set_dir(volatile usb_dwc_host_chan_regs_t *chan, bool is_in)
{
chan->hcchar_reg.epdir = is_in;
}
static inline void usb_dwc_ll_hcchar_set_ep_num(volatile usb_dwc_host_chan_regs_t *chan, uint32_t num)
{
chan->hcchar_reg.epnum = num;
}
static inline void usb_dwc_ll_hcchar_set_mps(volatile usb_dwc_host_chan_regs_t *chan, uint32_t mps)
{
chan->hcchar_reg.mps = mps;
}
static inline void usb_dwc_ll_hcchar_init(volatile usb_dwc_host_chan_regs_t *chan, int dev_addr, int ep_num, int mps, usb_dwc_xfer_type_t type, bool is_in, bool is_ls)
{
//Sets all persistent fields of the channel over its lifetimez
usb_dwc_ll_hcchar_set_dev_addr(chan, dev_addr);
usb_dwc_ll_hcchar_set_ep_type(chan, type);
usb_dwc_ll_hcchar_set_lspddev(chan, is_ls);
usb_dwc_ll_hcchar_set_dir(chan, is_in);
usb_dwc_ll_hcchar_set_ep_num(chan, ep_num);
usb_dwc_ll_hcchar_set_mps(chan, mps);
}
// ---------------------------- HCINTi Register --------------------------------
static inline uint32_t usb_dwc_ll_hcint_read_and_clear_intrs(volatile usb_dwc_host_chan_regs_t *chan)
{
usb_dwc_hcint_reg_t hcint;
hcint.val = chan->hcint_reg.val;
chan->hcint_reg.val = hcint.val;
return hcint.val;
}
// --------------------------- HCINTMSKi Register ------------------------------
static inline void usb_dwc_ll_hcintmsk_set_intr_mask(volatile usb_dwc_host_chan_regs_t *chan, uint32_t mask)
{
chan->hcintmsk_reg.val = mask;
}
// ---------------------------- HCTSIZi Register -------------------------------
static inline void usb_dwc_ll_hctsiz_init(volatile usb_dwc_host_chan_regs_t *chan)
{
usb_dwc_hctsiz_reg_t hctsiz;
hctsiz.val = chan->hctsiz_reg.val;
hctsiz.dopng = 0; // Don't do ping
hctsiz.pid = 0; // Set PID to DATA0
/*
* Set SCHED_INFO which occupies xfersize[7:0]
*
* Although the hardware documentation suggests that SCHED_INFO is only used for periodic channels,
* empirical evidence shows that omitting this configuration on non-periodic channels can cause them to freeze.
* Therefore, we set this field for all channels to ensure reliable operation.
*/
hctsiz.xfersize |= 0xFF;
chan->hctsiz_reg.val = hctsiz.val;
}
static inline void usb_dwc_ll_hctsiz_set_pid(volatile usb_dwc_host_chan_regs_t *chan, uint32_t data_pid)
{
if (data_pid == 0) {
chan->hctsiz_reg.pid = 0;
} else {
chan->hctsiz_reg.pid = 2;
}
}
static inline uint32_t usb_dwc_ll_hctsiz_get_pid(volatile usb_dwc_host_chan_regs_t *chan)
{
if (chan->hctsiz_reg.pid == 0) {
return 0; //DATA0
} else {
return 1; //DATA1
}
}
static inline void usb_dwc_ll_hctsiz_set_qtd_list_len(volatile usb_dwc_host_chan_regs_t *chan, int qtd_list_len)
{
usb_dwc_hctsiz_reg_t hctsiz;
hctsiz.val = chan->hctsiz_reg.val;
//Set the length of the descriptor list. NTD occupies xfersize[15:8]
hctsiz.xfersize &= ~(0xFF << 8);
hctsiz.xfersize |= ((qtd_list_len - 1) & 0xFF) << 8;
chan->hctsiz_reg.val = hctsiz.val;
}
/**
* @brief Perform PING protocol
*
* @note This function is here only for compatibility reasons. PING is not relevant on FS only targets
* @param[in] chan Channel registers
* @param[in] enable true: Enable PING, false: Disable PING
*/
static inline void usb_dwc_ll_hctsiz_set_dopng(volatile usb_dwc_host_chan_regs_t *chan, bool enable)
{
}
/**
* @brief Set scheduling info for Periodic channel
*
* @note ESP32-S3 is Full-Speed only, so SCHED_INFO is always set to 0xFF
* @attention This function must be called for each periodic channel!
* @see USB-OTG databook: Table 5-47
*
* @param[in] chan Channel registers
* @param[in] tokens_per_frame Ignored
* @param[in] offset Ignored
*/
static inline void usb_dwc_ll_hctsiz_set_sched_info(volatile usb_dwc_host_chan_regs_t *chan, int tokens_per_frame, int offset)
{
usb_dwc_hctsiz_reg_t hctsiz;
hctsiz.val = chan->hctsiz_reg.val;
hctsiz.xfersize |= 0xFF;
chan->hctsiz_reg.val = hctsiz.val;
}
// ---------------------------- HCDMAi Register --------------------------------
static inline void usb_dwc_ll_hcdma_set_qtd_list_addr(volatile usb_dwc_host_chan_regs_t *chan, void *dmaaddr, uint32_t qtd_idx)
{
usb_dwc_hcdma_reg_t hcdma;
/*
Set the base address portion of the field which is dmaaddr[31:9]. This is
the based address of the QTD list and must be 512 bytes aligned
*/
hcdma.dmaaddr = ((uint32_t)dmaaddr) & 0xFFFFFE00;
//Set the current QTD index in the QTD list which is dmaaddr[8:3]
hcdma.dmaaddr |= (qtd_idx & 0x3F) << 3;
//dmaaddr[2:0] is reserved thus doesn't not need to be set
chan->hcdma_reg.val = hcdma.val;
}
static inline int usb_dwc_ll_hcdam_get_cur_qtd_idx(usb_dwc_host_chan_regs_t *chan)
{
//The current QTD index is dmaaddr[8:3]
return (chan->hcdma_reg.dmaaddr >> 3) & 0x3F;
}
// ---------------------------- HCDMABi Register -------------------------------
static inline void *usb_dwc_ll_hcdmab_get_buff_addr(volatile usb_dwc_host_chan_regs_t *chan)
{
return (void *)chan->hcdmab_reg.hcdmab;
}
/* -----------------------------------------------------------------------------
---------------------------- Scatter/Gather DMA QTDs ---------------------------
----------------------------------------------------------------------------- */
// ---------------------------- Helper Functions -------------------------------
/**
* @brief Get the base address of a channel's register based on the channel's index
*
* @param dev Start address of the DWC_OTG registers
* @param chan_idx The channel's index
* @return usb_dwc_host_chan_regs_t* Pointer to channel's registers
*/
static inline usb_dwc_host_chan_regs_t *usb_dwc_ll_chan_get_regs(usb_dwc_dev_t *dev, int chan_idx)
{
return &dev->host_chans[chan_idx];
}
// ------------------------------ QTD related ----------------------------------
#define USB_DWC_LL_QTD_STATUS_SUCCESS 0x0 //If QTD was processed, it indicates the data was transmitted/received successfully
#define USB_DWC_LL_QTD_STATUS_PKTERR 0x1 //Data transmitted/received with errors (CRC/Timeout/Stuff/False EOP/Excessive NAK).
//Note: 0x2 is reserved
#define USB_DWC_LL_QTD_STATUS_BUFFER 0x3 //AHB error occurred.
#define USB_DWC_LL_QTD_STATUS_NOT_EXECUTED 0x4 //QTD as never processed
/**
* @brief Set a QTD for a non isochronous IN transfer
*
* @param qtd Pointer to the QTD
* @param data_buff Pointer to buffer containing the data to transfer
* @param xfer_len Number of bytes in transfer. Setting 0 will do a zero length IN transfer.
* Non zero length must be multiple of the endpoint's MPS.
* @param hoc Halt on complete (will generate an interrupt and halt the channel)
*/
static inline void usb_dwc_ll_qtd_set_in(usb_dwc_ll_dma_qtd_t *qtd, uint8_t *data_buff, int xfer_len, bool hoc)
{
qtd->buffer = data_buff; //Set pointer to data buffer
qtd->buffer_status_val = 0; //Reset all flags to zero
qtd->in_non_iso.xfer_size = xfer_len;
if (hoc) {
qtd->in_non_iso.intr_cplt = 1; //We need to set this to distinguish between a halt due to a QTD
qtd->in_non_iso.eol = 1; //Used to halt the channel at this qtd
}
qtd->in_non_iso.active = 1;
}
/**
* @brief Set a QTD for a non isochronous OUT transfer
*
* @param qtd Pointer to the QTD
* @param data_buff Pointer to buffer containing the data to transfer
* @param xfer_len Number of bytes to transfer. Setting 0 will do a zero length transfer.
* For ctrl setup packets, this should be set to 8.
* @param hoc Halt on complete (will generate an interrupt)
* @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 usb_dwc_ll_qtd_set_out(usb_dwc_ll_dma_qtd_t *qtd, uint8_t *data_buff, int xfer_len, bool hoc, bool is_setup)
{
qtd->buffer = data_buff; //Set pointer to data buffer
qtd->buffer_status_val = 0; //Reset all flags to zero
qtd->out_non_iso.xfer_size = xfer_len;
if (is_setup) {
qtd->out_non_iso.is_setup = 1;
}
if (hoc) {
qtd->in_non_iso.intr_cplt = 1; //We need to set this to distinguish between a halt due to a QTD
qtd->in_non_iso.eol = 1; //Used to halt the channel at this qtd
}
qtd->out_non_iso.active = 1;
}
/**
* @brief Set a QTD as NULL
*
* This sets the QTD to a value of 0. This is only useful when you need to insert
* blank QTDs into a list of QTDs
*
* @param qtd Pointer to the QTD
*/
static inline void usb_dwc_ll_qtd_set_null(usb_dwc_ll_dma_qtd_t *qtd)
{
qtd->buffer = NULL;
qtd->buffer_status_val = 0; //Disable qtd by clearing it to zero. Used by interrupt/isoc as an unscheudled frame
}
/**
* @brief Get the status of a QTD
*
* When a channel gets halted, call this to check whether each QTD was executed successfully
*
* @param qtd Pointer to the QTD
* @param[out] rem_len Number of bytes ramining in the QTD
* @param[out] status Status of the QTD
*/
static inline void usb_dwc_ll_qtd_get_status(usb_dwc_ll_dma_qtd_t *qtd, int *rem_len, int *status)
{
//Status is the same regardless of IN or OUT
if (qtd->in_non_iso.active) {
//QTD was never processed
*status = USB_DWC_LL_QTD_STATUS_NOT_EXECUTED;
} else {
*status = qtd->in_non_iso.rx_status;
}
*rem_len = qtd->in_non_iso.xfer_size;
//Clear the QTD just for safety
qtd->buffer_status_val = 0;
}
#ifdef __cplusplus
}
#endif
@@ -1,247 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_attr.h"
#include "soc/soc.h"
#include "soc/system_struct.h"
#include "soc/usb_wrap_struct.h"
#include "soc/rtc_cntl_struct.h"
#include "hal/usb_wrap_types.h"
/* ----------------------------- Macros & Types ----------------------------- */
#define USB_WRAP_LL_EXT_PHY_SUPPORTED 1 // Can route to an external FSLS PHY
#ifdef __cplusplus
extern "C" {
#endif
/* ---------------------------- USB PHY Control ---------------------------- */
/**
* @brief Enables and sets the override value for the session end signal
*
* @param hw Start address of the USB Wrap registers
* @param sessend Session end override value. True means VBus < 0.2V, false means VBus > 0.8V
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_srp_sessend_override(usb_wrap_dev_t *hw, bool sessend)
{
hw->otg_conf.srp_sessend_value = sessend;
hw->otg_conf.srp_sessend_override = 1;
}
/**
* @brief Disable session end override
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_srp_sessend_override(usb_wrap_dev_t *hw)
{
hw->otg_conf.srp_sessend_override = 0;
}
/**
* @brief Sets whether the USB Wrap's FSLS PHY interface routes to an internal or external PHY
*
* @param hw Start address of the USB Wrap registers
* @param enable Enables external PHY, internal otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_external(usb_wrap_dev_t *hw, bool enable)
{
hw->otg_conf.phy_sel = enable;
// Enable SW control of muxing USB OTG vs USJ to the internal USB FSLS PHY
RTCCNTL.usb_conf.sw_hw_usb_phy_sel = 1;
/*
For 'sw_usb_phy_sel':
0 - Internal USB FSLS PHY is mapped to the USJ. USB Wrap mapped to external PHY
1 - Internal USB FSLS PHY is mapped to the USB Wrap. USJ mapped to external PHY
*/
RTCCNTL.usb_conf.sw_usb_phy_sel = !enable;
}
/**
* @brief Enables/disables exchanging of the D+/D- pins USB PHY
*
* @param hw Start address of the USB Wrap registers
* @param enable Enables pin exchange, disabled otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pin_exchg(usb_wrap_dev_t *hw, bool enable)
{
if (enable) {
hw->otg_conf.exchg_pins = 1;
hw->otg_conf.exchg_pins_override = 1;
} else {
hw->otg_conf.exchg_pins_override = 0;
hw->otg_conf.exchg_pins = 0;
}
}
/**
* @brief Enables and sets voltage threshold overrides for USB FSLS PHY single-ended inputs
*
* @param hw Start address of the USB Wrap registers
* @param vrefh_step High voltage threshold. 0 to 3 indicating 80mV steps from 1.76V to 2V.
* @param vrefl_step Low voltage threshold. 0 to 3 indicating 80mV steps from 0.8V to 1.04V.
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_vref_override(usb_wrap_dev_t *hw, unsigned int vrefh_step, unsigned int vrefl_step)
{
hw->otg_conf.vrefh = vrefh_step;
hw->otg_conf.vrefl = vrefl_step;
hw->otg_conf.vref_override = 1;
}
/**
* @brief Disables voltage threshold overrides for USB FSLS PHY single-ended inputs
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_vref_override(usb_wrap_dev_t *hw)
{
hw->otg_conf.vref_override = 0;
}
/**
* @brief Enable override of USB FSLS PHY's pull up/down resistors
*
* @param hw Start address of the USB Wrap registers
* @param vals Override values to set
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pull_override(usb_wrap_dev_t *hw, const usb_wrap_pull_override_vals_t *vals)
{
hw->otg_conf.dp_pullup = vals->dp_pu;
hw->otg_conf.dp_pulldown = vals->dp_pd;
hw->otg_conf.dm_pullup = vals->dm_pu;
hw->otg_conf.dm_pulldown = vals->dm_pd;
hw->otg_conf.pad_pull_override = 1;
}
/**
* @brief Disable override of USB FSLS PHY pull up/down resistors
*
* @param hw Start address of the USB Wrap registers
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_disable_pull_override(usb_wrap_dev_t *hw)
{
hw->otg_conf.pad_pull_override = 0;
}
/**
* @brief Sets the strength of the pullup resistor
*
* @param hw Start address of the USB Wrap registers
* @param strong True is a ~1.4K pullup, false is a ~2.4K pullup
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_pullup_strength(usb_wrap_dev_t *hw, bool strong)
{
hw->otg_conf.pullup_value = strong;
}
/**
* @brief Check if USB FSLS PHY pads are enabled
*
* @param hw Start address of the USB Wrap registers
* @return True if enabled, false otherwise
*/
FORCE_INLINE_ATTR bool usb_wrap_ll_phy_is_pad_enabled(usb_wrap_dev_t *hw)
{
return hw->otg_conf.pad_enable;
}
/**
* @brief Enable the USB FSLS PHY pads
*
* @param hw Start address of the USB Wrap registers
* @param enable Whether to enable the USB FSLS PHY pads
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_pad(usb_wrap_dev_t *hw, bool enable)
{
hw->otg_conf.pad_enable = enable;
}
/**
* @brief Set USB FSLS PHY TX output clock edge
*
* @param hw Start address of the USB Wrap registers
* @param clk_neg_edge True if TX output at negedge, posedge otherwise
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_tx_edge(usb_wrap_dev_t *hw, bool clk_neg_edge)
{
hw->otg_conf.phy_tx_edge_sel = clk_neg_edge;
}
/* ------------------------------ USB PHY Test ------------------------------ */
/**
* @brief Enable the USB FSLS PHY's test mode
*
* @param hw Start address of the USB Wrap registers
* @param enable Whether to enable the USB FSLS PHY's test mode
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_enable_test_mode(usb_wrap_dev_t *hw, bool enable)
{
hw->test_conf.test_enable = enable;
}
/**
* @brief Set the USB FSLS PHY's signal test values
*
* @param hw Start address of the USB Wrap registers
* @param vals Test values to set
*/
FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw, const usb_wrap_test_mode_vals_t *vals)
{
usb_wrap_test_conf_reg_t test_conf;
test_conf.val = hw->test_conf.val;
test_conf.test_usb_wrap_oe = vals->tx_enable_n;
test_conf.test_tx_dp = vals->tx_dp;
test_conf.test_tx_dm = vals->tx_dm;
test_conf.test_rx_rcv = vals->rx_rcv;
test_conf.test_rx_dp = vals->rx_dp;
test_conf.test_rx_dm = vals->rx_dm;
hw->test_conf.val = test_conf.val;
}
/* ----------------------------- RCC Functions ----------------------------- */
/**
* Enable the bus clock for USB Wrap module
* @param clk_en True if enable the clock of USB Wrap module
*/
FORCE_INLINE_ATTR void _usb_wrap_ll_enable_bus_clock(bool clk_en)
{
SYSTEM.perip_clk_en0.usb_clk_en = clk_en;
}
// SYSTEM.perip_clk_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_wrap_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* @brief Reset the USB Wrap module
*/
FORCE_INLINE_ATTR void _usb_wrap_ll_reset_register(void)
{
SYSTEM.perip_rst_en0.usb_rst = 1;
SYSTEM.perip_rst_en0.usb_rst = 0;
}
// SYSTEM.perip_rst_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_reset_register(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_usb_wrap_ll_reset_register(__VA_ARGS__); \
} while(0)
#ifdef __cplusplus
}
#endif
-846
View File
@@ -1,846 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "soc/soc_caps.h"
/*
This header is shared across all targets. Resolve to an empty header for targets
that don't support USB OTG.
*/
#if SOC_USB_OTG_SUPPORTED
#include <stdint.h>
#include <stdbool.h>
#include "hal/usb_dwc_ll.h"
#include "hal/usb_dwc_types.h"
#include "hal/assert.h"
#endif // SOC_USB_OTG_SUPPORTED
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_USB_OTG_SUPPORTED
// ------------------------------------------------ Macros and Types ---------------------------------------------------
// ----------------------- Configs -------------------------
/**
* @brief MPS limits based on FIFO configuration
*
* In bytes
*
* The resulting values depend on
* 1. FIFO total size (chip specific)
* 2. Set FIFO bias
*/
typedef struct {
unsigned int in_mps; /**< Maximum packet size of IN packet */
unsigned int non_periodic_out_mps; /**< Maximum packet size of BULK and CTRL OUT packets */
unsigned int periodic_out_mps; /**< Maximum packet size of INTR and ISOC OUT packets */
} usb_hal_fifo_mps_limits_t;
/**
* @brief FIFO size configuration structure
*/
typedef struct {
uint32_t rx_fifo_lines; /**< Size of the RX FIFO in terms the number of FIFO lines */
uint32_t nptx_fifo_lines; /**< Size of the Non-periodic FIFO in terms the number of FIFO lines */
uint32_t ptx_fifo_lines; /**< Size of the Periodic FIFO in terms the number of FIFO lines */
} usb_dwc_hal_fifo_config_t;
// --------------------- HAL Events ------------------------
/**
* @brief Host port HAL events
*/
typedef enum {
USB_DWC_HAL_PORT_EVENT_NONE, /**< No event occurred, or could not decode interrupt */
USB_DWC_HAL_PORT_EVENT_CHAN, /**< A channel event has occurred. Call the the channel event handler instead */
USB_DWC_HAL_PORT_EVENT_CONN, /**< The host port has detected a connection */
USB_DWC_HAL_PORT_EVENT_DISCONN, /**< The host port has been disconnected */
USB_DWC_HAL_PORT_EVENT_ENABLED, /**< The host port has been enabled (i.e., connected to a device that has been reset. Started sending SOFs) */
USB_DWC_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) */
USB_DWC_HAL_PORT_EVENT_OVRCUR, /**< The host port has encountered an overcurrent condition */
USB_DWC_HAL_PORT_EVENT_OVRCUR_CLR, /**< The host port has been cleared of the overcurrent condition */
} usb_dwc_hal_port_event_t;
/**
* @brief Channel events
*/
typedef enum {
USB_DWC_HAL_CHAN_EVENT_CPLT, /**< The channel has completed execution of a transfer descriptor that had the USB_DWC_HAL_XFER_DESC_FLAG_HOC flag set. Channel is now halted */
USB_DWC_HAL_CHAN_EVENT_ERROR, /**< The channel has encountered an error. Channel is now halted. */
USB_DWC_HAL_CHAN_EVENT_HALT_REQ, /**< The channel has been successfully halted as requested */
USB_DWC_HAL_CHAN_EVENT_NONE, /**< No event (interrupt ran for internal processing) */
} usb_dwc_hal_chan_event_t;
// --------------------- HAL Errors ------------------------
/**
* @brief Channel errors
*/
typedef enum {
USB_DWC_HAL_CHAN_ERROR_XCS_XACT = 0, /**< Excessive (three consecutive) transaction errors (e.g., no response, bad CRC etc */
USB_DWC_HAL_CHAN_ERROR_BNA, /**< Buffer Not Available error (i.e., An inactive transfer descriptor was fetched by the channel) */
USB_DWC_HAL_CHAN_ERROR_PKT_BBL, /**< Packet babbler error (packet exceeded MPS) */
USB_DWC_HAL_CHAN_ERROR_STALL, /**< STALL response received */
} usb_dwc_hal_chan_error_t;
// ------------- Transfer Descriptor Related ---------------
/**
* @brief Flags used to describe the type of transfer descriptor to fill
*/
#define USB_DWC_HAL_XFER_DESC_FLAG_IN 0x01 /**< Indicates this transfer descriptor is of the IN direction */
#define USB_DWC_HAL_XFER_DESC_FLAG_SETUP 0x02 /**< Indicates this transfer descriptor is an OUT setup */
#define USB_DWC_HAL_XFER_DESC_FLAG_HOC 0x04 /**< Indicates that the channel will be halted after this transfer descriptor completes */
/**
* @brief Status value of a transfer descriptor
*
* A transfer descriptor's status remains unexecuted until the entire transfer descriptor completes (either successfully
* or an error). Therefore, if a channel halt is requested before a transfer descriptor completes, the transfer
* descriptor remains unexecuted.
*/
#define USB_DWC_HAL_XFER_DESC_STS_SUCCESS USB_DWC_LL_QTD_STATUS_SUCCESS
#define USB_DWC_HAL_XFER_DESC_STS_PKTERR USB_DWC_LL_QTD_STATUS_PKTERR
#define USB_DWC_HAL_XFER_DESC_STS_BUFFER_ERR USB_DWC_LL_QTD_STATUS_BUFFER
#define USB_DWC_HAL_XFER_DESC_STS_NOT_EXECUTED USB_DWC_LL_QTD_STATUS_NOT_EXECUTED
// -------------------- Object Types -----------------------
/**
* @brief Endpoint characteristics structure
*/
typedef struct {
union {
struct {
usb_dwc_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 ls_via_fs_hub: 1; /**< The endpoint is on a LS device that is routed through an FS hub.
Setting this bit will lead to the addition of the PREamble packet */
uint32_t reserved2: 2;
};
uint32_t val;
};
struct {
unsigned int interval; /**< The interval of the endpoint in frames (FS) or microframes (HS) */
uint32_t offset; /**< Offset of this channel in the periodic scheduler */
bool is_hs; /**< This endpoint is HighSpeed. Needed for Periodic Frame List (HAL layer) scheduling */
} periodic; /**< Characteristic for periodic (interrupt/isochronous) endpoints only */
} usb_dwc_hal_ep_char_t;
/**
* @brief Channel object
*/
typedef struct {
//Channel control, status, and information
union {
struct {
uint32_t active: 1; /**< Debugging bit to indicate whether channel is enabled */
uint32_t halt_requested: 1; /**< A halt has been requested */
uint32_t reserved: 2;
uint32_t chan_idx: 4; /**< The index number of the channel */
uint32_t reserved24: 24;
};
uint32_t val;
} flags; /**< Flags regarding channel's status and information */
usb_dwc_host_chan_regs_t *regs; /**< Pointer to the channel's register set */
usb_dwc_hal_chan_error_t error; /**< The last error that occurred on the channel */
usb_dwc_xfer_type_t type; /**< The transfer type of the channel */
void *chan_ctx; /**< Context variable for the owner of the channel */
} usb_dwc_hal_chan_t;
/**
* @brief HAL context structure
*/
typedef struct {
// HW context
usb_dwc_dev_t *dev; /**< Pointer to base address of DWC_OTG registers */
// Host Port related
uint32_t *periodic_frame_list; /**< Pointer to scheduling frame list */
usb_hal_frame_list_len_t frame_list_len; /**< Length of the periodic scheduling frame list */
// FIFO related
usb_dwc_hal_fifo_config_t fifo_config; /**< FIFO sizes configuration */
// Configuration of the USB-DWC core. Read from read-only HW registers
struct {
unsigned chan_num_total; /**< Total number of channels for this configuration */
unsigned hsphy_type; /**< HS PHY type of this configuration */
unsigned fifo_size; /**< Total FIFO size [in lines] in this configuration */
} constant_config;
union {
struct {
uint32_t dbnc_lock_enabled: 1; /**< Debounce lock enabled */
uint32_t fifo_sizes_set: 1; /**< Whether the FIFO sizes have been set or not */
uint32_t periodic_sched_enabled: 1; /**< Periodic scheduling (for interrupt and isochronous transfers) is enabled */
uint32_t reserved: 5;
uint32_t reserved24: 24;
};
uint32_t val;
} flags;
// Channel related
struct {
int num_allocated; /**< Number of channels currently allocated */
uint32_t chan_pend_intrs_msk; /**< Bit mask of channels with pending interrupts */
usb_dwc_hal_chan_t **hdls; /**< Handles of each channel. Set to NULL if channel has not been allocated */
} channels;
} usb_dwc_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
* - The USB PHY (internal or external) and associated GPIOs must already be configured
* - GPIO pins configured
* - Interrupt allocated but DISABLED (in case of an unknown interrupt state)
* Exit:
* - Checks to see if DWC_OTG is alive, and if HW version/config is correct
* - HAL context initialized
* - Read and save relevant USB-DWC configuration parameters
* - 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 -> USB_DWC_HAL_PORT_STATE_OTG
* - Interrupts cleared. Users can now enable their ISR
*
* @attention The user must allocate memory for channel handlers with
* `hal->channels.hdls = malloc(hal->constant_config.chan_num_total * sizeof(usb_dwc_hal_chan_t*))`
* @param[inout] hal Context of the HAL layer
* @param[in] port_id USB port ID
*/
void usb_dwc_hal_init(usb_dwc_hal_context_t *hal, int port_id);
/**
* @brief Deinitialize the HAL context
*
* Entry:
* - All channels must 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 usb_dwc_hal_deinit(usb_dwc_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 and channel registers.
* This function will result in the host port being put back into same state as after calling usb_dwc_hal_init().
*
* @note This has nothing to do with a USB bus reset. It simply resets the peripheral
*
* @param[in] hal Context of the HAL layer
*/
void usb_dwc_hal_core_soft_reset(usb_dwc_hal_context_t *hal);
/**
* @brief Check if FIFO configuration is valid
*
* This function checks that the sum of FIFO sizes does not exceed available space.
* It does not modify hardware state and is safe to call from HAL or upper layers.
*
* @param[in] hal Pointer to HAL context (must be initialized)
* @param[in] config Pointer to FIFO config to validate
* @return true if config is valid, false otherwise
*/
bool usb_dwc_hal_fifo_config_is_valid(const usb_dwc_hal_context_t *hal, const usb_dwc_hal_fifo_config_t *config);
/**
* @brief Set the FIFO sizes of the USB-DWC core
*
* This function programs the FIFO sizing registers (RX FIFO, Non-Periodic TX FIFO,
* and Periodic TX FIFO) based on the provided configuration. It must be called
* during USB initialization, before any channels are allocated or transfers started.
*
* The sum of all FIFO sizes must not exceed the hardware-defined limit
* (see HWCFG3.DfifoDepth and EPINFO_CTL).
*
* @note This function must be called exactly once during initialization and after
* each USB port reset. It is typically used internally by the USB Host stack.
*
* @param[inout] hal Pointer to the HAL context
* @param[in] config Pointer to the FIFO configuration to apply (must be valid)
*/
void usb_dwc_hal_set_fifo_config(usb_dwc_hal_context_t *hal, const usb_dwc_hal_fifo_config_t *config);
/**
* @brief Get MPS limits
*
* @param[in] hal Context of the HAL layer
* @param[out] mps_limits MPS limits
*/
void usb_dwc_hal_get_mps_limits(usb_dwc_hal_context_t *hal, usb_hal_fifo_mps_limits_t *mps_limits);
// ---------------------------------------------------- Host Port ------------------------------------------------------
// ------------------ Host Port Control --------------------
/**
* @brief Initialize the host port
*
* - Will enable the host port's interrupts allowing port and channel events to occur
*
* @param hal Context of the HAL layer
*/
static inline void usb_dwc_hal_port_init(usb_dwc_hal_context_t *hal)
{
//Configure Host related interrupts
usb_dwc_ll_haintmsk_dis_chan_intr(hal->dev, 0xFFFFFFFF); //Disable interrupts for all channels
usb_dwc_ll_gintmsk_en_intrs(hal->dev, USB_DWC_LL_INTR_CORE_PRTINT | USB_DWC_LL_INTR_CORE_HCHINT);
}
/**
* @brief Deinitialize the host port
*
* - Will disable the host port's interrupts preventing further port aand channel events from occurring
*
* @param hal Context of the HAL layer
*/
static inline void usb_dwc_hal_port_deinit(usb_dwc_hal_context_t *hal)
{
//Disable Host port and channel interrupts
usb_dwc_ll_gintmsk_dis_intrs(hal->dev, USB_DWC_LL_INTR_CORE_PRTINT | USB_DWC_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 usb_dwc_hal_port_toggle_power(usb_dwc_hal_context_t *hal, bool power_on)
{
if (power_on) {
usb_dwc_ll_hprt_en_pwr(hal->dev);
} else {
usb_dwc_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 USB_DWC_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
* USB_DWC_HAL_PORT_EVENT_DISABLED event. The host port will not be enabled until the reset signal is released (thus
* generating the USB_DWC_HAL_PORT_EVENT_ENABLED event)
*
* @param hal Context of the HAL layer
* @param enable Enable/disable reset signal
*/
static inline void usb_dwc_hal_port_toggle_reset(usb_dwc_hal_context_t *hal, bool enable)
{
usb_dwc_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 usb_dwc_hal_port_enable(usb_dwc_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 usb_dwc_hal_port_disable(usb_dwc_hal_context_t *hal)
{
usb_dwc_ll_hprt_port_dis(hal->dev);
}
/**
* @brief Suspend the host port
*
* @param hal Context of the HAL layers
*/
static inline void usb_dwc_hal_port_suspend(usb_dwc_hal_context_t *hal)
{
usb_dwc_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 usb_dwc_hal_port_toggle_resume(usb_dwc_hal_context_t *hal, bool enable)
{
if (enable) {
usb_dwc_ll_hprt_set_port_resume(hal->dev);
} else {
usb_dwc_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 usb_dwc_hal_port_check_resume(usb_dwc_hal_context_t *hal)
{
return usb_dwc_ll_hprt_get_port_resume(hal->dev);
}
// ---------------- Host Port Scheduling -------------------
/**
* @brief Sets the periodic scheduling frame list
*
* @note This function must be called before attempting configuring any channels to be period via
* usb_dwc_hal_chan_set_ep_char()
*
* @param hal Context of the HAL layer
* @param frame_list Base address of the frame list
* @param frame_list_len Number of entries in the frame list (can only be 8, 16, 32, 64)
*/
static inline void usb_dwc_hal_port_set_frame_list(usb_dwc_hal_context_t *hal, uint32_t *frame_list, usb_hal_frame_list_len_t len)
{
//Clear and save frame list
hal->periodic_frame_list = frame_list;
hal->frame_list_len = len;
}
/**
* @brief Enable periodic scheduling
*
* @note The periodic frame list must be set via usb_dwc_hal_port_set_frame_list() should be set before calling this
* function
* @note This function must be called before activating any periodic channels
*
* @param hal Context of the HAL layer
*/
static inline void usb_dwc_hal_port_periodic_enable(usb_dwc_hal_context_t *hal)
{
HAL_ASSERT(hal->periodic_frame_list != NULL);
usb_dwc_ll_hflbaddr_set_base_addr(hal->dev, (uint32_t)hal->periodic_frame_list);
usb_dwc_ll_hcfg_set_num_frame_list_entries(hal->dev, hal->frame_list_len);
usb_dwc_ll_hcfg_en_perio_sched(hal->dev);
hal->flags.periodic_sched_enabled = 1;
}
/**
* @brief Disable periodic scheduling
*
* Disabling periodic scheduling will save a bit of DMA bandwidth (as the controller will no longer fetch the schedule
* from the frame list).
*
* @note Before disabling periodic scheduling, it is the user's responsibility to ensure that all periodic channels have
* halted safely.
*
* @param hal Context of the HAL layer
*/
static inline void usb_dwc_hal_port_periodic_disable(usb_dwc_hal_context_t *hal)
{
HAL_ASSERT(hal->flags.periodic_sched_enabled);
usb_dwc_ll_hcfg_dis_perio_sched(hal->dev);
hal->flags.periodic_sched_enabled = 0;
}
static inline uint32_t usb_dwc_hal_port_get_cur_frame_num(usb_dwc_hal_context_t *hal)
{
return usb_dwc_ll_hfnum_get_frame_num(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
* - USB_DWC_HAL_PORT_EVENT_CONN
* - USB_DWC_HAL_PORT_EVENT_DISCONN
*
* @note No other connection/disconnection event will occur again until the debounce lock is disabled via
* usb_dwc_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 usb_dwc_hal_port_check_if_connected(usb_dwc_hal_context_t *hal)
{
return usb_dwc_ll_hprt_get_conn_status(hal->dev);
}
/**
* @brief Check the speed 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_dwc_speed_t Speed of the connected device
*/
static inline usb_dwc_speed_t usb_dwc_hal_port_get_conn_speed(usb_dwc_hal_context_t *hal)
{
return usb_dwc_ll_hprt_get_speed(hal->dev);
}
/**
* @brief Disable the debounce lock
*
* This function must be called after calling usb_dwc_hal_port_check_if_connected() and will allow connection/disconnection
* events to occur again. Any pending connection or disconnection interrupts are cleared.
*
* @param hal Context of the HAL layer
*/
static inline void usb_dwc_hal_disable_debounce_lock(usb_dwc_hal_context_t *hal)
{
hal->flags.dbnc_lock_enabled = 0;
//Clear Connection and disconnection interrupt in case it triggered again
usb_dwc_ll_gintsts_clear_intrs(hal->dev, USB_DWC_LL_INTR_CORE_DISCONNINT);
usb_dwc_ll_hprt_intr_clear(hal->dev, USB_DWC_LL_INTR_HPRT_PRTCONNDET);
//Re-enable the hprt (connection) and disconnection interrupts
usb_dwc_ll_gintmsk_en_intrs(hal->dev, USB_DWC_LL_INTR_CORE_PRTINT | USB_DWC_LL_INTR_CORE_DISCONNINT);
}
/**
* @brief Check if the root port is suspended
*
* This function checks if the root port entered suspended state, after calling usb_dwc_hal_port_suspend()
*
* @param hal Context of the HAL layer
* @return true The root port is suspended
* @return false The root port is not suspended
*/
static inline bool usb_dwc_hal_port_check_if_suspended(usb_dwc_hal_context_t *hal)
{
return usb_dwc_ll_hprt_get_port_suspend(hal->dev);
}
// ----------------------------------------------------- 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 usb_dwc_hal_chan_alloc(usb_dwc_hal_context_t *hal, usb_dwc_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 usb_dwc_hal_chan_free(usb_dwc_hal_context_t *hal, usb_dwc_hal_chan_t *chan_obj);
// ---------------- Channel Configuration ------------------
/**
* @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 *usb_dwc_hal_chan_get_context(usb_dwc_hal_chan_t *chan_obj)
{
return chan_obj->chan_ctx;
}
/**
* @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 hal Context of the HAL layer
* @param chan_obj Channel object
* @param ep_char Endpoint characteristics
*/
void usb_dwc_hal_chan_set_ep_char(usb_dwc_hal_context_t *hal, usb_dwc_hal_chan_t *chan_obj, usb_dwc_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 halted
*
* @param chan_obj Channel object
* @param is_in Whether the direction is IN
*/
static inline void usb_dwc_hal_chan_set_dir(usb_dwc_hal_chan_t *chan_obj, bool is_in)
{
//Cannot change direction whilst channel is still active or in error
HAL_ASSERT(!chan_obj->flags.active);
usb_dwc_ll_hcchar_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 usb_dwc_hal_chan_set_pid(usb_dwc_hal_chan_t *chan_obj, int pid)
{
//Cannot change pid whilst channel is still active or in error
HAL_ASSERT(!chan_obj->flags.active);
//Update channel object and set the register
usb_dwc_ll_hctsiz_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 usb_dwc_hal_chan_get_pid(usb_dwc_hal_chan_t *chan_obj)
{
HAL_ASSERT(!chan_obj->flags.active);
return usb_dwc_ll_hctsiz_get_pid(chan_obj->regs);
}
// ------------------- Channel Control ---------------------
/**
* @brief Activate a channel
*
* Activating a channel will cause the channel to start executing transfer descriptors.
*
* @note This function should only be called on channels that were previously halted
* @note An event will be generated when the channel is halted
*
* @param chan_obj Channel object
* @param xfer_desc_list A filled transfer descriptor list
* @param desc_list_len Transfer descriptor list length
* @param start_idx Index of the starting transfer descriptor in the list
*/
void usb_dwc_hal_chan_activate(usb_dwc_hal_chan_t *chan_obj, void *xfer_desc_list, int desc_list_len, int start_idx);
/**
* @brief Get the index of the current transfer descriptor
*
* @param chan_obj Channel object
* @return int Descriptor index
*/
static inline int usb_dwc_hal_chan_get_qtd_idx(usb_dwc_hal_chan_t *chan_obj)
{
return usb_dwc_ll_hcdam_get_cur_qtd_idx(chan_obj->regs);
}
/**
* @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
* USB_DWC_HAL_CHAN_EVENT_HALT_REQ event before treating the channel as halted.
*
* @note When a transfer is in progress (i.e., the channel is active) and a halt is requested, the channel will halt
* after the next USB packet is completed. If the transfer has more pending packets, the transfer will just be
* marked as USB_DWC_HAL_XFER_DESC_STS_NOT_EXECUTED.
*
* @param chan_obj Channel object
* @return true The channel is already halted
* @return false The halt was requested, wait for USB_DWC_HAL_CHAN_EVENT_HALT_REQ
*/
bool usb_dwc_hal_chan_request_halt(usb_dwc_hal_chan_t *chan_obj);
/**
* @brief Indicate that a channel is halted after a port error
*
* When a port error occurs (e.g., disconnect, overcurrent):
* - Any previously active channels will remain active (i.e., they will not receive a channel interrupt)
* - Attempting to disable them using usb_dwc_hal_chan_request_halt() will NOT generate an interrupt for ISOC channels
* (probably something to do with the periodic scheduling)
*
* However, the channel's enable bit can be left as 1 since after a port error, a soft reset will be done anyways.
* This function simply updates the channels internal state variable to indicate it is halted (thus allowing it to be
* freed).
*
* @param chan_obj Channel object
*/
static inline void usb_dwc_hal_chan_mark_halted(usb_dwc_hal_chan_t *chan_obj)
{
chan_obj->flags.active = 0;
}
/**
* @brief Get a channel's error
*
* @param chan_obj Channel object
* @return usb_dwc_hal_chan_error_t The type of error the channel has encountered
*/
static inline usb_dwc_hal_chan_error_t usb_dwc_hal_chan_get_error(usb_dwc_hal_chan_t *chan_obj)
{
return chan_obj->error;
}
// -------------------------------------------- Transfer Descriptor List -----------------------------------------------
/**
* @brief Fill a single entry in a transfer descriptor list
*
* - Depending on the transfer type, a single transfer descriptor may corresponds
* - A stage of a transfer (for control transfers)
* - A frame of a transfer interval (for interrupt and isoc)
* - An entire transfer (for bulk transfers)
* - Check the various USB_DWC_HAL_XFER_DESC_FLAG_ flags for filling a specific type of descriptor
* - For IN transfer entries, set the USB_DWC_HAL_XFER_DESC_FLAG_IN. The transfer size must also be an integer multiple of
* the endpoint's MPS
*
* @note Critical section is not required for this function
*
* @param desc_list Transfer descriptor list
* @param desc_idx Transfer descriptor index
* @param xfer_data_buff Transfer data buffer
* @param xfer_len Transfer length
* @param flags Transfer flags
*/
static inline void usb_dwc_hal_xfer_desc_fill(void *desc_list, uint32_t desc_idx, uint8_t *xfer_data_buff, int xfer_len, uint32_t flags)
{
usb_dwc_ll_dma_qtd_t *qtd_list = (usb_dwc_ll_dma_qtd_t *)desc_list;
if (flags & USB_DWC_HAL_XFER_DESC_FLAG_IN) {
usb_dwc_ll_qtd_set_in(&qtd_list[desc_idx],
xfer_data_buff, xfer_len,
flags & USB_DWC_HAL_XFER_DESC_FLAG_HOC);
} else {
usb_dwc_ll_qtd_set_out(&qtd_list[desc_idx],
xfer_data_buff,
xfer_len,
flags & USB_DWC_HAL_XFER_DESC_FLAG_HOC,
flags & USB_DWC_HAL_XFER_DESC_FLAG_SETUP);
}
}
/**
* @brief Clear a transfer descriptor (sets all its fields to NULL)
*
* @param desc_list Transfer descriptor list
* @param desc_idx Transfer descriptor index
*/
static inline void usb_dwc_hal_xfer_desc_clear(void *desc_list, uint32_t desc_idx)
{
usb_dwc_ll_dma_qtd_t *qtd_list = (usb_dwc_ll_dma_qtd_t *)desc_list;
usb_dwc_ll_qtd_set_null(&qtd_list[desc_idx]);
}
/**
* @brief Parse a transfer decriptor's results
*
* @param desc_list Transfer descriptor list
* @param 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 usb_dwc_hal_xfer_desc_parse(void *desc_list, uint32_t desc_idx, int *xfer_rem_len, int *xfer_status)
{
usb_dwc_ll_dma_qtd_t *qtd_list = (usb_dwc_ll_dma_qtd_t *)desc_list;
usb_dwc_ll_qtd_get_status(&qtd_list[desc_idx], xfer_rem_len, xfer_status);
//Clear the QTD to prevent it from being read again
usb_dwc_ll_qtd_set_null(&qtd_list[desc_idx]);
}
// ------------------------------------------------- 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 usb_dwc_hal_port_event_t Host port event
*/
usb_dwc_hal_port_event_t usb_dwc_hal_decode_intr(usb_dwc_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.
*
* @param hal Context of the HAL layer
* @return usb_dwc_hal_chan_t* Channel object. NULL if no channel are pending an interrupt.
*/
usb_dwc_hal_chan_t *usb_dwc_hal_get_chan_pending_intr(usb_dwc_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
* @note If the host port has an error (e.g., a sudden disconnect or an port error), any active channels will not
* receive an interrupt. Each active channel must be manually halted.
* @return usb_dwc_hal_chan_event_t Channel event
*/
usb_dwc_hal_chan_event_t usb_dwc_hal_chan_decode_intr(usb_dwc_hal_chan_t *chan_obj);
#endif // SOC_USB_OTG_SUPPORTED
#ifdef __cplusplus
}
#endif
@@ -1,56 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
Note: This header file contains USB2.0 related types and macros that can be used by code specific to the DWC_OTG
controller (i.e., the HW specific layers of the USB host stack). Thus, this header is only meant to be used below (and
including) the HAL layer. For types and macros that are HW implementation agnostic (i.e., HCD layer and above), add them
to the "usb/usb_types_ch9.h" header instead.
*/
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief USB speeds supported by the DWC OTG controller
*
* @note usb_dwc_speed_t enum values must match the values of the DWC_OTG prtspd register field
*/
typedef enum {
USB_DWC_SPEED_HIGH = 0,
USB_DWC_SPEED_FULL = 1,
USB_DWC_SPEED_LOW = 2,
} usb_dwc_speed_t;
/**
* @brief USB transfer types supported by the DWC OTG controller
*
* @note usb_dwc_xfer_type_t enum values must match the values of the DWC_OTG hcchar register field
*/
typedef enum {
USB_DWC_XFER_TYPE_CTRL = 0,
USB_DWC_XFER_TYPE_ISOCHRONOUS = 1,
USB_DWC_XFER_TYPE_BULK = 2,
USB_DWC_XFER_TYPE_INTR = 3,
} usb_dwc_xfer_type_t;
/**
* @brief Enumeration of different possible lengths of the periodic frame list
*/
typedef enum {
USB_HAL_FRAME_LIST_LEN_8 = 8,
USB_HAL_FRAME_LIST_LEN_16 = 16,
USB_HAL_FRAME_LIST_LEN_32 = 32,
USB_HAL_FRAME_LIST_LEN_64 = 64,
} usb_hal_frame_list_len_t;
#ifdef __cplusplus
}
#endif
@@ -1,65 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
Note: This header file contains USB2.0 related types and macros that can be used by code specific to the DWC_OTG
controller (i.e., the HW specific layers of the USB host stack). Thus, this header is only meant to be used below (and
including) the HAL layer. For types and macros that are HW implementation agnostic (i.e., HCD layer and above), add them
to the "usb/usb_types_ch9.h" header instead.
*/
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief USB PHY target
*/
typedef enum {
USB_PHY_TARGET_INT, /**< USB target is internal FSLS PHY */
USB_PHY_TARGET_UTMI, /**< USB target is internal UTMI PHY */
USB_PHY_TARGET_EXT, /**< USB target is external PHY */
USB_PHY_TARGET_MAX,
} usb_phy_target_t;
/**
* @brief USB PHY source
*/
typedef enum {
USB_PHY_CTRL_OTG, /**< PHY controller is USB OTG */
#if SOC_USB_SERIAL_JTAG_SUPPORTED
USB_PHY_CTRL_SERIAL_JTAG, /**< PHY controller is USB Serial JTAG */
#endif
USB_PHY_CTRL_MAX,
} usb_phy_controller_t;
/**
* @brief USB OTG mode
*/
typedef enum {
USB_PHY_MODE_DEFAULT, /**< USB OTG default mode */
USB_OTG_MODE_HOST, /**< USB OTG host mode */
USB_OTG_MODE_DEVICE, /**< USB OTG device mode */
USB_OTG_MODE_MAX,
} usb_otg_mode_t;
/**
* @brief USB speed
*/
typedef enum {
USB_PHY_SPEED_UNDEFINED,
USB_PHY_SPEED_LOW, /**< USB Low Speed (1.5 Mbit/s) */
USB_PHY_SPEED_FULL, /**< USB Full Speed (12 Mbit/s) */
USB_PHY_SPEED_HIGH, /**< USB High Speed (480 Mbit/s) */
USB_PHY_SPEED_MAX,
} usb_phy_speed_t;
#ifdef __cplusplus
}
#endif
-64
View File
@@ -1,64 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "soc/soc_caps.h"
#if (SOC_USB_UTMI_PHY_NUM > 0)
#include "soc/usb_utmi_struct.h"
#include "hal/usb_utmi_ll.h"
#endif // (SOC_USB_UTMI_PHY_NUM > 0)
#ifdef __cplusplus
extern "C" {
#endif
#if (SOC_USB_UTMI_PHY_NUM > 0)
/**
* @brief HAL context type of USB UTMI driver
*/
typedef struct {
usb_utmi_dev_t *dev;
} usb_utmi_hal_context_t;
/**
* @brief Sets UTMI defaults
*
* Enable clock, reset the peripheral, sets default options (LS support, disconnection detection)
*
* @param[in] hal USB UTMI HAL context
*/
void _usb_utmi_hal_init(usb_utmi_hal_context_t *hal);
#if SOC_RCC_IS_INDEPENDENT
#define usb_utmi_hal_init(...) _usb_utmi_hal_init(__VA_ARGS__)
#else
// Use a macro to wrap the function, force the caller to use it in a critical section
// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define usb_utmi_hal_init(...) do {(void)__DECLARE_RCC_ATOMIC_ENV; _usb_utmi_hal_init(__VA_ARGS__);} while(0)
#endif
/**
* @brief Disable UTMI
*
* Disable clock to the peripheral
*/
void _usb_utmi_hal_disable(void);
#if SOC_RCC_IS_INDEPENDENT
#define usb_utmi_hal_disable(...) _usb_utmi_hal_disable(__VA_ARGS__)
#else
// Use a macro to wrap the function, force the caller to use it in a critical section
// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define usb_utmi_hal_disable(...) do {(void)__DECLARE_RCC_ATOMIC_ENV; _usb_utmi_hal_disable(__VA_ARGS__);} while(0)
#endif
#endif // (SOC_USB_UTMI_PHY_NUM > 0)
#ifdef __cplusplus
}
#endif
-119
View File
@@ -1,119 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "soc/soc_caps.h"
#if (SOC_USB_OTG_PERIPH_NUM > 0)
#include "soc/usb_wrap_struct.h"
#include "hal/usb_wrap_ll.h"
#endif // (SOC_USB_OTG_PERIPH_NUM > 0)
#include "hal/usb_wrap_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (SOC_USB_OTG_PERIPH_NUM > 0)
/**
* @brief HAL context type of USB WRAP driver
*/
typedef struct {
usb_wrap_dev_t *dev;
} usb_wrap_hal_context_t;
/**
* @brief Initialize the USB WRAP HAL driver
*
* @param hal USB WRAP HAL context
*/
void _usb_wrap_hal_init(usb_wrap_hal_context_t *hal);
#if SOC_RCC_IS_INDEPENDENT
#define usb_wrap_hal_init(...) _usb_wrap_hal_init(__VA_ARGS__)
#else
// Use a macro to wrap the function, force the caller to use it in a critical section
// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define usb_wrap_hal_init(...) do {(void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_hal_init(__VA_ARGS__);} while(0)
#endif
/**
* @brief Disable USB WRAP
*
* Disable clock to the peripheral
*/
void _usb_wrap_hal_disable(void);
#if SOC_RCC_IS_INDEPENDENT
#define usb_wrap_hal_disable(...) _usb_wrap_hal_disable(__VA_ARGS__)
#else
// Use a macro to wrap the function, force the caller to use it in a critical section
// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define usb_wrap_hal_disable(...) do {(void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_hal_disable(__VA_ARGS__);} while(0)
#endif
/* ---------------------------- USB PHY Control ---------------------------- */
#if USB_WRAP_LL_EXT_PHY_SUPPORTED
/**
* @brief Configure whether USB WRAP is routed to internal/external FSLS PHY
*
* @param hal USB WRAP HAL context
* @param external True if external, False if internal
*/
void usb_wrap_hal_phy_set_external(usb_wrap_hal_context_t *hal, bool external);
#endif // USB_WRAP_LL_EXT_PHY_SUPPORTED
/**
* @brief Enables and sets override of pull up/down resistors
*
* @param hal USB WRAP HAL context
* @param vals Override values
*/
static inline void usb_wrap_hal_phy_enable_pull_override(usb_wrap_hal_context_t *hal, const usb_wrap_pull_override_vals_t *vals)
{
usb_wrap_ll_phy_enable_pull_override(hal->dev, vals);
}
/**
* @brief Disables pull up/down resistor override
*
* @param hal USB WRAP HAL context
*/
static inline void usb_wrap_hal_phy_disable_pull_override(usb_wrap_hal_context_t *hal)
{
usb_wrap_ll_phy_disable_pull_override(hal->dev);
}
/**
* @brief Enables/disables the USB FSLS PHY's test mode
*
* @param hal USB WRAP HAL context
* @param enable Whether to enable test mode
*/
static inline void usb_wrap_hal_phy_enable_test_mode(usb_wrap_hal_context_t *hal, bool enable)
{
usb_wrap_ll_phy_enable_test_mode(hal->dev, enable);
}
/**
* @brief Set the USB FSLS PHY's signal test values
*
* @param hal USB WRAP HAL context
* @param vals Test values
*/
static inline void usb_wrap_hal_phy_test_mode_set_signals(usb_wrap_hal_context_t *hal, const usb_wrap_test_mode_vals_t *vals)
{
usb_wrap_ll_phy_test_mode_set_signals(hal->dev, vals);
}
#endif // (SOC_USB_OTG_PERIPH_NUM > 0)
#ifdef __cplusplus
}
#endif
@@ -1,53 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (SOC_USB_OTG_PERIPH_NUM > 0)
/**
* @brief USB WRAP pull up/down resistor override values
*
* Specifies whether each pull up/down resistor should be enabled/disabled when
* overriding connected USB PHY's pull resistors.
*/
typedef struct {
bool dp_pu; /**< D+ pull-up resistor enable/disable */
bool dm_pu; /**< D- pull-up resistor enable/disable */
bool dp_pd; /**< D+ pull-down resistor enable/disable */
bool dm_pd; /**< D- pull-down resistor enable/disable */
} usb_wrap_pull_override_vals_t;
/**
* @brief USB WRAP test mode values
*
* Specifies the logic values of each of the USB FSLS Serial PHY interface
* signals when in test mode.
*
* @note See section "2.2.1.13 FsLsSerialMode" of UTMI+ specification for more
* details of each signal.
*/
typedef struct {
bool tx_enable_n; /**< Active low output enable signal */
bool tx_dp; /**< Single-ended D+ line driver */
bool tx_dm; /**< Single-ended D- line driver */
bool rx_dp; /**< Single-ended D+ signal from the transceiver */
bool rx_dm; /**< Single-ended D- signal from the transceiver */
bool rx_rcv; /**< Differential receive data from D+ and D- lines */
} usb_wrap_test_mode_vals_t;
#endif // (SOC_USB_OTG_PERIPH_NUM > 0)
#ifdef __cplusplus
}
#endif
-544
View File
@@ -1,544 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdint.h>
#include <string.h> // For memset()
#include <stdlib.h> // For abort()
#include "soc/soc_caps_full.h"
#include "soc/chip_revision.h"
#include "soc/usb_periph.h"
#include "hal/usb_dwc_hal.h"
#include "hal/usb_dwc_ll.h"
#include "hal/efuse_hal.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
// Core register IDs supported by this driver: v4.00a and v4.30a
#define CORE_REG_GSNPSID_4_00a 0x4F54400A
#define CORE_REG_GSNPSID_4_30a 0x4F54430A
// -------------------- Configurable -----------------------
/**
* The following core interrupts will be enabled (listed LSB to MSB). Some of these
* interrupts are enabled later than others.
* - USB_DWC_LL_INTR_CORE_PRTINT
* - USB_DWC_LL_INTR_CORE_HCHINT
* - USB_DWC_LL_INTR_CORE_DISCONNINT
* The following PORT interrupts cannot be masked, listed LSB to MSB
* - USB_DWC_LL_INTR_HPRT_PRTCONNDET
* - USB_DWC_LL_INTR_HPRT_PRTENCHNG
* - USB_DWC_LL_INTR_HPRT_PRTOVRCURRCHNG
*/
#define CORE_INTRS_EN_MSK (USB_DWC_LL_INTR_CORE_DISCONNINT)
//Interrupts that pertain to core events
#define CORE_EVENTS_INTRS_MSK (USB_DWC_LL_INTR_CORE_DISCONNINT | \
USB_DWC_LL_INTR_CORE_HCHINT)
//Interrupt that pertain to host port events
#define PORT_EVENTS_INTRS_MSK (USB_DWC_LL_INTR_HPRT_PRTCONNDET | \
USB_DWC_LL_INTR_HPRT_PRTENCHNG | \
USB_DWC_LL_INTR_HPRT_PRTOVRCURRCHNG)
/**
* The following channel interrupt bits are currently checked (in order LSB to MSB)
* - USB_DWC_LL_INTR_CHAN_XFERCOMPL
* - USB_DWC_LL_INTR_CHAN_CHHLTD
* - USB_DWC_LL_INTR_CHAN_STALL
* - USB_DWC_LL_INTR_CHAN_BBLEER
* - USB_DWC_LL_INTR_CHAN_BNAINTR
* - USB_DWC_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 USB_DWC_LL_INTR_CHAN_CHHLTD bit
* - USB_DWC_LL_INTR_CHAN_XCS_XACT_ERR is always unmasked
* - When USB_DWC_LL_INTR_CHAN_BNAINTR occurs, USB_DWC_LL_INTR_CHAN_CHHLTD will NOT.
* - USB_DWC_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 USB_DWC_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 (USB_DWC_LL_INTR_CHAN_XFERCOMPL | \
USB_DWC_LL_INTR_CHAN_CHHLTD | \
USB_DWC_LL_INTR_CHAN_BNAINTR)
#define CHAN_INTRS_ERROR_MSK (USB_DWC_LL_INTR_CHAN_STALL | \
USB_DWC_LL_INTR_CHAN_BBLEER | \
USB_DWC_LL_INTR_CHAN_BNAINTR | \
USB_DWC_LL_INTR_CHAN_XCS_XACT_ERR)
// -------------------------------------------------- Core (Global) ----------------------------------------------------
static void set_defaults(usb_dwc_hal_context_t *hal)
{
//GAHBCFG register
usb_dwc_ll_gahbcfg_en_dma_mode(hal->dev);
int hbstlen = 0; //Use AHB burst SINGLE by default
#if SOC_IS(ESP32S2)
/*
Hardware errata workaround for the ESP32-S2 ECO0 (see ESP32-S2 Errata Document section 4.0 for full details).
ESP32-S2 ECO0 has a hardware errata where the AHB bus arbiter may generate incorrect arbitration signals leading to
the DWC_OTG corrupting the DMA transfers of other peripherals (or vice versa) on the same bus. The peripherals that
share the same bus with DWC_OTG include I2C and SPI (see ESP32-S2 Errata Document for more details). To workaround
this, the DWC_OTG's AHB should use INCR mode to prevent change of arbitration during a burst operation, thus
avoiding this errata.
Note: Setting AHB burst to INCR increases the likeliness of DMA underruns on other peripherals sharing the same bus
arbiter as the DWC_OTG (e.g., I2C and SPI) as change of arbitration during the burst operation is not permitted.
Users should keep this limitation in mind when the DWC_OTG transfers large data payloads (e.g., 512 MPS transfers)
while this workaround is enabled.
*/
if (!ESP_CHIP_REV_ABOVE(efuse_hal_chip_revision(), 100)) {
hbstlen = 1; //Set AHB burst to INCR to workaround hardware errata
}
#endif // SOC_IS(ESP32S2)
usb_dwc_ll_gahbcfg_set_hbstlen(hal->dev, hbstlen); //Set AHB burst mode
//GUSBCFG register
usb_dwc_ll_gusbcfg_dis_hnp_cap(hal->dev); //Disable HNP
usb_dwc_ll_gusbcfg_dis_srp_cap(hal->dev); //Disable SRP
// If this USB-DWC supports HS PHY, use it
if (hal->constant_config.hsphy_type != 0) {
usb_dwc_ll_gusbcfg_set_timeout_cal(hal->dev, 5); // 5 PHY clocks for our HS PHY
usb_dwc_ll_gusbcfg_set_utmi_phy(hal->dev);
}
//Enable interruts
usb_dwc_ll_gintmsk_dis_intrs(hal->dev, 0xFFFFFFFF); //Mask all interrupts first
usb_dwc_ll_gintmsk_en_intrs(hal->dev, CORE_INTRS_EN_MSK); //Unmask global interrupts
usb_dwc_ll_gintsts_read_and_clear_intrs(hal->dev); //Clear interrupts
usb_dwc_ll_gahbcfg_en_global_intr(hal->dev); //Enable interrupt signal
//Enable host mode
usb_dwc_ll_gusbcfg_force_host_mode(hal->dev);
}
void usb_dwc_hal_init(usb_dwc_hal_context_t *hal, int port_id)
{
// Check if a peripheral is alive by reading the core ID registers
HAL_ASSERT(port_id < SOC_USB_OTG_PERIPH_NUM);
usb_dwc_dev_t *dev = USB_DWC_LL_GET_HW(port_id);
uint32_t core_id = usb_dwc_ll_gsnpsid_get_id(dev);
HAL_ASSERT(core_id == CORE_REG_GSNPSID_4_00a || core_id == CORE_REG_GSNPSID_4_30a);
(void) core_id; //Suppress unused variable warning if asserts are disabled
// Initialize HAL context
memset(hal, 0, sizeof(usb_dwc_hal_context_t));
hal->dev = dev;
// Save constant configuration of this USB-DWC instance
/*
* EPINFO_CTL is located at the end of FIFO, its size is fixed in HW.
* The reserved size is always the worst-case, which is device mode that requires 4 locations per EP direction (including EP0).
* Here we just read the FIFO size from HW register, to avoid any ambivalence
*/
hal->constant_config.fifo_size = usb_dwc_ll_ghwcfg_get_fifo_depth(dev);
hal->constant_config.hsphy_type = usb_dwc_ll_ghwcfg_get_hsphy_type(dev);
hal->constant_config.chan_num_total = usb_dwc_ll_ghwcfg_get_channel_num(dev);
set_defaults(hal);
}
void usb_dwc_hal_deinit(usb_dwc_hal_context_t *hal)
{
//Disable and clear global interrupt
usb_dwc_ll_gintmsk_dis_intrs(hal->dev, 0xFFFFFFFF); //Disable all interrupts
usb_dwc_ll_gintsts_read_and_clear_intrs(hal->dev); //Clear interrupts
usb_dwc_ll_gahbcfg_dis_global_intr(hal->dev); //Disable interrupt signal
hal->dev = NULL;
}
void usb_dwc_hal_core_soft_reset(usb_dwc_hal_context_t *hal)
{
usb_dwc_ll_grstctl_core_soft_reset(hal->dev);
while (!usb_dwc_ll_grstctl_is_ahb_idle(hal->dev)) {
; // Wait until AHB Master bus is idle before doing any other operations
}
// Set the default bits in USB-DWC registers
set_defaults(hal);
// Clear all the flags and channels
hal->periodic_frame_list = NULL;
hal->flags.val = 0;
hal->channels.num_allocated = 0;
hal->channels.chan_pend_intrs_msk = 0;
if (hal->channels.hdls) {
for (int i = 0; i < hal->constant_config.chan_num_total; i++) {
hal->channels.hdls[i] = NULL;
}
}
}
bool usb_dwc_hal_fifo_config_is_valid(const usb_dwc_hal_context_t *hal, const usb_dwc_hal_fifo_config_t *config)
{
if (!hal || !config) {
return false;
}
uint32_t used_lines = config->rx_fifo_lines + config->nptx_fifo_lines + config->ptx_fifo_lines;
return (used_lines <= hal->constant_config.fifo_size);
}
void usb_dwc_hal_set_fifo_config(usb_dwc_hal_context_t *hal, const usb_dwc_hal_fifo_config_t *config)
{
// Check internal HAL state
HAL_ASSERT(hal != NULL);
HAL_ASSERT(hal->channels.hdls != NULL);
// Validate provided config
HAL_ASSERT(config != NULL);
// Check if configuration exceeds available FIFO memory
HAL_ASSERT(usb_dwc_hal_fifo_config_is_valid(hal, config));
// Ensure no active channels (must only be called before USB install completes)
for (int i = 0; i < hal->constant_config.chan_num_total; i++) {
if (hal->channels.hdls[i] != NULL) {
HAL_ASSERT(!hal->channels.hdls[i]->flags.active);
}
}
// Program FIFO size registers
usb_dwc_ll_grxfsiz_set_fifo_size(hal->dev, config->rx_fifo_lines);// Set RX FIFO size (GRXFSIZ)
// Set Non-Periodic TX FIFO (GNPTXFSIZ)
// Offset = RX FIFO lines
usb_dwc_ll_gnptxfsiz_set_fifo_size(hal->dev, config->rx_fifo_lines, config->nptx_fifo_lines);
// Set Periodic TX FIFO (HPTXFSIZ)
// Offset = RX + NPTX
usb_dwc_ll_hptxfsiz_set_ptx_fifo_size(hal->dev,
config->rx_fifo_lines + config->nptx_fifo_lines,
config->ptx_fifo_lines);
// Flush all FIFOs
usb_dwc_ll_grstctl_flush_nptx_fifo(hal->dev);
usb_dwc_ll_grstctl_flush_ptx_fifo(hal->dev);
usb_dwc_ll_grstctl_flush_rx_fifo(hal->dev);
// Save configuration to HAL context
hal->fifo_config = *config;
hal->flags.fifo_sizes_set = 1;
}
void usb_dwc_hal_get_mps_limits(usb_dwc_hal_context_t *hal, usb_hal_fifo_mps_limits_t *mps_limits)
{
HAL_ASSERT(hal && mps_limits);
HAL_ASSERT(hal->flags.fifo_sizes_set);
const usb_dwc_hal_fifo_config_t *fifo_config = &(hal->fifo_config);
mps_limits->in_mps = (fifo_config->rx_fifo_lines - 2) * 4; // Two lines are reserved for status quadlets internally by USB_DWC
mps_limits->non_periodic_out_mps = fifo_config->nptx_fifo_lines * 4;
mps_limits->periodic_out_mps = fifo_config->ptx_fifo_lines * 4;
}
// ---------------------------------------------------- Host Port ------------------------------------------------------
static inline void debounce_lock_enable(usb_dwc_hal_context_t *hal)
{
//Disable the hprt (connection) and disconnection interrupts to prevent repeated triggerings
usb_dwc_ll_gintmsk_dis_intrs(hal->dev, USB_DWC_LL_INTR_CORE_PRTINT | USB_DWC_LL_INTR_CORE_DISCONNINT);
hal->flags.dbnc_lock_enabled = 1;
}
void usb_dwc_hal_port_enable(usb_dwc_hal_context_t *hal)
{
// Host Configuration
usb_dwc_ll_hcfg_en_scatt_gatt_dma(hal->dev); // Enable Scatther-Gather DMA mode
usb_dwc_ll_hcfg_dis_perio_sched(hal->dev); // Disable Periodic Scheduler (for now)
// Configure PHY clock: Only for USB-DWC with FSLS PHY
if (hal->constant_config.hsphy_type == 0) {
usb_dwc_ll_hcfg_set_fsls_phy_clock(hal->dev);
usb_dwc_ll_hfir_set_frame_interval(hal->dev);
}
}
// ----------------------------------------------------- Channel -------------------------------------------------------
// ----------------- Channel Allocation --------------------
bool usb_dwc_hal_chan_alloc(usb_dwc_hal_context_t *hal, usb_dwc_hal_chan_t *chan_obj, void *chan_ctx)
{
HAL_ASSERT(hal->channels.hdls);
HAL_ASSERT(hal->flags.fifo_sizes_set); //FIFO sizes should be set before attempting to allocate a channel
//Attempt to allocate channel
if (hal->channels.num_allocated == hal->constant_config.chan_num_total) {
return false; //Out of free channels
}
int chan_idx = -1;
for (int i = 0; i < hal->constant_config.chan_num_total; i++) {
if (hal->channels.hdls[i] == NULL) {
hal->channels.hdls[i] = chan_obj;
chan_idx = i;
hal->channels.num_allocated++;
break;
}
}
HAL_ASSERT(chan_idx != -1);
//Initialize channel object
memset(chan_obj, 0, sizeof(usb_dwc_hal_chan_t));
chan_obj->flags.chan_idx = chan_idx;
chan_obj->regs = usb_dwc_ll_chan_get_regs(hal->dev, chan_idx);
chan_obj->chan_ctx = chan_ctx;
//Note: EP characteristics configured separately
//Clean and unmask the channel's interrupt
usb_dwc_ll_hcint_read_and_clear_intrs(chan_obj->regs); //Clear the interrupt bits for that channel
usb_dwc_ll_haintmsk_en_chan_intr(hal->dev, 1 << chan_obj->flags.chan_idx);
usb_dwc_ll_hcintmsk_set_intr_mask(chan_obj->regs, CHAN_INTRS_EN_MSK); //Unmask interrupts for this channel
usb_dwc_ll_hctsiz_init(chan_obj->regs);
return true;
}
void usb_dwc_hal_chan_free(usb_dwc_hal_context_t *hal, usb_dwc_hal_chan_t *chan_obj)
{
HAL_ASSERT(hal->channels.hdls);
if (chan_obj->type == USB_DWC_XFER_TYPE_INTR || chan_obj->type == USB_DWC_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
usb_dwc_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_allocated--;
HAL_ASSERT(hal->channels.num_allocated >= 0);
}
// ---------------- Channel Configuration ------------------
void usb_dwc_hal_chan_set_ep_char(usb_dwc_hal_context_t *hal, usb_dwc_hal_chan_t *chan_obj, usb_dwc_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
usb_dwc_ll_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_DWC_XFER_TYPE_ISOCHRONOUS || ep_char->type == USB_DWC_XFER_TYPE_INTR) {
unsigned int interval_frame_list = ep_char->periodic.interval;
unsigned int offset_frame_list = ep_char->periodic.offset;
// Periodic Frame List works with USB frames. For HS endpoints we must divide interval[microframes] by 8 to get interval[frames]
if (ep_char->periodic.is_hs) {
interval_frame_list /= 8;
offset_frame_list /= 8;
}
// Interval in Periodic Frame List must be power of 2.
// This is not a HW restriction. It is just a lot easier to schedule channels like this.
if (interval_frame_list >= (int)hal->frame_list_len) { // Upper limits is Periodic Frame List length
interval_frame_list = (int)hal->frame_list_len;
} else if (interval_frame_list >= 32) {
interval_frame_list = 32;
} else if (interval_frame_list >= 16) {
interval_frame_list = 16;
} else if (interval_frame_list >= 8) {
interval_frame_list = 8;
} else if (interval_frame_list >= 4) {
interval_frame_list = 4;
} else if (interval_frame_list >= 2) {
interval_frame_list = 2;
} else { // Lower limit is 1
interval_frame_list = 1;
}
// Schedule the channel in the frame list
for (int i = 0; i < hal->frame_list_len; i+= interval_frame_list) {
int index = (offset_frame_list + i) % hal->frame_list_len;
hal->periodic_frame_list[index] |= 1 << chan_obj->flags.chan_idx;
}
// For HS endpoints we must write to sched_info field of HCTSIZ register to schedule microframes
// For FS endpoints sched_info is always 0xFF
// LS endpoints do not support periodic transfers
unsigned int tokens_per_frame = 0;
if (ep_char->periodic.is_hs) {
if (ep_char->periodic.interval >= 8) {
tokens_per_frame = 1; // 1 token every 8 microframes
} else if (ep_char->periodic.interval >= 4) {
tokens_per_frame = 2; // 1 token every 4 microframes
} else if (ep_char->periodic.interval >= 2) {
tokens_per_frame = 4; // 1 token every 2 microframes
} else {
tokens_per_frame = 8; // 1 token every microframe
}
} else {
tokens_per_frame = 8;
}
usb_dwc_ll_hctsiz_set_sched_info(chan_obj->regs, tokens_per_frame, ep_char->periodic.offset);
}
}
// ------------------- Channel Control ---------------------
void usb_dwc_hal_chan_activate(usb_dwc_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);
// Make sure that PING is not enabled from previous transaction
usb_dwc_ll_hctsiz_set_dopng(chan_obj->regs, false);
// Set start address of the QTD list and starting QTD index
usb_dwc_ll_hcdma_set_qtd_list_addr(chan_obj->regs, xfer_desc_list, start_idx);
usb_dwc_ll_hctsiz_set_qtd_list_len(chan_obj->regs, desc_list_len);
usb_dwc_ll_hcchar_enable_chan(chan_obj->regs); // Start the channel
chan_obj->flags.active = 1;
}
bool usb_dwc_hal_chan_request_halt(usb_dwc_hal_chan_t *chan_obj)
{
if (chan_obj->flags.active) {
/*
Request a halt so long as the channel's active flag is set.
- If the underlying hardware channel is already halted but the channel is pending interrupt handling,
disabling the channel will have no effect (i.e., no channel interrupt is generated).
- If the underlying channel is currently active, disabling the channel will trigger a channel interrupt.
Regardless, setting the "halt_requested" should cause "usb_dwc_hal_chan_decode_intr()" to report the
USB_DWC_HAL_CHAN_EVENT_HALT_REQ event when channel interrupt is handled (pending or triggered).
*/
usb_dwc_ll_hcchar_disable_chan(chan_obj->regs);
chan_obj->flags.halt_requested = 1;
return false;
} else {
//Channel was never active to begin with, simply return true
return true;
}
}
// ------------------------------------------------- Event Handling ----------------------------------------------------
usb_dwc_hal_port_event_t usb_dwc_hal_decode_intr(usb_dwc_hal_context_t *hal)
{
uint32_t intrs_core = usb_dwc_ll_gintsts_read_and_clear_intrs(hal->dev); //Read and clear core interrupts
uint32_t intrs_port = 0;
if (intrs_core & USB_DWC_LL_INTR_CORE_PRTINT) {
//There are host port interrupts. Read and clear those as well.
intrs_port = usb_dwc_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
usb_dwc_hal_port_event_t event = USB_DWC_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_DWC_LL_INTR_CORE_DISCONNINT) {
event = USB_DWC_HAL_PORT_EVENT_DISCONN;
debounce_lock_enable(hal);
//Mask the port connection and disconnection interrupts to prevent repeated triggering
} else if (intrs_port & USB_DWC_LL_INTR_HPRT_PRTOVRCURRCHNG) {
//Check if this is an overcurrent or an overcurrent cleared
if (usb_dwc_ll_hprt_get_port_overcur(hal->dev)) {
event = USB_DWC_HAL_PORT_EVENT_OVRCUR;
} else {
event = USB_DWC_HAL_PORT_EVENT_OVRCUR_CLR;
}
} else if (intrs_port & USB_DWC_LL_INTR_HPRT_PRTENCHNG) {
if (usb_dwc_ll_hprt_get_port_en(hal->dev)) { //Host port was enabled
event = USB_DWC_HAL_PORT_EVENT_ENABLED;
} else { //Host port has been disabled
event = USB_DWC_HAL_PORT_EVENT_DISABLED;
}
} else if (intrs_port & USB_DWC_LL_INTR_HPRT_PRTCONNDET && !hal->flags.dbnc_lock_enabled) {
event = USB_DWC_HAL_PORT_EVENT_CONN;
debounce_lock_enable(hal);
}
}
//Port events always take precedence over channel events
if (event == USB_DWC_HAL_PORT_EVENT_NONE && (intrs_core & USB_DWC_LL_INTR_CORE_HCHINT)) {
//One or more channels have pending interrupts. Store the mask of those channels
hal->channels.chan_pend_intrs_msk = usb_dwc_ll_haint_get_chan_intrs(hal->dev);
event = USB_DWC_HAL_PORT_EVENT_CHAN;
}
return event;
}
usb_dwc_hal_chan_t *usb_dwc_hal_get_chan_pending_intr(usb_dwc_hal_context_t *hal)
{
HAL_ASSERT(hal->channels.hdls);
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;
}
}
usb_dwc_hal_chan_event_t usb_dwc_hal_chan_decode_intr(usb_dwc_hal_chan_t *chan_obj)
{
uint32_t chan_intrs = usb_dwc_ll_hcint_read_and_clear_intrs(chan_obj->regs);
usb_dwc_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 usb_dwc_hal_chan_request_halt()
/*
Note: Do not change order of checks as some events take precedence over others.
Errors > Channel Halt Request > Transfer completed
*/
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 & USB_DWC_LL_INTR_CHAN_CHHLTD); //An error should have halted the channel
//Store the error in hal context
usb_dwc_hal_chan_error_t error;
if (chan_intrs & USB_DWC_LL_INTR_CHAN_STALL) {
error = USB_DWC_HAL_CHAN_ERROR_STALL;
} else if (chan_intrs & USB_DWC_LL_INTR_CHAN_BBLEER) {
error = USB_DWC_HAL_CHAN_ERROR_PKT_BBL;
} else if (chan_intrs & USB_DWC_LL_INTR_CHAN_BNAINTR) {
error = USB_DWC_HAL_CHAN_ERROR_BNA;
} else { //USB_DWC_LL_INTR_CHAN_XCS_XACT_ERR
error = USB_DWC_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 = USB_DWC_HAL_CHAN_EVENT_ERROR;
} else if (chan_intrs & USB_DWC_LL_INTR_CHAN_CHHLTD) {
if (chan_obj->flags.halt_requested) {
chan_obj->flags.halt_requested = 0;
chan_event = USB_DWC_HAL_CHAN_EVENT_HALT_REQ;
} else {
//Must have been halted due to QTD HOC
chan_event = USB_DWC_HAL_CHAN_EVENT_CPLT;
}
chan_obj->flags.active = 0;
} else if (chan_intrs & USB_DWC_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
usb_dwc_ll_hcchar_disable_chan(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 = USB_DWC_HAL_CHAN_EVENT_NONE;
} else {
abort();
}
return chan_event;
}
-29
View File
@@ -1,29 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "hal/usb_utmi_ll.h"
#include "hal/usb_utmi_hal.h"
void _usb_utmi_hal_init(usb_utmi_hal_context_t *hal)
{
hal->dev = &USB_UTMI;
_usb_utmi_ll_enable_bus_clock(true);
_usb_utmi_ll_reset_register();
/*
Additional setting to solve missing DCONN event on ESP32P4 (IDF-9953).
Note: On ESP32P4, the HP_SYSTEM_OTG_SUSPENDM is not connected to 1 by hardware.
For correct detection of the device detaching, internal signal should be set to 1 by the software.
*/
usb_utmi_ll_enable_precise_detection(true);
usb_utmi_ll_configure_ls(hal->dev, true);
}
void _usb_utmi_hal_disable(void)
{
_usb_utmi_ll_enable_bus_clock(false);
}
-36
View File
@@ -1,36 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#include "hal/usb_wrap_ll.h"
#include "hal/usb_wrap_hal.h"
void _usb_wrap_hal_init(usb_wrap_hal_context_t *hal)
{
hal->dev = &USB_WRAP;
_usb_wrap_ll_enable_bus_clock(true);
_usb_wrap_ll_reset_register();
#if !USB_WRAP_LL_EXT_PHY_SUPPORTED
usb_wrap_ll_phy_set_defaults(hal->dev);
#endif
}
void _usb_wrap_hal_disable(void)
{
_usb_wrap_ll_enable_bus_clock(false);
}
#if USB_WRAP_LL_EXT_PHY_SUPPORTED
void usb_wrap_hal_phy_set_external(usb_wrap_hal_context_t *hal, bool external)
{
if (external) {
usb_wrap_ll_phy_enable_external(hal->dev, true);
} else {
usb_wrap_ll_phy_enable_external(hal->dev, false);
usb_wrap_ll_phy_enable_pad(hal->dev, true);
}
}
#endif // USB_WRAP_LL_EXT_PHY_SUPPORTED