From 5f1d32378e919f729941538877cdcb26e926ea79 Mon Sep 17 00:00:00 2001 From: Eagle Claw <301410538+ladyada-eagleclaw@users.noreply.github.com> Date: Fri, 2 Oct 2026 18:38:28 -0400 Subject: [PATCH 1/3] Add Adafruit Metro ESP32-P4 board Co-authored-by: Limor Fried --- .../boards/adafruit_metro_esp32p4/board.c | 19 +++++ .../adafruit_metro_esp32p4/mpconfigboard.h | 30 +++++++ .../adafruit_metro_esp32p4/mpconfigboard.mk | 14 ++++ .../boards/adafruit_metro_esp32p4/pins.c | 79 +++++++++++++++++++ .../boards/adafruit_metro_esp32p4/sdkconfig | 2 + 5 files changed, 144 insertions(+) create mode 100644 ports/espressif/boards/adafruit_metro_esp32p4/board.c create mode 100644 ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.h create mode 100644 ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.mk create mode 100644 ports/espressif/boards/adafruit_metro_esp32p4/pins.c create mode 100644 ports/espressif/boards/adafruit_metro_esp32p4/sdkconfig diff --git a/ports/espressif/boards/adafruit_metro_esp32p4/board.c b/ports/espressif/boards/adafruit_metro_esp32p4/board.c new file mode 100644 index 00000000000..53af421609b --- /dev/null +++ b/ports/espressif/boards/adafruit_metro_esp32p4/board.c @@ -0,0 +1,19 @@ +// 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 "supervisor/board.h" +#include "esp_ldo_regulator.h" + +static esp_ldo_channel_handle_t io_ldo; + +void board_init(void) { + // The SD card and C6 control pins (GPIO39-48) require the VO4 3.3 V rail. + const esp_ldo_channel_config_t config = { + .chan_id = 4, + .voltage_mv = 3300, + }; + ESP_ERROR_CHECK(esp_ldo_acquire_channel(&config, &io_ldo)); +} diff --git a/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.h b/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.h new file mode 100644 index 00000000000..b6675893027 --- /dev/null +++ b/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.h @@ -0,0 +1,30 @@ +// 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 + +#pragma once + +#define MICROPY_HW_BOARD_NAME "Adafruit Metro ESP32-P4" +#define MICROPY_HW_MCU_NAME "ESP32P4" + +#define MICROPY_HW_NEOPIXEL (&pin_GPIO23) +#define CIRCUITPY_BOOT_BUTTON (&pin_GPIO35) + +#define DEFAULT_I2C_BUS_SCL (&pin_GPIO32) +#define DEFAULT_I2C_BUS_SDA (&pin_GPIO33) + +#define DEFAULT_SPI_BUS_SCK (&pin_GPIO15) +#define DEFAULT_SPI_BUS_MOSI (&pin_GPIO16) +#define DEFAULT_SPI_BUS_MISO (&pin_GPIO17) + +#define DEFAULT_UART_BUS_RX (&pin_GPIO0) +#define DEFAULT_UART_BUS_TX (&pin_GPIO1) + +// USB-C shares GPIO24/25 with the ROM USB Serial/JTAG downloader. +#define CIRCUITPY_USB_DEVICE_INSTANCE 0 +#define CIRCUITPY_ESP32P4_SWAP_LSFS (1) + +// USB-A is connected to the dedicated high-speed PHY. +#define CIRCUITPY_USB_HOST_INSTANCE 1 diff --git a/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.mk b/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.mk new file mode 100644 index 00000000000..e14f40574d4 --- /dev/null +++ b/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.mk @@ -0,0 +1,14 @@ +USB_VID = 0x239A +USB_PID = 0x817C +USB_PRODUCT = "Adafruit Metro ESP32-P4" +USB_MANUFACTURER = "Adafruit" + +IDF_TARGET = esp32p4 + +CIRCUITPY_ESP_FLASH_SIZE = 16MB +CIRCUITPY_ESP_FLASH_MODE = dio +CIRCUITPY_ESP_FLASH_FREQ = 80m + +CIRCUITPY_ESP_PSRAM_SIZE = 16MB +CIRCUITPY_ESP_PSRAM_MODE = hpi +CIRCUITPY_ESP_PSRAM_FREQ = 200m diff --git a/ports/espressif/boards/adafruit_metro_esp32p4/pins.c b/ports/espressif/boards/adafruit_metro_esp32p4/pins.c new file mode 100644 index 00000000000..12061b079ab --- /dev/null +++ b/ports/espressif/boards/adafruit_metro_esp32p4/pins.c @@ -0,0 +1,79 @@ +// 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 "shared-bindings/board/__init__.h" + +static const mp_rom_map_elem_t board_module_globals_table[] = { + CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS + + { MP_ROM_QSTR(MP_QSTR_A0), MP_ROM_PTR(&pin_GPIO49) }, + { MP_ROM_QSTR(MP_QSTR_A1), MP_ROM_PTR(&pin_GPIO50) }, + { MP_ROM_QSTR(MP_QSTR_A2), MP_ROM_PTR(&pin_GPIO51) }, + { MP_ROM_QSTR(MP_QSTR_A3), MP_ROM_PTR(&pin_GPIO52) }, + { MP_ROM_QSTR(MP_QSTR_A4), MP_ROM_PTR(&pin_GPIO53) }, + { MP_ROM_QSTR(MP_QSTR_A5), MP_ROM_PTR(&pin_GPIO54) }, + + { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO0) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO0) }, + { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_D3), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_D7), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_D8), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO13) }, + + { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO15) }, + { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO16) }, + { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO32) }, + { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO33) }, + + { MP_ROM_QSTR(MP_QSTR_BUTTON), MP_ROM_PTR(&pin_GPIO35) }, + { MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO23) }, + { MP_ROM_QSTR(MP_QSTR_VOLTAGE_MONITOR), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_USB_HOST_POWER), MP_ROM_PTR(&pin_GPIO14) }, + { MP_ROM_QSTR(MP_QSTR_DEBUG_TX), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_DEBUG_RX), MP_ROM_PTR(&pin_GPIO38) }, + + { MP_ROM_QSTR(MP_QSTR_SD_CLK), MP_ROM_PTR(&pin_GPIO43) }, + { MP_ROM_QSTR(MP_QSTR_SD_CMD), MP_ROM_PTR(&pin_GPIO44) }, + { MP_ROM_QSTR(MP_QSTR_SD_DATA0), MP_ROM_PTR(&pin_GPIO39) }, + { MP_ROM_QSTR(MP_QSTR_SD_DATA1), MP_ROM_PTR(&pin_GPIO40) }, + { MP_ROM_QSTR(MP_QSTR_SD_DATA2), MP_ROM_PTR(&pin_GPIO41) }, + { MP_ROM_QSTR(MP_QSTR_SD_DATA3), MP_ROM_PTR(&pin_GPIO42) }, + { MP_ROM_QSTR(MP_QSTR_SD_CD), MP_ROM_PTR(&pin_GPIO45) }, + + { MP_ROM_QSTR(MP_QSTR_C6_RX), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_C6_TX), MP_ROM_PTR(&pin_GPIO27) }, + { MP_ROM_QSTR(MP_QSTR_C6_BOOT), MP_ROM_PTR(&pin_GPIO46) }, + { MP_ROM_QSTR(MP_QSTR_C6_RESET), MP_ROM_PTR(&pin_GPIO47) }, + { MP_ROM_QSTR(MP_QSTR_C6_WAKE), MP_ROM_PTR(&pin_GPIO48) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO_CLK), MP_ROM_PTR(&pin_GPIO34) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO_CMD), MP_ROM_PTR(&pin_GPIO36) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO_DATA0), MP_ROM_PTR(&pin_GPIO31) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO_DATA1), MP_ROM_PTR(&pin_GPIO30) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO_DATA2), MP_ROM_PTR(&pin_GPIO29) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO_DATA3), MP_ROM_PTR(&pin_GPIO28) }, + + { MP_ROM_QSTR(MP_QSTR_CAMERA_RESET), MP_ROM_PTR(&pin_GPIO21) }, + { MP_ROM_QSTR(MP_QSTR_LCD_BACKLIGHT), MP_ROM_PTR(&pin_GPIO19) }, + { MP_ROM_QSTR(MP_QSTR_LCD_RESET), MP_ROM_PTR(&pin_GPIO20) }, + + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, + { MP_ROM_QSTR(MP_QSTR_STEMMA_I2C), MP_ROM_PTR(&board_i2c_obj) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) }, + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/espressif/boards/adafruit_metro_esp32p4/sdkconfig b/ports/espressif/boards/adafruit_metro_esp32p4/sdkconfig new file mode 100644 index 00000000000..6b0a548f6b9 --- /dev/null +++ b/ports/espressif/boards/adafruit_metro_esp32p4/sdkconfig @@ -0,0 +1,2 @@ +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y +CONFIG_ESP32P4_REV_MIN_1=y From 94b22626f8aa6495ee6229fad87aa10930de23e3 Mon Sep 17 00:00:00 2001 From: Eagle Claw <301410538+ladyada-eagleclaw@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:55:17 -0400 Subject: [PATCH 2/3] Add ESP32-P4 high-speed USB host port support Use the dedicated high-speed PHY for usb_host.Port while the other controller remains available for the USB device. Expose the dedicated pads as pins that cannot be claimed for GPIO, and process host events on the CircuitPython task under FreeRTOS. Enabled for Metro ESP32-P4. Hardware checks passed host initialization, singleton reuse, invalid-pin rejection, USB-C operation and resets. Attached-device enumeration and keyboard reports remain pending connection of test hardware. All six P4 boards built and pre-commit checks passed. Co-authored-by: Limor Fried --- ports/espressif/Makefile | 4 ++ .../adafruit_metro_esp32p4/mpconfigboard.mk | 2 + .../boards/adafruit_metro_esp32p4/pins.c | 2 + .../common-hal/microcontroller/Pin.c | 2 +- .../common-hal/microcontroller/__init__.c | 4 ++ ports/espressif/common-hal/usb_host/Port.c | 56 +++++++++++++++++++ ports/espressif/common-hal/usb_host/Port.h | 15 +++++ .../espressif/common-hal/usb_host/__init__.c | 7 +++ ports/espressif/peripherals/esp32p4/pins.c | 4 ++ ports/espressif/peripherals/esp32p4/pins.h | 3 + supervisor/shared/usb/tusb_config.h | 2 +- supervisor/shared/usb/usb.c | 5 ++ 12 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 ports/espressif/common-hal/usb_host/Port.c create mode 100644 ports/espressif/common-hal/usb_host/Port.h create mode 100644 ports/espressif/common-hal/usb_host/__init__.c diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index 881bfbf45e6..6b2d26e0457 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -660,6 +660,10 @@ SRC_C += \ lib/tinyusb/src/portable/synopsys/dwc2/dwc2_common.c endif +ifeq ($(CIRCUITPY_USB_HOST),1) +SRC_C += lib/tinyusb/src/portable/synopsys/dwc2/hcd_dwc2.c +endif + ifneq ($(CIRCUITPY_AUDIOBUSIO),0) CHIP_COMPONENTS += esp_driver_i2s endif diff --git a/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.mk b/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.mk index e14f40574d4..44668c81193 100644 --- a/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.mk @@ -5,6 +5,8 @@ USB_MANUFACTURER = "Adafruit" IDF_TARGET = esp32p4 +CIRCUITPY_USB_HOST = 1 + CIRCUITPY_ESP_FLASH_SIZE = 16MB CIRCUITPY_ESP_FLASH_MODE = dio CIRCUITPY_ESP_FLASH_FREQ = 80m diff --git a/ports/espressif/boards/adafruit_metro_esp32p4/pins.c b/ports/espressif/boards/adafruit_metro_esp32p4/pins.c index 12061b079ab..ee2679ae1f0 100644 --- a/ports/espressif/boards/adafruit_metro_esp32p4/pins.c +++ b/ports/espressif/boards/adafruit_metro_esp32p4/pins.c @@ -43,6 +43,8 @@ static const mp_rom_map_elem_t board_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_BUTTON), MP_ROM_PTR(&pin_GPIO35) }, { MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO23) }, { MP_ROM_QSTR(MP_QSTR_VOLTAGE_MONITOR), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_USB_HOST_DP), MP_ROM_PTR(&pin_USB_HS_DP) }, + { MP_ROM_QSTR(MP_QSTR_USB_HOST_DM), MP_ROM_PTR(&pin_USB_HS_DM) }, { MP_ROM_QSTR(MP_QSTR_USB_HOST_POWER), MP_ROM_PTR(&pin_GPIO14) }, { MP_ROM_QSTR(MP_QSTR_DEBUG_TX), MP_ROM_PTR(&pin_GPIO37) }, { MP_ROM_QSTR(MP_QSTR_DEBUG_RX), MP_ROM_PTR(&pin_GPIO38) }, 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 2da2d09f22d..c40d9d8c957 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..8fbe3eef3b2 --- /dev/null +++ b/ports/espressif/common-hal/usb_host/Port.c @@ -0,0 +1,56 @@ +// 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 "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; + 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) { + 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/peripherals/esp32p4/pins.c b/ports/espressif/peripherals/esp32p4/pins.c index a4e085e6971..b6e014199f2 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_4, NO_TOUCH_CHA const mcu_pin_obj_t pin_GPIO52 = PIN(52, ADC_UNIT_2, ADC_CHANNEL_5, NO_TOUCH_CHANNEL); const mcu_pin_obj_t pin_GPIO53 = PIN(53, ADC_UNIT_2, ADC_CHANNEL_6, NO_TOUCH_CHANNEL); const mcu_pin_obj_t pin_GPIO54 = PIN(54, ADC_UNIT_2, ADC_CHANNEL_7, 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/supervisor/shared/usb/tusb_config.h b/supervisor/shared/usb/tusb_config.h index 8bd920c996f..e66b5f71e90 100644 --- a/supervisor/shared/usb/tusb_config.h +++ b/supervisor/shared/usb/tusb_config.h @@ -180,7 +180,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 From 8f38438cef691a24bef220a89d203a5fe7d604e8 Mon Sep 17 00:00:00 2001 From: Eagle Claw <301410538+ladyada-eagleclaw@users.noreply.github.com> Date: Fri, 2 Oct 2026 20:08:37 -0400 Subject: [PATCH 3/3] Poll ESP32 USB host enumeration timers Co-authored-by: Limor Fried --- ports/espressif/background.c | 10 ++++++++++ ports/espressif/common-hal/usb_host/Port.c | 3 +++ 2 files changed, 13 insertions(+) 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/usb_host/Port.c b/ports/espressif/common-hal/usb_host/Port.c index 8fbe3eef3b2..510906289a1 100644 --- a/ports/espressif/common-hal/usb_host/Port.c +++ b/ports/espressif/common-hal/usb_host/Port.c @@ -8,6 +8,7 @@ #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" @@ -46,6 +47,8 @@ usb_host_port_obj_t *common_hal_usb_host_port_construct(const mcu_pin_obj_t *dp, 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; }