zephyr/drivers/usb/udc/udc_dwc2.c
Tomasz Moń 89a81e3c1f drivers: udc_dwc2: Disable control IN endpoint on SETUP
New control transfer is started prematurely from device perspective when
host timeout occurs. Any data transfer from previous control transfer
have to be cancelled prior to handling SETUP data. Unconditionally
disable control IN endpoint to prevent race for enqueued buffer between
udc_buf_get_all() called in dwc2_handle_evt_setup() and udc_buf_peek()
called in dwc2_handle_in_xfercompl().

Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
2025-06-18 09:11:13 -04:00

3302 lines
91 KiB
C

/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "udc_common.h"
#include "udc_dwc2.h"
#include <string.h>
#include <stdio.h>
#include <zephyr/cache.h>
#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/usb/usb_ch9.h>
#include <usb_dwc2_hw.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(udc_dwc2, CONFIG_UDC_DRIVER_LOG_LEVEL);
#include "udc_dwc2_vendor_quirks.h"
enum dwc2_drv_event_type {
/* USB connection speed determined after bus reset */
DWC2_DRV_EVT_ENUM_DONE,
/* Trigger next transfer, must not be used for control OUT */
DWC2_DRV_EVT_XFER,
/* Setup packet received */
DWC2_DRV_EVT_SETUP,
/* Transaction on endpoint is finished */
DWC2_DRV_EVT_EP_FINISHED,
/* Remote Wakeup should be initiated */
DWC2_DRV_EVT_REMOTE_WAKEUP,
/* Core should enter hibernation */
DWC2_DRV_EVT_ENTER_HIBERNATION,
/* Core should exit hibernation due to bus reset */
DWC2_DRV_EVT_HIBERNATION_EXIT_BUS_RESET,
/* Core should exit hibernation due to host resume */
DWC2_DRV_EVT_HIBERNATION_EXIT_HOST_RESUME,
};
/* Minimum RX FIFO size in 32-bit words considering the largest used OUT packet
* of 512 bytes. The value must be adjusted according to the number of OUT
* endpoints.
*/
#define UDC_DWC2_GRXFSIZ_FS_DEFAULT (15U + 512U/4U)
/* Default Rx FIFO size in 32-bit words calculated to support High-Speed with:
* * 1 control endpoint in Completer/Buffer DMA mode: 13 locations
* * Global OUT NAK: 1 location
* * Space for 3 * 1024 packets: ((1024/4) + 1) * 3 = 774 locations
* Driver adds 2 locations for each OUT endpoint to this value.
*/
#define UDC_DWC2_GRXFSIZ_HS_DEFAULT (13 + 1 + 774)
/* TX FIFO0 depth in 32-bit words (used by control IN endpoint)
* Try 2 * bMaxPacketSize0 to allow simultaneous operation with a fallback to
* whatever is available when 2 * bMaxPacketSize0 is not possible.
*/
#define UDC_DWC2_FIFO0_DEPTH (2 * 16U)
/* Get Data FIFO access register */
#define UDC_DWC2_EP_FIFO(base, idx) ((mem_addr_t)base + 0x1000 * (idx + 1))
enum dwc2_suspend_type {
DWC2_SUSPEND_NO_POWER_SAVING,
DWC2_SUSPEND_HIBERNATION,
};
/* Registers that have to be stored before Partial Power Down or Hibernation */
struct dwc2_reg_backup {
uint32_t gotgctl;
uint32_t gahbcfg;
uint32_t gusbcfg;
uint32_t gintmsk;
uint32_t grxfsiz;
uint32_t gnptxfsiz;
uint32_t gi2cctl;
uint32_t glpmcfg;
uint32_t gdfifocfg;
union {
uint32_t dptxfsiz[15];
uint32_t dieptxf[15];
};
uint32_t dcfg;
uint32_t dctl;
uint32_t diepmsk;
uint32_t doepmsk;
uint32_t daintmsk;
uint32_t diepctl[16];
uint32_t dieptsiz[16];
uint32_t diepdma[16];
uint32_t doepctl[16];
uint32_t doeptsiz[16];
uint32_t doepdma[16];
uint32_t pcgcctl;
};
/* Driver private data per instance */
struct udc_dwc2_data {
struct k_thread thread_data;
/* Main events the driver thread waits for */
struct k_event drv_evt;
/* Transfer triggers (IN on bits 0-15, OUT on bits 16-31) */
atomic_t xfer_new;
/* Finished transactions (IN on bits 0-15, OUT on bits 16-31) */
atomic_t xfer_finished;
struct dwc2_reg_backup backup;
uint32_t ghwcfg1;
uint32_t max_xfersize;
uint32_t max_pktcnt;
uint32_t tx_len[16];
uint32_t rx_siz[16];
uint16_t txf_set;
uint16_t pending_tx_flush;
uint16_t dfifodepth;
uint16_t rxfifo_depth;
uint16_t max_txfifo_depth[16];
uint16_t sof_num;
/* Configuration flags */
unsigned int dynfifosizing : 1;
unsigned int bufferdma : 1;
unsigned int syncrst : 1;
/* Defect workarounds */
unsigned int wa_essregrestored : 1;
/* Runtime state flags */
unsigned int hibernated : 1;
unsigned int enumdone : 1;
unsigned int enumspd : 2;
enum dwc2_suspend_type suspend_type;
/* Number of endpoints including control endpoint */
uint8_t numdeveps;
/* Number of IN endpoints including control endpoint */
uint8_t ineps;
/* Number of OUT endpoints including control endpoint */
uint8_t outeps;
uint8_t setup[8];
};
static void udc_dwc2_ep_disable(const struct device *dev,
struct udc_ep_config *const cfg, bool stall);
#if defined(CONFIG_PINCTRL)
#include <zephyr/drivers/pinctrl.h>
static int dwc2_init_pinctrl(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
const struct pinctrl_dev_config *const pcfg = config->pcfg;
int ret = 0;
if (pcfg == NULL) {
LOG_INF("Skip pinctrl configuration");
return 0;
}
ret = pinctrl_apply_state(pcfg, PINCTRL_STATE_DEFAULT);
if (ret) {
LOG_ERR("Failed to apply default pinctrl state (%d)", ret);
}
LOG_DBG("Apply pinctrl");
return ret;
}
#else
static int dwc2_init_pinctrl(const struct device *dev)
{
ARG_UNUSED(dev);
return 0;
}
#endif
static inline struct usb_dwc2_reg *dwc2_get_base(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
return config->base;
}
static void dwc2_wait_for_bit(const struct device *dev,
mem_addr_t addr, uint32_t bit)
{
k_timepoint_t timeout = sys_timepoint_calc(K_MSEC(100));
/* This could potentially be converted to use proper synchronization
* primitives instead of busy looping, but the number of interrupt bits
* this function can be waiting for is rather high.
*
* Busy looping is most likely fine unless profiling shows otherwise.
*/
while (!(sys_read32(addr) & bit)) {
if (dwc2_quirk_is_phy_clk_off(dev)) {
/* No point in waiting, because the bit can only be set
* when the PHY is actively clocked.
*/
return;
}
if (sys_timepoint_expired(timeout)) {
LOG_ERR("Timeout waiting for bit 0x%08X at 0x%08X",
bit, (uint32_t)addr);
return;
}
}
}
static inline bool dwc2_in_completer_mode(const struct device *dev)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
return !IS_ENABLED(CONFIG_UDC_DWC2_DMA) || !priv->bufferdma;
}
static inline bool dwc2_in_buffer_dma_mode(const struct device *dev)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
return IS_ENABLED(CONFIG_UDC_DWC2_DMA) && priv->bufferdma;
}
/* Get DOEPCTLn or DIEPCTLn register address */
static mem_addr_t dwc2_get_dxepctl_reg(const struct device *dev, const uint8_t ep)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
uint8_t ep_idx = USB_EP_GET_IDX(ep);
if (USB_EP_DIR_IS_OUT(ep)) {
return (mem_addr_t)&base->out_ep[ep_idx].doepctl;
} else {
return (mem_addr_t)&base->in_ep[ep_idx].diepctl;
}
}
/* Get available FIFO space in bytes */
static uint32_t dwc2_ftx_avail(const struct device *dev, const uint32_t idx)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
mem_addr_t reg = (mem_addr_t)&base->in_ep[idx].dtxfsts;
uint32_t dtxfsts;
dtxfsts = sys_read32(reg);
return usb_dwc2_get_dtxfsts_ineptxfspcavail(dtxfsts) * 4;
}
static uint32_t dwc2_get_iept_pktctn(const struct device *dev, const uint32_t idx)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
if (idx == 0) {
return usb_dwc2_get_dieptsiz0_pktcnt(UINT32_MAX);
} else {
return priv->max_pktcnt;
}
}
static uint32_t dwc2_get_iept_xfersize(const struct device *dev, const uint32_t idx)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
if (idx == 0) {
return usb_dwc2_get_dieptsiz0_xfersize(UINT32_MAX);
} else {
return priv->max_xfersize;
}
}
static uint32_t dwc2_get_oept_pktctn(const struct device *dev, const uint32_t idx)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
if (idx == 0) {
return usb_dwc2_get_doeptsiz0_pktcnt(UINT32_MAX);
} else {
return priv->max_pktcnt;
}
}
static uint32_t dwc2_get_oept_xfersize(const struct device *dev, const uint32_t idx)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
if (idx == 0) {
return usb_dwc2_get_doeptsiz0_xfersize(UINT32_MAX);
} else {
return priv->max_xfersize;
}
}
static void dwc2_flush_rx_fifo(const struct device *dev)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
mem_addr_t grstctl_reg = (mem_addr_t)&base->grstctl;
sys_write32(USB_DWC2_GRSTCTL_RXFFLSH, grstctl_reg);
while (sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_RXFFLSH) {
}
}
static void dwc2_flush_tx_fifo(const struct device *dev, const uint8_t fnum)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
mem_addr_t grstctl_reg = (mem_addr_t)&base->grstctl;
uint32_t grstctl;
grstctl = usb_dwc2_set_grstctl_txfnum(fnum) | USB_DWC2_GRSTCTL_TXFFLSH;
sys_write32(grstctl, grstctl_reg);
while (sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_TXFFLSH) {
}
}
/* Return TX FIFOi depth in 32-bit words (i = f_idx + 1) */
static uint32_t dwc2_get_txfdep(const struct device *dev, const uint32_t f_idx)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
uint32_t dieptxf;
dieptxf = sys_read32((mem_addr_t)&base->dieptxf[f_idx]);
return usb_dwc2_get_dieptxf_inepntxfdep(dieptxf);
}
/* Return TX FIFOi address (i = f_idx + 1) */
static uint32_t dwc2_get_txfaddr(const struct device *dev, const uint32_t f_idx)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
uint32_t dieptxf;
dieptxf = sys_read32((mem_addr_t)&base->dieptxf[f_idx]);
return usb_dwc2_get_dieptxf_inepntxfstaddr(dieptxf);
}
/* Set TX FIFOi address and depth (i = f_idx + 1) */
static void dwc2_set_txf(const struct device *dev, const uint32_t f_idx,
const uint32_t dep, const uint32_t addr)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
uint32_t dieptxf;
dieptxf = usb_dwc2_set_dieptxf_inepntxfdep(dep) |
usb_dwc2_set_dieptxf_inepntxfstaddr(addr);
sys_write32(dieptxf, (mem_addr_t)&base->dieptxf[f_idx]);
}
/* Enable/disable endpoint interrupt */
static void dwc2_set_epint(const struct device *dev,
struct udc_ep_config *const cfg, const bool enabled)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
mem_addr_t reg = (mem_addr_t)&base->daintmsk;
uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr);
uint32_t epmsk;
if (USB_EP_DIR_IS_IN(cfg->addr)) {
epmsk = USB_DWC2_DAINT_INEPINT(ep_idx);
} else {
epmsk = USB_DWC2_DAINT_OUTEPINT(ep_idx);
}
if (enabled) {
sys_set_bits(reg, epmsk);
} else {
sys_clear_bits(reg, epmsk);
}
}
static bool dwc2_ep_is_periodic(struct udc_ep_config *const cfg)
{
switch (cfg->attributes & USB_EP_TRANSFER_TYPE_MASK) {
case USB_EP_TYPE_INTERRUPT:
__fallthrough;
case USB_EP_TYPE_ISO:
return true;
default:
return false;
}
}
static bool dwc2_ep_is_iso(struct udc_ep_config *const cfg)
{
return (cfg->attributes & USB_EP_TRANSFER_TYPE_MASK) == USB_EP_TYPE_ISO;
}
static int dwc2_ctrl_feed_dout(const struct device *dev, const size_t length)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT);
struct net_buf *buf;
size_t alloc_len = length;
if (dwc2_in_buffer_dma_mode(dev)) {
/* Control OUT buffers must be multiple of bMaxPacketSize0 */
alloc_len = ROUND_UP(length, 64);
}
buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, alloc_len);
if (buf == NULL) {
return -ENOMEM;
}
udc_buf_put(ep_cfg, buf);
atomic_set_bit(&priv->xfer_new, 16);
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_XFER));
return 0;
}
static void dwc2_ensure_setup_ready(const struct device *dev)
{
if (dwc2_in_completer_mode(dev)) {
/* In Completer mode EP0 can always receive SETUP data */
return;
}
if (!udc_buf_peek(udc_get_ep_cfg(dev, USB_CONTROL_EP_IN))) {
dwc2_ctrl_feed_dout(dev, 8);
}
}
static bool dwc2_dma_buffer_ok_to_use(const struct device *dev, void *buf,
uint32_t xfersize, uint16_t mps)
{
ARG_UNUSED(dev);
if (!IS_ALIGNED(buf, 4)) {
LOG_ERR("Buffer not aligned");
return false;
}
/* We can only do 1 packet if Max Packet Size is not multiple of 4 */
if (unlikely(mps % 4) && (xfersize > USB_MPS_EP_SIZE(mps))) {
LOG_ERR("Padding not supported");
return false;
}
return true;
}
/* Can be called from ISR context */
static int dwc2_tx_fifo_write(const struct device *dev,
struct udc_ep_config *const cfg, struct net_buf *const buf)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_dwc2_data *const priv = udc_get_private(dev);
uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr);
mem_addr_t dieptsiz_reg = (mem_addr_t)&base->in_ep[ep_idx].dieptsiz;
/* TODO: use dwc2_get_dxepctl_reg() */
mem_addr_t diepctl_reg = (mem_addr_t)&base->in_ep[ep_idx].diepctl;
mem_addr_t diepint_reg = (mem_addr_t)&base->in_ep[ep_idx].diepint;
uint32_t diepctl;
uint32_t max_xfersize, max_pktcnt, pktcnt;
const uint32_t addnl = USB_MPS_ADDITIONAL_TRANSACTIONS(cfg->mps);
const size_t d = sizeof(uint32_t);
unsigned int key;
uint32_t len;
const bool is_periodic = dwc2_ep_is_periodic(cfg);
const bool is_iso = dwc2_ep_is_iso(cfg);
if (is_iso) {
/* Isochronous transfers can only be programmed one
* (micro-)frame at a time.
*/
len = MIN(buf->len, USB_MPS_TO_TPL(cfg->mps));
} else {
/* DMA automatically handles packet split. In completer mode,
* the value is sanitized below.
*/
len = buf->len;
}
if (dwc2_in_completer_mode(dev)) {
uint32_t spcavail = dwc2_ftx_avail(dev, ep_idx);
uint32_t spcperpkt = ROUND_UP(udc_mps_ep_size(cfg), 4);
uint32_t max_pkts, max_transfer;
/* Maximum number of packets that can fit in TxFIFO */
max_pkts = spcavail / spcperpkt;
/* We can transfer up to max_pkts MPS packets and a short one */
max_transfer = (max_pkts * udc_mps_ep_size(cfg)) +
(spcavail % spcperpkt);
/* If there is enough space for the transfer, there's no need
* to check any additional conditions. If the transfer is larger
* than TxFIFO then TxFIFO must be able to hold at least one
* packet (for periodic transfers at least the number of packets
* per microframe).
*/
if ((len > max_transfer) && ((1 + addnl) > max_pkts)) {
LOG_ERR("ep 0x%02x FIFO space is too low, %u (%u)",
cfg->addr, spcavail, len);
return -EAGAIN;
}
len = MIN(len, max_transfer);
}
if (len != 0U) {
max_pktcnt = dwc2_get_iept_pktctn(dev, ep_idx);
max_xfersize = dwc2_get_iept_xfersize(dev, ep_idx);
if (len > max_xfersize) {
/*
* Avoid short packets if the transfer size cannot be
* handled in one set.
*/
len = ROUND_DOWN(max_xfersize, USB_MPS_TO_TPL(cfg->mps));
}
/*
* Determine the number of packets for the current transfer;
* if the pktcnt is too large, truncate the actual transfer length.
*/
pktcnt = DIV_ROUND_UP(len, udc_mps_ep_size(cfg));
if (pktcnt > max_pktcnt) {
pktcnt = ROUND_DOWN(max_pktcnt, (1 + addnl));
len = pktcnt * udc_mps_ep_size(cfg);
}
} else {
/* ZLP */
pktcnt = 1U;
}
LOG_DBG("Prepare ep 0x%02x xfer len %u pktcnt %u addnl %u",
cfg->addr, len, pktcnt, addnl);
priv->tx_len[ep_idx] = len;
/* Lock and write to endpoint FIFO */
key = irq_lock();
/* Set number of packets and transfer size */
sys_write32((is_periodic ? usb_dwc2_set_dieptsizn_mc(1 + addnl) : 0) |
usb_dwc2_set_dieptsizn_pktcnt(pktcnt) |
usb_dwc2_set_dieptsizn_xfersize(len), dieptsiz_reg);
if (dwc2_in_buffer_dma_mode(dev)) {
if (!dwc2_dma_buffer_ok_to_use(dev, buf->data, len, cfg->mps)) {
/* Cannot continue unless buffer is bounced. Device will
* cease to function. Is fatal error appropriate here?
*/
irq_unlock(key);
return -ENOTSUP;
}
sys_write32((uint32_t)buf->data,
(mem_addr_t)&base->in_ep[ep_idx].diepdma);
sys_cache_data_flush_range(buf->data, len);
}
diepctl = sys_read32(diepctl_reg);
if (!(diepctl & USB_DWC2_DEPCTL_USBACTEP)) {
/* Do not attempt to write data on inactive endpoint, because
* no fifo is assigned to inactive endpoint and therefore it is
* possible that the write will corrupt other endpoint fifo.
*/
irq_unlock(key);
return -ENOENT;
}
if (is_iso) {
/* Queue transfer on next SOF. TODO: allow stack to explicitly
* specify on which (micro-)frame the data should be sent.
*/
if (priv->sof_num & 1) {
diepctl |= USB_DWC2_DEPCTL_SETEVENFR;
} else {
diepctl |= USB_DWC2_DEPCTL_SETODDFR;
}
}
/* Clear NAK and set endpoint enable */
diepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_CNAK;
sys_write32(diepctl, diepctl_reg);
/* Clear IN Endpoint NAK Effective interrupt in case it was set */
sys_write32(USB_DWC2_DIEPINT_INEPNAKEFF, diepint_reg);
if (dwc2_in_completer_mode(dev)) {
const uint8_t *src = buf->data;
while (pktcnt > 0) {
uint32_t pktlen = MIN(len, udc_mps_ep_size(cfg));
for (uint32_t i = 0UL; i < pktlen; i += d) {
uint32_t val = src[i];
if (i + 1 < pktlen) {
val |= ((uint32_t)src[i + 1UL]) << 8;
}
if (i + 2 < pktlen) {
val |= ((uint32_t)src[i + 2UL]) << 16;
}
if (i + 3 < pktlen) {
val |= ((uint32_t)src[i + 3UL]) << 24;
}
sys_write32(val, UDC_DWC2_EP_FIFO(base, ep_idx));
}
pktcnt--;
src += pktlen;
len -= pktlen;
}
}
irq_unlock(key);
return 0;
}
static inline int dwc2_read_fifo(const struct device *dev, const uint8_t ep,
struct net_buf *const buf, const size_t size)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
size_t len = buf ? MIN(size, net_buf_tailroom(buf)) : 0;
const size_t d = sizeof(uint32_t);
/* FIFO access is always in 32-bit words */
for (uint32_t n = 0; n < (len / d); n++) {
net_buf_add_le32(buf, sys_read32(UDC_DWC2_EP_FIFO(base, ep)));
}
if (len % d) {
uint8_t r[4];
/* Get the remaining */
sys_put_le32(sys_read32(UDC_DWC2_EP_FIFO(base, ep)), r);
for (uint32_t i = 0U; i < (len % d); i++) {
net_buf_add_u8(buf, r[i]);
}
}
if (unlikely(size > len)) {
for (uint32_t n = 0; n < DIV_ROUND_UP(size - len, d); n++) {
(void)sys_read32(UDC_DWC2_EP_FIFO(base, ep));
}
}
return 0;
}
/* Can be called from ISR and we call it only when there is a buffer in the queue */
static void dwc2_prep_rx(const struct device *dev, struct net_buf *buf,
struct udc_ep_config *const cfg)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_dwc2_data *const priv = udc_get_private(dev);
uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr);
mem_addr_t doeptsiz_reg = (mem_addr_t)&base->out_ep[ep_idx].doeptsiz;
mem_addr_t doepctl_reg = dwc2_get_dxepctl_reg(dev, ep_idx);
uint32_t max_xfersize, max_pktcnt;
const uint32_t addnl = USB_MPS_ADDITIONAL_TRANSACTIONS(cfg->mps);
uint32_t pktcnt;
uint32_t doeptsiz;
uint32_t doepctl;
uint32_t xfersize;
max_xfersize = dwc2_get_oept_xfersize(dev, ep_idx);
max_pktcnt = dwc2_get_oept_pktctn(dev, ep_idx);
/* Clear NAK and set endpoint enable */
doepctl = sys_read32(doepctl_reg);
doepctl |= USB_DWC2_DEPCTL_EPENA;
if (cfg->addr == USB_CONTROL_EP_OUT) {
struct udc_data *data = dev->data;
/* During OUT Data Stage every packet has to have CNAK set.
* In Buffer DMA mode the OUT endpoint is armed during IN Data
* Stage to accept either Status stage or new SETUP token. The
* Status stage (premature or not) can only be received if CNAK
* was set. In Completer mode the OUT endpoint armed during OUT
* Status stage needs CNAK.
*
* Setting CNAK in other cases opens up possibility for Buffer
* DMA controller to lock up completely if the endpoint is
* enabled (to receive SETUP data) when the host is transmitting
* subsequent control transfer OUT Data Stage packet (SETUP DATA
* is unconditionally ACKed regardless of software state).
*/
if (data->stage == CTRL_PIPE_STAGE_DATA_OUT ||
data->stage == CTRL_PIPE_STAGE_DATA_IN ||
data->stage == CTRL_PIPE_STAGE_STATUS_OUT) {
doepctl |= USB_DWC2_DEPCTL_CNAK;
}
} else {
/* Non-control endpoint, set CNAK for all transfers */
doepctl |= USB_DWC2_DEPCTL_CNAK;
}
if (dwc2_ep_is_iso(cfg)) {
xfersize = USB_MPS_TO_TPL(cfg->mps);
pktcnt = 1 + addnl;
if (xfersize > net_buf_tailroom(buf)) {
LOG_ERR("ISO RX buffer too small");
return;
}
/* Set the Even/Odd (micro-)frame appropriately */
if (priv->sof_num & 1) {
doepctl |= USB_DWC2_DEPCTL_SETEVENFR;
} else {
doepctl |= USB_DWC2_DEPCTL_SETODDFR;
}
} else {
xfersize = net_buf_tailroom(buf);
/* Do as many packets in a single transfer as possible */
if (xfersize > max_xfersize) {
xfersize = ROUND_DOWN(max_xfersize, USB_MPS_TO_TPL(cfg->mps));
}
pktcnt = DIV_ROUND_UP(xfersize, USB_MPS_EP_SIZE(cfg->mps));
}
if (pktcnt > max_pktcnt) {
pktcnt = ROUND_DOWN(max_pktcnt, (1 + addnl));
xfersize = pktcnt * udc_mps_ep_size(cfg);
}
doeptsiz = usb_dwc2_set_doeptsizn_pktcnt(pktcnt) |
usb_dwc2_set_doeptsizn_xfersize(xfersize);
if (cfg->addr == USB_CONTROL_EP_OUT) {
doeptsiz |= (3 << USB_DWC2_DOEPTSIZ0_SUPCNT_POS);
}
priv->rx_siz[ep_idx] = doeptsiz;
sys_write32(doeptsiz, doeptsiz_reg);
if (dwc2_in_buffer_dma_mode(dev)) {
void *data = net_buf_tail(buf);
if (!dwc2_dma_buffer_ok_to_use(dev, data, xfersize, cfg->mps)) {
/* Cannot continue unless buffer is bounced. Device will
* cease to function. Is fatal error appropriate here?
*/
return;
}
sys_write32((uint32_t)data,
(mem_addr_t)&base->out_ep[ep_idx].doepdma);
sys_cache_data_invd_range(data, xfersize);
}
sys_write32(doepctl, doepctl_reg);
LOG_INF("Prepare RX 0x%02x doeptsiz 0x%x", cfg->addr, doeptsiz);
}
static void dwc2_handle_xfer_next(const struct device *dev,
struct udc_ep_config *const cfg)
{
struct net_buf *buf;
buf = udc_buf_peek(cfg);
if (buf == NULL) {
return;
}
if (USB_EP_DIR_IS_OUT(cfg->addr)) {
dwc2_prep_rx(dev, buf, cfg);
} else {
int err = dwc2_tx_fifo_write(dev, cfg, buf);
if (cfg->addr == USB_CONTROL_EP_IN) {
/* Feed a buffer for the next setup packet after arming
* IN endpoint with the data. This is necessary both in
* IN Data Stage (Control Read Transfer) and IN Status
* Stage (Control Write Transfers and Control Transfers
* without Data Stage).
*
* The buffer must be fed here in Buffer DMA mode to
* allow receiving premature SETUP. This inevitably does
* automatically arm the buffer for OUT Status Stage.
*
* The buffer MUST NOT be fed here in Completer mode to
* avoid race condition where the next Control Write
* Transfer Data Stage is received into the buffer.
*/
if (dwc2_in_buffer_dma_mode(dev)) {
dwc2_ctrl_feed_dout(dev, 8);
}
}
if (err) {
LOG_ERR("Failed to start write to TX FIFO, ep 0x%02x (err: %d)",
cfg->addr, err);
buf = udc_buf_get(cfg);
if (udc_submit_ep_event(dev, buf, -ECONNREFUSED)) {
LOG_ERR("Failed to submit endpoint event");
};
return;
}
}
udc_ep_set_busy(cfg, true);
}
static int dwc2_handle_evt_setup(const struct device *dev)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct udc_ep_config *cfg_out = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT);
struct udc_ep_config *cfg_in = udc_get_ep_cfg(dev, USB_CONTROL_EP_IN);
struct net_buf *buf;
int err;
/* In Completer mode SETUP data is received without preparing endpoint 0
* transfer beforehand. In Buffer DMA the SETUP can be copied to any EP0
* OUT buffer. If there is any buffer queued, it is obsolete now.
*/
udc_dwc2_ep_disable(dev, cfg_in, false);
atomic_and(&priv->xfer_finished, ~(BIT(0) | BIT(16)));
buf = udc_buf_get_all(cfg_out);
if (buf) {
net_buf_unref(buf);
}
buf = udc_buf_get_all(cfg_in);
if (buf) {
net_buf_unref(buf);
}
udc_ep_set_busy(cfg_out, false);
udc_ep_set_busy(cfg_in, false);
/* Allocate buffer and copy received SETUP for processing */
buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, 8);
if (buf == NULL) {
LOG_ERR("No buffer available for control ep");
return -ENODATA;
}
net_buf_add_mem(buf, priv->setup, sizeof(priv->setup));
udc_ep_buf_set_setup(buf);
LOG_HEXDUMP_DBG(buf->data, buf->len, "setup");
/* Update to next stage of control transfer */
udc_ctrl_update_stage(dev, buf);
/* We always allocate and feed buffer large enough for a setup packet. */
if (udc_ctrl_stage_is_data_out(dev)) {
/* Allocate and feed buffer for data OUT stage */
LOG_DBG("s:%p|feed for -out-", buf);
err = dwc2_ctrl_feed_dout(dev, udc_data_stage_length(buf));
if (err == -ENOMEM) {
err = udc_submit_ep_event(dev, buf, err);
}
} else if (udc_ctrl_stage_is_data_in(dev)) {
LOG_DBG("s:%p|feed for -in-status", buf);
err = udc_ctrl_submit_s_in_status(dev);
} else {
LOG_DBG("s:%p|feed >setup", buf);
err = udc_ctrl_submit_s_status(dev);
}
return err;
}
static inline int dwc2_handle_evt_dout(const struct device *dev,
struct udc_ep_config *const cfg)
{
struct udc_data *data = dev->data;
struct net_buf *buf;
int err = 0;
buf = udc_buf_get(cfg);
if (buf == NULL) {
LOG_ERR("No buffer queued for ep 0x%02x", cfg->addr);
return -ENODATA;
}
udc_ep_set_busy(cfg, false);
if (cfg->addr == USB_CONTROL_EP_OUT) {
if (udc_ctrl_stage_is_status_out(dev)) {
/* s-in-status finished */
LOG_DBG("dout:%p| status, feed >s", buf);
/* Status stage finished, notify upper layer */
udc_ctrl_submit_status(dev, buf);
if (dwc2_in_buffer_dma_mode(dev)) {
dwc2_ctrl_feed_dout(dev, 8);
}
} else {
LOG_DBG("dout:%p| data, feed >s", buf);
}
/* Update to next stage of control transfer */
udc_ctrl_update_stage(dev, buf);
if (udc_ctrl_stage_is_status_in(dev)) {
err = udc_ctrl_submit_s_out_status(dev, buf);
}
if (data->stage == CTRL_PIPE_STAGE_ERROR) {
/* Allow receiving next SETUP. USB stack won't queue any
* buffer because it has no clue about this transfer.
*/
dwc2_ensure_setup_ready(dev);
}
} else {
err = udc_submit_ep_event(dev, buf, 0);
}
return err;
}
static int dwc2_handle_evt_din(const struct device *dev,
struct udc_ep_config *const cfg)
{
struct net_buf *buf;
buf = udc_buf_peek(cfg);
if (buf == NULL) {
LOG_ERR("No buffer for ep 0x%02x", cfg->addr);
udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS);
return -ENOBUFS;
}
if (buf->len) {
/* Looks like we failed to continue in ISR, retry */
return dwc2_tx_fifo_write(dev, cfg, buf);
}
if (cfg->addr == USB_CONTROL_EP_IN && udc_ep_buf_has_zlp(buf)) {
udc_ep_buf_clear_zlp(buf);
return dwc2_tx_fifo_write(dev, cfg, buf);
}
buf = udc_buf_get(cfg);
udc_ep_set_busy(cfg, false);
if (cfg->addr == USB_CONTROL_EP_IN) {
if (udc_ctrl_stage_is_status_in(dev) ||
udc_ctrl_stage_is_no_data(dev)) {
/* Status stage finished, notify upper layer */
udc_ctrl_submit_status(dev, buf);
}
/* Update to next stage of control transfer */
udc_ctrl_update_stage(dev, buf);
if (udc_ctrl_stage_is_status_out(dev)) {
if (dwc2_in_completer_mode(dev)) {
/* Allow OUT status stage */
dwc2_ctrl_feed_dout(dev, 8);
}
/* IN transfer finished, release buffer. */
net_buf_unref(buf);
}
return 0;
}
return udc_submit_ep_event(dev, buf, 0);
}
static void dwc2_backup_registers(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct dwc2_reg_backup *backup = &priv->backup;
backup->gotgctl = sys_read32((mem_addr_t)&base->gotgctl);
backup->gahbcfg = sys_read32((mem_addr_t)&base->gahbcfg);
backup->gusbcfg = sys_read32((mem_addr_t)&base->gusbcfg);
backup->gintmsk = sys_read32((mem_addr_t)&base->gintmsk);
backup->grxfsiz = sys_read32((mem_addr_t)&base->grxfsiz);
backup->gnptxfsiz = sys_read32((mem_addr_t)&base->gnptxfsiz);
backup->gi2cctl = sys_read32((mem_addr_t)&base->gi2cctl);
backup->glpmcfg = sys_read32((mem_addr_t)&base->glpmcfg);
backup->gdfifocfg = sys_read32((mem_addr_t)&base->gdfifocfg);
for (uint8_t i = 1U; i < priv->ineps; i++) {
backup->dieptxf[i - 1] = sys_read32((mem_addr_t)&base->dieptxf[i - 1]);
}
backup->dcfg = sys_read32((mem_addr_t)&base->dcfg);
backup->dctl = sys_read32((mem_addr_t)&base->dctl);
backup->diepmsk = sys_read32((mem_addr_t)&base->diepmsk);
backup->doepmsk = sys_read32((mem_addr_t)&base->doepmsk);
backup->daintmsk = sys_read32((mem_addr_t)&base->daintmsk);
for (uint8_t i = 0U; i < 16; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i);
if (epdir == USB_DWC2_GHWCFG1_EPDIR_IN || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
backup->diepctl[i] = sys_read32((mem_addr_t)&base->in_ep[i].diepctl);
if (backup->diepctl[i] & USB_DWC2_DEPCTL_DPID) {
backup->diepctl[i] |= USB_DWC2_DEPCTL_SETD1PID;
} else {
backup->diepctl[i] |= USB_DWC2_DEPCTL_SETD0PID;
}
backup->dieptsiz[i] = sys_read32((mem_addr_t)&base->in_ep[i].dieptsiz);
backup->diepdma[i] = sys_read32((mem_addr_t)&base->in_ep[i].diepdma);
}
if (epdir == USB_DWC2_GHWCFG1_EPDIR_OUT || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
backup->doepctl[i] = sys_read32((mem_addr_t)&base->out_ep[i].doepctl);
if (backup->doepctl[i] & USB_DWC2_DEPCTL_DPID) {
backup->doepctl[i] |= USB_DWC2_DEPCTL_SETD1PID;
} else {
backup->doepctl[i] |= USB_DWC2_DEPCTL_SETD0PID;
}
backup->doeptsiz[i] = sys_read32((mem_addr_t)&base->out_ep[i].doeptsiz);
backup->doepdma[i] = sys_read32((mem_addr_t)&base->out_ep[i].doepdma);
}
}
backup->pcgcctl = sys_read32((mem_addr_t)&base->pcgcctl);
}
static void dwc2_restore_essential_registers(const struct device *dev,
bool rwup, bool bus_reset)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct dwc2_reg_backup *backup = &priv->backup;
uint32_t pcgcctl = backup->pcgcctl & USB_DWC2_PCGCCTL_RESTOREVALUE_MASK;
if (usb_dwc2_get_pcgcctl_p2hd_dev_enum_spd(pcgcctl) ==
USB_DWC2_PCGCCTL_P2HD_DEV_ENUM_SPD_HS) {
pcgcctl |= BIT(17);
}
sys_write32(backup->glpmcfg, (mem_addr_t)&base->glpmcfg);
sys_write32(backup->gi2cctl, (mem_addr_t)&base->gi2cctl);
sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl);
sys_write32(backup->gahbcfg | USB_DWC2_GAHBCFG_GLBINTRMASK,
(mem_addr_t)&base->gahbcfg);
sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts);
sys_write32(USB_DWC2_GINTSTS_RSTRDONEINT, (mem_addr_t)&base->gintmsk);
sys_write32(backup->gusbcfg, (mem_addr_t)&base->gusbcfg);
sys_write32(backup->dcfg, (mem_addr_t)&base->dcfg);
if (bus_reset) {
sys_write32(backup->dcfg, (mem_addr_t)&base->dcfg);
}
if (!rwup) {
pcgcctl |= USB_DWC2_PCGCCTL_RESTOREMODE | USB_DWC2_PCGCCTL_RSTPDWNMODULE;
}
sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl);
k_busy_wait(1);
pcgcctl |= USB_DWC2_PCGCCTL_ESSREGRESTORED;
sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl);
/* Note: in Remote Wakeup case 15 ms max signaling time starts now */
/* Wait for Restore Done Interrupt */
dwc2_wait_for_bit(dev, (mem_addr_t)&base->gintsts, USB_DWC2_GINTSTS_RSTRDONEINT);
if (priv->wa_essregrestored) {
pcgcctl &= ~USB_DWC2_PCGCCTL_ESSREGRESTORED;
sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl);
k_busy_wait(1);
}
if (!bus_reset) {
sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts);
}
}
static void dwc2_restore_device_registers(const struct device *dev, bool rwup)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct dwc2_reg_backup *backup = &priv->backup;
sys_write32(backup->gotgctl, (mem_addr_t)&base->gotgctl);
sys_write32(backup->gahbcfg, (mem_addr_t)&base->gahbcfg);
sys_write32(backup->gusbcfg, (mem_addr_t)&base->gusbcfg);
sys_write32(backup->gintmsk, (mem_addr_t)&base->gintmsk);
sys_write32(backup->grxfsiz, (mem_addr_t)&base->grxfsiz);
sys_write32(backup->gnptxfsiz, (mem_addr_t)&base->gnptxfsiz);
sys_write32(backup->gdfifocfg, (mem_addr_t)&base->gdfifocfg);
for (uint8_t i = 1U; i < priv->ineps; i++) {
sys_write32(backup->dieptxf[i - 1], (mem_addr_t)&base->dieptxf[i - 1]);
}
if (!rwup) {
sys_write32(backup->dctl, (mem_addr_t)&base->dctl);
}
sys_write32(backup->diepmsk, (mem_addr_t)&base->diepmsk);
sys_write32(backup->doepmsk, (mem_addr_t)&base->doepmsk);
sys_write32(backup->daintmsk, (mem_addr_t)&base->daintmsk);
for (uint8_t i = 0U; i < 16; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i);
if (epdir == USB_DWC2_GHWCFG1_EPDIR_IN || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
sys_write32(backup->dieptsiz[i], (mem_addr_t)&base->in_ep[i].dieptsiz);
sys_write32(backup->diepdma[i], (mem_addr_t)&base->in_ep[i].diepdma);
sys_write32(backup->diepctl[i], (mem_addr_t)&base->in_ep[i].diepctl);
}
if (epdir == USB_DWC2_GHWCFG1_EPDIR_OUT || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
sys_write32(backup->doeptsiz[i], (mem_addr_t)&base->out_ep[i].doeptsiz);
sys_write32(backup->doepdma[i], (mem_addr_t)&base->out_ep[i].doepdma);
sys_write32(backup->doepctl[i], (mem_addr_t)&base->out_ep[i].doepctl);
}
}
}
static void dwc2_enter_hibernation(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
mem_addr_t gpwrdn_reg = (mem_addr_t)&base->gpwrdn;
mem_addr_t pcgcctl_reg = (mem_addr_t)&base->pcgcctl;
dwc2_backup_registers(dev);
/* This code currently only supports UTMI+. UTMI+ runs at either 30 or
* 60 MHz and therefore 1 us busy waits have sufficiently large margin.
*/
/* Enable PMU Logic */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUACTV);
k_busy_wait(1);
/* Stop PHY clock */
sys_set_bits(pcgcctl_reg, USB_DWC2_PCGCCTL_STOPPCLK);
k_busy_wait(1);
/* Enable PMU interrupt */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUINTSEL);
k_busy_wait(1);
/* Unmask PMU interrupt bits */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_LINESTAGECHANGEMSK |
USB_DWC2_GPWRDN_RESETDETMSK |
USB_DWC2_GPWRDN_DISCONNECTDETECTMSK |
USB_DWC2_GPWRDN_STSCHNGINTMSK);
k_busy_wait(1);
/* Enable power clamps */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNCLMP);
k_busy_wait(1);
/* Switch off power to the controller */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNSWTCH);
(void)dwc2_quirk_post_hibernation_entry(dev);
/* Mark that the core is hibernated */
priv->hibernated = 1;
LOG_DBG("Hibernated");
}
static void dwc2_exit_hibernation(const struct device *dev,
bool rwup, bool bus_reset)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
mem_addr_t gpwrdn_reg = (mem_addr_t)&base->gpwrdn;
mem_addr_t pcgcctl_reg = (mem_addr_t)&base->pcgcctl;
(void)dwc2_quirk_pre_hibernation_exit(dev);
/* Switch on power to the controller */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNSWTCH);
k_busy_wait(1);
/* Reset the controller */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNRST_N);
k_busy_wait(1);
/* Enable restore from PMU */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_RESTORE);
k_busy_wait(1);
/* Disable power clamps */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNCLMP);
if (rwup) {
if (priv->syncrst) {
k_busy_wait(1);
} else {
k_busy_wait(50);
}
}
/* Remove reset to the controller */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNRST_N);
k_busy_wait(1);
/* Disable PMU interrupt */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUINTSEL);
dwc2_restore_essential_registers(dev, rwup, bus_reset);
/* Disable restore from PMU */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_RESTORE);
k_busy_wait(1);
if (!rwup) {
/* Clear reset to power down module */
sys_clear_bits(pcgcctl_reg, USB_DWC2_PCGCCTL_RSTPDWNMODULE);
}
/* Restore GUSBCFG, DCFG and DCTL */
sys_write32(priv->backup.gusbcfg, (mem_addr_t)&base->gusbcfg);
sys_write32(priv->backup.dcfg, (mem_addr_t)&base->dcfg);
sys_write32(priv->backup.dctl, (mem_addr_t)&base->dctl);
/* Disable PMU */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUACTV);
if (!rwup) {
k_busy_wait(5);
sys_set_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_PWRONPRGDONE);
} else {
k_busy_wait(1);
sys_write32(USB_DWC2_DCTL_RMTWKUPSIG | priv->backup.dctl,
(mem_addr_t)&base->dctl);
}
k_msleep(1);
sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts);
}
static void cancel_hibernation_request(struct udc_dwc2_data *const priv)
{
k_event_clear(&priv->drv_evt, BIT(DWC2_DRV_EVT_ENTER_HIBERNATION));
}
static void request_hibernation(struct udc_dwc2_data *const priv)
{
if (priv->suspend_type == DWC2_SUSPEND_HIBERNATION) {
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_ENTER_HIBERNATION));
}
}
static void dwc2_unset_unused_fifo(const struct device *dev)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct udc_ep_config *tmp;
for (uint8_t i = priv->ineps - 1U; i > 0; i--) {
tmp = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN);
if (tmp->stat.enabled && (priv->txf_set & BIT(i))) {
return;
}
if (!tmp->stat.enabled && (priv->txf_set & BIT(i))) {
priv->txf_set &= ~BIT(i);
}
}
}
/*
* In dedicated FIFO mode there are i (i = 1 ... ineps - 1) FIFO size registers,
* e.g. DIEPTXF1, DIEPTXF2, ... DIEPTXF4. When dynfifosizing is enabled,
* the size register is mutable. The offset of DIEPTXF1 registers is 0.
*/
static int dwc2_set_dedicated_fifo(const struct device *dev,
struct udc_ep_config *const cfg,
uint32_t *const diepctl)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr);
const uint32_t addnl = USB_MPS_ADDITIONAL_TRANSACTIONS(cfg->mps);
uint32_t reqdep;
uint32_t txfaddr;
uint32_t txfdep;
uint32_t tmp;
/* Keep everything but FIFO number */
tmp = *diepctl & ~USB_DWC2_DEPCTL_TXFNUM_MASK;
reqdep = DIV_ROUND_UP(udc_mps_ep_size(cfg), 4U);
if (dwc2_in_buffer_dma_mode(dev)) {
/* In DMA mode, TxFIFO capable of holding 2 packets is enough */
reqdep *= MIN(2, (1 + addnl));
} else {
reqdep *= (1 + addnl);
}
if (priv->dynfifosizing) {
if (priv->txf_set & ~BIT_MASK(ep_idx)) {
dwc2_unset_unused_fifo(dev);
}
if (priv->txf_set & ~BIT_MASK(ep_idx)) {
LOG_WRN("Some of the FIFOs higher than %u are set, %lx",
ep_idx, priv->txf_set & ~BIT_MASK(ep_idx));
return -EIO;
}
if ((ep_idx - 1) != 0U) {
txfaddr = dwc2_get_txfdep(dev, ep_idx - 2) +
dwc2_get_txfaddr(dev, ep_idx - 2);
} else {
txfaddr = priv->rxfifo_depth +
MIN(UDC_DWC2_FIFO0_DEPTH, priv->max_txfifo_depth[0]);
}
/* Make sure to not set TxFIFO greater than hardware allows */
txfdep = reqdep;
if (txfdep > priv->max_txfifo_depth[ep_idx]) {
return -ENOMEM;
}
/* Do not allocate TxFIFO outside the SPRAM */
if (txfaddr + txfdep > priv->dfifodepth) {
return -ENOMEM;
}
/* Set FIFO depth (32-bit words) and address */
dwc2_set_txf(dev, ep_idx - 1, txfdep, txfaddr);
} else {
txfdep = dwc2_get_txfdep(dev, ep_idx - 1);
txfaddr = dwc2_get_txfaddr(dev, ep_idx - 1);
if (reqdep > txfdep) {
return -ENOMEM;
}
LOG_DBG("Reuse FIFO%u addr 0x%08x depth %u", ep_idx, txfaddr, txfdep);
}
/* Assign FIFO to the IN endpoint */
*diepctl = tmp | usb_dwc2_set_depctl_txfnum(ep_idx);
priv->txf_set |= BIT(ep_idx);
dwc2_flush_tx_fifo(dev, ep_idx);
LOG_INF("Set FIFO%u (ep 0x%02x) addr 0x%04x depth %u size %u",
ep_idx, cfg->addr, txfaddr, txfdep, dwc2_ftx_avail(dev, ep_idx));
return 0;
}
static int dwc2_ep_control_enable(const struct device *dev,
struct udc_ep_config *const cfg)
{
mem_addr_t dxepctl0_reg;
uint32_t dxepctl0;
dxepctl0_reg = dwc2_get_dxepctl_reg(dev, cfg->addr);
dxepctl0 = sys_read32(dxepctl0_reg);
dxepctl0 &= ~USB_DWC2_DEPCTL0_MPS_MASK;
switch (cfg->mps) {
case 8:
dxepctl0 |= USB_DWC2_DEPCTL0_MPS_8 << USB_DWC2_DEPCTL_MPS_POS;
break;
case 16:
dxepctl0 |= USB_DWC2_DEPCTL0_MPS_16 << USB_DWC2_DEPCTL_MPS_POS;
break;
case 32:
dxepctl0 |= USB_DWC2_DEPCTL0_MPS_32 << USB_DWC2_DEPCTL_MPS_POS;
break;
case 64:
dxepctl0 |= USB_DWC2_DEPCTL0_MPS_64 << USB_DWC2_DEPCTL_MPS_POS;
break;
default:
return -EINVAL;
}
dxepctl0 |= USB_DWC2_DEPCTL_USBACTEP;
if (cfg->addr == USB_CONTROL_EP_OUT) {
dwc2_flush_rx_fifo(dev);
} else {
dwc2_flush_tx_fifo(dev, 0);
}
sys_write32(dxepctl0, dxepctl0_reg);
dwc2_set_epint(dev, cfg, true);
return 0;
}
static int udc_dwc2_ep_activate(const struct device *dev,
struct udc_ep_config *const cfg)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_dwc2_data *const priv = udc_get_private(dev);
uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr);
mem_addr_t dxepctl_reg;
uint32_t dxepctl;
LOG_DBG("Enable ep 0x%02x", cfg->addr);
if (ep_idx == 0U) {
return dwc2_ep_control_enable(dev, cfg);
}
if (USB_EP_DIR_IS_OUT(cfg->addr)) {
/* TODO: use dwc2_get_dxepctl_reg() */
dxepctl_reg = (mem_addr_t)&base->out_ep[ep_idx].doepctl;
} else {
if (priv->ineps > 0U && ep_idx > (priv->ineps - 1U)) {
LOG_ERR("No resources available for ep 0x%02x", cfg->addr);
return -EINVAL;
}
dxepctl_reg = (mem_addr_t)&base->in_ep[ep_idx].diepctl;
}
dxepctl = sys_read32(dxepctl_reg);
/* Set max packet size */
dxepctl &= ~USB_DWC2_DEPCTL_MPS_MASK;
dxepctl |= usb_dwc2_set_depctl_mps(udc_mps_ep_size(cfg));
/* Set endpoint type */
dxepctl &= ~USB_DWC2_DEPCTL_EPTYPE_MASK;
switch (cfg->attributes & USB_EP_TRANSFER_TYPE_MASK) {
case USB_EP_TYPE_BULK:
dxepctl |= USB_DWC2_DEPCTL_EPTYPE_BULK <<
USB_DWC2_DEPCTL_EPTYPE_POS;
dxepctl |= USB_DWC2_DEPCTL_SETD0PID;
break;
case USB_EP_TYPE_INTERRUPT:
dxepctl |= USB_DWC2_DEPCTL_EPTYPE_INTERRUPT <<
USB_DWC2_DEPCTL_EPTYPE_POS;
dxepctl |= USB_DWC2_DEPCTL_SETD0PID;
break;
case USB_EP_TYPE_ISO:
dxepctl |= USB_DWC2_DEPCTL_EPTYPE_ISO <<
USB_DWC2_DEPCTL_EPTYPE_POS;
break;
default:
return -EINVAL;
}
if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U) {
int ret = dwc2_set_dedicated_fifo(dev, cfg, &dxepctl);
if (ret) {
return ret;
}
}
dxepctl |= USB_DWC2_DEPCTL_USBACTEP;
/* Enable endpoint interrupts */
dwc2_set_epint(dev, cfg, true);
sys_write32(dxepctl, dxepctl_reg);
for (uint8_t i = 1U; i < priv->ineps; i++) {
LOG_DBG("DIEPTXF%u %08x DIEPCTL%u %08x",
i, sys_read32((mem_addr_t)&base->dieptxf[i - 1U]), i, dxepctl);
}
return 0;
}
static int dwc2_unset_dedicated_fifo(const struct device *dev,
struct udc_ep_config *const cfg,
uint32_t *const diepctl)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr);
/* Clear FIFO number field */
*diepctl &= ~USB_DWC2_DEPCTL_TXFNUM_MASK;
if (priv->dynfifosizing) {
if (priv->txf_set & ~BIT_MASK(ep_idx)) {
LOG_WRN("Some of the FIFOs higher than %u are set, %lx",
ep_idx, priv->txf_set & ~BIT_MASK(ep_idx));
return 0;
}
dwc2_set_txf(dev, ep_idx - 1, 0, 0);
}
priv->txf_set &= ~BIT(ep_idx);
return 0;
}
/* Disabled IN endpoint means that device will send NAK (isochronous: ZLP) after
* receiving IN token from host even if there is packet available in TxFIFO.
* Disabled OUT endpoint means that device will NAK (isochronous: discard data)
* incoming OUT data (or HS PING) even if there is space available in RxFIFO.
*
* Set stall parameter to true if caller wants to send STALL instead of NAK.
*/
static void udc_dwc2_ep_disable(const struct device *dev,
struct udc_ep_config *const cfg, bool stall)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr);
mem_addr_t dxepctl_reg;
uint32_t dxepctl;
const bool is_iso = dwc2_ep_is_iso(cfg);
dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr);
dxepctl = sys_read32(dxepctl_reg);
if (priv->hibernated) {
/* Currently USB stack calls this function while hibernated,
* for example if usbd_disable() is called. We cannot access the
* real registers when hibernated, so just modify backup values
* that will be restored on hibernation exit.
*/
if (USB_EP_DIR_IS_OUT(cfg->addr)) {
dxepctl = priv->backup.doepctl[ep_idx];
dxepctl &= ~USB_DWC2_DEPCTL_EPENA;
if (stall) {
dxepctl |= USB_DWC2_DEPCTL_STALL;
} else {
dxepctl |= USB_DWC2_DEPCTL_SNAK;
}
priv->backup.doepctl[ep_idx] = dxepctl;
} else {
dxepctl = priv->backup.diepctl[ep_idx];
dxepctl &= ~USB_DWC2_DEPCTL_EPENA;
dxepctl |= USB_DWC2_DEPCTL_SNAK;
if (stall) {
dxepctl |= USB_DWC2_DEPCTL_STALL;
}
priv->backup.diepctl[ep_idx] = dxepctl;
priv->pending_tx_flush |= BIT(usb_dwc2_get_depctl_txfnum(dxepctl));
}
udc_ep_set_busy(cfg, false);
return;
}
if (!is_iso && (dxepctl & USB_DWC2_DEPCTL_NAKSTS) &&
!(dxepctl & USB_DWC2_DEPCTL_EPENA)) {
/* Endpoint already sends forced NAKs. STALL if necessary. */
if (stall) {
dxepctl |= USB_DWC2_DEPCTL_STALL;
sys_write32(dxepctl, dxepctl_reg);
}
return;
}
/* FIXME: This function needs to be changed to not synchronously wait
* for the events to happen because the actions here are racing against
* the USB host packets. It is possible that the IN token or OUT DATA
* gets sent shortly before this function disables the endpoint. If this
* happens, the XferCompl would be set and driver will incorrectly think
* that either:
* * never queued transfer finished, OR
* * transfer queued in incompisoin handler finished (before it really
* does and then it'll "double"-finish when it actually finishes)
*
* For the time being XferCompl is cleared as a workaround.
*/
if (USB_EP_DIR_IS_OUT(cfg->addr)) {
mem_addr_t dctl_reg, gintsts_reg, doepint_reg;
uint32_t dctl;
dctl_reg = (mem_addr_t)&base->dctl;
gintsts_reg = (mem_addr_t)&base->gintsts;
doepint_reg = (mem_addr_t)&base->out_ep[ep_idx].doepint;
dctl = sys_read32(dctl_reg);
if (sys_read32(gintsts_reg) & USB_DWC2_GINTSTS_GOUTNAKEFF) {
LOG_ERR("GOUTNAKEFF already active");
} else {
dctl |= USB_DWC2_DCTL_SGOUTNAK;
sys_write32(dctl, dctl_reg);
dctl &= ~USB_DWC2_DCTL_SGOUTNAK;
}
dwc2_wait_for_bit(dev, gintsts_reg, USB_DWC2_GINTSTS_GOUTNAKEFF);
/* The application cannot disable control OUT endpoint 0. */
if (ep_idx != 0) {
dxepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_EPDIS;
}
if (stall) {
/* For OUT endpoints STALL is set instead of SNAK */
dxepctl |= USB_DWC2_DEPCTL_STALL;
} else {
dxepctl |= USB_DWC2_DEPCTL_SNAK;
}
sys_write32(dxepctl, dxepctl_reg);
if (ep_idx != 0) {
dwc2_wait_for_bit(dev, doepint_reg, USB_DWC2_DOEPINT_EPDISBLD);
}
/* Clear Endpoint Disabled interrupt */
sys_write32(USB_DWC2_DOEPINT_EPDISBLD | USB_DWC2_DOEPINT_XFERCOMPL, doepint_reg);
dctl |= USB_DWC2_DCTL_CGOUTNAK;
sys_write32(dctl, dctl_reg);
} else {
mem_addr_t diepint_reg;
diepint_reg = (mem_addr_t)&base->in_ep[ep_idx].diepint;
dxepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_SNAK;
if (stall) {
/* For IN endpoints STALL is set in addition to SNAK */
dxepctl |= USB_DWC2_DEPCTL_STALL;
}
sys_write32(dxepctl, dxepctl_reg);
dwc2_wait_for_bit(dev, diepint_reg, USB_DWC2_DIEPINT_INEPNAKEFF);
dxepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_EPDIS;
sys_write32(dxepctl, dxepctl_reg);
dwc2_wait_for_bit(dev, diepint_reg, USB_DWC2_DIEPINT_EPDISBLD);
/* Clear Endpoint Disabled interrupt */
sys_write32(USB_DWC2_DIEPINT_EPDISBLD | USB_DWC2_DIEPINT_XFERCOMPL, diepint_reg);
/* TODO: Read DIEPTSIZn here? Programming Guide suggest it to
* let application know how many bytes of interrupted transfer
* were transferred to the host.
*/
dwc2_flush_tx_fifo(dev, usb_dwc2_get_depctl_txfnum(dxepctl));
}
udc_ep_set_busy(cfg, false);
}
/* Deactivated endpoint means that there will be a bus timeout when the host
* tries to access the endpoint.
*/
static int udc_dwc2_ep_deactivate(const struct device *dev,
struct udc_ep_config *const cfg)
{
uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr);
mem_addr_t dxepctl_reg;
uint32_t dxepctl;
dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr);
dxepctl = sys_read32(dxepctl_reg);
if (dxepctl & USB_DWC2_DEPCTL_USBACTEP) {
LOG_DBG("Disable ep 0x%02x DxEPCTL%u %x",
cfg->addr, ep_idx, dxepctl);
udc_dwc2_ep_disable(dev, cfg, false);
dxepctl = sys_read32(dxepctl_reg);
dxepctl &= ~USB_DWC2_DEPCTL_USBACTEP;
} else {
LOG_WRN("ep 0x%02x is not active DxEPCTL%u %x",
cfg->addr, ep_idx, dxepctl);
}
if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U &&
ep_idx != 0U) {
dwc2_unset_dedicated_fifo(dev, cfg, &dxepctl);
}
sys_write32(dxepctl, dxepctl_reg);
dwc2_set_epint(dev, cfg, false);
return 0;
}
static int udc_dwc2_ep_set_halt(const struct device *dev,
struct udc_ep_config *const cfg)
{
uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr);
udc_dwc2_ep_disable(dev, cfg, true);
LOG_DBG("Set halt ep 0x%02x", cfg->addr);
if (ep_idx != 0) {
cfg->stat.halted = true;
} else {
/* Data/Status stage is STALLed, allow receiving next SETUP */
dwc2_ensure_setup_ready(dev);
}
return 0;
}
static int udc_dwc2_ep_clear_halt(const struct device *dev,
struct udc_ep_config *const cfg)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
mem_addr_t dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr);
uint32_t dxepctl;
dxepctl = sys_read32(dxepctl_reg);
dxepctl &= ~USB_DWC2_DEPCTL_STALL;
dxepctl |= USB_DWC2_DEPCTL_SETD0PID;
sys_write32(dxepctl, dxepctl_reg);
LOG_DBG("Clear halt ep 0x%02x", cfg->addr);
cfg->stat.halted = false;
/* Resume queued transfers if any */
if (udc_buf_peek(cfg)) {
int ep_bit;
if (USB_EP_DIR_IS_IN(cfg->addr)) {
ep_bit = USB_EP_GET_IDX(cfg->addr);
} else {
ep_bit = 16 + USB_EP_GET_IDX(cfg->addr);
}
atomic_set_bit(&priv->xfer_new, ep_bit);
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_XFER));
}
return 0;
}
static int udc_dwc2_ep_enqueue(const struct device *dev,
struct udc_ep_config *const cfg,
struct net_buf *const buf)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
LOG_DBG("%p enqueue %x %p", dev, cfg->addr, buf);
udc_buf_put(cfg, buf);
if (!cfg->stat.halted) {
int ep_bit;
if (USB_EP_DIR_IS_IN(cfg->addr)) {
ep_bit = USB_EP_GET_IDX(cfg->addr);
} else {
ep_bit = 16 + USB_EP_GET_IDX(cfg->addr);
}
atomic_set_bit(&priv->xfer_new, ep_bit);
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_XFER));
}
return 0;
}
static int udc_dwc2_ep_dequeue(const struct device *dev,
struct udc_ep_config *const cfg)
{
struct net_buf *buf;
udc_dwc2_ep_disable(dev, cfg, false);
buf = udc_buf_get_all(cfg);
if (buf) {
udc_submit_ep_event(dev, buf, -ECONNABORTED);
}
udc_ep_set_busy(cfg, false);
LOG_DBG("dequeue ep 0x%02x", cfg->addr);
return 0;
}
static int udc_dwc2_set_address(const struct device *dev, const uint8_t addr)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
mem_addr_t dcfg_reg = (mem_addr_t)&base->dcfg;
uint32_t dcfg;
if (addr > (USB_DWC2_DCFG_DEVADDR_MASK >> USB_DWC2_DCFG_DEVADDR_POS)) {
return -EINVAL;
}
dcfg = sys_read32(dcfg_reg);
dcfg &= ~USB_DWC2_DCFG_DEVADDR_MASK;
dcfg |= usb_dwc2_set_dcfg_devaddr(addr);
sys_write32(dcfg, dcfg_reg);
LOG_DBG("Set new address %u for %p", addr, dev);
return 0;
}
static int udc_dwc2_test_mode(const struct device *dev,
const uint8_t mode, const bool dryrun)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
mem_addr_t dctl_reg = (mem_addr_t)&base->dctl;
uint32_t dctl;
if (mode == 0U || mode > USB_DWC2_DCTL_TSTCTL_TESTFE) {
return -EINVAL;
}
dctl = sys_read32(dctl_reg);
if (usb_dwc2_get_dctl_tstctl(dctl) != USB_DWC2_DCTL_TSTCTL_DISABLED) {
return -EALREADY;
}
if (dryrun) {
LOG_DBG("Test Mode %u supported", mode);
return 0;
}
dctl |= usb_dwc2_set_dctl_tstctl(mode);
sys_write32(dctl, dctl_reg);
LOG_DBG("Enable Test Mode %u", mode);
return 0;
}
static int udc_dwc2_host_wakeup(const struct device *dev)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
LOG_DBG("Remote wakeup from %p", dev);
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_REMOTE_WAKEUP));
return 0;
}
/* Return actual USB device speed */
static enum udc_bus_speed udc_dwc2_device_speed(const struct device *dev)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
switch (priv->enumspd) {
case USB_DWC2_DSTS_ENUMSPD_HS3060:
return UDC_BUS_SPEED_HS;
case USB_DWC2_DSTS_ENUMSPD_LS6:
__ASSERT(false, "Low speed mode not supported");
__fallthrough;
case USB_DWC2_DSTS_ENUMSPD_FS48:
__fallthrough;
case USB_DWC2_DSTS_ENUMSPD_FS3060:
__fallthrough;
default:
return UDC_BUS_SPEED_FS;
}
}
static int dwc2_core_soft_reset(const struct device *dev)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
mem_addr_t grstctl_reg = (mem_addr_t)&base->grstctl;
const unsigned int csr_timeout_us = 10000UL;
uint32_t cnt = 0UL;
/* Check AHB master idle state */
while (!(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_AHBIDLE)) {
k_busy_wait(1);
if (++cnt > csr_timeout_us) {
LOG_ERR("Wait for AHB idle timeout, GRSTCTL 0x%08x",
sys_read32(grstctl_reg));
return -EIO;
}
}
/* Apply Core Soft Reset */
sys_write32(USB_DWC2_GRSTCTL_CSFTRST, grstctl_reg);
cnt = 0UL;
do {
if (++cnt > csr_timeout_us) {
LOG_ERR("Wait for CSR done timeout, GRSTCTL 0x%08x",
sys_read32(grstctl_reg));
return -EIO;
}
k_busy_wait(1);
if (dwc2_quirk_is_phy_clk_off(dev)) {
/* Software reset won't finish without PHY clock */
return -EIO;
}
} while (sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRST &&
!(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRSTDONE));
/* CSFTRSTDONE is W1C so the write must have the bit set to clear it */
sys_clear_bits(grstctl_reg, USB_DWC2_GRSTCTL_CSFTRST);
return 0;
}
static int udc_dwc2_init_controller(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct usb_dwc2_reg *const base = config->base;
mem_addr_t grxfsiz_reg = (mem_addr_t)&base->grxfsiz;
mem_addr_t gahbcfg_reg = (mem_addr_t)&base->gahbcfg;
mem_addr_t gusbcfg_reg = (mem_addr_t)&base->gusbcfg;
mem_addr_t dcfg_reg = (mem_addr_t)&base->dcfg;
uint32_t gsnpsid;
uint32_t dcfg;
uint32_t gusbcfg;
uint32_t gahbcfg;
uint32_t ghwcfg2;
uint32_t ghwcfg3;
uint32_t ghwcfg4;
uint32_t val;
int ret;
bool hs_phy;
ret = dwc2_core_soft_reset(dev);
if (ret) {
return ret;
}
/* Enable RTL workarounds based on controller revision */
gsnpsid = sys_read32((mem_addr_t)&base->gsnpsid);
priv->wa_essregrestored = gsnpsid < USB_DWC2_GSNPSID_REV_5_00A;
priv->ghwcfg1 = sys_read32((mem_addr_t)&base->ghwcfg1);
ghwcfg2 = sys_read32((mem_addr_t)&base->ghwcfg2);
ghwcfg3 = sys_read32((mem_addr_t)&base->ghwcfg3);
ghwcfg4 = sys_read32((mem_addr_t)&base->ghwcfg4);
if (!(ghwcfg4 & USB_DWC2_GHWCFG4_DEDFIFOMODE)) {
LOG_ERR("Only dedicated TX FIFO mode is supported");
return -ENOTSUP;
}
/*
* Force device mode as we do no support role changes.
* Wait 25ms for the change to take effect.
*/
gusbcfg = USB_DWC2_GUSBCFG_FORCEDEVMODE;
sys_write32(gusbcfg, gusbcfg_reg);
k_msleep(25);
/* Buffer DMA is always supported in Internal DMA mode.
* TODO: check and support descriptor DMA if available
*/
priv->bufferdma = (usb_dwc2_get_ghwcfg2_otgarch(ghwcfg2) ==
USB_DWC2_GHWCFG2_OTGARCH_INTERNALDMA);
if (!IS_ENABLED(CONFIG_UDC_DWC2_DMA)) {
priv->bufferdma = 0;
} else if (priv->bufferdma) {
LOG_WRN("Experimental DMA enabled");
}
if (ghwcfg2 & USB_DWC2_GHWCFG2_DYNFIFOSIZING) {
LOG_DBG("Dynamic FIFO Sizing is enabled");
priv->dynfifosizing = true;
}
if (IS_ENABLED(CONFIG_UDC_DWC2_HIBERNATION) &&
ghwcfg4 & USB_DWC2_GHWCFG4_HIBERNATION) {
LOG_INF("Hibernation enabled");
priv->suspend_type = DWC2_SUSPEND_HIBERNATION;
} else {
priv->suspend_type = DWC2_SUSPEND_NO_POWER_SAVING;
}
/* Get the number or endpoints and IN endpoints we can use later */
priv->numdeveps = usb_dwc2_get_ghwcfg2_numdeveps(ghwcfg2) + 1U;
priv->ineps = usb_dwc2_get_ghwcfg4_ineps(ghwcfg4) + 1U;
LOG_DBG("Number of endpoints (NUMDEVEPS + 1) %u", priv->numdeveps);
LOG_DBG("Number of IN endpoints (INEPS + 1) %u", priv->ineps);
LOG_DBG("Number of periodic IN endpoints (NUMDEVPERIOEPS) %u",
usb_dwc2_get_ghwcfg4_numdevperioeps(ghwcfg4));
LOG_DBG("Number of additional control endpoints (NUMCTLEPS) %u",
usb_dwc2_get_ghwcfg4_numctleps(ghwcfg4));
LOG_DBG("OTG architecture (OTGARCH) %u, mode (OTGMODE) %u",
usb_dwc2_get_ghwcfg2_otgarch(ghwcfg2),
usb_dwc2_get_ghwcfg2_otgmode(ghwcfg2));
priv->dfifodepth = usb_dwc2_get_ghwcfg3_dfifodepth(ghwcfg3);
LOG_DBG("DFIFO depth (DFIFODEPTH) %u bytes", priv->dfifodepth * 4);
priv->max_pktcnt = GHWCFG3_PKTCOUNT(usb_dwc2_get_ghwcfg3_pktsizewidth(ghwcfg3));
priv->max_xfersize = GHWCFG3_XFERSIZE(usb_dwc2_get_ghwcfg3_xfersizewidth(ghwcfg3));
LOG_DBG("Max packet count %u, Max transfer size %u",
priv->max_pktcnt, priv->max_xfersize);
LOG_DBG("Vendor Control interface support enabled: %s",
(ghwcfg3 & USB_DWC2_GHWCFG3_VNDCTLSUPT) ? "true" : "false");
LOG_DBG("PHY interface type: FSPHYTYPE %u, HSPHYTYPE %u, DATAWIDTH %u",
usb_dwc2_get_ghwcfg2_fsphytype(ghwcfg2),
usb_dwc2_get_ghwcfg2_hsphytype(ghwcfg2),
usb_dwc2_get_ghwcfg4_phydatawidth(ghwcfg4));
LOG_DBG("LPM mode is %s",
(ghwcfg3 & USB_DWC2_GHWCFG3_LPMMODE) ? "enabled" : "disabled");
if (ghwcfg3 & USB_DWC2_GHWCFG3_RSTTYPE) {
priv->syncrst = 1;
}
/* Configure AHB, select Completer or DMA mode */
gahbcfg = sys_read32(gahbcfg_reg);
if (priv->bufferdma) {
gahbcfg |= USB_DWC2_GAHBCFG_DMAEN;
} else {
gahbcfg &= ~USB_DWC2_GAHBCFG_DMAEN;
}
sys_write32(gahbcfg, gahbcfg_reg);
dcfg = sys_read32(dcfg_reg);
dcfg &= ~USB_DWC2_DCFG_DESCDMA;
/* Configure PHY and device speed */
dcfg &= ~USB_DWC2_DCFG_DEVSPD_MASK;
switch (usb_dwc2_get_ghwcfg2_hsphytype(ghwcfg2)) {
case USB_DWC2_GHWCFG2_HSPHYTYPE_UTMIPLUSULPI:
__fallthrough;
case USB_DWC2_GHWCFG2_HSPHYTYPE_ULPI:
gusbcfg |= USB_DWC2_GUSBCFG_PHYSEL_USB20 |
USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_ULPI;
if (IS_ENABLED(CONFIG_UDC_DRIVER_HIGH_SPEED_SUPPORT_ENABLED)) {
dcfg |= usb_dwc2_set_dcfg_devspd(USB_DWC2_DCFG_DEVSPD_USBHS20);
} else {
dcfg |= usb_dwc2_set_dcfg_devspd(USB_DWC2_DCFG_DEVSPD_USBFS20);
}
hs_phy = true;
break;
case USB_DWC2_GHWCFG2_HSPHYTYPE_UTMIPLUS:
gusbcfg |= USB_DWC2_GUSBCFG_PHYSEL_USB20 |
USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_UTMI;
if (IS_ENABLED(CONFIG_UDC_DRIVER_HIGH_SPEED_SUPPORT_ENABLED)) {
dcfg |= usb_dwc2_set_dcfg_devspd(USB_DWC2_DCFG_DEVSPD_USBHS20);
} else {
dcfg |= usb_dwc2_set_dcfg_devspd(USB_DWC2_DCFG_DEVSPD_USBFS20);
}
hs_phy = true;
break;
case USB_DWC2_GHWCFG2_HSPHYTYPE_NO_HS:
__fallthrough;
default:
if (usb_dwc2_get_ghwcfg2_fsphytype(ghwcfg2) !=
USB_DWC2_GHWCFG2_FSPHYTYPE_NO_FS) {
gusbcfg |= USB_DWC2_GUSBCFG_PHYSEL_USB11;
}
dcfg |= usb_dwc2_set_dcfg_devspd(USB_DWC2_DCFG_DEVSPD_USBFS1148);
hs_phy = false;
}
if (usb_dwc2_get_ghwcfg4_phydatawidth(ghwcfg4)) {
gusbcfg |= USB_DWC2_GUSBCFG_PHYIF_16_BIT;
}
/* Update PHY configuration */
sys_write32(gusbcfg, gusbcfg_reg);
sys_write32(dcfg, dcfg_reg);
priv->outeps = 0U;
for (uint8_t i = 0U; i < priv->numdeveps; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i);
if (epdir == USB_DWC2_GHWCFG1_EPDIR_OUT ||
epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
mem_addr_t doepctl_reg = dwc2_get_dxepctl_reg(dev, i);
sys_write32(USB_DWC2_DEPCTL_SNAK, doepctl_reg);
priv->outeps++;
}
}
LOG_DBG("Number of OUT endpoints %u", priv->outeps);
/* Read and store all TxFIFO depths because Programmed FIFO Depths must
* not exceed the power-on values.
*/
val = sys_read32((mem_addr_t)&base->gnptxfsiz);
priv->max_txfifo_depth[0] = usb_dwc2_get_gnptxfsiz_nptxfdep(val);
for (uint8_t i = 1; i < priv->ineps; i++) {
priv->max_txfifo_depth[i] = dwc2_get_txfdep(dev, i - 1);
}
priv->rxfifo_depth = usb_dwc2_get_grxfsiz(sys_read32(grxfsiz_reg));
if (priv->dynfifosizing) {
uint32_t gnptxfsiz;
uint32_t default_depth;
/* TODO: For proper runtime FIFO sizing UDC driver would have to
* have prior knowledge of the USB configurations. Only with the
* prior knowledge, the driver will be able to fairly distribute
* available resources. For the time being just use different
* defaults based on maximum configured PHY speed, but this has
* to be revised if e.g. thresholding support would be necessary
* on some target.
*/
if (hs_phy) {
default_depth = UDC_DWC2_GRXFSIZ_HS_DEFAULT;
} else {
default_depth = UDC_DWC2_GRXFSIZ_FS_DEFAULT;
}
default_depth += priv->outeps * 2U;
/* Driver does not dynamically resize RxFIFO so there is no need
* to store reset value. Read the reset value and make sure that
* the programmed value is not greater than what driver sets.
*/
priv->rxfifo_depth = MIN(priv->rxfifo_depth, default_depth);
sys_write32(usb_dwc2_set_grxfsiz(priv->rxfifo_depth), grxfsiz_reg);
/* Set TxFIFO 0 depth */
val = MIN(UDC_DWC2_FIFO0_DEPTH, priv->max_txfifo_depth[0]);
gnptxfsiz = usb_dwc2_set_gnptxfsiz_nptxfdep(val) |
usb_dwc2_set_gnptxfsiz_nptxfstaddr(priv->rxfifo_depth);
sys_write32(gnptxfsiz, (mem_addr_t)&base->gnptxfsiz);
}
LOG_DBG("RX FIFO size %u bytes", priv->rxfifo_depth * 4);
for (uint8_t i = 1U; i < priv->ineps; i++) {
LOG_DBG("TX FIFO%u depth %u addr %u",
i, priv->max_txfifo_depth[i], dwc2_get_txfaddr(dev, i));
}
if (udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT,
USB_EP_TYPE_CONTROL, 64, 0)) {
LOG_ERR("Failed to enable control endpoint");
return -EIO;
}
if (udc_ep_enable_internal(dev, USB_CONTROL_EP_IN,
USB_EP_TYPE_CONTROL, 64, 0)) {
LOG_ERR("Failed to enable control endpoint");
return -EIO;
}
/* Unmask interrupts */
sys_write32(USB_DWC2_GINTSTS_OEPINT | USB_DWC2_GINTSTS_IEPINT |
USB_DWC2_GINTSTS_ENUMDONE | USB_DWC2_GINTSTS_USBRST |
USB_DWC2_GINTSTS_WKUPINT | USB_DWC2_GINTSTS_USBSUSP |
USB_DWC2_GINTSTS_INCOMPISOOUT | USB_DWC2_GINTSTS_INCOMPISOIN |
USB_DWC2_GINTSTS_SOF,
(mem_addr_t)&base->gintmsk);
return 0;
}
static int udc_dwc2_enable(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
int err;
err = dwc2_quirk_pre_enable(dev);
if (err) {
LOG_ERR("Quirk pre enable failed %d", err);
return err;
}
err = udc_dwc2_init_controller(dev);
if (err) {
return err;
}
/* Enable global interrupt */
sys_set_bits((mem_addr_t)&base->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK);
config->irq_enable_func(dev);
/* Disable soft disconnect */
sys_clear_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_SFTDISCON);
LOG_DBG("Enable device %p", base);
err = dwc2_quirk_post_enable(dev);
if (err) {
LOG_ERR("Quirk post enable failed %d", err);
return err;
}
return 0;
}
static int udc_dwc2_disable(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
mem_addr_t dctl_reg = (mem_addr_t)&base->dctl;
struct net_buf *buf;
int err;
LOG_DBG("Disable device %p", dev);
if (udc_ep_disable_internal(dev, USB_CONTROL_EP_OUT)) {
LOG_DBG("Failed to disable control endpoint");
return -EIO;
}
if (udc_ep_disable_internal(dev, USB_CONTROL_EP_IN)) {
LOG_DBG("Failed to disable control endpoint");
return -EIO;
}
config->irq_disable_func(dev);
if (priv->hibernated) {
dwc2_exit_hibernation(dev, false, true);
priv->hibernated = 0;
priv->pending_tx_flush = 0;
}
sys_clear_bits((mem_addr_t)&base->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK);
/* Enable soft disconnect */
sys_set_bits(dctl_reg, USB_DWC2_DCTL_SFTDISCON);
/* OUT endpoint 0 cannot be disabled by software. The buffer allocated
* in dwc2_ctrl_feed_dout() can only be freed after core reset if the
* core was in Buffer DMA mode.
*
* Soft Reset does timeout if PHY clock is not running. However, just
* triggering Soft Reset seems to be enough on shutdown clean up.
*/
dwc2_core_soft_reset(dev);
buf = udc_buf_get_all(udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT));
if (buf) {
net_buf_unref(buf);
}
err = dwc2_quirk_disable(dev);
if (err) {
LOG_ERR("Quirk disable failed %d", err);
return err;
}
return 0;
}
static int udc_dwc2_init(const struct device *dev)
{
int ret;
ret = dwc2_quirk_init(dev);
if (ret) {
LOG_ERR("Quirk init failed %d", ret);
return ret;
}
return dwc2_init_pinctrl(dev);
}
static int udc_dwc2_shutdown(const struct device *dev)
{
int ret;
ret = dwc2_quirk_shutdown(dev);
if (ret) {
LOG_ERR("Quirk shutdown failed %d", ret);
return ret;
}
return 0;
}
static int dwc2_driver_preinit(const struct device *dev)
{
const struct udc_dwc2_config *config = dev->config;
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct udc_data *data = dev->data;
uint16_t mps = 1023;
uint32_t numdeveps;
uint32_t ineps;
int err;
k_mutex_init(&data->mutex);
k_event_init(&priv->drv_evt);
atomic_clear(&priv->xfer_new);
atomic_clear(&priv->xfer_finished);
data->caps.rwup = true;
data->caps.addr_before_status = true;
data->caps.mps0 = UDC_MPS0_64;
(void)dwc2_quirk_caps(dev);
if (data->caps.hs) {
mps = 1024;
}
/*
* At this point, we cannot or do not want to access the hardware
* registers to get GHWCFGn values. For now, we will use devicetree to
* get GHWCFGn values and use them to determine the number and type of
* configured endpoints in the hardware. This can be considered a
* workaround, and we may change the upper layer internals to avoid it
* in the future.
*/
ineps = usb_dwc2_get_ghwcfg4_ineps(config->ghwcfg4) + 1U;
numdeveps = usb_dwc2_get_ghwcfg2_numdeveps(config->ghwcfg2) + 1U;
LOG_DBG("Number of endpoints (NUMDEVEPS + 1) %u", numdeveps);
LOG_DBG("Number of IN endpoints (INEPS + 1) %u", ineps);
for (uint32_t i = 0, n = 0; i < numdeveps; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i);
if (epdir != USB_DWC2_GHWCFG1_EPDIR_OUT &&
epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) {
continue;
}
if (i == 0) {
config->ep_cfg_out[n].caps.control = 1;
config->ep_cfg_out[n].caps.mps = 64;
} else {
config->ep_cfg_out[n].caps.bulk = 1;
config->ep_cfg_out[n].caps.interrupt = 1;
config->ep_cfg_out[n].caps.iso = 1;
config->ep_cfg_out[n].caps.high_bandwidth = data->caps.hs;
config->ep_cfg_out[n].caps.mps = mps;
}
config->ep_cfg_out[n].caps.out = 1;
config->ep_cfg_out[n].addr = USB_EP_DIR_OUT | i;
LOG_DBG("Register ep 0x%02x (%u)", i, n);
err = udc_register_ep(dev, &config->ep_cfg_out[n]);
if (err != 0) {
LOG_ERR("Failed to register endpoint");
return err;
}
n++;
/* Also check the number of desired OUT endpoints in devicetree. */
if (n >= config->num_out_eps) {
break;
}
}
for (uint32_t i = 0, n = 0; i < numdeveps; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i);
if (epdir != USB_DWC2_GHWCFG1_EPDIR_IN &&
epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) {
continue;
}
if (i == 0) {
config->ep_cfg_in[n].caps.control = 1;
config->ep_cfg_in[n].caps.mps = 64;
} else {
config->ep_cfg_in[n].caps.bulk = 1;
config->ep_cfg_in[n].caps.interrupt = 1;
config->ep_cfg_in[n].caps.iso = 1;
config->ep_cfg_in[n].caps.high_bandwidth = data->caps.hs;
config->ep_cfg_in[n].caps.mps = mps;
}
config->ep_cfg_in[n].caps.in = 1;
config->ep_cfg_in[n].addr = USB_EP_DIR_IN | i;
LOG_DBG("Register ep 0x%02x (%u)", USB_EP_DIR_IN | i, n);
err = udc_register_ep(dev, &config->ep_cfg_in[n]);
if (err != 0) {
LOG_ERR("Failed to register endpoint");
return err;
}
n++;
/* Also check the number of desired IN endpoints in devicetree. */
if (n >= MIN(ineps, config->num_in_eps)) {
break;
}
}
config->make_thread(dev);
return 0;
}
static void udc_dwc2_lock(const struct device *dev)
{
k_sched_lock();
udc_lock_internal(dev, K_FOREVER);
}
static void udc_dwc2_unlock(const struct device *dev)
{
udc_unlock_internal(dev);
k_sched_unlock();
}
static void dwc2_on_bus_reset(const struct device *dev)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_dwc2_data *const priv = udc_get_private(dev);
uint32_t doepmsk;
/* Set the NAK bit for all OUT endpoints */
for (uint8_t i = 0U; i < priv->numdeveps; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i);
mem_addr_t doepctl_reg;
LOG_DBG("ep 0x%02x EPDIR %u", i, epdir);
if (epdir == USB_DWC2_GHWCFG1_EPDIR_OUT ||
epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
doepctl_reg = dwc2_get_dxepctl_reg(dev, i);
sys_write32(USB_DWC2_DEPCTL_SNAK, doepctl_reg);
}
}
doepmsk = USB_DWC2_DOEPINT_SETUP | USB_DWC2_DOEPINT_XFERCOMPL;
if (dwc2_in_buffer_dma_mode(dev)) {
doepmsk |= USB_DWC2_DOEPINT_STSPHSERCVD;
}
sys_write32(doepmsk, (mem_addr_t)&base->doepmsk);
sys_set_bits((mem_addr_t)&base->diepmsk, USB_DWC2_DIEPINT_XFERCOMPL);
/* Software has to handle RxFLvl interrupt only in Completer mode */
if (dwc2_in_completer_mode(dev)) {
sys_set_bits((mem_addr_t)&base->gintmsk,
USB_DWC2_GINTSTS_RXFLVL);
}
/* Clear device address during reset. */
sys_clear_bits((mem_addr_t)&base->dcfg, USB_DWC2_DCFG_DEVADDR_MASK);
/* Speed enumeration must happen after reset. */
priv->enumdone = 0;
}
static void dwc2_handle_enumdone(const struct device *dev)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_dwc2_data *const priv = udc_get_private(dev);
uint32_t dsts;
dsts = sys_read32((mem_addr_t)&base->dsts);
priv->enumspd = usb_dwc2_get_dsts_enumspd(dsts);
priv->enumdone = 1;
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_ENUM_DONE));
}
static inline int dwc2_read_fifo_setup(const struct device *dev, uint8_t ep,
const size_t size)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_dwc2_data *const priv = udc_get_private(dev);
size_t offset;
/* FIFO access is always in 32-bit words */
if (size != 8) {
LOG_ERR("%d bytes SETUP", size);
}
/*
* We store the setup packet temporarily in the driver's private data
* because there is always a race risk after the status stage OUT
* packet from the host and the new setup packet. This is fine in
* bottom-half processing because the events arrive in a queue and
* there will be a next net_buf for the setup packet.
*/
for (offset = 0; offset < MIN(size, 8); offset += 4) {
sys_put_le32(sys_read32(UDC_DWC2_EP_FIFO(base, ep)),
&priv->setup[offset]);
}
/* On protocol error simply discard extra data */
while (offset < size) {
sys_read32(UDC_DWC2_EP_FIFO(base, ep));
offset += 4;
}
return 0;
}
static inline void dwc2_handle_rxflvl(const struct device *dev)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_ep_config *ep_cfg;
struct net_buf *buf;
uint32_t grxstsp;
uint32_t pktsts;
uint32_t bcnt;
uint8_t ep;
grxstsp = sys_read32((mem_addr_t)&base->grxstsp);
ep = usb_dwc2_get_grxstsp_epnum(grxstsp);
bcnt = usb_dwc2_get_grxstsp_bcnt(grxstsp);
pktsts = usb_dwc2_get_grxstsp_pktsts(grxstsp);
LOG_DBG("ep 0x%02x: pktsts %u, bcnt %u", ep, pktsts, bcnt);
switch (pktsts) {
case USB_DWC2_GRXSTSR_PKTSTS_SETUP:
dwc2_read_fifo_setup(dev, ep, bcnt);
break;
case USB_DWC2_GRXSTSR_PKTSTS_OUT_DATA:
ep_cfg = udc_get_ep_cfg(dev, ep);
buf = udc_buf_peek(ep_cfg);
/* RxFIFO data must be retrieved even when buf is NULL */
dwc2_read_fifo(dev, ep, buf, bcnt);
break;
case USB_DWC2_GRXSTSR_PKTSTS_OUT_DATA_DONE:
LOG_DBG("RX pktsts DONE");
break;
case USB_DWC2_GRXSTSR_PKTSTS_SETUP_DONE:
LOG_DBG("SETUP pktsts DONE");
case USB_DWC2_GRXSTSR_PKTSTS_GLOBAL_OUT_NAK:
LOG_DBG("Global OUT NAK");
break;
default:
break;
}
}
static inline void dwc2_handle_in_xfercompl(const struct device *dev,
const uint8_t ep_idx)
{
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct udc_ep_config *ep_cfg;
struct net_buf *buf;
ep_cfg = udc_get_ep_cfg(dev, ep_idx | USB_EP_DIR_IN);
buf = udc_buf_peek(ep_cfg);
if (buf == NULL) {
udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS);
return;
}
net_buf_pull(buf, priv->tx_len[ep_idx]);
if (buf->len && dwc2_tx_fifo_write(dev, ep_cfg, buf) == 0) {
return;
}
atomic_set_bit(&priv->xfer_finished, ep_idx);
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_EP_FINISHED));
}
static inline void dwc2_handle_iepint(const struct device *dev)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
uint32_t diepmsk;
uint32_t epint;
diepmsk = sys_read32((mem_addr_t)&base->diepmsk);
epint = usb_dwc2_get_daint_inepint(sys_read32((mem_addr_t)&base->daint));
while (epint) {
uint8_t n = find_lsb_set(epint) - 1;
mem_addr_t diepint_reg = (mem_addr_t)&base->in_ep[n].diepint;
uint32_t diepint;
uint32_t status;
/* Read and clear interrupt status */
diepint = sys_read32(diepint_reg);
status = diepint & diepmsk;
sys_write32(status, diepint_reg);
LOG_DBG("ep 0x%02x interrupt status: 0x%x",
n | USB_EP_DIR_IN, status);
if (status & USB_DWC2_DIEPINT_XFERCOMPL) {
dwc2_handle_in_xfercompl(dev, n);
}
epint &= ~BIT(n);
}
/* Clear IEPINT interrupt */
sys_write32(USB_DWC2_GINTSTS_IEPINT, (mem_addr_t)&base->gintsts);
}
static inline void dwc2_handle_out_xfercompl(const struct device *dev,
const uint8_t ep_idx)
{
struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, ep_idx);
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
uint32_t bcnt;
struct net_buf *buf;
uint32_t doeptsiz;
const bool is_iso = dwc2_ep_is_iso(ep_cfg);
doeptsiz = sys_read32((mem_addr_t)&base->out_ep[ep_idx].doeptsiz);
buf = udc_buf_peek(ep_cfg);
if (!buf) {
LOG_ERR("No buffer for ep 0x%02x", ep_cfg->addr);
udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS);
return;
}
/* The original transfer size value is necessary here because controller
* decreases the value for every byte stored.
*/
bcnt = usb_dwc2_get_doeptsizn_xfersize(priv->rx_siz[ep_idx]) -
usb_dwc2_get_doeptsizn_xfersize(doeptsiz);
if (is_iso) {
uint32_t pkts;
bool valid;
pkts = usb_dwc2_get_doeptsizn_pktcnt(priv->rx_siz[ep_idx]) -
usb_dwc2_get_doeptsizn_pktcnt(doeptsiz);
switch (usb_dwc2_get_doeptsizn_rxdpid(doeptsiz)) {
case USB_DWC2_DOEPTSIZN_RXDPID_DATA0:
valid = (pkts == 1);
break;
case USB_DWC2_DOEPTSIZN_RXDPID_DATA1:
valid = (pkts == 2);
break;
case USB_DWC2_DOEPTSIZN_RXDPID_DATA2:
valid = (pkts == 3);
break;
case USB_DWC2_DOEPTSIZN_RXDPID_MDATA:
default:
valid = false;
break;
}
if (!valid) {
if (dwc2_in_completer_mode(dev)) {
/* RxFlvl added data to net buf, rollback */
net_buf_remove_mem(buf, bcnt);
}
/* Data is not valid, discard it */
bcnt = 0;
}
}
if (dwc2_in_buffer_dma_mode(dev) && bcnt) {
sys_cache_data_invd_range(net_buf_tail(buf), bcnt);
net_buf_add(buf, bcnt);
}
if (!is_iso && bcnt && (bcnt % udc_mps_ep_size(ep_cfg)) == 0 &&
net_buf_tailroom(buf)) {
dwc2_prep_rx(dev, buf, ep_cfg);
} else {
atomic_set_bit(&priv->xfer_finished, 16 + ep_idx);
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_EP_FINISHED));
}
}
static inline void dwc2_handle_oepint(const struct device *dev)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_dwc2_data *const priv = udc_get_private(dev);
uint32_t doepmsk;
uint32_t epint;
doepmsk = sys_read32((mem_addr_t)&base->doepmsk);
epint = usb_dwc2_get_daint_outepint(sys_read32((mem_addr_t)&base->daint));
while (epint) {
uint8_t n = find_lsb_set(epint) - 1;
mem_addr_t doepint_reg = (mem_addr_t)&base->out_ep[n].doepint;
uint32_t doepint;
uint32_t status;
/* Read and clear interrupt status */
doepint = sys_read32(doepint_reg);
status = doepint & doepmsk;
sys_write32(status, doepint_reg);
LOG_DBG("ep 0x%02x interrupt status: 0x%x", n, status);
/* StupPktRcvd is not enabled for interrupt, but must be checked
* when XferComp hits to determine if SETUP token was received.
*/
if (dwc2_in_buffer_dma_mode(dev) &&
(status & USB_DWC2_DOEPINT_XFERCOMPL) &&
(doepint & USB_DWC2_DOEPINT_STUPPKTRCVD)) {
uint32_t addr;
sys_write32(USB_DWC2_DOEPINT_STUPPKTRCVD, doepint_reg);
status &= ~USB_DWC2_DOEPINT_XFERCOMPL;
/* DMAAddr points past the memory location where the
* SETUP data was stored. Copy the received SETUP data
* to temporary location used also in Completer mode
* which allows common SETUP interrupt handling.
*/
addr = sys_read32((mem_addr_t)&base->out_ep[0].doepdma);
sys_cache_data_invd_range((void *)(addr - 8), 8);
memcpy(priv->setup, (void *)(addr - 8), sizeof(priv->setup));
}
if (status & USB_DWC2_DOEPINT_SETUP) {
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_SETUP));
}
if (status & USB_DWC2_DOEPINT_STSPHSERCVD) {
/* Driver doesn't need any special handling, but it is
* mandatory that the bit is cleared in Buffer DMA mode.
* If the bit is not cleared (i.e. when this interrupt
* bit is masked), then SETUP interrupts will cease
* after first control transfer with data stage from
* device to host.
*/
}
if (status & USB_DWC2_DOEPINT_XFERCOMPL) {
dwc2_handle_out_xfercompl(dev, n);
}
epint &= ~BIT(n);
}
/* Clear OEPINT interrupt */
sys_write32(USB_DWC2_GINTSTS_OEPINT, (mem_addr_t)&base->gintsts);
}
/* In DWC2 otg context incomplete isochronous IN transfer means that the host
* did not issue IN token to at least one isochronous endpoint and software has
* find on which endpoints the data is no longer valid and discard it.
*/
static void dwc2_handle_incompisoin(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
mem_addr_t gintsts_reg = (mem_addr_t)&base->gintsts;
const uint32_t mask =
USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_EPTYPE_MASK |
USB_DWC2_DEPCTL_USBACTEP;
const uint32_t val =
USB_DWC2_DEPCTL_EPENA |
usb_dwc2_set_depctl_eptype(USB_DWC2_DEPCTL_EPTYPE_ISO) |
USB_DWC2_DEPCTL_USBACTEP;
for (uint8_t i = 1U; i < priv->numdeveps; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i);
if (epdir == USB_DWC2_GHWCFG1_EPDIR_IN ||
epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
mem_addr_t diepctl_reg = dwc2_get_dxepctl_reg(dev, i | USB_EP_DIR_IN);
uint32_t diepctl;
diepctl = sys_read32(diepctl_reg);
/* Check if endpoint didn't receive ISO OUT data */
if ((diepctl & mask) == val) {
struct udc_ep_config *cfg;
struct net_buf *buf;
cfg = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN);
__ASSERT_NO_MSG(cfg && cfg->stat.enabled &&
dwc2_ep_is_iso(cfg));
udc_dwc2_ep_disable(dev, cfg, false);
buf = udc_buf_get(cfg);
if (buf) {
/* Data is no longer relevant */
udc_submit_ep_event(dev, buf, 0);
/* Try to queue next packet before SOF */
dwc2_handle_xfer_next(dev, cfg);
}
}
}
}
sys_write32(USB_DWC2_GINTSTS_INCOMPISOIN, gintsts_reg);
}
/* In DWC2 otg context incomplete isochronous OUT transfer means that the host
* did not issue OUT token to at least one isochronous endpoint and software has
* to find on which endpoint it didn't receive any data and let the stack know.
*/
static void dwc2_handle_incompisoout(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
mem_addr_t gintsts_reg = (mem_addr_t)&base->gintsts;
const uint32_t mask =
USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_EPTYPE_MASK |
USB_DWC2_DEPCTL_DPID | USB_DWC2_DEPCTL_USBACTEP;
const uint32_t val =
USB_DWC2_DEPCTL_EPENA |
usb_dwc2_set_depctl_eptype(USB_DWC2_DEPCTL_EPTYPE_ISO) |
((priv->sof_num & 1) ? USB_DWC2_DEPCTL_DPID : 0) |
USB_DWC2_DEPCTL_USBACTEP;
for (uint8_t i = 1U; i < priv->numdeveps; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i);
if (epdir == USB_DWC2_GHWCFG1_EPDIR_OUT ||
epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
mem_addr_t doepctl_reg = dwc2_get_dxepctl_reg(dev, i);
uint32_t doepctl;
doepctl = sys_read32(doepctl_reg);
/* Check if endpoint didn't receive ISO OUT data */
if ((doepctl & mask) == val) {
struct udc_ep_config *cfg;
struct net_buf *buf;
cfg = udc_get_ep_cfg(dev, i);
__ASSERT_NO_MSG(cfg && cfg->stat.enabled &&
dwc2_ep_is_iso(cfg));
udc_dwc2_ep_disable(dev, cfg, false);
buf = udc_buf_get(cfg);
if (buf) {
udc_submit_ep_event(dev, buf, 0);
}
}
}
}
sys_write32(USB_DWC2_GINTSTS_INCOMPISOOUT, gintsts_reg);
}
static void udc_dwc2_isr_handler(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
mem_addr_t gintsts_reg = (mem_addr_t)&base->gintsts;
uint32_t int_status;
uint32_t gintmsk;
if (priv->hibernated) {
uint32_t gpwrdn = sys_read32((mem_addr_t)&base->gpwrdn);
bool reset, resume = false;
/* Clear interrupts */
sys_write32(gpwrdn, (mem_addr_t)&base->gpwrdn);
if (gpwrdn & USB_DWC2_GPWRDN_LNSTSCHNG) {
resume = usb_dwc2_get_gpwrdn_linestate(gpwrdn) ==
USB_DWC2_GPWRDN_LINESTATE_DM1DP0;
}
reset = gpwrdn & USB_DWC2_GPWRDN_RESETDETECTED;
if (resume) {
k_event_post(&priv->drv_evt,
BIT(DWC2_DRV_EVT_HIBERNATION_EXIT_HOST_RESUME));
}
if (reset) {
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_HIBERNATION_EXIT_BUS_RESET));
}
(void)dwc2_quirk_irq_clear(dev);
return;
}
gintmsk = sys_read32((mem_addr_t)&base->gintmsk);
/* Read and handle interrupt status register */
while ((int_status = sys_read32(gintsts_reg) & gintmsk)) {
LOG_DBG("GINTSTS 0x%x", int_status);
if (int_status & USB_DWC2_GINTSTS_SOF) {
uint32_t dsts;
/* Clear USB SOF interrupt. */
sys_write32(USB_DWC2_GINTSTS_SOF, gintsts_reg);
dsts = sys_read32((mem_addr_t)&base->dsts);
priv->sof_num = usb_dwc2_get_dsts_soffn(dsts);
udc_submit_event(dev, UDC_EVT_SOF, 0);
}
if (int_status & USB_DWC2_GINTSTS_USBRST) {
/* Clear and handle USB Reset interrupt. */
sys_write32(USB_DWC2_GINTSTS_USBRST, gintsts_reg);
dwc2_on_bus_reset(dev);
LOG_DBG("USB Reset interrupt");
cancel_hibernation_request(priv);
}
if (int_status & USB_DWC2_GINTSTS_ENUMDONE) {
/* Clear and handle Enumeration Done interrupt. */
sys_write32(USB_DWC2_GINTSTS_ENUMDONE, gintsts_reg);
dwc2_handle_enumdone(dev);
udc_submit_event(dev, UDC_EVT_RESET, 0);
}
if (int_status & USB_DWC2_GINTSTS_WKUPINT) {
/* Clear Resume/Remote Wakeup Detected interrupt. */
sys_write32(USB_DWC2_GINTSTS_WKUPINT, gintsts_reg);
udc_set_suspended(dev, false);
udc_submit_event(dev, UDC_EVT_RESUME, 0);
cancel_hibernation_request(priv);
}
if (int_status & USB_DWC2_GINTSTS_IEPINT) {
/* Handle IN Endpoints interrupt */
dwc2_handle_iepint(dev);
}
if (int_status & USB_DWC2_GINTSTS_RXFLVL) {
/* Handle RxFIFO Non-Empty interrupt */
dwc2_handle_rxflvl(dev);
}
if (int_status & USB_DWC2_GINTSTS_OEPINT) {
/* Handle OUT Endpoints interrupt */
dwc2_handle_oepint(dev);
}
if (int_status & USB_DWC2_GINTSTS_INCOMPISOIN) {
dwc2_handle_incompisoin(dev);
}
if (int_status & USB_DWC2_GINTSTS_INCOMPISOOUT) {
dwc2_handle_incompisoout(dev);
}
if (int_status & USB_DWC2_GINTSTS_USBSUSP) {
/* Clear USB Suspend interrupt. */
sys_write32(USB_DWC2_GINTSTS_USBSUSP, gintsts_reg);
/* Notify the stack */
udc_set_suspended(dev, true);
udc_submit_event(dev, UDC_EVT_SUSPEND, 0);
request_hibernation(priv);
}
}
(void)dwc2_quirk_irq_clear(dev);
}
static void dwc2_handle_hibernation_exit(const struct device *dev,
bool rwup, bool bus_reset)
{
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_dwc2_data *const priv = udc_get_private(dev);
dwc2_exit_hibernation(dev, rwup, bus_reset);
dwc2_restore_device_registers(dev, rwup);
priv->hibernated = 0;
if (!rwup) {
LOG_DBG("Hibernation exit complete");
}
/* Let stack know we are no longer suspended */
udc_set_suspended(dev, false);
udc_submit_event(dev, UDC_EVT_RESUME, 0);
if (rwup) {
/* Resume has been driven for at least 1 ms now, do 1 ms more to
* have sufficient margin.
*/
k_msleep(1);
sys_clear_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_RMTWKUPSIG);
}
if (rwup) {
LOG_DBG("Hibernation exit on Remote Wakeup complete");
}
if (bus_reset) {
/* Clear all pending transfers */
atomic_clear(&priv->xfer_new);
atomic_clear(&priv->xfer_finished);
dwc2_on_bus_reset(dev);
} else {
/* Resume any pending transfer handling */
if (atomic_get(&priv->xfer_new)) {
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_XFER));
}
if (atomic_get(&priv->xfer_finished)) {
k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_EP_FINISHED));
}
}
while (priv->pending_tx_flush) {
unsigned int fifo = find_lsb_set(priv->pending_tx_flush) - 1;
priv->pending_tx_flush &= ~BIT(fifo);
dwc2_flush_tx_fifo(dev, fifo);
}
}
static uint8_t pull_next_ep_from_bitmap(uint32_t *bitmap)
{
unsigned int bit;
__ASSERT_NO_MSG(bitmap && *bitmap);
bit = find_lsb_set(*bitmap) - 1;
*bitmap &= ~BIT(bit);
if (bit >= 16) {
return USB_EP_DIR_OUT | (bit - 16);
} else {
return USB_EP_DIR_IN | bit;
}
}
static ALWAYS_INLINE void dwc2_thread_handler(void *const arg)
{
const struct device *dev = (const struct device *)arg;
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
struct udc_dwc2_data *const priv = udc_get_private(dev);
const struct udc_dwc2_config *const config = dev->config;
struct udc_ep_config *ep_cfg;
const uint32_t hibernation_exit_events = (BIT(DWC2_DRV_EVT_HIBERNATION_EXIT_BUS_RESET) |
BIT(DWC2_DRV_EVT_HIBERNATION_EXIT_HOST_RESUME));
uint32_t prev;
uint32_t evt;
uint32_t eps;
uint8_t ep;
/* This is the bottom-half of the ISR handler and the place where
* a new transfer can be fed.
*/
evt = k_event_wait(&priv->drv_evt, UINT32_MAX, false, K_FOREVER);
udc_lock_internal(dev, K_FOREVER);
if (evt & BIT(DWC2_DRV_EVT_XFER)) {
k_event_clear(&priv->drv_evt, BIT(DWC2_DRV_EVT_XFER));
if (!priv->hibernated) {
LOG_DBG("New transfer(s) in the queue");
eps = atomic_clear(&priv->xfer_new);
} else {
/* Events will be handled after hibernation exit */
eps = 0;
}
while (eps) {
ep = pull_next_ep_from_bitmap(&eps);
ep_cfg = udc_get_ep_cfg(dev, ep);
if (!udc_ep_is_busy(ep_cfg)) {
dwc2_handle_xfer_next(dev, ep_cfg);
} else {
LOG_DBG("ep 0x%02x busy", ep_cfg->addr);
}
}
}
if (evt & BIT(DWC2_DRV_EVT_EP_FINISHED)) {
k_event_clear(&priv->drv_evt, BIT(DWC2_DRV_EVT_EP_FINISHED));
if (!priv->hibernated) {
eps = atomic_clear(&priv->xfer_finished);
} else {
/* Events will be handled after hibernation exit */
eps = 0;
}
while (eps) {
ep = pull_next_ep_from_bitmap(&eps);
ep_cfg = udc_get_ep_cfg(dev, ep);
if (USB_EP_DIR_IS_IN(ep)) {
LOG_DBG("DIN event ep 0x%02x", ep);
dwc2_handle_evt_din(dev, ep_cfg);
} else {
LOG_DBG("DOUT event ep 0x%02x", ep_cfg->addr);
dwc2_handle_evt_dout(dev, ep_cfg);
}
if (!udc_ep_is_busy(ep_cfg)) {
dwc2_handle_xfer_next(dev, ep_cfg);
} else {
LOG_DBG("ep 0x%02x busy", ep_cfg->addr);
}
}
}
if (evt & BIT(DWC2_DRV_EVT_SETUP)) {
k_event_clear(&priv->drv_evt, BIT(DWC2_DRV_EVT_SETUP));
LOG_DBG("SETUP event");
dwc2_handle_evt_setup(dev);
}
if (evt & BIT(DWC2_DRV_EVT_REMOTE_WAKEUP)) {
k_event_clear(&priv->drv_evt, BIT(DWC2_DRV_EVT_REMOTE_WAKEUP) |
BIT(DWC2_DRV_EVT_ENTER_HIBERNATION));
if (priv->hibernated) {
config->irq_disable_func(dev);
dwc2_handle_hibernation_exit(dev, true, false);
config->irq_enable_func(dev);
} else {
sys_set_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_RMTWKUPSIG);
udc_set_suspended(dev, false);
udc_submit_event(dev, UDC_EVT_RESUME, 0);
/* Drive resume for 2 ms to have sufficient margin */
k_msleep(2);
sys_clear_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_RMTWKUPSIG);
}
}
if (evt & BIT(DWC2_DRV_EVT_ENTER_HIBERNATION)) {
config->irq_disable_func(dev);
prev = k_event_clear(&priv->drv_evt, BIT(DWC2_DRV_EVT_ENTER_HIBERNATION));
/* Only enter hibernation if IRQ did not cancel the request */
if (prev & BIT(DWC2_DRV_EVT_ENTER_HIBERNATION)) {
dwc2_enter_hibernation(dev);
}
config->irq_enable_func(dev);
}
if (evt & hibernation_exit_events) {
bool bus_reset;
LOG_DBG("Hibernation exit event");
config->irq_disable_func(dev);
prev = k_event_clear(&priv->drv_evt, hibernation_exit_events);
bus_reset = prev & BIT(DWC2_DRV_EVT_HIBERNATION_EXIT_BUS_RESET);
if (priv->hibernated) {
dwc2_handle_hibernation_exit(dev, false, bus_reset);
}
config->irq_enable_func(dev);
}
if (evt & BIT(DWC2_DRV_EVT_ENUM_DONE)) {
k_event_clear(&priv->drv_evt, BIT(DWC2_DRV_EVT_ENUM_DONE));
dwc2_ensure_setup_ready(dev);
}
udc_unlock_internal(dev);
}
static const struct udc_api udc_dwc2_api = {
.lock = udc_dwc2_lock,
.unlock = udc_dwc2_unlock,
.device_speed = udc_dwc2_device_speed,
.init = udc_dwc2_init,
.enable = udc_dwc2_enable,
.disable = udc_dwc2_disable,
.shutdown = udc_dwc2_shutdown,
.set_address = udc_dwc2_set_address,
.test_mode = udc_dwc2_test_mode,
.host_wakeup = udc_dwc2_host_wakeup,
.ep_enable = udc_dwc2_ep_activate,
.ep_disable = udc_dwc2_ep_deactivate,
.ep_set_halt = udc_dwc2_ep_set_halt,
.ep_clear_halt = udc_dwc2_ep_clear_halt,
.ep_enqueue = udc_dwc2_ep_enqueue,
.ep_dequeue = udc_dwc2_ep_dequeue,
};
#define DT_DRV_COMPAT snps_dwc2
#define UDC_DWC2_VENDOR_QUIRK_GET(n) \
COND_CODE_1(DT_NODE_VENDOR_HAS_IDX(DT_DRV_INST(n), 1), \
(&dwc2_vendor_quirks_##n), \
(NULL))
#define UDC_DWC2_DT_INST_REG_ADDR(n) \
COND_CODE_1(DT_NUM_REGS(DT_DRV_INST(n)), (DT_INST_REG_ADDR(n)), \
(DT_INST_REG_ADDR_BY_NAME(n, core)))
#define UDC_DWC2_PINCTRL_DT_INST_DEFINE(n) \
COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \
(PINCTRL_DT_INST_DEFINE(n)), ())
#define UDC_DWC2_PINCTRL_DT_INST_DEV_CONFIG_GET(n) \
COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \
((void *)PINCTRL_DT_INST_DEV_CONFIG_GET(n)), (NULL))
#define UDC_DWC2_IRQ_FLAGS_TYPE0(n) 0
#define UDC_DWC2_IRQ_FLAGS_TYPE1(n) DT_INST_IRQ(n, type)
#define DW_IRQ_FLAGS(n) \
_CONCAT(UDC_DWC2_IRQ_FLAGS_TYPE, DT_INST_IRQ_HAS_CELL(n, type))(n)
/*
* A UDC driver should always be implemented as a multi-instance
* driver, even if your platform does not require it.
*/
#define UDC_DWC2_DEVICE_DEFINE(n) \
UDC_DWC2_PINCTRL_DT_INST_DEFINE(n); \
\
K_THREAD_STACK_DEFINE(udc_dwc2_stack_##n, CONFIG_UDC_DWC2_STACK_SIZE); \
\
static void udc_dwc2_thread_##n(void *dev, void *arg1, void *arg2) \
{ \
while (true) { \
dwc2_thread_handler(dev); \
} \
} \
\
static void udc_dwc2_make_thread_##n(const struct device *dev) \
{ \
struct udc_dwc2_data *priv = udc_get_private(dev); \
\
k_thread_create(&priv->thread_data, \
udc_dwc2_stack_##n, \
K_THREAD_STACK_SIZEOF(udc_dwc2_stack_##n), \
udc_dwc2_thread_##n, \
(void *)dev, NULL, NULL, \
K_PRIO_COOP(CONFIG_UDC_DWC2_THREAD_PRIORITY), \
K_ESSENTIAL, \
K_NO_WAIT); \
k_thread_name_set(&priv->thread_data, dev->name); \
} \
\
static void udc_dwc2_irq_enable_func_##n(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQN(n), \
DT_INST_IRQ(n, priority), \
udc_dwc2_isr_handler, \
DEVICE_DT_INST_GET(n), \
DW_IRQ_FLAGS(n)); \
\
irq_enable(DT_INST_IRQN(n)); \
} \
\
static void udc_dwc2_irq_disable_func_##n(const struct device *dev) \
{ \
irq_disable(DT_INST_IRQN(n)); \
} \
\
static struct udc_ep_config ep_cfg_out[DT_INST_PROP(n, num_out_eps)]; \
static struct udc_ep_config ep_cfg_in[DT_INST_PROP(n, num_in_eps)]; \
\
static const struct udc_dwc2_config udc_dwc2_config_##n = { \
.num_out_eps = DT_INST_PROP(n, num_out_eps), \
.num_in_eps = DT_INST_PROP(n, num_in_eps), \
.ep_cfg_in = ep_cfg_in, \
.ep_cfg_out = ep_cfg_out, \
.make_thread = udc_dwc2_make_thread_##n, \
.base = (struct usb_dwc2_reg *)UDC_DWC2_DT_INST_REG_ADDR(n), \
.pcfg = UDC_DWC2_PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
.irq_enable_func = udc_dwc2_irq_enable_func_##n, \
.irq_disable_func = udc_dwc2_irq_disable_func_##n, \
.quirks = UDC_DWC2_VENDOR_QUIRK_GET(n), \
.ghwcfg1 = DT_INST_PROP(n, ghwcfg1), \
.ghwcfg2 = DT_INST_PROP(n, ghwcfg2), \
.ghwcfg4 = DT_INST_PROP(n, ghwcfg4), \
}; \
\
static struct udc_dwc2_data udc_priv_##n = { \
}; \
\
static struct udc_data udc_data_##n = { \
.mutex = Z_MUTEX_INITIALIZER(udc_data_##n.mutex), \
.priv = &udc_priv_##n, \
}; \
\
DEVICE_DT_INST_DEFINE(n, dwc2_driver_preinit, NULL, \
&udc_data_##n, &udc_dwc2_config_##n, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&udc_dwc2_api);
DT_INST_FOREACH_STATUS_OKAY(UDC_DWC2_DEVICE_DEFINE)