summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHa Thach <[email protected]>2026-03-05 21:47:41 +0700
committerGitHub <[email protected]>2026-03-05 21:47:41 +0700
commit97476872aa841fcd9cd622e4082d6c57b183c67f (patch)
tree232aca150cd4c890457b363bfcbfba9ff9a656cd
parentae3864d21f2b206ae41d1d424b96fea0ac7e0dc6 (diff)
parent75a5bedf357342762688a3fecc90739f58bbff0e (diff)
Merge pull request #3378 from hathach/dwc2_deinit
Add DWC2 deinit support
-rw-r--r--examples/dual/CMakeLists.txt1
-rw-r--r--examples/dual/dynamic_switch/CMakeLists.txt30
-rw-r--r--examples/dual/dynamic_switch/CMakePresets.json6
-rw-r--r--examples/dual/dynamic_switch/Makefile16
-rw-r--r--examples/dual/dynamic_switch/README.md60
-rw-r--r--examples/dual/dynamic_switch/only.txt10
-rw-r--r--examples/dual/dynamic_switch/src/CMakeLists.txt4
-rw-r--r--examples/dual/dynamic_switch/src/main.c496
-rw-r--r--examples/dual/dynamic_switch/src/tusb_config.h158
-rw-r--r--examples/dual/dynamic_switch/src/usb_descriptors.c199
-rw-r--r--hw/bsp/stm32f2/family.c2
-rw-r--r--hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h2
-rw-r--r--src/common/tusb_fifo.h10
-rw-r--r--src/device/usbd.c2
-rw-r--r--src/host/usbh.c2
-rw-r--r--src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c8
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c6
-rw-r--r--src/portable/synopsys/dwc2/dwc2_at32.h6
-rw-r--r--src/portable/synopsys/dwc2/dwc2_bcm.h7
-rw-r--r--src/portable/synopsys/dwc2/dwc2_common.c30
-rw-r--r--src/portable/synopsys/dwc2/dwc2_common.h2
-rw-r--r--src/portable/synopsys/dwc2/dwc2_efm32.h8
-rw-r--r--src/portable/synopsys/dwc2/dwc2_esp32.h7
-rw-r--r--src/portable/synopsys/dwc2/dwc2_gd32.h7
-rw-r--r--src/portable/synopsys/dwc2/dwc2_nrf.h6
-rw-r--r--src/portable/synopsys/dwc2/dwc2_stm32.h16
-rw-r--r--src/portable/synopsys/dwc2/dwc2_xmc.h7
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c46
-rw-r--r--src/tusb.c6
29 files changed, 1128 insertions, 32 deletions
diff --git a/examples/dual/CMakeLists.txt b/examples/dual/CMakeLists.txt
index 4978f1fab..8727ea938 100644
--- a/examples/dual/CMakeLists.txt
+++ b/examples/dual/CMakeLists.txt
@@ -12,6 +12,7 @@ else ()
set(EXAMPLE_LIST
host_hid_to_device_cdc
host_info_to_device_cdc
+ dynamic_switch
)
foreach (example ${EXAMPLE_LIST})
diff --git a/examples/dual/dynamic_switch/CMakeLists.txt b/examples/dual/dynamic_switch/CMakeLists.txt
new file mode 100644
index 000000000..7cad30727
--- /dev/null
+++ b/examples/dual/dynamic_switch/CMakeLists.txt
@@ -0,0 +1,30 @@
+cmake_minimum_required(VERSION 3.20)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
+
+project(dynamic_switch C CXX ASM)
+
+# Checks this example is valid for the family and initializes the project
+family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
+
+# Espressif has its own cmake build system
+if(FAMILY STREQUAL "espressif")
+ return()
+endif()
+
+add_executable(${PROJECT_NAME})
+
+# Example source
+target_sources(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
+ )
+
+# Example include
+target_include_directories(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
+ )
+
+# Configure compilation flags and libraries for the example without RTOS.
+# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
+family_configure_dual_usb_example(${PROJECT_NAME} noos)
diff --git a/examples/dual/dynamic_switch/CMakePresets.json b/examples/dual/dynamic_switch/CMakePresets.json
new file mode 100644
index 000000000..5cd8971e9
--- /dev/null
+++ b/examples/dual/dynamic_switch/CMakePresets.json
@@ -0,0 +1,6 @@
+{
+ "version": 6,
+ "include": [
+ "../../../hw/bsp/BoardPresets.json"
+ ]
+}
diff --git a/examples/dual/dynamic_switch/Makefile b/examples/dual/dynamic_switch/Makefile
new file mode 100644
index 000000000..44ab54ad7
--- /dev/null
+++ b/examples/dual/dynamic_switch/Makefile
@@ -0,0 +1,16 @@
+include ../../../hw/bsp/family_support.mk
+
+INC += \
+ src \
+
+# Example source
+EXAMPLE_SOURCE += $(wildcard src/*.c)
+SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
+
+# Include device and host stack
+SRC_C += \
+ src/class/cdc/cdc_device.c \
+ src/host/hub.c \
+ src/host/usbh.c
+
+include ../../../hw/bsp/family_rules.mk
diff --git a/examples/dual/dynamic_switch/README.md b/examples/dual/dynamic_switch/README.md
new file mode 100644
index 000000000..034787f18
--- /dev/null
+++ b/examples/dual/dynamic_switch/README.md
@@ -0,0 +1,60 @@
+# Dynamic Switch Example
+
+This example demonstrates TinyUSB's dual-role capability by allowing runtime switching between USB device and host modes.
+
+## Features
+
+- **Button-triggered mode switching**: Press the board button to switch between device and host modes
+- **Device Mode**: Acts as a USB CDC (Virtual Serial Port) that echoes all received data
+- **Host Mode**: Enumerates connected USB devices and prints device information
+- **Dynamic switching**: Deinitializes the current stack and reinitializes in the new mode
+
+## Usage
+
+1. **Build and flash** the example to your board
+2. **Default behavior**: The board starts in **Device mode**
+3. **Device mode**:
+ - Connect the board to a PC
+ - Open a serial terminal (e.g., `screen /dev/ttyACM0` or PuTTY)
+ - Type characters - they will be echoed back to you
+4. **Switch to Host mode**:
+ - Press the board button
+ - Connect a USB device to the board
+ - The board will enumerate the device and print its descriptors to the debug console
+5. **Switch back to Device mode**: Press the button again
+
+## LED Patterns
+
+The onboard LED indicates the USB connection status:
+
+- **Fast blink (250ms)**: Not mounted/connected
+- **Slow blink (1000ms)**: Successfully mounted/connected
+- **Very slow blink (2500ms)**: Suspended (device mode only)
+
+## Serial Output
+
+The example prints status messages to the debug UART:
+
+```
+======================================
+TinyUSB Dynamic Switch Example
+Press button to switch between device and host modes
+Starting in DEVICE mode...
+======================================
+
+[DEVICE] Mounted
+
+--- Switching USB mode ---
+Stopping DEVICE mode...
+Starting HOST mode...
+Mode switch complete!
+
+[HOST] Device attached, address = 1
+Device 1: ID 1234:5678 SN ABC123
+Device Descriptor:
+ bLength 18
+ bDescriptorType 1
+ bcdUSB 0200
+ bDeviceClass 239
+ ...
+```
diff --git a/examples/dual/dynamic_switch/only.txt b/examples/dual/dynamic_switch/only.txt
new file mode 100644
index 000000000..8508780e6
--- /dev/null
+++ b/examples/dual/dynamic_switch/only.txt
@@ -0,0 +1,10 @@
+family:espressif
+mcu:STM32C0
+mcu:STM32G0
+mcu:STM32H5
+mcu:STM32F2
+mcu:STM32F4
+mcu:STM32U5
+mcu:STM32F7
+mcu:STM32H7
+mcu:STM32H7RS
diff --git a/examples/dual/dynamic_switch/src/CMakeLists.txt b/examples/dual/dynamic_switch/src/CMakeLists.txt
new file mode 100644
index 000000000..cef2b46ee
--- /dev/null
+++ b/examples/dual/dynamic_switch/src/CMakeLists.txt
@@ -0,0 +1,4 @@
+# This file is for ESP-IDF only
+idf_component_register(SRCS "main.c" "usb_descriptors.c"
+ INCLUDE_DIRS "."
+ REQUIRES boards tinyusb_src)
diff --git a/examples/dual/dynamic_switch/src/main.c b/examples/dual/dynamic_switch/src/main.c
new file mode 100644
index 000000000..c9ad2a835
--- /dev/null
+++ b/examples/dual/dynamic_switch/src/main.c
@@ -0,0 +1,496 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+/* This example demonstrates dynamic switching between device and host modes:
+ * - Press button to switch between device and host modes
+ * - Device mode: CDC echo (echoes input back to output)
+ * - Host mode: Prints connected device information
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "bsp/board_api.h"
+#include "tusb.h"
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+ #ifdef ESP_PLATFORM
+ #define USBD_STACK_SIZE 4096
+ #define USBH_STACK_SIZE 4096
+ #else
+ // Increase stack size when debug log is enabled
+ #define USBD_STACK_SIZE (3*configMINIMAL_STACK_SIZE/2) * (CFG_TUSB_DEBUG ? 2 : 1)
+ #define USBH_STACK_SIZE (3*configMINIMAL_STACK_SIZE/2) * (CFG_TUSB_DEBUG ? 2 : 1)
+ #endif
+
+ #define CDC_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 2 : 1))
+ #define BLINKY_STACK_SIZE configMINIMAL_STACK_SIZE
+#endif
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF PROTOTYPES
+//--------------------------------------------------------------------+
+
+// English
+#define LANGUAGE_ID 0x0409
+
+/* Blink pattern
+ * - 250 ms : not mounted
+ * - 1000 ms : mounted
+ * - 2500 ms : suspended
+ */
+enum {
+ BLINK_NOT_MOUNTED = 250,
+ BLINK_MOUNTED = 1000,
+ BLINK_SUSPENDED = 2500,
+};
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+// static task for FreeRTOS
+#if configSUPPORT_STATIC_ALLOCATION
+StackType_t blinky_stack[BLINKY_STACK_SIZE];
+StaticTask_t blinky_taskdef;
+
+StackType_t usb_stack[USBD_STACK_SIZE > USBH_STACK_SIZE ? USBD_STACK_SIZE : USBH_STACK_SIZE];
+StaticTask_t usb_taskdef;
+
+StackType_t cdc_stack[CDC_STACK_SIZE];
+StaticTask_t cdc_taskdef;
+#endif
+#endif
+
+static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
+static tusb_role_t current_role = TUSB_ROLE_DEVICE;
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+static void usb_task(void *param);
+void led_blinking_task(void *param);
+void cdc_task(void *params);
+#else
+void led_blinking_task(void);
+void cdc_task(void);
+#endif
+void usb_mode_switch(void);
+static void print_device_info(uint8_t daddr);
+static void print_utf16(uint16_t* temp_buf, size_t buf_len);
+
+// Declare buffer for USB transfer
+CFG_TUH_MEM_SECTION struct {
+ TUH_EPBUF_TYPE_DEF(tusb_desc_device_t, device);
+ TUH_EPBUF_DEF(serial, 64*sizeof(uint16_t));
+ TUH_EPBUF_DEF(buf, 128*sizeof(uint16_t));
+} desc;
+
+//--------------------------------------------------------------------+
+// Main
+//--------------------------------------------------------------------+
+
+int main(void) {
+ board_init();
+
+ printf("\r\n======================================\r\n");
+ printf("TinyUSB Dynamic Switch Example\r\n");
+ printf("Press button to switch between device and host modes\r\n");
+ printf("Starting in DEVICE mode...\r\n");
+ printf("======================================\r\n\r\n");
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+ // Create FreeRTOS tasks
+#if configSUPPORT_STATIC_ALLOCATION
+ xTaskCreateStatic(led_blinking_task, "blinky", BLINKY_STACK_SIZE, NULL, 1, blinky_stack, &blinky_taskdef);
+ xTaskCreateStatic(usb_task, "usb", USBD_STACK_SIZE > USBH_STACK_SIZE ? USBD_STACK_SIZE : USBH_STACK_SIZE,
+ NULL, configMAX_PRIORITIES-1, usb_stack, &usb_taskdef);
+ xTaskCreateStatic(cdc_task, "cdc", CDC_STACK_SIZE, NULL, configMAX_PRIORITIES - 2, cdc_stack, &cdc_taskdef);
+#else
+ xTaskCreate(led_blinking_task, "blinky", BLINKY_STACK_SIZE, NULL, 1, NULL);
+ xTaskCreate(usb_task, "usb", USBD_STACK_SIZE > USBH_STACK_SIZE ? USBD_STACK_SIZE : USBH_STACK_SIZE,
+ NULL, configMAX_PRIORITIES - 1, NULL);
+ xTaskCreate(cdc_task, "cdc", CDC_STACK_SIZE, NULL, configMAX_PRIORITIES - 2, NULL);
+#endif
+
+#ifndef ESP_PLATFORM
+ // only start scheduler for non-espressif mcu
+ vTaskStartScheduler();
+#endif
+
+#else
+ // Initialize in device mode by default
+ tusb_rhport_init_t dev_init = {
+ .role = TUSB_ROLE_DEVICE,
+ .speed = TUSB_SPEED_AUTO
+ };
+ tusb_init(BOARD_RHPORT, &dev_init);
+ current_role = TUSB_ROLE_DEVICE;
+
+ board_init_after_tusb();
+
+ while (1) {
+ // Check for button press to switch modes
+ static bool pending_switch = false;
+ if (board_button_read()) {
+ if (!pending_switch) {
+ pending_switch = true;
+ usb_mode_switch();
+ }
+ } else {
+ pending_switch = false;
+ }
+
+ // Process USB tasks based on current mode
+ if (current_role == TUSB_ROLE_DEVICE) {
+ tud_task();
+ cdc_task();
+ } else {
+ tuh_task();
+ }
+
+ led_blinking_task();
+ }
+#endif
+}
+
+#ifdef ESP_PLATFORM
+void app_main(void) {
+ main();
+}
+#endif
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+// USB Task for FreeRTOS
+// This top level thread processes all usb events and mode switching
+static void usb_task(void *param) {
+ (void) param;
+
+ // init device stack on configured roothub port
+ // This should be called after scheduler/kernel is started.
+ // Otherwise it could cause kernel issue since USB IRQ handler does use RTOS queue API.
+ tusb_rhport_init_t dev_init = {
+ .role = TUSB_ROLE_DEVICE,
+ .speed = TUSB_SPEED_AUTO
+ };
+ tusb_init(BOARD_RHPORT, &dev_init);
+ current_role = TUSB_ROLE_DEVICE;
+
+ board_init_after_tusb();
+
+ // RTOS forever loop
+ while (1) {
+ // Check for button press to switch modes
+ static bool pending_switch = false;
+ if (board_button_read()) {
+ if (!pending_switch) {
+ pending_switch = true;
+ usb_mode_switch();
+ }
+ } else {
+ pending_switch = false;
+ }
+
+ // Process USB tasks based on current mode
+ // Use _ext version to allow return and read button state
+ if (current_role == TUSB_ROLE_DEVICE) {
+ tud_task_ext(10, false);
+ } else {
+ tuh_task_ext(10, false);
+ }
+ }
+}
+#endif
+
+//--------------------------------------------------------------------+
+// Mode Switching
+//--------------------------------------------------------------------+
+
+void usb_mode_switch(void) {
+ printf("\r\n--- Switching USB mode ---\r\n");
+
+ // Deinitialize current mode
+ if (current_role == TUSB_ROLE_DEVICE) {
+ printf("Stopping DEVICE mode...\r\n");
+ tusb_deinit(BOARD_RHPORT);
+ } else {
+ printf("Stopping HOST mode...\r\n");
+ tusb_deinit(BOARD_RHPORT);
+ }
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+ vTaskDelay(pdMS_TO_TICKS(100)); // Small delay for clean transition
+#else
+ tusb_time_delay_ms_api(100); // Small delay for clean transition
+#endif // Switch to the other mode
+ if (current_role == TUSB_ROLE_DEVICE) {
+ printf("Starting HOST mode...\r\n");
+ tusb_rhport_init_t host_init = {
+ .role = TUSB_ROLE_HOST,
+ .speed = TUSB_SPEED_AUTO
+ };
+ tusb_init(BOARD_RHPORT, &host_init);
+ current_role = TUSB_ROLE_HOST;
+ } else {
+ printf("Starting DEVICE mode...\r\n");
+ tusb_rhport_init_t dev_init = {
+ .role = TUSB_ROLE_DEVICE,
+ .speed = TUSB_SPEED_AUTO
+ };
+ tusb_init(BOARD_RHPORT, &dev_init);
+ current_role = TUSB_ROLE_DEVICE;
+ }
+
+ blink_interval_ms = BLINK_NOT_MOUNTED;
+ printf("Mode switch complete!\r\n\r\n");
+}
+
+//--------------------------------------------------------------------+
+// Device Mode: CDC Task
+//--------------------------------------------------------------------+
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+void cdc_task(void *params) {
+ (void) params;
+
+ // RTOS forever loop
+ while (1) {
+ // Only process CDC when in device mode
+ if (current_role == TUSB_ROLE_DEVICE) {
+ // Connected and there are data available
+ while (tud_cdc_available()) {
+ uint8_t buf[64];
+
+ // Read data
+ uint32_t count = tud_cdc_read(buf, sizeof(buf));
+
+ // Echo back
+ tud_cdc_write(buf, count);
+
+ // Add newline for carriage return
+ for (uint32_t i = 0; i < count; i++) {
+ if (buf[i] == '\r') {
+ tud_cdc_write_char('\n');
+ break;
+ }
+ }
+ }
+
+ tud_cdc_write_flush();
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+}
+#else
+void cdc_task(void) {
+ // Connected and there are data available
+ if (tud_cdc_available()) {
+ uint8_t buf[64];
+
+ // Read data
+ uint32_t count = tud_cdc_read(buf, sizeof(buf));
+
+ // Echo back
+ for (uint32_t i = 0; i < count; i++) {
+ tud_cdc_write_char(buf[i]);
+
+ if (buf[i] == '\r') {
+ tud_cdc_write_char('\n');
+ }
+ }
+
+ tud_cdc_write_flush();
+ }
+}
+#endif
+
+//--------------------------------------------------------------------+
+// Device Callbacks
+//--------------------------------------------------------------------+
+
+// Invoked when device is mounted
+void tud_mount_cb(void) {
+ printf("[DEVICE] Mounted\r\n");
+ blink_interval_ms = BLINK_MOUNTED;
+}
+
+// Invoked when device is unmounted
+void tud_umount_cb(void) {
+ printf("[DEVICE] Unmounted\r\n");
+ blink_interval_ms = BLINK_NOT_MOUNTED;
+}
+
+// Invoked when usb bus is suspended
+void tud_suspend_cb(bool remote_wakeup_en) {
+ (void) remote_wakeup_en;
+ printf("[DEVICE] Suspended\r\n");
+ blink_interval_ms = BLINK_SUSPENDED;
+}
+
+// Invoked when usb bus is resumed
+void tud_resume_cb(void) {
+ printf("[DEVICE] Resumed\r\n");
+ blink_interval_ms = tud_mounted() ? BLINK_MOUNTED : BLINK_NOT_MOUNTED;
+}
+
+//--------------------------------------------------------------------+
+// Host Callbacks
+//--------------------------------------------------------------------+
+
+// Invoked when device is mounted (configured)
+void tuh_mount_cb(uint8_t daddr) {
+ printf("[HOST] Device attached, address = %d\r\n", daddr);
+ blink_interval_ms = BLINK_MOUNTED;
+ print_device_info(daddr);
+}
+
+// Invoked when device is unmounted (unplugged)
+void tuh_umount_cb(uint8_t daddr) {
+ printf("[HOST] Device removed, address = %d\r\n", daddr);
+ blink_interval_ms = BLINK_NOT_MOUNTED;
+}
+
+//--------------------------------------------------------------------+
+// Host Device Info
+//--------------------------------------------------------------------+
+
+static void print_device_info(uint8_t daddr) {
+ // Get Device Descriptor
+ uint8_t xfer_result = tuh_descriptor_get_device_sync(daddr, &desc.device, 18);
+ if (XFER_RESULT_SUCCESS != xfer_result) {
+ printf("Failed to get device descriptor\r\n");
+ return;
+ }
+
+ printf("Device %u: ID %04x:%04x SN ", daddr, desc.device.idVendor, desc.device.idProduct);
+
+ xfer_result = XFER_RESULT_FAILED;
+ if (desc.device.iSerialNumber != 0) {
+ xfer_result = tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, desc.serial, sizeof(desc.serial));
+ }
+ if (XFER_RESULT_SUCCESS != xfer_result) {
+ uint16_t* serial = (uint16_t*)(uintptr_t) desc.serial;
+ serial[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * 3 + 2));
+ serial[1] = 'n';
+ serial[2] = '/';
+ serial[3] = 'a';
+ serial[4] = 0;
+ }
+ print_utf16((uint16_t*)(uintptr_t) desc.serial, sizeof(desc.serial)/2);
+ printf("\r\n");
+
+ printf("Device Descriptor:\r\n");
+ printf(" bLength %u\r\n", desc.device.bLength);
+ printf(" bDescriptorType %u\r\n", desc.device.bDescriptorType);
+ printf(" bcdUSB %04x\r\n", desc.device.bcdUSB);
+ printf(" bDeviceClass %u\r\n", desc.device.bDeviceClass);
+ printf(" bDeviceSubClass %u\r\n", desc.device.bDeviceSubClass);
+ printf(" bDeviceProtocol %u\r\n", desc.device.bDeviceProtocol);
+ printf(" bMaxPacketSize0 %u\r\n", desc.device.bMaxPacketSize0);
+ printf(" idVendor 0x%04x\r\n", desc.device.idVendor);
+ printf(" idProduct 0x%04x\r\n", desc.device.idProduct);
+ printf(" bcdDevice %04x\r\n", desc.device.bcdDevice);
+
+ // Get Manufacturer string
+ if (desc.device.iManufacturer) {
+ if (XFER_RESULT_SUCCESS == tuh_descriptor_get_manufacturer_string_sync(daddr, LANGUAGE_ID, desc.buf, sizeof(desc.buf))) {
+ printf(" iManufacturer %u ", desc.device.iManufacturer);
+ print_utf16((uint16_t*)(uintptr_t) desc.buf, sizeof(desc.buf)/2);
+ printf("\r\n");
+ }
+ }
+
+ // Get Product string
+ if (desc.device.iProduct) {
+ if (XFER_RESULT_SUCCESS == tuh_descriptor_get_product_string_sync(daddr, LANGUAGE_ID, desc.buf, sizeof(desc.buf))) {
+ printf(" iProduct %u ", desc.device.iProduct);
+ print_utf16((uint16_t*)(uintptr_t) desc.buf, sizeof(desc.buf)/2);
+ printf("\r\n");
+ }
+ }
+
+ // Get Serial string
+ if (desc.device.iSerialNumber) {
+ printf(" iSerialNumber %u ", desc.device.iSerialNumber);
+ print_utf16((uint16_t*)(uintptr_t) desc.serial, sizeof(desc.serial)/2);
+ printf("\r\n");
+ } else {
+ printf(" iSerialNumber 0\r\n");
+ }
+
+ printf(" bNumConfigurations %u\r\n", desc.device.bNumConfigurations);
+ printf("\r\n");
+}
+
+static void print_utf16(uint16_t* temp_buf, size_t buf_len) {
+ if (temp_buf[0] == 0 || (temp_buf[0] >> 8) != TUSB_DESC_STRING) {
+ printf("(invalid)");
+ return;
+ }
+
+ size_t chr_count = (temp_buf[0] & 0xff) / 2 - 1;
+ if (chr_count > buf_len - 1) {
+ chr_count = buf_len - 1;
+ }
+
+ for (size_t i = 0; i < chr_count; i++) {
+ uint16_t ch = temp_buf[1 + i];
+ if (ch <= 0x7F) {
+ putchar((char) ch);
+ } else {
+ // TODO support UTF16 to UTF8 conversion
+ putchar('?');
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+// Blinking Task
+//--------------------------------------------------------------------+
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+void led_blinking_task(void *param) {
+ (void) param;
+ static bool led_state = false;
+
+ // RTOS forever loop
+ while (1) {
+ board_led_write(led_state);
+ led_state = 1 - led_state; // toggle
+ vTaskDelay(pdMS_TO_TICKS(blink_interval_ms));
+ }
+}
+#else
+void led_blinking_task(void) {
+ static uint32_t start_ms = 0;
+ static bool led_state = false;
+
+ // Blink every interval ms
+ if (tusb_time_millis_api() - start_ms < blink_interval_ms) {
+ return; // not enough time
+ }
+ start_ms += blink_interval_ms;
+
+ board_led_write(led_state);
+ led_state = 1 - led_state; // toggle
+}
+#endif
diff --git a/examples/dual/dynamic_switch/src/tusb_config.h b/examples/dual/dynamic_switch/src/tusb_config.h
new file mode 100644
index 000000000..af0d55f14
--- /dev/null
+++ b/examples/dual/dynamic_switch/src/tusb_config.h
@@ -0,0 +1,158 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef _TUSB_CONFIG_H_
+#define _TUSB_CONFIG_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Board Specific Configuration
+//--------------------------------------------------------------------+
+
+// RHPort number used can be defined by board.mk, default to port 0
+#ifndef BOARD_RHPORT
+ #if defined(BOARD_TUD_RHPORT)
+ #define BOARD_RHPORT BOARD_TUD_RHPORT
+ #else
+ #define BOARD_RHPORT 0
+ #define BOARD_TUD_RHPORT 0
+ #endif
+#endif
+
+#if defined(BOARD_TUH_RHPORT)
+ #if BOARD_TUH_RHPORT != BOARD_RHPORT
+ #undef BOARD_TUH_RHPORT
+ #define BOARD_TUH_RHPORT BOARD_RHPORT
+ #endif
+#else
+ #define BOARD_TUH_RHPORT BOARD_RHPORT
+#endif
+
+// RHPort max operational speed can defined by board.mk
+#ifndef BOARD_MAX_SPEED
+ #if defined(BOARD_TUD_MAX_SPEED)
+ #define BOARD_MAX_SPEED BOARD_TUD_MAX_SPEED
+ #else
+ #define BOARD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
+ #endif
+#endif
+
+//--------------------------------------------------------------------
+// COMMON CONFIGURATION
+//--------------------------------------------------------------------
+
+// defined by compiler flags for flexibility
+#ifndef CFG_TUSB_MCU
+#error CFG_TUSB_MCU must be defined
+#endif
+
+#ifndef CFG_TUSB_OS
+#define CFG_TUSB_OS OPT_OS_NONE
+#endif
+
+#ifndef CFG_TUSB_DEBUG
+#define CFG_TUSB_DEBUG 0
+#endif
+
+// Enable Device and Host stacks (dual role)
+#define CFG_TUD_ENABLED 1
+#define CFG_TUH_ENABLED 1
+
+// Default is max speed that hardware controller could support with on-chip PHY
+#define CFG_TUD_MAX_SPEED BOARD_MAX_SPEED
+#define CFG_TUH_MAX_SPEED BOARD_MAX_SPEED
+
+/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
+ * Tinyusb use follows macros to declare transferring memory so that they can be put
+ * into those specific section.
+ * e.g
+ * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
+ * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
+ */
+#ifndef CFG_TUD_MEM_SECTION
+#define CFG_TUD_MEM_SECTION
+#endif
+
+#ifndef CFG_TUD_MEM_ALIGN
+#define CFG_TUD_MEM_ALIGN __attribute__((aligned(4)))
+#endif
+
+#ifndef CFG_TUH_MEM_SECTION
+#define CFG_TUH_MEM_SECTION CFG_TUD_MEM_SECTION
+#endif
+
+#ifndef CFG_TUH_MEM_ALIGN
+#define CFG_TUH_MEM_ALIGN CFG_TUD_MEM_ALIGN
+#endif
+
+//--------------------------------------------------------------------
+// DEVICE CONFIGURATION
+//--------------------------------------------------------------------
+
+#ifndef CFG_TUD_ENDPOINT0_SIZE
+#define CFG_TUD_ENDPOINT0_SIZE 64
+#endif
+
+//------------- CLASS -------------//
+#define CFG_TUD_CDC 1
+#define CFG_TUD_MSC 0
+#define CFG_TUD_HID 0
+#define CFG_TUD_MIDI 0
+#define CFG_TUD_VENDOR 0
+
+// CDC FIFO size of TX and RX
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+
+// CDC Endpoint transfer buffer size, more is faster
+#define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+
+//--------------------------------------------------------------------
+// HOST CONFIGURATION
+//--------------------------------------------------------------------
+
+// Size of buffer to hold descriptors and other data used for enumeration
+#define CFG_TUH_ENUMERATION_BUFSIZE 256
+
+#define CFG_TUH_HUB 1
+// max device support (excluding hub device)
+#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports
+
+#define CFG_TUH_CDC 0
+#define CFG_TUH_HID 0
+#define CFG_TUH_MSC 0
+#define CFG_TUH_VENDOR 0
+
+// max endpoint pair supported by each device
+#define CFG_TUH_ENDPOINT_MAX 16
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TUSB_CONFIG_H_ */
diff --git a/examples/dual/dynamic_switch/src/usb_descriptors.c b/examples/dual/dynamic_switch/src/usb_descriptors.c
new file mode 100644
index 000000000..54ffc2c18
--- /dev/null
+++ b/examples/dual/dynamic_switch/src/usb_descriptors.c
@@ -0,0 +1,199 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "bsp/board_api.h"
+#include "tusb.h"
+
+/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
+ * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
+ *
+ * Auto ProductID layout's Bitmap:
+ * [MSB] AUDIO | MIDI | HID | MSC | CDC [LSB]
+ */
+#define PID_MAP(itf, n) ((CFG_TUD_##itf) ? (1 << (n)) : 0)
+#define USB_PID (0x4000 | PID_MAP(CDC, 0) | PID_MAP(MSC, 1) | PID_MAP(HID, 2) | \
+ PID_MAP(MIDI, 3) | PID_MAP(VENDOR, 4))
+
+#define USB_VID 0xCafe
+#define USB_BCD 0x0200
+
+//--------------------------------------------------------------------+
+// Device Descriptors
+//--------------------------------------------------------------------+
+static tusb_desc_device_t const desc_device = {
+ .bLength = sizeof(tusb_desc_device_t),
+ .bDescriptorType = TUSB_DESC_DEVICE,
+ .bcdUSB = USB_BCD,
+ .bDeviceClass = TUSB_CLASS_MISC,
+ .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+ .bDeviceProtocol = MISC_PROTOCOL_IAD,
+
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+
+ .idVendor = USB_VID,
+ .idProduct = USB_PID,
+ .bcdDevice = 0x0100,
+
+ .iManufacturer = 0x01,
+ .iProduct = 0x02,
+ .iSerialNumber = 0x03,
+
+ .bNumConfigurations = 0x01
+};
+
+// Invoked when received GET DEVICE DESCRIPTOR
+// Application return pointer to descriptor
+uint8_t const *tud_descriptor_device_cb(void) {
+ return (uint8_t const *) &desc_device;
+}
+
+//--------------------------------------------------------------------+
+// Configuration Descriptor
+//--------------------------------------------------------------------+
+
+enum {
+ ITF_NUM_CDC = 0,
+ ITF_NUM_CDC_DATA,
+ ITF_NUM_TOTAL
+};
+
+#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX
+ // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number
+ // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ...
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x82
+
+#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+ // MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
+ // e.g EP1 OUT & EP1 IN cannot exist together
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x83
+
+#else
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x82
+
+#endif
+
+#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN)
+
+static uint8_t const desc_fs_configuration[] = {
+ // Config number, interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+ // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
+ TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
+};
+
+#if TUD_OPT_HIGH_SPEED
+static uint8_t const desc_hs_configuration[] = {
+ // Config number, interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+ // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
+ TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 512),
+};
+#endif
+
+// Invoked when received GET CONFIGURATION DESCRIPTOR
+// Application return pointer to descriptor
+// Descriptor contents must exist long enough for transfer to complete
+uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
+ (void) index; // for multiple configurations
+
+#if TUD_OPT_HIGH_SPEED
+ // Although we are highspeed, host may be fullspeed.
+ return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration;
+#else
+ return desc_fs_configuration;
+#endif
+}
+
+//--------------------------------------------------------------------+
+// String Descriptors
+//--------------------------------------------------------------------+
+
+// String Descriptor Index
+enum {
+ STRID_LANGID = 0,
+ STRID_MANUFACTURER,
+ STRID_PRODUCT,
+ STRID_SERIAL,
+};
+
+// array of pointer to string descriptors
+static char const *string_desc_arr[] = {
+ (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
+ "TinyUSB", // 1: Manufacturer
+ "TinyUSB Device", // 2: Product
+ NULL, // 3: Serials, will use unique ID if possible
+ "TinyUSB CDC", // 4: CDC Interface
+};
+
+static uint16_t _desc_str[32 + 1];
+
+// Invoked when received GET STRING DESCRIPTOR request
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
+uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
+ (void) langid;
+ size_t chr_count;
+
+ switch (index) {
+ case STRID_LANGID:
+ memcpy(&_desc_str[1], string_desc_arr[0], 2);
+ chr_count = 1;
+ break;
+
+ case STRID_SERIAL:
+ chr_count = board_usb_get_serial(_desc_str + 1, 32);
+ break;
+
+ default:
+ // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
+ // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
+
+ if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) { return NULL; }
+
+ const char *str = string_desc_arr[index];
+
+ // Cap at max char
+ chr_count = strlen(str);
+ size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type
+ if (chr_count > max_count) { chr_count = max_count; }
+
+ // Convert ASCII string into UTF-16
+ for (size_t i = 0; i < chr_count; i++) {
+ _desc_str[1 + i] = str[i];
+ }
+ break;
+ }
+
+ // first byte is length (including header), second byte is string type
+ _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
+ return _desc_str;
+}
diff --git a/hw/bsp/stm32f2/family.c b/hw/bsp/stm32f2/family.c
index b901bf4cc..f1fd4ddb2 100644
--- a/hw/bsp/stm32f2/family.c
+++ b/hw/bsp/stm32f2/family.c
@@ -35,7 +35,7 @@
// Forward USB interrupt events to TinyUSB IRQ Handler
//--------------------------------------------------------------------+
void OTG_FS_IRQHandler(void) {
- tud_int_handler(0);
+ tusb_int_handler(0, true);
}
//--------------------------------------------------------------------+
diff --git a/hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h b/hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h
index 16c2fd335..4b8564709 100644
--- a/hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h
+++ b/hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h
@@ -62,7 +62,7 @@ static board_pindef_t board_pindef[] = {
{ // Button
.port = GPIOC,
.pin_init = { .Pin = GPIO_PIN_13, .Mode = GPIO_MODE_INPUT, .Pull = GPIO_PULLUP, .Speed = GPIO_SPEED_FREQ_HIGH, .Alternate = 0 },
- .active_state = 1
+ .active_state = 0
},
{ // UART TX
.port = GPIOD,
diff --git a/src/common/tusb_fifo.h b/src/common/tusb_fifo.h
index 86ba59059..a3829e38e 100644
--- a/src/common/tusb_fifo.h
+++ b/src/common/tusb_fifo.h
@@ -289,6 +289,12 @@ TU_ATTR_ALWAYS_INLINE static inline bool tu_fifo_empty(const tu_fifo_t *f) {
return wr_idx == rd_idx;
}
+// Suppress IAR warning
+// Warning[Pa082]: undefined behavior: the order of volatile accesses is undefined in this statement
+#if defined(__ICCARM__)
+#pragma diag_suppress = Pa082
+#endif
+
// return number of items in fifo, capped to fifo's depth
TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_count(const tu_fifo_t *f) {
return tu_min16(tu_ff_overflow_count(f->depth, f->wr_idx, f->rd_idx), f->depth);
@@ -303,6 +309,10 @@ TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_remaining(const tu_fifo_t *
return tu_ff_remaining_local(f->depth, f->wr_idx, f->rd_idx);
}
+#if defined(__ICCARM__)
+ #pragma diag_default=Pa082
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/src/device/usbd.c b/src/device/usbd.c
index cca4169d7..127a7bd62 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -585,7 +585,7 @@ bool tud_deinit(uint8_t rhport) {
// Deinit device controller driver
dcd_int_disable(rhport);
dcd_disconnect(rhport);
- TU_VERIFY(dcd_deinit(rhport));
+ TU_ASSERT(dcd_deinit(rhport));
// Deinit class drivers
for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) {
diff --git a/src/host/usbh.c b/src/host/usbh.c
index 20791340e..33d74862f 100644
--- a/src/host/usbh.c
+++ b/src/host/usbh.c
@@ -571,7 +571,7 @@ bool tuh_deinit(uint8_t rhport) {
// deinit host controller
hcd_int_disable(rhport);
- hcd_deinit(rhport);
+ TU_ASSERT(hcd_deinit(rhport));
_usbh_data.controller_id = TUSB_INDEX_INVALID_8;
// remove all devices on this rhport (hub_addr = 0, hub_port = 0)
diff --git a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
index acdeccf6d..1813ef70b 100644
--- a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
@@ -223,9 +223,13 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
tu_memclr(&_hcd_data, sizeof(_hcd_data));
+ // Clear pending interrupts
+ // Normally no interrupts should be pending here since we just reset the core,
+ // but device mode suspend needs to cleared by WKUP flag
+ FSDEV_REG->ISTR = 0;
+
// Enable interrupts for host mode
- FSDEV_REG->CNTR |= USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SOFM | USB_CNTR_SUSPM |
- USB_CNTR_WKUPM | USB_CNTR_ERRM | USB_CNTR_PMAOVRM;
+ FSDEV_REG->CNTR |= USB_CNTR_DCON | USB_CNTR_CTRM | USB_CNTR_SOFM | USB_CNTR_ERRM | USB_CNTR_PMAOVRM;
// Initialize port state
_hcd_data.connected = false;
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index b52646a5d..8685ec6dc 100644
--- a/src/portable/synopsys/dwc2/dcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/dcd_dwc2.c
@@ -492,6 +492,12 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
return true;
}
+bool dcd_deinit(uint8_t rhport) {
+ dcd_disconnect(rhport);
+ dwc2_core_deinit(rhport);
+ return true;
+}
+
void dcd_int_enable(uint8_t rhport) {
dwc2_dcd_int_enable(rhport);
}
diff --git a/src/portable/synopsys/dwc2/dwc2_at32.h b/src/portable/synopsys/dwc2/dwc2_at32.h
index 10824ae92..fa6d10c12 100644
--- a/src/portable/synopsys/dwc2/dwc2_at32.h
+++ b/src/portable/synopsys/dwc2/dwc2_at32.h
@@ -112,6 +112,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_
}
}
+// MCU specific PHY deinit, disable PHY power
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
+ (void) hs_phy_type;
+ dwc2->stm32_gccfg &= ~(STM32_GCCFG_PWRDWN | STM32_GCCFG_DCDEN | STM32_GCCFG_PDEN);
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
(void) dwc2;
diff --git a/src/portable/synopsys/dwc2/dwc2_bcm.h b/src/portable/synopsys/dwc2/dwc2_bcm.h
index df6d4a852..00842bba2 100644
--- a/src/portable/synopsys/dwc2/dwc2_bcm.h
+++ b/src/portable/synopsys/dwc2/dwc2_bcm.h
@@ -73,6 +73,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
// nothing to do
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) {
+ (void) dwc2;
+ (void) hs_phy_type;
+ // nothing to do
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_common.c b/src/portable/synopsys/dwc2/dwc2_common.c
index 27dda44ee..33eabaeab 100644
--- a/src/portable/synopsys/dwc2/dwc2_common.c
+++ b/src/portable/synopsys/dwc2/dwc2_common.c
@@ -39,14 +39,11 @@ static void reset_core(dwc2_regs_t* dwc2) {
while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) {
}
- // load gsnpsid (it is not readable after reset is asserted)
- const uint32_t gsnpsid = dwc2->gsnpsid;
-
- // reset core
- dwc2->grstctl |= GRSTCTL_CSRST;
+ const uint32_t gsnpsid = dwc2->gsnpsid; // preload gsnpsid which is not readable while resetting
+ dwc2->grstctl |= GRSTCTL_CSRST; // reset core
if ((gsnpsid & DWC2_CORE_REV_MASK) < (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) {
- // prior v4.20a: CSRST is self-clearing and the core clears this bit after all the necessary logic is reset in
+ // prior v4.20a: CSRST is self-clearing, and the core clears this bit after all the necessary logic is reset in
// the core, which can take several clocks, depending on the current state of the core. Once this bit has been
// cleared, the software must wait at least 3 PHY clocks before accessing the PHY domain (synchronization delay).
while (dwc2->grstctl & GRSTCTL_CSRST) {}
@@ -88,8 +85,7 @@ static void phy_fs_init(dwc2_regs_t* dwc2) {
}
/* dwc2 has 2 highspeed PHYs options
- * - UTMI+ is internal highspeed PHY, can be clocked at 30/60 Mhz for fullspeed or 60 Mhz for highspeed. Can be either
- * 8 or 16-bit interface.
+ * - UTMI+ is internal highspeed PHY, can be clocked at 30 Mhz (8-bit) or 60 Mhz (16-bit).
* - ULPI is external highspeed PHY, clocked at 60Mhz with 8-bit interface.
*
* In addition, UTMI+/ULPI can be shared to run at fullspeed mode with 48Mhz
@@ -247,6 +243,24 @@ bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma) {
return true;
}
+void dwc2_core_deinit(uint8_t rhport) {
+ dwc2_regs_t* dwc2 = DWC2_REG(rhport);
+
+ // Disable global interrupt
+ dwc2->gahbcfg &= ~GAHBCFG_GINT;
+
+ // Reset core: this also flushes FIFOs and clears all interrupt registers
+ reset_core(dwc2);
+
+ // Stop PHY clock and gate HCLK for power saving (per databook chapter 14)
+ dwc2->pcgcctl |= PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK;
+
+ // MCU-specific PHY deinit (disable PHY power)
+ const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2};
+ const uint8_t hs_phy_type = (dwc2->gusbcfg & GUSBCFG_PHYSEL) ? GHWCFG2_HSPHY_NOT_SUPPORTED : ghwcfg2.hs_phy_type;
+ dwc2_phy_deinit(dwc2, hs_phy_type);
+}
+
// void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr) {
// (void) in_isr;
// dwc2_regs_t * const dwc2 = DWC2_REG(rhport);
diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h
index 1947173b2..9f28ab2e0 100644
--- a/src/portable/synopsys/dwc2/dwc2_common.h
+++ b/src/portable/synopsys/dwc2/dwc2_common.h
@@ -42,6 +42,7 @@
// - _dwc2_controller[]: array of controllers
// - DWC2_EP_MAX: largest EP counts of all controllers
// - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset
+// - dwc2_phy_deinit(dwc2, hs_phy_type): phy deinit to disable PHY power, only deinit the phy used by core
// - dwc2_dcd_int_enable/dwc2_dcd_int_disable
// - dwc2_remote_wakeup_delay
@@ -87,6 +88,7 @@ TU_ATTR_ALWAYS_INLINE static inline dwc2_regs_t* DWC2_REG(uint8_t rhport) {
// check if highspeed phy should be used
bool dwc2_core_is_highspeed_phy(dwc2_regs_t* dwc2, bool prefer_hs_phy);
bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma);
+void dwc2_core_deinit(uint8_t rhport);
void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr);
//--------------------------------------------------------------------+
diff --git a/src/portable/synopsys/dwc2/dwc2_efm32.h b/src/portable/synopsys/dwc2/dwc2_efm32.h
index 0e3570cbb..e1cb7c769 100644
--- a/src/portable/synopsys/dwc2/dwc2_efm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_efm32.h
@@ -72,6 +72,14 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
USB->ROUTE = USB_ROUTE_PHYPEN;
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) {
+ (void) dwc2;
+ (void) hs_phy_type;
+ // Disable PHY pin
+ USB->ROUTE = 0;
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h
index a4e0d1770..ff9f216bd 100644
--- a/src/portable/synopsys/dwc2/dwc2_esp32.h
+++ b/src/portable/synopsys/dwc2/dwc2_esp32.h
@@ -121,6 +121,13 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_
}
+// MCU specific PHY deinit, disable PHY power
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+ (void)dwc2;
+ (void)hs_phy_type;
+ // PHY managed by ESP-IDF
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
(void)dwc2;
diff --git a/src/portable/synopsys/dwc2/dwc2_gd32.h b/src/portable/synopsys/dwc2/dwc2_gd32.h
index 0375fffe4..ccbf93a76 100644
--- a/src/portable/synopsys/dwc2/dwc2_gd32.h
+++ b/src/portable/synopsys/dwc2/dwc2_gd32.h
@@ -85,6 +85,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
// nothing to do
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) {
+ (void) dwc2;
+ (void) hs_phy_type;
+ // nothing to do
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_nrf.h b/src/portable/synopsys/dwc2/dwc2_nrf.h
index b93571f16..51f2d684f 100644
--- a/src/portable/synopsys/dwc2/dwc2_nrf.h
+++ b/src/portable/synopsys/dwc2/dwc2_nrf.h
@@ -52,6 +52,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_
(void)hs_phy_type;
}
+// MCU specific PHY deinit, disable PHY power
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+ (void)dwc2;
+ (void)hs_phy_type;
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
(void)dwc2;
diff --git a/src/portable/synopsys/dwc2/dwc2_stm32.h b/src/portable/synopsys/dwc2/dwc2_stm32.h
index 753917a20..259ad21b9 100644
--- a/src/portable/synopsys/dwc2/dwc2_stm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_stm32.h
@@ -264,6 +264,22 @@ static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
}
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+ if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) {
+ // Disable on-chip FS PHY
+ dwc2->stm32_gccfg &= ~STM32_GCCFG_PWRDWN;
+ } else {
+ // Disable HS PHY
+ #ifdef USB_HS_PHYC
+ dwc2->stm32_gccfg &= ~STM32_GCCFG_PHYHSEN;
+ // Disable PLL and LDO
+ USB_HS_PHYC->USB_HS_PHYC_PLL &= ~USB_HS_PHYC_PLL_PLLEN;
+ USB_HS_PHYC->USB_HS_PHYC_LDO &= ~USB_HS_PHYC_LDO_ENABLE;
+ #endif
+ }
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
// used to set turnaround time for fullspeed, nothing to do in highspeed mode
diff --git a/src/portable/synopsys/dwc2/dwc2_xmc.h b/src/portable/synopsys/dwc2/dwc2_xmc.h
index 63419abf7..aca3873df 100644
--- a/src/portable/synopsys/dwc2/dwc2_xmc.h
+++ b/src/portable/synopsys/dwc2/dwc2_xmc.h
@@ -71,6 +71,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
//USB->ROUTE = USB_ROUTE_PHYPEN;
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) {
+ (void) dwc2;
+ (void) hs_phy_type;
+ // nothing to do
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c
index 0fd60b35d..e12e44a41 100644
--- a/src/portable/synopsys/dwc2/hcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/hcd_dwc2.c
@@ -416,11 +416,6 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// force host mode and wait for mode switch
dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_FDMOD) | GUSBCFG_FHMOD;
- #if CFG_TUSB_MCU == OPT_MCU_STM32N6
- // No hardware detection of Vbus B-session is available on the STM32N6
- dwc2->stm32_gccfg &= ~STM32_GCCFG_VBVALOVAL;
- #endif
-
while ((dwc2->gintsts & GINTSTS_CMOD) != GINTSTS_CMODE_HOST) {}
#ifdef TUP_USBIP_DWC2_STM32
@@ -440,7 +435,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
dwc2->hprt = HPRT_POWER; // turn on VBUS
// Enable required interrupts
- dwc2->gintmsk |= GINTSTS_OTGINT | GINTSTS_CONIDSTSCHNG | GINTSTS_HPRTINT | GINTSTS_HCINT | GINTSTS_DISCINT;
+ dwc2->gintmsk |= GINTSTS_OTGINT | GINTSTS_HPRTINT | GINTSTS_HCINT | GINTSTS_DISCINT;
// NPTX can hold at least 2 packet, change interrupt level to half-empty
uint32_t gahbcfg = dwc2->gahbcfg & ~GAHBCFG_TX_FIFO_EPMTY_LVL;
@@ -450,6 +445,17 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
return true;
}
+bool hcd_deinit(uint8_t rhport) {
+ dwc2_regs_t* dwc2 = DWC2_REG(rhport);
+
+ // Turn off VBUS
+ dwc2->hprt = HPRT_W1_MASK; // clear w1c bits without side effects
+ // HPRT_POWER is not set -> VBUS off
+
+ dwc2_core_deinit(rhport);
+ return true;
+}
+
// Enable USB interrupt
void hcd_int_enable (uint8_t rhport) {
dwc2_int_set(rhport, TUSB_ROLE_HOST, true);
@@ -874,7 +880,7 @@ static void handle_rxflvl_irq(uint8_t rhport) {
break;
case GRXSTS_PKTSTS_HOST_DATATOGGLE_ERR:
- TU_ASSERT(0, ); // maybe try to change DToggle
+ // handle in channel interrupt
break;
case GRXSTS_PKTSTS_HOST_CHANNEL_HALTED:
@@ -1014,8 +1020,11 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h
channel_xfer_in_retry(dwc2, ch_id, hcint);
}
} else if (hcint & HCINT_DATATOGGLE_ERR) {
+ channel->hcintmsk &= ~HCINT_DATATOGGLE_ERR;
xfer->err_count = 0;
- TU_ASSERT(false);
+ hcsplt.split_compl = 0; // restart with start-split
+ channel->hcsplt = hcsplt.value;
+ channel_disable(dwc2, channel);
} else {
// nothing to do
}
@@ -1351,6 +1360,17 @@ static bool handle_sof_irq(uint8_t rhport, bool in_isr) {
}
// Config HCFG FS/LS clock and HFIR for SOF interval according to link speed (value is in PHY clock unit)
+// Databook Table 2-2: System Clock Speeds
+// +-----------+------------------+----------+-----------+-------------------+
+// | PHY | PHY Clock (MHz) | Width | HCFG.Sel | HFIR (clk cycles) |
+// +-----------+------------------+----------+-----------+-------------------+
+// | HS UTMI+ | 30 | 16-bit | 30_60 | HS:3749 FS:29999 |
+// | HS UTMI+ | 60 | 8-bit | 30_60 | HS:7499 FS:59999 |
+// | HS ULPI | 60 | 8-bit | 30_60 | HS:7499 FS:59999 |
+// | FS (dead.) | 48 | internal | 48 | FS:47999 |
+// | LS via FS | 48 (6 effective) | internal | 6 | LS:47999 |
+// +-----------+------------------+----------+-----------+-------------------+
+// HFIR = (interval_us * phy_clock) - 1, where interval is 125us (HS) or 1000us (FS/LS)
static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) {
uint32_t hcfg = dwc2->hcfg & ~HCFG_FSLS_PHYCLK_SEL;
@@ -1443,16 +1463,6 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) {
// TU_LOG1_HEX(gintsts);
- if (gintsts & GINTSTS_CONIDSTSCHNG) {
- // Connector ID status change
- dwc2->gintsts = GINTSTS_CONIDSTSCHNG;
-
- //if (dwc2->gotgctl)
- // dwc2->hprt = HPRT_POWER; // power on port to turn on VBUS
- //dwc2->gintmsk |= GINTMSK_PRTIM;
- // TODO wait for SRP if OTG
- }
-
if (gintsts & GINTSTS_SOF) {
const bool more_sof = handle_sof_irq(rhport, in_isr);
if (!more_sof) {
diff --git a/src/tusb.c b/src/tusb.c
index 37aecf693..5241fcf3d 100644
--- a/src/tusb.c
+++ b/src/tusb.c
@@ -39,6 +39,12 @@
#include "host/usbh_pvt.h"
#endif
+// Suppress IAR warning
+// Warning[Pe111]: statement is unreachable
+#if defined(__ICCARM__)
+#pragma diag_suppress = Pe111
+#endif
+
tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM] = { TUSB_ROLE_INVALID };
//--------------------------------------------------------------------