diff --git a/boards/nordic/nrf54lm20dk/nrf54lm20_a_b_cpuapp_common.dtsi b/boards/nordic/nrf54lm20dk/nrf54lm20_a_b_cpuapp_common.dtsi index fc4275f44bc12..42c73d126c453 100644 --- a/boards/nordic/nrf54lm20dk/nrf54lm20_a_b_cpuapp_common.dtsi +++ b/boards/nordic/nrf54lm20dk/nrf54lm20_a_b_cpuapp_common.dtsi @@ -110,6 +110,10 @@ temp_sensor: &temp { status = "okay"; }; +zephyr_uhc0: &usbhs { + status = "okay"; +}; + zephyr_udc0: &usbhs { status = "okay"; }; diff --git a/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20a_cpuapp.yaml b/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20a_cpuapp.yaml index 36c37206b219a..571be8ea2ed74 100644 --- a/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20a_cpuapp.yaml +++ b/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20a_cpuapp.yaml @@ -21,4 +21,5 @@ supported: - pwm - spi - usbd + - usbh - watchdog diff --git a/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20a_cpuapp_ns.yaml b/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20a_cpuapp_ns.yaml index 2bd2c023ca718..0edce1a2a541f 100644 --- a/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20a_cpuapp_ns.yaml +++ b/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20a_cpuapp_ns.yaml @@ -17,6 +17,7 @@ supported: - pwm - spi - usbd + - usbh - watchdog vendor: nordic sysbuild: true diff --git a/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20b_cpuapp.yaml b/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20b_cpuapp.yaml index 6b1a8ed806ae4..55c4d2ccea8f0 100644 --- a/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20b_cpuapp.yaml +++ b/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20b_cpuapp.yaml @@ -21,4 +21,5 @@ supported: - pwm - spi - usbd + - usbh - watchdog diff --git a/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20b_cpuapp_ns.yaml b/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20b_cpuapp_ns.yaml index afd7bcf905c00..8c94a106832c5 100644 --- a/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20b_cpuapp_ns.yaml +++ b/boards/nordic/nrf54lm20dk/nrf54lm20dk_nrf54lm20b_cpuapp_ns.yaml @@ -17,6 +17,7 @@ supported: - pwm - spi - usbd + - usbh - watchdog vendor: nordic sysbuild: true diff --git a/drivers/usb/common/usb_dwc2_hw.h b/drivers/usb/common/usb_dwc2_hw.h index 50022d1a3eeb7..641713ef0d352 100644 --- a/drivers/usb/common/usb_dwc2_hw.h +++ b/drivers/usb/common/usb_dwc2_hw.h @@ -374,7 +374,12 @@ USB_DWC2_SET_FIELD_DEFINE(gnptxfsiz_nptxfstaddr, GNPTXFSIZ_NPTXFSTADDR) #define USB_DWC2_GGPIO_STM32_PWRDWN BIT(USB_DWC2_GGPIO_STM32_PWRDWN_POS) /* GSNPSID register */ +#define USB_DWC2_GSNPSID_REV_4_20A 0x4F54420AUL #define USB_DWC2_GSNPSID_REV_5_00A 0x4F54500AUL +#define USB_DWC2_GSNPSID_REV_POS 0UL +#define USB_DWC2_GSNPSID_REV_MASK (0xFFFFUL << USB_DWC2_GSNPSID_REV_POS) + +USB_DWC2_GET_FIELD_DEFINE(gsnpsid_rev, GSNPSID_REV) /* GHWCFG1 register */ #define USB_DWC2_GHWCFG1 0x0044UL 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 a9cdbad42e4d2..c4c2895d9bfc0 100644 --- a/drivers/usb/uhc/Kconfig +++ b/drivers/usb/uhc/Kconfig @@ -43,6 +43,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..6cc10d0579b79 --- /dev/null +++ b/drivers/usb/uhc/Kconfig.dwc2 @@ -0,0 +1,91 @@ +# Copyright (c) 2026 Roman Leonov +# SPDX-License-Identifier: Apache-2.0 + +config UHC_DWC2 + bool "DWC2 USB host controller driver" + depends on DT_HAS_SNPS_DWC2_ENABLED + default y + select EVENTS + select REGULATOR + help + DWC2 USB host controller driver. + +if UHC_DWC2 +config UHC_DWC2_MAX_CHANNELS + int "UHC DWC2 driver maximum supported number of channels" + default 16 + range 1 16 + help + Number of channels driver can support. + Reduce this value to save memory. + Increase to support more concurrent transfers. + +config UHC_DWC2_STACK_SIZE + int "UHC DWC2 driver thread stack size" + default 512 + help + DWC2 driver thread stack size. + +config UHC_DWC2_THREAD_PRIORITY + int "UHC DWC2 driver thread priority" + default 8 + help + DWC2 driver thread priority. + +config UHC_DWC2_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_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_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_SET_ADDR_DELAY_MS + int "Delay after set device address" + 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. + +config UHC_DWC2_USBHS_VBUS_READY_TIMEOUT + int "USBHS VBUS ready event timeout in ms" + depends on SOC_SERIES_NRF54L + default 0 + help + VBUS ready event timeout for the USBHS variant of DWC2 found on nRF MCUs. + If the VBUS is not ready and the Nordic USBHS controller is used, the udc_enable() is + blocked for this amount of time. Set it to zero to wait forever. + +endif # UHC_DWC2 diff --git a/drivers/usb/uhc/uhc_dwc2.c b/drivers/usb/uhc/uhc_dwc2.c new file mode 100644 index 0000000000000..9db4fa8d62144 --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2.c @@ -0,0 +1,1665 @@ +/* + * Copyright (c) 2026 Roman Leonov + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT snps_dwc2 + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + +#include "uhc_common.h" +#include "uhc_dwc2.h" + +#define DEBOUNCE_DELAY_MS CONFIG_UHC_DWC2_DEBOUNCE_DELAY_MS +#define RESET_HOLD_MS CONFIG_UHC_DWC2_RESET_HOLD_MS +#define RESET_RECOVERY_MS CONFIG_UHC_DWC2_RESET_RECOVERY_MS +#define SET_ADDR_DELAY_MS CONFIG_UHC_DWC2_SET_ADDR_DELAY_MS +#define MAX_CHANNELS CONFIG_UHC_DWC2_MAX_CHANNELS + +enum uhc_dwc2_event { + /* A device has been connected to the port */ + UHC_DWC2_EVENT_PORT_CONNECTION, + /* A device is connected to the port but has not been reset. */ + UHC_DWC2_EVENT_PORT_DISABLED, + /* A device disconnection has been detected */ + UHC_DWC2_EVENT_PORT_DISCONNECTION, + /* Port error detected */ + UHC_DWC2_EVENT_PORT_ERROR, + /* Overcurrent detected */ + UHC_DWC2_EVENT_PORT_OVERCURRENT, + /* Port has pending channel event */ + UHC_DWC2_EVENT_PORT_PEND_CHANNEL +}; + +enum uhc_dwc2_channel_event { + /* The channel has completed a transfer. Channel is now halted */ + UHC_DWC2_CHANNEL_EVENT_CPLT, + /* The channel has completed a transfer. Request STALLed. Channel is now halted */ + UHC_DWC2_CHANNEL_EVENT_STALL, + /* The channel has encountered an error. Channel is now halted */ + UHC_DWC2_CHANNEL_EVENT_ERROR, + /* Need to release the channel for the next transfer */ + UHC_DWC2_CHANNEL_DO_RELEASE, + /* Need to submit the transfer after channel error. Channel is now halted */ + UHC_DWC2_CHANNEL_DO_REINIT, + /* Need to rewind the buffer being transmitted. Channel is now halted */ + UHC_DWC2_CHANNEL_DO_REWIND, +}; + +enum { + EPSIZE_BULK_FS = 64, + EPSIZE_BULK_HS = 512, + + EPSIZE_ISO_FS_MAX = 1023, + EPSIZE_ISO_HS_MAX = 1024, +}; + +#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)) + + +/* Mask to clear HPRT register */ +#define USB_DWC2_HPRT_W1C_MSK (USB_DWC2_HPRT_PRTENA | \ + USB_DWC2_HPRT_PRTCONNDET | \ + USB_DWC2_HPRT_PRTENCHNG | \ + USB_DWC2_HPRT_PRTOVRCURRCHNG) + +/* Mask of errors received for host channels (STALL excluded) */ +#define USB_DWC2_HCINT_ERRORS (USB_DWC2_HCINT_AHBERR | \ + USB_DWC2_HCINT_XACTERR | \ + USB_DWC2_HCINT_BBLERR | \ + USB_DWC2_HCINT_FRMOVRUN | \ + USB_DWC2_HCINT_DTGERR) + +/* Mask of reason of transfer completion for host channels */ +#define USB_DWC2_HCINT_CPLT_BITS (USB_DWC2_HCINT_XFERCOMPL | \ + USB_DWC2_HCINT_STALL | \ + USB_DWC2_HCINT_BBLERR | \ + USB_DWC2_HCINT_XACTERR) +struct uhc_dwc2_channel { + /* Index of the channel */ + uint8_t index; + /* Accessed in ISR. Number of error to track errors for transactions. */ + uint8_t error_count; + /* Used */ + uint32_t hcint_pending; + /* Cached pointer to channel registers */ + const struct usb_dwc2_host_chan *regs; + /* Pointer to the transfer */ + struct uhc_transfer *xfer; + /* Channel events */ + atomic_t events; + + /* Channel flags */ + /* Transfer stage is IN */ + uint8_t data_stg_in: 1; + /* Transfer has no data stage */ + uint8_t data_stg_skip: 1; + /* Transfer will change the device address */ + uint8_t set_address: 1; +}; + +struct uhc_dwc2_data { + struct k_thread thread; + /* Event bitmask the driver thread waits for */ + struct k_event events; + /* Semaphore used to indicate that port is enabled */ + struct k_sem sem_port_enabled; + /* Port channels */ + struct uhc_dwc2_channel channels[MAX_CHANNELS]; + /* Bit mask of channels with pending interrupts */ + uint32_t pending_channels_msk; + /* Root Port flags */ + uint8_t debouncing: 1; + uint8_t has_device: 1; +}; + +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 inline void dwc2_hal_set_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_set_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 inline uint32_t dwc2_hal_get_speed(struct usb_dwc2_reg *const dwc2) +{ + uint32_t hprt = sys_read32((mem_addr_t)&dwc2->hprt); + + return usb_dwc2_get_hprt_prtspd(hprt); +} + +static int dwc2_hal_flush_rx_fifo(struct usb_dwc2_reg *const dwc2) +{ + const k_timepoint_t timepoint = sys_timepoint_calc(K_MSEC(100)); + + sys_write32(USB_DWC2_GRSTCTL_RXFFLSH, (mem_addr_t)&dwc2->grstctl); + + while (sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_RXFFLSH) { + k_busy_wait(1); + + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for RXFFLSH timeout"); + return -ETIMEDOUT; + } + } + + return 0; +} + +static int dwc2_hal_flush_tx_fifo(struct usb_dwc2_reg *const dwc2, const uint8_t fnum) +{ + const k_timepoint_t timepoint = sys_timepoint_calc(K_MSEC(100)); + 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) { + k_busy_wait(1); + + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for TXFFLSH timeout"); + return -ETIMEDOUT; + } + } + + return 0; +} + +static void dwc2_hal_config_hcfg_fsls(struct usb_dwc2_reg *const dwc2) +{ + uint32_t hcfg = sys_read32((mem_addr_t)&dwc2->hcfg); + + /* We can select Buffer DMA of Scatter-Gather DMA mode here: Buffer DMA for now */ + hcfg &= ~USB_DWC2_HCFG_DESCDMA; + /* Disable periodic scheduling, will enable later */ + hcfg &= ~USB_DWC2_HCFG_PERSCHEDENA; + /* 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. + */ + hcfg &= ~USB_DWC2_HCFG_FSLSPCLKSEL_MASK; + if (dwc2_hal_get_speed(dwc2) == USB_DWC2_HPRT_PRTSPD_FULL) { + hcfg |= (USB_DWC2_HCFG_FSLSPCLKSEL_CLK48 << USB_DWC2_HCFG_FSLSPCLKSEL_POS); + } else { + hcfg |= (USB_DWC2_HCFG_FSLSPCLKSEL_CLK6 << USB_DWC2_HCFG_FSLSPCLKSEL_POS); + } + sys_write32(hcfg, (mem_addr_t)&dwc2->hcfg); +} + +static void dwc2_hal_config_hcfg_hs(struct usb_dwc2_reg *const dwc2) +{ + LOG_WRN("HCFG config for High speed is not implemented yet"); +} + +static void dwc2_hal_config_hfir_fsls(struct usb_dwc2_reg *const dwc2) +{ + uint32_t hfir = sys_read32((mem_addr_t)&dwc2->hfir); + + /* 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; + if (dwc2_hal_get_speed(dwc2) == USB_DWC2_HPRT_PRTSPD_FULL) { + hfir |= usb_dwc2_set_hfir_frint(48000); + } else { + hfir |= usb_dwc2_set_hfir_frint(6000); + } + sys_write32(hfir, (mem_addr_t)&dwc2->hfir); +} + +static void dwc2_hal_config_hfir_hs(struct usb_dwc2_reg *const dwc2) +{ + uint32_t hfir = sys_read32((mem_addr_t)&dwc2->hfir); + + /* Disable dynamic loading */ + hfir &= ~USB_DWC2_HFIR_HFIRRLDCTRL; + /* TODO: Program the FrInt based on the UTMI+/ULPI clock */ + /* Leave empty for now to calculate the value based on HCFG */ + + sys_write32(hfir, (mem_addr_t)&dwc2->hfir); +} + +static inline void dwc2_hal_enable_port(struct usb_dwc2_reg *const dwc2) +{ + uint32_t ghwcfg2 = sys_read32((mem_addr_t)&dwc2->ghwcfg2); + + if (usb_dwc2_get_ghwcfg2_hsphytype(ghwcfg2) == 0) { + dwc2_hal_config_hcfg_fsls(dwc2); + dwc2_hal_config_hfir_fsls(dwc2); + } else { + dwc2_hal_config_hcfg_hs(dwc2); + dwc2_hal_config_hfir_hs(dwc2); + } +} + +static inline int dwc2_hal_init_gusbcfg(struct usb_dwc2_reg *const dwc2) +{ + uint32_t gusbcfg = sys_read32((mem_addr_t)&dwc2->gusbcfg); + uint32_t ghwcfg2 = sys_read32((mem_addr_t)&dwc2->ghwcfg2); + uint32_t ghwcfg4 = sys_read32((mem_addr_t)&dwc2->ghwcfg4); + const k_timepoint_t timepoint = sys_timepoint_calc(K_MSEC(100)); + + /* 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) == 0) { + k_busy_wait(1); + + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for Host mode timeout, GINTSTS 0x%08x", + sys_read32((mem_addr_t)&dwc2->gintsts)); + return -ETIMEDOUT; + } + } + + if (usb_dwc2_get_ghwcfg2_hsphytype(ghwcfg2) != 0) { + /* De-select FS PHY */ + gusbcfg &= ~USB_DWC2_GUSBCFG_PHYSEL_USB11; + + if (usb_dwc2_get_ghwcfg2_hsphytype(ghwcfg2) == USB_DWC2_GHWCFG2_HSPHYTYPE_ULPI) { + LOG_DBG("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 Suspend mode */ + gusbcfg &= ~(USB_DWC2_GUSBCFG_ULPIFSLS | USB_DWC2_GUSBCFG_ULPICLK_SUSM); + } else { + LOG_DBG("Highspeed UTMI+ PHY init"); + /* Select UTMI+ PHY (internal) */ + gusbcfg &= ~USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_ULPI; + /* Set 16-bit interface if supported */ + if (usb_dwc2_get_ghwcfg4_phydatawidth(ghwcfg4)) { + gusbcfg |= USB_DWC2_GUSBCFG_PHYIF_16_BIT; + } else { + gusbcfg &= ~USB_DWC2_GUSBCFG_PHYIF_16_BIT; + } + } + sys_write32(gusbcfg, (mem_addr_t)&dwc2->gusbcfg); + } else { + LOG_DBG("Fullspeed PHY init"); + sys_set_bits((mem_addr_t)&dwc2->gusbcfg, USB_DWC2_GUSBCFG_PHYSEL_USB11); + } + + return 0; +} + +static inline int dwc2_hal_init_gahbcfg(struct usb_dwc2_reg *const dwc2) +{ + uint32_t ghwcfg2; + uint32_t gahbcfg; + + /* Disable Global Interrupt */ + sys_clear_bits((mem_addr_t)&dwc2->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK); + + /* Configure AHB */ + gahbcfg = sys_read32((mem_addr_t)&dwc2->gahbcfg); + gahbcfg &= ~USB_DWC2_GAHBCFG_HBSTLEN_MASK; + gahbcfg |= usb_dwc2_set_gahbcfg_hbstlen(USB_DWC2_GAHBCFG_HBSTLEN_INCR16); + sys_write32(gahbcfg, (mem_addr_t)&dwc2->gahbcfg); + + ghwcfg2 = sys_read32((mem_addr_t)&dwc2->ghwcfg2); + if (usb_dwc2_get_ghwcfg2_otgarch(ghwcfg2) == USB_DWC2_GHWCFG2_OTGARCH_INTERNALDMA) { + sys_set_bits((mem_addr_t)&dwc2->gahbcfg, USB_DWC2_GAHBCFG_DMAEN); + } else { + /* TODO: Implement Slave mode */ + LOG_ERR("DMA isn't supported by the hardware"); + return -ENXIO; + } + + /* Enable Global Interrupt */ + sys_set_bits((mem_addr_t)&dwc2->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK); + return 0; +} + +static int dwc2_core_soft_reset(const struct device *dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + mem_addr_t grstctl_reg = (mem_addr_t)&dwc2->grstctl; + mem_addr_t gsnpsid_reg = (mem_addr_t)&dwc2->gsnpsid; + k_timepoint_t timepoint; + uint32_t gsnpsid; + uint32_t grstctl; + uint32_t gsnpsid_rev; + + /* Check AHB master idle state before starting reset */ + timepoint = sys_timepoint_calc(K_MSEC(100)); + while (!(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_AHBIDLE)) { + k_busy_wait(1); + + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for AHB idle timeout, GRSTCTL 0x%08x", + sys_read32(grstctl_reg)); + return -ETIMEDOUT; + } + } + + /* + * Load GSNPSID before reset. On some cores it may not be readable + * after reset is asserted. + */ + gsnpsid = sys_read32(gsnpsid_reg); + gsnpsid_rev = usb_dwc2_get_gsnpsid_rev(gsnpsid); + + /* Apply Core Soft Reset */ + sys_set_bits(grstctl_reg, USB_DWC2_GRSTCTL_CSFTRST); + + if (gsnpsid_rev < usb_dwc2_get_gsnpsid_rev(USB_DWC2_GSNPSID_REV_4_20A)) { + /* + * Before v4.20a, CSFTRST is self-clearing. + * Wait until hardware clears it. + */ + timepoint = sys_timepoint_calc(K_MSEC(100)); + while (sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRST) { + k_busy_wait(1); + + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for CSR clear timeout, GRSTCTL 0x%08x", + sys_read32(grstctl_reg)); + return -ETIMEDOUT; + } + + if (uhc_dwc2_quirk_is_phy_clk_off(dev)) { + LOG_ERR("Core soft reset stuck, PHY clock is off"); + return -EIO; + } + } + + /* Hardware requires at least 3 PHY clocks after CSFTRST clears. */ + k_busy_wait(1); + } else { + /* + * From v4.20a, CSFTRST is write-only/reset-controlled. + * Wait for CSFTRSTDONE, then clear it. + */ + timepoint = sys_timepoint_calc(K_MSEC(100)); + while (!(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_CSFTRSTDONE)) { + k_busy_wait(1); + + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for CSR done timeout, GRSTCTL 0x%08x", + sys_read32(grstctl_reg)); + return -ETIMEDOUT; + } + + if (uhc_dwc2_quirk_is_phy_clk_off(dev)) { + LOG_ERR("Core soft reset stuck, PHY clock is off"); + return -EIO; + } + } + + /* Clear CSFTRST, and write 1 to CSFTRSTDONE to clear the done indication. */ + grstctl = sys_read32(grstctl_reg); + grstctl &= ~USB_DWC2_GRSTCTL_CSFTRST; + grstctl |= USB_DWC2_GRSTCTL_CSFTRSTDONE; + sys_write32(grstctl, grstctl_reg); + } + + /* Wait for AHB master idle again after reset */ + timepoint = sys_timepoint_calc(K_MSEC(100)); + while (!(sys_read32(grstctl_reg) & USB_DWC2_GRSTCTL_AHBIDLE)) { + k_busy_wait(1); + + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for AHB idle after reset timeout, GRSTCTL 0x%08x", + sys_read32(grstctl_reg)); + return -ETIMEDOUT; + } + } + + LOG_DBG("DWC2 core reset done"); + + return 0; +} + +static int dwc2_hal_init_host(struct usb_dwc2_reg *const dwc2) +{ + uint32_t gintsts; + int ret; + + /* Init GUSBCFG */ + ret = dwc2_hal_init_gusbcfg(dwc2); + if (ret != 0) { + LOG_ERR("Unable to configure USB global register"); + return ret; + } + + /* Init GAHBCFG */ + ret = dwc2_hal_init_gahbcfg(dwc2); + if (ret != 0) { + /* TODO: Implement Slave Mode */ + LOG_WRN("DMA isn't supported, but Slave Mode isn't implemented yet"); + return ret; + } + + /* Clear interrupts */ + sys_clear_bits((mem_addr_t)&dwc2->gintmsk, 0xFFFFFFFFUL); + sys_set_bits((mem_addr_t)&dwc2->gintmsk, USB_DWC2_GINTSTS_DISCONNINT); + + /* Clear status */ + gintsts = sys_read32((mem_addr_t)&dwc2->gintsts); + sys_write32(gintsts, (mem_addr_t)&dwc2->gintsts); + + return ret; +} + +static int dwc2_hal_set_fifo_sizes(struct usb_dwc2_reg *const dwc2, + const uint32_t ghwcfg2, + const uint32_t ghwcfg3) +{ + const uint32_t nptx_largest = EPSIZE_BULK_FS / 4; + const uint32_t ptx_largest = 256 / 4; + const uint32_t dfifodepth = FIELD_GET(USB_DWC2_GHWCFG3_DFIFODEPTH_MASK, ghwcfg3); + const uint32_t numhstchnl = FIELD_GET(USB_DWC2_GHWCFG2_NUMHSTCHNL_MASK, ghwcfg2); + uint32_t fifo_available = dfifodepth - (numhstchnl + 1); + /* TODO: support HS */ + const uint16_t fifo_nptxfdep = 2 * nptx_largest; + const uint16_t fifo_rxfsiz = 2 * (ptx_largest + 2) + numhstchnl + 1; + const uint16_t fifo_ptxfsiz = fifo_available - (fifo_nptxfdep + fifo_rxfsiz); + uint32_t gdfifocfg; + uint32_t grxfsiz; + uint32_t gnptxfsiz; + uint32_t hptxfsiz; + int ret; + + LOG_DBG("Setting FIFO sizes: top=%u, nptx=%u, rx=%u, ptx=%u", + fifo_available * 4, fifo_nptxfdep * 4, fifo_rxfsiz * 4, fifo_ptxfsiz * 4); + + /* FIFO config */ + gdfifocfg = usb_dwc2_set_gdfifocfg_epinfobaseaddr(fifo_available); + gdfifocfg |= usb_dwc2_set_gdfifocfg_gdfifocfg(fifo_available); + sys_write32(gdfifocfg, (mem_addr_t)&dwc2->gdfifocfg); + + /* RX FIFO size */ + fifo_available -= fifo_rxfsiz; + grxfsiz = usb_dwc2_set_grxfsiz(fifo_rxfsiz); + sys_write32(grxfsiz, (mem_addr_t)&dwc2->grxfsiz); + + /* Non-periodic TX FIFO size */ + fifo_available -= fifo_nptxfdep; + gnptxfsiz = usb_dwc2_set_gnptxfsiz_nptxfdep(fifo_nptxfdep); + gnptxfsiz |= usb_dwc2_set_gnptxfsiz_nptxfstaddr(fifo_available); + sys_write32(gnptxfsiz, (mem_addr_t)&dwc2->gnptxfsiz); + + /* Periodic TX FIFO size */ + fifo_available -= fifo_ptxfsiz; + hptxfsiz = usb_dwc2_set_hptxfsiz_ptxfsize(fifo_ptxfsiz); + hptxfsiz |= fifo_available; + sys_write32(hptxfsiz, (mem_addr_t)&dwc2->hptxfsiz); + + /* Flush TX and RX FIFOs */ + ret = dwc2_hal_flush_tx_fifo(dwc2, MAX_CHANNELS); + if (ret != 0) { + return ret; + } + + return dwc2_hal_flush_rx_fifo(dwc2); +} + +static void uhc_dwc2_port_debounce_lock(const struct device *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); + /* Set the debounce lock flag */ + priv->debouncing = 1; +} + +static void uhc_dwc2_port_debounce_unlock(const struct device *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); + + /* Clear the flag */ + 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_channel_process_control(struct uhc_dwc2_channel *channel) +{ + struct uhc_transfer *const xfer = channel->xfer; + bool next_dir_is_in; + uint16_t size = 0; + uint16_t pkt_cnt; + uint8_t *dma_addr = NULL; + uint32_t hcchar; + uint32_t hctsiz; + + if (xfer->stage == UHC_CONTROL_STAGE_SETUP) { + /* Just finished UHC_CONTROL_STAGE_SETUP */ + if (channel->data_stg_skip) { + /* No data stage. Go strait to status */ + next_dir_is_in = true; + xfer->stage = UHC_CONTROL_STAGE_STATUS; + } else { + /* Data stage is present, go to data stage */ + next_dir_is_in = channel->data_stg_in; + xfer->stage = UHC_CONTROL_STAGE_DATA; + + /* + * NOTE: + * For OUT - number of bytes host sends to device + * For IN - number of bytes host reserves to receive + */ + if (!next_dir_is_in) { + size = xfer->buf->len; + } else { + /* TODO: Check the buffer is large enough for the next transfer? */ + size = net_buf_tailroom(xfer->buf); + } + + /* TODO: Toggle PID? */ + + if (xfer->buf != NULL) { + /* Get the tail of the buffer to append data */ + dma_addr = net_buf_tail(xfer->buf); + } + } + } else { + /* Just finished UHC_CONTROL_STAGE_DATA */ + hctsiz = sys_read32((mem_addr_t)&channel->regs->hctsiz); + /* Increase the net_buf for the actual transferred len */ + net_buf_add(xfer->buf, xfer->buf->size - usb_dwc2_get_hctsiz_xfersize(hctsiz)); + /* Status stage is always the opposite direction of data stage */ + next_dir_is_in = !channel->data_stg_in; + xfer->stage = UHC_CONTROL_STAGE_STATUS; + } + + /* Calculate new packet count */ + pkt_cnt = calc_packet_count(size, xfer->mps); + + if (next_dir_is_in) { + sys_set_bits((mem_addr_t)&channel->regs->hcchar, USB_DWC2_HCCHAR_EPDIR); + } else { + sys_clear_bits((mem_addr_t)&channel->regs->hcchar, USB_DWC2_HCCHAR_EPDIR); + } + + hctsiz = usb_dwc2_set_hctsiz_pid(USB_DWC2_HCTSIZ_PID_DATA1) | + usb_dwc2_set_hctsiz_pktcnt(pkt_cnt) | + usb_dwc2_set_hctsiz_xfersize(size); + + sys_write32(hctsiz, (mem_addr_t)&channel->regs->hctsiz); + sys_write32((uint32_t)dma_addr, (mem_addr_t)&channel->regs->hcdma); + + /* TODO: Configure split transaction if needed */ + + /* TODO: sync CACHE */ + + hcchar = sys_read32((mem_addr_t)&channel->regs->hcchar); + hcchar |= USB_DWC2_HCCHAR_CHENA; + hcchar &= ~USB_DWC2_HCCHAR_CHDIS; + sys_write32(hcchar, (mem_addr_t)&channel->regs->hcchar); +} + +static bool uhc_dwc2_xfer_is_done(const struct uhc_transfer *xfer) +{ + return xfer->type != USB_EP_TYPE_CONTROL || + xfer->stage == UHC_CONTROL_STAGE_STATUS; +} + +static uint32_t uhc_dwc2_handle_xfer_complete(struct uhc_dwc2_channel *channel, + uint32_t channel_events) +{ + if ((channel_events & BIT(UHC_DWC2_CHANNEL_EVENT_CPLT)) && + !uhc_dwc2_xfer_is_done(channel->xfer)) { + uhc_dwc2_channel_process_control(channel); + return 0; + } + + return channel_events; +} + +static uint32_t uhc_dwc2_channel_handle_in_bulk_control(struct uhc_dwc2_channel *channel, + uint32_t hcint) +{ + uint32_t channel_events = 0; + + if (hcint & USB_DWC2_HCINT_CHHLTD) { + if (hcint & (USB_DWC2_HCINT_XFERCOMPL | + USB_DWC2_HCINT_STALL | + USB_DWC2_HCINT_BBLERR)) { + /* Transfer completed, STALLed or Babble Error */ + channel->error_count = 0; + /* TODO: Mask ACK */ + + if (hcint & USB_DWC2_HCINT_XFERCOMPL) { + channel_events |= BIT(UHC_DWC2_CHANNEL_EVENT_CPLT); + } else if (hcint & USB_DWC2_HCINT_STALL) { + channel_events |= BIT(UHC_DWC2_CHANNEL_EVENT_STALL); + } else { + channel_events |= BIT(UHC_DWC2_CHANNEL_EVENT_ERROR); + } + channel_events |= BIT(UHC_DWC2_CHANNEL_DO_RELEASE); + } else if (hcint & USB_DWC2_HCINT_XACTERR) { + if (channel->error_count == 2) { + channel_events |= BIT(UHC_DWC2_CHANNEL_DO_RELEASE); + channel_events |= BIT(UHC_DWC2_CHANNEL_EVENT_ERROR); + } else { + /* TODO: Unmask ACK, NAK, DTGERR */ + channel->error_count++; + channel_events |= BIT(UHC_DWC2_CHANNEL_DO_REINIT); + } + } else { + /* TODO: Add handling for other cases */ + LOG_WRN("IN halted, unhandled HCINT 0x%08x", hcint); + } + } else if (hcint & (USB_DWC2_HCINT_ACK | + USB_DWC2_HCINT_NAK | + USB_DWC2_HCINT_DTGERR)) { + channel->error_count = 0; + /* TODO: Mask ACK, NAK, DTGERR */ + } else { + /* TODO: Add handling for other cases */ + LOG_WRN("IN not halted, unhandled HCINT 0x%08x", hcint); + } + + return uhc_dwc2_handle_xfer_complete(channel, channel_events); +} + +static inline uint32_t uhc_dwc2_channel_handle_out_bulk_control(struct uhc_dwc2_channel *channel, + uint32_t hcint) +{ + uint32_t channel_events = 0; + + if (hcint & USB_DWC2_HCINT_CHHLTD) { + if (hcint & (USB_DWC2_HCINT_XFERCOMPL | USB_DWC2_HCINT_STALL)) { + /* Transfer completed or STALLed*/ + channel->error_count = 1; + /* TODO: Mask ACK */ + + if (hcint & USB_DWC2_HCINT_XFERCOMPL) { + channel_events |= BIT(UHC_DWC2_CHANNEL_EVENT_CPLT); + } else { + channel_events |= BIT(UHC_DWC2_CHANNEL_EVENT_STALL); + } + channel_events |= BIT(UHC_DWC2_CHANNEL_DO_RELEASE); + } else if (hcint & USB_DWC2_HCINT_XACTERR) { + /* Transfer Error */ + if (hcint & (USB_DWC2_HCINT_NAK | + USB_DWC2_HCINT_NYET | + USB_DWC2_HCINT_ACK)) { + channel->error_count = 1; + channel_events |= BIT(UHC_DWC2_CHANNEL_DO_REINIT); + channel_events |= BIT(UHC_DWC2_CHANNEL_DO_REWIND); + } else { + channel->error_count++; + if (channel->error_count == 3) { + channel_events |= BIT(UHC_DWC2_CHANNEL_DO_RELEASE); + channel_events |= BIT(UHC_DWC2_CHANNEL_EVENT_ERROR); + } else { + channel_events |= BIT(UHC_DWC2_CHANNEL_DO_REINIT); + channel_events |= BIT(UHC_DWC2_CHANNEL_DO_REWIND); + } + } + } else { + /* TODO: Add handling for other cases */ + LOG_WRN("OUT halted, unhandled HCINT 0x%08x", hcint); + } + } else if (hcint & USB_DWC2_HCINT_ACK) { + channel->error_count = 1; + /* TODO: Unmask ACK */ + } else { + /* TODO: Expand handling */ + LOG_WRN("OUT not halted, unhandled HCINT 0x%08x", hcint); + } + + return uhc_dwc2_handle_xfer_complete(channel, channel_events); +} + +static uint32_t uhc_dwc2_channel_handle_irq_events(struct uhc_dwc2_channel *channel) +{ + struct uhc_transfer *const xfer = channel->xfer; + uint32_t channel_events; + uint32_t hcint; + + hcint = sys_read32((mem_addr_t)&channel->regs->hcint); + sys_write32(hcint, (mem_addr_t)&channel->regs->hcint); + + /* + * Completeion bits might come earlier, than channel halted interrupt. + * Save the hcint as pending and wait for channel halted. + */ + channel->hcint_pending |= hcint & USB_DWC2_HCINT_CPLT_BITS; + + if (hcint & USB_DWC2_HCINT_CHHLTD) { + /* Channel halted */ + hcint |= channel->hcint_pending; + channel->hcint_pending = 0U; + } else if (hcint & USB_DWC2_HCINT_CPLT_BITS) { + /* Channel not halted, wait for channel to be halted */ + return 0; + } + + if (hcint & USB_DWC2_HCINT_ERRORS) { + LOG_ERR("Error on channel%d, HCINT 0x%08x", channel->index, hcint); + } + + LOG_DBG("HCINT 0x%08x", hcint); + + /* Bulk & Control */ + if (xfer->type == USB_EP_TYPE_BULK || xfer->type == USB_EP_TYPE_CONTROL) { + if (USB_EP_DIR_IS_IN(xfer->ep)) { + channel_events = uhc_dwc2_channel_handle_in_bulk_control(channel, hcint); + } else { + channel_events = uhc_dwc2_channel_handle_out_bulk_control(channel, hcint); + } + } else { + LOG_ERR("Unhandled transfer type %u, HCINT 0x%08x", xfer->type, hcint); + channel_events = 0; + } + + return channel_events; +} + +static inline bool uhc_dwc2_port_debounce(const struct device *dev, + enum uhc_dwc2_event event) +{ + const struct uhc_dwc2_config *config = dev->config; + struct usb_dwc2_reg *dwc2 = config->base; + + /* Perform the debounce delay outside of the global lock */ + uhc_unlock_internal(dev); + + k_msleep(DEBOUNCE_DELAY_MS); + + uhc_lock_internal(dev, K_FOREVER); + + bool connected = ((sys_read32((mem_addr_t)&dwc2->hprt) & USB_DWC2_HPRT_PRTCONNSTS) != 0); + bool want_connected = (event == UHC_DWC2_EVENT_PORT_CONNECTION); + + uhc_dwc2_port_debounce_unlock(dev); + + /* True if stable state matches the event */ + return connected == want_connected; +} + +static inline void uhc_dwc2_port_power_on(const struct device *dev) +{ + 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, 0xFFFFFFFFUL); + sys_set_bits((mem_addr_t)&dwc2->gintmsk, USB_DWC2_GINTSTS_PRTINT | + USB_DWC2_GINTSTS_HCHINT); + dwc2_hal_set_power(dwc2, true); +} + +static inline void uhc_dwc2_port_power_off(const struct device *dev) +{ + 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, 0xFFFFFFFFUL); + sys_clear_bits((mem_addr_t)&dwc2->gintmsk, USB_DWC2_GINTSTS_PRTINT | + USB_DWC2_GINTSTS_HCHINT); + dwc2_hal_set_power(dwc2, false); +} + +static inline int uhc_dwc2_soft_reset(const struct device *dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + int ret; + + /* TODO: Check that port has no ongoing transfers */ + + /* Disable Global IRQ */ + config->irq_disable_func(dev); + + /* Reset the core */ + ret = dwc2_core_soft_reset(dev); + if (ret != 0) { + LOG_ERR("Failed to perform soft reset: %d", ret); + return ret; + } + + /* Re-config after reset */ + dwc2_hal_init_gahbcfg(dwc2); + + /* Enable Global IRQ */ + config->irq_enable_func(dev); + + return ret; +} + +static int uhc_dwc2_channel_claim(const struct device *const dev, + struct uhc_transfer *const xfer, + struct uhc_dwc2_channel **const channel_p) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + /* TODO: select non-claimed channel, use channel 0 for now */ + uint8_t idx = 0; + struct uhc_dwc2_channel *const channel = &priv->channels[idx]; + + LOG_DBG("Claimed channel%d, configuring it with xfer %p, channel %p", + idx, (void *)xfer, (void *)channel); + + /* Save channel characteristics of the underlying channel */ + channel->xfer = xfer; + + *channel_p = channel; + + return 0; +} + +static int uhc_dwc2_channel_configure(const struct device *const dev, + struct uhc_dwc2_channel *const channel) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_transfer *const xfer = channel->xfer; + struct usb_device *const udev = xfer->udev; + uint32_t hcint; + uint32_t hcintmsk; + uint32_t hcchar; + + /* Clear the interrupt bits by writing them back */ + hcint = sys_read32((mem_addr_t)&channel->regs->hcint); + sys_write32(hcint, (mem_addr_t)&channel->regs->hcint); + + /* Enable channel interrupt in the core */ + sys_set_bits((mem_addr_t)&dwc2->haintmsk, BIT(channel->index)); + + hcintmsk = sys_read32((mem_addr_t)&channel->regs->hcintmsk); + + /* Enable transfer complete and channel halted interrupts */ + hcintmsk |= USB_DWC2_HCINT_CHHLTD; + /* Enable error interrupts */ + hcintmsk |= USB_DWC2_HCINT_ERRORS; + hcintmsk |= USB_DWC2_HCINT_STALL; + sys_write32(hcintmsk, (mem_addr_t)&channel->regs->hcintmsk); + + /* Configure the channel main properties */ + hcchar = usb_dwc2_set_hcchar_mps(xfer->mps); + hcchar |= usb_dwc2_set_hcchar_epnum(USB_EP_GET_IDX(xfer->ep)); + hcchar |= usb_dwc2_set_hcchar_eptype(xfer->type); + hcchar |= usb_dwc2_set_hcchar_ec(1UL /* TODO: ep_config->mult */); + hcchar |= usb_dwc2_set_hcchar_devaddr(udev->addr); + + if (USB_EP_DIR_IS_IN(xfer->ep)) { + hcchar |= USB_DWC2_HCCHAR_EPDIR; + } + + if (false /* TODO: Support Hubs channel->ls_via_fs_hub */) { + hcchar |= USB_DWC2_HCCHAR_LSPDDEV; + } + + if (xfer->type == USB_EP_TYPE_INTERRUPT) { + hcchar |= USB_DWC2_HCCHAR_ODDFRM; + } + + sys_write32(hcchar, (mem_addr_t)&channel->regs->hcchar); + + return 0; +} + +static int uhc_dwc2_channel_release(const struct device *dev, + struct uhc_dwc2_channel *channel) +{ + 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 << channel->index)); + + /* Release channel */ + channel->xfer = NULL; + + LOG_DBG("Released channel%d", channel->index); + + return 0; +} + +static int uhc_dwc2_channel_start_transfer_ctrl(struct uhc_dwc2_channel *channel) +{ + struct uhc_transfer *const xfer = channel->xfer; + const struct usb_setup_packet *setup_pkt = (const struct usb_setup_packet *)xfer->setup_pkt; + uint16_t pkt_cnt; + uint32_t hctsiz; + uint32_t hcint; + uint32_t hcchar; + + LOG_HEXDUMP_DBG(xfer->setup_pkt, 8, "setup"); + + xfer->stage = UHC_CONTROL_STAGE_SETUP; + + /* Get information about the control transfer by analyzing the setup packet */ + channel->data_stg_in = usb_reqtype_is_to_host(setup_pkt); + channel->data_stg_skip = (setup_pkt->wLength == 0); + channel->set_address = (setup_pkt->bRequest == USB_SREQ_SET_ADDRESS) ? 1 : 0; + + LOG_DBG("data_in: %s, data_skip: %s", + channel->data_stg_in ? "true" : "false", + channel->data_stg_skip ? "true" : "false"); + + /* TODO: Control transfer should always use EP0, make sense to assert */ + + /* Control stage is always OUT */ + sys_clear_bits((mem_addr_t)&channel->regs->hcchar, USB_DWC2_HCCHAR_EPDIR); + + pkt_cnt = calc_packet_count(sizeof(struct usb_setup_packet), xfer->mps); + hctsiz = usb_dwc2_set_hctsiz_pid(USB_DWC2_HCTSIZ_PID_SETUP) | + usb_dwc2_set_hctsiz_pktcnt(pkt_cnt) | + usb_dwc2_set_hctsiz_xfersize(sizeof(struct usb_setup_packet)); + + sys_write32(hctsiz, (mem_addr_t)&channel->regs->hctsiz); + sys_write32((uint32_t)xfer->setup_pkt, (mem_addr_t)&channel->regs->hcdma); + + /* TODO: Configure split transaction if needed */ + + hcint = sys_read32((mem_addr_t)&channel->regs->hcint); + sys_write32(hcint, (mem_addr_t)&channel->regs->hcint); + + /* TODO: sync CACHE */ + + hcchar = sys_read32((mem_addr_t)&channel->regs->hcchar); + hcchar |= USB_DWC2_HCCHAR_CHENA; + hcchar &= ~USB_DWC2_HCCHAR_CHDIS; + sys_write32(hcchar, (mem_addr_t)&channel->regs->hcchar); + + return 0; +} + +static inline void uhc_dwc2_submit_new_device(const struct device *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; + static const char *const uhc_dwc2_speed_str[] = {"High", "Full", "Low"}; + uint32_t speed = dwc2_hal_get_speed(dwc2); + + LOG_DBG("New device, %s-speed", uhc_dwc2_speed_str[speed]); + + priv->has_device = 1; + + switch (speed) { + case USB_DWC2_HPRT_PRTSPD_LOW: + uhc_submit_event(dev, UHC_EVT_DEV_CONNECTED_LS, 0); + break; + case USB_DWC2_HPRT_PRTSPD_FULL: + uhc_submit_event(dev, UHC_EVT_DEV_CONNECTED_FS, 0); + break; + case USB_DWC2_HPRT_PRTSPD_HIGH: + uhc_submit_event(dev, UHC_EVT_DEV_CONNECTED_HS, 0); + break; + default: + LOG_ERR("Unsupported speed %d", speed); + break; + } +} + +static inline void uhc_dwc2_submit_dev_gone(const struct device *dev) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + + if (!priv->has_device) { + return; + } + + LOG_DBG("Device removed"); + + uhc_submit_event(dev, UHC_EVT_DEV_REMOVED, 0); + priv->has_device = 0; +} + +static int uhc_dwc2_submit_xfer(const struct device *const dev, struct uhc_transfer *const xfer) +{ + struct uhc_dwc2_channel *channel = NULL; + int ret; + + LOG_DBG("ep=%02Xh, mps=%d, int=%d, start_frame=%d, stage=%d, no_status=%d", + xfer->ep, xfer->mps, xfer->interval, + xfer->start_frame, xfer->stage, xfer->no_status); + + /* TODO: dma requirement, 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: dma requirement, buffer addr that will used as dma addr also should be aligned */ + if ((xfer->buf != NULL) && ((uintptr_t)net_buf_tail(xfer->buf) % 4)) { + LOG_WRN("Buffer address %08lXh is not 4-byte aligned", + (uintptr_t)net_buf_tail(xfer->buf)); + } + + if (xfer->type != USB_EP_TYPE_CONTROL) { + LOG_WRN("Channel type %d isn't supported yet", xfer->type); + return -EINVAL; + } + + ret = uhc_dwc2_channel_claim(dev, xfer, &channel); + if (ret != 0) { + LOG_ERR("Failed to claim channel: %d", ret); + return ret; + } + + ret = uhc_dwc2_channel_configure(dev, channel); + if (ret != 0) { + LOG_ERR("Failed to configure channel: %d", ret); + uhc_dwc2_channel_release(dev, channel); + return ret; + } + + return uhc_dwc2_channel_start_transfer_ctrl(channel); +} + +static void uhc_dwc2_port_handle_events(const struct device *dev, uint32_t event_mask) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + + LOG_DBG("Port events: %08Xh", event_mask); + + if (event_mask & BIT(UHC_DWC2_EVENT_PORT_CONNECTION)) { + /* Port connected */ + LOG_DBG("Port connected"); + /* Debounce port connection */ + if (uhc_dwc2_port_debounce(dev, UHC_DWC2_EVENT_PORT_CONNECTION)) { + /* Notify the higher logic about the new device */ + uhc_dwc2_submit_new_device(dev); + } else { + /* TODO: Implement handling */ + LOG_ERR("Port changed during debouncing connect"); + } + } + + if (event_mask & BIT(UHC_DWC2_EVENT_PORT_DISCONNECTION)) { + /* Port disconnected */ + /* Debounce port disconnection */ + if (uhc_dwc2_port_debounce(dev, UHC_DWC2_EVENT_PORT_DISCONNECTION)) { + LOG_DBG("Port disconnected"); + /* Notify upper layer */ + uhc_dwc2_submit_dev_gone(dev); + /* Reset the controller to handle new connection */ + uhc_dwc2_soft_reset(dev); + /* Prepare for device connection */ + uhc_dwc2_port_power_on(dev); + } else { + /* TODO: Implement handling */ + LOG_ERR("Port changed during debouncing disconnect"); + } + } + + if (event_mask & BIT(UHC_DWC2_EVENT_PORT_ERROR)) { + LOG_DBG("Port error"); + /* Notify upper layer */ + uhc_dwc2_submit_dev_gone(dev); + /* TODO: recover from the error */ + + /* Reset the controller to handle new connection */ + uhc_dwc2_soft_reset(dev); + /* Prepare for device connection */ + uhc_dwc2_port_power_on(dev); + } + + if (event_mask & BIT(UHC_DWC2_EVENT_PORT_OVERCURRENT)) { + LOG_ERR("Port overcurrent"); + /* TODO: Handle overcurrent */ + LOG_WRN("Handle overcurrent is not implemented"); + /* Just power off the port via registers */ + dwc2_hal_set_power(dwc2, false); + } +} + +static void uhc_dwc2_channel_handle_events(const struct device *dev, + struct uhc_dwc2_channel *channel) +{ + uint32_t events = (uint32_t)atomic_set(&channel->events, 0); + struct uhc_transfer *const xfer = channel->xfer; + int err = -EIO; + + __ASSERT_NO_MSG(xfer != NULL); + + LOG_DBG("Thread channel%d events: %08Xh", channel->index, events); + + /* ERROR, STALL and COMPLETE are mutually exclusive */ + + if (events & BIT(UHC_DWC2_CHANNEL_EVENT_ERROR)) { + LOG_ERR("Channel%d error", channel->index); + LOG_WRN("Channel error handling has not been implemented yet"); + err = -EIO; + } else if (events & BIT(UHC_DWC2_CHANNEL_EVENT_STALL)) { + err = -EPIPE; + } else if (events & BIT(UHC_DWC2_CHANNEL_EVENT_CPLT)) { + if (channel->set_address) { + /* When the device is processing new address, delay is required. */ + k_msleep(SET_ADDR_DELAY_MS); + } + + err = 0; + } + + /* TODO: DO_REWIND and DO_INIT always go one by one */ + + if (events & BIT(UHC_DWC2_CHANNEL_DO_REWIND)) { + LOG_WRN("DO_REWIND"); + /* TODO: Implement rewind xfer */ + } + + if (events & BIT(UHC_DWC2_CHANNEL_DO_REINIT)) { + LOG_WRN("DO_REINIT"); + /* TODO: Start transfer again */ + } + + /* TODO: DO_RELEASE is mutually exclusive with DO_REWIND and DO_INIT */ + + if (events & BIT(UHC_DWC2_CHANNEL_DO_RELEASE)) { + uhc_dwc2_channel_release(dev, channel); + uhc_xfer_return(dev, xfer, err); + } +} + +static void uhc_dwc2_isr_handler(const struct device *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; + uint32_t gintsts; + uint32_t hprt = 0; + uint32_t ch_events = 0; + unsigned int ch_index; + + /* Read and clear core interrupt status */ + gintsts = sys_read32((mem_addr_t)&dwc2->gintsts); + sys_write32(gintsts, (mem_addr_t)&dwc2->gintsts); + + LOG_DBG("GINTSTS=0x%08X", gintsts); + + /* Handle disconnect first */ + if (gintsts & USB_DWC2_GINTSTS_DISCONNINT) { + /* Port disconnected */ + uhc_dwc2_port_debounce_lock(dev); + k_event_post(&priv->events, BIT(UHC_DWC2_EVENT_PORT_DISCONNECTION)); + } + + /* To have better throughput, handle channels right after disconnection */ + if (gintsts & USB_DWC2_GINTSTS_HCHINT) { + /* Handle pending channel event */ + uint32_t haint = sys_read32((mem_addr_t)&dwc2->haint); + + LOG_DBG("HAINT 0x%08x", haint); + + /* Channel count might be configured via menuconfig, trim it if needed */ + if (haint & ~BIT_MASK(MAX_CHANNELS)) { + haint &= BIT_MASK(MAX_CHANNELS); + LOG_WRN("HAINT trimmed to valid channel mask 0x%08X", haint); + } + + while (haint != 0U) { + /* Channel index = channel number - 1 */ + ch_index = __builtin_ffs(haint) - 1U; + + __ASSERT_NO_MSG(priv->channels[ch_index].index == ch_index); + + ch_events = uhc_dwc2_channel_handle_irq_events(&priv->channels[ch_index]); + if (ch_events) { + atomic_or(&priv->channels[ch_index].events, ch_events); + k_event_set(&priv->events, + BIT(UHC_DWC2_EVENT_PORT_PEND_CHANNEL + ch_index)); + } + haint &= ~BIT(ch_index); + } + } + + if (gintsts & USB_DWC2_GINTSTS_PRTINT) { + /* Port interrupt, read and clear all, except port enable */ + hprt = sys_read32((mem_addr_t)&dwc2->hprt); + sys_write32(hprt & ~USB_DWC2_HPRT_PRTENA, (mem_addr_t)&dwc2->hprt); + + LOG_DBG("HPRT 0x%08x", hprt); + + /* Handle port overcurrent as it is a failure state */ + if (hprt & USB_DWC2_HPRT_PRTOVRCURRCHNG) { + /* TODO: Overcurrent or overcurrent clear? */ + k_event_post(&priv->events, BIT(UHC_DWC2_EVENT_PORT_OVERCURRENT)); + } + + /* Handle port change */ + if (hprt & USB_DWC2_HPRT_PRTENCHNG) { + if (hprt & USB_DWC2_HPRT_PRTENA) { + /* Enable the rest registers of the port */ + dwc2_hal_enable_port(dwc2); + /* Give port enable semaphore to unlock reset requense */ + k_sem_give(&priv->sem_port_enabled); + } else { + /* Host port has been disabled */ + k_event_post(&priv->events, BIT(UHC_DWC2_EVENT_PORT_DISABLED)); + } + } + + /* Handle port connection */ + if (hprt & USB_DWC2_HPRT_PRTCONNDET && !priv->debouncing) { + /* Port connected */ + uhc_dwc2_port_debounce_lock(dev); + k_event_post(&priv->events, BIT(UHC_DWC2_EVENT_PORT_CONNECTION)); + } + } + + (void)uhc_dwc2_quirk_irq_clear(dev); +} + +static void uhc_dwc2_thread(void *arg0, void *arg1, void *arg2) +{ + const struct device *const dev = (const struct device *)arg0; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + uint32_t event_mask; + + while (true) { + event_mask = k_event_wait_safe(&priv->events, UINT32_MAX, false, K_FOREVER); + + uhc_lock_internal(dev, K_FOREVER); + + /* Handle port events */ + uhc_dwc2_port_handle_events(dev, event_mask); + + /* Interate channels events */ + for (uint32_t index = 0; index < MAX_CHANNELS; index++) { + if (event_mask & BIT(UHC_DWC2_EVENT_PORT_PEND_CHANNEL + index)) { + uhc_dwc2_channel_handle_events(dev, &priv->channels[index]); + } + } + + uhc_unlock_internal(dev); + } +} + +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_WRN("%s has not been implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_bus_suspend(const struct device *const dev) +{ + LOG_WRN("%s has not been implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_bus_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; + + /* Reset the port */ + dwc2_hal_set_reset(dwc2, true); + + /* Hold the bus in the reset state */ + k_msleep(RESET_HOLD_MS); + + /* Return the bus to the idle state. Port enabled event should occur */ + dwc2_hal_set_reset(dwc2, false); + + /* Give 10 ms to the port to become enabled */ + ret = k_sem_take(&priv->sem_port_enabled, K_MSEC(10)); + if (ret != 0) { + /* Port didn't change its state to ENABLED */ + LOG_ERR("Port wasn't enabled after reset"); + return ret; + } + + /* Give the device some extra time to recover after port reset */ + k_msleep(RESET_RECOVERY_MS); + + /* Finish the port config for the appeared device */ + dwc2_hal_set_fifo_sizes(dwc2, config->ghwcfg2, config->ghwcfg3); + + /* TODO: set frame list for the ISOC/INTR xfer */ + /* TODO: enable periodic transfer */ + + uhc_submit_event(dev, UHC_EVT_RESETED, 0); + return 0; +} + +static int uhc_dwc2_bus_resume(const struct device *const dev) +{ + LOG_WRN("%s has not been implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_enqueue(const struct device *const dev, struct uhc_transfer *const xfer) +{ + int ret; + + (void)uhc_xfer_append(dev, xfer); + + uhc_lock_internal(dev, K_FOREVER); + + ret = uhc_dwc2_submit_xfer(dev, xfer); + + uhc_unlock_internal(dev); + + return ret; +} + +static int uhc_dwc2_dequeue(const struct device *const dev, struct uhc_transfer *const xfer) +{ + LOG_WRN("%s has not been implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_preinit(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; + + for (uint32_t idx = 0; idx < MAX_CHANNELS; idx++) { + priv->channels[idx].index = idx; + priv->channels[idx].regs = + (struct usb_dwc2_host_chan *)((mem_addr_t)dwc2 + USB_DWC2_HCCHAR(idx)); + } + + k_thread_create(&priv->thread, + config->stack, + config->stack_size, + 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); + + ret = uhc_dwc2_quirk_post_preinit(dev); + if (ret != 0) { + LOG_ERR("Quirk post preinit failed %d", ret); + return ret; + } + + 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_pre_init(dev); + if (ret != 0) { + LOG_ERR("Quirk pre 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; + } + + LOG_DBG("DWC2 Core %04x", usb_dwc2_get_gsnpsid_rev(reg)); + + 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; + } + + /* Core reset */ + + ret = dwc2_core_soft_reset(dev); + if (ret != 0) { + LOG_ERR("DWC2 core reset failed after PHY init: %d", ret); + return ret; + } + + /* FIFO */ + + ret = dwc2_hal_set_fifo_sizes(dwc2, config->ghwcfg2, config->ghwcfg3); + if (ret != 0) { + LOG_ERR("Unable to configure FIFO"); + return ret; + } + + /* Init controller as a USB host */ + + return dwc2_hal_init_host(dwc2); +} + +static int uhc_dwc2_enable(const struct device *const dev) +{ + int ret; + + ret = uhc_dwc2_quirk_pre_enable(dev); + if (ret != 0) { + LOG_ERR("Quirk pre enable failed %d", ret); + return ret; + } + + uhc_dwc2_soft_reset(dev); + + uhc_dwc2_port_power_on(dev); + + ret = uhc_dwc2_quirk_post_enable(dev); + if (ret != 0) { + LOG_ERR("Quirk post enable failed %d", ret); + return ret; + } + + return 0; +} + +static int uhc_dwc2_disable(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + int ret; + /* TODO: Check ongoing transfer? */ + + uhc_dwc2_submit_dev_gone(dev); + + config->irq_disable_func(dev); + + uhc_dwc2_port_power_off(dev); + + ret = uhc_dwc2_quirk_disable(dev); + if (ret != 0) { + LOG_ERR("Quirk disable failed %d", ret); + return ret; + } + + return 0; +} + +static int uhc_dwc2_shutdown(const struct device *const dev) +{ + LOG_WRN("%s has not been implemented", __func__); + + 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))) + +#if !defined(UHC_DWC2_IRQ_DT_INST_DEFINE) +#define UHC_DWC2_IRQ_FLAGS_TYPE0(n) 0 +#define UHC_DWC2_IRQ_FLAGS_TYPE1(n) DT_INST_IRQ(n, type) +#define DW_IRQ_FLAGS(n) \ + _CONCAT(UHC_DWC2_IRQ_FLAGS_TYPE, DT_INST_IRQ_HAS_CELL(n, type))(n) + +#define UHC_DWC2_IRQ_DT_INST_DEFINE(n) \ + static void uhc_dwc2_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), \ + DW_IRQ_FLAGS(n)); \ + \ + irq_enable(DT_INST_IRQN(n)); \ + } \ + \ + static void uhc_dwc2_irq_disable_func_##n(const struct device *dev) \ + { \ + irq_disable(DT_INST_IRQN(n)); \ + } +#endif + +/* Multi-instance device definition for DWC2 host controller */ +#define UHC_DWC2_DEVICE_DEFINE(n) \ + \ + K_THREAD_STACK_DEFINE(uhc_dwc2_stack_##n, \ + CONFIG_UHC_DWC2_STACK_SIZE); \ + \ + UHC_DWC2_IRQ_DT_INST_DEFINE(n) \ + \ + static struct uhc_dwc2_data uhc_dwc2_priv_##n = { \ + .events = Z_EVENT_INITIALIZER(uhc_dwc2_priv_##n.events), \ + .sem_port_enabled = \ + Z_SEM_INITIALIZER(uhc_dwc2_priv_##n.sem_port_enabled, \ + 0, \ + 1), \ + }; \ + \ + static struct uhc_data uhc_data_##n = { \ + .mutex = Z_MUTEX_INITIALIZER(uhc_data_##n.mutex), \ + .priv = &uhc_dwc2_priv_##n, \ + }; \ + \ + static const struct uhc_dwc2_config uhc_dwc2_config_##n = { \ + .base = (struct usb_dwc2_reg *)UHC_DWC2_DT_INST_REG_ADDR(n), \ + .quirks = UHC_DWC2_VENDOR_QUIRK_GET(n), \ + .quirk_data = &uhc_dwc2_quirk_data_##n, \ + .quirk_config = &uhc_dwc2_quirk_config_##n, \ + .stack = uhc_dwc2_stack_##n, \ + .stack_size = K_THREAD_STACK_SIZEOF(uhc_dwc2_stack_##n), \ + .irq_enable_func = uhc_dwc2_irq_enable_func_##n, \ + .irq_disable_func = uhc_dwc2_irq_disable_func_##n, \ + .gsnpsid = DT_INST_PROP(n, gsnpsid), \ + .ghwcfg1 = DT_INST_PROP(n, ghwcfg1), \ + .ghwcfg2 = DT_INST_PROP(n, ghwcfg2), \ + .ghwcfg3 = DT_INST_PROP(n, ghwcfg3), \ + .ghwcfg4 = DT_INST_PROP(n, ghwcfg4), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, uhc_dwc2_preinit, NULL, \ + &uhc_data_##n, &uhc_dwc2_config_##n, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &uhc_dwc2_api); + +DT_INST_FOREACH_STATUS_OKAY(UHC_DWC2_DEVICE_DEFINE) diff --git a/drivers/usb/uhc/uhc_dwc2.h b/drivers/usb/uhc/uhc_dwc2.h new file mode 100644 index 0000000000000..19d830e55f0a6 --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2026 Roman Leonov + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_USB_UHC_DWC2_H +#define ZEPHYR_DRIVERS_USB_UHC_DWC2_H + +#include +#include +#include +#include + +/* Vendor quirks per driver instance */ +struct uhc_dwc2_vendor_quirks { + /* Called on uhc_dwc2_preinit() after the thread is initialized */ + int (*post_preinit)(const struct device *dev); + /* Called at the beginning of uhc_dwc2_init(), before controller is initialized */ + int (*pre_init)(const struct device *dev); + /* Called on uhc_dwc2_enable() before enable the port power */ + int (*pre_enable)(const struct device *dev); + /* Called on uhc_dwc2_enable() after enable the port power */ + 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); + /* Called at the end of IRQ handling */ + int (*irq_clear)(const struct device *dev); + /* Called while waiting for bits that require PHY to be clocked */ + int (*is_phy_clk_off)(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; + /* Vendor specific quirks */ + const struct uhc_dwc2_vendor_quirks *const quirks; + void *quirk_data; + const void *quirk_config; + /* Pointer to the stack used by the driver thread */ + k_thread_stack_t *stack; + /* Size of the stack used by the driver thread */ + size_t stack_size; + + void (*irq_enable_func)(const struct device *dev); + void (*irq_disable_func)(const struct device *dev); + + /* Hardware configuration registers */ + uint32_t gsnpsid; + uint32_t ghwcfg1; + uint32_t ghwcfg2; + uint32_t ghwcfg3; + uint32_t ghwcfg4; +}; + +#define UHC_DWC2_QUIRK_CONFIG(dev) \ + (((const struct uhc_dwc2_config *)dev->config)->quirk_config) + +#define UHC_DWC2_QUIRK_DATA(dev) \ + (((const struct uhc_dwc2_config *)dev->config)->quirk_data) + +#if DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) +#include "uhc_dwc2_esp32_usb_otg.h" +#endif + +#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l) +#include "uhc_dwc2_nrf_usbhs_nrf54l.h" +#endif + +#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 *const dev) \ +{ \ + const struct uhc_dwc2_config *const config = dev->config; \ + const struct uhc_dwc2_vendor_quirks *const quirks = config->quirks; \ + \ + if (quirks != NULL && quirks->fname != NULL) { \ + return config->quirks->fname(dev); \ + } \ + return 0; \ +} + +DWC2_QUIRK_FUNC_DEFINE(post_preinit) +DWC2_QUIRK_FUNC_DEFINE(pre_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_clear) +DWC2_QUIRK_FUNC_DEFINE(is_phy_clk_off) + +#endif /* ZEPHYR_DRIVERS_USB_UHC_DWC2_H */ diff --git a/drivers/usb/uhc/uhc_dwc2_esp32_usb_otg.h b/drivers/usb/uhc/uhc_dwc2_esp32_usb_otg.h new file mode 100644 index 0000000000000..5453b8a2343db --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2_esp32_usb_otg.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2026 Roman Leonov + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_USB_UHC_DWC2_ESP32_USB_OTG_H +#define ZEPHYR_DRIVERS_USB_UHC_DWC2_ESP32_USB_OTG_H + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +struct esp32_usb_otg_config { + const struct device *clock_dev; + const clock_control_subsys_t clock_subsys; + int irq_source; + int irq_priority; + int irq_flags; + usb_phy_target_t phy_target; +}; + +struct esp32_usb_otg_data { + struct intr_handle_data_t *int_handle; + usb_wrap_hal_context_t wrap_hal; +}; + +static void uhc_dwc2_isr_handler(const struct device *dev); + +static inline int esp32_usb_otg_init(const struct device *dev) +{ + const struct esp32_usb_otg_config *const cfg = UHC_DWC2_QUIRK_CONFIG(dev); + struct esp32_usb_otg_data *const data = UHC_DWC2_QUIRK_DATA(dev); + + int ret; + + 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); + + /* 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); + + return ret; +} + +static int esp32_usb_otg_enable_phy(const struct device *const dev) +{ + const struct esp32_usb_otg_config *const cfg = UHC_DWC2_QUIRK_CONFIG(dev); + struct esp32_usb_otg_data *const data = UHC_DWC2_QUIRK_DATA(dev); + + usb_wrap_hal_init(&data->wrap_hal); + usb_wrap_ll_phy_enable_pad(data->wrap_hal.dev, true); + +#if USB_WRAP_LL_EXT_PHY_SUPPORTED + if (cfg->phy_target == USB_PHY_TARGET_INT) { + usb_wrap_hal_phy_set_external(&data->wrap_hal, false); + } else { + /* If External PHY target is supported, set external to true */ + LOG_WRN("esp32 phy: External phy support isn't implemented"); + return -EINVAL; + } +#endif + + if (cfg->phy_target == USB_PHY_TARGET_INT) { + /* Set drive capabilities for DM and DP */ + gpio_ll_set_drive_capability(GPIO_LL_GET_HW(0), USBPHY_DM_NUM, GPIO_DRIVE_CAP_3); + gpio_ll_set_drive_capability(GPIO_LL_GET_HW(0), USBPHY_DP_NUM, GPIO_DRIVE_CAP_3); + /* Configure pull resistors for Host DM and DP */ + 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(&data->wrap_hal, &vals); + } + + LOG_DBG("PHY enabled"); + return 0; +} + +static int esp32_usb_otg_disable_phy(const struct device *const dev) +{ + struct esp32_usb_otg_data *const data = UHC_DWC2_QUIRK_DATA(dev); + + usb_wrap_hal_disable(); + usb_wrap_ll_phy_enable_pad(data->wrap_hal.dev, false); + + LOG_DBG("PHY disabled"); + return 0; +} + +static int esp32_usb_otg_pre_init(const struct device *const dev) +{ + int ret; + + /* Power up PHY */ + ret = esp32_usb_otg_enable_phy(dev); + + if (ret) { + LOG_ERR("Unable to power on esp32 usb otg PHY"); + return ret; + } + + /* Enable clock, configure pins and init interrupt */ + return esp32_usb_otg_init(dev); +} + +static int esp32_usb_otg_shutdown(const struct device *const dev) +{ + /* TODO: Disable clock, disconnect gpio signals and deregister the interrupt */ + + /* Power down the PHY */ + return esp32_usb_otg_disable_phy(dev); +} + +static void esp32_usb_otg_irq_enable_func(const struct device *const dev) +{ + struct esp32_usb_otg_data *const data = UHC_DWC2_QUIRK_DATA(dev); + + esp_intr_enable(data->int_handle); +} + +static void esp32_usb_otg_irq_disable_func(const struct device *const dev) +{ + struct esp32_usb_otg_data *const data = UHC_DWC2_QUIRK_DATA(dev); + + esp_intr_disable(data->int_handle); +} + +#define QUIRK_ESP32_USB_OTG_DEFINE(n) \ + \ + static const struct esp32_usb_otg_config uhc_dwc2_quirk_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_target = USB_PHY_TARGET_INT, \ + }; \ + \ + static struct esp32_usb_otg_data uhc_dwc2_quirk_data_##n; \ + \ + const struct uhc_dwc2_vendor_quirks uhc_dwc2_vendor_quirks_##n = { \ + .pre_init = esp32_usb_otg_pre_init, \ + .shutdown = esp32_usb_otg_shutdown, \ + }; + +#define UHC_DWC2_IRQ_DT_INST_DEFINE(n) \ + static void uhc_dwc2_irq_enable_func_##n(const struct device *dev) \ + { \ + esp32_usb_otg_irq_enable_func(dev); \ + } \ + \ + static void uhc_dwc2_irq_disable_func_##n(const struct device *dev) \ + { \ + esp32_usb_otg_irq_disable_func(dev); \ + } + +DT_INST_FOREACH_STATUS_OKAY(QUIRK_ESP32_USB_OTG_DEFINE) + +#endif /* ZEPHYR_DRIVERS_USB_UHC_DWC2_ESP32_USB_OTG_H */ diff --git a/drivers/usb/uhc/uhc_dwc2_nrf_usbhs_nrf54l.h b/drivers/usb/uhc/uhc_dwc2_nrf_usbhs_nrf54l.h new file mode 100644 index 0000000000000..3e89195c8ed6f --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2_nrf_usbhs_nrf54l.h @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_UHC_DWC2_NRF_USBHS_NRF54L_H +#define ZEPHYR_DRIVERS_UHC_DWC2_NRF_USBHS_NRF54L_H + +#include +#include +#include +#include +#include + +struct nrf_usbhs_nrf54l_config { + NRF_USBHS_Type *wrapper_base; + const struct device *vregusb_dev; +}; + +struct nrf_usbhs_nrf54l_data { + struct k_event events; + struct onoff_manager *pclk24m_mgr; + struct onoff_client pclk24m_cli; +}; + +/* + * 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 + * udc_enable() without a "VBUS ready" notification, the event wait will block + * until a valid VBUS signal is detected or until the + * CONFIG_UHC_DWC2_USBHS_VBUS_READY_TIMEOUT timeout expires. + */ +#define USBHS_VBUS_READY BIT(0) + +static void vregusb_event_cb(const struct device *dev, + const struct regulator_event *const evt, + const void *const user_data) +{ + const struct device *const dwc2_dev = user_data; + struct nrf_usbhs_nrf54l_data *const data = UHC_DWC2_QUIRK_DATA(dwc2_dev); + + if (evt->type == REGULATOR_VOLTAGE_DETECTED) { + k_event_post(&data->events, USBHS_VBUS_READY); + } + + if (evt->type == REGULATOR_VOLTAGE_REMOVED) { + k_event_set_masked(&data->events, 0, USBHS_VBUS_READY); + } +} + +static inline int usbhs_enable_core(const struct device *dev) +{ + const struct nrf_usbhs_nrf54l_config *const cfg = UHC_DWC2_QUIRK_CONFIG(dev); + struct nrf_usbhs_nrf54l_data *const data = UHC_DWC2_QUIRK_DATA(dev); + NRF_USBHS_Type *wrapper = cfg->wrapper_base; + k_timeout_t timeout = K_FOREVER; + int err; + + data->pclk24m_mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF24M); + + if (CONFIG_UHC_DWC2_USBHS_VBUS_READY_TIMEOUT > 0) { + timeout = K_MSEC(CONFIG_UHC_DWC2_USBHS_VBUS_READY_TIMEOUT); + } + + if (!k_event_wait(&data->events, USBHS_VBUS_READY, false, timeout)) { + LOG_ERR("Timed out while waiting VBUS to be ready"); + return -ETIMEDOUT; + } + + /* Request PCLK24M using clock control driver */ + sys_notify_init_spinwait(&data->pclk24m_cli.notify); + err = onoff_request(data->pclk24m_mgr, &data->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 force D+ pull-up off for now */ + 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); + + /* DWC2 opmode is now guaranteed to be Non-Driving, allow D+ pull-up to + * become active once driver clears DCTL SftDiscon bit. + */ + wrapper->PHY.INPUTOVERRIDE = (1 << 31); + + return 0; +} + +static inline int usbhs_init_vreg_and_clock(const struct device *dev) +{ + const struct nrf_usbhs_nrf54l_config *const cfg = UHC_DWC2_QUIRK_CONFIG(dev); + int err; + + if (!device_is_ready(cfg->vregusb_dev)) { + LOG_ERR("%s is not ready", cfg->vregusb_dev->name); + return -ENODEV; + } + + err = regulator_set_callback(cfg->vregusb_dev, vregusb_event_cb, dev); + if (err != 0) { + LOG_ERR("Failed to set regulator callback"); + return err; + } + + err = regulator_enable(cfg->vregusb_dev); + if (err != 0) { + LOG_ERR("Failed to enable %s", cfg->vregusb_dev->name); + return err; + } + + err = usbhs_enable_core(dev); + if (err != 0) { + LOG_ERR("Failed to enable DWC2 core"); + return err; + } + + return 0; +} + +static inline int usbhs_disable_core(const struct device *dev) +{ + const struct nrf_usbhs_nrf54l_config *const cfg = UHC_DWC2_QUIRK_CONFIG(dev); + struct nrf_usbhs_nrf54l_data *const data = UHC_DWC2_QUIRK_DATA(dev); + NRF_USBHS_Type *wrapper = cfg->wrapper_base; + int err; + + /* Set ID to Host and forcefully disable D+ pull-up */ + wrapper->PHY.OVERRIDEVALUES = (1 << 24) | (1 << 23); + wrapper->PHY.INPUTOVERRIDE = (1 << 31) | (1 << 30) | (1 << 24) | (1 << 23); + + wrapper->ENABLE = 0UL; + + /* Release PCLK24M using clock control driver */ + err = onoff_cancel_or_release(data->pclk24m_mgr, &data->pclk24m_cli); + if (err != 0) { + LOG_ERR("Failed to stop PCLK24M %d", err); + return err; + } + + return 0; +} + +static inline int usbhs_disable_vreg(const struct device *dev) +{ + const struct nrf_usbhs_nrf54l_config *const cfg = UHC_DWC2_QUIRK_CONFIG(dev); + + return regulator_disable(cfg->vregusb_dev); +} + +#define QUIRK_NRF_USBHS_NRF54L_DEFINE(n) \ + struct uhc_dwc2_vendor_quirks uhc_dwc2_vendor_quirks_##n = { \ + .post_preinit = usbhs_init_vreg_and_clock, \ + .disable = usbhs_disable_core, \ + .shutdown = usbhs_disable_vreg, \ + }; \ + \ + static const struct nrf_usbhs_nrf54l_config uhc_dwc2_quirk_config_##n = {\ + .wrapper_base = UINT_TO_POINTER(DT_REG_ADDR(DT_INST_PARENT(n))),\ + .vregusb_dev = DEVICE_DT_GET(DT_PHANDLE(DT_INST_PARENT(n), \ + regulator)), \ + }; \ + \ + static struct nrf_usbhs_nrf54l_data uhc_dwc2_quirk_data_##n = { \ + .events = Z_EVENT_INITIALIZER(uhc_dwc2_quirk_data_##n.events), \ + }; + +DT_INST_FOREACH_STATUS_OKAY(QUIRK_NRF_USBHS_NRF54L_DEFINE) + +#endif /* ZEPHYR_DRIVERS_UHC_DWC2_NRF_USBHS_NRF54L_H */ diff --git a/dts/arm/syna/sr100.dtsi b/dts/arm/syna/sr100.dtsi index 8c504ebc2b0ef..49ebc9751d636 100644 --- a/dts/arm/syna/sr100.dtsi +++ b/dts/arm/syna/sr100.dtsi @@ -189,8 +189,10 @@ num-out-eps = <8>; num-in-eps = <8>; clocks = <&gcr SYNA_USB_CLK>; + gsnpsid = <0x5532430a>; ghwcfg1 = <0x00000000>; ghwcfg2 = <0x02882054>; // 8 endpoints, dynamic sizing + ghwcfg3 = <0x0c78c468>; ghwcfg4 = <0xe2003e20>; // 9 in endpoints, desc-DMA status = "disabled"; }; diff --git a/dts/bindings/usb/snps,dwc2.yaml b/dts/bindings/usb/snps,dwc2.yaml index d5b0167f26b32..dc9daa3961f26 100644 --- a/dts/bindings/usb/snps,dwc2.yaml +++ b/dts/bindings/usb/snps,dwc2.yaml @@ -31,6 +31,11 @@ properties: description: | Number of configured IN endpoints including control endpoint. + gsnpsid: + type: int + description: | + Value of the GSNPSID register, used to declare the version of the core. + ghwcfg1: type: int required: true @@ -45,6 +50,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/nrf54h20.dtsi b/dts/vendor/nordic/nrf54h20.dtsi index aecd7d8966782..9b686ca918047 100644 --- a/dts/vendor/nordic/nrf54h20.dtsi +++ b/dts/vendor/nordic/nrf54h20.dtsi @@ -670,8 +670,10 @@ interrupts = <134 NRF_DEFAULT_IRQ_PRIORITY>; num-in-eps = <8>; num-out-eps = <10>; + gsnpsid = <0x4f54430a>; ghwcfg1 = <0xaa555000>; ghwcfg2 = <0x22abfc72>; + ghwcfg3 = <0x0beac0e8>; ghwcfg4 = <0x1e10aa60>; status = "disabled"; }; diff --git a/dts/vendor/nordic/nrf54lm20_a_b.dtsi b/dts/vendor/nordic/nrf54lm20_a_b.dtsi index 39c94ffdea061..46fd019179225 100644 --- a/dts/vendor/nordic/nrf54lm20_a_b.dtsi +++ b/dts/vendor/nordic/nrf54lm20_a_b.dtsi @@ -249,8 +249,10 @@ interrupts = <90 NRF_DEFAULT_IRQ_PRIORITY>; num-in-eps = <16>; num-out-eps = <16>; + gsnpsid = <0x4f54500b>; ghwcfg1 = <0x0>; 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 f963d6527c7b8..87acdb135b2b1 100644 --- a/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi +++ b/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi @@ -439,8 +439,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/esp32s3_devkitc_procpu.overlay b/samples/subsys/usb/shell/boards/esp32s3_devkitc_procpu.overlay new file mode 100644 index 0000000000000..9f66255629ad7 --- /dev/null +++ b/samples/subsys/usb/shell/boards/esp32s3_devkitc_procpu.overlay @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +zephyr_uhc0: &usb_otg { + status = "okay"; +};