From 2a1a1e5eb010221171cb629afa22b046ea638b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Thu, 12 Sep 2024 11:10:19 +0200 Subject: [PATCH] [nrf fromtree] drivers: usb: udc_dwc2: Support Remote Wakeup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to USB 2.0 Specification the remote wakeup device must hold the resume signaling for at least 1 ms but for no more than 15 ms. The DWC2 otg controller requires the software to drive the remote wakeup signalling for appropriate duration (when LPM is disabled, which is currently always the case in udc_dwc2). Arbitrarily choose to drive the resume signalling for 2 ms to have sufficient margin in both directions. Signed-off-by: Tomasz Moń (cherry picked from commit ac7d55b939598e702f5a118c8d31e2c0704769e8) --- drivers/usb/udc/udc_dwc2.c | 110 +++++++++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 17 deletions(-) diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index 54a946c5b05..2e56a9de180 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -31,6 +31,8 @@ enum dwc2_drv_event_type { DWC2_DRV_EVT_SETUP, /* Transaction on endpoint is finished */ DWC2_DRV_EVT_EP_FINISHED, + /* Remote Wakeup should be initiated */ + DWC2_DRV_EVT_REMOTE_WAKEUP, /* Core should enter hibernation */ DWC2_DRV_EVT_ENTER_HIBERNATION, /* Core should exit hibernation due to bus reset */ @@ -115,6 +117,7 @@ struct udc_dwc2_data { /* Configuration flags */ unsigned int dynfifosizing : 1; unsigned int bufferdma : 1; + unsigned int syncrst : 1; /* Runtime state flags */ unsigned int hibernated : 1; unsigned int enumdone : 1; @@ -900,7 +903,8 @@ static void dwc2_backup_registers(const struct device *dev) backup->pcgcctl = sys_read32((mem_addr_t)&base->pcgcctl); } -static void dwc2_restore_essential_registers(const struct device *dev) +static void dwc2_restore_essential_registers(const struct device *dev, + bool rwup) { const struct udc_dwc2_config *const config = dev->config; struct usb_dwc2_reg *const base = config->base; @@ -921,7 +925,9 @@ static void dwc2_restore_essential_registers(const struct device *dev) sys_write32(backup->gusbcfg, (mem_addr_t)&base->gusbcfg); sys_write32(backup->dcfg, (mem_addr_t)&base->dcfg); - pcgcctl |= USB_DWC2_PCGCCTL_RESTOREMODE | USB_DWC2_PCGCCTL_RSTPDWNMODULE; + if (!rwup) { + pcgcctl |= USB_DWC2_PCGCCTL_RESTOREMODE | USB_DWC2_PCGCCTL_RSTPDWNMODULE; + } sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl); k_busy_wait(1); @@ -929,7 +935,7 @@ static void dwc2_restore_essential_registers(const struct device *dev) sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl); } -static void dwc2_restore_device_registers(const struct device *dev) +static void dwc2_restore_device_registers(const struct device *dev, bool rwup) { const struct udc_dwc2_config *const config = dev->config; struct usb_dwc2_reg *const base = config->base; @@ -948,7 +954,10 @@ static void dwc2_restore_device_registers(const struct device *dev) sys_write32(backup->dieptxf[i - 1], (mem_addr_t)&base->dieptxf[i - 1]); } - sys_write32(backup->dctl, (mem_addr_t)&base->dctl); + if (!rwup) { + sys_write32(backup->dctl, (mem_addr_t)&base->dctl); + } + sys_write32(backup->diepmsk, (mem_addr_t)&base->diepmsk); sys_write32(backup->doepmsk, (mem_addr_t)&base->doepmsk); sys_write32(backup->daintmsk, (mem_addr_t)&base->daintmsk); @@ -1015,7 +1024,7 @@ static void dwc2_enter_hibernation(const struct device *dev) LOG_DBG("Hibernated"); } -static void dwc2_exit_hibernation(const struct device *dev) +static void dwc2_exit_hibernation(const struct device *dev, bool rwup) { const struct udc_dwc2_config *const config = dev->config; struct usb_dwc2_reg *const base = config->base; @@ -1038,6 +1047,14 @@ static void dwc2_exit_hibernation(const struct device *dev) /* Disable power clamps */ sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNCLMP); + if (rwup) { + if (priv->syncrst) { + k_busy_wait(1); + } else { + k_busy_wait(50); + } + } + /* Remove reset to the controller */ sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNRST_N); k_busy_wait(1); @@ -1045,7 +1062,9 @@ static void dwc2_exit_hibernation(const struct device *dev) /* Disable PMU interrupt */ sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUINTSEL); - dwc2_restore_essential_registers(dev); + dwc2_restore_essential_registers(dev, rwup); + + /* Note: in Remote Wakeup case 15 ms max signaling time starts now */ /* Wait for Restore Done Interrupt */ dwc2_wait_for_bit(dev, (mem_addr_t)&base->gintsts, USB_DWC2_GINTSTS_RSTRDONEINT); @@ -1055,8 +1074,10 @@ static void dwc2_exit_hibernation(const struct device *dev) 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); + if (!rwup) { + /* Clear reset to power down module */ + sys_clear_bits(pcgcctl_reg, USB_DWC2_PCGCCTL_RSTPDWNMODULE); + } /* Restore GUSBCFG, DCFG and DCTL */ sys_write32(priv->backup.gusbcfg, (mem_addr_t)&base->gusbcfg); @@ -1065,9 +1086,15 @@ static void dwc2_exit_hibernation(const struct device *dev) /* Disable PMU */ sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUACTV); - k_busy_wait(5); + if (!rwup) { + k_busy_wait(5); + sys_set_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_PWRONPRGDONE); + } else { + k_busy_wait(1); + sys_write32(USB_DWC2_DCTL_RMTWKUPSIG | priv->backup.dctl, + (mem_addr_t)&base->dctl); + } - sys_set_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_PWRONPRGDONE); k_msleep(1); sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts); } @@ -1621,9 +1648,13 @@ static int udc_dwc2_test_mode(const struct device *dev, static int udc_dwc2_host_wakeup(const struct device *dev) { + struct udc_dwc2_data *const priv = udc_get_private(dev); + LOG_DBG("Remote wakeup from %p", dev); - return -ENOTSUP; + k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_REMOTE_WAKEUP)); + + return 0; } /* Return actual USB device speed */ @@ -1785,6 +1816,10 @@ static int udc_dwc2_init_controller(const struct device *dev) LOG_DBG("LPM mode is %s", (ghwcfg3 & USB_DWC2_GHWCFG3_LPMMODE) ? "enabled" : "disabled"); + if (ghwcfg3 & USB_DWC2_GHWCFG3_RSTTYPE) { + priv->syncrst = 1; + } + /* Configure AHB, select Completer or DMA mode */ gahbcfg = sys_read32(gahbcfg_reg); @@ -1988,7 +2023,7 @@ static int udc_dwc2_disable(const struct device *dev) config->irq_disable_func(dev); if (priv->hibernated) { - dwc2_exit_hibernation(dev); + dwc2_exit_hibernation(dev, false); priv->hibernated = 0; } @@ -2045,6 +2080,7 @@ static int dwc2_driver_preinit(const struct device *dev) k_event_init(&priv->xfer_new); k_event_init(&priv->xfer_finished); + data->caps.rwup = true; data->caps.addr_before_status = true; data->caps.mps0 = UDC_MPS0_64; @@ -2704,20 +2740,36 @@ static void udc_dwc2_isr_handler(const struct device *dev) } static void dwc2_handle_hibernation_exit(const struct device *dev, - bool bus_reset) + bool rwup, bool bus_reset) { + struct usb_dwc2_reg *const base = dwc2_get_base(dev); struct udc_dwc2_data *const priv = udc_get_private(dev); - dwc2_exit_hibernation(dev); - dwc2_restore_device_registers(dev); + dwc2_exit_hibernation(dev, rwup); + dwc2_restore_device_registers(dev, rwup); priv->hibernated = 0; - LOG_DBG("Hibernation exit complete"); + if (!rwup) { + LOG_DBG("Hibernation exit complete"); + } /* Let stack know we are no longer suspended */ udc_set_suspended(dev, false); udc_submit_event(dev, UDC_EVT_RESUME, 0); + if (rwup) { + /* Resume has been driven for at least 1 ms now, do 1 ms more to + * have sufficient margin. + */ + k_msleep(1); + + sys_clear_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_RMTWKUPSIG); + } + + if (rwup) { + LOG_DBG("Hibernation exit on Remote Wakeup complete"); + } + if (bus_reset) { /* Clear all pending transfers */ k_event_clear(&priv->xfer_new, UINT32_MAX); @@ -2754,6 +2806,7 @@ static uint8_t pull_next_ep_from_bitmap(uint32_t *bitmap) static ALWAYS_INLINE void dwc2_thread_handler(void *const arg) { const struct device *dev = (const struct device *)arg; + struct usb_dwc2_reg *const base = dwc2_get_base(dev); struct udc_dwc2_data *const priv = udc_get_private(dev); const struct udc_dwc2_config *const config = dev->config; struct udc_ep_config *ep_cfg; @@ -2833,6 +2886,29 @@ static ALWAYS_INLINE void dwc2_thread_handler(void *const arg) dwc2_handle_evt_setup(dev); } + if (evt & BIT(DWC2_DRV_EVT_REMOTE_WAKEUP)) { + k_event_clear(&priv->drv_evt, BIT(DWC2_DRV_EVT_REMOTE_WAKEUP) | + BIT(DWC2_DRV_EVT_ENTER_HIBERNATION)); + + if (priv->hibernated) { + config->irq_disable_func(dev); + + dwc2_handle_hibernation_exit(dev, true, false); + + config->irq_enable_func(dev); + } else { + sys_set_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_RMTWKUPSIG); + + udc_set_suspended(dev, false); + udc_submit_event(dev, UDC_EVT_RESUME, 0); + + /* Drive resume for 2 ms to have sufficient margin */ + k_msleep(2); + + sys_clear_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_RMTWKUPSIG); + } + } + if (evt & BIT(DWC2_DRV_EVT_ENTER_HIBERNATION)) { config->irq_disable_func(dev); @@ -2856,7 +2932,7 @@ static ALWAYS_INLINE void dwc2_thread_handler(void *const arg) bus_reset = prev & BIT(DWC2_DRV_EVT_HIBERNATION_EXIT_BUS_RESET); if (priv->hibernated) { - dwc2_handle_hibernation_exit(dev, bus_reset); + dwc2_handle_hibernation_exit(dev, false, bus_reset); } config->irq_enable_func(dev);