2023-09-07 13:40:13 +02:00
|
|
|
/*
|
|
|
|
* 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/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);
|
2024-05-14 14:10:30 +02:00
|
|
|
#include "udc_dwc2_vendor_quirks.h"
|
2023-09-07 13:40:13 +02:00
|
|
|
|
|
|
|
enum dwc2_drv_event_type {
|
|
|
|
/* Trigger next transfer, must not be used for control OUT */
|
|
|
|
DWC2_DRV_EVT_XFER,
|
|
|
|
/* Setup packet received */
|
|
|
|
DWC2_DRV_EVT_SETUP,
|
|
|
|
/* OUT transaction for specific endpoint is finished */
|
|
|
|
DWC2_DRV_EVT_DOUT,
|
|
|
|
/* IN transaction for specific endpoint is finished */
|
|
|
|
DWC2_DRV_EVT_DIN,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct dwc2_drv_event {
|
|
|
|
const struct device *dev;
|
|
|
|
enum dwc2_drv_event_type type;
|
|
|
|
uint32_t bcnt;
|
|
|
|
uint8_t ep;
|
|
|
|
};
|
|
|
|
|
|
|
|
K_MSGQ_DEFINE(drv_msgq, sizeof(struct dwc2_drv_event),
|
|
|
|
CONFIG_UDC_DWC2_MAX_QMESSAGES, sizeof(void *));
|
|
|
|
|
|
|
|
|
|
|
|
/* 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_DEFAULT (15U + 512U/4U)
|
|
|
|
|
|
|
|
/* TX FIFO0 depth in 32-bit words (used by control IN endpoint) */
|
|
|
|
#define UDC_DWC2_FIFO0_DEPTH 16U
|
|
|
|
|
|
|
|
/* Get Data FIFO access register */
|
|
|
|
#define UDC_DWC2_EP_FIFO(base, idx) ((mem_addr_t)base + 0x1000 * (idx + 1))
|
|
|
|
|
|
|
|
/* Driver private data per instance */
|
|
|
|
struct udc_dwc2_data {
|
|
|
|
struct k_thread thread_data;
|
|
|
|
uint32_t ghwcfg1;
|
|
|
|
uint32_t enumspd;
|
|
|
|
uint32_t txf_set;
|
|
|
|
uint32_t grxfsiz;
|
|
|
|
uint32_t dfifodepth;
|
|
|
|
uint32_t max_xfersize;
|
|
|
|
uint32_t max_pktcnt;
|
|
|
|
uint32_t tx_len[16];
|
|
|
|
unsigned int dynfifosizing : 1;
|
2024-05-16 11:43:42 +02:00
|
|
|
/* Number of endpoints including control endpoint */
|
2023-09-07 13:40:13 +02:00
|
|
|
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];
|
|
|
|
};
|
|
|
|
|
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 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 idx)
|
|
|
|
{
|
|
|
|
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
|
|
|
|
mem_addr_t grstctl_reg = (mem_addr_t)&base->grstctl;
|
|
|
|
/* TODO: use dwc2_get_dxepctl_reg() */
|
|
|
|
mem_addr_t diepctl_reg = (mem_addr_t)&base->in_ep[idx].diepctl;
|
|
|
|
uint32_t grstctl;
|
|
|
|
uint32_t fnum;
|
|
|
|
|
|
|
|
fnum = usb_dwc2_get_depctl_txfnum(sys_read32(diepctl_reg));
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
2024-05-10 14:47:01 +02:00
|
|
|
mem_addr_t diepint_reg = (mem_addr_t)&base->in_ep[ep_idx].diepint;
|
2023-09-07 13:40:13 +02:00
|
|
|
|
|
|
|
uint32_t max_xfersize, max_pktcnt, pktcnt, spcavail;
|
|
|
|
const size_t d = sizeof(uint32_t);
|
|
|
|
unsigned int key;
|
|
|
|
uint32_t len;
|
|
|
|
|
|
|
|
spcavail = dwc2_ftx_avail(dev, ep_idx);
|
|
|
|
/* Round down to multiple of endpoint MPS */
|
|
|
|
spcavail -= spcavail % cfg->mps;
|
|
|
|
/*
|
|
|
|
* Here, the available space should be equal to the FIFO space
|
|
|
|
* assigned/configured for that endpoint because we do not schedule another
|
|
|
|
* transfer until the previous one has not finished. For simplicity,
|
|
|
|
* we only check that the available space is not less than the endpoint
|
|
|
|
* MPS.
|
|
|
|
*/
|
|
|
|
if (spcavail < cfg->mps) {
|
|
|
|
LOG_ERR("ep 0x%02x FIFO space is too low, %u (%u)",
|
|
|
|
cfg->addr, spcavail, dwc2_ftx_avail(dev, ep_idx));
|
|
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
|
|
|
|
len = MIN(buf->len, spcavail);
|
|
|
|
|
|
|
|
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, 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, cfg->mps);
|
|
|
|
if (pktcnt > max_pktcnt) {
|
|
|
|
pktcnt = max_pktcnt;
|
|
|
|
len = pktcnt * cfg->mps;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* ZLP */
|
|
|
|
pktcnt = 1U;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG_DBG("Prepare ep 0x%02x xfer len %u pktcnt %u spcavail %u",
|
|
|
|
cfg->addr, len, pktcnt, spcavail);
|
|
|
|
priv->tx_len[ep_idx] = len;
|
|
|
|
|
|
|
|
/* Lock and write to endpoint FIFO */
|
|
|
|
key = irq_lock();
|
|
|
|
|
|
|
|
/* Set number of packets and transfer size */
|
|
|
|
sys_write32((pktcnt << USB_DWC2_DEPTSIZN_PKTCNT_POS) | len, dieptsiz_reg);
|
|
|
|
|
|
|
|
/* Clear NAK and set endpoint enable */
|
|
|
|
sys_set_bits(diepctl_reg, USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_CNAK);
|
2024-05-10 14:47:01 +02:00
|
|
|
/* Clear IN Endpoint NAK Effective interrupt in case it was set */
|
|
|
|
sys_write32(USB_DWC2_DIEPINT_INEPNAKEFF, diepint_reg);
|
2023-09-07 13:40:13 +02:00
|
|
|
|
|
|
|
/* FIFO access is always in 32-bit words */
|
|
|
|
|
|
|
|
for (uint32_t i = 0UL; i < len; i += d) {
|
|
|
|
uint32_t val = buf->data[i];
|
|
|
|
|
|
|
|
if (i + 1 < len) {
|
|
|
|
val |= ((uint32_t)buf->data[i + 1UL]) << 8;
|
|
|
|
}
|
|
|
|
if (i + 2 < len) {
|
|
|
|
val |= ((uint32_t)buf->data[i + 2UL]) << 16;
|
|
|
|
}
|
|
|
|
if (i + 3 < len) {
|
|
|
|
val |= ((uint32_t)buf->data[i + 3UL]) << 24;
|
|
|
|
}
|
|
|
|
|
|
|
|
sys_write32(val, UDC_DWC2_EP_FIFO(base, ep_idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2024-05-16 10:39:15 +02:00
|
|
|
size_t len = buf ? MIN(size, net_buf_tailroom(buf)) : 0;
|
2023-09-07 13:40:13 +02:00
|
|
|
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 udc_ep_config *const cfg, const bool ncnak)
|
|
|
|
{
|
|
|
|
struct usb_dwc2_reg *const base = dwc2_get_base(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 doeptsiz;
|
|
|
|
|
|
|
|
doeptsiz = (1 << USB_DWC2_DOEPTSIZ0_PKTCNT_POS) | cfg->mps;
|
|
|
|
if (cfg->addr == USB_CONTROL_EP_OUT) {
|
2024-05-16 10:39:15 +02:00
|
|
|
doeptsiz |= (3 << USB_DWC2_DOEPTSIZ0_SUPCNT_POS);
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sys_write32(doeptsiz, doeptsiz_reg);
|
|
|
|
|
|
|
|
if (ncnak) {
|
|
|
|
sys_set_bits(doepctl_reg, USB_DWC2_DEPCTL_EPENA);
|
|
|
|
} else {
|
|
|
|
sys_set_bits(doepctl_reg, USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_CNAK);
|
|
|
|
}
|
|
|
|
|
|
|
|
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(dev, cfg->addr);
|
|
|
|
if (buf == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (USB_EP_DIR_IS_OUT(cfg->addr)) {
|
|
|
|
dwc2_prep_rx(dev, cfg, 0);
|
|
|
|
} else {
|
|
|
|
if (dwc2_tx_fifo_write(dev, cfg, buf)) {
|
|
|
|
LOG_ERR("Failed to start write to TX FIFO, ep 0x%02x",
|
|
|
|
cfg->addr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
udc_ep_set_busy(dev, cfg->addr, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dwc2_ctrl_feed_dout(const struct device *dev, const size_t length)
|
|
|
|
{
|
|
|
|
struct net_buf *buf;
|
|
|
|
|
|
|
|
buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, length);
|
|
|
|
if (buf == NULL) {
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
udc_buf_put(udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT), buf);
|
|
|
|
dwc2_prep_rx(dev, udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT), 0);
|
|
|
|
LOG_DBG("feed buf %p", buf);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dwc2_handle_evt_setup(const struct device *dev)
|
|
|
|
{
|
|
|
|
struct udc_dwc2_data *const priv = udc_get_private(dev);
|
|
|
|
struct net_buf *buf;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
buf = udc_buf_get(dev, USB_CONTROL_EP_OUT);
|
|
|
|
if (buf == NULL) {
|
|
|
|
LOG_ERR("No buffer queued 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 = dwc2_ctrl_feed_dout(dev, 8);
|
|
|
|
if (err == -ENOMEM) {
|
|
|
|
err = udc_submit_ep_event(dev, buf, err);
|
|
|
|
}
|
|
|
|
|
|
|
|
err = udc_ctrl_submit_s_in_status(dev);
|
|
|
|
} else {
|
|
|
|
LOG_DBG("s:%p|feed >setup", buf);
|
|
|
|
|
|
|
|
err = dwc2_ctrl_feed_dout(dev, 8);
|
|
|
|
if (err == -ENOMEM) {
|
|
|
|
err = udc_submit_ep_event(dev, buf, err);
|
|
|
|
}
|
|
|
|
|
|
|
|
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 net_buf *buf;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
buf = udc_buf_get(dev, cfg->addr);
|
|
|
|
if (buf == NULL) {
|
|
|
|
LOG_ERR("No buffer queued for control ep");
|
|
|
|
return -ENODATA;
|
|
|
|
}
|
|
|
|
|
|
|
|
udc_ep_set_busy(dev, cfg->addr, 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);
|
|
|
|
|
|
|
|
/* Feed a buffer for the next setup packet */
|
|
|
|
err = dwc2_ctrl_feed_dout(dev, 8);
|
|
|
|
if (err == -ENOMEM) {
|
|
|
|
err = udc_submit_ep_event(dev, buf, err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Status stage finished, notify upper layer */
|
|
|
|
udc_ctrl_submit_status(dev, buf);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* For all other cases we feed with a buffer
|
|
|
|
* large enough for setup packet.
|
|
|
|
*/
|
|
|
|
LOG_DBG("dout:%p| data, feed >s", buf);
|
|
|
|
|
|
|
|
err = dwc2_ctrl_feed_dout(dev, 8);
|
|
|
|
if (err == -ENOMEM) {
|
|
|
|
err = udc_submit_ep_event(dev, buf, err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
} 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(dev, cfg->addr);
|
|
|
|
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(dev, cfg->addr);
|
|
|
|
udc_ep_set_busy(dev, cfg->addr, 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)) {
|
|
|
|
/*
|
|
|
|
* IN transfer finished, release buffer,
|
|
|
|
* control OUT buffer should be already fed.
|
|
|
|
*/
|
|
|
|
net_buf_unref(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return udc_submit_ep_event(dev, buf, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ALWAYS_INLINE void dwc2_thread_handler(void *const arg)
|
|
|
|
{
|
|
|
|
const struct device *dev = (const struct device *)arg;
|
|
|
|
struct udc_ep_config *ep_cfg;
|
|
|
|
struct dwc2_drv_event evt;
|
|
|
|
|
|
|
|
/* This is the bottom-half of the ISR handler and the place where
|
|
|
|
* a new transfer can be fed.
|
|
|
|
*/
|
|
|
|
k_msgq_get(&drv_msgq, &evt, K_FOREVER);
|
|
|
|
ep_cfg = udc_get_ep_cfg(dev, evt.ep);
|
|
|
|
|
|
|
|
switch (evt.type) {
|
|
|
|
case DWC2_DRV_EVT_XFER:
|
|
|
|
LOG_DBG("New transfer in the queue");
|
|
|
|
break;
|
|
|
|
case DWC2_DRV_EVT_SETUP:
|
|
|
|
LOG_DBG("SETUP event");
|
|
|
|
dwc2_handle_evt_setup(dev);
|
|
|
|
break;
|
|
|
|
case DWC2_DRV_EVT_DOUT:
|
|
|
|
LOG_DBG("DOUT event ep 0x%02x", ep_cfg->addr);
|
|
|
|
dwc2_handle_evt_dout(dev, ep_cfg);
|
|
|
|
break;
|
|
|
|
case DWC2_DRV_EVT_DIN:
|
|
|
|
LOG_DBG("DIN event");
|
|
|
|
dwc2_handle_evt_din(dev, ep_cfg);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ep_cfg->addr != USB_CONTROL_EP_OUT && !udc_ep_is_busy(dev, ep_cfg->addr)) {
|
|
|
|
dwc2_handle_xfer_next(dev, ep_cfg);
|
|
|
|
} else {
|
|
|
|
LOG_DBG("ep 0x%02x busy", ep_cfg->addr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-16 10:39:15 +02:00
|
|
|
sys_write32(USB_DWC2_DOEPINT_SETUP, (mem_addr_t)&base->doepmsk);
|
2023-09-07 13:40:13 +02:00
|
|
|
sys_set_bits((mem_addr_t)&base->gintmsk, USB_DWC2_GINTSTS_RXFLVL);
|
|
|
|
sys_set_bits((mem_addr_t)&base->diepmsk, USB_DWC2_DIEPINT_XFERCOMPL);
|
|
|
|
|
|
|
|
/* Clear device address during reset. */
|
|
|
|
sys_clear_bits((mem_addr_t)&base->dcfg, USB_DWC2_DCFG_DEVADDR_MASK);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-05-16 10:39:15 +02:00
|
|
|
static inline int dwc2_read_fifo_setup(const struct device *dev, uint8_t ep,
|
|
|
|
const size_t size)
|
2023-09-07 13:40:13 +02:00
|
|
|
{
|
|
|
|
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
|
|
|
|
struct udc_dwc2_data *const priv = udc_get_private(dev);
|
2024-05-16 10:39:15 +02:00
|
|
|
size_t offset;
|
2023-09-07 13:40:13 +02:00
|
|
|
|
|
|
|
/* FIFO access is always in 32-bit words */
|
|
|
|
|
2024-05-16 10:39:15 +02:00
|
|
|
if (size != 8) {
|
|
|
|
LOG_ERR("%d bytes SETUP", size);
|
|
|
|
}
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2024-05-16 10:39:15 +02:00
|
|
|
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;
|
|
|
|
}
|
2023-09-07 13:40:13 +02:00
|
|
|
|
|
|
|
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 dwc2_drv_event evt;
|
|
|
|
struct net_buf *buf;
|
|
|
|
uint32_t grxstsp;
|
|
|
|
uint32_t pktsts;
|
|
|
|
|
|
|
|
grxstsp = sys_read32((mem_addr_t)&base->grxstsp);
|
|
|
|
evt.ep = usb_dwc2_get_grxstsp_epnum(grxstsp);
|
|
|
|
evt.bcnt = usb_dwc2_get_grxstsp_bcnt(grxstsp);
|
|
|
|
pktsts = usb_dwc2_get_grxstsp_pktsts(grxstsp);
|
|
|
|
|
|
|
|
LOG_DBG("ep 0x%02x: pktsts %u, bcnt %u", evt.ep, pktsts, evt.bcnt);
|
|
|
|
|
|
|
|
switch (pktsts) {
|
|
|
|
case USB_DWC2_GRXSTSR_PKTSTS_SETUP:
|
2024-05-16 10:39:15 +02:00
|
|
|
dwc2_read_fifo_setup(dev, evt.ep, evt.bcnt);
|
2023-09-07 13:40:13 +02:00
|
|
|
break;
|
|
|
|
case USB_DWC2_GRXSTSR_PKTSTS_OUT_DATA:
|
|
|
|
evt.type = DWC2_DRV_EVT_DOUT;
|
|
|
|
ep_cfg = udc_get_ep_cfg(dev, evt.ep);
|
|
|
|
|
|
|
|
buf = udc_buf_peek(dev, ep_cfg->addr);
|
2024-05-16 10:39:15 +02:00
|
|
|
|
|
|
|
/* RxFIFO data must be retrieved even when buf is NULL */
|
|
|
|
dwc2_read_fifo(dev, evt.ep, buf, evt.bcnt);
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
if (buf == NULL) {
|
|
|
|
LOG_ERR("No buffer for ep 0x%02x", ep_cfg->addr);
|
|
|
|
udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (net_buf_tailroom(buf) && evt.bcnt == ep_cfg->mps) {
|
|
|
|
dwc2_prep_rx(dev, ep_cfg, 0);
|
|
|
|
} else {
|
|
|
|
k_msgq_put(&drv_msgq, &evt, K_NO_WAIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
case USB_DWC2_GRXSTSR_PKTSTS_OUT_DATA_DONE:
|
|
|
|
LOG_DBG("RX pktsts DONE");
|
|
|
|
break;
|
2024-05-16 10:39:15 +02:00
|
|
|
case USB_DWC2_GRXSTSR_PKTSTS_SETUP_DONE:
|
|
|
|
LOG_DBG("SETUP pktsts DONE");
|
2024-05-10 14:47:01 +02:00
|
|
|
case USB_DWC2_GRXSTSR_PKTSTS_GLOBAL_OUT_NAK:
|
|
|
|
LOG_DBG("Global OUT NAK");
|
2024-05-16 10:39:15 +02:00
|
|
|
break;
|
2023-09-07 13:40:13 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void dwc2_handle_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 dwc2_drv_event evt;
|
|
|
|
struct net_buf *buf;
|
|
|
|
|
|
|
|
ep_cfg = udc_get_ep_cfg(dev, ep_idx | USB_EP_DIR_IN);
|
|
|
|
buf = udc_buf_peek(dev, ep_cfg->addr);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
evt.dev = dev;
|
|
|
|
evt.ep = ep_cfg->addr;
|
|
|
|
evt.type = DWC2_DRV_EVT_DIN;
|
|
|
|
k_msgq_put(&drv_msgq, &evt, K_NO_WAIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void dwc2_handle_iepint(const struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
|
|
|
|
const uint8_t n_max = 16;
|
|
|
|
uint32_t diepmsk;
|
|
|
|
uint32_t daint;
|
|
|
|
|
|
|
|
diepmsk = sys_read32((mem_addr_t)&base->diepmsk);
|
|
|
|
daint = sys_read32((mem_addr_t)&base->daint);
|
|
|
|
|
|
|
|
for (uint8_t n = 0U; n < n_max; n++) {
|
|
|
|
mem_addr_t diepint_reg = (mem_addr_t)&base->in_ep[n].diepint;
|
|
|
|
uint32_t diepint;
|
|
|
|
uint32_t status;
|
|
|
|
|
|
|
|
if (daint & USB_DWC2_DAINT_INEPINT(n)) {
|
|
|
|
/* 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_xfercompl(dev, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear IEPINT interrupt */
|
|
|
|
sys_write32(USB_DWC2_GINTSTS_IEPINT, (mem_addr_t)&base->gintsts);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void dwc2_handle_oepint(const struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
|
|
|
|
const uint8_t n_max = 16;
|
|
|
|
uint32_t doepmsk;
|
|
|
|
uint32_t daint;
|
|
|
|
|
|
|
|
doepmsk = sys_read32((mem_addr_t)&base->doepmsk);
|
|
|
|
daint = sys_read32((mem_addr_t)&base->daint);
|
|
|
|
|
|
|
|
for (uint8_t n = 0U; n < n_max; n++) {
|
|
|
|
mem_addr_t doepint_reg = (mem_addr_t)&base->out_ep[n].doepint;
|
|
|
|
uint32_t doepint;
|
|
|
|
uint32_t status;
|
|
|
|
|
|
|
|
if (daint & USB_DWC2_DAINT_OUTEPINT(n)) {
|
|
|
|
/* 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);
|
2024-05-16 10:39:15 +02:00
|
|
|
|
|
|
|
if (status & USB_DWC2_DOEPINT_SETUP) {
|
|
|
|
struct dwc2_drv_event evt = {
|
|
|
|
.type = DWC2_DRV_EVT_SETUP,
|
|
|
|
.ep = USB_CONTROL_EP_OUT,
|
|
|
|
.bcnt = 8,
|
|
|
|
};
|
|
|
|
|
|
|
|
k_msgq_put(&drv_msgq, &evt, K_NO_WAIT);
|
|
|
|
}
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear OEPINT interrupt */
|
|
|
|
sys_write32(USB_DWC2_GINTSTS_OEPINT, (mem_addr_t)&base->gintsts);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
mem_addr_t gintsts_reg = (mem_addr_t)&base->gintsts;
|
|
|
|
uint32_t int_status;
|
|
|
|
uint32_t gintmsk;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2024-02-06 11:49:18 +01:00
|
|
|
if (int_status & USB_DWC2_GINTSTS_SOF) {
|
|
|
|
/* Clear USB SOF interrupt. */
|
|
|
|
sys_write32(USB_DWC2_GINTSTS_SOF, gintsts_reg);
|
|
|
|
udc_submit_event(dev, UDC_EVT_SOF, 0);
|
|
|
|
}
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2024-05-08 14:31:32 +02:00
|
|
|
udc_submit_event(dev, UDC_EVT_RESET, 0);
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (int_status & USB_DWC2_GINTSTS_USBSUSP) {
|
|
|
|
/* Clear USB Suspend interrupt. */
|
|
|
|
sys_write32(USB_DWC2_GINTSTS_USBSUSP, gintsts_reg);
|
|
|
|
udc_set_suspended(dev, true);
|
|
|
|
udc_submit_event(dev, UDC_EVT_SUSPEND, 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (int_status & USB_DWC2_GINTSTS_IEPINT) {
|
|
|
|
/* Handle IN Endpoints interrupt */
|
|
|
|
dwc2_handle_iepint(dev);
|
|
|
|
}
|
|
|
|
|
2024-05-08 15:28:55 +02:00
|
|
|
if (int_status & USB_DWC2_GINTSTS_RXFLVL) {
|
|
|
|
/* Handle RxFIFO Non-Empty interrupt */
|
|
|
|
dwc2_handle_rxflvl(dev);
|
|
|
|
}
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
if (int_status & USB_DWC2_GINTSTS_OEPINT) {
|
|
|
|
/* Handle OUT Endpoints interrupt */
|
|
|
|
dwc2_handle_oepint(dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-14 14:10:30 +02:00
|
|
|
(void)dwc2_quirk_irq_clear(dev);
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2024-05-16 11:43:42 +02:00
|
|
|
for (uint8_t i = priv->ineps - 1U; i > 0; i--) {
|
2023-09-07 13:40:13 +02:00
|
|
|
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);
|
|
|
|
uint32_t txfaddr;
|
|
|
|
uint32_t txfdep;
|
|
|
|
uint32_t tmp;
|
|
|
|
|
|
|
|
/* Keep everything but FIFO number */
|
|
|
|
tmp = *diepctl & ~USB_DWC2_DEPCTL_TXFNUM_MASK;
|
|
|
|
|
|
|
|
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 = UDC_DWC2_FIFO0_DEPTH + priv->grxfsiz;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set FIFO depth (32-bit words) and address */
|
|
|
|
txfdep = cfg->mps / 4U;
|
|
|
|
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 (cfg->mps < txfdep * 4U) {
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
|
|
|
|
struct udc_dwc2_data *const priv = udc_get_private(dev);
|
|
|
|
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;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The following applies to the Control IN endpoint only.
|
|
|
|
*
|
|
|
|
* Set endpoint 0 TxFIFO depth when dynfifosizing is enabled.
|
|
|
|
* Note that only dedicated mode is supported at this time.
|
|
|
|
*/
|
|
|
|
if (cfg->addr == USB_CONTROL_EP_IN && priv->dynfifosizing) {
|
|
|
|
uint32_t gnptxfsiz;
|
|
|
|
|
|
|
|
gnptxfsiz = usb_dwc2_set_gnptxfsiz_nptxfdep(UDC_DWC2_FIFO0_DEPTH) |
|
|
|
|
usb_dwc2_set_gnptxfsiz_nptxfstaddr(priv->grxfsiz);
|
|
|
|
|
|
|
|
sys_write32(gnptxfsiz, (mem_addr_t)&base->gnptxfsiz);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cfg->addr == USB_CONTROL_EP_OUT) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
dwc2_flush_rx_fifo(dev);
|
|
|
|
ret = dwc2_ctrl_feed_dout(dev, 8);
|
|
|
|
if (ret) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dwc2_flush_tx_fifo(dev, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
sys_write32(dxepctl0, dxepctl0_reg);
|
|
|
|
dwc2_set_epint(dev, cfg, true);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-05-10 14:47:01 +02:00
|
|
|
static int udc_dwc2_ep_activate(const struct device *dev,
|
|
|
|
struct udc_ep_config *const cfg)
|
2023-09-07 13:40:13 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cfg->mps > usb_dwc2_get_depctl_mps(UINT16_MAX)) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
dxepctl = sys_read32(dxepctl_reg);
|
|
|
|
/* Set max packet size */
|
|
|
|
dxepctl &= ~USB_DWC2_DEPCTL_MPS_MASK;
|
|
|
|
dxepctl |= cfg->mps << USB_DWC2_DEPCTL_MPS_POS;
|
|
|
|
|
|
|
|
/* 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) && cfg->mps != 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",
|
2024-05-14 07:12:50 +02:00
|
|
|
i, sys_read32((mem_addr_t)&base->dieptxf[i - 1U]), i, dxepctl);
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-05-10 14:47:01 +02:00
|
|
|
static void dwc2_wait_for_bit(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 (sys_timepoint_expired(timeout)) {
|
|
|
|
LOG_ERR("Timeout waiting for bit 0x%08X at 0x%08X",
|
|
|
|
bit, (uint32_t)addr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 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;
|
|
|
|
|
|
|
|
dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr);
|
|
|
|
dxepctl = sys_read32(dxepctl_reg);
|
|
|
|
|
2024-05-21 11:56:28 +02:00
|
|
|
if (dxepctl & USB_DWC2_DEPCTL_NAKSTS) {
|
|
|
|
/* Endpoint already sends forced NAKs. STALL if necessary. */
|
|
|
|
if (stall) {
|
|
|
|
dxepctl |= USB_DWC2_DEPCTL_STALL;
|
|
|
|
sys_write32(dxepctl, dxepctl_reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-10 14:47:01 +02:00
|
|
|
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(gintsts_reg, USB_DWC2_GINTSTS_GOUTNAKEFF);
|
|
|
|
|
2024-05-21 11:56:28 +02:00
|
|
|
/* The application cannot disable control OUT endpoint 0. */
|
|
|
|
if (ep_idx != 0) {
|
|
|
|
dxepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_EPDIS;
|
|
|
|
}
|
|
|
|
|
2024-05-10 14:47:01 +02:00
|
|
|
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);
|
|
|
|
|
2024-05-21 11:56:28 +02:00
|
|
|
if (ep_idx != 0) {
|
|
|
|
dwc2_wait_for_bit(doepint_reg, USB_DWC2_DOEPINT_EPDISBLD);
|
|
|
|
}
|
2024-05-10 14:47:01 +02:00
|
|
|
|
|
|
|
/* Clear Endpoint Disabled interrupt */
|
|
|
|
sys_write32(USB_DWC2_DIEPINT_EPDISBLD, 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_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(diepint_reg, USB_DWC2_DIEPINT_INEPNAKEFF);
|
|
|
|
|
|
|
|
dxepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_EPDIS;
|
|
|
|
sys_write32(dxepctl, dxepctl_reg);
|
|
|
|
|
|
|
|
dwc2_wait_for_bit(diepint_reg, USB_DWC2_DIEPINT_EPDISBLD);
|
|
|
|
|
|
|
|
/* Clear Endpoint Disabled interrupt */
|
|
|
|
sys_write32(USB_DWC2_DIEPINT_EPDISBLD, 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, ep_idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
udc_ep_set_busy(dev, cfg->addr, 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)
|
2023-09-07 13:40:13 +02:00
|
|
|
{
|
|
|
|
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);
|
2024-05-10 14:47:01 +02:00
|
|
|
|
|
|
|
udc_dwc2_ep_disable(dev, cfg, false);
|
|
|
|
|
|
|
|
dxepctl = sys_read32(dxepctl_reg);
|
|
|
|
dxepctl &= ~USB_DWC2_DEPCTL_USBACTEP;
|
2023-09-07 13:40:13 +02:00
|
|
|
} 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) && cfg->mps != 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);
|
|
|
|
|
2024-05-10 14:47:01 +02:00
|
|
|
udc_dwc2_ep_disable(dev, cfg, true);
|
2023-09-07 13:40:13 +02:00
|
|
|
|
|
|
|
LOG_DBG("Set halt ep 0x%02x", cfg->addr);
|
|
|
|
if (ep_idx != 0) {
|
|
|
|
cfg->stat.halted = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int udc_dwc2_ep_clear_halt(const struct device *dev,
|
|
|
|
struct udc_ep_config *const cfg)
|
|
|
|
{
|
2024-05-10 14:47:01 +02:00
|
|
|
mem_addr_t dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr);
|
|
|
|
uint32_t dxepctl;
|
|
|
|
struct dwc2_drv_event evt = {
|
|
|
|
.ep = cfg->addr,
|
|
|
|
.type = DWC2_DRV_EVT_XFER,
|
|
|
|
};
|
|
|
|
|
|
|
|
dxepctl = sys_read32(dxepctl_reg);
|
|
|
|
dxepctl &= ~USB_DWC2_DEPCTL_STALL;
|
|
|
|
dxepctl |= USB_DWC2_DEPCTL_SETD0PID;
|
|
|
|
sys_write32(dxepctl, dxepctl_reg);
|
2023-09-07 13:40:13 +02:00
|
|
|
|
|
|
|
LOG_DBG("Clear halt ep 0x%02x", cfg->addr);
|
|
|
|
cfg->stat.halted = false;
|
|
|
|
|
2024-05-10 14:47:01 +02:00
|
|
|
/* Resume queued transfers if any */
|
|
|
|
if (udc_buf_peek(dev, cfg->addr)) {
|
|
|
|
k_msgq_put(&drv_msgq, &evt, K_NO_WAIT);
|
|
|
|
}
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-05-20 14:12:06 +02:00
|
|
|
static int udc_dwc2_ep_enqueue(const struct device *dev,
|
|
|
|
struct udc_ep_config *const cfg,
|
|
|
|
struct net_buf *const buf)
|
|
|
|
{
|
|
|
|
struct dwc2_drv_event evt = {
|
|
|
|
.ep = cfg->addr,
|
|
|
|
.type = DWC2_DRV_EVT_XFER,
|
|
|
|
};
|
|
|
|
|
|
|
|
LOG_DBG("%p enqueue %x %p", dev, cfg->addr, buf);
|
|
|
|
udc_buf_put(cfg, buf);
|
|
|
|
|
|
|
|
if (!cfg->stat.halted) {
|
|
|
|
k_msgq_put(&drv_msgq, &evt, K_NO_WAIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
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(dev, cfg->addr);
|
|
|
|
if (buf) {
|
|
|
|
udc_submit_ep_event(dev, buf, -ECONNABORTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
udc_ep_set_busy(dev, cfg->addr, false);
|
|
|
|
|
|
|
|
LOG_DBG("dequeue ep 0x%02x", cfg->addr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
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;
|
|
|
|
|
|
|
|
if (addr > (USB_DWC2_DCFG_DEVADDR_MASK >> USB_DWC2_DCFG_DEVADDR_POS)) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
sys_clear_bits(dcfg_reg, USB_DWC2_DCFG_DEVADDR_MASK);
|
|
|
|
sys_set_bits(dcfg_reg, usb_dwc2_set_dcfg_devaddr(addr));
|
|
|
|
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 tstctl;
|
|
|
|
|
|
|
|
if (mode == 0U || mode > USB_DWC2_DCTL_TSTCTL_TESTFE) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
tstctl = usb_dwc2_get_dctl_tstctl(sys_read32(dctl_reg));
|
|
|
|
if (tstctl != USB_DWC2_DCTL_TSTCTL_DISABLED) {
|
|
|
|
return -EALREADY;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dryrun) {
|
|
|
|
LOG_DBG("Test Mode %u supported", mode);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sys_set_bits(dctl_reg, usb_dwc2_set_dctl_tstctl(mode));
|
|
|
|
LOG_DBG("Enable Test Mode %u", mode);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int udc_dwc2_host_wakeup(const struct device *dev)
|
|
|
|
{
|
|
|
|
LOG_DBG("Remote wakeup from %p", dev);
|
|
|
|
|
|
|
|
return -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
} while (sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRST &&
|
|
|
|
!(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRSTDONE));
|
|
|
|
|
|
|
|
sys_clear_bits(grstctl_reg, USB_DWC2_GRSTCTL_CSFTRST | USB_DWC2_GRSTCTL_CSFTRSTDONE);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-05-13 11:55:58 +02:00
|
|
|
static int udc_dwc2_init_controller(const struct device *dev)
|
2023-09-07 13:40:13 +02:00
|
|
|
{
|
|
|
|
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 gusbcfg_reg = (mem_addr_t)&base->gusbcfg;
|
|
|
|
mem_addr_t dcfg_reg = (mem_addr_t)&base->dcfg;
|
|
|
|
uint32_t gusbcfg;
|
|
|
|
uint32_t ghwcfg2;
|
|
|
|
uint32_t ghwcfg3;
|
|
|
|
uint32_t ghwcfg4;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = dwc2_core_soft_reset(dev);
|
|
|
|
if (ret) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (ghwcfg2 & USB_DWC2_GHWCFG2_DYNFIFOSIZING) {
|
|
|
|
LOG_DBG("Dynamic FIFO Sizing is enabled");
|
|
|
|
priv->dynfifosizing = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the number or endpoints and IN endpoints we can use later */
|
2024-05-16 11:43:42 +02:00
|
|
|
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);
|
2023-09-07 13:40:13 +02:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
/* Configure PHY and device speed */
|
|
|
|
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;
|
|
|
|
sys_set_bits(dcfg_reg, USB_DWC2_DCFG_DEVSPD_USBHS20);
|
|
|
|
break;
|
|
|
|
case USB_DWC2_GHWCFG2_HSPHYTYPE_UTMIPLUS:
|
|
|
|
gusbcfg |= USB_DWC2_GUSBCFG_PHYSEL_USB20 |
|
|
|
|
USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_UTMI;
|
|
|
|
sys_set_bits(dcfg_reg, USB_DWC2_DCFG_DEVSPD_USBHS20);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
sys_set_bits(dcfg_reg, USB_DWC2_DCFG_DEVSPD_USBFS1148);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (usb_dwc2_get_ghwcfg4_phydatawidth(ghwcfg4)) {
|
|
|
|
gusbcfg |= USB_DWC2_GUSBCFG_PHYIF_16_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Update PHY configuration */
|
|
|
|
sys_set_bits(gusbcfg_reg, gusbcfg);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (priv->dynfifosizing) {
|
|
|
|
priv->grxfsiz = UDC_DWC2_GRXFSIZ_DEFAULT + priv->outeps * 2U;
|
|
|
|
sys_write32(usb_dwc2_set_grxfsiz(priv->grxfsiz), (mem_addr_t)&base->grxfsiz);
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG_DBG("RX FIFO size %u bytes", priv->grxfsiz * 4);
|
|
|
|
for (uint8_t i = 1U; i < priv->ineps; i++) {
|
|
|
|
LOG_DBG("TX FIFO%u depth %u addr %u",
|
|
|
|
i, dwc2_get_txfdep(dev, 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 |
|
2024-02-06 11:49:18 +01:00
|
|
|
USB_DWC2_GINTSTS_WKUPINT | USB_DWC2_GINTSTS_USBSUSP |
|
|
|
|
USB_DWC2_GINTSTS_SOF,
|
2023-09-07 13:40:13 +02:00
|
|
|
(mem_addr_t)&base->gintmsk);
|
|
|
|
|
2024-05-13 11:55:58 +02:00
|
|
|
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;
|
|
|
|
|
2024-05-14 14:10:30 +02:00
|
|
|
err = dwc2_quirk_pre_enable(dev);
|
|
|
|
if (err) {
|
|
|
|
LOG_ERR("Quirk pre enable failed %d", err);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2024-05-13 11:55:58 +02:00
|
|
|
err = udc_dwc2_init_controller(dev);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2023-09-07 13:40:13 +02:00
|
|
|
|
2024-05-14 14:10:30 +02:00
|
|
|
err = dwc2_quirk_post_enable(dev);
|
|
|
|
if (err) {
|
|
|
|
LOG_ERR("Quirk post enable failed %d", err);
|
|
|
|
return err;
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Enable global interrupt */
|
|
|
|
sys_set_bits((mem_addr_t)&base->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK);
|
|
|
|
config->irq_enable_func(dev);
|
|
|
|
|
2024-05-13 11:55:58 +02:00
|
|
|
/* Disable soft disconnect */
|
|
|
|
sys_clear_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_SFTDISCON);
|
|
|
|
LOG_DBG("Enable device %p", base);
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-05-13 11:55:58 +02:00
|
|
|
static int udc_dwc2_disable(const struct device *dev)
|
2023-09-07 13:40:13 +02:00
|
|
|
{
|
|
|
|
const struct udc_dwc2_config *const config = dev->config;
|
2024-05-13 11:55:58 +02:00
|
|
|
struct usb_dwc2_reg *const base = dwc2_get_base(dev);
|
|
|
|
mem_addr_t dctl_reg = (mem_addr_t)&base->dctl;
|
2024-05-14 14:10:30 +02:00
|
|
|
int err;
|
2024-05-13 11:55:58 +02:00
|
|
|
|
|
|
|
/* Enable soft disconnect */
|
|
|
|
sys_set_bits(dctl_reg, USB_DWC2_DCTL_SFTDISCON);
|
|
|
|
LOG_DBG("Disable device %p", dev);
|
2023-09-07 13:40:13 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-05-21 11:56:28 +02:00
|
|
|
config->irq_disable_func(dev);
|
|
|
|
sys_clear_bits((mem_addr_t)&base->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK);
|
|
|
|
|
2024-05-14 14:10:30 +02:00
|
|
|
err = dwc2_quirk_disable(dev);
|
|
|
|
if (err) {
|
|
|
|
LOG_ERR("Quirk disable failed %d", err);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-05-13 11:55:58 +02:00
|
|
|
static int udc_dwc2_init(const struct device *dev)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2024-05-14 14:10:30 +02:00
|
|
|
ret = dwc2_quirk_init(dev);
|
|
|
|
if (ret) {
|
|
|
|
LOG_ERR("Quirk init failed %d", ret);
|
|
|
|
return ret;
|
2024-05-13 11:55:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return dwc2_init_pinctrl(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int udc_dwc2_shutdown(const struct device *dev)
|
|
|
|
{
|
2024-05-14 14:10:30 +02:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = dwc2_quirk_shutdown(dev);
|
|
|
|
if (ret) {
|
|
|
|
LOG_ERR("Quirk shutdown failed %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-05-13 11:55:58 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
static int dwc2_driver_preinit(const struct device *dev)
|
|
|
|
{
|
|
|
|
const struct udc_dwc2_config *config = dev->config;
|
|
|
|
struct udc_data *data = dev->data;
|
|
|
|
uint16_t mps = 1023;
|
2024-05-14 12:22:06 +02:00
|
|
|
uint32_t numdeveps;
|
|
|
|
uint32_t ineps;
|
2023-09-07 13:40:13 +02:00
|
|
|
int err;
|
|
|
|
|
|
|
|
k_mutex_init(&data->mutex);
|
|
|
|
|
|
|
|
data->caps.rwup = true;
|
|
|
|
data->caps.addr_before_status = true;
|
|
|
|
data->caps.mps0 = UDC_MPS0_64;
|
2024-05-14 12:22:06 +02:00
|
|
|
|
|
|
|
(void)dwc2_quirk_caps(dev);
|
|
|
|
if (data->caps.hs) {
|
2023-09-07 13:40:13 +02:00
|
|
|
mps = 1024;
|
|
|
|
}
|
|
|
|
|
2024-05-14 12:22:06 +02:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
if (i == 0) {
|
2024-05-14 12:22:06 +02:00
|
|
|
config->ep_cfg_out[n].caps.control = 1;
|
|
|
|
config->ep_cfg_out[n].caps.mps = 64;
|
2023-09-07 13:40:13 +02:00
|
|
|
} else {
|
2024-05-14 12:22:06 +02:00
|
|
|
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.mps = mps;
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
|
2024-05-14 12:22:06 +02:00
|
|
|
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]);
|
2023-09-07 13:40:13 +02:00
|
|
|
if (err != 0) {
|
|
|
|
LOG_ERR("Failed to register endpoint");
|
|
|
|
return err;
|
|
|
|
}
|
2024-05-14 12:22:06 +02:00
|
|
|
|
|
|
|
n++;
|
|
|
|
/* Also check the number of desired OUT endpoints in devicetree. */
|
|
|
|
if (n >= config->num_out_eps) {
|
|
|
|
break;
|
|
|
|
}
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
|
2024-05-14 12:22:06 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-09-07 13:40:13 +02:00
|
|
|
if (i == 0) {
|
2024-05-14 12:22:06 +02:00
|
|
|
config->ep_cfg_in[n].caps.control = 1;
|
|
|
|
config->ep_cfg_in[n].caps.mps = 64;
|
2023-09-07 13:40:13 +02:00
|
|
|
} else {
|
2024-05-14 12:22:06 +02:00
|
|
|
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.mps = mps;
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
|
2024-05-14 12:22:06 +02:00
|
|
|
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]);
|
2023-09-07 13:40:13 +02:00
|
|
|
if (err != 0) {
|
|
|
|
LOG_ERR("Failed to register endpoint");
|
|
|
|
return err;
|
|
|
|
}
|
2024-05-14 12:22:06 +02:00
|
|
|
|
|
|
|
n++;
|
|
|
|
/* Also check the number of desired IN endpoints in devicetree. */
|
|
|
|
if (n >= MIN(ineps, config->num_in_eps)) {
|
|
|
|
break;
|
|
|
|
}
|
2023-09-07 13:40:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
config->make_thread(dev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int udc_dwc2_lock(const struct device *dev)
|
|
|
|
{
|
|
|
|
return udc_lock_internal(dev, K_FOREVER);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int udc_dwc2_unlock(const struct device *dev)
|
|
|
|
{
|
|
|
|
return 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,
|
2024-05-10 14:47:01 +02:00
|
|
|
.ep_enable = udc_dwc2_ep_activate,
|
|
|
|
.ep_disable = udc_dwc2_ep_deactivate,
|
2023-09-07 13:40:13 +02:00
|
|
|
.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)); \
|
|
|
|
} \
|
|
|
|
\
|
2024-05-14 12:22:06 +02:00
|
|
|
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)]; \
|
2023-09-07 13:40:13 +02:00
|
|
|
\
|
|
|
|
static const struct udc_dwc2_config udc_dwc2_config_##n = { \
|
2024-05-14 12:22:06 +02:00
|
|
|
.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, \
|
2023-09-07 13:40:13 +02:00
|
|
|
.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), \
|
2024-05-14 12:22:06 +02:00
|
|
|
.ghwcfg1 = DT_INST_PROP(n, ghwcfg1), \
|
|
|
|
.ghwcfg2 = DT_INST_PROP(n, ghwcfg2), \
|
|
|
|
.ghwcfg4 = DT_INST_PROP(n, ghwcfg4), \
|
2023-09-07 13:40:13 +02:00
|
|
|
}; \
|
|
|
|
\
|
|
|
|
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)
|