diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index 881bfbf45e6..658508c71f2 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -655,9 +655,15 @@ SRC_C += peripherals/touch.c endif ifneq ($(CIRCUITPY_USB_DEVICE),0) -SRC_C += \ - lib/tinyusb/src/portable/synopsys/dwc2/dcd_dwc2.c \ - lib/tinyusb/src/portable/synopsys/dwc2/dwc2_common.c +SRC_C += lib/tinyusb/src/portable/synopsys/dwc2/dcd_dwc2.c +endif + +ifeq ($(CIRCUITPY_USB_HOST),1) +SRC_C += lib/tinyusb/src/portable/synopsys/dwc2/hcd_dwc2.c +endif + +ifneq ($(filter 1,$(CIRCUITPY_USB_DEVICE) $(CIRCUITPY_USB_HOST)),) +SRC_C += lib/tinyusb/src/portable/synopsys/dwc2/dwc2_common.c endif ifneq ($(CIRCUITPY_AUDIOBUSIO),0) diff --git a/ports/espressif/background.c b/ports/espressif/background.c index b1b81ac2c83..d28b1bb3765 100644 --- a/ports/espressif/background.c +++ b/ports/espressif/background.c @@ -8,11 +8,21 @@ #include "supervisor/filesystem.h" #include "supervisor/port.h" #include "supervisor/shared/stack.h" +#if CIRCUITPY_USB_HOST +#include "supervisor/usb.h" +#include "tusb.h" +#endif #include "freertos/FreeRTOS.h" #include "freertos/task.h" void port_background_tick(void) { + #if CIRCUITPY_USB_HOST + // Enumeration delays need polling even when no new USB IRQ arrives. + if (tuh_task_event_ready()) { + usb_background_schedule(); + } + #endif // Yield with zero delay in case FreeRTOS wants to switch to something else. port_task_yield(); } diff --git a/ports/espressif/common-hal/microcontroller/Pin.c b/ports/espressif/common-hal/microcontroller/Pin.c index 0c49318f4bc..36a83c7d900 100644 --- a/ports/espressif/common-hal/microcontroller/Pin.c +++ b/ports/espressif/common-hal/microcontroller/Pin.c @@ -471,7 +471,7 @@ void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { } bool pin_number_is_free(gpio_num_t pin_number) { - return !(_in_use_pin_mask & PIN_BIT(pin_number)); + return GPIO_IS_VALID_GPIO(pin_number) && !(_in_use_pin_mask & PIN_BIT(pin_number)); } bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { diff --git a/ports/espressif/common-hal/microcontroller/__init__.c b/ports/espressif/common-hal/microcontroller/__init__.c index 7e0d8b7233d..04eb54f8c2c 100644 --- a/ports/espressif/common-hal/microcontroller/__init__.c +++ b/ports/espressif/common-hal/microcontroller/__init__.c @@ -347,5 +347,9 @@ static const mp_rom_map_elem_t mcu_pin_global_dict_table[] = { #ifdef GPIO54_EXISTS { MP_ROM_QSTR(MP_QSTR_GPIO54), MP_ROM_PTR(&pin_GPIO54) }, #endif + #if defined(CONFIG_IDF_TARGET_ESP32P4) + { MP_ROM_QSTR(MP_QSTR_USB_HS_DP), MP_ROM_PTR(&pin_USB_HS_DP) }, + { MP_ROM_QSTR(MP_QSTR_USB_HS_DM), MP_ROM_PTR(&pin_USB_HS_DM) }, + #endif }; MP_DEFINE_CONST_DICT(mcu_pin_globals, mcu_pin_global_dict_table); diff --git a/ports/espressif/common-hal/usb_host/Port.c b/ports/espressif/common-hal/usb_host/Port.c new file mode 100644 index 00000000000..3411006d375 --- /dev/null +++ b/ports/espressif/common-hal/usb_host/Port.c @@ -0,0 +1,62 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Limor Fried for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "bindings/espidf/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/usb_host/Port.h" +#include "supervisor/usb.h" +#include "supervisor/shared/tick.h" +#include "esp_private/usb_phy.h" +#include "tusb.h" + +#if !defined(CONFIG_IDF_TARGET_ESP32P4) || CIRCUITPY_USB_HOST_INSTANCE != 1 +#error "USB host requires the ESP32-P4 high-speed controller" +#endif +#if CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_DEVICE_INSTANCE == CIRCUITPY_USB_HOST_INSTANCE +#error "USB host and device must use different controllers" +#endif + +static usb_host_port_obj_t usb_host_instance; +static usb_phy_handle_t host_phy; + +usb_host_port_obj_t *common_hal_usb_host_port_construct(const mcu_pin_obj_t *dp, const mcu_pin_obj_t *dm) { + // The high-speed PHY has dedicated pads, not GPIOs. + if (dp != &pin_USB_HS_DP || dm != &pin_USB_HS_DM) { + raise_ValueError_invalid_pins(); + } + usb_host_port_obj_t *self = &usb_host_instance; + if (self->dp != NULL) { + return self; + } + + const usb_phy_config_t config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_UTMI, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_HIGH, + }; + CHECK_ESP_RESULT(usb_new_phy(&config, &host_phy)); + if (!tuh_init(TUH_OPT_RHPORT)) { + usb_del_phy(host_phy); + host_phy = NULL; + CHECK_ESP_RESULT(ESP_FAIL); + } + self->base.type = &usb_host_port_type; + self->dp = dp; + self->dm = dm; + // Keep polling enumeration timers even without an active display. + supervisor_enable_tick(); + return self; +} + +// TinyUSB installs the controller IRQ. Process its events on the VM task, +// where the Python USB device and keyboard callbacks are safe to run. +void tuh_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { + // The background tick polls IRQ events; scheduling here wakes a FreeRTOS task. + if (!in_isr) { + usb_background_schedule(); + } +} diff --git a/ports/espressif/common-hal/usb_host/Port.h b/ports/espressif/common-hal/usb_host/Port.h new file mode 100644 index 00000000000..e7b44c9524c --- /dev/null +++ b/ports/espressif/common-hal/usb_host/Port.h @@ -0,0 +1,15 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *dp; + const mcu_pin_obj_t *dm; +} usb_host_port_obj_t; diff --git a/ports/espressif/common-hal/usb_host/__init__.c b/ports/espressif/common-hal/usb_host/__init__.c new file mode 100644 index 00000000000..c3cdbae38c4 --- /dev/null +++ b/ports/espressif/common-hal/usb_host/__init__.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// Nothing diff --git a/ports/espressif/mpconfigport.mk b/ports/espressif/mpconfigport.mk index 48ab8d04580..9bb6793a940 100644 --- a/ports/espressif/mpconfigport.mk +++ b/ports/espressif/mpconfigport.mk @@ -359,6 +359,7 @@ UF2_BOOTLOADER = 0 CIRCUITPY_USB_HID = 0 CIRCUITPY_USB_MIDI = 0 CIRCUITPY_TUSB_MEM_ALIGN = 64 +CIRCUITPY_ALL_MEMORY_DMA_CAPABLE = 0 CIRCUITPY_MAX3421E = 0 diff --git a/ports/espressif/peripherals/esp32p4/pins.c b/ports/espressif/peripherals/esp32p4/pins.c index c981d83abe0..05a6bc33bfa 100644 --- a/ports/espressif/peripherals/esp32p4/pins.c +++ b/ports/espressif/peripherals/esp32p4/pins.c @@ -61,3 +61,7 @@ const mcu_pin_obj_t pin_GPIO51 = PIN(51, ADC_UNIT_2, ADC_CHANNEL_2, NO_TOUCH_CHA const mcu_pin_obj_t pin_GPIO52 = PIN(52, ADC_UNIT_2, ADC_CHANNEL_3, NO_TOUCH_CHANNEL); const mcu_pin_obj_t pin_GPIO53 = PIN(53, ADC_UNIT_2, ADC_CHANNEL_4, NO_TOUCH_CHANNEL); const mcu_pin_obj_t pin_GPIO54 = PIN(54, ADC_UNIT_2, ADC_CHANNEL_5, NO_TOUCH_CHANNEL); + +// The USB high-speed PHY pads cannot be used as GPIOs. +const mcu_pin_obj_t pin_USB_HS_DP = PIN(NO_PIN, NO_ADC, NO_ADC_CHANNEL, NO_TOUCH_CHANNEL); +const mcu_pin_obj_t pin_USB_HS_DM = PIN(NO_PIN, NO_ADC, NO_ADC_CHANNEL, NO_TOUCH_CHANNEL); diff --git a/ports/espressif/peripherals/esp32p4/pins.h b/ports/espressif/peripherals/esp32p4/pins.h index 59cf4258a40..7f9f7717c88 100644 --- a/ports/espressif/peripherals/esp32p4/pins.h +++ b/ports/espressif/peripherals/esp32p4/pins.h @@ -120,3 +120,6 @@ extern const mcu_pin_obj_t pin_GPIO52; extern const mcu_pin_obj_t pin_GPIO53; #define GPIO54_EXISTS 1 extern const mcu_pin_obj_t pin_GPIO54; + +extern const mcu_pin_obj_t pin_USB_HS_DP; +extern const mcu_pin_obj_t pin_USB_HS_DM; diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index 68d97c20dcf..1dc80c04955 100644 --- a/ports/espressif/supervisor/port.c +++ b/ports/espressif/supervisor/port.c @@ -332,7 +332,13 @@ void port_heap_init(void) { void *port_malloc(size_t size, bool dma_capable) { if (dma_capable) { // SPIRAM is not DMA-capable, so don't bother to ask for it. + #if defined(CONFIG_IDF_TARGET_ESP32P4) + // Cache maintenance must not touch memory outside this allocation. + size = (size + CONFIG_CACHE_L1_CACHE_LINE_SIZE - 1) & ~(CONFIG_CACHE_L1_CACHE_LINE_SIZE - 1); + return heap_caps_aligned_alloc(CONFIG_CACHE_L1_CACHE_LINE_SIZE, size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + #else return heap_caps_malloc(size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + #endif } void *ptr = NULL; @@ -346,6 +352,13 @@ void *port_malloc(size_t size, bool dma_capable) { return ptr; } +#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE +bool port_buffer_is_dma_capable(const void *ptr) { + // Python buffers may share cache lines with other objects, even when aligned. + return false; +} +#endif + void port_free(void *ptr) { heap_caps_free(ptr); } diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index a3bb449e87e..b5eaf086bb7 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -248,18 +248,20 @@ static mp_obj_t _get_string(const uint16_t *temp_buf) { return utf16le_to_string(temp_buf + 1, utf16_len); } +static void _get_descriptor(usb_core_device_obj_t *self, uint8_t type, uint8_t index, + uint16_t language, void *buffer, size_t len) { + common_hal_usb_core_device_ctrl_transfer(self, 0x80, TUSB_REQ_GET_DESCRIPTOR, + (type << 8) | index, language, buffer, len, 1000); +} + static void _get_langid(usb_core_device_obj_t *self) { if (self->first_langid != 0) { return; } // Two control bytes and one uint16_t language code. uint16_t temp_buf[2]; - _prepare_for_transfer(); - if (!tuh_descriptor_get_string(self->device_address, 0, 0, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { - mp_raise_usb_core_USBError(NULL); - } else if (_wait_for_callback()) { - self->first_langid = temp_buf[1]; - } + _get_descriptor(self, TUSB_DESC_STRING, 0, 0, temp_buf, sizeof(temp_buf)); + self->first_langid = temp_buf[1]; } mp_obj_t common_hal_usb_core_device_get_serial_number(usb_core_device_obj_t *self) { @@ -274,13 +276,8 @@ mp_obj_t common_hal_usb_core_device_get_serial_number(usb_core_device_obj_t *sel } // Device does provide this string, so continue _get_langid(self); - _prepare_for_transfer(); - if (!tuh_descriptor_get_serial_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { - mp_raise_usb_core_USBError(NULL); - } else if (_wait_for_callback()) { - return _get_string(temp_buf); - } - return mp_const_none; + _get_descriptor(self, TUSB_DESC_STRING, descriptor.iSerialNumber, self->first_langid, temp_buf, sizeof(temp_buf)); + return _get_string(temp_buf); } mp_obj_t common_hal_usb_core_device_get_product(usb_core_device_obj_t *self) { @@ -295,13 +292,8 @@ mp_obj_t common_hal_usb_core_device_get_product(usb_core_device_obj_t *self) { } // Device does provide this string, so continue _get_langid(self); - _prepare_for_transfer(); - if (!tuh_descriptor_get_product_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { - mp_raise_usb_core_USBError(NULL); - } else if (_wait_for_callback()) { - return _get_string(temp_buf); - } - return mp_const_none; + _get_descriptor(self, TUSB_DESC_STRING, descriptor.iProduct, self->first_langid, temp_buf, sizeof(temp_buf)); + return _get_string(temp_buf); } mp_obj_t common_hal_usb_core_device_get_manufacturer(usb_core_device_obj_t *self) { @@ -316,13 +308,8 @@ mp_obj_t common_hal_usb_core_device_get_manufacturer(usb_core_device_obj_t *self } // Device does provide this string, so continue _get_langid(self); - _prepare_for_transfer(); - if (!tuh_descriptor_get_manufacturer_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { - mp_raise_usb_core_USBError(NULL); - } else if (_wait_for_callback()) { - return _get_string(temp_buf); - } - return mp_const_none; + _get_descriptor(self, TUSB_DESC_STRING, descriptor.iManufacturer, self->first_langid, temp_buf, sizeof(temp_buf)); + return _get_string(temp_buf); } @@ -382,17 +369,12 @@ void common_hal_usb_core_device_set_configuration(usb_core_device_obj_t *self, m // Get only the config descriptor first. tusb_desc_configuration_t desc; - if (!tuh_descriptor_get_configuration(self->device_address, config_index, &desc, sizeof(desc), _transfer_done_cb, 0) || - !_wait_for_callback()) { - return; - } + _get_descriptor(self, TUSB_DESC_CONFIGURATION, config_index, 0, &desc, sizeof(desc)); // Get the config descriptor plus interfaces and endpoints. self->configuration_descriptor = m_realloc(self->configuration_descriptor, desc.wTotalLength); - if (!tuh_descriptor_get_configuration(self->device_address, config_index, self->configuration_descriptor, desc.wTotalLength, _transfer_done_cb, 0) || - !_wait_for_callback()) { - return; - } + _get_descriptor(self, TUSB_DESC_CONFIGURATION, config_index, 0, self->configuration_descriptor, desc.wTotalLength); + _prepare_for_transfer(); tuh_configuration_set(self->device_address, configuration, _transfer_done_cb, 0); _wait_for_callback(); } @@ -566,6 +548,9 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, _prepare_for_transfer(); if (!tuh_control_xfer(&xfer)) { + if (dma_buffer != buffer) { + port_free(dma_buffer); + } mp_raise_usb_core_USBError(NULL); return 0; } diff --git a/supervisor/shared/usb/tusb_config.h b/supervisor/shared/usb/tusb_config.h index 8bd920c996f..eeb97c7787f 100644 --- a/supervisor/shared/usb/tusb_config.h +++ b/supervisor/shared/usb/tusb_config.h @@ -94,7 +94,6 @@ extern "C" { // Use DMA with the USB peripheral. #if defined(CONFIG_IDF_TARGET_ESP32P4) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) #define CFG_TUD_DWC2_DMA_ENABLE (1) -#define CFG_TUH_DWC2_DMA_ENABLE (1) #endif // Vendor name included in Inquiry response, max 8 bytes @@ -173,6 +172,10 @@ extern "C" { #if CIRCUITPY_USB_HOST || CIRCUITPY_MAX3421E #define CFG_TUH_ENABLED 1 +#if defined(CONFIG_IDF_TARGET_ESP32P4) +#define CFG_TUH_DWC2_DMA_ENABLE (1) +#endif + // Always use PIO to do host on RP2. #if !CIRCUITPY_MAX3421E #define CFG_TUH_RPI_PIO_USB 1 @@ -180,7 +183,7 @@ extern "C" { #define CFG_TUH_RPI_PIO_USB 1 #endif -#if CIRCUITPY_USB_DEVICE_HIGH_SPEED == 1 +#if CIRCUITPY_USB_DEVICE_HIGH_SPEED == 1 || (defined(CONFIG_IDF_TARGET_ESP32P4) && CIRCUITPY_USB_HOST_INSTANCE == 1) #define _HOST_SPEED OPT_MODE_HIGH_SPEED #else #define _HOST_SPEED 0 diff --git a/supervisor/shared/usb/usb.c b/supervisor/shared/usb/usb.c index 51222be0e59..fbb00541cbc 100644 --- a/supervisor/shared/usb/usb.c +++ b/supervisor/shared/usb/usb.c @@ -167,6 +167,11 @@ void usb_background(void) { #elif CFG_TUSB_OS == OPT_OS_FREERTOS // TinyUSB may run in a separate task, at the same priority as CircuitPython. port_task_yield(); + #if CIRCUITPY_USB_HOST + if (tuh_inited()) { + tuh_task_ext(0, false); + } + #endif #endif // No need to flush if there's no REPL. #if CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_CDC