From eb7953ce22e385216a938057829d31d57a083d78 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Thu, 13 Nov 2025 17:41:54 +0000 Subject: [PATCH 1/5] dts: nrf54lm20a: apply modifications required by the linting tool A new linting tool got introduced, and prior to submit new contributions to .dtsi files, files need to be made compliant. Signed-off-by: Josuah Demangeon --- dts/vendor/nordic/nrf54lm20a.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dts/vendor/nordic/nrf54lm20a.dtsi b/dts/vendor/nordic/nrf54lm20a.dtsi index 7e511c8ff6c68..b0f70d437a55e 100644 --- a/dts/vendor/nordic/nrf54lm20a.dtsi +++ b/dts/vendor/nordic/nrf54lm20a.dtsi @@ -99,6 +99,7 @@ #ifdef USE_NON_SECURE_ADDRESS_MAP /* intentionally empty because UICR is hardware fixed to Secure */ #else + uicr: uicr@ffd000 { compatible = "nordic,nrf-uicr"; reg = <0xffd000 0x1000>; @@ -128,6 +129,7 @@ #size-cells = <1>; ranges = <0x0 0x40000000 0x10000000>; #else + global_peripherals: peripheral@50000000 { reg = <0x50000000 0x10000000>; ranges = <0x0 0x50000000 0x10000000>; @@ -763,6 +765,7 @@ #ifdef USE_NON_SECURE_ADDRESS_MAP /* intentionally empty because WDT30 is hardware fixed to Secure */ #else + wdt30: watchdog@108000 { compatible = "nordic,nrf-wdt"; reg = <0x108000 0x620>; From 2d4f3413a82b0caa2341203a68be92af29446ae4 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Thu, 6 Nov 2025 22:22:00 +0000 Subject: [PATCH 2/5] west: tempoprary commit for updating the HAL ref Update hal_espressif to include DWC2 headers when compiled with USB Host Controller (UHC) as well, instead of USB Device Controller only. Signed-off-by: Josuah Demangeon --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index cafcb2a1b39ea..05c02b738b94f 100644 --- a/west.yml +++ b/west.yml @@ -169,7 +169,7 @@ manifest: groups: - hal - name: hal_espressif - revision: af6cfa2e3e7098b596062ab516b80a48a7ba7332 + revision: refs/pull/500/head path: modules/hal/espressif west-commands: west/west-commands.yml groups: From 92191863685d067c3eaebcc88c0c3e4f66cdafe1 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Wed, 23 Jul 2025 17:10:43 +0200 Subject: [PATCH 3/5] 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 3a6e99ed78dcf237df5a1a693f152d8318b9708c Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 24 Jul 2025 13:30:28 +0200 Subject: [PATCH 4/5] 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 8c3e2a37cc34f4ab028f067942d62104d33dfcb7 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Wed, 23 Jul 2025 18:35:38 +0200 Subject: [PATCH 5/5] 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 | 1655 +++++++++++++++++ drivers/usb/uhc/uhc_dwc2.h | 106 ++ drivers/usb/uhc/uhc_dwc2_vendor_quirks.h | 443 +++++ 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, 2313 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..70abadc0c222d --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2.c @@ -0,0 +1,1655 @@ +/* + * 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))) + +#define UHC_DWC2_FIFODEPTH(config) \ + ((uint32_t)(((config)->ghwcfg3 & USB_DWC2_GHWCFG3_DFIFODEPTH_MASK) >> \ + USB_DWC2_GHWCFG3_DFIFODEPTH_POS)) + +#define UHC_DWC2_NUMHSTCHNL(config) \ + ((uint32_t)(((config)->ghwcfg2 & USB_DWC2_GHWCFG2_NUMHSTCHNL_MASK) >> \ + USB_DWC2_GHWCFG2_NUMHSTCHNL_POS)) + +#define UHC_DWC2_OTGARCH(config) \ + ((uint32_t)(((config)->ghwcfg2 & USB_DWC2_GHWCFG2_OTGARCH_MASK) >> \ + USB_DWC2_GHWCFG2_OTGARCH_POS)) + +#define UHC_DWC2_HSPHYTYPE(config) \ + ((uint32_t)(((config)->ghwcfg2 & USB_DWC2_GHWCFG2_HSPHYTYPE_MASK) >> \ + USB_DWC2_GHWCFG2_HSPHYTYPE_POS)) + +#define UHC_DWC2_FSPHYTYPE(config) \ + ((uint32_t)(((config)->ghwcfg2 & USB_DWC2_GHWCFG2_FSPHYTYPE_MASK) >> \ + USB_DWC2_GHWCFG2_FSPHYTYPE_POS)) + +#define UHC_DWC2_PHYDATAWIDTH(config) \ + ((uint32_t)(((config)->ghwcfg4 & USB_DWC2_GHWCFG4_PHYDATAWIDTH_MASK) >> \ + USB_DWC2_GHWCFG4_PHYDATAWIDTH_POS)) + +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_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); + const uint32_t nptx_largest = EPSIZE_BULK_FS / 4; + const uint32_t ptx_largest = 256 / 4; + + LOG_DBG("Configuring FIFO sizes"); + + /* TODO: support HS */ + + priv->fifo_top = UHC_DWC2_FIFODEPTH(config) - UHC_DWC2_NUMHSTCHNL(config); + priv->fifo_nptxfsiz = 2 * nptx_largest; + priv->fifo_rxfsiz = 2 * (ptx_largest + 2) + UHC_DWC2_NUMHSTCHNL(config); + 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 dwc2_port_enable(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 = sys_read32((mem_addr_t)&dwc2->hcfg); + uint32_t hfir = sys_read32((mem_addr_t)&dwc2->hfir); + uint8_t fslspclksel; + uint16_t frint; + + /* 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 (UHC_DWC2_HSPHYTYPE(config) == 0) { + /* 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. + */ + fslspclksel = (speed == UHC_DWC2_SPEED_FULL) ? 1 : 2; + hcfg &= ~USB_DWC2_HCFG_FSLSPCLKSEL_MASK; + hcfg |= (fslspclksel << USB_DWC2_HCFG_FSLSPCLKSEL_POS); + + /* 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. + */ + frint = (speed == UHC_DWC2_SPEED_FULL) ? 48000 : 6000; + hfir &= ~USB_DWC2_HFIR_FRINT_MASK; + hfir |= (frint << USB_DWC2_HFIR_FRINT_POS); + + sys_write32(hcfg, (mem_addr_t)&dwc2->hcfg); + sys_write32(hfir, (mem_addr_t)&dwc2->hfir); + } else { + LOG_ERR("Configuring clocks for HS PHY is not implemented"); + } +} + +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; + } + + dwc2_apply_fifo_config(dev); + dwc2_hal_set_frame_list(dwc2, NULL /* priv->frame_list , FRAME_LIST_LEN */); + dwc2_hal_periodic_enable(dwc2); + 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)); + } + + 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; (i = __builtin_ffs(channels)) != 0; channels &= !BIT(i - 1)) { + uhc_dwc2_isr_chan_handler(dev, &priv->chan[i - 1]); + } + } + + 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 (UHC_DWC2_HSPHYTYPE(config) != 0) { + gusbcfg = sys_read32((mem_addr_t)&dwc2->gusbcfg); + + /* De-select FS PHY */ + gusbcfg &= ~USB_DWC2_GUSBCFG_PHYSEL_USB11; + + if (UHC_DWC2_HSPHYTYPE(config) == 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 (UHC_DWC2_PHYDATAWIDTH(config)) { + 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 (UHC_DWC2_OTGARCH(config) == 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 inline bool uhc_dwc2_port_debounced(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; + bool is_connected; + + k_msleep(DEBOUNCE_DELAY_MS); + + /* Check the post-debounce state (i.e., whether it's actually connected/disconnected) */ + is_connected = ((sys_read32((mem_addr_t)&dwc2->hprt) & USB_DWC2_HPRT_PRTCONNSTS) != 0); + if (is_connected) { + priv->port_state = UHC_PORT_STATE_DISABLED; + } else { + priv->port_state = UHC_PORT_STATE_DISCONNECTED; + } + + uhc_dwc2_debounce_disable(dev); + + return is_connected; +} + +/* + * 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) +{ + static const char *const uhc_dwc2_speed_str[] = {"High", "Full", "Low"}; + enum uhc_event_type type; + + LOG_WRN("New dev, %s Speed", uhc_dwc2_speed_str[speed]); + + switch (speed) { + case UHC_DWC2_SPEED_LOW: + type = UHC_EVT_DEV_CONNECTED_LS; + break; + case UHC_DWC2_SPEED_FULL: + type = UHC_EVT_DEV_CONNECTED_FS; + break; + case UHC_DWC2_SPEED_HIGH: + type = UHC_EVT_DEV_CONNECTED_HS; + break; + default: + LOG_ERR("Unsupported speed %d", speed); + return; + } + uhc_submit_event(dev, type, 0); +} + +/* + * 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)) { + /* Initialize remaining host port registers */ + dwc2_port_enable(dev); + + priv->port_state = UHC_PORT_STATE_ENABLED; + port_speed = dwc2_hal_get_port_speed(dwc2); + + uhc_dwc2_chan_config(dev, 0, 0, 0, UHC_DWC2_SPEED_FULL, 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 */ + if (priv->port_state != UHC_PORT_STATE_RESETTING) { + /* 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)) { + /* Don't update state immediately, we still need to debounce. */ + if (uhc_dwc2_port_debounced(dev)) { + uhc_dwc2_port_reset(dev); + } else { + LOG_ERR("Port is not connected after debounce"); + /* TODO: Simulate and/or verify */ + LOG_WRN("Port debounce error handling is not implemented yet"); + } + } + + 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); + } + + /* Recover the port */ + 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? */ + + /* TODO: setup packet must be aligned 4 bytes? */ + if (((uintptr_t)xfer->setup_pkt % 4)) { + LOG_WRN("Setup packet address %p is not 4-byte aligned", 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 < 32; 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; + } + + /* 1. 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); + + /* 2. Program the GAHBCFG register */ + uhc_dwc2_init_gahbcfg(dev); + + /* 3. Disable RX FIFO level interrupts for the time of the configuration */ + /* TODO */ + + /* 4. Configure the reference clock */ + /* TODO */ + + /* 5. Program the GUSBCFG register */ + uhc_dwc2_init_gusbcfg(dev); + + /* 6. Disable OTG and mode-mismatch interrupts */ + /* TODO */ + + 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..02b77274dd777 --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2_vendor_quirks.h @@ -0,0 +1,443 @@ +/* + * 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 +#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 d5b0167f26b32..2146005a2507e 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 b0f70d437a55e..90c4fb31c1c62 100644 --- a/dts/vendor/nordic/nrf54lm20a.dtsi +++ b/dts/vendor/nordic/nrf54lm20a.dtsi @@ -231,8 +231,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>; status = "disabled"; }; diff --git a/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi b/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi index 4a38170ac4a87..c333096ab311e 100644 --- a/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi +++ b/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi @@ -407,8 +407,10 @@ clocks = <&clock ESP32_USB_MODULE>; num-out-eps = <6>; num-in-eps = <6>; + gsnpsid = <0x4f54400a>; ghwcfg1 = <0x00000000>; ghwcfg2 = <0x224dd930>; + ghwcfg3 = <0x00c804b5>; ghwcfg4 = <0xd3f0a030>; }; 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"; +};