diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index dc520abe78a..945c242bed4 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -139,13 +139,6 @@ struct udc_dwc2_data { uint8_t setup[8]; }; -static void dwc2_on_bus_reset(const struct device *dev); -static void dwc2_exit_hibernation(const struct device *dev); -static void dwc2_wait_for_bit(const struct device *dev, - mem_addr_t addr, uint32_t bit); -static void udc_dwc2_ep_disable(const struct device *dev, - struct udc_ep_config *const cfg, bool stall); - #if defined(CONFIG_PINCTRL) #include @@ -185,6 +178,33 @@ static inline struct usb_dwc2_reg *dwc2_get_base(const struct device *dev) 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; + } + } +} + /* Get DOEPCTLn or DIEPCTLn register address */ static mem_addr_t dwc2_get_dxepctl_reg(const struct device *dev, const uint8_t ep) { @@ -834,1934 +854,1907 @@ static int dwc2_handle_evt_din(const struct device *dev, return udc_submit_ep_event(dev, buf, 0); } -static ALWAYS_INLINE void dwc2_thread_handler(void *const arg) +static void dwc2_backup_registers(const struct device *dev) { - const struct device *dev = (const struct device *)arg; - 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; - struct dwc2_drv_event evt; + 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; - /* 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); + 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); - 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; - case DWC2_DRV_EVT_HIBERNATION_EXIT: - LOG_DBG("Hibernation exit event"); - config->irq_disable_func(dev); + for (uint8_t i = 1U; i < priv->ineps; i++) { + backup->dieptxf[i - 1] = sys_read32((mem_addr_t)&base->dieptxf[i - 1]); + } - if (priv->hibernated) { - dwc2_exit_hibernation(dev); + 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); - /* Let stack know we are no longer suspended */ - udc_set_suspended(dev, false); - udc_submit_event(dev, UDC_EVT_RESUME, 0); + for (uint8_t i = 0U; i < 16; i++) { + uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i); - if (evt.exit_reason == DWC2_HIBERNATION_EXIT_BUS_RESET) { - dwc2_on_bus_reset(dev); + 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); } - config->irq_enable_func(dev); - break; + 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); + } } - 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); - } + backup->pcgcctl = sys_read32((mem_addr_t)&base->pcgcctl); } -static void dwc2_on_bus_reset(const struct device *dev) +static void dwc2_restore_essential_registers(const struct device *dev) { - struct usb_dwc2_reg *const base = dwc2_get_base(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); - 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; + struct dwc2_reg_backup *backup = &priv->backup; + uint32_t pcgcctl = backup->pcgcctl & USB_DWC2_PCGCCTL_RESTOREVALUE_MASK; - 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); - } - } + 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); - doepmsk = USB_DWC2_DOEPINT_SETUP | USB_DWC2_DOEPINT_XFERCOMPL; - if (priv->bufferdma) { - doepmsk |= USB_DWC2_DOEPINT_STSPHSERCVD; - } + sys_write32(backup->gahbcfg | USB_DWC2_GAHBCFG_GLBINTRMASK, + (mem_addr_t)&base->gahbcfg); - sys_write32(doepmsk, (mem_addr_t)&base->doepmsk); - sys_set_bits((mem_addr_t)&base->diepmsk, USB_DWC2_DIEPINT_XFERCOMPL); + sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts); + sys_write32(USB_DWC2_GINTSTS_RSTRDONEINT, (mem_addr_t)&base->gintmsk); - /* Software has to handle RxFLvl interrupt only in Completer mode */ - if (!priv->bufferdma) { - sys_set_bits((mem_addr_t)&base->gintmsk, - USB_DWC2_GINTSTS_RXFLVL); - } + sys_write32(backup->gusbcfg, (mem_addr_t)&base->gusbcfg); + sys_write32(backup->dcfg, (mem_addr_t)&base->dcfg); - /* Clear device address during reset. */ - sys_clear_bits((mem_addr_t)&base->dcfg, USB_DWC2_DCFG_DEVADDR_MASK); + pcgcctl |= USB_DWC2_PCGCCTL_RESTOREMODE | USB_DWC2_PCGCCTL_RSTPDWNMODULE; + sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl); + k_busy_wait(1); - /* Speed enumeration must happen after reset. */ - priv->enumdone = 0; + pcgcctl |= USB_DWC2_PCGCCTL_ESSREGRESTORED; + sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl); } -static void dwc2_handle_enumdone(const struct device *dev) +static void dwc2_restore_device_registers(const struct device *dev) { - struct usb_dwc2_reg *const base = dwc2_get_base(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); - uint32_t dsts; + struct dwc2_reg_backup *backup = &priv->backup; - dsts = sys_read32((mem_addr_t)&base->dsts); - priv->enumspd = usb_dwc2_get_dsts_enumspd(dsts); - priv->enumdone = 1; -} + 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); -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; + for (uint8_t i = 1U; i < priv->ineps; i++) { + sys_write32(backup->dieptxf[i - 1], (mem_addr_t)&base->dieptxf[i - 1]); + } - /* FIFO access is always in 32-bit words */ + 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); - if (size != 8) { - LOG_ERR("%d bytes SETUP", size); - } + for (uint8_t i = 0U; i < 16; i++) { + uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i); - /* - * 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]); - } + 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); + } - /* On protocol error simply discard extra data */ - while (offset < size) { - sys_read32(UDC_DWC2_EP_FIFO(base, ep)); - offset += 4; + 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); + } } - - return 0; } -static inline void dwc2_handle_rxflvl(const struct device *dev) +static void dwc2_enter_hibernation(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; + 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; - 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); + dwc2_backup_registers(dev); - LOG_DBG("ep 0x%02x: pktsts %u, bcnt %u", ep, pktsts, bcnt); + /* This code currently only supports UTMI+. UTMI+ runs at either 30 or + * 60 MHz and therefore 1 us busy waits have sufficiently large margin. + */ - 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); + /* Enable PMU Logic */ + sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUACTV); + k_busy_wait(1); - buf = udc_buf_peek(dev, ep_cfg->addr); + /* Stop PHY clock */ + sys_set_bits(pcgcctl_reg, USB_DWC2_PCGCCTL_STOPPCLK); + k_busy_wait(1); - /* 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; - } + /* 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); + + /* Mark that the core is hibernated */ + priv->hibernated = 1; + LOG_DBG("Hibernated"); } -static inline void dwc2_handle_in_xfercompl(const struct device *dev, - const uint8_t ep_idx) +static void dwc2_exit_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); - struct udc_ep_config *ep_cfg; - struct dwc2_drv_event evt; - struct net_buf *buf; + mem_addr_t gpwrdn_reg = (mem_addr_t)&base->gpwrdn; + mem_addr_t pcgcctl_reg = (mem_addr_t)&base->pcgcctl; - 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; - } + /* Switch on power to the controller */ + sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNSWTCH); + k_busy_wait(1); - net_buf_pull(buf, priv->tx_len[ep_idx]); - if (buf->len && dwc2_tx_fifo_write(dev, ep_cfg, buf) == 0) { - return; - } + /* Reset the controller */ + sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNRST_N); + k_busy_wait(1); - evt.dev = dev; - evt.ep = ep_cfg->addr; - evt.type = DWC2_DRV_EVT_DIN; - k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); -} + /* Enable restore from PMU */ + sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_RESTORE); + k_busy_wait(1); -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; + /* Disable power clamps */ + sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNCLMP); - diepmsk = sys_read32((mem_addr_t)&base->diepmsk); - daint = sys_read32((mem_addr_t)&base->daint); + /* Remove reset to the controller */ + sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNRST_N); + k_busy_wait(1); - 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; + /* Disable PMU interrupt */ + sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUINTSEL); - 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); + dwc2_restore_essential_registers(dev); - LOG_DBG("ep 0x%02x interrupt status: 0x%x", - n | USB_EP_DIR_IN, status); + /* Wait for Restore Done Interrupt */ + dwc2_wait_for_bit(dev, (mem_addr_t)&base->gintsts, USB_DWC2_GINTSTS_RSTRDONEINT); + sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts); - if (status & USB_DWC2_DIEPINT_XFERCOMPL) { - dwc2_handle_in_xfercompl(dev, n); - } + /* Disable restore from PMU */ + sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_RESTORE); + k_busy_wait(1); - } - } + /* Clear reset to power down module */ + sys_clear_bits(pcgcctl_reg, USB_DWC2_PCGCCTL_RSTPDWNMODULE); - /* Clear IEPINT interrupt */ - sys_write32(USB_DWC2_GINTSTS_IEPINT, (mem_addr_t)&base->gintsts); -} + /* 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); -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); - struct dwc2_drv_event evt; - uint32_t bcnt; - struct net_buf *buf; - uint32_t doeptsiz; - const bool is_iso = dwc2_ep_is_iso(ep_cfg); + /* Disable PMU */ + sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUACTV); + k_busy_wait(5); - doeptsiz = sys_read32((mem_addr_t)&base->out_ep[ep_idx].doeptsiz); + sys_set_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_PWRONPRGDONE); + k_msleep(1); + sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts); - buf = udc_buf_peek(dev, ep_cfg->addr); - if (!buf) { - LOG_ERR("No buffer for ep 0x%02x", ep_cfg->addr); - udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); - return; - } + dwc2_restore_device_registers(dev); - evt.type = DWC2_DRV_EVT_DOUT; - evt.ep = ep_cfg->addr; + priv->hibernated = 0; + LOG_DBG("Hibernation exit complete"); +} - /* 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); +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; - if (is_iso) { - uint32_t pkts; - bool valid; + for (uint8_t i = priv->ineps - 1U; i > 0; i--) { + tmp = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN); - 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 (tmp->stat.enabled && (priv->txf_set & BIT(i))) { + return; } - if (!valid) { - if (!priv->bufferdma) { - /* RxFlvl added data to net buf, rollback */ - net_buf_remove_mem(buf, bcnt); - } - /* Data is not valid, discard it */ - bcnt = 0; + if (!tmp->stat.enabled && (priv->txf_set & BIT(i))) { + priv->txf_set &= ~BIT(i); } } - - if (priv->bufferdma && bcnt) { - sys_cache_data_invd_range(buf->data, bcnt); - net_buf_add(buf, bcnt); - } - - if (!is_iso && (bcnt % udc_mps_ep_size(ep_cfg)) == 0 && - net_buf_tailroom(buf)) { - dwc2_prep_rx(dev, buf, ep_cfg); - } else { - k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); - } } -static inline void dwc2_handle_oepint(const struct device *dev) +/* + * 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 usb_dwc2_reg *const base = dwc2_get_base(dev); struct udc_dwc2_data *const priv = udc_get_private(dev); - const uint8_t n_max = 16; - uint32_t doepmsk; - uint32_t daint; + 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; - doepmsk = sys_read32((mem_addr_t)&base->doepmsk); - daint = sys_read32((mem_addr_t)&base->daint); + /* Keep everything but FIFO number */ + tmp = *diepctl & ~USB_DWC2_DEPCTL_TXFNUM_MASK; - 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; + reqdep = DIV_ROUND_UP(udc_mps_ep_size(cfg), 4U); + if (priv->bufferdma) { + /* In DMA mode, TxFIFO capable of holding 2 packets is enough */ + reqdep *= MIN(2, (1 + addnl)); + } else { + reqdep *= (1 + addnl); + } - if (!(daint & USB_DWC2_DAINT_OUTEPINT(n))) { - continue; + if (priv->dynfifosizing) { + if (priv->txf_set & ~BIT_MASK(ep_idx)) { + dwc2_unset_unused_fifo(dev); } - /* Read and clear interrupt status */ - doepint = sys_read32(doepint_reg); - status = doepint & doepmsk; - sys_write32(status, doepint_reg); + 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; + } - 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 (priv->bufferdma && (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 ((ep_idx - 1) != 0U) { + txfaddr = dwc2_get_txfdep(dev, ep_idx - 2) + + dwc2_get_txfaddr(dev, ep_idx - 2); + } else { + txfaddr = priv->rxfifo_depth + + MAX(UDC_DWC2_FIFO0_DEPTH, priv->max_txfifo_depth[0]); } - if (status & USB_DWC2_DOEPINT_SETUP) { - struct dwc2_drv_event evt = { - .type = DWC2_DRV_EVT_SETUP, - .ep = USB_CONTROL_EP_OUT, - }; - - k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); + /* Make sure to not set TxFIFO greater than hardware allows */ + txfdep = reqdep; + if (txfdep > priv->max_txfifo_depth[ep_idx]) { + return -ENOMEM; } - 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. - */ + /* Do not allocate TxFIFO outside the SPRAM */ + if (txfaddr + txfdep > priv->dfifodepth) { + return -ENOMEM; } - if (status & USB_DWC2_DOEPINT_XFERCOMPL) { - dwc2_handle_out_xfercompl(dev, n); + /* 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); } - /* Clear OEPINT interrupt */ - sys_write32(USB_DWC2_GINTSTS_OEPINT, (mem_addr_t)&base->gintsts); -} + /* 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); -/* 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; + 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)); - for (uint8_t i = 1U; i < priv->numdeveps; i++) { - uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i); + return 0; +} - 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; +static int dwc2_ep_control_enable(const struct device *dev, + struct udc_ep_config *const cfg) +{ + mem_addr_t dxepctl0_reg; + uint32_t dxepctl0; - diepctl = sys_read32(diepctl_reg); + dxepctl0_reg = dwc2_get_dxepctl_reg(dev, cfg->addr); + dxepctl0 = sys_read32(dxepctl0_reg); - /* Check if endpoint didn't receive ISO OUT data */ - if ((diepctl & mask) == val) { - struct udc_ep_config *cfg; - struct net_buf *buf; + 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; + } - cfg = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN); - __ASSERT_NO_MSG(cfg && cfg->stat.enabled && - dwc2_ep_is_iso(cfg)); + dxepctl0 |= USB_DWC2_DEPCTL_USBACTEP; - udc_dwc2_ep_disable(dev, cfg, false); + if (cfg->addr == USB_CONTROL_EP_OUT) { + int ret; - buf = udc_buf_get(dev, cfg->addr); - if (buf) { - udc_submit_ep_event(dev, buf, 0); - } - } + 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(USB_DWC2_GINTSTS_INCOMPISOIN, gintsts_reg); + sys_write32(dxepctl0, dxepctl0_reg); + dwc2_set_epint(dev, cfg, true); + + return 0; } -/* 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) +static int udc_dwc2_ep_activate(const struct device *dev, + struct udc_ep_config *const cfg) { - const struct udc_dwc2_config *const config = dev->config; - struct usb_dwc2_reg *const base = config->base; + struct usb_dwc2_reg *const base = dwc2_get_base(dev); 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; + uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr); + mem_addr_t dxepctl_reg; + uint32_t dxepctl; - for (uint8_t i = 1U; i < priv->numdeveps; i++) { - uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i); + LOG_DBG("Enable ep 0x%02x", cfg->addr); - 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; + if (ep_idx == 0U) { + return dwc2_ep_control_enable(dev, cfg); + } - doepctl = sys_read32(doepctl_reg); + 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; + } - /* Check if endpoint didn't receive ISO OUT data */ - if ((doepctl & mask) == val) { - struct udc_ep_config *cfg; - struct net_buf *buf; + dxepctl_reg = (mem_addr_t)&base->in_ep[ep_idx].diepctl; + } - cfg = udc_get_ep_cfg(dev, i); - __ASSERT_NO_MSG(cfg && cfg->stat.enabled && - dwc2_ep_is_iso(cfg)); + 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)); - udc_dwc2_ep_disable(dev, cfg, false); + /* Set endpoint type */ + dxepctl &= ~USB_DWC2_DEPCTL_EPTYPE_MASK; - buf = udc_buf_get(dev, cfg->addr); - if (buf) { - udc_submit_ep_event(dev, buf, 0); - } - } - } + 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; } - sys_write32(USB_DWC2_GINTSTS_INCOMPISOOUT, gintsts_reg); -} + if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U) { + int ret = dwc2_set_dedicated_fifo(dev, cfg, &dxepctl); -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; + if (ret) { + return ret; + } + } - 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); + 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++) { - backup->dieptxf[i - 1] = sys_read32((mem_addr_t)&base->dieptxf[i - 1]); + LOG_DBG("DIEPTXF%u %08x DIEPCTL%u %08x", + i, sys_read32((mem_addr_t)&base->dieptxf[i - 1U]), i, dxepctl); } - 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); + return 0; } -static void dwc2_restore_essential_registers(const struct device *dev) +static int dwc2_unset_dedicated_fifo(const struct device *dev, + struct udc_ep_config *const cfg, + uint32_t *const diepctl) { - 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; - - 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); + uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr); - sys_write32(backup->gahbcfg | USB_DWC2_GAHBCFG_GLBINTRMASK, - (mem_addr_t)&base->gahbcfg); + /* Clear FIFO number field */ + *diepctl &= ~USB_DWC2_DEPCTL_TXFNUM_MASK; - sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts); - sys_write32(USB_DWC2_GINTSTS_RSTRDONEINT, (mem_addr_t)&base->gintmsk); + 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; + } - sys_write32(backup->gusbcfg, (mem_addr_t)&base->gusbcfg); - sys_write32(backup->dcfg, (mem_addr_t)&base->dcfg); + dwc2_set_txf(dev, ep_idx - 1, 0, 0); + } - pcgcctl |= USB_DWC2_PCGCCTL_RESTOREMODE | USB_DWC2_PCGCCTL_RSTPDWNMODULE; - sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl); - k_busy_wait(1); + priv->txf_set &= ~BIT(ep_idx); - pcgcctl |= USB_DWC2_PCGCCTL_ESSREGRESTORED; - sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl); + return 0; } -static void dwc2_restore_device_registers(const struct device *dev) +/* 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) { - 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; + 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; - 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); + dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr); + dxepctl = sys_read32(dxepctl_reg); - for (uint8_t i = 1U; i < priv->ineps; i++) { - sys_write32(backup->dieptxf[i - 1], (mem_addr_t)&base->dieptxf[i - 1]); + 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; } - 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); + if (USB_EP_DIR_IS_OUT(cfg->addr)) { + mem_addr_t dctl_reg, gintsts_reg, doepint_reg; + uint32_t dctl; - for (uint8_t i = 0U; i < 16; i++) { - uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i); + 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; - 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); - } + dctl = sys_read32(dctl_reg); - 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); + 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; } - } -} -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_wait_for_bit(dev, gintsts_reg, USB_DWC2_GINTSTS_GOUTNAKEFF); - dwc2_backup_registers(dev); + /* The application cannot disable control OUT endpoint 0. */ + if (ep_idx != 0) { + dxepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_EPDIS; + } - /* This code currently only supports UTMI+. UTMI+ runs at either 30 or - * 60 MHz and therefore 1 us busy waits have sufficiently large margin. - */ + 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); - /* Enable PMU Logic */ - sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUACTV); - k_busy_wait(1); + if (ep_idx != 0) { + dwc2_wait_for_bit(dev, doepint_reg, USB_DWC2_DOEPINT_EPDISBLD); + } - /* Stop PHY clock */ - sys_set_bits(pcgcctl_reg, USB_DWC2_PCGCCTL_STOPPCLK); - k_busy_wait(1); + /* Clear Endpoint Disabled interrupt */ + sys_write32(USB_DWC2_DIEPINT_EPDISBLD, doepint_reg); - /* Enable PMU interrupt */ - sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUINTSEL); - k_busy_wait(1); + dctl |= USB_DWC2_DCTL_CGOUTNAK; + sys_write32(dctl, dctl_reg); + } else { + mem_addr_t diepint_reg; - /* 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); + diepint_reg = (mem_addr_t)&base->in_ep[ep_idx].diepint; - /* Enable power clamps */ - sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNCLMP); - k_busy_wait(1); + 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); - /* Switch off power to the controller */ - sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNSWTCH); + dwc2_wait_for_bit(dev, diepint_reg, USB_DWC2_DIEPINT_INEPNAKEFF); - /* Mark that the core is hibernated */ - priv->hibernated = 1; - LOG_DBG("Hibernated"); -} + dxepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_EPDIS; + sys_write32(dxepctl, dxepctl_reg); -static void dwc2_exit_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_wait_for_bit(dev, diepint_reg, USB_DWC2_DIEPINT_EPDISBLD); - /* Switch on power to the controller */ - sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNSWTCH); - k_busy_wait(1); + /* Clear Endpoint Disabled interrupt */ + sys_write32(USB_DWC2_DIEPINT_EPDISBLD, diepint_reg); - /* Reset the controller */ - sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNRST_N); - k_busy_wait(1); + /* TODO: Read DIEPTSIZn here? Programming Guide suggest it to + * let application know how many bytes of interrupted transfer + * were transferred to the host. + */ - /* Enable restore from PMU */ - sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_RESTORE); - k_busy_wait(1); + dwc2_flush_tx_fifo(dev, usb_dwc2_get_depctl_txfnum(dxepctl)); + } - /* Disable power clamps */ - sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNCLMP); + udc_ep_set_busy(dev, cfg->addr, false); +} - /* Remove reset to the controller */ - sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNRST_N); - k_busy_wait(1); +/* 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; - /* Disable PMU interrupt */ - sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUINTSEL); + dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr); + dxepctl = sys_read32(dxepctl_reg); - dwc2_restore_essential_registers(dev); + if (dxepctl & USB_DWC2_DEPCTL_USBACTEP) { + LOG_DBG("Disable ep 0x%02x DxEPCTL%u %x", + cfg->addr, ep_idx, dxepctl); - /* Wait for Restore Done Interrupt */ - dwc2_wait_for_bit(dev, (mem_addr_t)&base->gintsts, USB_DWC2_GINTSTS_RSTRDONEINT); - sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts); + udc_dwc2_ep_disable(dev, cfg, false); - /* Disable restore from PMU */ - sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_RESTORE); - k_busy_wait(1); - - /* Clear reset to power down module */ - sys_clear_bits(pcgcctl_reg, USB_DWC2_PCGCCTL_RSTPDWNMODULE); + 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); + } - /* 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); + if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U && + ep_idx != 0U) { + dwc2_unset_dedicated_fifo(dev, cfg, &dxepctl); + } - /* Disable PMU */ - sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUACTV); - k_busy_wait(5); + sys_write32(dxepctl, dxepctl_reg); + dwc2_set_epint(dev, cfg, false); - sys_set_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_PWRONPRGDONE); - k_msleep(1); - sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts); + if (cfg->addr == USB_CONTROL_EP_OUT) { + struct net_buf *buf = udc_buf_get_all(dev, cfg->addr); - dwc2_restore_device_registers(dev); + /* Release the buffer allocated in dwc2_ctrl_feed_dout() */ + if (buf) { + net_buf_unref(buf); + } + } - priv->hibernated = 0; - LOG_DBG("Hibernation exit complete"); + return 0; } -static void udc_dwc2_isr_handler(const struct device *dev) +static int udc_dwc2_ep_set_halt(const struct device *dev, + struct udc_ep_config *const cfg) { - 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; + uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr); - if (priv->hibernated) { - uint32_t gpwrdn = sys_read32((mem_addr_t)&base->gpwrdn); - bool reset, resume = false; - enum dwc2_hibernation_exit_reason exit_reason; + udc_dwc2_ep_disable(dev, cfg, true); - /* Clear interrupts */ - sys_write32(gpwrdn, (mem_addr_t)&base->gpwrdn); + LOG_DBG("Set halt ep 0x%02x", cfg->addr); + if (ep_idx != 0) { + cfg->stat.halted = true; + } - if (gpwrdn & USB_DWC2_GPWRDN_LNSTSCHNG) { - resume = usb_dwc2_get_gpwrdn_linestate(gpwrdn) == - USB_DWC2_GPWRDN_LINESTATE_DM1DP0; - exit_reason = DWC2_HIBERNATION_EXIT_HOST_WAKEUP; - } + return 0; +} - reset = gpwrdn & USB_DWC2_GPWRDN_RESETDETECTED; - if (reset) { - exit_reason = DWC2_HIBERNATION_EXIT_BUS_RESET; - } +static int udc_dwc2_ep_clear_halt(const struct device *dev, + struct udc_ep_config *const cfg) +{ + 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, + }; - if (reset || resume) { - struct dwc2_drv_event evt = { - .dev = dev, - .type = DWC2_DRV_EVT_HIBERNATION_EXIT, - .exit_reason = exit_reason, - }; + dxepctl = sys_read32(dxepctl_reg); + dxepctl &= ~USB_DWC2_DEPCTL_STALL; + dxepctl |= USB_DWC2_DEPCTL_SETD0PID; + sys_write32(dxepctl, dxepctl_reg); - k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); - } + LOG_DBG("Clear halt ep 0x%02x", cfg->addr); + cfg->stat.halted = false; - (void)dwc2_quirk_irq_clear(dev); - return; + /* Resume queued transfers if any */ + if (udc_buf_peek(dev, cfg->addr)) { + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); } - gintmsk = sys_read32((mem_addr_t)&base->gintmsk); + return 0; +} - /* Read and handle interrupt status register */ - while ((int_status = sys_read32(gintsts_reg) & gintmsk)) { +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("GINTSTS 0x%x", int_status); + LOG_DBG("%p enqueue %x %p", dev, cfg->addr, buf); + udc_buf_put(cfg, buf); - if (int_status & USB_DWC2_GINTSTS_SOF) { - uint32_t dsts; + if (!cfg->stat.halted) { + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); + } - /* Clear USB SOF interrupt. */ - sys_write32(USB_DWC2_GINTSTS_SOF, gintsts_reg); + return 0; +} - 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); - } +static int udc_dwc2_ep_dequeue(const struct device *dev, + struct udc_ep_config *const cfg) +{ + struct net_buf *buf; - 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"); - } + udc_dwc2_ep_disable(dev, cfg, false); - 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); - } + buf = udc_buf_get_all(dev, cfg->addr); + if (buf) { + udc_submit_ep_event(dev, buf, -ECONNABORTED); + } - 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); - } + udc_ep_set_busy(dev, cfg->addr, false); - if (int_status & USB_DWC2_GINTSTS_IEPINT) { - /* Handle IN Endpoints interrupt */ - dwc2_handle_iepint(dev); - } + LOG_DBG("dequeue ep 0x%02x", cfg->addr); - if (int_status & USB_DWC2_GINTSTS_RXFLVL) { - /* Handle RxFIFO Non-Empty interrupt */ - dwc2_handle_rxflvl(dev); - } + return 0; +} - if (int_status & USB_DWC2_GINTSTS_OEPINT) { - /* Handle OUT Endpoints interrupt */ - dwc2_handle_oepint(dev); - } +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 (int_status & USB_DWC2_GINTSTS_INCOMPISOIN) { - dwc2_handle_incompisoin(dev); - } + if (addr > (USB_DWC2_DCFG_DEVADDR_MASK >> USB_DWC2_DCFG_DEVADDR_POS)) { + return -EINVAL; + } - if (int_status & USB_DWC2_GINTSTS_INCOMPISOOUT) { - dwc2_handle_incompisoout(dev); - } + 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); - if (int_status & USB_DWC2_GINTSTS_USBSUSP) { - if (!priv->enumdone) { - /* Clear stale suspend interrupt */ - sys_write32(USB_DWC2_GINTSTS_USBSUSP, gintsts_reg); - continue; - } + return 0; +} - /* Notify the stack */ - udc_set_suspended(dev, true); - udc_submit_event(dev, UDC_EVT_SUSPEND, 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 (priv->suspend_type == DWC2_SUSPEND_HIBERNATION) { - dwc2_enter_hibernation(dev); - /* Next interrupt will be from PMU */ - break; - } + if (mode == 0U || mode > USB_DWC2_DCTL_TSTCTL_TESTFE) { + return -EINVAL; + } - /* Clear USB Suspend interrupt. */ - sys_write32(USB_DWC2_GINTSTS_USBSUSP, gintsts_reg); - } + dctl = sys_read32(dctl_reg); + if (usb_dwc2_get_dctl_tstctl(dctl) != USB_DWC2_DCTL_TSTCTL_DISABLED) { + return -EALREADY; } - (void)dwc2_quirk_irq_clear(dev); -} + if (dryrun) { + LOG_DBG("Test Mode %u supported", mode); + return 0; + } -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; + dctl |= usb_dwc2_set_dctl_tstctl(mode); + sys_write32(dctl, dctl_reg); + LOG_DBG("Enable Test Mode %u", mode); - for (uint8_t i = priv->ineps - 1U; i > 0; i--) { - tmp = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN); + return 0; +} - if (tmp->stat.enabled && (priv->txf_set & BIT(i))) { - return; - } +static int udc_dwc2_host_wakeup(const struct device *dev) +{ + LOG_DBG("Remote wakeup from %p", dev); - if (!tmp->stat.enabled && (priv->txf_set & BIT(i))) { - priv->txf_set &= ~BIT(i); - } - } + return -ENOTSUP; } -/* - * 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) +/* 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); - 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 (priv->bufferdma) { - /* In DMA mode, TxFIFO capable of holding 2 packets is enough */ - reqdep *= MIN(2, (1 + addnl)); - } else { - reqdep *= (1 + addnl); + 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; } +} - 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 + - MAX(UDC_DWC2_FIFO0_DEPTH, priv->max_txfifo_depth[0]); - } +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; - /* Make sure to not set TxFIFO greater than hardware allows */ - txfdep = reqdep; - if (txfdep > priv->max_txfifo_depth[ep_idx]) { - return -ENOMEM; - } + /* Check AHB master idle state */ + while (!(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_AHBIDLE)) { + k_busy_wait(1); - /* Do not allocate TxFIFO outside the SPRAM */ - if (txfaddr + txfdep > priv->dfifodepth) { - return -ENOMEM; + if (++cnt > csr_timeout_us) { + LOG_ERR("Wait for AHB idle timeout, GRSTCTL 0x%08x", + sys_read32(grstctl_reg)); + return -EIO; } + } - /* 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); + /* Apply Core Soft Reset */ + sys_write32(USB_DWC2_GRSTCTL_CSFTRST, grstctl_reg); - if (reqdep > txfdep) { - return -ENOMEM; + cnt = 0UL; + do { + if (++cnt > csr_timeout_us) { + LOG_ERR("Wait for CSR done timeout, GRSTCTL 0x%08x", + sys_read32(grstctl_reg)); + return -EIO; } - 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); + k_busy_wait(1); + } while (sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRST && + !(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRSTDONE)); - 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)); + sys_clear_bits(grstctl_reg, USB_DWC2_GRSTCTL_CSFTRST | USB_DWC2_GRSTCTL_CSFTRSTDONE); return 0; } -static int dwc2_ep_control_enable(const struct device *dev, - struct udc_ep_config *const cfg) +static int udc_dwc2_init_controller(const struct device *dev) { - mem_addr_t dxepctl0_reg; - uint32_t dxepctl0; + 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 dcfg; + uint32_t gusbcfg; + uint32_t gahbcfg; + uint32_t ghwcfg2; + uint32_t ghwcfg3; + uint32_t ghwcfg4; + uint32_t val; + int ret; + bool hs_phy; - dxepctl0_reg = dwc2_get_dxepctl_reg(dev, cfg->addr); - dxepctl0 = sys_read32(dxepctl0_reg); + ret = dwc2_core_soft_reset(dev); + if (ret) { + return ret; + } - 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; + 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; } - dxepctl0 |= USB_DWC2_DEPCTL_USBACTEP; + /* + * 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 (cfg->addr == USB_CONTROL_EP_OUT) { - int ret; + /* 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); - dwc2_flush_rx_fifo(dev); - ret = dwc2_ctrl_feed_dout(dev, 8); - if (ret) { - return ret; - } + 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 { - dwc2_flush_tx_fifo(dev, 0); + priv->suspend_type = DWC2_SUSPEND_NO_POWER_SAVING; } - sys_write32(dxepctl0, dxepctl0_reg); - dwc2_set_epint(dev, cfg, true); + /* 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); - return 0; -} + 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)); -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("OTG architecture (OTGARCH) %u, mode (OTGMODE) %u", + usb_dwc2_get_ghwcfg2_otgarch(ghwcfg2), + usb_dwc2_get_ghwcfg2_otgmode(ghwcfg2)); - LOG_DBG("Enable ep 0x%02x", cfg->addr); + priv->dfifodepth = usb_dwc2_get_ghwcfg3_dfifodepth(ghwcfg3); + LOG_DBG("DFIFO depth (DFIFODEPTH) %u bytes", priv->dfifodepth * 4); - if (ep_idx == 0U) { - return dwc2_ep_control_enable(dev, cfg); - } + 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); - 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; - } + LOG_DBG("Vendor Control interface support enabled: %s", + (ghwcfg3 & USB_DWC2_GHWCFG3_VNDCTLSUPT) ? "true" : "false"); - dxepctl_reg = (mem_addr_t)&base->in_ep[ep_idx].diepctl; + 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 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; } - 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)); + sys_write32(gahbcfg, gahbcfg_reg); - /* Set endpoint type */ - dxepctl &= ~USB_DWC2_DEPCTL_EPTYPE_MASK; + dcfg = sys_read32(dcfg_reg); - 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; + 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; + dcfg |= USB_DWC2_DCFG_DEVSPD_USBHS20 + << USB_DWC2_DCFG_DEVSPD_POS; + hs_phy = true; break; - case USB_EP_TYPE_ISO: - dxepctl |= USB_DWC2_DEPCTL_EPTYPE_ISO << - USB_DWC2_DEPCTL_EPTYPE_POS; + case USB_DWC2_GHWCFG2_HSPHYTYPE_UTMIPLUS: + gusbcfg |= USB_DWC2_GUSBCFG_PHYSEL_USB20 | + USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_UTMI; + dcfg |= USB_DWC2_DCFG_DEVSPD_USBHS20 + << USB_DWC2_DCFG_DEVSPD_POS; + hs_phy = true; break; + case USB_DWC2_GHWCFG2_HSPHYTYPE_NO_HS: + __fallthrough; default: - return -EINVAL; - } + if (usb_dwc2_get_ghwcfg2_fsphytype(ghwcfg2) != + USB_DWC2_GHWCFG2_FSPHYTYPE_NO_FS) { + gusbcfg |= USB_DWC2_GUSBCFG_PHYSEL_USB11; + } - if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U) { - int ret = dwc2_set_dedicated_fifo(dev, cfg, &dxepctl); + dcfg |= USB_DWC2_DCFG_DEVSPD_USBFS1148 + << USB_DWC2_DCFG_DEVSPD_POS; + hs_phy = false; + } - if (ret) { - return ret; - } + if (usb_dwc2_get_ghwcfg4_phydatawidth(ghwcfg4)) { + gusbcfg |= USB_DWC2_GUSBCFG_PHYIF_16_BIT; } - dxepctl |= USB_DWC2_DEPCTL_USBACTEP; + /* Update PHY configuration */ + sys_write32(gusbcfg, gusbcfg_reg); + sys_write32(dcfg, dcfg_reg); - /* Enable endpoint interrupts */ - dwc2_set_epint(dev, cfg, true); - sys_write32(dxepctl, dxepctl_reg); + priv->outeps = 0U; + for (uint8_t i = 0U; i < priv->numdeveps; i++) { + uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i); - 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); + 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++; + } } - return 0; -} + LOG_DBG("Number of OUT endpoints %u", priv->outeps); -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); + /* 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); + } - /* Clear FIFO number field */ - *diepctl &= ~USB_DWC2_DEPCTL_TXFNUM_MASK; + priv->rxfifo_depth = usb_dwc2_get_grxfsiz(sys_read32(grxfsiz_reg)); 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; + 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; - dwc2_set_txf(dev, ep_idx - 1, 0, 0); + /* 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 = MAX(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); } - priv->txf_set &= ~BIT(ep_idx); + 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 void dwc2_wait_for_bit(const struct device *dev, - mem_addr_t addr, uint32_t bit) +static int udc_dwc2_enable(const struct device *dev) { - k_timepoint_t timeout = sys_timepoint_calc(K_MSEC(100)); + const struct udc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const base = dwc2_get_base(dev); + int err; - /* 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; - } + err = dwc2_quirk_pre_enable(dev); + if (err) { + LOG_ERR("Quirk pre enable failed %d", err); + return err; + } - if (sys_timepoint_expired(timeout)) { - LOG_ERR("Timeout waiting for bit 0x%08X at 0x%08X", - bit, (uint32_t)addr); - return; - } + err = udc_dwc2_init_controller(dev); + if (err) { + return err; + } + + err = dwc2_quirk_post_enable(dev); + if (err) { + LOG_ERR("Quirk post enable failed %d", 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); + + 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) +static int udc_dwc2_disable(const struct device *dev) { + const struct udc_dwc2_config *const config = dev->config; 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; + mem_addr_t dctl_reg = (mem_addr_t)&base->dctl; + int err; - dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr); - dxepctl = sys_read32(dxepctl_reg); + /* Enable soft disconnect */ + sys_set_bits(dctl_reg, USB_DWC2_DCTL_SFTDISCON); + LOG_DBG("Disable device %p", dev); - 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); - } + if (udc_ep_disable_internal(dev, USB_CONTROL_EP_OUT)) { + LOG_DBG("Failed to disable control endpoint"); + return -EIO; + } - return; + if (udc_ep_disable_internal(dev, USB_CONTROL_EP_IN)) { + LOG_DBG("Failed to disable control endpoint"); + return -EIO; } - if (USB_EP_DIR_IS_OUT(cfg->addr)) { - mem_addr_t dctl_reg, gintsts_reg, doepint_reg; - uint32_t dctl; + config->irq_disable_func(dev); + sys_clear_bits((mem_addr_t)&base->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK); - 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; + err = dwc2_quirk_disable(dev); + if (err) { + LOG_ERR("Quirk disable failed %d", err); + return err; + } - dctl = sys_read32(dctl_reg); + return 0; +} - 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; - } +static int udc_dwc2_init(const struct device *dev) +{ + int ret; - 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); + ret = dwc2_quirk_init(dev); + if (ret) { + LOG_ERR("Quirk init failed %d", ret); + return ret; + } - if (ep_idx != 0) { - dwc2_wait_for_bit(dev, doepint_reg, USB_DWC2_DOEPINT_EPDISBLD); - } + return dwc2_init_pinctrl(dev); +} - /* Clear Endpoint Disabled interrupt */ - sys_write32(USB_DWC2_DIEPINT_EPDISBLD, doepint_reg); +static int udc_dwc2_shutdown(const struct device *dev) +{ + int ret; - dctl |= USB_DWC2_DCTL_CGOUTNAK; - sys_write32(dctl, dctl_reg); - } else { - mem_addr_t diepint_reg; + ret = dwc2_quirk_shutdown(dev); + if (ret) { + LOG_ERR("Quirk shutdown failed %d", ret); + return ret; + } - diepint_reg = (mem_addr_t)&base->in_ep[ep_idx].diepint; + return 0; +} - 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); +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; + uint32_t numdeveps; + uint32_t ineps; + int err; - dwc2_wait_for_bit(dev, diepint_reg, USB_DWC2_DIEPINT_INEPNAKEFF); + k_mutex_init(&data->mutex); - dxepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_EPDIS; - sys_write32(dxepctl, dxepctl_reg); + data->caps.addr_before_status = true; + data->caps.mps0 = UDC_MPS0_64; - dwc2_wait_for_bit(dev, diepint_reg, USB_DWC2_DIEPINT_EPDISBLD); + (void)dwc2_quirk_caps(dev); + if (data->caps.hs) { + mps = 1024; + } - /* Clear Endpoint Disabled interrupt */ - sys_write32(USB_DWC2_DIEPINT_EPDISBLD, diepint_reg); + /* + * 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); - /* TODO: Read DIEPTSIZn here? Programming Guide suggest it to - * let application know how many bytes of interrupted transfer - * were transferred to the host. - */ + for (uint32_t i = 0, n = 0; i < numdeveps; i++) { + uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); - dwc2_flush_tx_fifo(dev, usb_dwc2_get_depctl_txfnum(dxepctl)); - } + if (epdir != USB_DWC2_GHWCFG1_EPDIR_OUT && + epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) { + continue; + } - udc_ep_set_busy(dev, cfg->addr, false); -} + 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; + } -/* 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; + config->ep_cfg_out[n].caps.out = 1; + config->ep_cfg_out[n].addr = USB_EP_DIR_OUT | i; - dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr); - dxepctl = sys_read32(dxepctl_reg); + 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; + } - if (dxepctl & USB_DWC2_DEPCTL_USBACTEP) { - LOG_DBG("Disable ep 0x%02x DxEPCTL%u %x", - cfg->addr, ep_idx, dxepctl); + n++; + /* Also check the number of desired OUT endpoints in devicetree. */ + if (n >= config->num_out_eps) { + break; + } + } - udc_dwc2_ep_disable(dev, cfg, false); + for (uint32_t i = 0, n = 0; i < numdeveps; i++) { + uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); - 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 (epdir != USB_DWC2_GHWCFG1_EPDIR_IN && + epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) { + continue; + } - if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U && - ep_idx != 0U) { - dwc2_unset_dedicated_fifo(dev, cfg, &dxepctl); - } + 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; + } - sys_write32(dxepctl, dxepctl_reg); - dwc2_set_epint(dev, cfg, false); + config->ep_cfg_in[n].caps.in = 1; + config->ep_cfg_in[n].addr = USB_EP_DIR_IN | i; - if (cfg->addr == USB_CONTROL_EP_OUT) { - struct net_buf *buf = udc_buf_get_all(dev, cfg->addr); + 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; + } - /* Release the buffer allocated in dwc2_ctrl_feed_dout() */ - if (buf) { - net_buf_unref(buf); + 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 int udc_dwc2_ep_set_halt(const struct device *dev, - struct udc_ep_config *const cfg) +static int udc_dwc2_lock(const struct device *dev) { - 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; - } + return udc_lock_internal(dev, K_FOREVER); +} - return 0; +static int udc_dwc2_unlock(const struct device *dev) +{ + return udc_unlock_internal(dev); } -static int udc_dwc2_ep_clear_halt(const struct device *dev, - struct udc_ep_config *const cfg) +static void dwc2_on_bus_reset(const struct device *dev) { - 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, - }; + struct usb_dwc2_reg *const base = dwc2_get_base(dev); + struct udc_dwc2_data *const priv = udc_get_private(dev); + uint32_t doepmsk; - dxepctl = sys_read32(dxepctl_reg); - dxepctl &= ~USB_DWC2_DEPCTL_STALL; - dxepctl |= USB_DWC2_DEPCTL_SETD0PID; - sys_write32(dxepctl, dxepctl_reg); + /* 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("Clear halt ep 0x%02x", cfg->addr); - cfg->stat.halted = false; + 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); + } + } - /* Resume queued transfers if any */ - if (udc_buf_peek(dev, cfg->addr)) { - k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); + doepmsk = USB_DWC2_DOEPINT_SETUP | USB_DWC2_DOEPINT_XFERCOMPL; + if (priv->bufferdma) { + doepmsk |= USB_DWC2_DOEPINT_STSPHSERCVD; } - return 0; + 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 (!priv->bufferdma) { + 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 int udc_dwc2_ep_enqueue(const struct device *dev, - struct udc_ep_config *const cfg, - struct net_buf *const buf) +static void dwc2_handle_enumdone(const struct device *dev) { - 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); - } + struct usb_dwc2_reg *const base = dwc2_get_base(dev); + struct udc_dwc2_data *const priv = udc_get_private(dev); + uint32_t dsts; - return 0; + dsts = sys_read32((mem_addr_t)&base->dsts); + priv->enumspd = usb_dwc2_get_dsts_enumspd(dsts); + priv->enumdone = 1; } -static int udc_dwc2_ep_dequeue(const struct device *dev, - struct udc_ep_config *const cfg) +static inline int dwc2_read_fifo_setup(const struct device *dev, uint8_t ep, + const size_t size) { - struct net_buf *buf; + struct usb_dwc2_reg *const base = dwc2_get_base(dev); + struct udc_dwc2_data *const priv = udc_get_private(dev); + size_t offset; - udc_dwc2_ep_disable(dev, cfg, false); + /* FIFO access is always in 32-bit words */ - buf = udc_buf_get_all(dev, cfg->addr); - if (buf) { - udc_submit_ep_event(dev, buf, -ECONNABORTED); + if (size != 8) { + LOG_ERR("%d bytes SETUP", size); } - udc_ep_set_busy(dev, cfg->addr, 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; + /* + * 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]); } - 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); + /* On protocol error simply discard extra data */ + while (offset < size) { + sys_read32(UDC_DWC2_EP_FIFO(base, ep)); + offset += 4; + } return 0; } -static int udc_dwc2_test_mode(const struct device *dev, - const uint8_t mode, const bool dryrun) +static inline void dwc2_handle_rxflvl(const struct device *dev) { 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; - } + struct udc_ep_config *ep_cfg; + struct net_buf *buf; + uint32_t grxstsp; + uint32_t pktsts; + uint32_t bcnt; + uint8_t ep; - if (dryrun) { - LOG_DBG("Test Mode %u supported", mode); - return 0; - } + 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); - dctl |= usb_dwc2_set_dctl_tstctl(mode); - sys_write32(dctl, dctl_reg); - LOG_DBG("Enable Test Mode %u", mode); + LOG_DBG("ep 0x%02x: pktsts %u, bcnt %u", ep, pktsts, bcnt); - return 0; -} + 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); -static int udc_dwc2_host_wakeup(const struct device *dev) -{ - LOG_DBG("Remote wakeup from %p", dev); + buf = udc_buf_peek(dev, ep_cfg->addr); - return -ENOTSUP; + /* 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; + } } -/* Return actual USB device speed */ -static enum udc_bus_speed udc_dwc2_device_speed(const struct device *dev) +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 dwc2_drv_event evt; + struct net_buf *buf; - 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; + 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 int dwc2_core_soft_reset(const struct device *dev) +static inline void dwc2_handle_iepint(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; + const uint8_t n_max = 16; + uint32_t diepmsk; + uint32_t daint; - /* Check AHB master idle state */ - while (!(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_AHBIDLE)) { - k_busy_wait(1); + diepmsk = sys_read32((mem_addr_t)&base->diepmsk); + daint = sys_read32((mem_addr_t)&base->daint); - if (++cnt > csr_timeout_us) { - LOG_ERR("Wait for AHB idle timeout, GRSTCTL 0x%08x", - sys_read32(grstctl_reg)); - return -EIO; - } - } + 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; - /* Apply Core Soft Reset */ - sys_write32(USB_DWC2_GRSTCTL_CSFTRST, grstctl_reg); + 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); - cnt = 0UL; - do { - if (++cnt > csr_timeout_us) { - LOG_ERR("Wait for CSR done timeout, GRSTCTL 0x%08x", - sys_read32(grstctl_reg)); - return -EIO; - } + LOG_DBG("ep 0x%02x interrupt status: 0x%x", + n | USB_EP_DIR_IN, status); - k_busy_wait(1); - } while (sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRST && - !(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRSTDONE)); + if (status & USB_DWC2_DIEPINT_XFERCOMPL) { + dwc2_handle_in_xfercompl(dev, n); + } - sys_clear_bits(grstctl_reg, USB_DWC2_GRSTCTL_CSFTRST | USB_DWC2_GRSTCTL_CSFTRSTDONE); + } + } - return 0; + /* Clear IEPINT interrupt */ + sys_write32(USB_DWC2_GINTSTS_IEPINT, (mem_addr_t)&base->gintsts); } -static int udc_dwc2_init_controller(const struct device *dev) +static inline void dwc2_handle_out_xfercompl(const struct device *dev, + const uint8_t ep_idx) { - const struct udc_dwc2_config *const config = dev->config; + 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 = 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 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; - } + struct usb_dwc2_reg *const base = dwc2_get_base(dev); + struct dwc2_drv_event evt; + uint32_t bcnt; + struct net_buf *buf; + uint32_t doeptsiz; + const bool is_iso = dwc2_ep_is_iso(ep_cfg); - 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); + doeptsiz = sys_read32((mem_addr_t)&base->out_ep[ep_idx].doeptsiz); - if (!(ghwcfg4 & USB_DWC2_GHWCFG4_DEDFIFOMODE)) { - LOG_ERR("Only dedicated TX FIFO mode is supported"); - return -ENOTSUP; + buf = udc_buf_peek(dev, ep_cfg->addr); + if (!buf) { + LOG_ERR("No buffer for ep 0x%02x", ep_cfg->addr); + udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + return; } - /* - * 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); + evt.type = DWC2_DRV_EVT_DOUT; + evt.ep = ep_cfg->addr; - /* Buffer DMA is always supported in Internal DMA mode. - * TODO: check and support descriptor DMA if available + /* The original transfer size value is necessary here because controller + * decreases the value for every byte stored. */ - priv->bufferdma = (usb_dwc2_get_ghwcfg2_otgarch(ghwcfg2) == - USB_DWC2_GHWCFG2_OTGARCH_INTERNALDMA); + bcnt = usb_dwc2_get_doeptsizn_xfersize(priv->rx_siz[ep_idx]) - + usb_dwc2_get_doeptsizn_xfersize(doeptsiz); - if (!IS_ENABLED(CONFIG_UDC_DWC2_DMA)) { - priv->bufferdma = 0; - } else if (priv->bufferdma) { - LOG_WRN("Experimental DMA enabled"); - } + if (is_iso) { + uint32_t pkts; + bool valid; - if (ghwcfg2 & USB_DWC2_GHWCFG2_DYNFIFOSIZING) { - LOG_DBG("Dynamic FIFO Sizing is enabled"); - priv->dynfifosizing = true; - } + 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 (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; + if (!valid) { + if (!priv->bufferdma) { + /* RxFlvl added data to net buf, rollback */ + net_buf_remove_mem(buf, bcnt); + } + /* Data is not valid, discard it */ + bcnt = 0; + } } - /* 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"); - - /* Configure AHB, select Completer or DMA mode */ - gahbcfg = sys_read32(gahbcfg_reg); + if (priv->bufferdma && bcnt) { + sys_cache_data_invd_range(buf->data, bcnt); + net_buf_add(buf, bcnt); + } - if (priv->bufferdma) { - gahbcfg |= USB_DWC2_GAHBCFG_DMAEN; + if (!is_iso && (bcnt % udc_mps_ep_size(ep_cfg)) == 0 && + net_buf_tailroom(buf)) { + dwc2_prep_rx(dev, buf, ep_cfg); } else { - gahbcfg &= ~USB_DWC2_GAHBCFG_DMAEN; + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); } +} - sys_write32(gahbcfg, gahbcfg_reg); +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); + const uint8_t n_max = 16; + uint32_t doepmsk; + uint32_t daint; - dcfg = sys_read32(dcfg_reg); + doepmsk = sys_read32((mem_addr_t)&base->doepmsk); + daint = sys_read32((mem_addr_t)&base->daint); - dcfg &= ~USB_DWC2_DCFG_DESCDMA; + 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; - /* 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; - dcfg |= USB_DWC2_DCFG_DEVSPD_USBHS20 - << USB_DWC2_DCFG_DEVSPD_POS; - hs_phy = true; - break; - case USB_DWC2_GHWCFG2_HSPHYTYPE_UTMIPLUS: - gusbcfg |= USB_DWC2_GUSBCFG_PHYSEL_USB20 | - USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_UTMI; - dcfg |= USB_DWC2_DCFG_DEVSPD_USBHS20 - << USB_DWC2_DCFG_DEVSPD_POS; - 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; + if (!(daint & USB_DWC2_DAINT_OUTEPINT(n))) { + continue; } - dcfg |= USB_DWC2_DCFG_DEVSPD_USBFS1148 - << USB_DWC2_DCFG_DEVSPD_POS; - hs_phy = false; - } - - if (usb_dwc2_get_ghwcfg4_phydatawidth(ghwcfg4)) { - gusbcfg |= USB_DWC2_GUSBCFG_PHYIF_16_BIT; - } + /* Read and clear interrupt status */ + doepint = sys_read32(doepint_reg); + status = doepint & doepmsk; + sys_write32(status, doepint_reg); - /* Update PHY configuration */ - sys_write32(gusbcfg, gusbcfg_reg); - sys_write32(dcfg, dcfg_reg); + LOG_DBG("ep 0x%02x interrupt status: 0x%x", n, status); - priv->outeps = 0U; - for (uint8_t i = 0U; i < priv->numdeveps; i++) { - uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i); + /* StupPktRcvd is not enabled for interrupt, but must be checked + * when XferComp hits to determine if SETUP token was received. + */ + if (priv->bufferdma && (status & USB_DWC2_DOEPINT_XFERCOMPL) && + (doepint & USB_DWC2_DOEPINT_STUPPKTRCVD)) { + uint32_t addr; - 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_DOEPINT_STUPPKTRCVD, doepint_reg); + status &= ~USB_DWC2_DOEPINT_XFERCOMPL; - sys_write32(USB_DWC2_DEPCTL_SNAK, doepctl_reg); - priv->outeps++; + /* 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)); } - } - - 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; + if (status & USB_DWC2_DOEPINT_SETUP) { + struct dwc2_drv_event evt = { + .type = DWC2_DRV_EVT_SETUP, + .ep = USB_CONTROL_EP_OUT, + }; - /* 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; + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); } - 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 = MAX(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 (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 (udc_ep_enable_internal(dev, USB_CONTROL_EP_IN, - USB_EP_TYPE_CONTROL, 64, 0)) { - LOG_ERR("Failed to enable control endpoint"); - return -EIO; + if (status & USB_DWC2_DOEPINT_XFERCOMPL) { + dwc2_handle_out_xfercompl(dev, n); + } } - /* 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; + /* Clear OEPINT interrupt */ + sys_write32(USB_DWC2_GINTSTS_OEPINT, (mem_addr_t)&base->gintsts); } -static int udc_dwc2_enable(const struct device *dev) +/* 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 = dwc2_get_base(dev); - int err; + 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; - err = dwc2_quirk_pre_enable(dev); - if (err) { - LOG_ERR("Quirk pre enable failed %d", err); - return err; - } + for (uint8_t i = 1U; i < priv->numdeveps; i++) { + uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i); - err = udc_dwc2_init_controller(dev); - if (err) { - return err; - } + 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; - err = dwc2_quirk_post_enable(dev); - if (err) { - LOG_ERR("Quirk post enable failed %d", err); - return err; - } + diepctl = sys_read32(diepctl_reg); - /* Enable global interrupt */ - sys_set_bits((mem_addr_t)&base->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK); - config->irq_enable_func(dev); + /* Check if endpoint didn't receive ISO OUT data */ + if ((diepctl & mask) == val) { + struct udc_ep_config *cfg; + struct net_buf *buf; - /* Disable soft disconnect */ - sys_clear_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_SFTDISCON); - LOG_DBG("Enable device %p", base); + cfg = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN); + __ASSERT_NO_MSG(cfg && cfg->stat.enabled && + dwc2_ep_is_iso(cfg)); - return 0; + udc_dwc2_ep_disable(dev, cfg, false); + + buf = udc_buf_get(dev, cfg->addr); + if (buf) { + udc_submit_ep_event(dev, buf, 0); + } + } + } + } + + sys_write32(USB_DWC2_GINTSTS_INCOMPISOIN, gintsts_reg); } -static int udc_dwc2_disable(const struct device *dev) +/* 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 = dwc2_get_base(dev); - mem_addr_t dctl_reg = (mem_addr_t)&base->dctl; - int err; - - /* Enable soft disconnect */ - sys_set_bits(dctl_reg, USB_DWC2_DCTL_SFTDISCON); - 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; - } + 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; - if (udc_ep_disable_internal(dev, USB_CONTROL_EP_IN)) { - LOG_DBG("Failed to disable control endpoint"); - return -EIO; - } + for (uint8_t i = 1U; i < priv->numdeveps; i++) { + uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i); - config->irq_disable_func(dev); - sys_clear_bits((mem_addr_t)&base->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK); + 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; - err = dwc2_quirk_disable(dev); - if (err) { - LOG_ERR("Quirk disable failed %d", err); - return err; - } + doepctl = sys_read32(doepctl_reg); - return 0; -} + /* Check if endpoint didn't receive ISO OUT data */ + if ((doepctl & mask) == val) { + struct udc_ep_config *cfg; + struct net_buf *buf; -static int udc_dwc2_init(const struct device *dev) -{ - int ret; + cfg = udc_get_ep_cfg(dev, i); + __ASSERT_NO_MSG(cfg && cfg->stat.enabled && + dwc2_ep_is_iso(cfg)); - ret = dwc2_quirk_init(dev); - if (ret) { - LOG_ERR("Quirk init failed %d", ret); - return ret; + udc_dwc2_ep_disable(dev, cfg, false); + + buf = udc_buf_get(dev, cfg->addr); + if (buf) { + udc_submit_ep_event(dev, buf, 0); + } + } + } } - return dwc2_init_pinctrl(dev); + sys_write32(USB_DWC2_GINTSTS_INCOMPISOOUT, gintsts_reg); } -static int udc_dwc2_shutdown(const struct device *dev) +static void udc_dwc2_isr_handler(const struct device *dev) { - int ret; + 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; - ret = dwc2_quirk_shutdown(dev); - if (ret) { - LOG_ERR("Quirk shutdown failed %d", ret); - return ret; - } + if (priv->hibernated) { + uint32_t gpwrdn = sys_read32((mem_addr_t)&base->gpwrdn); + bool reset, resume = false; + enum dwc2_hibernation_exit_reason exit_reason; - return 0; -} + /* Clear interrupts */ + sys_write32(gpwrdn, (mem_addr_t)&base->gpwrdn); -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; - uint32_t numdeveps; - uint32_t ineps; - int err; + if (gpwrdn & USB_DWC2_GPWRDN_LNSTSCHNG) { + resume = usb_dwc2_get_gpwrdn_linestate(gpwrdn) == + USB_DWC2_GPWRDN_LINESTATE_DM1DP0; + exit_reason = DWC2_HIBERNATION_EXIT_HOST_WAKEUP; + } - k_mutex_init(&data->mutex); + reset = gpwrdn & USB_DWC2_GPWRDN_RESETDETECTED; + if (reset) { + exit_reason = DWC2_HIBERNATION_EXIT_BUS_RESET; + } - data->caps.addr_before_status = true; - data->caps.mps0 = UDC_MPS0_64; + if (reset || resume) { + struct dwc2_drv_event evt = { + .dev = dev, + .type = DWC2_DRV_EVT_HIBERNATION_EXIT, + .exit_reason = exit_reason, + }; - (void)dwc2_quirk_caps(dev); - if (data->caps.hs) { - mps = 1024; + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); + } + + (void)dwc2_quirk_irq_clear(dev); + return; } - /* - * 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); + gintmsk = sys_read32((mem_addr_t)&base->gintmsk); - for (uint32_t i = 0, n = 0; i < numdeveps; i++) { - uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); + /* Read and handle interrupt status register */ + while ((int_status = sys_read32(gintsts_reg) & gintmsk)) { - if (epdir != USB_DWC2_GHWCFG1_EPDIR_OUT && - epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) { - continue; - } + LOG_DBG("GINTSTS 0x%x", int_status); - 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; + 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); } - config->ep_cfg_out[n].caps.out = 1; - config->ep_cfg_out[n].addr = USB_EP_DIR_OUT | i; + 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"); + } - 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; + 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); } - n++; - /* Also check the number of desired OUT endpoints in devicetree. */ - if (n >= config->num_out_eps) { - break; + 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); } - } - for (uint32_t i = 0, n = 0; i < numdeveps; i++) { - uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); + if (int_status & USB_DWC2_GINTSTS_IEPINT) { + /* Handle IN Endpoints interrupt */ + dwc2_handle_iepint(dev); + } - if (epdir != USB_DWC2_GHWCFG1_EPDIR_IN && - epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) { - continue; + if (int_status & USB_DWC2_GINTSTS_RXFLVL) { + /* Handle RxFIFO Non-Empty interrupt */ + dwc2_handle_rxflvl(dev); } - 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; + if (int_status & USB_DWC2_GINTSTS_OEPINT) { + /* Handle OUT Endpoints interrupt */ + dwc2_handle_oepint(dev); } - config->ep_cfg_in[n].caps.in = 1; - config->ep_cfg_in[n].addr = USB_EP_DIR_IN | i; + if (int_status & USB_DWC2_GINTSTS_INCOMPISOIN) { + dwc2_handle_incompisoin(dev); + } - 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; + if (int_status & USB_DWC2_GINTSTS_INCOMPISOOUT) { + dwc2_handle_incompisoout(dev); } - n++; - /* Also check the number of desired IN endpoints in devicetree. */ - if (n >= MIN(ineps, config->num_in_eps)) { - break; + if (int_status & USB_DWC2_GINTSTS_USBSUSP) { + if (!priv->enumdone) { + /* Clear stale suspend interrupt */ + sys_write32(USB_DWC2_GINTSTS_USBSUSP, gintsts_reg); + continue; + } + + /* Notify the stack */ + udc_set_suspended(dev, true); + udc_submit_event(dev, UDC_EVT_SUSPEND, 0); + + if (priv->suspend_type == DWC2_SUSPEND_HIBERNATION) { + dwc2_enter_hibernation(dev); + /* Next interrupt will be from PMU */ + break; + } + + /* Clear USB Suspend interrupt. */ + sys_write32(USB_DWC2_GINTSTS_USBSUSP, gintsts_reg); } } - config->make_thread(dev); - - return 0; + (void)dwc2_quirk_irq_clear(dev); } -static int udc_dwc2_lock(const struct device *dev) +static ALWAYS_INLINE void dwc2_thread_handler(void *const arg) { - return udc_lock_internal(dev, K_FOREVER); -} + const struct device *dev = (const struct device *)arg; + 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; + struct dwc2_drv_event evt; -static int udc_dwc2_unlock(const struct device *dev) -{ - return udc_unlock_internal(dev); + /* 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; + case DWC2_DRV_EVT_HIBERNATION_EXIT: + LOG_DBG("Hibernation exit event"); + config->irq_disable_func(dev); + + if (priv->hibernated) { + dwc2_exit_hibernation(dev); + + /* Let stack know we are no longer suspended */ + udc_set_suspended(dev, false); + udc_submit_event(dev, UDC_EVT_RESUME, 0); + + if (evt.exit_reason == DWC2_HIBERNATION_EXIT_BUS_RESET) { + dwc2_on_bus_reset(dev); + } + } + + config->irq_enable_func(dev); + 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 const struct udc_api udc_dwc2_api = {