diff --git a/device/esp_tinyusb/CMakeLists.txt b/device/esp_tinyusb/CMakeLists.txt index 04ab9b8b..b5ca4152 100644 --- a/device/esp_tinyusb/CMakeLists.txt +++ b/device/esp_tinyusb/CMakeLists.txt @@ -1,3 +1,5 @@ +idf_build_get_property(target IDF_TARGET) + set(srcs "descriptors_control.c" "tinyusb.c" @@ -43,6 +45,13 @@ if(CONFIG_TINYUSB_NET_MODE_NCM) ) endif() # CONFIG_TINYUSB_NET_MODE_NCM +# Software VBUS monitoring is available only on esp32p4 +if(${target} STREQUAL "esp32p4") + list(APPEND srcs + "tinyusb_vbus_monitor.c" + ) +endif() # esp32p4 + idf_component_register(SRCS ${srcs} INCLUDE_DIRS "include" PRIV_INCLUDE_DIRS "include_private" diff --git a/device/esp_tinyusb/idf_component.yml b/device/esp_tinyusb/idf_component.yml index 408fc428..b7a45722 100644 --- a/device/esp_tinyusb/idf_component.yml +++ b/device/esp_tinyusb/idf_component.yml @@ -11,5 +11,7 @@ targets: dependencies: idf: '>=5.0' # IDF 4.x contains TinyUSB as submodule tinyusb: - version: '>=0.17.0~2' # 0.17.0~2 is the first version that supports deinit + # TODO: Revert after IEC-403 + # version: '>=0.17.0~2' # 0.17.0~2 is the first version that supports deinit + version: '^0.18.0' public: true diff --git a/device/esp_tinyusb/include/tinyusb.h b/device/esp_tinyusb/include/tinyusb.h index cb302263..cab99e47 100644 --- a/device/esp_tinyusb/include/tinyusb.h +++ b/device/esp_tinyusb/include/tinyusb.h @@ -51,6 +51,7 @@ typedef struct { The voltage divider output should be (0.75 * Vdd) if VBUS is 4.4V (lowest valid voltage at device port). The comparator thresholds should be set with hysteresis: 4.35V (falling edge) and 4.75V (raising edge). */ int vbus_monitor_io; /*!< GPIO for VBUS monitoring, 3.3 V tolerant (use a comparator or a resistior divider to detect the VBUS valid condition). Ignored if not self_powered. */ + uint32_t vbus_monitor_debounce_ms; /*!< Debounce delay for VBUS monitoring in milliseconds. Default is 250 ms. Relevant only for ESP32P4 and ignored if not self_powered. */ } tinyusb_phy_config_t; /** diff --git a/device/esp_tinyusb/include/tinyusb_default_config.h b/device/esp_tinyusb/include/tinyusb_default_config.h index a1cb9714..e61043ad 100644 --- a/device/esp_tinyusb/include/tinyusb_default_config.h +++ b/device/esp_tinyusb/include/tinyusb_default_config.h @@ -63,54 +63,58 @@ extern "C" { #define TINYUSB_DEFAULT_TASK_SIZE 4096 // Default priority for task used in TinyUSB task creation #define TINYUSB_DEFAULT_TASK_PRIO 5 +// Default VBUS debounce time in milliseconds +#define TINYUSB_DEFAULT_DEBOUNCE_MS 250 -#define TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) \ - (tinyusb_config_t) { \ - .port = TINYUSB_PORT_FULL_SPEED_0, \ - .phy = { \ - .skip_setup = false, \ - .self_powered = false, \ - .vbus_monitor_io = -1, \ - }, \ - .task = TINYUSB_TASK_DEFAULT(), \ - .descriptor = { \ - .device = NULL, \ - .qualifier = NULL, \ - .string = NULL, \ - .string_count = 0, \ - .full_speed_config = NULL, \ - .high_speed_config = NULL, \ - }, \ - .event_cb = (event_hdl), \ - .event_arg = (arg), \ +#define TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) \ + (tinyusb_config_t) { \ + .port = TINYUSB_PORT_FULL_SPEED_0, \ + .phy = { \ + .skip_setup = false, \ + .self_powered = false, \ + .vbus_monitor_io = -1, \ + .vbus_monitor_debounce_ms = TINYUSB_DEFAULT_DEBOUNCE_MS, \ + }, \ + .task = TINYUSB_TASK_DEFAULT(), \ + .descriptor = { \ + .device = NULL, \ + .qualifier = NULL, \ + .string = NULL, \ + .string_count = 0, \ + .full_speed_config = NULL, \ + .high_speed_config = NULL, \ + }, \ + .event_cb = (event_hdl), \ + .event_arg = (arg), \ } -#define TINYUSB_CONFIG_HIGH_SPEED(event_hdl, arg) \ - (tinyusb_config_t) { \ - .port = TINYUSB_PORT_HIGH_SPEED_0, \ - .phy = { \ - .skip_setup = false, \ - .self_powered = false, \ - .vbus_monitor_io = -1, \ - }, \ - .task = TINYUSB_TASK_DEFAULT(), \ - .descriptor = { \ - .device = NULL, \ - .qualifier = NULL, \ - .string = NULL, \ - .string_count = 0, \ - .full_speed_config = NULL, \ - .high_speed_config = NULL, \ - }, \ - .event_cb = (event_hdl), \ - .event_arg = (arg), \ +#define TINYUSB_CONFIG_HIGH_SPEED(event_hdl, arg) \ + (tinyusb_config_t) { \ + .port = TINYUSB_PORT_HIGH_SPEED_0, \ + .phy = { \ + .skip_setup = false, \ + .self_powered = false, \ + .vbus_monitor_io = -1, \ + .vbus_monitor_debounce_ms = TINYUSB_DEFAULT_DEBOUNCE_MS, \ + }, \ + .task = TINYUSB_TASK_DEFAULT(), \ + .descriptor = { \ + .device = NULL, \ + .qualifier = NULL, \ + .string = NULL, \ + .string_count = 0, \ + .full_speed_config = NULL, \ + .high_speed_config = NULL, \ + }, \ + .event_cb = (event_hdl), \ + .event_arg = (arg), \ } -#define TINYUSB_TASK_DEFAULT() \ - (tinyusb_task_config_t) { \ - .size = TINYUSB_DEFAULT_TASK_SIZE, \ - .priority = TINYUSB_DEFAULT_TASK_PRIO, \ - .xCoreID = TINYUSB_DEFAULT_TASK_AFFINITY, \ +#define TINYUSB_TASK_DEFAULT() \ + (tinyusb_task_config_t) { \ + .size = TINYUSB_DEFAULT_TASK_SIZE, \ + .priority = TINYUSB_DEFAULT_TASK_PRIO, \ + .xCoreID = TINYUSB_DEFAULT_TASK_AFFINITY, \ } /** diff --git a/device/esp_tinyusb/include_private/tinyusb_task.h b/device/esp_tinyusb/include_private/tinyusb_task.h index f0302638..23244d67 100644 --- a/device/esp_tinyusb/include_private/tinyusb_task.h +++ b/device/esp_tinyusb/include_private/tinyusb_task.h @@ -8,6 +8,7 @@ #include "esp_err.h" #include "tinyusb.h" +#include "tinyusb_vbus_monitor.h" #ifdef __cplusplus extern "C" { @@ -42,7 +43,10 @@ esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *task_cfg); * - ESP_ERR_NO_MEM if memory allocation failed * - ESP_OK if TinyUSB Task initialized successfully */ -esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *task_cfg, const tinyusb_desc_config_t *desc_cfg); +esp_err_t tinyusb_task_start(tinyusb_port_t port, + const tinyusb_task_config_t *task_cfg, + const tinyusb_desc_config_t *desc_cfg, + const tinyusb_vbus_monitor_config_t *vbus_monitor_cfg); /** * @brief Stops TinyUSB Task diff --git a/device/esp_tinyusb/include_private/tinyusb_vbus_monitor.h b/device/esp_tinyusb/include_private/tinyusb_vbus_monitor.h new file mode 100644 index 00000000..cd2b14fe --- /dev/null +++ b/device/esp_tinyusb/include_private/tinyusb_vbus_monitor.h @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "tinyusb.h" +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + bool enabled; /*!< Enable VBUS monitoring */ + gpio_num_t gpio_num; /*!< GPIO number used for VBUS monitoring, 3.3 V tolerant */ + uint32_t debounce_delay_ms; /*!< Debounce delay in milliseconds */ +} tinyusb_vbus_monitor_config_t; + +/** + * @brief Initialize VBUS monitoring on the specified GPIO + * + * Note: + * - This function should be called after tusb_init() when GOTGCTL register is initialized + * - This is a single-threaded implementation, so only one instance of VBUS monitoring is supported + * + * @param vbus_io_num GPIO number used for VBUS monitoring, 3.3 V tolerant (use a comparator or a resistor divider to detect the VBUS valid condition). + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if failed to create resources + * - ESP_ERR_INVALID_STATE if already initialized + */ +esp_err_t tinyusb_vbus_monitor_init(tinyusb_vbus_monitor_config_t *config); + +/** + * @brief Deinitialize VBUS monitoring + */ +void tinyusb_vbus_monitor_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/device/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c b/device/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c deleted file mode 100644 index 27da67d1..00000000 --- a/device/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "soc/soc_caps.h" - -#if SOC_USB_OTG_SUPPORTED - -#include -#include -#include "esp_system.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_log.h" -#include "esp_err.h" -#include "driver/gpio.h" -#include "esp_rom_gpio.h" -#include "soc/gpio_sig_map.h" -#include "unity.h" -#include "tinyusb.h" -#include "tinyusb_default_config.h" - -#define DEVICE_DETACH_TEST_ROUNDS 10 -#define DEVICE_DETACH_ROUND_DELAY_MS 1000 - -#if (CONFIG_IDF_TARGET_ESP32P4) -#define USB_SRP_BVALID_IN_IDX USB_SRP_BVALID_PAD_IN_IDX -#endif // CONFIG_IDF_TARGET_ESP32P4 - -/* TinyUSB descriptors - ********************************************************************* */ -#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN) - -static unsigned int dev_mounted = 0; -static unsigned int dev_umounted = 0; - -static uint8_t const test_configuration_descriptor[] = { - // Config number, interface count, string index, total length, attribute, power in mA - TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), -}; - -static const tusb_desc_device_t test_device_descriptor = { - .bLength = sizeof(test_device_descriptor), - .bDescriptorType = TUSB_DESC_DEVICE, - .bcdUSB = 0x0200, - .bDeviceClass = TUSB_CLASS_MISC, - .bDeviceSubClass = MISC_SUBCLASS_COMMON, - .bDeviceProtocol = MISC_PROTOCOL_IAD, - .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, - .idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers - .idProduct = 0x4002, - .bcdDevice = 0x100, - .iManufacturer = 0x01, - .iProduct = 0x02, - .iSerialNumber = 0x03, - .bNumConfigurations = 0x01 -}; - -#if (TUD_OPT_HIGH_SPEED) -static const tusb_desc_device_qualifier_t device_qualifier = { - .bLength = sizeof(tusb_desc_device_qualifier_t), - .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, - .bcdUSB = 0x0200, - .bDeviceClass = TUSB_CLASS_MISC, - .bDeviceSubClass = MISC_SUBCLASS_COMMON, - .bDeviceProtocol = MISC_PROTOCOL_IAD, - .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, - .bNumConfigurations = 0x01, - .bReserved = 0 -}; -#endif // TUD_OPT_HIGH_SPEED - -/** - * @brief TinyUSB callback for device event - * - * @note - * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. - * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. - */ -void test_dconn_event_handler(tinyusb_event_t *event, void *arg) -{ - switch (event->id) { - case TINYUSB_EVENT_ATTACHED: - printf("%s\n", __FUNCTION__); - dev_mounted++; - break; - case TINYUSB_EVENT_DETACHED: - printf("%s\n", __FUNCTION__); - dev_umounted++; - break; - default: - break; - } -} - -/** - * @brief TinyUSB Disconnect Detection test case - * - * This is specific artificial test for verifying the disconnection detection event. - * Normally, this event comes as a result of detaching USB device from the port and disappearing the VBUS voltage. - * In this test case, we use GPIO matrix and connect the signal to the ZERO or ONE constant inputs. - * Connection to constant ONE input emulates the attachment to the USB Host port (appearing VBUS). - * Connection to constant ZERO input emulates the detachment from the USB Host port (removing VBUS). - * - * Test logic: - * - Install TinyUSB Device stack without any class - * - In cycle: - * - Emulate the detachment, get the tud_umount_cb(), increase the dev_umounted value - * - Emulate the attachment, get the tud_mount_cb(), increase the dev_mounted value - * - Verify that dev_umounted == dev_mounted - * - Verify that dev_mounted == DEVICE_DETACH_TEST_ROUNDS, where DEVICE_DETACH_TEST_ROUNDS - amount of rounds - * - Uninstall TinyUSB Device stack - * - */ -TEST_CASE("dconn_detection", "[esp_tinyusb][dconn]") -{ - unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; - - // Install TinyUSB driver - tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); - tusb_cfg.descriptor.device = &test_device_descriptor; - tusb_cfg.descriptor.full_speed_config = test_configuration_descriptor; -#if (TUD_OPT_HIGH_SPEED) - tusb_cfg.descriptor.qualifier = &device_qualifier; - tusb_cfg.descriptor.high_speed_config = test_configuration_descriptor; -#endif // TUD_OPT_HIGH_SPEED - - TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); - - dev_mounted = 0; - dev_umounted = 0; - - while (rounds--) { - // LOW to emulate disconnect USB device - esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false); - vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); - // HIGH to emulate connect USB device - esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_SRP_BVALID_IN_IDX, false); - vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); - } - - // Verify - TEST_ASSERT_EQUAL(dev_umounted, dev_mounted); - TEST_ASSERT_EQUAL(DEVICE_DETACH_TEST_ROUNDS, dev_mounted); - - // Cleanup - TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); -} -#endif // SOC_USB_OTG_SUPPORTED diff --git a/device/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py b/device/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py deleted file mode 100644 index b7a4e7a3..00000000 --- a/device/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -import pytest -from pytest_embedded_idf.dut import IdfDut - - -@pytest.mark.esp32s2 -@pytest.mark.esp32s3 -@pytest.mark.esp32p4 -#@pytest.mark.usb_device Disable in CI: unavailable teardown for P4 -def test_usb_device_dconn_detection(dut: IdfDut) -> None: - dut.run_all_single_board_cases(group='dconn') diff --git a/device/esp_tinyusb/test_apps/runtime_config/main/test_config.c b/device/esp_tinyusb/test_apps/runtime_config/main/test_config.c index 562c1a0a..58956d9b 100644 --- a/device/esp_tinyusb/test_apps/runtime_config/main/test_config.c +++ b/device/esp_tinyusb/test_apps/runtime_config/main/test_config.c @@ -61,6 +61,7 @@ TEST_CASE("Config: Full-speed default (Full-speed)", "[runtime_config][full_spee TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.skip_setup, "Wrong default skip_setup value"); TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.self_powered, "Wrong default self-powered flag"); TEST_ASSERT_EQUAL_MESSAGE(-1, tusb_cfg.phy.vbus_monitor_io, "Wrong default VBUS monitor IO"); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_DEBOUNCE_MS, tusb_cfg.phy.vbus_monitor_debounce_ms, "Wrong default VBUS monitor debounce time"); TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_SIZE, tusb_cfg.task.size, "Wrong default task size"); TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_PRIO, tusb_cfg.task.priority, "Wrong default task priority"); #if CONFIG_FREERTOS_UNICORE @@ -85,6 +86,7 @@ TEST_CASE("Config: Full-speed (High-speed)", "[runtime_config][full_speed]") TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.skip_setup, "Wrong default skip_setup value"); TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.self_powered, "Wrong default self-powered flag"); TEST_ASSERT_EQUAL_MESSAGE(-1, tusb_cfg.phy.vbus_monitor_io, "Wrong default VBUS monitor IO"); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_DEBOUNCE_MS, tusb_cfg.phy.vbus_monitor_debounce_ms, "Wrong default VBUS monitor debounce time"); TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_SIZE, tusb_cfg.task.size, "Wrong default task size"); TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_PRIO, tusb_cfg.task.priority, "Wrong default task priority"); TEST_ASSERT_EQUAL_MESSAGE(1, tusb_cfg.task.xCoreID, "Wrong default task affinity, should be 1 on multicore"); @@ -104,6 +106,7 @@ TEST_CASE("Config: High-speed default (High-speed)", "[runtime_config][high_spee TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.skip_setup, "Wrong default skip_setup value"); TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.self_powered, "Wrong default self-powered flag"); TEST_ASSERT_EQUAL_MESSAGE(-1, tusb_cfg.phy.vbus_monitor_io, "Wrong default VBUS monitor IO"); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_DEBOUNCE_MS, tusb_cfg.phy.vbus_monitor_debounce_ms, "Wrong default VBUS monitor debounce time"); TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_SIZE, tusb_cfg.task.size, "Wrong default task size"); TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_PRIO, tusb_cfg.task.priority, "Wrong default task priority"); TEST_ASSERT_EQUAL_MESSAGE(1, tusb_cfg.task.xCoreID, "Wrong default task affinity, should be 1 on multicore"); diff --git a/device/esp_tinyusb/test_apps/dconn_detection/CMakeLists.txt b/device/esp_tinyusb/test_apps/vbus_monitor/CMakeLists.txt similarity index 90% rename from device/esp_tinyusb/test_apps/dconn_detection/CMakeLists.txt rename to device/esp_tinyusb/test_apps/vbus_monitor/CMakeLists.txt index ff14e204..b0b2f41b 100644 --- a/device/esp_tinyusb/test_apps/dconn_detection/CMakeLists.txt +++ b/device/esp_tinyusb/test_apps/vbus_monitor/CMakeLists.txt @@ -6,4 +6,4 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. set(COMPONENTS main) -project(test_app_dconn_detection) +project(test_app_vbus_monitor) diff --git a/device/esp_tinyusb/test_apps/dconn_detection/main/CMakeLists.txt b/device/esp_tinyusb/test_apps/vbus_monitor/main/CMakeLists.txt similarity index 100% rename from device/esp_tinyusb/test_apps/dconn_detection/main/CMakeLists.txt rename to device/esp_tinyusb/test_apps/vbus_monitor/main/CMakeLists.txt diff --git a/device/esp_tinyusb/test_apps/dconn_detection/main/idf_component.yml b/device/esp_tinyusb/test_apps/vbus_monitor/main/idf_component.yml similarity index 100% rename from device/esp_tinyusb/test_apps/dconn_detection/main/idf_component.yml rename to device/esp_tinyusb/test_apps/vbus_monitor/main/idf_component.yml diff --git a/device/esp_tinyusb/test_apps/dconn_detection/main/test_app_main.c b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_app_main.c similarity index 84% rename from device/esp_tinyusb/test_apps/dconn_detection/main/test_app_main.c rename to device/esp_tinyusb/test_apps/vbus_monitor/main/test_app_main.c index b1321edd..62813305 100644 --- a/device/esp_tinyusb/test_apps/dconn_detection/main/test_app_main.c +++ b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_app_main.c @@ -1,15 +1,19 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "unity.h" #include "unity_test_runner.h" #include "unity_test_utils_memory.h" +#include "test_vbus_monitor.h" + void app_main(void) { /* @@ -53,10 +57,16 @@ void app_main(void) void setUp(void) { unity_utils_record_free_mem(); + test_device_event_queue_setup(); + test_vbus_monitor_control_setup(); } /* tearDown runs after every test */ void tearDown(void) { + // Short delay to allow task to be cleaned up + vTaskDelay(10); + test_vbus_monitor_control_teardown(); + test_device_event_queue_teardown(); unity_utils_evaluate_leaks(); } diff --git a/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.c b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.c new file mode 100644 index 00000000..029464f7 --- /dev/null +++ b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.c @@ -0,0 +1,611 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED + +#include +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_err.h" +#include "driver/gpio.h" +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_cdc_acm.h" +#include "tinyusb_default_config.h" + +// For GPIO matrix +#include "esp_rom_gpio.h" +#include "soc/gpio_sig_map.h" +// For DWC USB registers access +#include "soc/usb_dwc_struct.h" + + +#if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3) +#define USB_BVALID_PAD_IN_IDX USB_SRP_BVALID_IN_IDX +#elif (CONFIG_IDF_TARGET_ESP32P4) +// Only for USB OTG 1.1 +#define USB_BVALID_PAD_IN_IDX USB_SRP_BVALID_PAD_IN_IDX +#else +#error "This example is not supported on the selected target" +#endif // + +#define DEVICE_DETACH_TEST_ROUNDS 3 +#define DEVICE_DETACH_ROUND_DELAY_MS 1000 +#define VBUS_TRIGGER_GPIO_NUM 5 // GPIO to be used to software VBUS signal triggering +#define VBUS_MONITOR_GPIO_NUM 4 // GPIO connected to VBUS via resistor divider, used for VBUS monitoring + +#define TEST_DEVICE_EVENT_QUEUE_LEN 4 // Length of the queue for device events +#define TEST_DEVICE_EVENT_TIMEOUT_MS 5000 // Timeout for waiting device events +// Device event queue +typedef struct { + tinyusb_event_id_t id; /*!< Event ID */ +} test_device_event_t; + +static QueueHandle_t _test_device_event_queue = NULL; + +void test_device_event_queue_setup(void) +{ + _test_device_event_queue = xQueueCreate(TEST_DEVICE_EVENT_QUEUE_LEN, sizeof(test_device_event_t)); + TEST_ASSERT_NOT_NULL(_test_device_event_queue); +} + +void test_device_event_queue_teardown(void) +{ + if (_test_device_event_queue) { + vQueueDelete(_test_device_event_queue); + _test_device_event_queue = NULL; + } +} + +/** + * @brief TinyUSB callback for device event + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +static void test_dconn_event_handler(tinyusb_event_t *event, void *arg) +{ + printf("\t Device event: \n"); + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + printf("\t\t -> ATTACHED\n"); + break; + case TINYUSB_EVENT_DETACHED: + printf("\t\t <- DETACHED\n"); + break; + default: + break; + } + + test_device_event_t dev_evt = { + .id = event->id, + }; + if (_test_device_event_queue) { + xQueueSend(_test_device_event_queue, &dev_evt, portMAX_DELAY); + } +} + +/** + * @brief Wait for a specific device event to be received in the event queue + * + * @param event_id The expected event ID to wait for + */ +static void test_device_wait_event(tinyusb_event_id_t event_id) +{ + TEST_ASSERT_NOT_NULL(_test_device_event_queue); + // Wait for port callback to send an event message + test_device_event_t dev_evt; + BaseType_t ret = xQueueReceive(_test_device_event_queue, &dev_evt, pdMS_TO_TICKS(TEST_DEVICE_EVENT_TIMEOUT_MS)); + TEST_ASSERT_EQUAL_MESSAGE(pdPASS, ret, "Device event not received on time"); + // Check the contents of that event message + TEST_ASSERT_EQUAL_MESSAGE(event_id, dev_evt.id, "Unexpected device event type received"); +} + +void test_vbus_monitor_control_setup(void) +{ + printf("VBUS monitor control setup:\n"); +#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + // Triggering is done by signal multiplexing through connection VBUS monitor GPIO and BVALID signal + printf("\t - might be triggered by signal multiplexing.\n"); +#elif CONFIG_IDF_TARGET_ESP32P4 + // On ESP32P4 OTG signals from USB-DWC are not wired to GPIO matrix, we need to toggle the USB OTG GOTGCTL value in application + // Note: this approach fits S2/S3 as well, but we use signal mux there as more convenient way + printf("\t - might be triggered by manipulating the GOTGCTL register.\n"); +#endif // + // Triggering is done by external GPIO, connected to the VBUS monitor GPIO + printf("\t - might be triggered by GPIO%d. \n", VBUS_TRIGGER_GPIO_NUM); + printf("\t\t To use this, connect GPIO%d to VBUS monitor GPIO%d. \n", VBUS_TRIGGER_GPIO_NUM, VBUS_MONITOR_GPIO_NUM); + // Configure the GPIO to output + const gpio_config_t vbus_pin = { + .pin_bit_mask = BIT64(VBUS_TRIGGER_GPIO_NUM), + .mode = GPIO_MODE_OUTPUT, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, gpio_config(&vbus_pin), "Failed to configure the GPIO for VBUS triggering"); + // Set initial level to HIGH, as we assume that the device is connected at the start + gpio_set_level(VBUS_TRIGGER_GPIO_NUM, 1); +} + +void test_vbus_monitor_control_teardown(void) +{ + // Disable the GPIO + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, gpio_reset_pin(VBUS_TRIGGER_GPIO_NUM), "Failed to reset the GPIO for VBUS triggering"); +} + +/** + * @brief Control routines for VBUS monitoring + * + * Connect the GPIO signal 1 to Bvalid input with GPIO matrix + */ +static void test_vbus_monitor_control_signal_connect(void) +{ + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_BVALID_PAD_IN_IDX, false); +} + +/** + * @brief Control routines for VBUS monitoring + * + * Connect the GPIO signal 0 to Bvalid input with GPIO matrix + */ +static void test_vbus_monitor_control_signal_disconnect(void) +{ + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_BVALID_PAD_IN_IDX, false); +} + +#if (SOC_USB_OTG_PERIPH_NUM > 1) +/** + * @brief Control routines for VBUS monitoring + * + * Connect by manipulating the GOTGCTL register + */ +static void test_vbus_monitor_control_gotgctl_connect(usb_dwc_dev_t *dwc_otg) +{ + // Hardware should allow to override the BVALIDOVVAL register value + TEST_ASSERT_EQUAL_MESSAGE(1, dwc_otg->gotgctl_reg.bvalidoven, "Bvalid overriding is not enabled"); + // Set Bvalid signal to 1 + dwc_otg->gotgctl_reg.bvalidovval = 1; + // For USB OTG2.0 we need to reset the soft-disconnect bit + dwc_otg->dctl_reg.sftdiscon = 0; +} + +/** + * @brief Control routines for VBUS monitoring + * + * Disconnect by manipulating the GOTGCTL register + */ +static void test_vbus_monitor_control_gotgctl_disconnect(usb_dwc_dev_t *dwc_otg) +{ + // Hardware should allow to override the BVALIDOVVAL register value + TEST_ASSERT_EQUAL_MESSAGE(1, dwc_otg->gotgctl_reg.bvalidoven, "Bvalid overriding is not enabled"); + // Set Bvalid signal to 1 + dwc_otg->gotgctl_reg.bvalidovval = 0; + // For USB OTG2.0 we need to set the soft-disconnect bit + dwc_otg->dctl_reg.sftdiscon = 1; +} +#endif // (SOC_USB_OTG_SUPPORTED > 1) + +/** + * @brief Control routines for VBUS monitoring + * + * Set the GPIO, which represent VBUS triggering to HIGH level to emulate the VBUS presence + */ +static void test_vbus_monitor_control_trigger_connect(void) +{ + // Set the GPIO to HIGH to emulate the VBUS presence + gpio_set_level(VBUS_TRIGGER_GPIO_NUM, 1); +} + +/** + * @brief Control routines for VBUS monitoring + * + * Set the GPIO, which represent VBUS triggering to LOW level to emulate the VBUS absence + */ +static void test_vbus_monitor_control_trigger_disconnect(void) +{ + // Set the GPIO to LOW to emulate the VBUS absence + gpio_set_level(VBUS_TRIGGER_GPIO_NUM, 0); +} + +/** + * @brief Test routine to emulate device attach/detach by manipulating the Bvalid signal directly + * + * @return + * Number of times the device was mounted + */ +static uint8_t test_vbus_emulated_via_bvalid_signal(void) +{ + unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; + uint8_t mount_cnt = 0; + + while (rounds--) { + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Connect Bvalid signal to LOW level (0) to emulate the detachment + test_vbus_monitor_control_signal_disconnect(); + // Wait for the TINYUSB_EVENT_DETACHED event + test_device_wait_event(TINYUSB_EVENT_DETACHED); + // Allow some time for the device to be removed from the USB Host driver + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Connect Bvalid signal to HIGH level (1) to emulate the attachment + test_vbus_monitor_control_signal_connect(); + // Wait for the TINYUSB_EVENT_ATTACHED event + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + // Increase the mount_cnt value + mount_cnt++; + } + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + return mount_cnt; +} + +#if (SOC_USB_OTG_PERIPH_NUM > 1) +/** + * @brief Test routine to emulate device attach/detach by toggling the GOTGCTL register Bvalid value + * + * @return + * Number of times the device was mounted + */ +static uint8_t test_vbus_emulated_via_gotgctl_bvalid(usb_dwc_dev_t *dwc_otg) +{ + unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; + uint8_t mount_cnt = 0; + + while (rounds--) { + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Emulate the detachment by setting the VBUS to Lo state + test_vbus_monitor_control_gotgctl_disconnect(dwc_otg); + // Wait for the TINYUSB_EVENT_DETACHED event + test_device_wait_event(TINYUSB_EVENT_DETACHED); + // Allow some time for the device to be removed from the USB Host driver + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Emulate the attachment by setting the VBUS to Hi state + test_vbus_monitor_control_gotgctl_connect(dwc_otg); + // Wait for the TINYUSB_EVENT_ATTACHED event + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + // Increase the dev_mounted value + mount_cnt++; + } + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + return mount_cnt; +} +#endif // (SOC_USB_OTG_SUPPORTED > 1) + +/** + * @brief Test routine to emulate device attach/detach by toggling the VBUS monitoring GPIO + * + * Note: + * To run this test, connect VBUS monitor GPIO (VBUS_MONITOR_GPIO_NUM) to GPIO (VBUS_TRIGGER_GPIO_NUM) + * + * @return + * Number of times the device was mounted + */ +static uint8_t test_vbus_controlled_by_gpio(void) +{ + unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; + uint8_t mount_cnt = 0; + + while (rounds--) { + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Emulate the detachment by setting the VBUS to Lo state + test_vbus_monitor_control_trigger_disconnect(); + // Wait for the TINYUSB_EVENT_DETACHED event + test_device_wait_event(TINYUSB_EVENT_DETACHED); + // Allow some time for the device to be removed from the USB Host driver + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Emulate the attachment by setting the VBUS to Hi state + test_vbus_monitor_control_trigger_connect(); + // Wait for the TINYUSB_EVENT_ATTACHED event + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + // Increase the dev_mounted value + mount_cnt++; + } + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + return mount_cnt; +} + +/** + * @brief Test routine to emulate device attach/detach by manually connecting/disconnecting the device + * + * Note: this test requires external hardware (resistor divider or similar) to connect VBUS to the VBUS monitor GPIO + * and an operator, who will manually connect/disconnect the device from the USB Host port according the prompts + * + * @return + * Number of times the device was mounted + */ +static uint8_t test_vbus_manual_attach_detach(void) +{ + unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; + uint8_t mount_cnt = 0; + + while (rounds--) { + printf("Detach device from the Host port ...\n"); + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Wait for the TINYUSB_EVENT_DETACHED event + test_device_wait_event(TINYUSB_EVENT_DETACHED); + printf("OK\n"); + // + printf("Attach device to the Host port ...\n"); + // Allow some time for the device to be removed from the USB Host driver + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Wait for the TINYUSB_EVENT_ATTACHED event + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + printf("OK\n"); + // Increase the dev_mounted value + mount_cnt++; + } + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + return mount_cnt; +} + +#if (SOC_USB_OTG_PERIPH_NUM == 1 && (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3)) + +/** + * @brief TinyUSB Attach/Detach events test, when Bvalid value is manipulated directly + */ +TEST_CASE("Emulated VBUS, verify attach/detach events callback (via Bvalid signal)", "[ci][dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // In this test we do not use VBUS monitoring GPIO, because we manipulate Bvalid value directly + tusb_cfg.phy.self_powered = false; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_emulated_via_bvalid_signal(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to GPIO + */ +TEST_CASE("Controlled VBUS, verify attach/detach events callback", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_controlled_by_gpio(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to real VBUS + */ +TEST_CASE("Real VBUS, verify attach/detach events callback (requires manual handling)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // In this test we use VBUS monitoring GPIO, we need to enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_manual_attach_detach(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} +#endif // (SOC_USB_OTG_PERIPH_NUM == 1 && (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3)) + +#if (SOC_USB_OTG_PERIPH_NUM > 1 && CONFIG_IDF_TARGET_ESP32P4) +/** + * @brief TinyUSB Attach/Detach events test, when Bvalid value is manipulated directly + */ +TEST_CASE("Emulated VBUS USB OTG 1.1, verify attach/detach events callback (via Bvalid signal)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 1.1 + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; + // In this test we do not use VBUS monitoring GPIO, because we manipulate Bvalid value directly + tusb_cfg.phy.self_powered = false; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_emulated_via_bvalid_signal(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when Bvalid value is manipulated directly + */ +TEST_CASE("Emulated VBUS USB OTG 1.1, verify attach/detach events callback (via GOTGCTL register)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 1.1 + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; + // In this test we do not use VBUS monitoring GPIO, because we manipulate Bvalid value directly + tusb_cfg.phy.self_powered = false; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + + // When we do not set self_powered mode, the Bvalid override should be not enabled + TEST_ASSERT_EQUAL_MESSAGE(0, USB_DWC_FS.gotgctl_reg.bvalidoven, "Bvalid override value is already enabled"); + // Set Bvalid signal to 1 initially + USB_DWC_FS.gotgctl_reg.bvalidovval = 1; + // Wait 5 PHY clocks + esp_rom_delay_us(1); + // Enable to override the signal from PHY + USB_DWC_FS.gotgctl_reg.bvalidoven = 1; + + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_emulated_via_gotgctl_bvalid(&USB_DWC_FS); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when Bvalid value is manipulated via GOTGCTL register + */ +TEST_CASE("Emulated VBUS USB OTG 2.0, verify attach/detach events callback (via GOTGCTL register)", "[ci][dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 2.0 + tusb_cfg.port = TINYUSB_PORT_HIGH_SPEED_0; + // In this test we do not use VBUS monitoring GPIO, because we manipulate Bvalid value directly + tusb_cfg.phy.self_powered = false; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + + // When we do not set self_powered mode, the Bvalid override should be not enabled + TEST_ASSERT_EQUAL_MESSAGE(0, USB_DWC_HS.gotgctl_reg.bvalidoven, "Bvalid override value is already enabled"); + // Set Bvalid signal to 1 initially + USB_DWC_HS.gotgctl_reg.bvalidovval = 1; + // Wait 5 PHY clocks + esp_rom_delay_us(1); + // Enable to override the signal from PHY + USB_DWC_HS.gotgctl_reg.bvalidoven = 1; + + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_emulated_via_gotgctl_bvalid(&USB_DWC_HS); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to GPIO + */ +TEST_CASE("Controlled VBUS USB OTG 1.1, verify attach/detach events callback", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 1.1 + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_controlled_by_gpio(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to GPIO + */ +TEST_CASE("Controlled VBUS USB OTG 2.0, verify attach/detach events callback", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 2.0 + tusb_cfg.port = TINYUSB_PORT_HIGH_SPEED_0; + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_controlled_by_gpio(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to real VBUS + */ +TEST_CASE("Real VBUS USB OTG 1.1, verify attach/detach events callback (requires manual handling)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 1.1 peripheral + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_manual_attach_detach(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to real VBUS + */ +TEST_CASE("Real VBUS USB OTG 2.0, verify attach/detach events callback (requires manual handling)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 2.0 + tusb_cfg.port = TINYUSB_PORT_HIGH_SPEED_0; + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_manual_attach_detach(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +#endif // (SOC_USB_OTG_PERIPH_NUM > 1 && CONFIG_IDF_TARGET_ESP32P4) + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.h b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.h new file mode 100644 index 00000000..9b5101de --- /dev/null +++ b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.h @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief Setup the device event queue + */ +void test_device_event_queue_setup(void); + +/** + * @brief Teardown the device event queue + */ +void test_device_event_queue_teardown(void); + +/** + * @brief Setup the VBUS monitor control + */ +void test_vbus_monitor_control_setup(void); + +/** + * @brief Teardown the VBUS monitor control + */ +void test_vbus_monitor_control_teardown(void); diff --git a/device/esp_tinyusb/test_apps/vbus_monitor/pytest_vbus_monitor.py b/device/esp_tinyusb/test_apps/vbus_monitor/pytest_vbus_monitor.py new file mode 100644 index 00000000..5dd743ce --- /dev/null +++ b/device/esp_tinyusb/test_apps/vbus_monitor/pytest_vbus_monitor.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded_idf.dut import IdfDut + +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32p4 +@pytest.mark.usb_device +def test_usb_device_vbus_monitor(dut: IdfDut) -> None: + dut.run_all_single_board_cases(group='ci') diff --git a/device/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults b/device/esp_tinyusb/test_apps/vbus_monitor/sdkconfig.defaults similarity index 77% rename from device/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults rename to device/esp_tinyusb/test_apps/vbus_monitor/sdkconfig.defaults index 0a6271fd..21bf3a74 100644 --- a/device/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults +++ b/device/esp_tinyusb/test_apps/vbus_monitor/sdkconfig.defaults @@ -1,8 +1,6 @@ # Configure TinyUSB, it will be used to mock USB devices -CONFIG_TINYUSB_MSC_ENABLED=n -CONFIG_TINYUSB_CDC_ENABLED=n -CONFIG_TINYUSB_CDC_COUNT=0 -CONFIG_TINYUSB_HID_COUNT=0 +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_COUNT=1 # Disable watchdogs, they'd get triggered during unity interactive menu # CONFIG_ESP_TASK_WDT_INIT is not set diff --git a/device/esp_tinyusb/tinyusb.c b/device/esp_tinyusb/tinyusb.c index a2be1a63..d8edf054 100644 --- a/device/esp_tinyusb/tinyusb.c +++ b/device/esp_tinyusb/tinyusb.c @@ -100,6 +100,14 @@ static esp_err_t tinyusb_check_config(const tinyusb_config_t *config) ESP_RETURN_ON_FALSE(config->port != TINYUSB_PORT_0, ESP_ERR_INVALID_ARG, TAG, "USB PHY support for OTG1.1 has not been implemented, please update your esp-idf"); #endif // ESP-IDF supports OTG1.1 peripheral #endif // CONFIG_IDF_TARGET_ESP32P4 + + if (config->phy.self_powered) { + // Device is self powered + ESP_RETURN_ON_FALSE(config->phy.vbus_monitor_io >= 0 && config->phy.vbus_monitor_io < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, TAG, "VBUS monitor GPIO must be a valid GPIO number when self_powered is true"); +#if (CONFIG_IDF_TARGET_ESP32P4) + ESP_RETURN_ON_FALSE(config->phy.vbus_monitor_debounce_ms > 0, ESP_ERR_INVALID_ARG, TAG, "VBUS monitor debounce time must be greater than 0 when self_powered is true"); +#endif // CONFIG_IDF_TARGET_ESP32P4 + } return ESP_OK; } @@ -110,6 +118,10 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config) esp_err_t ret; usb_phy_handle_t phy_hdl = NULL; + tinyusb_vbus_monitor_config_t vbus_cfg = { + .enabled = false, + }; + if (!config->phy.skip_setup) { // Configure USB PHY usb_phy_config_t phy_conf = { @@ -128,14 +140,25 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config) #endif // (SOC_USB_OTG_PERIPH_NUM > 1) // OTG IOs config - const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io); if (config->phy.self_powered) { - phy_conf.otg_io_conf = &otg_io_conf; + if (config->port == TINYUSB_PORT_FULL_SPEED_0) { + // For USB OTG 1.1, we map VBUS monitor io signal to the BVALID while enable PHY + const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io); + phy_conf.otg_io_conf = &otg_io_conf; + } +#if (SOC_USB_OTG_PERIPH_NUM > 1) + else if (config->port == TINYUSB_PORT_HIGH_SPEED_0) { + // For USB OTG 2.0, we use VBUS monitoring GPIO to control BVALID value over GOTGCTL register + vbus_cfg.gpio_num = config->phy.vbus_monitor_io; + vbus_cfg.debounce_delay_ms = config->phy.vbus_monitor_debounce_ms; + vbus_cfg.enabled = true; + } +#endif // SOC_USB_OTG_PERIPH_NUM > 1 } ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed"); } // Init TinyUSB stack in task - ESP_GOTO_ON_ERROR(tinyusb_task_start(config->port, &config->task, &config->descriptor), del_phy, TAG, "Init TinyUSB task failed"); + ESP_GOTO_ON_ERROR(tinyusb_task_start(config->port, &config->task, &config->descriptor, &vbus_cfg), del_phy, TAG, "Init TinyUSB task failed"); s_ctx.port = config->port; // Save the port number s_ctx.phy_hdl = phy_hdl; // Save the PHY handle for uninstallation @@ -155,9 +178,11 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config) esp_err_t tinyusb_driver_uninstall(void) { ESP_RETURN_ON_ERROR(tinyusb_task_stop(), TAG, "Deinit TinyUSB task failed"); + if (s_ctx.phy_hdl) { ESP_RETURN_ON_ERROR(usb_del_phy(s_ctx.phy_hdl), TAG, "Unable to delete PHY"); s_ctx.phy_hdl = NULL; } + return ESP_OK; } diff --git a/device/esp_tinyusb/tinyusb_task.c b/device/esp_tinyusb/tinyusb_task.c index 79d4acaa..51a2a0fa 100644 --- a/device/esp_tinyusb/tinyusb_task.c +++ b/device/esp_tinyusb/tinyusb_task.c @@ -11,8 +11,11 @@ #include "esp_log.h" #include "esp_check.h" #include "tinyusb.h" +#include "tinyusb_task.h" +#include "tinyusb_vbus_monitor.h" #include "sdkconfig.h" #include "descriptors_control.h" +#include "soc/usb_dwc_struct.h" #if TUSB_VERSION_NUMBER < 1900 // < 0.19.0 #define tusb_deinit(x) tusb_teardown(x) // For compatibility with tinyusb component versions from 0.17.0~2 to 0.18.0~5 @@ -45,7 +48,10 @@ typedef struct { const tinyusb_desc_config_t *desc_cfg; /*!< USB Device descriptors configuration pointer */ // Task related TaskHandle_t handle; /*!< Task handle */ - volatile TaskHandle_t awaiting_handle; /*!< Task handle, waiting to be notified after successful start of TinyUSB stack */ + volatile TaskHandle_t awaiting_handle; /*!< Task handle, waiting to be notified after successful start of TinyUSB stack */ +#if (CONFIG_IDF_TARGET_ESP32P4) + tinyusb_vbus_monitor_config_t vbus_monitor_cfg; /*!< VBUS monitoring configuration */ +#endif // CONFIG_IDF_TARGET_ESP32P4 } tinyusb_task_ctx_t; static bool _task_is_running = false; // Locking flag for the task, access only from the critical section @@ -77,6 +83,15 @@ static void tinyusb_device_task(void *arg) goto desc_free; } +#if (CONFIG_IDF_TARGET_ESP32P4) + if (task_ctx->vbus_monitor_cfg.gpio_num != GPIO_NUM_NC) { + if (tinyusb_vbus_monitor_init(&task_ctx->vbus_monitor_cfg) != ESP_OK) { + ESP_LOGE(TAG, "Init VBUS monitoring failed"); + goto desc_free; + } + } +#endif // CONFIG_IDF_TARGET_ESP32P4 + TINYUSB_TASK_ENTER_CRITICAL(); task_ctx->handle = xTaskGetCurrentTaskHandle(); // Save task handle p_tusb_task_ctx = task_ctx; // Save global task context pointer @@ -111,7 +126,10 @@ esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *config) return ESP_OK; } -esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *config, const tinyusb_desc_config_t *desc_cfg) +esp_err_t tinyusb_task_start(tinyusb_port_t port, + const tinyusb_task_config_t *config, + const tinyusb_desc_config_t *desc_cfg, + const tinyusb_vbus_monitor_config_t *vbus_monitor_cfg) { ESP_RETURN_ON_ERROR(tinyusb_descriptors_check(port, desc_cfg), TAG, "TinyUSB descriptors check failed"); @@ -133,6 +151,15 @@ esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *c task_ctx->rhport_init.role = TUSB_ROLE_DEVICE; // Role selection: esp_tinyusb is always a device task_ctx->rhport_init.speed = (port == TINYUSB_PORT_FULL_SPEED_0) ? TUSB_SPEED_FULL : TUSB_SPEED_HIGH; // Speed selection task_ctx->desc_cfg = desc_cfg; +#if (CONFIG_IDF_TARGET_ESP32P4) + // VBUS monitor config + if (vbus_monitor_cfg->enabled) { + task_ctx->vbus_monitor_cfg.gpio_num = vbus_monitor_cfg->gpio_num; + task_ctx->vbus_monitor_cfg.debounce_delay_ms = vbus_monitor_cfg->debounce_delay_ms; + } else { + task_ctx->vbus_monitor_cfg.gpio_num = GPIO_NUM_NC; + } +#endif // CONFIG_IDF_TARGET_ESP32P4 TaskHandle_t task_hdl = NULL; ESP_LOGD(TAG, "Creating TinyUSB main task on CPU%d", config->xCoreID); @@ -177,8 +204,23 @@ esp_err_t tinyusb_task_stop(void) vTaskDelete(task_ctx->handle); task_ctx->handle = NULL; } + + /* TODO: Free descriptors and disable the VBUS monitor should be in the task itself + * but currently we don't have a way to signal the task to do it and exit. + * So we do it here for now. + * Refer to https://github.com/espressif/esp-usb/pull/272 + */ + // Free descriptors tinyusb_descriptors_free(); + +#if (CONFIG_IDF_TARGET_ESP32P4) + if (task_ctx->vbus_monitor_cfg.gpio_num != GPIO_NUM_NC) { + // Deinit VBUS monitoring if it was enabled + tinyusb_vbus_monitor_deinit(); + } +#endif // CONFIG_IDF_TARGET_ESP32P4 + // Stop TinyUSB stack ESP_RETURN_ON_FALSE(tusb_deinit(task_ctx->rhport), ESP_ERR_NOT_FINISHED, TAG, "Unable to teardown TinyUSB stack"); // Cleanup diff --git a/device/esp_tinyusb/tinyusb_vbus_monitor.c b/device/esp_tinyusb/tinyusb_vbus_monitor.c new file mode 100644 index 00000000..3d3ec9fd --- /dev/null +++ b/device/esp_tinyusb/tinyusb_vbus_monitor.c @@ -0,0 +1,268 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/timers.h" +#include "driver/gpio.h" +#include "tinyusb.h" +#include "tinyusb_vbus_monitor.h" +#include "sdkconfig.h" + +const static char *TAG = "VBUS mon"; + +#if (CONFIG_IDF_TARGET_ESP32P4) +#include "soc/usb_dwc_struct.h" +// On ESP32-P4 USB OTG 2.0 signals are not wired to GPIO matrix +// So we need to override the Bvalid signal from PHY +#define USB_DWC_REG USB_DWC_HS +#endif // CONFIG_IDF_TARGET_ESP32P4 + +#define GLITCH_FILTER 0 /* TODO: make configurable ? */ +typedef struct { + gpio_num_t vbus_io_num; + bool vbus_prev_state; + bool vbus_state_changed; +} vbus_context_t; + +static vbus_context_t _vbus_ctx; +static TimerHandle_t _vbus_debounce_timer = NULL; + +// +// Additional low-level USB DWC functions, which are not present in the IDF USB DWC HAL +// + +// --------------- GOTGCTL register ------------------ + +static void usb_dwc_ll_gotgctl_set_bvalid_override_value(usb_dwc_dev_t *hw, uint8_t value) +{ + hw->gotgctl_reg.bvalidovval = value; +} + +static void usb_dwc_ll_gotgctl_enable_bvalid_override(usb_dwc_dev_t *hw, bool enable) +{ + hw->gotgctl_reg.bvalidoven = enable ? 1 : 0; +} + +// ------------------ DCTL register -------------------- + +static void usb_dwc_ll_dctl_set_soft_disconnect(usb_dwc_dev_t *hw, bool enable) +{ + hw->dctl_reg.sftdiscon = enable ? 1 : 0; +} + +// -------------- VBUS Internal Logic ------------------ + +/** + * @brief Handle VBUS appeared event + */ +static void vbus_appeared(void) +{ + ESP_LOGD(TAG, "Appeared"); + usb_dwc_ll_gotgctl_set_bvalid_override_value(&USB_DWC_REG, 1); + usb_dwc_ll_dctl_set_soft_disconnect(&USB_DWC_REG, false); +} + +/** + * @brief Handle VBUS disappeared event + */ +static void vbus_disappeared(void) +{ + ESP_LOGD(TAG, "Disappeared"); + usb_dwc_ll_gotgctl_set_bvalid_override_value(&USB_DWC_REG, 0); + usb_dwc_ll_dctl_set_soft_disconnect(&USB_DWC_REG, true); +} + +/** + * @brief GPIO interrupt handler for VBUS monitoring io + * + * @param arg GPIO number + */ +static void vbus_io_cb(void *arg) +{ + int vbus_io_num = (int) arg; + // disable interrupts for a while to debounce + gpio_intr_disable(vbus_io_num); + + bool vbus_curr_state = gpio_get_level(vbus_io_num); + + if (_vbus_ctx.vbus_prev_state != vbus_curr_state) { + _vbus_ctx.vbus_prev_state = vbus_curr_state; + _vbus_ctx.vbus_state_changed = true; + // VBUS pin state has changed, start the debounce timer + BaseType_t yield = pdFALSE; + if (xTimerStartFromISR(_vbus_debounce_timer, &yield) != pdPASS) { + ESP_EARLY_LOGE(TAG, "Failed to start VBUS debounce timer"); + } + if (yield == pdTRUE) { + portYIELD_FROM_ISR(); + } + } else { + /* TODO: Verify happening with and without glitch filter */ + // VBUS gpio glitch, ignore and re-enable interrupt + _vbus_ctx.vbus_state_changed = false; + gpio_intr_enable(vbus_io_num); + } +} + +/** + * @brief VBUS debounce timer callback + * + * @param xTimer Timer handle + */ +static void vbus_debounce_timer_cb(TimerHandle_t xTimer) +{ + int vbus_io_num = _vbus_ctx.vbus_io_num; + bool vbus_prev_state = _vbus_ctx.vbus_prev_state; + bool vbus_curr_state = gpio_get_level(vbus_io_num); + + if (vbus_curr_state && vbus_prev_state) { + vbus_appeared(); + } else if (!vbus_curr_state && !vbus_prev_state) { + vbus_disappeared(); + } else { + // State changed again during debounce period, ignore + } + // Update the state + _vbus_ctx.vbus_prev_state = vbus_curr_state; + // Re-enable GPIO interrupt + gpio_intr_enable(vbus_io_num); +} + +// -------------- Public API ------------------ + +esp_err_t tinyusb_vbus_monitor_init(tinyusb_vbus_monitor_config_t *config) +{ + esp_err_t ret; + + // There could be only one instance of VBUS monitoring + if (_vbus_debounce_timer) { + ESP_LOGE(TAG, "Already initialized"); + return ESP_ERR_INVALID_STATE; + } + + _vbus_ctx.vbus_io_num = config->gpio_num; + _vbus_ctx.vbus_prev_state = false; + _vbus_ctx.vbus_state_changed = false; + + // VBUS Debounce timer + _vbus_debounce_timer = xTimerCreate("vbus_debounce_timer", + pdMS_TO_TICKS(config->debounce_delay_ms), + pdFALSE, + NULL, + vbus_debounce_timer_cb); + + if (_vbus_debounce_timer == NULL) { + ESP_LOGE(TAG, "Create VBUS debounce timer failed"); + return ESP_ERR_NO_MEM; + } + + // Init gpio IRQ for VBUS monitoring + const gpio_config_t vbus_io_cfg = { + .pin_bit_mask = BIT64(_vbus_ctx.vbus_io_num), + .mode = GPIO_MODE_INPUT, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .intr_type = GPIO_INTR_ANYEDGE, + }; + + ret = gpio_config(&vbus_io_cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Config VBUS GPIO failed"); + goto gpio_fail; + } + +#if (GLITCH_FILTER) + ESP_LOGW(TAG, "Apply glitch filter to the VBUS GPIO"); + gpio_glitch_filter_handle_t filter; + gpio_flex_glitch_filter_config_t filter_cfg = { + .clk_src = GLITCH_FILTER_CLK_SRC_DEFAULT, + .gpio_num = _vbus_ctx.vbus_io_num, + .window_thres_ns = 500 /* DEBOUNCE_DELAY_MS * 1000 * 1000 */, + .window_width_ns = 500 /* DEBOUNCE_DELAY_MS * 1000 * 1000 */, + }; + ret = (gpio_new_flex_glitch_filter(&filter_cfg, &filter)); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Create glitch filter failed"); + // goto filter_fail; + } + + ESP_LOGW(TAG, "Enable the glitch filter"); + ret = gpio_glitch_filter_enable(filter); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Enable glitch filter failed"); + // goto enable_filter_fail; + } +#endif // GLITCH_FILTER + + ret = gpio_install_isr_service(ESP_INTR_FLAG_LOWMED); + // Service could be already installed, it is not an error + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "Install GPIO ISR service failed"); + goto isr_fail; + } + + ret = gpio_isr_handler_add(_vbus_ctx.vbus_io_num, vbus_io_cb, (void *) _vbus_ctx.vbus_io_num); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Add GPIO ISR handler failed"); + goto add_isr_hdl_fail; + } + // Disable GPIO interrupt + gpio_intr_disable(_vbus_ctx.vbus_io_num); + // Set initial Bvalid override value and enable override + usb_dwc_ll_gotgctl_set_bvalid_override_value(&USB_DWC_REG, 0); + // Wait 1 microsecond (sufficient for >5 PHY clocks) + esp_rom_delay_us(1); + // Enable to override the signal from PHY + usb_dwc_ll_gotgctl_enable_bvalid_override(&USB_DWC_REG, true); + + // Device could be already connected, check the status and start the timer if needed + if (gpio_get_level(_vbus_ctx.vbus_io_num)) { + _vbus_ctx.vbus_prev_state = true; + // Start debounce timer + if (xTimerStart(_vbus_debounce_timer, 0) != pdPASS) { + ESP_LOGE(TAG, "Failed to start VBUS debounce timer"); + } + } else { + // Enable GPIO interrupt + gpio_intr_enable(_vbus_ctx.vbus_io_num); + } + + ESP_LOGD(TAG, "Configured via GPIO%d, debounce delay: %"PRIu32" ms", _vbus_ctx.vbus_io_num, config->debounce_delay_ms); + return ESP_OK; + +add_isr_hdl_fail: + gpio_uninstall_isr_service(); +isr_fail: + gpio_reset_pin(_vbus_ctx.vbus_io_num); +gpio_fail: + if (_vbus_debounce_timer) { + xTimerDelete(_vbus_debounce_timer, 0); + _vbus_debounce_timer = NULL; + } + return ret; +} + +void tinyusb_vbus_monitor_deinit(void) +{ + // Deinit gpio IRQ for VBUS monitoring + if (xTimerIsTimerActive(_vbus_debounce_timer) == pdTRUE) { + xTimerStop(_vbus_debounce_timer, 0); + } + xTimerDelete(_vbus_debounce_timer, 0); + _vbus_debounce_timer = NULL; + + // Disable to override the signal from PHY + usb_dwc_ll_gotgctl_enable_bvalid_override(&USB_DWC_REG, false); + + // Deinit gpio IRQ for VBUS monitoring + ESP_ERROR_CHECK(gpio_isr_handler_remove(_vbus_ctx.vbus_io_num)); + gpio_intr_disable(_vbus_ctx.vbus_io_num); + gpio_uninstall_isr_service(); + + ESP_LOGD(TAG, "Deinit"); +}