-
Notifications
You must be signed in to change notification settings - Fork 7.6k
drivers: usb: dwc2: esp32: Add support #85600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
056de98
849f669
9337a9b
81538cc
de96473
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,6 +66,9 @@ enum dwc2_drv_event_type { | |
/* Get Data FIFO access register */ | ||
#define UDC_DWC2_EP_FIFO(base, idx) ((mem_addr_t)base + 0x1000 * (idx + 1)) | ||
|
||
/* Percentage limit of how much SPRAM can be allocated for RxFIFO */ | ||
#define MAX_RXFIFO_GDFIFO_PERCENTAGE 25 | ||
|
||
enum dwc2_suspend_type { | ||
DWC2_SUSPEND_NO_POWER_SAVING, | ||
DWC2_SUSPEND_HIBERNATION, | ||
|
@@ -2099,6 +2102,13 @@ static int udc_dwc2_init_controller(const struct device *dev) | |
if (priv->dynfifosizing) { | ||
uint32_t gnptxfsiz; | ||
uint32_t default_depth; | ||
uint32_t spram_size; | ||
uint32_t max_rxfifo; | ||
|
||
/* Get available SPRAM size and calculate max allocatable RX fifo size */ | ||
val = sys_read32((mem_addr_t)&base->gdfifocfg); | ||
spram_size = usb_dwc2_get_gdfifocfg_gdfifocfg(val); | ||
max_rxfifo = ((spram_size * MAX_RXFIFO_GDFIFO_PERCENTAGE) / 100); | ||
|
||
/* TODO: For proper runtime FIFO sizing UDC driver would have to | ||
* have prior knowledge of the USB configurations. Only with the | ||
|
@@ -2119,7 +2129,7 @@ static int udc_dwc2_init_controller(const struct device *dev) | |
* to store reset value. Read the reset value and make sure that | ||
* the programmed value is not greater than what driver sets. | ||
*/ | ||
priv->rxfifo_depth = MIN(priv->rxfifo_depth, default_depth); | ||
priv->rxfifo_depth = MIN(MIN(priv->rxfifo_depth, default_depth), max_rxfifo); | ||
sys_write32(usb_dwc2_set_grxfsiz(priv->rxfifo_depth), grxfsiz_reg); | ||
|
||
/* Set TxFIFO 0 depth */ | ||
|
@@ -3289,6 +3299,30 @@ static const struct udc_api udc_dwc2_api = { | |
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(UDC_DWC2_IRQ_DT_INST_DEFINE) | ||
#define UDC_DWC2_IRQ_FLAGS_TYPE0(n) 0 | ||
#define UDC_DWC2_IRQ_FLAGS_TYPE1(n) DT_INST_IRQ(n, type) | ||
#define DW_IRQ_FLAGS(n) \ | ||
_CONCAT(UDC_DWC2_IRQ_FLAGS_TYPE, DT_INST_IRQ_HAS_CELL(n, type))(n) | ||
|
||
#define UDC_DWC2_IRQ_DT_INST_DEFINE(n) \ | ||
static void udc_dwc2_irq_enable_func_##n(const struct device *dev) \ | ||
{ \ | ||
IRQ_CONNECT(DT_INST_IRQN(n), \ | ||
DT_INST_IRQ(n, priority), \ | ||
udc_dwc2_isr_handler, \ | ||
DEVICE_DT_INST_GET(n), \ | ||
DW_IRQ_FLAGS(n)); \ | ||
\ | ||
irq_enable(DT_INST_IRQN(n)); \ | ||
} \ | ||
\ | ||
static void udc_dwc2_irq_disable_func_##n(const struct device *dev) \ | ||
{ \ | ||
irq_disable(DT_INST_IRQN(n)); \ | ||
} | ||
#endif | ||
|
||
#define UDC_DWC2_PINCTRL_DT_INST_DEFINE(n) \ | ||
COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \ | ||
(PINCTRL_DT_INST_DEFINE(n)), ()) | ||
|
@@ -3297,11 +3331,6 @@ static const struct udc_api udc_dwc2_api = { | |
COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \ | ||
((void *)PINCTRL_DT_INST_DEV_CONFIG_GET(n)), (NULL)) | ||
|
||
#define UDC_DWC2_IRQ_FLAGS_TYPE0(n) 0 | ||
#define UDC_DWC2_IRQ_FLAGS_TYPE1(n) DT_INST_IRQ(n, type) | ||
#define DW_IRQ_FLAGS(n) \ | ||
_CONCAT(UDC_DWC2_IRQ_FLAGS_TYPE, DT_INST_IRQ_HAS_CELL(n, type))(n) | ||
|
||
/* | ||
* A UDC driver should always be implemented as a multi-instance | ||
* driver, even if your platform does not require it. | ||
|
@@ -3333,21 +3362,7 @@ static const struct udc_api udc_dwc2_api = { | |
k_thread_name_set(&priv->thread_data, dev->name); \ | ||
} \ | ||
\ | ||
static void udc_dwc2_irq_enable_func_##n(const struct device *dev) \ | ||
{ \ | ||
IRQ_CONNECT(DT_INST_IRQN(n), \ | ||
DT_INST_IRQ(n, priority), \ | ||
udc_dwc2_isr_handler, \ | ||
DEVICE_DT_INST_GET(n), \ | ||
DW_IRQ_FLAGS(n)); \ | ||
\ | ||
irq_enable(DT_INST_IRQN(n)); \ | ||
} \ | ||
\ | ||
static void udc_dwc2_irq_disable_func_##n(const struct device *dev) \ | ||
{ \ | ||
irq_disable(DT_INST_IRQN(n)); \ | ||
} \ | ||
UDC_DWC2_IRQ_DT_INST_DEFINE(n) \ | ||
Comment on lines
-3336
to
+3365
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like a stray change that was not justified in the commit message. Anyway, I do not agree with it, you should use irq_enable()/irq_disable(). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for the time being we are using Espressif's interrupt allocator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not understand the problem with irq_enable/irq_disable. How was it accepted not to use generic API for your platform? Are you working on fixing it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did some checking, and the current behavior for ESP Xtensa chips is: Zephyr's API is used to control non-muxed interrupt lines (such as wifi, systimer). Most peripherals such as USB allow routing sources through an interrupt matrix and for this, the interrupt controller API is used. I'm not sure how much this deviates from the standard implementation, but I believe significant changes would be needed to otherwise accommodate this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jfischer-no, for esp32 device, we still need the irq calls in there, otherwise it is no-go for now. Would you review it again? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still cannot find an explanation as to why your platform cannot use generic functions, or why this has not been done all the time is supported in Zephyr. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ESP chips use an interrupt controller peripheral to route interrupt sources coming from each peripheral to the CPU's IRQ lines. For this reason we implement an interrupt controller driver and call it's API directly from all ESP drivers. A few peripherals have their interrupt source lines connected directly to the CPU IRQ's, thus having dedicated lines (e.g. wifi). As USB OTG is routed using the Interrupt Matrix peripheral we need to connect it's interrupt source to the IRQ accessing HAL LL calls. As this can't be accomplished using Zephyr's |
||
\ | ||
static struct udc_ep_config ep_cfg_out[DT_INST_PROP(n, num_out_eps)]; \ | ||
static struct udc_ep_config ep_cfg_in[DT_INST_PROP(n, num_in_eps)]; \ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -312,6 +312,171 @@ DT_INST_FOREACH_STATUS_OKAY(QUIRK_NRF_USBHS_DEFINE) | |
|
||
#endif /*DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs) */ | ||
|
||
#if DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) | ||
|
||
#include <zephyr/drivers/interrupt_controller/intc_esp32.h> | ||
#include <zephyr/drivers/clock_control.h> | ||
#include <zephyr/logging/log.h> | ||
|
||
#include <esp_err.h> | ||
#include <esp_private/usb_phy.h> | ||
#include <hal/usb_wrap_hal.h> | ||
#include <soc/usb_dwc_periph.h> | ||
|
||
#include <esp_rom_gpio.h> | ||
#include <driver/gpio.h> | ||
#include <soc/usb_pins.h> | ||
#include <soc/gpio_sig_map.h> | ||
|
||
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 *iopins; | ||
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 udc_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; | ||
|
||
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_DEVICE */ | ||
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_IDDIG_IN_IDX, false); | ||
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_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_ZERO_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); | ||
} | ||
Comment on lines
+373
to
+382
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can it use pinctrl if not why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As we are adding support initially for OTG using only internal transceiver, pins are fixed (cannot be configured freely). |
||
|
||
/* 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)udc_dwc2_isr_handler, (void *)dev, &data->int_handle); | ||
|
||
return ret; | ||
} | ||
|
||
static inline int esp32_usb_otg_enable_phy(struct phy_context_t *phy_ctx, bool enable) | ||
{ | ||
LOG_MODULE_DECLARE(udc_dwc2, CONFIG_UDC_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 | ||
|
||
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; | ||
} | ||
|
||
#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_DEVICE, \ | ||
.otg_speed = USB_PHY_SPEED_FULL, \ | ||
.iopins = 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); \ | ||
} \ | ||
\ | ||
const struct dwc2_vendor_quirks dwc2_vendor_quirks_##n = { \ | ||
.init = esp32_usb_otg_init_##n, \ | ||
.post_enable = esp32_usb_otg_enable_phy_##n, \ | ||
.disable = esp32_usb_otg_disable_phy_##n, \ | ||
}; \ | ||
|
||
#define UDC_DWC2_IRQ_DT_INST_DEFINE(n) \ | ||
static void udc_dwc2_irq_enable_func_##n(const struct device *dev) \ | ||
{ \ | ||
esp_intr_enable(usb_otg_data_##n.int_handle); \ | ||
} \ | ||
\ | ||
static void udc_dwc2_irq_disable_func_##n(const struct device *dev) \ | ||
{ \ | ||
esp_intr_disable(usb_otg_data_##n.int_handle); \ | ||
} | ||
|
||
DT_INST_FOREACH_STATUS_OKAY(QUIRK_ESP32_USB_OTG_DEFINE) | ||
|
||
#endif /*DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) */ | ||
|
||
/* Add next vendor quirks definition above this line */ | ||
|
||
#endif /* ZEPHYR_DRIVERS_USB_UDC_DWC2_VENDOR_QUIRKS_H */ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# | ||
description: ESP32 USB-OTG (DWC2 compatible controller) | ||
|
||
compatible: "espressif,esp32-usb-otg" | ||
|
||
include: ["snps,dwc2.yaml"] | ||
|
||
properties: | ||
clocks: | ||
required: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we introduced a maximum limit in percentage, maybe it could be better to make this constant configurable (I tried it with several values and seems that we can set the range
25-75
). WDYT ?Something like:
with the changes in
zephyr/drivers/usb/udc/Kconfig
:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why 25 to 75? If this ends up as Kconfig, then 100% should be the default (disable the limit altogether, which should remove the code that puts the limit in place) because the DWC2 Kconfig options are shared by all targets. You could then add different default based on your target.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, agreed.
May I ask is there any available Nordic board (dev kit) that can easily be used to verify usb applications?