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/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/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..44668c81193 --- /dev/null +++ b/ports/espressif/boards/adafruit_metro_esp32p4/mpconfigboard.mk @@ -0,0 +1,16 @@ +USB_VID = 0x239A +USB_PID = 0x817C +USB_PRODUCT = "Adafruit Metro ESP32-P4" +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 + +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..ee2679ae1f0 --- /dev/null +++ b/ports/espressif/boards/adafruit_metro_esp32p4/pins.c @@ -0,0 +1,81 @@ +// 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_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) }, + + { 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 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..510906289a1 --- /dev/null +++ b/ports/espressif/common-hal/usb_host/Port.c @@ -0,0 +1,59 @@ +// 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) { + 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