From 380db44885d8e73658435619d7e51a3e057577a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Mon, 17 Nov 2025 10:29:45 +0100 Subject: [PATCH 1/6] dts: bindings: usb: Specify fifo sizes in devicetree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add g-rx-fifo-size, g-np-tx-fifo-size and g-tx-fifo-size required properties to snps,dwc2 compatible. Property names are the same as used in Linux because Zephyr aims for devicetree source compatibility with other operating systems. Specifying fifo sizes in devicetree greatly reduces necessary driver complexity and allows application developer to adjust the sizes if necessary to best utilize underlying hardware. Signed-off-by: Tomasz Moń --- dts/arm/intel_socfpga_std/socfpga.dtsi | 10 ++++++++++ dts/bindings/usb/snps,dwc2.yaml | 19 +++++++++++++++++++ dts/vendor/nordic/nrf54h20.dtsi | 10 ++++++++++ dts/vendor/nordic/nrf54lm20a.dtsi | 5 +++++ dts/vendor/nordic/nrf9280.dtsi | 10 ++++++++++ .../espressif/esp32s3/esp32s3_common.dtsi | 3 +++ 6 files changed, 57 insertions(+) diff --git a/dts/arm/intel_socfpga_std/socfpga.dtsi b/dts/arm/intel_socfpga_std/socfpga.dtsi index accf63c8acfaa..a6e08bf6c8327 100644 --- a/dts/arm/intel_socfpga_std/socfpga.dtsi +++ b/dts/arm/intel_socfpga_std/socfpga.dtsi @@ -238,6 +238,11 @@ ghwcfg1 = <0x00000000>; ghwcfg2 = <0x208ffc90>; ghwcfg4 = <0xfe0f0020>; + /* 8064 locations with full flexibility */ + g-rx-fifo-size = <608>; + g-np-tx-fifo-size = <32>; + g-tx-fifo-size = <512 512 512 512 512 512 512 512 + 512 512 512 512 512 512 256>; status = "disabled"; }; @@ -251,6 +256,11 @@ ghwcfg1 = <0x00000000>; ghwcfg2 = <0x208ffc90>; ghwcfg4 = <0xfe0f0020>; + /* 8064 locations with full flexibility */ + g-rx-fifo-size = <608>; + g-np-tx-fifo-size = <32>; + g-tx-fifo-size = <512 512 512 512 512 512 512 512 + 512 512 512 512 512 512 256>; status = "okay"; }; diff --git a/dts/bindings/usb/snps,dwc2.yaml b/dts/bindings/usb/snps,dwc2.yaml index d5b0167f26b32..4ab41f9214063 100644 --- a/dts/bindings/usb/snps,dwc2.yaml +++ b/dts/bindings/usb/snps,dwc2.yaml @@ -51,3 +51,22 @@ properties: description: | Value of the GHWCFG4 register. It is used to determine available endpoint types during driver pre-initialization. + + g-rx-fifo-size: + type: int + required: true + description: | + Rx Data FIFO Depth. Must not exceed GRXFSIZ.RxFDep power-on value. + + g-np-tx-fifo-size: + type: int + required: true + description: | + IN Endpoint 0 FIFO Depth. Must not exceed GNPTXFSIZ.NPTXFDep power-on value. + + g-tx-fifo-size: + type: array + required: true + description: | + IN Endpoint Transmit fifo depths. Values must not exceed their respective + DIEPTXFn.INEPnTxFDep power-on values. diff --git a/dts/vendor/nordic/nrf54h20.dtsi b/dts/vendor/nordic/nrf54h20.dtsi index a6a2f10c19ab9..09a9ffbfb1669 100644 --- a/dts/vendor/nordic/nrf54h20.dtsi +++ b/dts/vendor/nordic/nrf54h20.dtsi @@ -637,6 +637,16 @@ ghwcfg1 = <0xaa555000>; ghwcfg2 = <0x22abfc72>; ghwcfg4 = <0x1e10aa60>; + /* 3050 locations available, GRXFSIZ is 548, + * GNPTXFSIZ and DIEPTXFn are 512. The default + * values (upper bounds) sum up above the limit. + * Allow High-Bandwidth only on TxFIFO 1 and 2. + * Use maximum possible for RxFIFO and allocate + * everything remaining for TxFIFO 0. + */ + g-rx-fifo-size = <548>; + g-np-tx-fifo-size = <198>; + g-tx-fifo-size = <512 512 256 256 256 256 256>; status = "disabled"; }; diff --git a/dts/vendor/nordic/nrf54lm20a.dtsi b/dts/vendor/nordic/nrf54lm20a.dtsi index 1b467f1171ed4..fa542abf3f5bb 100644 --- a/dts/vendor/nordic/nrf54lm20a.dtsi +++ b/dts/vendor/nordic/nrf54lm20a.dtsi @@ -232,6 +232,11 @@ ghwcfg1 = <0x0>; ghwcfg2 = <0x22affc52>; ghwcfg4 = <0x3e10aa60>; + /* 3040 locations with full flexibility */ + g-rx-fifo-size = <592>; + g-np-tx-fifo-size = <32>; + g-tx-fifo-size = <512 256 256 256 256 256 256 256 + 16 16 16 16 16 16 16>; status = "disabled"; }; diff --git a/dts/vendor/nordic/nrf9280.dtsi b/dts/vendor/nordic/nrf9280.dtsi index df0a57d99a9b0..725865cfeb0a7 100644 --- a/dts/vendor/nordic/nrf9280.dtsi +++ b/dts/vendor/nordic/nrf9280.dtsi @@ -398,6 +398,16 @@ ghwcfg1 = <0xaa555000>; ghwcfg2 = <0x22abfc72>; ghwcfg4 = <0x1e10aa60>; + /* 3050 locations available, GRXFSIZ is 548, + * GNPTXFSIZ and DIEPTXFn are 512. The default + * values (upper bounds) sum up above the limit. + * Allow High-Bandwidth only on TxFIFO 1 and 2. + * Use maximum possible for RxFIFO and allocate + * everything remaining for TxFIFO 0. + */ + g-rx-fifo-size = <548>; + g-np-tx-fifo-size = <198>; + g-tx-fifo-size = <512 512 256 256 256 256 256>; status = "disabled"; }; diff --git a/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi b/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi index c7d8fbdc7ddb4..65cbf8f7fcb6c 100644 --- a/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi +++ b/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi @@ -422,6 +422,9 @@ ghwcfg1 = <0x00000000>; ghwcfg2 = <0x224dd930>; ghwcfg4 = <0xd3f0a030>; + g-rx-fifo-size = <64>; + g-np-tx-fifo-size = <16>; + g-tx-fifo-size = <32 32 32 24>; }; timer0: counter@6001f000 { From 76fa040472406db3a546fa8c0a2ee0bf0a3cbf33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Mon, 17 Nov 2025 10:38:50 +0100 Subject: [PATCH 2/6] drivers: usb: dwc2: Use fifo sizes from devicetree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dynamically allocating fifo sizes leads to very complex code that yields subpar results. DWC2 controller has very specific requirements related to configured FIFO sizes that significantly increase the complexity: * each fifo has specific upper bound on both size and starting location (fixed for each silicon) * assigned locations must be contiguous * assigned locations cannot be changed on-the-fly Zephyr tried to dynamically assign fifos using simple but essentially broken algorithm that: * assumed 1-to-1 endpoint number to TxFIFO mapping DWC2 controller can be configured with non-contiguous IN endpoint numbers, but TxFIFOs are always contiguous. * allocated minimum required space for each endpoint There is no benefit in using less SPRAM than hardware has available, but using only 128 locations per bulk endpoint significantly limits maximum transfer rate when using DMA. At least 256 locations allows DMA to load next packet data before previous packet is transmitted on the bus. Actual performance degradation depends on clock rates and latencies. MSC on nRF54H20 using bulk endpoints with only 128 locations can achieve no more than 15.6 MB/s throughput, while with 256 locations speeds up to 36.9 MB/s are possible (running otherwise identical software). * used one-size-fits-all defaults for RxFIFO Remedied with some upper limits calculated at runtime, but the limit was not taking into account complete configuration. * was unable to effectively handle multiple alternate settings This did lead to set interface failures where e.g. HID interface with wMaxPacketSize > 64 was not using the highest numbered IN endpoint within active configuration. Instead of trying to come up with bandaid for each issue that would significantly complicate the code, just shift the responsibility towards firmware developer. While for many typical applications board defaults are perfectly fine, the application developer may want to come up with specific devicetree overrides best suited for their use case. Signed-off-by: Tomasz Moń --- drivers/usb/udc/udc_dwc2.c | 352 +++++++++++++------------------------ drivers/usb/udc/udc_dwc2.h | 1 + 2 files changed, 125 insertions(+), 228 deletions(-) diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index c83e6671c29b8..644369e22ed79 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -44,31 +44,9 @@ enum dwc2_drv_event_type { DWC2_DRV_EVT_HIBERNATION_EXIT_HOST_RESUME, }; -/* Minimum RX FIFO size in 32-bit words considering the largest used OUT packet - * of 512 bytes. The value must be adjusted according to the number of OUT - * endpoints. - */ -#define UDC_DWC2_GRXFSIZ_FS_DEFAULT (15U + 512U/4U) -/* Default Rx FIFO size in 32-bit words calculated to support High-Speed with: - * * 1 control endpoint in Completer/Buffer DMA mode: 13 locations - * * Global OUT NAK: 1 location - * * Space for 3 * 1024 packets: ((1024/4) + 1) * 3 = 774 locations - * Driver adds 2 locations for each OUT endpoint to this value. - */ -#define UDC_DWC2_GRXFSIZ_HS_DEFAULT (13 + 1 + 774) - -/* TX FIFO0 depth in 32-bit words (used by control IN endpoint) - * Try 2 * bMaxPacketSize0 to allow simultaneous operation with a fallback to - * whatever is available when 2 * bMaxPacketSize0 is not possible. - */ -#define UDC_DWC2_FIFO0_DEPTH (2 * 16U) - /* Get Data FIFO access register */ #define UDC_DWC2_EP_FIFO(base, idx) ((mem_addr_t)base + 0x1000 * (idx + 1)) -/* Percentage limit of how much SPRAM can be allocated for RxFIFO */ -#define MAX_RXFIFO_GDFIFO_PERCENTAGE 25 - enum dwc2_suspend_type { DWC2_SUSPEND_NO_POWER_SAVING, DWC2_SUSPEND_HIBERNATION, @@ -127,11 +105,7 @@ struct udc_dwc2_data { uint16_t iso_out_rearm; uint16_t ep_out_disable; uint16_t ep_out_stall; - uint16_t txf_set; uint16_t pending_tx_flush; - uint16_t dfifodepth; - uint16_t rxfifo_depth; - uint16_t max_txfifo_depth[16]; uint16_t sof_num; /* Configuration flags */ unsigned int dynfifosizing : 1; @@ -331,41 +305,6 @@ static void dwc2_flush_tx_fifo(const struct device *dev, const uint8_t fnum) } } -/* Return TX FIFOi depth in 32-bit words (i = f_idx + 1) */ -static uint32_t dwc2_get_txfdep(const struct device *dev, const uint32_t f_idx) -{ - struct usb_dwc2_reg *const base = dwc2_get_base(dev); - uint32_t dieptxf; - - dieptxf = sys_read32((mem_addr_t)&base->dieptxf[f_idx]); - - return usb_dwc2_get_dieptxf_inepntxfdep(dieptxf); -} - -/* Return TX FIFOi address (i = f_idx + 1) */ -static uint32_t dwc2_get_txfaddr(const struct device *dev, const uint32_t f_idx) -{ - struct usb_dwc2_reg *const base = dwc2_get_base(dev); - uint32_t dieptxf; - - dieptxf = sys_read32((mem_addr_t)&base->dieptxf[f_idx]); - - return usb_dwc2_get_dieptxf_inepntxfstaddr(dieptxf); -} - -/* Set TX FIFOi address and depth (i = f_idx + 1) */ -static void dwc2_set_txf(const struct device *dev, const uint32_t f_idx, - const uint32_t dep, const uint32_t addr) -{ - struct usb_dwc2_reg *const base = dwc2_get_base(dev); - uint32_t dieptxf; - - dieptxf = usb_dwc2_set_dieptxf_inepntxfdep(dep) | - usb_dwc2_set_dieptxf_inepntxfstaddr(addr); - - sys_write32(dieptxf, (mem_addr_t)&base->dieptxf[f_idx]); -} - /* Enable/disable endpoint interrupt */ static void dwc2_set_epint(const struct device *dev, struct udc_ep_config *const cfg, const bool enabled) @@ -1353,24 +1292,6 @@ static void request_hibernation(struct udc_dwc2_data *const priv) } } -static void dwc2_unset_unused_fifo(const struct device *dev) -{ - struct udc_dwc2_data *const priv = udc_get_private(dev); - struct udc_ep_config *tmp; - - for (uint8_t i = priv->ineps - 1U; i > 0; i--) { - tmp = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN); - - if (tmp->stat.enabled && (priv->txf_set & BIT(i))) { - return; - } - - if (!tmp->stat.enabled && (priv->txf_set & BIT(i))) { - priv->txf_set &= ~BIT(i); - } - } -} - /* * In dedicated FIFO mode there are i (i = 1 ... ineps - 1) FIFO size registers, * e.g. DIEPTXF1, DIEPTXF2, ... DIEPTXF4. When dynfifosizing is enabled, @@ -1380,16 +1301,11 @@ static int dwc2_set_dedicated_fifo(const struct device *dev, struct udc_ep_config *const cfg, uint32_t *const diepctl) { - struct udc_dwc2_data *const priv = udc_get_private(dev); - uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr); + struct usb_dwc2_reg *const base = dwc2_get_base(dev); const uint32_t addnl = USB_MPS_ADDITIONAL_TRANSACTIONS(cfg->mps); + uint32_t dieptxf; uint32_t reqdep; - uint32_t txfaddr; - uint32_t txfdep; - uint32_t tmp; - - /* Keep everything but FIFO number */ - tmp = *diepctl & ~USB_DWC2_DEPCTL_TXFNUM_MASK; + uint8_t txfnum; reqdep = DIV_ROUND_UP(udc_mps_ep_size(cfg), 4U); if (dwc2_in_buffer_dma_mode(dev)) { @@ -1399,63 +1315,19 @@ static int dwc2_set_dedicated_fifo(const struct device *dev, reqdep *= (1 + addnl); } - if (priv->dynfifosizing) { - if (priv->txf_set & ~BIT_MASK(ep_idx)) { - dwc2_unset_unused_fifo(dev); - } - - if ((ep_idx - 1) != 0U) { - txfaddr = dwc2_get_txfdep(dev, ep_idx - 2) + - dwc2_get_txfaddr(dev, ep_idx - 2); - } else { - txfaddr = priv->rxfifo_depth + - MIN(UDC_DWC2_FIFO0_DEPTH, priv->max_txfifo_depth[0]); - } - - if (priv->txf_set & BIT(ep_idx)) { - uint32_t curaddr; + /* TxFIFOs are preassigned, just verify it can be used */ + txfnum = usb_dwc2_get_depctl_txfnum(*diepctl); + dieptxf = sys_read32((mem_addr_t)&base->dieptxf[txfnum]); - curaddr = dwc2_get_txfaddr(dev, ep_idx - 1); - txfdep = dwc2_get_txfdep(dev, ep_idx - 1); - if (txfaddr != curaddr || reqdep > txfdep) { - LOG_ERR("FIFO%u cannot be reused, new addr 0x%04x depth %u", - ep_idx, txfaddr, reqdep); - return -ENOMEM; - } - } else { - txfdep = reqdep; - } - - /* Make sure to not set TxFIFO greater than hardware allows */ - if (txfdep > priv->max_txfifo_depth[ep_idx]) { - return -ENOMEM; - } - - /* Do not allocate TxFIFO outside the SPRAM */ - if (txfaddr + txfdep > priv->dfifodepth) { - return -ENOMEM; - } - - /* Set FIFO depth (32-bit words) and address */ - dwc2_set_txf(dev, ep_idx - 1, txfdep, txfaddr); - } else { - txfdep = dwc2_get_txfdep(dev, ep_idx - 1); - txfaddr = dwc2_get_txfaddr(dev, ep_idx - 1); - - if (reqdep > txfdep) { - return -ENOMEM; - } - - LOG_DBG("Reuse FIFO%u addr 0x%08x depth %u", ep_idx, txfaddr, txfdep); + if (reqdep > usb_dwc2_get_dieptxf_inepntxfdep(dieptxf)) { + return -ENOMEM; } - /* 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); + dwc2_flush_tx_fifo(dev, txfnum); 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)); + txfnum, cfg->addr, usb_dwc2_get_dieptxf_inepntxfstaddr(dieptxf), + usb_dwc2_get_dieptxf_inepntxfdep(dieptxf), dwc2_ftx_avail(dev, txfnum)); return 0; } @@ -1583,33 +1455,6 @@ static int udc_dwc2_ep_activate(const struct device *dev, return 0; } -static int dwc2_unset_dedicated_fifo(const struct device *dev, - struct udc_ep_config *const cfg, - uint32_t *const diepctl) -{ - struct udc_dwc2_data *const priv = udc_get_private(dev); - uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr); - - /* Clear FIFO number field */ - *diepctl &= ~USB_DWC2_DEPCTL_TXFNUM_MASK; - - if (priv->dynfifosizing) { - uint16_t higher_mask = ~BIT_MASK(ep_idx + 1); - - if (priv->txf_set & higher_mask) { - LOG_WRN("Some of the FIFOs higher than %u are set, %x", - ep_idx, priv->txf_set & higher_mask); - return 0; - } - - dwc2_set_txf(dev, ep_idx - 1, 0, 0); - } - - priv->txf_set &= ~BIT(ep_idx); - - return 0; -} - /* Disabled IN endpoint means that device will send NAK (isochronous: ZLP) after * receiving IN token from host even if there is packet available in TxFIFO. * Disabled OUT endpoint means that device will NAK (isochronous: discard data) @@ -1757,11 +1602,6 @@ static int udc_dwc2_ep_deactivate(const struct device *dev, LOG_DBG("Disable ep 0x%02x DxEPCTL%u %x", cfg->addr, ep_idx, dxepctl); dxepctl &= ~USB_DWC2_DEPCTL_USBACTEP; - if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U && - ep_idx != 0U) { - dwc2_unset_dedicated_fifo(dev, cfg, &dxepctl); - } - sys_write32(dxepctl, dxepctl_reg); dwc2_set_epint(dev, cfg, false); @@ -2031,6 +1871,8 @@ static int udc_dwc2_init_controller(const struct device *dev) uint32_t ghwcfg3; uint32_t ghwcfg4; uint32_t val; + uint16_t dfifodepth; + uint8_t txfnum; int ret; bool hs_phy; @@ -2101,8 +1943,8 @@ static int udc_dwc2_init_controller(const struct device *dev) 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); + dfifodepth = usb_dwc2_get_ghwcfg3_dfifodepth(ghwcfg3); + LOG_DBG("DFIFO depth (DFIFODEPTH) %u bytes", dfifodepth * 4); priv->max_pktcnt = GHWCFG3_PKTCOUNT(usb_dwc2_get_ghwcfg3_pktsizewidth(ghwcfg3)); priv->max_xfersize = GHWCFG3_XFERSIZE(usb_dwc2_get_ghwcfg3_xfersizewidth(ghwcfg3)); @@ -2206,62 +2048,88 @@ static int udc_dwc2_init_controller(const struct device *dev) 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; - uint32_t spram_size; - uint32_t max_rxfifo; - - /* Get available SPRAM size and calculate max allocatable RX fifo size */ - val = sys_read32((mem_addr_t)&base->gdfifocfg); - spram_size = usb_dwc2_get_gdfifocfg_gdfifocfg(val); - max_rxfifo = ((spram_size * MAX_RXFIFO_GDFIFO_PERCENTAGE) / 100); - - /* 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. + uint32_t start_addr; + + /* Rx FIFO always starts at address 0 */ + __ASSERT_EVAL(, + val = usb_dwc2_get_grxfsiz(sys_read32(grxfsiz_reg)), + val >= config->fifo_sizes[0], + "DT g-rx-fifo-size %d exceeds power-on value %d", + config->fifo_sizes[0], val); + sys_write32(usb_dwc2_set_grxfsiz(config->fifo_sizes[0]), grxfsiz_reg); + + /* TxFIFO 0 starts after Rx FIFO */ + start_addr = config->fifo_sizes[0]; + __ASSERT_EVAL(, val = usb_dwc2_get_gnptxfsiz_nptxfdep( + sys_read32((mem_addr_t)&base->gnptxfsiz)), + val >= config->fifo_sizes[1], + "DT g-np-tx-fifo-size %d exceeds power-on value %d", + config->fifo_sizes[1], val); + val = usb_dwc2_set_gnptxfsiz_nptxfdep(config->fifo_sizes[1]) | + usb_dwc2_set_gnptxfsiz_nptxfstaddr(start_addr); + sys_write32(val, (mem_addr_t)&base->gnptxfsiz); + + /* Afterwards come all TxFIFOs */ + start_addr += config->fifo_sizes[1]; + for (uint8_t i = 0; i < priv->ineps - 1; i++) { + mem_addr_t dieptxf_reg = (mem_addr_t)&base->dieptxf[i]; + uint32_t dieptxf; + + __ASSERT_EVAL(, val = usb_dwc2_get_dieptxf_inepntxfdep( + sys_read32(dieptxf_reg)), + val >= config->fifo_sizes[2 + i], + "DT g-tx-fifo-size %d at idx %d exceeds power-on value %d", + config->fifo_sizes[2 + i], i, val); + dieptxf = usb_dwc2_set_dieptxf_inepntxfdep(config->fifo_sizes[2 + i]) | + usb_dwc2_set_dieptxf_inepntxfstaddr(start_addr); + sys_write32(dieptxf, dieptxf_reg); + + start_addr += config->fifo_sizes[2 + i]; + } + + /* Sanity check that we don't collide with EPInfoBaseAddr */ + __ASSERT(dfifodepth >= start_addr, + "FIFO memory ends at 0x%x but DFIFO Depth is 0x%x", + start_addr, dfifodepth); + } + + /* Assign FIFOs to IN endpoints */ + txfnum = 0; + for (uint8_t i = 0; i < 16; i++) { + /* Endpoint information was populated based on config->ghwcfg1, + * we have to use the same value even though we can access real + * register here (DT should really match HW, so this shouldn't + * be of any practical difference). */ - if (hs_phy) { - default_depth = UDC_DWC2_GRXFSIZ_HS_DEFAULT; - } else { - default_depth = UDC_DWC2_GRXFSIZ_FS_DEFAULT; - } - default_depth += priv->outeps * 2U; + uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); - /* 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(MIN(priv->rxfifo_depth, default_depth), max_rxfifo); - sys_write32(usb_dwc2_set_grxfsiz(priv->rxfifo_depth), grxfsiz_reg); + if (epdir == USB_DWC2_GHWCFG1_EPDIR_IN || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) { + mem_addr_t diepctl_reg = (mem_addr_t)&base->in_ep[i].diepctl; + uint32_t diepctl; - /* Set TxFIFO 0 depth */ - val = MIN(UDC_DWC2_FIFO0_DEPTH, priv->max_txfifo_depth[0]); - gnptxfsiz = usb_dwc2_set_gnptxfsiz_nptxfdep(val) | - usb_dwc2_set_gnptxfsiz_nptxfstaddr(priv->rxfifo_depth); + diepctl = sys_read32(diepctl_reg); + diepctl &= ~USB_DWC2_DEPCTL_TXFNUM_MASK; + diepctl |= usb_dwc2_set_depctl_txfnum(txfnum); + sys_write32(diepctl, diepctl_reg); - sys_write32(gnptxfsiz, (mem_addr_t)&base->gnptxfsiz); + txfnum++; + } } - 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)); + LOG_DBG("RX FIFO depth %u", usb_dwc2_get_grxfsiz(sys_read32(grxfsiz_reg))); + if (CONFIG_UDC_DRIVER_LOG_LEVEL >= LOG_LEVEL_DBG) { + val = sys_read32((mem_addr_t)&base->gnptxfsiz); + LOG_DBG("NPTX FIFO depth %u addr %u", + usb_dwc2_get_gnptxfsiz_nptxfdep(val), + usb_dwc2_get_gnptxfsiz_nptxfstaddr(val)); + + for (uint8_t i = 0; i < priv->ineps - 1; i++) { + val = sys_read32((mem_addr_t)&base->dieptxf[i]); + LOG_DBG("TX FIFO%u depth %u addr %u", + i + 1, usb_dwc2_get_dieptxf_inepntxfdep(val), + usb_dwc2_get_dieptxf_inepntxfstaddr(val)); + } } if (udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT, @@ -2418,9 +2286,10 @@ static int dwc2_driver_preinit(const struct device *dev) const struct udc_dwc2_config *config = dev->config; struct udc_dwc2_data *const priv = udc_get_private(dev); struct udc_data *data = dev->data; - uint16_t mps = 1023; + uint16_t mps = 1023, bulk_bytes = 64; uint32_t numdeveps; uint32_t ineps; + bool high_bandwidth; int err; k_mutex_init(&data->mutex); @@ -2441,6 +2310,7 @@ static int dwc2_driver_preinit(const struct device *dev) (void)dwc2_quirk_caps(dev); if (data->caps.hs) { mps = 1024; + bulk_bytes = 512; } /* @@ -2456,6 +2326,15 @@ static int dwc2_driver_preinit(const struct device *dev) LOG_DBG("Number of endpoints (NUMDEVEPS + 1) %u", numdeveps); LOG_DBG("Number of IN endpoints (INEPS + 1) %u", ineps); + /* RxFIFO has to be able to hold at least: + * * 1 control endpoint in Completer/Buffer DMA mode: 13 locations + * * Global OUT NAK: 1 location + * If in addition to above TxFIFO also has a space for at least: + * * Space for 2 * 1024 packets: ((1024/4) + 1) * 2 = 514 locations + * then consider OUT endpoints to be High-Bandwidth capable. + */ + high_bandwidth = data->caps.hs && config->fifo_sizes[0] >= 528; + for (uint32_t i = 0, n = 0; i < numdeveps; i++) { uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); @@ -2471,7 +2350,7 @@ static int dwc2_driver_preinit(const struct device *dev) 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.high_bandwidth = high_bandwidth; config->ep_cfg_out[n].caps.mps = mps; } @@ -2494,21 +2373,25 @@ static int dwc2_driver_preinit(const struct device *dev) for (uint32_t i = 0, n = 0; i < numdeveps; i++) { uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); + uint32_t fifo_bytes; if (epdir != USB_DWC2_GHWCFG1_EPDIR_IN && epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) { continue; } + fifo_bytes = config->fifo_sizes[1 + n] * 4; + high_bandwidth = data->caps.hs && fifo_bytes >= 2 * mps; + if (i == 0) { config->ep_cfg_in[n].caps.control = 1; - config->ep_cfg_in[n].caps.mps = 64; + config->ep_cfg_in[n].caps.mps = MIN(fifo_bytes, 64); } else { - config->ep_cfg_in[n].caps.bulk = 1; + config->ep_cfg_in[n].caps.bulk = fifo_bytes >= bulk_bytes; config->ep_cfg_in[n].caps.interrupt = 1; config->ep_cfg_in[n].caps.iso = 1; - config->ep_cfg_in[n].caps.high_bandwidth = data->caps.hs; - config->ep_cfg_in[n].caps.mps = mps; + config->ep_cfg_in[n].caps.high_bandwidth = high_bandwidth; + config->ep_cfg_in[n].caps.mps = MIN(fifo_bytes, mps); } config->ep_cfg_in[n].caps.in = 1; @@ -3560,6 +3443,18 @@ static const struct udc_api udc_dwc2_api = { static struct udc_ep_config ep_cfg_out[DT_INST_PROP(n, num_out_eps)]; \ static struct udc_ep_config ep_cfg_in[DT_INST_PROP(n, num_in_eps)]; \ \ + BUILD_ASSERT(DT_INST_PROP_LEN(n, g_tx_fifo_size) == \ + FIELD_GET(USB_DWC2_GHWCFG4_INEPS_MASK, \ + DT_INST_PROP(n, ghwcfg4)), \ + "g-tx-fifo-size length does not match GHWCFG4.INEps"); \ + \ + static const uint16_t fifo_sizes_##n[] = { \ + DT_INST_PROP(n, g_rx_fifo_size), \ + DT_INST_PROP(n, g_np_tx_fifo_size), \ + DT_INST_FOREACH_PROP_ELEM_SEP(n, g_tx_fifo_size, \ + DT_PROP_BY_IDX, (,)), \ + }; \ + \ static const struct udc_dwc2_config udc_dwc2_config_##n = { \ .num_out_eps = DT_INST_PROP(n, num_out_eps), \ .num_in_eps = DT_INST_PROP(n, num_in_eps), \ @@ -3567,6 +3462,7 @@ static const struct udc_api udc_dwc2_api = { .ep_cfg_out = ep_cfg_out, \ .make_thread = udc_dwc2_make_thread_##n, \ .base = (struct usb_dwc2_reg *)UDC_DWC2_DT_INST_REG_ADDR(n), \ + .fifo_sizes = fifo_sizes_##n, \ .pcfg = UDC_DWC2_PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ .irq_enable_func = udc_dwc2_irq_enable_func_##n, \ .irq_disable_func = udc_dwc2_irq_disable_func_##n, \ diff --git a/drivers/usb/udc/udc_dwc2.h b/drivers/usb/udc/udc_dwc2.h index 77be96adaa5f8..865fa59787307 100644 --- a/drivers/usb/udc/udc_dwc2.h +++ b/drivers/usb/udc/udc_dwc2.h @@ -43,6 +43,7 @@ struct udc_dwc2_config { struct udc_ep_config *ep_cfg_in; struct udc_ep_config *ep_cfg_out; struct usb_dwc2_reg *const base; + const uint16_t *const fifo_sizes; /* Pointer to pin control configuration or NULL */ struct pinctrl_dev_config *const pcfg; /* Pointer to vendor quirks or NULL */ From c45b8525348bd32adc4309536bb7c0cbeac47edd Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Wed, 23 Jul 2025 17:10:43 +0200 Subject: [PATCH 3/6] samples: usb: shell: host_prj.conf overaly for esp32 Add board support files needed to test the ESP32-S3 devkit with host support, as well as host_prj.conf used to test the USB shell with host support exclusively. Example build command: west build -b esp32s3_devkitm/esp32s3/procpu samples/subsys/usb/shell/ \ -DEXTRA_CONF_FILE=host_prj.conf -DCONFIG_USB_DEVICE_STACK_NEXT=n Signed-off-by: Roman Leonov --- .../boards/esp32s3_devkitm_procpu.overlay | 9 ++++++++ samples/subsys/usb/shell/host_prj.conf | 22 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 samples/subsys/usb/shell/boards/esp32s3_devkitm_procpu.overlay create mode 100644 samples/subsys/usb/shell/host_prj.conf diff --git a/samples/subsys/usb/shell/boards/esp32s3_devkitm_procpu.overlay b/samples/subsys/usb/shell/boards/esp32s3_devkitm_procpu.overlay new file mode 100644 index 0000000000000..9f66255629ad7 --- /dev/null +++ b/samples/subsys/usb/shell/boards/esp32s3_devkitm_procpu.overlay @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +zephyr_uhc0: &usb_otg { + status = "okay"; +}; diff --git a/samples/subsys/usb/shell/host_prj.conf b/samples/subsys/usb/shell/host_prj.conf new file mode 100644 index 0000000000000..a5c0c6e326c35 --- /dev/null +++ b/samples/subsys/usb/shell/host_prj.conf @@ -0,0 +1,22 @@ +CONFIG_SHELL=y + +CONFIG_USB_HOST_STACK=y +CONFIG_USBH_SHELL=y + +# CONFIG_DT_HAS_ZEPHYR_UHC_VIRTUAL_ENABLED=y +# CONFIG_UHC_VIRTUAL=y + +CONFIG_UHC_DWC2=y +CONFIG_UHC_DWC2_DMA=y + +# CONFIG_ASSERT=y # Enable asserts +# CONFIG_LOG=y # Logging framework +# CONFIG_LOG_MODE_IMMEDIATE=y # Useful in ISRs +# CONFIG_PRINTK=y # Enable printk (low-level logging) +# CONFIG_STACK_SENTINEL=y # Detect stack overflows +# CONFIG_THREAD_NAME=y # Name threads for easier debugging +# CONFIG_IRQ_OFFLOAD=y # Offload handlers in debug code + +CONFIG_LOG=y +CONFIG_USBH_LOG_LEVEL_WRN=y +CONFIG_UHC_DRIVER_LOG_LEVEL_WRN=y From 28f66469a0d5920d21806db85062f4271b143a40 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 24 Jul 2025 13:30:28 +0200 Subject: [PATCH 4/6] drivers: usb: dwc2: Added host register set Added register bitmask description with low-level abstraction Signed-off-by: Roman Leonov --- drivers/usb/common/usb_dwc2_hw.h | 196 ++++++++++++++++++++++++++++++- 1 file changed, 194 insertions(+), 2 deletions(-) diff --git a/drivers/usb/common/usb_dwc2_hw.h b/drivers/usb/common/usb_dwc2_hw.h index e02ae81722474..ddb89e1f88652 100644 --- a/drivers/usb/common/usb_dwc2_hw.h +++ b/drivers/usb/common/usb_dwc2_hw.h @@ -40,6 +40,18 @@ struct usb_dwc2_out_ep { volatile uint32_t doepdmab; }; +/* HOST channer register block */ +struct usb_dwc2_host_chan { + volatile uint32_t hcchar; + volatile uint32_t hcsplt; + volatile uint32_t hcint; + volatile uint32_t hcintmsk; + volatile uint32_t hctsiz; + volatile uint32_t hcdma; + volatile uint32_t reserved_0x18[1]; + volatile uint32_t hcdmab; +}; + /* DWC2 register map * TODO: This should probably be split into global, host, and device register * blocks @@ -80,8 +92,20 @@ struct usb_dwc2_reg { volatile uint32_t dieptxf[15]; }; volatile uint32_t reserved2[176]; - /* Host mode register 0x0400 .. 0x0670 */ - uint32_t reserved3[256]; + /* Host mode register 0x0400 .. 0x07FF */ + volatile uint32_t hcfg; + volatile uint32_t hfir; + volatile uint32_t hfnum; + volatile uint32_t reserved_0x40c[1]; + volatile uint32_t hptxsts; + volatile uint32_t haint; + volatile uint32_t haintmsk; + volatile uint32_t hflbaddr; + volatile uint32_t reserved_0x420_0x43c[8]; + volatile uint32_t hprt; + volatile uint32_t reserved_0x0444_0x04fc[47]; + struct usb_dwc2_host_chan host_chan_regs[16]; + volatile uint32_t reserved_0x0704_0x07fc[64]; /* Device mode register 0x0800 .. 0x0D00 */ volatile uint32_t dcfg; volatile uint32_t dctl; @@ -191,6 +215,17 @@ USB_DWC2_GET_FIELD_DEFINE(gahbcfg_hbstlen, GAHBCFG_HBSTLEN) #define USB_DWC2_GUSBCFG_FORCEDEVMODE BIT(USB_DWC2_GUSBCFG_FORCEDEVMODE_POS) #define USB_DWC2_GUSBCFG_FORCEHSTMODE_POS 29UL #define USB_DWC2_GUSBCFG_FORCEHSTMODE BIT(USB_DWC2_GUSBCFG_FORCEHSTMODE_POS) +#define USB_DWC2_GUSBCFG_ULPIEVBUSD_POS 20UL +#define USB_DWC2_GUSBCFG_ULPIEVBUSD BIT(USB_DWC2_GUSBCFG_ULPIEVBUSD_POS) +#define USB_DWC2_GUSBCFG_ULPIEVBUSI_POS 21UL +#define USB_DWC2_GUSBCFG_ULPIEVBUSI BIT(USB_DWC2_GUSBCFG_ULPIEVBUSI_POS) +#define USB_DWC2_GUSBCFG_ULPICLK_SUSM_POS 19UL +#define USB_DWC2_GUSBCFG_ULPICLK_SUSM BIT(USB_DWC2_GUSBCFG_ULPICLK_SUSM_POS) +#define USB_DWC2_GUSBCFG_ULPIFSLS_POS 17UL +#define USB_DWC2_GUSBCFG_ULPIFSLS BIT(USB_DWC2_GUSBCFG_ULPIFSLS_POS) +#define USB_DWC2_GUSBCFG_DDR_SEL_POS 7UL +#define USB_DWC2_GUSBCFG_DDR_SINGLE 0UL +#define USB_DWC2_GUSBCFG_DDR_DOUBLE BIT(USB_DWC2_GUSBCFG_DDR_SEL_POS) #define USB_DWC2_GUSBCFG_PHYSEL_POS 6UL #define USB_DWC2_GUSBCFG_PHYSEL_USB11 BIT(USB_DWC2_GUSBCFG_PHYSEL_POS) #define USB_DWC2_GUSBCFG_PHYSEL_USB20 0UL @@ -663,6 +698,163 @@ USB_DWC2_GET_FIELD_DEFINE(dieptxf_inepntxfstaddr, DIEPTXF_INEPNTXFSTADDR) USB_DWC2_SET_FIELD_DEFINE(dieptxf_inepntxfdep, DIEPTXF_INEPNTXFDEP) USB_DWC2_SET_FIELD_DEFINE(dieptxf_inepntxfstaddr, DIEPTXF_INEPNTXFSTADDR) +/* Periodic transmit FIFO size register (host mode) */ +#define USB_DWC2_HPTXFSIZ 0x0100UL +#define USB_DWC2_HPTXFSIZ_PTXFSIZE_POS 16UL +#define USB_DWC2_HPTXFSIZ_PTXFSIZE_MASK (0xFFFFUL << USB_DWC2_HPTXFSIZ_PTXFSIZE_POS) +#define USB_DWC2_HPTXFSIZ_PTXFSTADDR_POS 0UL +#define USB_DWC2_HPTXFSIZ_PTXFSTADDR_MASK (0xFFFFUL << USB_DWC2_HPTXFSIZ_PTXFSTADDR_POS) + + +/* Host Configuration Register */ +#define USB_DWC2_HCFG 0x0400UL +#define USB_DWC2_HCFG_MODECHTIMEN_POS 31UL +#define USB_DWC2_HCFG_MODECHTIMEN BIT(USB_DWC2_HCFG_MODECHTIMEN_POS) +#define USB_DWC2_HCFG_PERSCHEDENA_POS 26UL +#define USB_DWC2_HCFG_PERSCHEDENA BIT(USB_DWC2_HCFG_PERSCHEDENA_POS) +#define USB_DWC2_HCFG_FRLISTEN_POS 24UL +#define USB_DWC2_HCFG_FRLISTEN_MASK (0x3UL << USB_DWC2_HCFG_FRLISTEN_POS) +#define USB_DWC2_HCFG_DESCDMA_POS 23UL +#define USB_DWC2_HCFG_DESCDMA BIT(USB_DWC2_HCFG_DESCDMA_POS) +#define USB_DWC2_HCFG_DIS_TX_IPGAP_DLY_CHECK_POS 16UL +#define USB_DWC2_HCFG_DIS_TX_IPGAP_DLY_CHECK BIT(USB_DWC2_HCFG_DIS_TX_IPGAP_DLY_CHECK_POS) +#define USB_DWC2_HCFG_RESVALID_POS 8UL +#define USB_DWC2_HCFG_RESVALID_MASK (0xFFUL << USB_DWC2_HCFG_RESVALID_POS) +#define USB_DWC2_HCFG_ENA32KHZS_POS 7UL +#define USB_DWC2_HCFG_ENA32KHZS BIT(USB_DWC2_HCFG_ENA32KHZS_POS) +#define USB_DWC2_HCFG_FSLSSUPP_POS 2UL +#define USB_DWC2_HCFG_FSLSSUPP BIT(USB_DWC2_HCFG_FSLSSUPP_POS) +#define USB_DWC2_HCFG_FSLSPCLKSEL_POS 0UL +#define USB_DWC2_HCFG_FSLSPCLKSEL_MASK (0x3UL << USB_DWC2_HCFG_FSLSPCLKSEL_POS) + +USB_DWC2_SET_FIELD_DEFINE(hcfg_frlisten, HCFG_FRLISTEN) +USB_DWC2_SET_FIELD_DEFINE(hcfg_resvalid, HCFG_RESVALID) +USB_DWC2_SET_FIELD_DEFINE(hcfg_fslspclksel, HCFG_FSLSPCLKSEL) +USB_DWC2_GET_FIELD_DEFINE(hcfg_frlisten, HCFG_FRLISTEN) +USB_DWC2_GET_FIELD_DEFINE(hcfg_resvalid, HCFG_RESVALID) +USB_DWC2_GET_FIELD_DEFINE(hcfg_fslspclksel, HCFG_FSLSPCLKSEL) + +/* Host Frame Interval Register */ +#define USB_DWC2_HFIR 0x0404UL + +#define USB_DWC2_HFIR_HFIRRLDCTRL_POS 16UL +#define USB_DWC2_HFIR_HFIRRLDCTRL BIT(USB_DWC2_HFIR_HFIRRLDCTRL_POS) +#define USB_DWC2_HFIR_FRINT_POS 0UL +#define USB_DWC2_HFIR_FRINT_MASK (0xFFFFUL << USB_DWC2_HFIR_FRINT_POS) + +USB_DWC2_SET_FIELD_DEFINE(hfir_frint, HFIR_FRINT) +USB_DWC2_GET_FIELD_DEFINE(hfir_frint, HFIR_FRINT) + +/* Host All Channels Interrupt Register */ +#define USB_DWC2_HAINT 0x0414UL +#define USB_DWC2_HAINT_HAINT_POS 0UL +#define USB_DWC2_HAINT_HAINT_MASK 0xFFFFUL /* Bits [15:0], 1 bit per host channel */ + +USB_DWC2_GET_FIELD_DEFINE(haint_haint, HAINT_HAINT) + +/* Host Port Control and Status Register */ +#define USB_DWC2_HPRT 0x0440UL +#define USB_DWC2_HPRT_PRTSPD_POS 17UL +#define USB_DWC2_HPRT_PRTSPD_MASK (0x3UL << USB_DWC2_HPRT_PRTSPD_POS) +#define USB_DWC2_HPRT_PRTTSTCTL_POS 13UL +#define USB_DWC2_HPRT_PRTTSTCTL_MASK (0xFUL << USB_DWC2_HPRT_PRTTSTCTL_POS) +#define USB_DWC2_HPRT_PRTLNSTS_POS 10UL +#define USB_DWC2_HPRT_PRTLNSTS_MASK (0x3UL << USB_DWC2_HPRT_PRTLNSTS_POS) +#define USB_DWC2_HPRT_PRTENA BIT(2) +#define USB_DWC2_HPRT_PRTENCHNG BIT(3) +#define USB_DWC2_HPRT_PRTOVRCURRACT BIT(4) +#define USB_DWC2_HPRT_PRTOVRCURRCHNG BIT(5) +#define USB_DWC2_HPRT_PRTRES BIT(6) +#define USB_DWC2_HPRT_PRTSUSP BIT(7) +#define USB_DWC2_HPRT_PRTRST BIT(8) +#define USB_DWC2_HPRT_PRTCONNDET BIT(1) +#define USB_DWC2_HPRT_PRTCONNSTS BIT(0) +#define USB_DWC2_HPRT_PRTPWR BIT(12) +#define USB_DWC2_HPRT_PRTSPD_HIGH 0 +#define USB_DWC2_HPRT_PRTSPD_FULL 1 +#define USB_DWC2_HPRT_PRTSPD_LOW 2 + +USB_DWC2_SET_FIELD_DEFINE(hprt_prtspd, HPRT_PRTSPD) +USB_DWC2_SET_FIELD_DEFINE(hprt_prttstctl, HPRT_PRTTSTCTL) +USB_DWC2_SET_FIELD_DEFINE(hprt_prtlnsts, HPRT_PRTLNSTS) +USB_DWC2_GET_FIELD_DEFINE(hprt_prtspd, HPRT_PRTSPD) +USB_DWC2_GET_FIELD_DEFINE(hprt_prttstctl, HPRT_PRTTSTCTL) +USB_DWC2_GET_FIELD_DEFINE(hprt_prtlnsts, HPRT_PRTLNSTS) + +/* Host Channel Characteristics Register (HCCHAR0) */ +#define USB_DWC2_HCCHAR0 0x0500UL + +/* Bitfield Masks */ +#define USB_DWC2_HCCHAR0_CHENA BIT(31) +#define USB_DWC2_HCCHAR0_CHDIS BIT(30) +#define USB_DWC2_HCCHAR0_ODDFRM BIT(29) +#define USB_DWC2_HCCHAR0_DEVADDR_POS 22UL +#define USB_DWC2_HCCHAR0_DEVADDR_MASK (0x7FUL << USB_DWC2_HCCHAR0_DEVADDR_POS) +#define USB_DWC2_HCCHAR0_EC_POS 20UL +#define USB_DWC2_HCCHAR0_EC_MASK (0x3UL << USB_DWC2_HCCHAR0_EC_POS) +#define USB_DWC2_HCCHAR0_EPTYPE_POS 18UL +#define USB_DWC2_HCCHAR0_EPTYPE_MASK (0x3UL << USB_DWC2_HCCHAR0_EPTYPE_POS) +#define USB_DWC2_HCCHAR0_LSPDDEV BIT(17) +#define USB_DWC2_HCCHAR0_EPDIR BIT(15) +#define USB_DWC2_HCCHAR0_EPNUM_POS 11UL +#define USB_DWC2_HCCHAR0_EPNUM_MASK (0xFUL << USB_DWC2_HCCHAR0_EPNUM_POS) +#define USB_DWC2_HCCHAR0_MPS_POS 0UL +#define USB_DWC2_HCCHAR0_MPS_MASK (0x7FFUL << USB_DWC2_HCCHAR0_MPS_POS) + +USB_DWC2_SET_FIELD_DEFINE(hcchar0_devaddr, HCCHAR0_DEVADDR) +USB_DWC2_SET_FIELD_DEFINE(hcchar0_ec, HCCHAR0_EC) +USB_DWC2_SET_FIELD_DEFINE(hcchar0_eptype, HCCHAR0_EPTYPE) +USB_DWC2_SET_FIELD_DEFINE(hcchar0_epnum, HCCHAR0_EPNUM) +USB_DWC2_SET_FIELD_DEFINE(hcchar0_mps, HCCHAR0_MPS) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_devaddr, HCCHAR0_DEVADDR) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_ec, HCCHAR0_EC) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_eptype, HCCHAR0_EPTYPE) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_epnum, HCCHAR0_EPNUM) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_mps, HCCHAR0_MPS) + +/* + * Host Channel Interrupt Mask Registers (HCINTMSK) + * Offset: 0x050C + (0x20 * i), i = 0 .. (OTG_NUM_HOST_CHAN - 1) + */ +#define USB_DWC2_HCINT0 0x0508UL +#define USB_DWC2_HCINTMSK0 0x050CUL +#define USB_DWC2_HCINT_XFERCOMPL BIT(0) +#define USB_DWC2_HCINT_CHHLTD BIT(1) +#define USB_DWC2_HCINT_AHBERR BIT(2) +#define USB_DWC2_HCINT_STALL BIT(3) +#define USB_DWC2_HCINT_NAK BIT(4) +#define USB_DWC2_HCINT_ACK BIT(5) +#define USB_DWC2_HCINT_NYET BIT(6) +#define USB_DWC2_HCINT_XACTERR BIT(7) +#define USB_DWC2_HCINT_BBLERR BIT(8) +#define USB_DWC2_HCINT_FRMOVRUN BIT(9) +#define USB_DWC2_HCINT_DTGERR BIT(10) +#define USB_DWC2_HCINT_BNA BIT(11) +#define USB_DWC2_HCINT_DESC_LST_ROLL BIT(13) + +/* Host Channel Transfer Size Register */ +#define USB_DWC2_HCTSIZ0 0x0510UL +#define USB_DWC2_HCTSIZ_XFERSIZE_POS 0UL +#define USB_DWC2_HCTSIZ_XFERSIZE_MASK (0x7FFFFUL << USB_DWC2_HCTSIZ_XFERSIZE_POS) +#define USB_DWC2_HCTSIZ_PKTCNT_POS 19UL +#define USB_DWC2_HCTSIZ_PKTCNT_MASK (0x3FFUL << USB_DWC2_HCTSIZ_PKTCNT_POS) +#define USB_DWC2_HCTSIZ_PID_POS 29UL +#define USB_DWC2_HCTSIZ_PID_MASK (0x3UL << USB_DWC2_HCTSIZ_PID_POS) +#define USB_DWC2_HCTSIZ_DOPNG BIT(31) + +USB_DWC2_SET_FIELD_DEFINE(hctsiz_xfersize, HCTSIZ_XFERSIZE) +USB_DWC2_SET_FIELD_DEFINE(hctsiz_pktcnt, HCTSIZ_PKTCNT) +USB_DWC2_SET_FIELD_DEFINE(hctsiz_pid, HCTSIZ_PID) +USB_DWC2_GET_FIELD_DEFINE(hctsiz_xfersize, HCTSIZ_XFERSIZE) +USB_DWC2_GET_FIELD_DEFINE(hctsiz_pktcnt, HCTSIZ_PKTCNT) +USB_DWC2_GET_FIELD_DEFINE(hctsiz_pid, HCTSIZ_PID) + +/* Host Channel DMA Address Register */ +#define USB_DWC2_HCDMA0 0x0514UL + +/* Host Channel DMA Buffer Address Register */ +#define USB_DWC2_HCDMAB0 0x051CUL + /* Device configuration registers */ #define USB_DWC2_DCFG 0x0800UL #define USB_DWC2_DCFG_RESVALID_POS 26UL From 1858571ed7bd603774b1dea28b5ed44f27d1b0bf Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Wed, 23 Jul 2025 18:35:38 +0200 Subject: [PATCH 5/6] drivers: usb: uhc_dwc2: added uhc_dwc2 driver with vendor quirks Introduce an USB host driver for Synopsys Designware USB OTG controller (DWC2) with vendor quirks for nRF54LM20 and ESP32-S3, using the nRF54LM20-DK and ESP32-S3 DevitC. Both need VBUS supplied with an external +5V to power the USB device. The driver currently only support control commands on the control endpoint which is enough for the enumeration process. Signed-off-by: Roman Leonov Co-authored-by: Josuah Demangeon --- drivers/usb/uhc/CMakeLists.txt | 2 + drivers/usb/uhc/Kconfig | 1 + drivers/usb/uhc/Kconfig.dwc2 | 81 + drivers/usb/uhc/uhc_dwc2.c | 1697 +++++++++++++++++ drivers/usb/uhc/uhc_dwc2.h | 106 + drivers/usb/uhc/uhc_dwc2_vendor_quirks.h | 442 +++++ dts/bindings/usb/snps,dwc2.yaml | 12 + dts/vendor/nordic/nrf54lm20a.dtsi | 4 +- .../espressif/esp32s3/esp32s3_common.dtsi | 2 + .../nrf54lm20dk_nrf54lm20a_cpuapp.overlay | 8 + 10 files changed, 2354 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/uhc/Kconfig.dwc2 create mode 100644 drivers/usb/uhc/uhc_dwc2.c create mode 100644 drivers/usb/uhc/uhc_dwc2.h create mode 100644 drivers/usb/uhc/uhc_dwc2_vendor_quirks.h create mode 100644 samples/subsys/usb/shell/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay diff --git a/drivers/usb/uhc/CMakeLists.txt b/drivers/usb/uhc/CMakeLists.txt index 69f1cea8433b2..8f834f1d6e086 100644 --- a/drivers/usb/uhc/CMakeLists.txt +++ b/drivers/usb/uhc/CMakeLists.txt @@ -3,8 +3,10 @@ # SPDX-License-Identifier: Apache-2.0 zephyr_library() +zephyr_library_include_directories(${ZEPHYR_BASE}/drivers/usb/common/) zephyr_library_sources(uhc_common.c) +zephyr_library_sources_ifdef(CONFIG_UHC_DWC2 uhc_dwc2.c) zephyr_library_sources_ifdef(CONFIG_UHC_MAX3421E uhc_max3421e.c) zephyr_library_sources_ifdef(CONFIG_UHC_VIRTUAL uhc_virtual.c) zephyr_library_sources_ifdef(CONFIG_UHC_NXP_EHCI uhc_mcux_common.c uhc_mcux_ehci.c) diff --git a/drivers/usb/uhc/Kconfig b/drivers/usb/uhc/Kconfig index 052e9437f6ae2..dc89b9460acab 100644 --- a/drivers/usb/uhc/Kconfig +++ b/drivers/usb/uhc/Kconfig @@ -36,6 +36,7 @@ module = UHC_DRIVER module-str = uhc drv source "subsys/logging/Kconfig.template.log_config" +source "drivers/usb/uhc/Kconfig.dwc2" source "drivers/usb/uhc/Kconfig.max3421e" source "drivers/usb/uhc/Kconfig.virtual" source "drivers/usb/uhc/Kconfig.mcux" diff --git a/drivers/usb/uhc/Kconfig.dwc2 b/drivers/usb/uhc/Kconfig.dwc2 new file mode 100644 index 0000000000000..e28044329ea1f --- /dev/null +++ b/drivers/usb/uhc/Kconfig.dwc2 @@ -0,0 +1,81 @@ +# Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 + +config UHC_DWC2 + bool "UHC DWC2 USB device controller driver" + default n + depends on DT_HAS_SNPS_DWC2_ENABLED + select EVENTS + help + DWC2 USB host controller driver. + +config UHC_DWC2_DMA + bool "UHC DWC2 USB DMA support" + default n + depends on UHC_DWC2 + help + Enable Buffer DMA if DWC2 USB controller supports Internal DMA. + +config UHC_DWC2_STACK_SIZE + int "UHC DWC2 driver internal thread stack size" + depends on UHC_DWC2 + default 512 + help + DWC2 driver internal thread stack size. + +config UHC_DWC2_THREAD_PRIORITY + int "UDC DWC2 driver thread priority" + depends on UHC_DWC2 + default 8 + help + DWC2 driver thread priority. + +menu "Root port configuration" + + config UHC_DWC2_PORT_DEBOUNCE_DELAY_MS + int "Debounce delay in ms" + default 250 + help + On connection of a USB device, the USB 2.0 specification requires + a "debounce interval with a minimum duration of 100ms" to allow the connection to stabilize + (see USB 2.0 chapter 7.1.7.3 for more details). + During the debounce interval, no new connection/disconnection events are registered. + + The default value is set to 250 ms to be safe. + + config UHC_DWC2_PORT_RESET_HOLD_MS + int "Reset hold in ms" + default 30 + help + The reset signaling can be generated on any Hub or Host Controller port by request from + the USB System Software. The USB 2.0 specification requires that "the reset signaling must + be driven for a minimum of 10ms" (see USB 2.0 chapter 7.1.7.5 for more details). + After the reset, the hub port will transition to the Enabled state (refer to Section 11.5). + + The default value is set to 30 ms to be safe. + + config UHC_DWC2_PORT_RESET_RECOVERY_MS + int "Reset recovery delay in ms" + default 30 + help + After a port stops driving the reset signal, the USB 2.0 specification requires that + the "USB System Software guarantees a minimum of 10 ms for reset recovery" before the + attached device is expected to respond to data transfers (see USB 2.0 chapter 7.1.7.3 for + more details). + The device may ignore any data transfers during the recovery interval. + + The default value is set to 30 ms to be safe. + + config UHC_DWC2_PORT_SET_ADDR_DELAY_MS + int "Delay after SetAddress()" + default 10 + help + "After successful completion of the Status stage, the device is allowed a SetAddress() + recovery interval of 2 ms. At the end of this interval, the device must be able to accept + Setup packets addressed to the new address. Also, at the end of the recovery interval, the + device must not respond to tokens sent to the old address (unless, of course, the old and new + address is the same)." See USB 2.0 chapter 9.2.6.3 for more details. + + The default value is set to 10 ms to be safe. + +endmenu #Root Hub configuration diff --git a/drivers/usb/uhc/uhc_dwc2.c b/drivers/usb/uhc/uhc_dwc2.c new file mode 100644 index 0000000000000..120c59b73fa38 --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2.c @@ -0,0 +1,1697 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT snps_dwc2 + +#include "uhc_common.h" +#include "uhc_dwc2.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + +#define DEBOUNCE_DELAY_MS CONFIG_UHC_DWC2_PORT_DEBOUNCE_DELAY_MS +#define RESET_HOLD_MS CONFIG_UHC_DWC2_PORT_RESET_HOLD_MS +#define RESET_RECOVERY_MS CONFIG_UHC_DWC2_PORT_RESET_RECOVERY_MS +#define SET_ADDR_DELAY_MS CONFIG_UHC_DWC2_PORT_SET_ADDR_DELAY_MS + +#define CTRL_EP_MAX_MPS_LS 8U +#define CTRL_EP_MAX_MPS_HSFS 64U +#define UHC_DWC2_MAX_CHAN 16 + +enum uhc_dwc2_event { + /* Root port event */ + UHC_DWC2_EVENT_PORT, + /* The host port has been enabled (i.e., connected device has been reset. Send SOFs) */ + UHC_DWC2_EVENT_ENABLED, + /* The host port has been disabled (no more SOFs) */ + UHC_DWC2_EVENT_DISABLED, + /* Overcurrent detected. Port is now UHC_PORT_STATE_RECOVERY */ + UHC_DWC2_EVENT_OVERCURRENT, + /* The host port has been cleared of the overcurrent condition */ + UHC_DWC2_EVENT_OVERCURRENT_CLEAR, + /* A device has been connected to the port */ + UHC_DWC2_EVENT_CONNECTION, + /* A device disconnection has been detected */ + UHC_DWC2_EVENT_DISCONNECTION, + /* Port error detected. Port is now UHC_PORT_STATE_RECOVERY */ + UHC_DWC2_EVENT_ERROR, + /* Event on a channel 0. Use +N for channel N */ + UHC_DWC2_EVENT_CHAN0, +}; + +enum uhc_dwc2_chan_event { + /* The channel has completed execution of a transfer. Channel is now halted */ + DWC2_CHAN_EVENT_CPLT, + /* The channel has encountered an error. Channel is now halted */ + DWC2_CHAN_EVENT_ERROR, + /* A halt has been requested on the channel */ + DWC2_CHAN_EVENT_HALT_REQ, +}; + +enum uhc_dwc2_speed { + UHC_DWC2_SPEED_HIGH = 0, + UHC_DWC2_SPEED_FULL = 1, + UHC_DWC2_SPEED_LOW = 2, +}; + +enum uhc_dwc2_xfer_type { + UHC_DWC2_XFER_TYPE_CTRL = 0, + UHC_DWC2_XFER_TYPE_ISOCHRONOUS = 1, + UHC_DWC2_XFER_TYPE_BULK = 2, + UHC_DWC2_XFER_TYPE_INTR = 3, +}; + +enum uhc_port_state { + /* The port is not powered */ + UHC_PORT_STATE_NOT_POWERED, + /* The port is powered but no device is connected */ + UHC_PORT_STATE_DISCONNECTED, + /* A device is connected to the port but has not been reset. */ + /* SOF/keep alive aren't being sent */ + UHC_PORT_STATE_DISABLED, + /* The port is issuing a reset condition */ + UHC_PORT_STATE_RESETTING, + /* The port has been suspended. */ + UHC_PORT_STATE_SUSPENDED, + /* The port is issuing a resume condition */ + UHC_PORT_STATE_RESUMING, + /* The port has been enabled. SOF/keep alive are being sent */ + UHC_PORT_STATE_ENABLED, + /* Port needs to be recovered from a fatal error (error, overcurrent, or disconnection) */ + UHC_PORT_STATE_RECOVERY, +}; + +enum uhc_dwc2_ctrl_stage { + CTRL_STAGE_DATA0 = 0, + CTRL_STAGE_DATA2 = 1, + CTRL_STAGE_DATA1 = 2, + CTRL_STAGE_SETUP = 3, +}; + +struct uhc_dwc2_chan { + /* Pointer to the transfer associated with the buffer */ + struct uhc_transfer *xfer; + /* Interval in frames (FS) or microframes (HS) */ + unsigned int interval; + /* Offset in the periodic scheduler */ + uint32_t offset; + /* Type of endpoint */ + enum uhc_dwc2_xfer_type type; + atomic_t event; + /* The index of the channel */ + uint8_t chan_idx; + /* Maximum Packet Size */ + uint16_t ep_mps; + /* Endpoint address */ + uint8_t ep_addr; + /* Device Address */ + uint8_t dev_addr; + /* Stage index */ + uint8_t cur_stg; + /* New address */ + uint8_t new_addr; + /* Set address request */ + uint8_t is_setting_addr: 1; + /* Data stage is IN */ + uint8_t data_stg_in: 1; + /* Has no data stage */ + uint8_t data_stg_skip: 1; + /* High-speed flag */ + uint8_t is_hs: 1; + /* Support for Low-Speed is via a Full-Speed HUB */ + uint8_t ls_via_fs_hub: 1; + uint8_t chan_cmd_processing: 1; + /* Halt has been requested */ + uint8_t halt_requested: 1; + /* TODO: Lists of pending and done? */ + /* TODO: Add channel error? */ +}; + +struct uhc_dwc2_data { + struct k_sem irq_sem; + struct k_thread thread; + /* Main events the driver thread waits for */ + struct k_event event; + struct uhc_dwc2_chan chan[UHC_DWC2_MAX_CHAN]; + /* Data, that is used in multiple threads */ + enum uhc_port_state port_state; + /* FIFO */ + uint16_t fifo_top; + uint16_t fifo_nptxfsiz; + uint16_t fifo_rxfsiz; + uint16_t fifo_ptxfsiz; + /* Debounce lock */ + uint8_t debouncing: 1; + /* TODO: Port context and callback? */ + /* TODO: FRAME LIST? */ + /* TODO: Pipes/channels LIST? */ + /* TODO: spinlock? */ +}; + +#define UHC_DWC2_CHAN_REG(base, chan_idx) \ + ((struct usb_dwc2_host_chan *)(((mem_addr_t)(base)) + 0x500UL + ((chan_idx) * 0x20UL))) + +K_THREAD_STACK_DEFINE(uhc_dwc2_stack, CONFIG_UHC_DWC2_STACK_SIZE); + +/* + * DWC2 low-level HAL Functions, + * + * Never use device structs or other driver config/data structs, but only the registers, + * directly provided as arguments. + */ + +static void dwc2_hal_flush_rx_fifo(struct usb_dwc2_reg *const dwc2) +{ + sys_write32(USB_DWC2_GRSTCTL_RXFFLSH, (mem_addr_t)&dwc2->grstctl); + + while (sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_RXFFLSH) { + continue; + } +} + +static void dwc2_hal_flush_tx_fifo(struct usb_dwc2_reg *const dwc2, const uint8_t fnum) +{ + uint32_t grstctl; + + grstctl = usb_dwc2_set_grstctl_txfnum(fnum) | USB_DWC2_GRSTCTL_TXFFLSH; + sys_write32(grstctl, (mem_addr_t)&dwc2->grstctl); + + while (sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_TXFFLSH) { + continue; + } +} + +static inline void dwc2_hal_set_frame_list(struct usb_dwc2_reg *const dwc2, void *const frame_list) +{ + LOG_WRN("Setting frame list not implemented yet"); +} + +static inline void dwc2_hal_periodic_enable(struct usb_dwc2_reg *const dwc2) +{ + LOG_WRN("Enabling periodic scheduling not implemented yet"); +} + +static inline void dwc2_hal_port_init(struct usb_dwc2_reg *const dwc2) +{ + sys_clear_bits((mem_addr_t)&dwc2->haintmsk, 0xFFFFFFFFUL); + sys_set_bits((mem_addr_t)&dwc2->gintmsk, USB_DWC2_GINTSTS_PRTINT | USB_DWC2_GINTSTS_HCHINT); +} + +#define USB_DWC2_HPRT_W1C_MSK \ + (USB_DWC2_HPRT_PRTENA | USB_DWC2_HPRT_PRTCONNDET | USB_DWC2_HPRT_PRTENCHNG | \ + USB_DWC2_HPRT_PRTOVRCURRCHNG) + +static inline void dwc2_hal_toggle_reset(struct usb_dwc2_reg *const dwc2, const bool reset_on) +{ + uint32_t hprt; + + hprt = sys_read32((mem_addr_t)&dwc2->hprt); + if (reset_on) { + hprt |= USB_DWC2_HPRT_PRTRST; + } else { + hprt &= ~USB_DWC2_HPRT_PRTRST; + } + sys_write32(hprt & (~USB_DWC2_HPRT_W1C_MSK), (mem_addr_t)&dwc2->hprt); +} + +static inline void dwc2_hal_toggle_power(struct usb_dwc2_reg *const dwc2, const bool power_on) +{ + uint32_t hprt; + + hprt = sys_read32((mem_addr_t)&dwc2->hprt); + if (power_on) { + hprt |= USB_DWC2_HPRT_PRTPWR; + } else { + hprt &= ~USB_DWC2_HPRT_PRTPWR; + } + sys_write32(hprt & (~USB_DWC2_HPRT_W1C_MSK), (mem_addr_t)&dwc2->hprt); +} + +static int dwc2_hal_core_reset(struct usb_dwc2_reg *const dwc2, const k_timeout_t timeout) +{ + const k_timepoint_t timepoint = sys_timepoint_calc(timeout); + + /* Check AHB master idle state */ + while ((sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_AHBIDLE) == 0) { + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for AHB idle timeout, GRSTCTL 0x%08x", + sys_read32((mem_addr_t)&dwc2->grstctl)); + return -EIO; + } + + k_busy_wait(1); + } + + /* Apply Core Soft Reset */ + sys_write32(USB_DWC2_GRSTCTL_CSFTRST, (mem_addr_t)&dwc2->grstctl); + + /* Wait for reset to complete */ + while ((sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_CSFTRST) != 0 && + (sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_CSFTRSTDONE) == 0) { + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for CSR done timeout, GRSTCTL 0x%08x", + sys_read32((mem_addr_t)&dwc2->grstctl)); + return -EIO; + } + + k_busy_wait(1); + } + + /* CSFTRSTDONE is W1C so the write must have the bit set to clear it */ + sys_clear_bits((mem_addr_t)&dwc2->grstctl, USB_DWC2_GRSTCTL_CSFTRST); + + LOG_DBG("DWC2 core reset done"); + + return 0; +} + +static inline enum uhc_dwc2_speed dwc2_hal_get_port_speed(struct usb_dwc2_reg *const dwc2) +{ + uint32_t hprt; + + hprt = sys_read32((mem_addr_t)&dwc2->hprt); + return (hprt & USB_DWC2_HPRT_PRTSPD_MASK) >> USB_DWC2_HPRT_PRTSPD_POS; +} + +/* + * DWC2 FIFO Management + * + * Programming Guide 2.1.2 FIFO RAM allocation + * + * RX: + * - Largest-EPsize/4 + 2 (status info). recommended x2 if high bandwidth or multiple ISO are used. + * - 2 for transfer complete and channel halted status + * - 1 for each Control/Bulk out endpoint to Handle NAK/NYET (i.e max is number of host channel) + * + * TX non-periodic (NPTX): + * - At least largest-EPsize/4, recommended x2 + * + * TX periodic (PTX): + * - At least largest-EPsize*MulCount/4 (MulCount up to 3 for high-bandwidth ISO/interrupt) + */ + +enum { + EPSIZE_BULK_LS = 64, + EPSIZE_BULK_FS = 64, + EPSIZE_BULK_HS = 512, + + EPSIZE_ISO_FS_MAX = 1023, + EPSIZE_ISO_HS_MAX = 1024, +}; + +static inline void uhc_dwc2_config_fifo_fixed_dma(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct usb_dwc2_reg *const dwc2 = config->base; + const enum uhc_dwc2_speed speed = dwc2_hal_get_port_speed(dwc2); + uint32_t nptx_largest; + const uint32_t ptx_largest = 256 / 4; + + LOG_DBG("Configuring FIFO sizes"); + + switch (speed) { + case UHC_DWC2_SPEED_LOW: + nptx_largest = EPSIZE_BULK_LS / 4; + break; + case UHC_DWC2_SPEED_FULL: + nptx_largest = EPSIZE_BULK_FS / 4; + break; + case UHC_DWC2_SPEED_HIGH: + nptx_largest = EPSIZE_BULK_HS / 4; + break; + default: + CODE_UNREACHABLE; + } + + priv->fifo_top = FIELD_GET(USB_DWC2_GHWCFG3_DFIFODEPTH_MASK, config->ghwcfg3) - + (FIELD_GET(USB_DWC2_GHWCFG2_NUMHSTCHNL_MASK, config->ghwcfg2) + 1); + priv->fifo_nptxfsiz = 2 * nptx_largest; + priv->fifo_rxfsiz = 2 * (ptx_largest + 2) + + (FIELD_GET(USB_DWC2_GHWCFG2_NUMHSTCHNL_MASK, config->ghwcfg2) + 1); + priv->fifo_ptxfsiz = priv->fifo_top - (priv->fifo_nptxfsiz + priv->fifo_rxfsiz); + + /* TODO: verify ptxfsiz is overflowed */ + + LOG_DBG("FIFO sizes: top=%u, nptx=%u, rx=%u, ptx=%u", priv->fifo_top * 4, + priv->fifo_nptxfsiz * 4, priv->fifo_rxfsiz * 4, priv->fifo_ptxfsiz * 4); +} + +static inline void dwc2_apply_fifo_config(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + uint16_t fifo_available = priv->fifo_top; + + sys_write32(fifo_available << USB_DWC2_GDFIFOCFG_EPINFOBASEADDR_POS | fifo_available, + (mem_addr_t)&dwc2->gdfifocfg); + + fifo_available -= priv->fifo_rxfsiz; + + sys_write32(priv->fifo_rxfsiz << USB_DWC2_GRXFSIZ_RXFDEP_POS, (mem_addr_t)&dwc2->grxfsiz); + + fifo_available -= priv->fifo_nptxfsiz; + + sys_write32(priv->fifo_nptxfsiz << USB_DWC2_GNPTXFSIZ_NPTXFDEP_POS | fifo_available, + (mem_addr_t)&dwc2->gnptxfsiz); + + fifo_available -= priv->fifo_ptxfsiz; + + sys_write32(priv->fifo_ptxfsiz << USB_DWC2_HPTXFSIZ_PTXFSIZE_POS | fifo_available, + (mem_addr_t)&dwc2->hptxfsiz); + + dwc2_hal_flush_tx_fifo(dwc2, 0x10UL); + dwc2_hal_flush_rx_fifo(dwc2); + + LOG_DBG("FIFO configuration applied nptx=%u, rx=%u, ptx=%u", + priv->fifo_nptxfsiz * 4, priv->fifo_rxfsiz * 4, priv->fifo_ptxfsiz * 4); +} + +/* + * DWC2 Port Management + * + * Operation of the USB port and handling if events related to it, and helpers to query information + * about their speed, occupancy, status... + */ + +/* Host Port Control and Status Register */ +#define USB_DWC2_HPRT_PRTENCHNG BIT(3) +#define USB_DWC2_HPRT_PRTOVRCURRCHNG BIT(5) +#define USB_DWC2_HPRT_PRTCONNDET BIT(1) + +#define CORE_INTRS_EN_MSK (USB_DWC2_GINTSTS_DISCONNINT) + +/* Interrupts that pertain to core events */ +#define CORE_EVENTS_INTRS_MSK (USB_DWC2_GINTSTS_DISCONNINT | USB_DWC2_GINTSTS_HCHINT) + +/* Interrupt that pertain to host port events */ +#define PORT_EVENTS_INTRS_MSK \ + (USB_DWC2_HPRT_PRTCONNDET | USB_DWC2_HPRT_PRTENCHNG | USB_DWC2_HPRT_PRTOVRCURRCHNG) + +static void uhc_dwc2_debounce_enable(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + + /* Disable Connection and disconnection interrupts to prevent spurious events */ + sys_clear_bits((mem_addr_t)&dwc2->gintmsk, + USB_DWC2_GINTSTS_PRTINT | USB_DWC2_GINTSTS_DISCONNINT); + priv->debouncing = 1; +} + +static inline void uhc_dwc2_debounce_disable(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + + priv->debouncing = 0; + /* Clear Connection and disconnection interrupt in case it triggered again */ + sys_set_bits((mem_addr_t)&dwc2->gintsts, USB_DWC2_GINTSTS_DISCONNINT); + /* Clear the PRTCONNDET interrupt by writing 1 to the corresponding bit (W1C logic) */ + sys_set_bits((mem_addr_t)&dwc2->hprt, USB_DWC2_HPRT_PRTCONNDET); + /* Re-enable the HPRT (connection) and disconnection interrupts */ + sys_set_bits((mem_addr_t)&dwc2->gintmsk, + USB_DWC2_GINTSTS_PRTINT | USB_DWC2_GINTSTS_DISCONNINT); +} + +static inline void uhc_dwc2_port_debounce(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct usb_dwc2_reg *const dwc2 = config->base; + + k_msleep(DEBOUNCE_DELAY_MS); + + /* Check the post-debounce state (i.e., whether it's actually connected/disconnected) */ + if ((sys_read32((mem_addr_t)&dwc2->hprt) & USB_DWC2_HPRT_PRTCONNSTS) != 0) { + priv->port_state = UHC_PORT_STATE_DISABLED; + } else { + priv->port_state = UHC_PORT_STATE_DISCONNECTED; + } +} + +static inline void uhc_dwc2_init_hfir(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const enum uhc_dwc2_speed speed = dwc2_hal_get_port_speed(dwc2); + uint32_t hfir; + + hfir = sys_read32((mem_addr_t)&dwc2->hfir); + + /* Disable dynamic loading */ + hfir &= ~USB_DWC2_HFIR_HFIRRLDCTRL; + + /* Disable dynamic loading */ + hfir &= ~USB_DWC2_HFIR_HFIRRLDCTRL; + /* Set frame interval to be equal to 1ms + * Note: FSLS PHY has an implicit 8 divider applied when in LS mode, + * so the values of FSLSPclkSel and FrInt have to be adjusted accordingly. + */ + hfir &= ~USB_DWC2_HFIR_FRINT_MASK; + switch (speed) { + case UHC_DWC2_SPEED_LOW: + hfir |= (6 * 1000) << USB_DWC2_HFIR_FRINT_POS; + break; + case UHC_DWC2_SPEED_FULL: + hfir |= (48 * 1000) << USB_DWC2_HFIR_FRINT_POS; + break; + case UHC_DWC2_SPEED_HIGH: + hfir |= (60 * 125) << USB_DWC2_HFIR_FRINT_POS; + break; + } + + sys_write32(hfir, (mem_addr_t)&dwc2->hfir); +} + +static int uhc_dwc2_power_on(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct usb_dwc2_reg *const dwc2 = config->base; + + /* Port can only be powered on if it's currently unpowered */ + if (priv->port_state == UHC_PORT_STATE_NOT_POWERED) { + priv->port_state = UHC_PORT_STATE_DISCONNECTED; + dwc2_hal_port_init(dwc2); + dwc2_hal_toggle_power(dwc2, true); + return 0; + } + + return -EINVAL; +} + +static inline int uhc_dwc2_port_reset(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct usb_dwc2_reg *const dwc2 = config->base; + int ret; + + /* TODO: implement port checks */ + + /* Hint: + * Port can only a reset when it is in the enabled or disabled (in the case of a new + * connection) states. priv->port_state == UHC_PORT_STATE_ENABLED; + * priv->port_state == UHC_PORT_STATE_DISABLED; + */ + + /* Proceed to resetting the bus: + * - Update the port's state variable + * - Hold the bus in the reset state for RESET_HOLD_MS. + * - Return the bus to the idle state for RESET_RECOVERY_MS + * During this reset the port state should be set to RESETTING and do not change. + */ + priv->port_state = UHC_PORT_STATE_RESETTING; + dwc2_hal_toggle_reset(dwc2, true); + + /* Hold the bus in the reset state */ + k_msleep(RESET_HOLD_MS); + + if (priv->port_state != UHC_PORT_STATE_RESETTING) { + /* The port state has unexpectedly changed */ + LOG_ERR("Port state changed during reset"); + ret = -EIO; + goto bailout; + } + + /* Return the bus to the idle state. Port enabled event should occur */ + dwc2_hal_toggle_reset(dwc2, false); + + /* Give the port time to recover */ + k_msleep(RESET_RECOVERY_MS); + + if (priv->port_state != UHC_PORT_STATE_RESETTING) { + /* The port state has unexpectedly changed */ + LOG_ERR("Port state changed during reset"); + ret = -EIO; + goto bailout; + } + + ret = 0; +bailout: + /* TODO: For each chan, reinitialize the channel with EP characteristics */ + /* TODO: Sync CACHE */ + return ret; +} + +static int uhc_dwc2_init(const struct device *const dev); + +/* + * Port recovery is necessary when the port is in an error state and needs to be reset. + */ +static inline int uhc_dwc2_port_recovery(const struct device *const dev) +{ + int ret; + + /* TODO: Implement port checks */ + + /* Port should be in recovery state and no ongoing transfers + * Port flags should be 0 + */ + + ret = uhc_dwc2_quirk_irq_disable_func(dev); + if (ret) { + LOG_ERR("Quirk IRQ disable failed %d", ret); + return ret; + } + + /* Init controller */ + ret = uhc_dwc2_init(dev); + if (ret) { + LOG_ERR("Failed to init controller: %d", ret); + return ret; + } + + ret = uhc_dwc2_quirk_irq_enable_func(dev); + if (ret) { + LOG_ERR("Quirk IRQ enable failed %d", ret); + return ret; + } + + ret = uhc_dwc2_power_on(dev); + if (ret) { + LOG_ERR("Failed to power on root port: %d", ret); + return ret; + } + + return ret; +} + +/* + * Buffer management + * + * Functions handling the operation of buffers: loading-unloading of the data, filling the + * content, allocate and pass them between USB stack transfers and USB hardware. + */ + +static inline bool uhc_dwc2_buffer_is_done(struct uhc_dwc2_chan *const chan) +{ + /* Only control transfers need to be continued */ + if (chan->type != UHC_DWC2_XFER_TYPE_CTRL) { + return true; + } + + return (chan->cur_stg == 2); +} + +static inline void uhc_dwc2_buffer_fill_ctrl(struct uhc_dwc2_chan *const chan, + struct uhc_transfer *const xfer) +{ + const struct usb_setup_packet *const setup_pkt = (void *)xfer->setup_pkt; + + chan->cur_stg = 0; + chan->data_stg_in = usb_reqtype_is_to_host(setup_pkt); + chan->data_stg_skip = (setup_pkt->wLength == 0); + chan->is_setting_addr = 0; + + if (setup_pkt->bRequest == USB_SREQ_SET_ADDRESS) { + chan->is_setting_addr = 1; + chan->new_addr = setup_pkt->wValue & 0x7F; + LOG_DBG("Set address request, new address %d", chan->new_addr); + } + + LOG_DBG("data_stg_in: %d, data_stg_skip: %d", chan->data_stg_in, chan->data_stg_skip); + + /* Save the xfer pointer in the buffer */ + chan->xfer = xfer; + + /* TODO Sync data from cache to memory. For OUT and CTRL transfers */ +} + +static inline uint16_t calc_packet_count(const uint16_t size, const uint8_t mps) +{ + if (size == 0) { + return 1; /* in Buffer DMA mode Zero Length Packet still counts as 1 packet */ + } else { + return DIV_ROUND_UP(size, mps); + } +} + +static void uhc_dwc2_buffer_exec_proceed(const struct device *const dev, + struct uhc_dwc2_chan *const chan) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_transfer *const xfer = chan->xfer; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + bool next_dir_is_in; + enum uhc_dwc2_ctrl_stage next_pid; + uint16_t size = 0; + uint8_t *dma_addr = NULL; + uint16_t pkt_cnt; + uint32_t hctsiz; + uint32_t hcchar; + + __ASSERT(xfer != NULL, "No transfer assigned to buffer"); + __ASSERT(chan->cur_stg != 2, "Invalid control stage: %d", chan->cur_stg); + + if (chan->cur_stg == 0) { /* Just finished control stage */ + if (chan->data_stg_skip) { + /* No data stage. Go straight to status stage */ + next_dir_is_in = true; /* With no data stage, status stage must be IN */ + next_pid = CTRL_STAGE_DATA1; /* Status stage always has a PID of DATA1 */ + chan->cur_stg = 2; /* Skip over */ + } else { + /* Go to data stage */ + next_dir_is_in = chan->data_stg_in; + /* Data stage always starts with a PID of DATA1 */ + next_pid = CTRL_STAGE_DATA1; + chan->cur_stg = 1; + + /* NOTE: + * For OUT - number of bytes host sends to device + * For IN - number of bytes host reserves to receive + */ + size = xfer->buf->size; + + /* TODO: Toggle PID? */ + + /* TODO: Check if the buffer is large enough for the next transfer? */ + + /* TODO: Check that the buffer is DMA and CACHE aligned and compatible with + * the DMA (better to do this on enqueue) + */ + + if (xfer->buf != NULL) { + /* Get the tail of the buffer to append data */ + dma_addr = net_buf_tail(xfer->buf); + /* TODO: Ensure the buffer has enough space? */ + net_buf_add(xfer->buf, size); + } + } + } else { + /* cur_stg == 1. Just finished data stage. Go to status stage + * Status stage is always the opposite direction of data stage + */ + next_dir_is_in = !chan->data_stg_in; + next_pid = CTRL_STAGE_DATA1; /* Status stage always has a PID of DATA1 */ + chan->cur_stg = 2; + } + + /* Calculate new packet count */ + pkt_cnt = calc_packet_count(size, chan->ep_mps); + + if (next_dir_is_in) { + sys_set_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); + } else { + sys_clear_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); + } + + hctsiz = (next_pid << USB_DWC2_HCTSIZ_PID_POS) & USB_DWC2_HCTSIZ_PID_MASK; + hctsiz |= (pkt_cnt << USB_DWC2_HCTSIZ_PKTCNT_POS) & USB_DWC2_HCTSIZ_PKTCNT_MASK; + hctsiz |= (size << USB_DWC2_HCTSIZ_XFERSIZE_POS) & USB_DWC2_HCTSIZ_XFERSIZE_MASK; + sys_write32(hctsiz, (mem_addr_t)&chan_regs->hctsiz); + + sys_write32((uint32_t)dma_addr, (mem_addr_t)&chan_regs->hcdma); + + /* TODO: Configure split transaction if needed */ + + /* TODO: sync CACHE */ + hcchar = sys_read32((mem_addr_t)&chan_regs->hcchar); + hcchar |= USB_DWC2_HCCHAR0_CHENA; + hcchar &= ~USB_DWC2_HCCHAR0_CHDIS; + sys_write32(hcchar, (mem_addr_t)&chan_regs->hcchar); +} + +static void uhc_dwc2_buffer_exec(const struct device *const dev, struct uhc_dwc2_chan *const chan) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_transfer *const xfer = (struct uhc_transfer *)chan->xfer; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + uint16_t pkt_cnt; + uint16_t size; + int next_pid; + uint32_t hctsiz; + uint32_t hcint; + uint32_t hcchar; + + LOG_DBG("ep=%02X, mps=%d", xfer->ep, chan->ep_mps); + + if (USB_EP_GET_IDX(xfer->ep) == 0) { + /* Control stage is always OUT */ + sys_clear_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); + } + + if (xfer->interval != 0) { + LOG_ERR("Periodic transfer is not supported"); + } + + pkt_cnt = calc_packet_count(sizeof(struct usb_setup_packet), chan->ep_mps); + next_pid = CTRL_STAGE_SETUP; + size = sizeof(struct usb_setup_packet); + + hctsiz = (next_pid << USB_DWC2_HCTSIZ_PID_POS) & USB_DWC2_HCTSIZ_PID_MASK; + hctsiz |= (pkt_cnt << USB_DWC2_HCTSIZ_PKTCNT_POS) & USB_DWC2_HCTSIZ_PKTCNT_MASK; + hctsiz |= (size << USB_DWC2_HCTSIZ_XFERSIZE_POS) & USB_DWC2_HCTSIZ_XFERSIZE_MASK; + sys_write32(hctsiz, (mem_addr_t)&chan_regs->hctsiz); + + sys_write32((uint32_t)xfer->setup_pkt, (mem_addr_t)&chan_regs->hcdma); + + /* TODO: Configure split transaction if needed */ + + hcint = sys_read32((mem_addr_t)&chan_regs->hcint); + sys_write32(hcint, (mem_addr_t)&chan_regs->hcint); + + /* TODO: sync CACHE */ + hcchar = sys_read32((mem_addr_t)&chan_regs->hcchar); + hcchar |= USB_DWC2_HCCHAR0_CHENA; + hcchar &= ~USB_DWC2_HCCHAR0_CHDIS; + sys_write32(hcchar, (mem_addr_t)&chan_regs->hcchar); +} + +/* + * Interrupt handler (ISR) + * + * Handle the interrupts being dispatched into events, as well as some immediate handling of + * events directly from the IRQ handler. + */ + +static void uhc_dwc2_isr_chan_handler(const struct device *const dev, + struct uhc_dwc2_chan *const chan) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + uint32_t chan_event = 0; + uint32_t hcint; + + /* Clear the interrupt bits by writing them back */ + hcint = sys_read32((mem_addr_t)&chan_regs->hcint); + sys_write32(hcint, (mem_addr_t)&chan_regs->hcint); + + /* Note: + * Do not change order of checks as some events take precedence over others. + * Errors > Channel Halt Request > Transfer completed + */ + if (hcint & (USB_DWC2_HCINT_STALL | USB_DWC2_HCINT_BBLERR | USB_DWC2_HCINT_XACTERR)) { + __ASSERT(hcint & USB_DWC2_HCINT_CHHLTD, + "Channel error without channel halted interrupt"); + + LOG_ERR("Channel %d error: 0x%08x", chan->chan_idx, hcint); + /* TODO: Store the error in hal context */ + chan_event |= BIT(DWC2_CHAN_EVENT_ERROR); + + } else if (hcint & USB_DWC2_HCINT_CHHLTD) { + if (chan->halt_requested) { + chan->halt_requested = 0; + chan_event |= BIT(DWC2_CHAN_EVENT_HALT_REQ); + } else if (uhc_dwc2_buffer_is_done(chan)) { + chan_event |= BIT(DWC2_CHAN_EVENT_CPLT); + } else { + uhc_dwc2_buffer_exec_proceed(dev, chan); + } + + } else if (hcint & USB_DWC2_HCINT_XFERCOMPL) { + /* Note: + * The channel isn't halted yet, so we need to halt it manually to stop the + * execution of the next packet. Relevant only for Scatter-Gather DMA and never + * occurs oin Buffer DMA. + */ + sys_set_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_CHDIS); + + /* + * After setting the halt bit, this will generate another channel halted interrupt. + * We treat this interrupt as no event, then cycle back with the channel halted + * interrupt to handle the CPLT event. + */ + } else { + __ASSERT(false, "Unknown channel interrupt, HCINT=%08Xh", hcint); + } + + if (chan_event != 0) { + atomic_or(&chan->event, chan_event); + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_CHAN0 + chan->chan_idx)); + } +} + +static void uhc_dwc2_isr_handler(const struct device *const dev) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + uint32_t core_intrs; + uint32_t port_intrs = 0; + uint32_t channels = 0; + + /* Read and clear core interrupt status */ + core_intrs = sys_read32((mem_addr_t)&dwc2->gintsts); + sys_write32(core_intrs, (mem_addr_t)&dwc2->gintsts); + + LOG_DBG("GINTSTS=%08Xh, HPRT=%08Xh", core_intrs, port_intrs); + + if (core_intrs & USB_DWC2_GINTSTS_PRTINT) { + port_intrs = sys_read32((mem_addr_t)&dwc2->hprt); + /* Clear the interrupt status by writing 1 to the W1C bits, except the PRTENA bit */ + sys_write32(port_intrs & ~USB_DWC2_HPRT_PRTENA, (mem_addr_t)&dwc2->hprt); + } + + /* Disconnection takes precedense over connection */ + if (core_intrs & USB_DWC2_GINTSTS_DISCONNINT) { + /* Disconnect event */ + uhc_dwc2_debounce_enable(dev); + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_DISCONNECTION)); + /* Port still connected, check port event */ + } else if (port_intrs & USB_DWC2_HPRT_PRTCONNDET && !priv->debouncing) { + uhc_dwc2_debounce_enable(dev); + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_CONNECTION)); + } else { + /* Nothing */ + } + + if (core_intrs & USB_DWC2_GINTSTS_HCHINT) { + /* One or more channels have pending interrupts. Store the mask of those channels */ + channels = sys_read32((mem_addr_t)&dwc2->haint); + for (uint8_t i; channels != 0; channels &= ~BIT(i)) { + i = __builtin_ffs(channels) - 1; + uhc_dwc2_isr_chan_handler(dev, &priv->chan[i]); + } + } + + if (port_intrs & USB_DWC2_HPRT_PRTOVRCURRCHNG) { + /* Check if this is an overcurrent or an overcurrent cleared */ + if (port_intrs & USB_DWC2_HPRT_PRTOVRCURRACT) { + /* TODO: Verify handling logic during overcurrent */ + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_OVERCURRENT)); + } else { + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_OVERCURRENT_CLEAR)); + } + } + + if (port_intrs & USB_DWC2_HPRT_PRTENCHNG) { + if (port_intrs & USB_DWC2_HPRT_PRTENA) { + /* Host port was enabled */ + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_ENABLED)); + } else { + /* Host port has been disabled */ + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_DISABLED)); + } + } + + (void)uhc_dwc2_quirk_irq_clear(dev); +} + +/* + * Initialization sequence + * + * Configure registers as described by the programmer manual. + */ + +static inline void uhc_dwc2_init_gusbcfg(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + uint32_t gusbcfg; + + /* Init PHY based on the speed */ + if (FIELD_GET(USB_DWC2_GHWCFG2_HSPHYTYPE_MASK, config->ghwcfg2) != + USB_DWC2_GHWCFG2_HSPHYTYPE_NO_HS) { + gusbcfg = sys_read32((mem_addr_t)&dwc2->gusbcfg); + + /* De-select FS PHY */ + gusbcfg &= ~USB_DWC2_GUSBCFG_PHYSEL_USB11; + + if (FIELD_GET(USB_DWC2_GHWCFG2_HSPHYTYPE_MASK, config->ghwcfg2) == + USB_DWC2_GHWCFG2_HSPHYTYPE_ULPI) { + LOG_WRN("Highspeed ULPI PHY init"); + /* Select ULPI PHY (external) */ + gusbcfg |= USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_ULPI; + /* ULPI is always 8-bit interface */ + gusbcfg &= ~USB_DWC2_GUSBCFG_PHYIF_16_BIT; + /* ULPI select single data rate */ + gusbcfg &= ~USB_DWC2_GUSBCFG_DDR_DOUBLE; + /* Default internal VBUS Indicator and Drive */ + gusbcfg &= ~(USB_DWC2_GUSBCFG_ULPIEVBUSD | USB_DWC2_GUSBCFG_ULPIEVBUSI); + /* Disable FS/LS ULPI and Supend mode */ + gusbcfg &= ~(USB_DWC2_GUSBCFG_ULPIFSLS | USB_DWC2_GUSBCFG_ULPICLK_SUSM); + } else { + LOG_WRN("Highspeed UTMI+ PHY init"); + /* Select UTMI+ PHY (internal) */ + gusbcfg &= ~USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_ULPI; + /* Set 16-bit interface if supported */ + if (FIELD_GET(USB_DWC2_GHWCFG4_PHYDATAWIDTH_MASK, config->ghwcfg4) > 0) { + gusbcfg |= USB_DWC2_GUSBCFG_PHYIF_16_BIT; + } else { + gusbcfg &= ~USB_DWC2_GUSBCFG_PHYIF_16_BIT; + } + } + sys_write32(gusbcfg, (mem_addr_t)&dwc2->gusbcfg); + } else { + sys_set_bits((mem_addr_t)&dwc2->gusbcfg, USB_DWC2_GUSBCFG_PHYSEL_USB11); + } +} + +static inline void uhc_dwc2_init_gahbcfg(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + uint32_t core_intrs; + uint32_t gahbcfg; + + /* Disable Global Interrupt */ + sys_clear_bits((mem_addr_t)&dwc2->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK); + + /* Enable Host mode */ + sys_set_bits((mem_addr_t)&dwc2->gusbcfg, USB_DWC2_GUSBCFG_FORCEHSTMODE); + /* Wait until core is in host mode */ + while ((sys_read32((mem_addr_t)&dwc2->gintsts) & USB_DWC2_GINTSTS_CURMOD) != 1) { + continue; + } + + /* TODO: Set AHB burst mode for some ECO only for ESP32S2 */ + /* Make config quirk? */ + + /* TODO: Disable HNP and SRP capabilities */ + /* Also move to quirk? */ + + sys_clear_bits((mem_addr_t)&dwc2->gintmsk, 0xFFFFFFFFUL); + + sys_set_bits((mem_addr_t)&dwc2->gintmsk, CORE_INTRS_EN_MSK); + + /* Clear status */ + core_intrs = sys_read32((mem_addr_t)&dwc2->gintsts); + sys_write32(core_intrs, (mem_addr_t)&dwc2->gintsts); + + /* Configure AHB */ + gahbcfg = sys_read32((mem_addr_t)&dwc2->gahbcfg); + gahbcfg |= USB_DWC2_GAHBCFG_NPTXFEMPLVL; + gahbcfg &= ~USB_DWC2_GAHBCFG_HBSTLEN_MASK; + gahbcfg |= (USB_DWC2_GAHBCFG_HBSTLEN_INCR16 << USB_DWC2_GAHBCFG_HBSTLEN_POS); + sys_write32(gahbcfg, (mem_addr_t)&dwc2->gahbcfg); + + if (FIELD_GET(USB_DWC2_GHWCFG2_OTGARCH_MASK, config->ghwcfg2) == + USB_DWC2_GHWCFG2_OTGARCH_INTERNALDMA) { + sys_set_bits((mem_addr_t)&dwc2->gahbcfg, USB_DWC2_GAHBCFG_DMAEN); + } + + /* Enable Global Interrupt */ + sys_set_bits((mem_addr_t)&dwc2->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK); +} + +static void uhc_dwc2_init_hcfg(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const enum uhc_dwc2_speed speed = dwc2_hal_get_port_speed(dwc2); + uint32_t hcfg; + + hcfg = sys_read32((mem_addr_t)&dwc2->hcfg); + + /* We can select Buffer DMA of Scatter-Gather DMA mode here: Buffer DMA by default */ + hcfg &= ~USB_DWC2_HCFG_DESCDMA; + + /* Disable periodic scheduling, will enable later */ + hcfg &= ~USB_DWC2_HCFG_PERSCHEDENA; + + if (FIELD_GET(USB_DWC2_GHWCFG2_HSPHYTYPE_MASK, config->ghwcfg2) == + USB_DWC2_GHWCFG2_HSPHYTYPE_NO_HS) { + /* Disable HighSpeed support */ + hcfg |= USB_DWC2_HCFG_FSLSSUPP; + } else { + /* Enable HighSpeed support */ + hcfg &= ~USB_DWC2_HCFG_FSLSSUPP; + } + + /* Indicate to the OTG core what speed the PHY clock is at + * Note: FSLS PHY has an implicit 8 divider applied when in LS mode, + * so the values of FSLSPclkSel and FrInt have to be adjusted accordingly. + */ + switch (speed) { + case UHC_DWC2_SPEED_LOW: + hcfg &= ~USB_DWC2_HCFG_FSLSPCLKSEL_MASK; + hcfg |= 2 << USB_DWC2_HCFG_FSLSPCLKSEL_POS; + break; + case UHC_DWC2_SPEED_FULL: + hcfg &= ~USB_DWC2_HCFG_FSLSPCLKSEL_MASK; + hcfg |= 1 << USB_DWC2_HCFG_FSLSPCLKSEL_POS; + break; + case UHC_DWC2_SPEED_HIGH: + /* Leave to default value */ + break; + } + + sys_write32(hcfg, (mem_addr_t)&dwc2->hcfg); +} + +/* + * Submit a new device connected event to the higher logic. + */ +static inline void uhc_dwc2_submit_new_device(const struct device *const dev, + const enum uhc_dwc2_speed speed) +{ + switch (speed) { + case UHC_DWC2_SPEED_LOW: + LOG_INF("New Low-Speed device"); + uhc_submit_event(dev, UHC_EVT_DEV_CONNECTED_LS, 0); + break; + case UHC_DWC2_SPEED_FULL: + LOG_INF("New Full-Speed device"); + uhc_submit_event(dev, UHC_EVT_DEV_CONNECTED_FS, 0); + break; + case UHC_DWC2_SPEED_HIGH: + LOG_INF("New High-Speed device"); + uhc_submit_event(dev, UHC_EVT_DEV_CONNECTED_HS, 0); + break; + default: + LOG_ERR("Unsupported speed %d", speed); + } +} + +/* + * Submit a device gone event to the higher logic. + */ +static inline void uhc_dwc2_submit_dev_gone(const struct device *const dev) +{ + LOG_WRN("Dev gone"); + uhc_submit_event(dev, UHC_EVT_DEV_REMOVED, 0); +} + +/* + * Allocate a chan holding the underlying channel object and the DMA buffer for transfer purposes. + */ +static inline void uhc_dwc2_chan_config(const struct device *const dev, + const uint8_t chan_idx, + const uint8_t ep_addr, + const uint8_t dev_addr, + const enum uhc_dwc2_speed dev_speed, + const enum uhc_dwc2_xfer_type type) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct uhc_dwc2_chan *const chan = &priv->chan[0]; + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan_idx); + uint32_t hcchar; + uint32_t hcint; + + /* TODO: Double buffering scheme? */ + + /* Set the default chan's MPS to the worst case MPS for the device's speed */ + chan->ep_mps = + (dev_speed == UHC_DWC2_SPEED_LOW) ? CTRL_EP_MAX_MPS_LS : CTRL_EP_MAX_MPS_HSFS; + chan->type = type; + chan->ep_addr = ep_addr; + chan->chan_idx = chan_idx; + chan->dev_addr = dev_addr; + chan->ls_via_fs_hub = 0; + chan->interval = 0; + + LOG_DBG("Allocating channel %d", chan->chan_idx); + + /* Init underlying channel registers */ + + /* Clear the interrupt bits by writing them back */ + hcint = sys_read32((mem_addr_t)&chan_regs->hcint); + sys_write32(hcint, (mem_addr_t)&chan_regs->hcint); + + /* Enable channel interrupts in the core */ + sys_set_bits((mem_addr_t)&dwc2->haintmsk, (1 << chan->chan_idx)); + + /* Enable transfer complete and channel halted interrupts */ + sys_set_bits((mem_addr_t)&chan_regs->hcintmsk, + USB_DWC2_HCINT_XFERCOMPL | USB_DWC2_HCINT_CHHLTD); + + hcchar = ((uint32_t)chan->ep_mps << USB_DWC2_HCCHAR0_MPS_POS); + hcchar |= ((uint32_t)USB_EP_GET_IDX(chan->ep_addr) << USB_DWC2_HCCHAR0_EPNUM_POS); + hcchar |= ((uint32_t)chan->type << USB_DWC2_HCCHAR0_EPTYPE_POS); + hcchar |= ((uint32_t)1UL /* TODO: chan->mult */ << USB_DWC2_HCCHAR0_EC_POS); + hcchar |= ((uint32_t)chan->dev_addr << USB_DWC2_HCCHAR0_DEVADDR_POS); + + if (USB_EP_DIR_IS_IN(chan->ep_addr)) { + hcchar |= USB_DWC2_HCCHAR0_EPDIR; + } + + /* TODO: LS device plugged to HUB */ + if (false) { + hcchar |= USB_DWC2_HCCHAR0_LSPDDEV; + } + + if (chan->type == UHC_DWC2_XFER_TYPE_INTR) { + hcchar |= USB_DWC2_HCCHAR0_ODDFRM; + } + + if (chan->type == UHC_DWC2_XFER_TYPE_ISOCHRONOUS) { + LOG_WRN("ISOC channels are note supported yet"); + } + + if (chan->type == UHC_DWC2_XFER_TYPE_INTR) { + LOG_WRN(" INTR channels are note supported yet"); + } + + sys_write32(hcchar, (mem_addr_t)&chan_regs->hcchar); + + /* TODO: sync CACHE */ + + /* TODO: Add the chan to the list of idle chans in the port object */ +} + +/* + * Free the chan and its resources. + */ +static void uhc_dwc2_chan_deinit(const struct device *const dev, struct uhc_dwc2_chan *const chan) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + + sys_clear_bits((mem_addr_t)&dwc2->haintmsk, (1 << chan->chan_idx)); +} + +static inline void uhc_dwc2_handle_port_events(const struct device *const dev, + uint32_t events) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + enum uhc_dwc2_speed port_speed; + bool port_has_device; + + LOG_DBG("Port events: 0x08%x", events); + + if (events & BIT(UHC_DWC2_EVENT_ENABLED)) { + priv->port_state = UHC_PORT_STATE_ENABLED; + + /* Configuring clock for selected speed */ + uhc_dwc2_init_hfir(dev); + + port_speed = dwc2_hal_get_port_speed(dwc2); + + dwc2_apply_fifo_config(dev); + dwc2_hal_set_frame_list(dwc2, NULL /* priv->frame_list , FRAME_LIST_LEN */); + dwc2_hal_periodic_enable(dwc2); + + uhc_dwc2_chan_config(dev, 0, 0, 0, port_speed, UHC_DWC2_XFER_TYPE_CTRL); + + /* Notify the higher logic about the new device */ + uhc_dwc2_submit_new_device(dev, port_speed); + } + + if (events & BIT(UHC_DWC2_EVENT_DISABLED)) { + /* Could be due to a disable request or reset request, or due to a port error */ + /* Ignore the disable event if it's due to a reset request */ + switch (priv->port_state) { + case UHC_PORT_STATE_RESETTING: + case UHC_PORT_STATE_ENABLED: + break; + default: + LOG_DBG("port state %d", priv->port_state); + /* Disabled due to a port error */ + LOG_ERR("Port disabled due to an error, changing state to recovery"); + priv->port_state = UHC_PORT_STATE_RECOVERY; + events |= BIT(UHC_DWC2_EVENT_ERROR); + /* TODO: Notify the port event from ISR */ + /* TODO: Port disabled by request, not implemented yet */ + } + } + + if (events & BIT(UHC_DWC2_EVENT_CONNECTION)) { + /* Check the status after debounding the connection events */ + uhc_dwc2_port_debounce(dev); + if (priv->port_state == UHC_PORT_STATE_DISCONNECTED) { + LOG_ERR("Port is not connected after debounce"); + /* TODO: Simulate and/or verify */ + LOG_WRN("Port debounce error handling is not implemented yet"); + } else { + uhc_dwc2_debounce_disable(dev); + /* Device present but port not yet enabled: follow with an USB reset */ + uhc_dwc2_port_reset(dev); + } + } + + if ((events & BIT(UHC_DWC2_EVENT_OVERCURRENT)) || + (events & BIT(UHC_DWC2_EVENT_OVERCURRENT_CLEAR))) { + /* If port state powered, we need to power it off to protect it + * change port state to recovery + * generate port event UHC_DWC2_EVENT_OVERCURRENT + */ + LOG_ERR("Overcurrent detected on port, not implemented yet"); + /* TODO: Handle overcurrent event */ + } + + if ((events & BIT(UHC_DWC2_EVENT_DISCONNECTION)) || + (events & BIT(UHC_DWC2_EVENT_ERROR)) || + (events & BIT(UHC_DWC2_EVENT_OVERCURRENT))) { + port_has_device = false; + + switch (priv->port_state) { + case UHC_PORT_STATE_DISABLED: + break; + case UHC_PORT_STATE_NOT_POWERED: + case UHC_PORT_STATE_ENABLED: + port_has_device = true; + break; + default: + LOG_ERR("Unexpected port state %d", priv->port_state); + break; + } + + if (port_has_device) { + uhc_dwc2_chan_deinit(dev, &priv->chan[0]); + uhc_dwc2_submit_dev_gone(dev); + } + } + + /* Failure events that need a port recovery */ + if ((events & BIT(UHC_DWC2_EVENT_ERROR)) || + (events & BIT(UHC_DWC2_EVENT_OVERCURRENT))) { + uhc_dwc2_port_recovery(dev); + } +} + +static inline void uhc_dwc2_handle_chan_events(const struct device *const dev, + struct uhc_dwc2_chan *const chan) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + const uint32_t events = atomic_clear(&chan->event); + + LOG_DBG("Channel events: 0x%08x", events); + + if (events & BIT(DWC2_CHAN_EVENT_CPLT)) { + /* XFER transfer is done, process the transfer and release the chan buffer */ + struct uhc_transfer *const xfer = (struct uhc_transfer *)chan->xfer; + + if (xfer->buf != NULL) { + LOG_HEXDUMP_INF(xfer->buf->data, xfer->buf->len, "data"); + } + + /* TODO: Refactor the address setting logic. */ + /* To configure the channel, we need to get the dev addr from higher logic */ + if (chan->is_setting_addr) { + chan->is_setting_addr = 0; + chan->dev_addr = chan->new_addr; + /* Set the new device address in the channel */ + sys_set_bits((mem_addr_t)&chan_regs->hcchar, + (chan->dev_addr << USB_DWC2_HCCHAR0_DEVADDR_POS)); + k_msleep(SET_ADDR_DELAY_MS); + } + + uhc_xfer_return(dev, xfer, 0); + } + + if (events & BIT(DWC2_CHAN_EVENT_ERROR)) { + LOG_ERR("Channel error handling not implemented yet"); + /* TODO: get channel error, halt the chan */ + } + + if (events & BIT(DWC2_CHAN_EVENT_HALT_REQ)) { + LOG_ERR("Channel halt request handling not implemented yet"); + + /* TODO: Implement halting the ongoing transfer */ + + /* Hint: + * We've halted a transfer, so we need to trigger the chan callback + * Halt request event is triggered when packet is successful completed. + * But just treat all halted transfers as errors + * Notify the task waiting for the chan halt or halt it right away + * _internal_chan_event_notify(chan, true); + */ + } +} + +static inline int uhc_dwc2_submit_ctrl_xfer(const struct device *const dev, + struct uhc_dwc2_chan *const chan) +{ + struct uhc_transfer *const xfer = uhc_xfer_get_next(dev); + + LOG_HEXDUMP_INF(xfer->setup_pkt, 8, "setup"); + + LOG_DBG("endpoint=%02Xh, mps=%d, interval=%d, start_frame=%d, stage=%d, no_status=%d", + xfer->ep, xfer->mps, xfer->interval, xfer->start_frame, xfer->stage, + xfer->no_status); + + /* TODO: Check that XFER has not already been enqueued? */ + + /* net_buf library default to sizeof(void *) alignment, which is at least 4 bytes */ + if (((uintptr_t)xfer->setup_pkt % 4)) { + LOG_WRN("Setup packet address %p is not 4-byte aligned", (void *)xfer->setup_pkt); + } + + /* TODO: Buffer addr that will used as dma addr also should be aligned */ + if (xfer->buf != NULL && (uintptr_t)net_buf_tail(xfer->buf) % 4 != 0) { + LOG_WRN("XFER buffer address %08lXh is not 4-byte aligned", + (uintptr_t)net_buf_tail(xfer->buf)); + } + + uhc_dwc2_buffer_fill_ctrl(chan, xfer); + uhc_dwc2_buffer_exec(dev, chan); + + return 0; +} + +static void uhc_dwc2_thread(void *const arg1, void *const arg2, void *const arg3) +{ + const struct device *const dev = (const struct device *)arg1; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + uint32_t events; + + while (true) { + events = k_event_wait_safe(&priv->event, UINT32_MAX, false, K_FOREVER); + + uhc_lock_internal(dev, K_FOREVER); + + uhc_dwc2_handle_port_events(dev, events); + + for (uint32_t i = 0; i < UHC_DWC2_MAX_CHAN; i++) { + if (events & BIT(UHC_DWC2_EVENT_CHAN0 + i)) { + uhc_dwc2_handle_chan_events(dev, &priv->chan[i]); + } + } + + uhc_unlock_internal(dev); + } +} + +/* + * UHC DWC2 Driver API + */ + +static int uhc_dwc2_lock(const struct device *const dev) +{ + struct uhc_data *data = dev->data; + + return k_mutex_lock(&data->mutex, K_FOREVER); +} + +static int uhc_dwc2_unlock(const struct device *const dev) +{ + struct uhc_data *data = dev->data; + + return k_mutex_unlock(&data->mutex); +} + +static int uhc_dwc2_sof_enable(const struct device *const dev) +{ + LOG_ERR("%s not implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_bus_suspend(const struct device *const dev) +{ + LOG_ERR("%s not implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_bus_reset(const struct device *const dev) +{ + /* TODO: move the reset logic here */ + + /* Hint: + * First reset is done by the uhc dwc2 driver, so we don't need to do anything here. + */ + uhc_submit_event(dev, UHC_EVT_RESETED, 0); + + return 0; +} + +static int uhc_dwc2_bus_resume(const struct device *const dev) +{ + LOG_ERR("%s not implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_enqueue(const struct device *const dev, struct uhc_transfer *const xfer) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + int ret; + + (void)uhc_xfer_append(dev, xfer); + + uhc_lock_internal(dev, K_FOREVER); + + if (USB_EP_GET_IDX(xfer->ep) == 0) { + ret = uhc_dwc2_submit_ctrl_xfer(dev, &priv->chan[0]); + if (ret) { + LOG_ERR("Failed to submit xfer: %d", ret); + goto err; + } + } else { + LOG_ERR("Non-control endpoint enqueue not implemented yet"); + ret = -ENOSYS; + goto err; + } + + ret = 0; +err: + uhc_unlock_internal(dev); + + return ret; +} + +static int uhc_dwc2_dequeue(const struct device *const dev, struct uhc_transfer *const xfer) +{ + LOG_ERR("%s not implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_preinit(const struct device *const dev) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct uhc_data *const data = dev->data; + + /* Initialize the private data structure */ + memset(priv, 0, sizeof(struct uhc_dwc2_data)); + k_mutex_init(&data->mutex); + k_event_init(&priv->event); + + /* TODO: Overwrite the DWC2 register values with the devicetree values? */ + + uhc_dwc2_quirk_caps(dev); + + k_thread_create(&priv->thread, uhc_dwc2_stack, K_THREAD_STACK_SIZEOF(uhc_dwc2_stack), + uhc_dwc2_thread, (void *)dev, NULL, NULL, + K_PRIO_COOP(CONFIG_UHC_DWC2_THREAD_PRIORITY), K_ESSENTIAL, K_NO_WAIT); + k_thread_name_set(&priv->thread, dev->name); + + return 0; +} + +static int uhc_dwc2_init(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + uint32_t reg; + int ret; + + ret = uhc_dwc2_quirk_init(dev); + if (ret) { + LOG_ERR("Quirk init failed %d", ret); + return ret; + } + + /* Read hardware configuration registers */ + + reg = sys_read32((mem_addr_t)&dwc2->gsnpsid); + if (reg != config->gsnpsid) { + LOG_ERR("Unexpected GSNPSID 0x%08x instead of 0x%08x", reg, config->gsnpsid); + return -ENOTSUP; + } + + reg = sys_read32((mem_addr_t)&dwc2->ghwcfg1); + if (reg != config->ghwcfg1) { + LOG_ERR("Unexpected GHWCFG1 0x%08x instead of 0x%08x", reg, config->ghwcfg1); + return -ENOTSUP; + } + + reg = sys_read32((mem_addr_t)&dwc2->ghwcfg2); + if (reg != config->ghwcfg2) { + LOG_ERR("Unexpected GHWCFG2 0x%08x instead of 0x%08x", reg, config->ghwcfg2); + return -ENOTSUP; + } + + reg = sys_read32((mem_addr_t)&dwc2->ghwcfg3); + if (reg != config->ghwcfg3) { + LOG_ERR("Unexpected GHWCFG3 0x%08x instead of 0x%08x", reg, config->ghwcfg3); + return -ENOTSUP; + } + + reg = sys_read32((mem_addr_t)&dwc2->ghwcfg4); + if (reg != config->ghwcfg4) { + LOG_ERR("Unexpected GHWCFG4 0x%08x instead of 0x%08x", reg, config->ghwcfg4); + return -ENOTSUP; + } + + if ((config->ghwcfg4 & USB_DWC2_GHWCFG4_DEDFIFOMODE) == 0) { + LOG_ERR("Only dedicated TX FIFO mode is supported"); + return -ENOTSUP; + } + + ret = uhc_dwc2_quirk_phy_pre_select(dev); + if (ret) { + LOG_ERR("Quirk PHY pre select failed %d", ret); + return ret; + } + + /* Software reset won't finish without PHY clock */ + if (uhc_dwc2_quirk_is_phy_clk_off(dev)) { + LOG_ERR("PHY clock is turned off, cannot reset"); + return -EIO; + } + + /* Reset core after selecting PHY */ + ret = dwc2_hal_core_reset(config->base, K_MSEC(10)); + if (ret) { + LOG_ERR("DWC2 core reset failed after PHY init: %d", ret); + return ret; + } + + ret = uhc_dwc2_quirk_phy_post_select(dev); + if (ret) { + LOG_ERR("Quirk PHY post select failed %d", ret); + return ret; + } + + /* Pre-calculate FIFO settings */ + uhc_dwc2_config_fifo_fixed_dma(dev); + + /* Program the GAHBCFG register */ + uhc_dwc2_init_gahbcfg(dev); + + /* Disable RX FIFO level interrupts for the time of the configuration */ + /* TODO */ + + /* Configure the reference clock */ + /* TODO */ + + /* Program the GUSBCFG register */ + uhc_dwc2_init_gusbcfg(dev); + + /* Disable OTG and mode-mismatch interrupts */ + /* TODO */ + + /* Program the HCFG register */ + uhc_dwc2_init_hcfg(dev); + + return 0; +} + +static int uhc_dwc2_enable(const struct device *const dev) +{ + int ret; + + ret = uhc_dwc2_quirk_pre_enable(dev); + if (ret) { + LOG_ERR("Quirk pre enable failed %d", ret); + return ret; + } + + ret = uhc_dwc2_quirk_irq_enable_func(dev); + if (ret) { + LOG_ERR("Quirk IRQ enable failed %d", ret); + return ret; + } + + ret = uhc_dwc2_power_on(dev); + if (ret) { + LOG_ERR("Failed to power on port: %d", ret); + return ret; + } + + return 0; +} + +static int uhc_dwc2_disable(const struct device *const dev) +{ + int ret; + + LOG_ERR("%s not implemented", __func__); + + ret = uhc_dwc2_quirk_disable(dev); + if (ret) { + LOG_ERR("Quirk disable failed %d", ret); + return ret; + } + + return -ENOSYS; +} + +static int uhc_dwc2_shutdown(const struct device *const dev) +{ + int ret; + + LOG_ERR("%s not implemented", __func__); + + /* TODO: Release memory for channel handles */ + + ret = uhc_dwc2_quirk_shutdown(dev); + if (ret) { + LOG_ERR("Quirk shutdown failed %d", ret); + return ret; + } + + return -ENOSYS; +} + +/* + * Device Definition and Initialization + */ + +static const struct uhc_api uhc_dwc2_api = { + /* Common */ + .lock = uhc_dwc2_lock, + .unlock = uhc_dwc2_unlock, + .init = uhc_dwc2_init, + .enable = uhc_dwc2_enable, + .disable = uhc_dwc2_disable, + .shutdown = uhc_dwc2_shutdown, + /* Bus related */ + .bus_reset = uhc_dwc2_bus_reset, + .sof_enable = uhc_dwc2_sof_enable, + .bus_suspend = uhc_dwc2_bus_suspend, + .bus_resume = uhc_dwc2_bus_resume, + /* EP related */ + .ep_enqueue = uhc_dwc2_enqueue, + .ep_dequeue = uhc_dwc2_dequeue, +}; + +#define UHC_DWC2_DT_INST_REG_ADDR(n) \ + COND_CODE_1(DT_NUM_REGS(DT_DRV_INST(n)), \ + (DT_INST_REG_ADDR(n)), \ + (DT_INST_REG_ADDR_BY_NAME(n, core))) + +static struct uhc_dwc2_data uhc_dwc2_data = { + .irq_sem = Z_SEM_INITIALIZER(uhc_dwc2_data.irq_sem, 0, 1), +}; + +static const struct uhc_dwc2_config uhc_dwc2_config_host = { + .base = (struct usb_dwc2_reg *)UHC_DWC2_DT_INST_REG_ADDR(0), + .quirks = UHC_DWC2_VENDOR_QUIRK_GET(0), + .gsnpsid = DT_INST_PROP(0, gsnpsid), + .ghwcfg1 = DT_INST_PROP(0, ghwcfg1), + .ghwcfg2 = DT_INST_PROP(0, ghwcfg2), + .ghwcfg3 = DT_INST_PROP(0, ghwcfg3), + .ghwcfg4 = DT_INST_PROP(0, ghwcfg4), +}; + +static struct uhc_data uhc_dwc2_priv_data = { + .priv = &uhc_dwc2_data, +}; + +DEVICE_DT_INST_DEFINE(0, uhc_dwc2_preinit, NULL, &uhc_dwc2_priv_data, &uhc_dwc2_config_host, + POST_KERNEL, 99, &uhc_dwc2_api); diff --git a/drivers/usb/uhc/uhc_dwc2.h b/drivers/usb/uhc/uhc_dwc2.h new file mode 100644 index 0000000000000..f4ccb658f4621 --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_USB_UDC_DWC2_H +#define ZEPHYR_DRIVERS_USB_UDC_DWC2_H + +#include +#include +#include +#include + +/* Vendor quirks per driver instance */ +struct uhc_dwc2_vendor_quirks { + /* Called at the beginning of uhc_dwc2_init() */ + int (*init)(const struct device *dev); + /* Called on uhc_dwc2_enable() before the controller is initialized */ + int (*pre_enable)(const struct device *dev); + /* Called on uhc_dwc2_enable() after the controller is initialized */ + int (*post_enable)(const struct device *dev); + /* Called at the end of uhc_dwc2_disable() */ + int (*disable)(const struct device *dev); + /* Called at the end of uhc_dwc2_shutdown() */ + int (*shutdown)(const struct device *dev); + /* Enable interrupts function */ + int (*irq_enable_func)(const struct device *dev); + /* Disable interrupts function */ + int (*irq_disable_func)(const struct device *dev); + /* Called at the end of IRQ handling */ + int (*irq_clear)(const struct device *dev); + /* Called on driver pre-init */ + int (*caps)(const struct device *dev); + /* Called on PHY pre-select */ + int (*phy_pre_select)(const struct device *dev); + /* Called on PHY post-select and core reset */ + int (*phy_post_select)(const struct device *dev); + /* Called while waiting for bits that require PHY to be clocked */ + int (*is_phy_clk_off)(const struct device *dev); + /* PHY get clock */ + int (*get_phy_clk)(const struct device *dev); + /* Called after hibernation entry sequence */ + int (*post_hibernation_entry)(const struct device *dev); + /* Called before hibernation exit sequence */ + int (*pre_hibernation_exit)(const struct device *dev); +}; + +/* Driver configuration per instance */ +struct uhc_dwc2_config { + /* Pointer to base address of DWC_OTG registers */ + struct usb_dwc2_reg *const base; + /* Pointer to pin control configuration or NULL */ + struct pinctrl_dev_config *const pcfg; + /* Pointer to vendor quirks or NULL */ + const struct uhc_dwc2_vendor_quirks *const quirks; + void (*make_thread)(const struct device *dev); + void (*irq_enable_func)(const struct device *dev); + void (*irq_disable_func)(const struct device *dev); + uint32_t gsnpsid; + uint32_t ghwcfg1; + uint32_t ghwcfg2; + uint32_t ghwcfg3; + uint32_t ghwcfg4; +}; + +#include "uhc_dwc2_vendor_quirks.h" + +#define UHC_DWC2_VENDOR_QUIRK_GET(n) \ + COND_CODE_1(DT_NODE_VENDOR_HAS_IDX(DT_DRV_INST(n), 1), \ + (&uhc_dwc2_vendor_quirks_##n), \ + (NULL)) + +#define DWC2_QUIRK_FUNC_DEFINE(fname) \ + static inline int uhc_dwc2_quirk_##fname(const struct device *dev) \ + { \ + __maybe_unused const struct uhc_dwc2_config *const config = dev->config; \ + const struct uhc_dwc2_vendor_quirks *const quirks = \ + COND_CODE_1(IS_EQ(DT_NUM_INST_STATUS_OKAY(snps_dwc2), 1), \ + (UHC_DWC2_VENDOR_QUIRK_GET(0)), \ + (config->quirks)); \ + \ + if (quirks != NULL && quirks->fname != NULL) { \ + return quirks->fname(dev); \ + } \ + \ + return 0; \ + } + +DWC2_QUIRK_FUNC_DEFINE(init) +DWC2_QUIRK_FUNC_DEFINE(pre_enable) +DWC2_QUIRK_FUNC_DEFINE(post_enable) +DWC2_QUIRK_FUNC_DEFINE(disable) +DWC2_QUIRK_FUNC_DEFINE(shutdown) +DWC2_QUIRK_FUNC_DEFINE(irq_enable_func) +DWC2_QUIRK_FUNC_DEFINE(irq_disable_func) +DWC2_QUIRK_FUNC_DEFINE(irq_clear) +DWC2_QUIRK_FUNC_DEFINE(caps) +DWC2_QUIRK_FUNC_DEFINE(phy_pre_select) +DWC2_QUIRK_FUNC_DEFINE(phy_post_select) +DWC2_QUIRK_FUNC_DEFINE(is_phy_clk_off) +DWC2_QUIRK_FUNC_DEFINE(get_phy_clk) +DWC2_QUIRK_FUNC_DEFINE(post_hibernation_entry) +DWC2_QUIRK_FUNC_DEFINE(pre_hibernation_exit) + +#endif /* ZEPHYR_DRIVERS_USB_UDC_DWC2_H */ diff --git a/drivers/usb/uhc/uhc_dwc2_vendor_quirks.h b/drivers/usb/uhc/uhc_dwc2_vendor_quirks.h new file mode 100644 index 0000000000000..861ba5cff726c --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2_vendor_quirks.h @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_USB_UHC_DWC2_VENDOR_QUIRKS_H +#define ZEPHYR_DRIVERS_USB_UHC_DWC2_VENDOR_QUIRKS_H + +#include "uhc_dwc2.h" + +#include +#include +#include + +static void uhc_dwc2_isr_handler(const struct device *dev); + +#if DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct phy_context_t { + usb_phy_target_t target; + usb_phy_controller_t controller; + usb_phy_status_t status; + usb_otg_mode_t otg_mode; + usb_phy_speed_t otg_speed; + usb_phy_ext_io_conf_t *ext_io_pins; + usb_wrap_hal_context_t wrap_hal; +}; + +struct usb_dw_esp32_config { + const struct device *clock_dev; + const clock_control_subsys_t clock_subsys; + int irq_source; + int irq_priority; + int irq_flags; + struct phy_context_t *phy_ctx; +}; + +struct usb_dw_esp32_data { + struct intr_handle_data_t *int_handle; +}; + +static void uhc_dwc2_isr_handler(const struct device *dev); + +static inline int esp32_usb_otg_init(const struct device *dev, + const struct usb_dw_esp32_config *cfg, + struct usb_dw_esp32_data *data) +{ + int ret; + + LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + + if (!device_is_ready(cfg->clock_dev)) { + return -ENODEV; + } + + ret = clock_control_on(cfg->clock_dev, cfg->clock_subsys); + + if (ret != 0) { + return ret; + } + + /* pinout config to work in USB_OTG_MODE_HOST */ + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_OTG_IDDIG_IN_IDX, false); + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false); + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_VBUSVALID_IN_IDX, + false); + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_AVALID_IN_IDX, false); + + if (cfg->phy_ctx->target == USB_PHY_TARGET_INT) { + gpio_set_drive_capability(USBPHY_DM_NUM, GPIO_DRIVE_CAP_3); + gpio_set_drive_capability(USBPHY_DP_NUM, GPIO_DRIVE_CAP_3); + } + + /* allocate interrupt but keep it disabled to avoid + * spurious suspend/resume event at enumeration phase + */ + ret = esp_intr_alloc(cfg->irq_source, + ESP_INTR_FLAG_INTRDISABLED | ESP_PRIO_TO_FLAGS(cfg->irq_priority) | + ESP_INT_FLAGS_CHECK(cfg->irq_flags), + (intr_handler_t)uhc_dwc2_isr_handler, (void *)dev, &data->int_handle); + + if (ret != 0) { + return -ECANCELED; + } + + LOG_DBG("PHY inited"); + return 0; +} + +static inline int esp32_usb_otg_enable_phy(struct phy_context_t *phy_ctx, bool enable) +{ + LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + + if (enable) { + usb_wrap_ll_enable_bus_clock(true); + usb_wrap_hal_init(&phy_ctx->wrap_hal); + +#if USB_WRAP_LL_EXT_PHY_SUPPORTED + usb_wrap_hal_phy_set_external(&phy_ctx->wrap_hal, + (phy_ctx->target == USB_PHY_TARGET_EXT)); +#endif + if (phy_ctx->target == USB_PHY_TARGET_INT) { + /* Configure pull resistors for host */ + usb_wrap_pull_override_vals_t vals = { + .dp_pu = false, + .dm_pu = false, + .dp_pd = true, + .dm_pd = true, + }; + usb_wrap_hal_phy_enable_pull_override(&phy_ctx->wrap_hal, &vals); + } + LOG_DBG("PHY enabled"); + } else { + usb_wrap_ll_enable_bus_clock(false); + usb_wrap_ll_phy_enable_pad(phy_ctx->wrap_hal.dev, false); + + LOG_DBG("PHY disabled"); + } + return 0; +} + +static inline int esp32_usb_otg_get_phy_clock(struct phy_context_t *phy_ctx) +{ + if (phy_ctx->otg_speed == USB_PHY_SPEED_FULL) { + return MHZ(48); + } + + if (phy_ctx->otg_speed == USB_PHY_SPEED_LOW) { + /* PHY has implicit divider of 8 when in low speed */ + return MHZ(48) / 8; + } + + /* non supported speed */ + return 0; +} + +#define QUIRK_ESP32_USB_OTG_DEFINE(n) \ + \ + static struct phy_context_t phy_ctx_##n = { \ + .target = USB_PHY_TARGET_INT, \ + .controller = USB_PHY_CTRL_OTG, \ + .otg_mode = USB_OTG_MODE_HOST, \ + .otg_speed = USB_PHY_SPEED_UNDEFINED, \ + .ext_io_pins = NULL, \ + .wrap_hal = {}, \ + }; \ + \ + static const struct usb_dw_esp32_config usb_otg_config_##n = { \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, offset), \ + .irq_source = DT_INST_IRQ_BY_IDX(n, 0, irq), \ + .irq_priority = DT_INST_IRQ_BY_IDX(n, 0, priority), \ + .irq_flags = DT_INST_IRQ_BY_IDX(n, 0, flags), \ + .phy_ctx = &phy_ctx_##n, \ + }; \ + \ + static struct usb_dw_esp32_data usb_otg_data_##n; \ + \ + static int esp32_usb_otg_init_##n(const struct device *dev) \ + { \ + return esp32_usb_otg_init(dev, &usb_otg_config_##n, &usb_otg_data_##n); \ + } \ + \ + static int esp32_usb_otg_enable_phy_##n(const struct device *dev) \ + { \ + return esp32_usb_otg_enable_phy(&phy_ctx_##n, true); \ + } \ + \ + static int esp32_usb_otg_disable_phy_##n(const struct device *dev) \ + { \ + return esp32_usb_otg_enable_phy(&phy_ctx_##n, false); \ + } \ + static int esp32_usb_int_enable_func_##n(const struct device *dev) \ + { \ + return esp_intr_enable(usb_otg_data_##n.int_handle); \ + } \ + \ + static int esp32_usb_int_disable_func_##n(const struct device *dev) \ + { \ + return esp_intr_disable(usb_otg_data_##n.int_handle); \ + } \ + \ + static int esp32_usb_get_phy_clock_##n(const struct device *dev) \ + { \ + return esp32_usb_otg_get_phy_clock(&phy_ctx_##n); \ + } \ + \ + struct uhc_dwc2_vendor_quirks uhc_dwc2_vendor_quirks_##n = { \ + .init = esp32_usb_otg_init_##n, \ + .pre_enable = esp32_usb_otg_enable_phy_##n, \ + .disable = esp32_usb_otg_disable_phy_##n, \ + .irq_enable_func = esp32_usb_int_enable_func_##n, \ + .irq_disable_func = esp32_usb_int_disable_func_##n, \ + .get_phy_clk = esp32_usb_get_phy_clock_##n, \ + }; + +DT_INST_FOREACH_STATUS_OKAY(QUIRK_ESP32_USB_OTG_DEFINE) + +#endif /*DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) */ + +#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l) + +#define USBHS_DT_WRAPPER_REG_ADDR(n) UINT_TO_POINTER(DT_INST_REG_ADDR_BY_NAME(n, wrapper)) + +#include +#include +#include + +#define NRF_DEFAULT_IRQ_PRIORITY 1 + +/* + * On USBHS, we cannot access the DWC2 register until VBUS is detected and + * valid. If the user tries to force usbd_enable() and the corresponding + * uhc_enable() without a "VBUS ready" notification, the event wait will block. + */ +static K_EVENT_DEFINE(usbhs_events); +#define USBHS_VBUS_READY BIT(0) +#define USBHS_VBUS_REMOVED BIT(1) + +static struct onoff_manager *pclk24m_mgr; +static struct onoff_client pclk24m_cli; + +static void vregusb_isr(const void *arg) +{ + if (NRF_VREGUSB->EVENTS_VBUSDETECTED) { + NRF_VREGUSB->EVENTS_VBUSDETECTED = 0; + k_event_post(&usbhs_events, USBHS_VBUS_READY); + } + + if (NRF_VREGUSB->EVENTS_VBUSREMOVED) { + NRF_VREGUSB->EVENTS_VBUSREMOVED = 0; + k_event_set_masked(&usbhs_events, 0, USBHS_VBUS_REMOVED); + } +} + +static inline int usbhs_enable_core(const struct device *dev) +{ + NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); + int err; + + LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + + if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, K_NO_WAIT)) { + LOG_WRN("VBUS is not ready, block uhc_enable()"); + if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, K_FOREVER)) { + return -ETIMEDOUT; + } + } + + /* Request PCLK24M using clock control driver */ + sys_notify_init_spinwait(&pclk24m_cli.notify); + err = onoff_request(pclk24m_mgr, &pclk24m_cli); + if (err < 0) { + LOG_ERR("Failed to start PCLK24M %d", err); + return err; + } + + /* Power up peripheral */ + wrapper->ENABLE = USBHS_ENABLE_CORE_Msk; + + /* Set ID to Host and disable D+ pull-up */ + wrapper->PHY.OVERRIDEVALUES = (1 << 24) | (1 << 23); + wrapper->PHY.INPUTOVERRIDE = (1 << 31) | (1 << 30) | (1 << 24) | (1 << 23); + + /* Release PHY power-on reset */ + wrapper->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk; + + /* Wait for PHY clock to start */ + k_busy_wait(45); + + /* Release DWC2 reset */ + wrapper->TASKS_START = 1UL; + + /* Wait for clock to start to avoid hang on too early register read */ + k_busy_wait(1); + + return 0; +} + +static inline int usbhs_disable_core(const struct device *dev) +{ + NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); + int err; + + LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + + /* Set ID to Device and forcefully disable D+ pull-up */ + wrapper->PHY.OVERRIDEVALUES = (1 << 31); + wrapper->PHY.INPUTOVERRIDE = (1 << 31) | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk; + + wrapper->ENABLE = 0UL; + + /* Release PCLK24M using clock control driver */ + err = onoff_cancel_or_release(pclk24m_mgr, &pclk24m_cli); + if (err < 0) { + LOG_ERR("Failed to stop PCLK24M %d", err); + return err; + } + + return 0; +} + +static inline int usbhs_init_vreg_and_clock_and_core(const struct device *dev) +{ + /* Init VREG */ + + IRQ_CONNECT(VREGUSB_IRQn, NRF_DEFAULT_IRQ_PRIORITY, vregusb_isr, DEVICE_DT_INST_GET(0), 0); + + NRF_VREGUSB->INTEN = VREGUSB_INTEN_VBUSDETECTED_Msk | VREGUSB_INTEN_VBUSREMOVED_Msk; + NRF_VREGUSB->TASKS_START = 1; + + /* TODO: Determine conditions when VBUSDETECTED is not generated */ + if (sys_read32((mem_addr_t)NRF_VREGUSB + 0x400) & BIT(2)) { + k_event_post(&usbhs_events, USBHS_VBUS_READY); + } + + irq_enable(VREGUSB_IRQn); + + /* Init the clock */ + + pclk24m_mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF24M); + + /* Enable the core */ + + return usbhs_enable_core(dev); + + /* It is now possible to access the configuration registers */ +} + +static inline int usbhs_disable_vreg(const struct device *dev) +{ + NRF_VREGUSB->INTEN = 0; + NRF_VREGUSB->TASKS_STOP = 1; + + return 0; +} + +static inline int usbhs_init_caps(const struct device *dev) +{ + struct uhc_data *data = dev->data; + + data->caps.hs = true; + + return 0; +} + +static inline int usbhs_is_phy_clk_off(const struct device *dev) +{ + return !k_event_test(&usbhs_events, USBHS_VBUS_READY); +} + +static inline int usbhs_post_hibernation_entry(const struct device *dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const base = config->base; + NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); + + sys_set_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK); + + wrapper->TASKS_STOP = 1; + + return 0; +} + +static inline int usbhs_pre_hibernation_exit(const struct device *dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const base = config->base; + NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); + + sys_clear_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK); + + wrapper->TASKS_START = 1; + + return 0; +} + +#define UHC_DWC2_IRQ_FLAGS_TYPE0(n) 0 +#define UHC_DWC2_IRQ_FLAGS_TYPE1(n) DT_INST_IRQ(n, type) + +#define UHC_DWC2_IRQ_FLAGS(n) _CONCAT(UHC_DWC2_IRQ_FLAGS_TYPE, DT_INST_IRQ_HAS_CELL(n, type))(n) + +#define QUIRK_NRF_USBHS_DEFINE(n) \ + \ + static int usbhs_irq_enable_func_##n(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), uhc_dwc2_isr_handler, \ + DEVICE_DT_INST_GET(n), UHC_DWC2_IRQ_FLAGS(n)); \ + \ + irq_enable(DT_INST_IRQN(n)); \ + \ + return 0; \ + } \ + \ + static int usbhs_irq_disable_func_##n(const struct device *dev) \ + { \ + irq_disable(DT_INST_IRQN(n)); \ + \ + return 0; \ + } \ + \ + struct uhc_dwc2_vendor_quirks uhc_dwc2_vendor_quirks_##n = { \ + .init = usbhs_init_vreg_and_clock_and_core, \ + .pre_enable = usbhs_enable_core, \ + .disable = usbhs_disable_core, \ + .shutdown = usbhs_disable_vreg, \ + .caps = usbhs_init_caps, \ + .is_phy_clk_off = usbhs_is_phy_clk_off, \ + .post_hibernation_entry = usbhs_post_hibernation_entry, \ + .pre_hibernation_exit = usbhs_pre_hibernation_exit, \ + .irq_enable_func = usbhs_irq_enable_func_##n, \ + .irq_disable_func = usbhs_irq_disable_func_##n, \ + }; + +DT_INST_FOREACH_STATUS_OKAY(QUIRK_NRF_USBHS_DEFINE) + +/* TODO remove from uhc_dwc2.c */ +#define IRAM_ATTR + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l) */ + +/* Add next vendor quirks definition above this line */ + +#endif /* ZEPHYR_DRIVERS_USB_UHC_DWC2_VENDOR_QUIRKS_H */ diff --git a/dts/bindings/usb/snps,dwc2.yaml b/dts/bindings/usb/snps,dwc2.yaml index 4ab41f9214063..ee345d3a92413 100644 --- a/dts/bindings/usb/snps,dwc2.yaml +++ b/dts/bindings/usb/snps,dwc2.yaml @@ -31,6 +31,12 @@ properties: description: | Number of configured IN endpoints including control endpoint. + gsnpsid: + type: int + description: | + Value of the GSNPSID register, used to identify the version of the core + and avoid mismatch by checking the register at runtime. + ghwcfg1: type: int required: true @@ -45,6 +51,12 @@ properties: Value of the GHWCFG2 register. It is used to determine available endpoint types during driver pre-initialization. + ghwcfg3: + type: int + description: | + Value of the GHWCFG3 register. It is used to determine available endpoint + types during driver pre-initialization. + ghwcfg4: type: int required: true diff --git a/dts/vendor/nordic/nrf54lm20a.dtsi b/dts/vendor/nordic/nrf54lm20a.dtsi index fa542abf3f5bb..045368ee64cd9 100644 --- a/dts/vendor/nordic/nrf54lm20a.dtsi +++ b/dts/vendor/nordic/nrf54lm20a.dtsi @@ -229,8 +229,10 @@ interrupts = <90 NRF_DEFAULT_IRQ_PRIORITY>; num-in-eps = <16>; num-out-eps = <16>; - ghwcfg1 = <0x0>; + gsnpsid = <0x4f54500b>; + ghwcfg1 = <0x00000000>; ghwcfg2 = <0x22affc52>; + ghwcfg3 = <0x0be0c0e8>; ghwcfg4 = <0x3e10aa60>; /* 3040 locations with full flexibility */ g-rx-fifo-size = <592>; diff --git a/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi b/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi index 65cbf8f7fcb6c..3d2ecceefdb8b 100644 --- a/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi +++ b/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi @@ -419,8 +419,10 @@ clocks = <&clock ESP32_USB_MODULE>; num-out-eps = <6>; num-in-eps = <6>; + gsnpsid = <0x4f54400a>; ghwcfg1 = <0x00000000>; ghwcfg2 = <0x224dd930>; + ghwcfg3 = <0x00c804b5>; ghwcfg4 = <0xd3f0a030>; g-rx-fifo-size = <64>; g-np-tx-fifo-size = <16>; diff --git a/samples/subsys/usb/shell/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay b/samples/subsys/usb/shell/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay new file mode 100644 index 0000000000000..eefe9e01a7996 --- /dev/null +++ b/samples/subsys/usb/shell/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +zephyr_uhc0: &usbhs { + status = "okay"; +}; From ca144b00cfd91bfa3db7b5b98fca15290aa6351f Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Sat, 13 Dec 2025 23:55:52 +0000 Subject: [PATCH 6/6] WIP --- drivers/usb/common/usb_dwc2_hw.h | 5 + drivers/usb/uhc/uhc_dwc2.c | 248 ++++++++++++++++++++----------- 2 files changed, 163 insertions(+), 90 deletions(-) diff --git a/drivers/usb/common/usb_dwc2_hw.h b/drivers/usb/common/usb_dwc2_hw.h index ddb89e1f88652..30304eec545ff 100644 --- a/drivers/usb/common/usb_dwc2_hw.h +++ b/drivers/usb/common/usb_dwc2_hw.h @@ -839,6 +839,11 @@ USB_DWC2_GET_FIELD_DEFINE(hcchar0_mps, HCCHAR0_MPS) #define USB_DWC2_HCTSIZ_PKTCNT_POS 19UL #define USB_DWC2_HCTSIZ_PKTCNT_MASK (0x3FFUL << USB_DWC2_HCTSIZ_PKTCNT_POS) #define USB_DWC2_HCTSIZ_PID_POS 29UL +#define USB_DWC2_HCTSIZ_PID_DATA0 0UL +#define USB_DWC2_HCTSIZ_PID_DATA2 1UL +#define USB_DWC2_HCTSIZ_PID_DATA1 2UL +#define USB_DWC2_HCTSIZ_PID_MDATA 3UL +#define USB_DWC2_HCTSIZ_PID_SETUP 3UL #define USB_DWC2_HCTSIZ_PID_MASK (0x3UL << USB_DWC2_HCTSIZ_PID_POS) #define USB_DWC2_HCTSIZ_DOPNG BIT(31) diff --git a/drivers/usb/uhc/uhc_dwc2.c b/drivers/usb/uhc/uhc_dwc2.c index 120c59b73fa38..8a7df246665ab 100644 --- a/drivers/usb/uhc/uhc_dwc2.c +++ b/drivers/usb/uhc/uhc_dwc2.c @@ -636,6 +636,9 @@ static inline void uhc_dwc2_buffer_fill_ctrl(struct uhc_dwc2_chan *const chan, /* Save the xfer pointer in the buffer */ chan->xfer = xfer; + /* Control stage is always OUT */ + //sys_clear_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); + /* TODO Sync data from cache to memory. For OUT and CTRL transfers */ } @@ -648,7 +651,7 @@ static inline uint16_t calc_packet_count(const uint16_t size, const uint8_t mps) } } -static void uhc_dwc2_buffer_exec_proceed(const struct device *const dev, +static void uhc_dwc2_buffer_continue(const struct device *const dev, struct uhc_dwc2_chan *const chan) { const struct uhc_dwc2_config *const config = dev->config; @@ -734,53 +737,6 @@ static void uhc_dwc2_buffer_exec_proceed(const struct device *const dev, sys_write32(hcchar, (mem_addr_t)&chan_regs->hcchar); } -static void uhc_dwc2_buffer_exec(const struct device *const dev, struct uhc_dwc2_chan *const chan) -{ - const struct uhc_dwc2_config *const config = dev->config; - struct usb_dwc2_reg *const dwc2 = config->base; - struct uhc_transfer *const xfer = (struct uhc_transfer *)chan->xfer; - const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); - uint16_t pkt_cnt; - uint16_t size; - int next_pid; - uint32_t hctsiz; - uint32_t hcint; - uint32_t hcchar; - - LOG_DBG("ep=%02X, mps=%d", xfer->ep, chan->ep_mps); - - if (USB_EP_GET_IDX(xfer->ep) == 0) { - /* Control stage is always OUT */ - sys_clear_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); - } - - if (xfer->interval != 0) { - LOG_ERR("Periodic transfer is not supported"); - } - - pkt_cnt = calc_packet_count(sizeof(struct usb_setup_packet), chan->ep_mps); - next_pid = CTRL_STAGE_SETUP; - size = sizeof(struct usb_setup_packet); - - hctsiz = (next_pid << USB_DWC2_HCTSIZ_PID_POS) & USB_DWC2_HCTSIZ_PID_MASK; - hctsiz |= (pkt_cnt << USB_DWC2_HCTSIZ_PKTCNT_POS) & USB_DWC2_HCTSIZ_PKTCNT_MASK; - hctsiz |= (size << USB_DWC2_HCTSIZ_XFERSIZE_POS) & USB_DWC2_HCTSIZ_XFERSIZE_MASK; - sys_write32(hctsiz, (mem_addr_t)&chan_regs->hctsiz); - - sys_write32((uint32_t)xfer->setup_pkt, (mem_addr_t)&chan_regs->hcdma); - - /* TODO: Configure split transaction if needed */ - - hcint = sys_read32((mem_addr_t)&chan_regs->hcint); - sys_write32(hcint, (mem_addr_t)&chan_regs->hcint); - - /* TODO: sync CACHE */ - hcchar = sys_read32((mem_addr_t)&chan_regs->hcchar); - hcchar |= USB_DWC2_HCCHAR0_CHENA; - hcchar &= ~USB_DWC2_HCCHAR0_CHDIS; - sys_write32(hcchar, (mem_addr_t)&chan_regs->hcchar); -} - /* * Interrupt handler (ISR) * @@ -821,7 +777,7 @@ static void uhc_dwc2_isr_chan_handler(const struct device *const dev, } else if (uhc_dwc2_buffer_is_done(chan)) { chan_event |= BIT(DWC2_CHAN_EVENT_CPLT); } else { - uhc_dwc2_buffer_exec_proceed(dev, chan); + uhc_dwc2_buffer_continue(dev, chan); } } else if (hcint & USB_DWC2_HCINT_XFERCOMPL) { @@ -1331,36 +1287,6 @@ static inline void uhc_dwc2_handle_chan_events(const struct device *const dev, } } -static inline int uhc_dwc2_submit_ctrl_xfer(const struct device *const dev, - struct uhc_dwc2_chan *const chan) -{ - struct uhc_transfer *const xfer = uhc_xfer_get_next(dev); - - LOG_HEXDUMP_INF(xfer->setup_pkt, 8, "setup"); - - LOG_DBG("endpoint=%02Xh, mps=%d, interval=%d, start_frame=%d, stage=%d, no_status=%d", - xfer->ep, xfer->mps, xfer->interval, xfer->start_frame, xfer->stage, - xfer->no_status); - - /* TODO: Check that XFER has not already been enqueued? */ - - /* net_buf library default to sizeof(void *) alignment, which is at least 4 bytes */ - if (((uintptr_t)xfer->setup_pkt % 4)) { - LOG_WRN("Setup packet address %p is not 4-byte aligned", (void *)xfer->setup_pkt); - } - - /* TODO: Buffer addr that will used as dma addr also should be aligned */ - if (xfer->buf != NULL && (uintptr_t)net_buf_tail(xfer->buf) % 4 != 0) { - LOG_WRN("XFER buffer address %08lXh is not 4-byte aligned", - (uintptr_t)net_buf_tail(xfer->buf)); - } - - uhc_dwc2_buffer_fill_ctrl(chan, xfer); - uhc_dwc2_buffer_exec(dev, chan); - - return 0; -} - static void uhc_dwc2_thread(void *const arg1, void *const arg2, void *const arg3) { const struct device *const dev = (const struct device *)arg1; @@ -1435,34 +1361,176 @@ static int uhc_dwc2_bus_resume(const struct device *const dev) return -ENOSYS; } -static int uhc_dwc2_enqueue(const struct device *const dev, struct uhc_transfer *const xfer) +static int uhc_dwc2_config_hctsiz(const struct device *const dev, struct uhc_dwc2_chan *const chan, + const size_t buf_size) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + uint16_t pkt_cnt; + uint16_t pkt_size; + uint8_t pkt_type = USB_EP_TYPE_CONTROL; + uint8_t pkt_pid; + uint32_t hctsiz; + + pkt_cnt = calc_packet_count(buf_size, chan->ep_mps); + pkt_size = MIN(buf_size, chan->ep_mps); + + switch (pkt_type) { + case USB_EP_TYPE_CONTROL: + pkt_pid = USB_DWC2_HCTSIZ_PID_SETUP; + pkt_cnt = calc_packet_count(sizeof(struct usb_setup_packet), chan->ep_mps); + pkt_size = sizeof(struct usb_setup_packet); + break; + case USB_EP_TYPE_BULK: + case USB_EP_TYPE_INTERRUPT: + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA0; + break; + case USB_EP_TYPE_ISO: + if (dwc2_hal_get_port_speed(dwc2) == UHC_DWC2_SPEED_HIGH) { + /* Full-Speed isochronous transfers are always having a single packet: + * - only 1 pkt needed: DATA0 + */ + if (pkt_cnt != 1) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA0; + } else { + return -EINVAL; + } + } else if (USB_EP_DIR_IS_OUT(chan->ep_addr)) { + /* High-Speed isochronous OUT transfers are "high-bandwidth": + * - if 1 pkt needed: DATA0 + * - if 2 pkt needed: MDATA, DATA1 + * - if 3 pkt needed: MDATA, MDATA, DATA2 + */ + if (pkt_cnt == 1) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA0; + } else { + pkt_pid = USB_DWC2_HCTSIZ_PID_MDATA; + } + } else { + /* High-Speed isochronous IN transfers are "high-bandwidth": + * - if 1 pkt needed: DATA0 + * - if 2 pkt needed: DATA1, DATA0 + * - if 3 pkt needed: DATA2, DATA1, DATA0 + */ + if (pkt_cnt == 1) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA0; + } else if (pkt_cnt == 2) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA1; + } else if (pkt_cnt == 3) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA2; + } else { + LOG_WRN("unsupported transfer size %d, aborting", buf_size); + return -EINVAL; + } + } + break; + default: + LOG_WRN("unsupported transfer type %d, aborting", pkt_type); + return -ENOSYS; + } + + LOG_DBG("ep=%02X, mps=%d", chan->ep_addr, chan->ep_mps); + + if (USB_EP_GET_IDX(chan->ep_addr) == 0) { + /* Control stage is always OUT */ + sys_clear_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); + } + + hctsiz = (pkt_pid << USB_DWC2_HCTSIZ_PID_POS) & USB_DWC2_HCTSIZ_PID_MASK; + hctsiz |= (pkt_cnt << USB_DWC2_HCTSIZ_PKTCNT_POS) & USB_DWC2_HCTSIZ_PKTCNT_MASK; + hctsiz |= (pkt_size << USB_DWC2_HCTSIZ_XFERSIZE_POS) & USB_DWC2_HCTSIZ_XFERSIZE_MASK; + sys_write32(hctsiz, (mem_addr_t)&chan_regs->hctsiz); + + return 0; +} + +static int uhc_dwc2_process_xfer(const struct device *const dev, struct uhc_transfer *const xfer) { + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct uhc_dwc2_chan *const chan = &priv->chan[USB_EP_GET_IDX(xfer->ep)]; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + const size_t buf_size = USB_EP_DIR_IS_IN(xfer->ep) ? xfer->buf->size : xfer->buf->len; + uint8_t pkt_type; + uint32_t hcint; + uint32_t hcchar; int ret; - (void)uhc_xfer_append(dev, xfer); + /* net_buf library default to sizeof(void *) alignment, which is at least 4 bytes */ + if (xfer->ep == 0 && (uintptr_t)xfer->setup_pkt % 4 != 0) { + LOG_ERR("Setup packet address %p is not 4-byte aligned", (void *)xfer->setup_pkt); + return -EINVAL; + } + + /* TODO: Buffer addr that will used as dma addr also should be aligned */ + if (xfer->buf != NULL && (uintptr_t)net_buf_tail(xfer->buf) % 4 != 0) { + LOG_ERR("XFER buffer address %08lXh is not 4-byte aligned", + (uintptr_t)net_buf_tail(xfer->buf)); + return -EINVAL; + } + + if (xfer->interval != 0) { + LOG_ERR("Periodic transfer is not supported"); + return -EINVAL; + } uhc_lock_internal(dev, K_FOREVER); - if (USB_EP_GET_IDX(xfer->ep) == 0) { - ret = uhc_dwc2_submit_ctrl_xfer(dev, &priv->chan[0]); - if (ret) { - LOG_ERR("Failed to submit xfer: %d", ret); - goto err; - } + /* TODO: Use bmAttributes from the descriptors from the host class */ + if (USB_EP_GET_IDX(xfer->ep) == 0x00 || USB_EP_GET_IDX(xfer->ep) == 0x80) { + pkt_type = USB_EP_TYPE_CONTROL; } else { - LOG_ERR("Non-control endpoint enqueue not implemented yet"); - ret = -ENOSYS; + pkt_type = USB_EP_TYPE_BULK; + } + + if (pkt_type == USB_EP_TYPE_CONTROL) { + uhc_dwc2_buffer_fill_ctrl(chan, xfer); + LOG_DBG("endpoint=%02Xh, mps=%d, interval=%d, start_frame=%d, stage=%d, no_status=%d", + xfer->ep, xfer->mps, xfer->interval, xfer->start_frame, xfer->stage, + xfer->no_status); + LOG_HEXDUMP_INF(xfer->setup_pkt, 8, "setup"); + } + + ret = uhc_dwc2_config_hctsiz(dev, chan, buf_size); + if (ret != 0) { goto err; } + switch (pkt_type) { + case USB_EP_TYPE_CONTROL: + sys_write32((uint32_t)xfer->setup_pkt, (mem_addr_t)&chan_regs->hcdma); + break; + default: + sys_write32((uint32_t)xfer->buf->data, (mem_addr_t)&chan_regs->hcdma); + break; + } + + /* TODO: Configure split transaction if needed */ + + hcint = sys_read32((mem_addr_t)&chan_regs->hcint); + sys_write32(hcint, (mem_addr_t)&chan_regs->hcint); + + /* TODO: sync CACHE */ + + hcchar = sys_read32((mem_addr_t)&chan_regs->hcchar); + hcchar |= USB_DWC2_HCCHAR0_CHENA; + hcchar &= ~USB_DWC2_HCCHAR0_CHDIS; + sys_write32(hcchar, (mem_addr_t)&chan_regs->hcchar); + ret = 0; err: uhc_unlock_internal(dev); - return ret; } +static int uhc_dwc2_enqueue(const struct device *const dev, struct uhc_transfer *const xfer) +{ + uhc_xfer_append(dev, xfer); + return uhc_dwc2_process_xfer(dev, uhc_xfer_get_next(dev)); +} + static int uhc_dwc2_dequeue(const struct device *const dev, struct uhc_transfer *const xfer) { LOG_ERR("%s not implemented", __func__);