diff --git a/drivers/usb/common/CMakeLists.txt b/drivers/usb/common/CMakeLists.txt index 339498c982bb4..3b7a78af7d779 100644 --- a/drivers/usb/common/CMakeLists.txt +++ b/drivers/usb/common/CMakeLists.txt @@ -3,3 +3,7 @@ add_subdirectory(buf) add_subdirectory_ifdef(CONFIG_HAS_NRFX nrf_usbd_common) add_subdirectory_ifdef(CONFIG_SOC_FAMILY_STM32 stm32) + +if(CONFIG_UDC_DRIVER OR CONFIG_UHC_DRIVER) + zephyr_include_directories(.) +endif() diff --git a/drivers/usb/uhc/CMakeLists.txt b/drivers/usb/uhc/CMakeLists.txt index 69f1cea8433b2..ae5059740dc98 100644 --- a/drivers/usb/uhc/CMakeLists.txt +++ b/drivers/usb/uhc/CMakeLists.txt @@ -5,6 +5,7 @@ zephyr_library() 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..7c57fa6f17889 --- /dev/null +++ b/drivers/usb/uhc/Kconfig.dwc2 @@ -0,0 +1,28 @@ +# Copyright (c) 2026 Roman Leonov +# SPDX-License-Identifier: Apache-2.0 + +config UHC_DWC2 + bool "DWC2 USB device controller driver" + depends on DT_HAS_SNPS_DWC2_ENABLED + default y + select EVENTS + help + DWC2 USB host controller driver. + +if UHC_DWC2 + +config UHC_DWC2_STACK_SIZE + int "UHC DWC2 driver thread stack size" + depends on UHC_DWC2 + default 512 + help + DWC2 driver thread stack size. + +config UHC_DWC2_THREAD_PRIORITY + int "UHC DWC2 driver thread priority" + depends on UHC_DWC2 + default 8 + help + DWC2 driver thread priority. + +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..c07d58311f600 --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2.c @@ -0,0 +1,351 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 Roman Leonov + * SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT snps_dwc2 + +#include + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + +#include + +#include "uhc_common.h" + +#define UHC_DWC2_CHAN_REG(base, chan_idx) \ + ((struct usb_dwc2_host_chan *)(((mem_addr_t)(base)) + USB_DWC2_HCCHAR(chan_idx))) + +struct uhc_dwc2_config { + /* Pointer to base address of DWC_OTG registers */ + struct usb_dwc2_reg *const base; + /* 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; + /* Sendor-specific structure */ + struct uhc_dwc2_quirk_data *quirk_data; + + void (*irq_enable_func)(const struct device *const dev); + void (*irq_disable_func)(const struct device *const dev); +}; + +struct uhc_dwc2_data { + struct k_thread thread; + struct k_event events; +}; + +/* + * Vendor quirks handling + * + * Definition of vendor-specific functions that can be overwritten on a per-SoC basis + * by defining the associated macro to inhibit the default no-op alias. + */ + +#if DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) +#include "uhc_dwc2_esp32.h" +#endif + +/* Fallback no-op implementations */ +#ifndef UHC_DWC2_HAS_QUIRK_INIT +static int uhc_dwc2_quirk_init(const struct device *const dev) +{ + LOG_DBG("Fallback quirk called for %s", __func__); + return 0; +} +#endif +#ifndef UHC_DWC2_HAS_QUIRK_PRE_ENABLE +static int uhc_dwc2_quirk_pre_enable(const struct device *const dev) +{ + LOG_DBG("Fallback quirk called for %s", __func__); + return 0; +} +#endif +#ifndef UHC_DWC2_HAS_QUIRK_POST_ENABLE +static int uhc_dwc2_quirk_post_enable(const struct device *const dev) +{ + LOG_DBG("Fallback quirk called for %s", __func__); + return 0; +} +#endif +#ifndef UHC_DWC2_HAS_QUIRK_DISABLE +static int uhc_dwc2_quirk_disable(const struct device *const dev) +{ + LOG_DBG("Fallback quirk called for %s", __func__); + return 0; +} +#endif +#ifndef UHC_DWC2_HAS_QUIRK_SHUTDOWN +static int uhc_dwc2_quirk_shutdown(const struct device *const dev) +{ + LOG_DBG("Fallback quirk called for %s", __func__); + return 0; +} +#endif +#ifndef UHC_DWC2_HAS_QUIRK_IRQ_CLEAR +static int uhc_dwc2_quirk_irq_clear(const struct device *const dev) +{ + LOG_DBG("Fallback quirk called for %s", __func__); + return 0; +} +#endif +#ifndef UHC_DWC2_HAS_QUIRK_PREINIT +static int uhc_dwc2_quirk_preinit(const struct device *const dev) +{ + LOG_DBG("Fallback quirk called for %s", __func__); + return 0; +} +#endif + +/* + * Event handling + */ + +static void uhc_dwc2_isr_handler(const struct device *dev) +{ + /* TODO: Interrupt handling */ + + (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 events; + + while (true) { + events = k_event_wait_safe(&priv->events, UINT32_MAX, false, K_FOREVER); + + uhc_lock_internal(dev, K_FOREVER); + + /* TODO: handle port and channel events */ + (void)events; + + uhc_unlock_internal(dev); + } +} + +/* + * Device 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_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) +{ + LOG_WRN("%s has not been implemented", __func__); + + return -ENOSYS; +} + +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) +{ + LOG_WRN("%s has not been implemented", __func__); + + return -ENOSYS; +} + +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); + int ret; + + ret = uhc_dwc2_quirk_preinit(dev); + if (ret != 0) { + return ret; + } + + /* + * TODO: + * use devicetree to get GHWCFGn values and use them to determine the + * number and type of configured endpoints in the hardware as in udc? + */ + + 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); + + return 0; +} + +static int uhc_dwc2_init(const struct device *const dev) +{ + int ret; + + LOG_WRN("%s has not been implemented", __func__); + + ret = uhc_dwc2_quirk_init(dev); + if (ret != 0) { + return ret; + } + + return -ENOSYS; +} + +static int uhc_dwc2_enable(const struct device *const dev) +{ + int ret; + + LOG_WRN("%s has not been implemented", __func__); + + ret = uhc_dwc2_quirk_pre_enable(dev); + if (ret != 0) { + return ret; + } + + ret = uhc_dwc2_quirk_post_enable(dev); + if (ret != 0) { + return ret; + } + + return -ENOSYS; +} + +static int uhc_dwc2_disable(const struct device *const dev) +{ + int ret; + + LOG_WRN("%s has not been implemented", __func__); + + ret = uhc_dwc2_quirk_disable(dev); + if (ret != 0) { + return ret; + } + + return -ENOSYS; +} + +static int uhc_dwc2_shutdown(const struct device *const dev) +{ + int ret; + + LOG_WRN("%s has not been implemented", __func__); + + ret = uhc_dwc2_quirk_shutdown(dev); + if (ret != 0) { + return ret; + } + + return -ENOSYS; +} + +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, +}; + +/* Default IRQ enable/disable functions */ +#ifndef UHC_DWC2_IRQ_DT_INST_DEFINE +#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), 0); \ + 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 + +#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) \ + UHC_DWC2_QUIRK_DEFINE(n) \ + \ + static struct uhc_dwc2_data uhc_dwc2_priv_##n = { \ + .events = Z_EVENT_INITIALIZER(uhc_dwc2_priv_##n.events), \ + }; \ + \ + 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 *)DT_INST_REG_ADDR(n), \ + .stack = uhc_dwc2_stack_##n, \ + .stack_size = K_THREAD_STACK_SIZEOF(uhc_dwc2_stack_##n), \ + .quirk_data = &uhc_dwc2_quirk_data_##n, \ + .irq_enable_func = uhc_dwc2_irq_enable_func_##n, \ + .irq_disable_func = uhc_dwc2_irq_disable_func_##n, \ + }; \ + \ + 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_esp32.h b/drivers/usb/uhc/uhc_dwc2_esp32.h new file mode 100644 index 0000000000000..fbb90d65d8153 --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2_esp32.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 Roman Leonov + * SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +struct uhc_dwc2_quirk_data { + int placeholder; +}; + +static void uhc_dwc2_isr_handler(const struct device *const dev); + +#define UHC_DWC2_IRQ_DT_INST_DEFINE(n) \ + static void uhc_dwc2_irq_enable_func_##n(const struct device *const dev)\ + { \ + (void)uhc_dwc2_isr_handler; \ + /* TODO: esp_intr_enable */ \ + } \ + \ + static void uhc_dwc2_irq_disable_func_##n(const struct device *const dev)\ + { \ + /* TODO: esp_intr_enable */ \ + } + +#define UHC_DWC2_QUIRK_DEFINE(n) \ + static struct uhc_dwc2_quirk_data uhc_dwc2_quirk_data_##n = { \ + .placeholder = 0x1234, \ + }; 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"; +}; diff --git a/samples/subsys/usb/shell/host_prj.conf b/samples/subsys/usb/shell/host_prj.conf new file mode 100644 index 0000000000000..1b8a5f4cc5f45 --- /dev/null +++ b/samples/subsys/usb/shell/host_prj.conf @@ -0,0 +1,7 @@ +CONFIG_LOG=y +CONFIG_SHELL=y +CONFIG_UHC_DRIVER_LOG_LEVEL_WRN=y +CONFIG_USBH_LOG_LEVEL_WRN=y +CONFIG_USBH_SHELL=y +CONFIG_USB_HOST_STACK=y +CONFIG_USB_DEVICE_STACK_NEXT=n