Skip to content

Commit 849e0ae

Browse files
committed
feature(tinyusb): Added VBUS monitor api and structs
1 parent e77ff0b commit 849e0ae

File tree

8 files changed

+233
-58
lines changed

8 files changed

+233
-58
lines changed

device/esp_tinyusb/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
idf_build_get_property(target IDF_TARGET)
2+
13
set(srcs
24
"descriptors_control.c"
35
"tinyusb.c"
@@ -43,6 +45,13 @@ if(CONFIG_TINYUSB_NET_MODE_NCM)
4345
)
4446
endif() # CONFIG_TINYUSB_NET_MODE_NCM
4547

48+
# Software VBUS monitoring is available only on esp32p4
49+
if(${target} STREQUAL "esp32p4")
50+
list(APPEND srcs
51+
"tinyusb_vbus_monitor.c"
52+
)
53+
endif() # esp32p4
54+
4655
idf_component_register(SRCS ${srcs}
4756
INCLUDE_DIRS "include"
4857
PRIV_INCLUDE_DIRS "include_private"

device/esp_tinyusb/include/tinyusb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ typedef struct {
5151
The voltage divider output should be (0.75 * Vdd) if VBUS is 4.4V (lowest valid voltage at device port).
5252
The comparator thresholds should be set with hysteresis: 4.35V (falling edge) and 4.75V (raising edge). */
5353
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. */
54+
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. */
5455
} tinyusb_phy_config_t;
5556

5657
/**

device/esp_tinyusb/include/tinyusb_default_config.h

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -63,54 +63,58 @@ extern "C" {
6363
#define TINYUSB_DEFAULT_TASK_SIZE 4096
6464
// Default priority for task used in TinyUSB task creation
6565
#define TINYUSB_DEFAULT_TASK_PRIO 5
66+
// Default VBUS debounce time in milliseconds
67+
#define TINYUSB_DEFAULT_DEBOUNCE_MS 250
6668

67-
#define TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) \
68-
(tinyusb_config_t) { \
69-
.port = TINYUSB_PORT_FULL_SPEED_0, \
70-
.phy = { \
71-
.skip_setup = false, \
72-
.self_powered = false, \
73-
.vbus_monitor_io = -1, \
74-
}, \
75-
.task = TINYUSB_TASK_DEFAULT(), \
76-
.descriptor = { \
77-
.device = NULL, \
78-
.qualifier = NULL, \
79-
.string = NULL, \
80-
.string_count = 0, \
81-
.full_speed_config = NULL, \
82-
.high_speed_config = NULL, \
83-
}, \
84-
.event_cb = (event_hdl), \
85-
.event_arg = (arg), \
69+
#define TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) \
70+
(tinyusb_config_t) { \
71+
.port = TINYUSB_PORT_FULL_SPEED_0, \
72+
.phy = { \
73+
.skip_setup = false, \
74+
.self_powered = false, \
75+
.vbus_monitor_io = -1, \
76+
.vbus_monitor_debounce_ms = TINYUSB_DEFAULT_DEBOUNCE_MS, \
77+
}, \
78+
.task = TINYUSB_TASK_DEFAULT(), \
79+
.descriptor = { \
80+
.device = NULL, \
81+
.qualifier = NULL, \
82+
.string = NULL, \
83+
.string_count = 0, \
84+
.full_speed_config = NULL, \
85+
.high_speed_config = NULL, \
86+
}, \
87+
.event_cb = (event_hdl), \
88+
.event_arg = (arg), \
8689
}
8790

88-
#define TINYUSB_CONFIG_HIGH_SPEED(event_hdl, arg) \
89-
(tinyusb_config_t) { \
90-
.port = TINYUSB_PORT_HIGH_SPEED_0, \
91-
.phy = { \
92-
.skip_setup = false, \
93-
.self_powered = false, \
94-
.vbus_monitor_io = -1, \
95-
}, \
96-
.task = TINYUSB_TASK_DEFAULT(), \
97-
.descriptor = { \
98-
.device = NULL, \
99-
.qualifier = NULL, \
100-
.string = NULL, \
101-
.string_count = 0, \
102-
.full_speed_config = NULL, \
103-
.high_speed_config = NULL, \
104-
}, \
105-
.event_cb = (event_hdl), \
106-
.event_arg = (arg), \
91+
#define TINYUSB_CONFIG_HIGH_SPEED(event_hdl, arg) \
92+
(tinyusb_config_t) { \
93+
.port = TINYUSB_PORT_HIGH_SPEED_0, \
94+
.phy = { \
95+
.skip_setup = false, \
96+
.self_powered = false, \
97+
.vbus_monitor_io = -1, \
98+
.vbus_monitor_debounce_ms = TINYUSB_DEFAULT_DEBOUNCE_MS, \
99+
}, \
100+
.task = TINYUSB_TASK_DEFAULT(), \
101+
.descriptor = { \
102+
.device = NULL, \
103+
.qualifier = NULL, \
104+
.string = NULL, \
105+
.string_count = 0, \
106+
.full_speed_config = NULL, \
107+
.high_speed_config = NULL, \
108+
}, \
109+
.event_cb = (event_hdl), \
110+
.event_arg = (arg), \
107111
}
108112

109-
#define TINYUSB_TASK_DEFAULT() \
110-
(tinyusb_task_config_t) { \
111-
.size = TINYUSB_DEFAULT_TASK_SIZE, \
112-
.priority = TINYUSB_DEFAULT_TASK_PRIO, \
113-
.xCoreID = TINYUSB_DEFAULT_TASK_AFFINITY, \
113+
#define TINYUSB_TASK_DEFAULT() \
114+
(tinyusb_task_config_t) { \
115+
.size = TINYUSB_DEFAULT_TASK_SIZE, \
116+
.priority = TINYUSB_DEFAULT_TASK_PRIO, \
117+
.xCoreID = TINYUSB_DEFAULT_TASK_AFFINITY, \
114118
}
115119

116120
/**

device/esp_tinyusb/include_private/tinyusb_task.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "esp_err.h"
1010
#include "tinyusb.h"
11+
#include "tinyusb_vbus_monitor.h"
1112

1213
#ifdef __cplusplus
1314
extern "C" {
@@ -42,7 +43,10 @@ esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *task_cfg);
4243
* - ESP_ERR_NO_MEM if memory allocation failed
4344
* - ESP_OK if TinyUSB Task initialized successfully
4445
*/
45-
esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *task_cfg, const tinyusb_desc_config_t *desc_cfg);
46+
esp_err_t tinyusb_task_start(tinyusb_port_t port,
47+
const tinyusb_task_config_t *task_cfg,
48+
const tinyusb_desc_config_t *desc_cfg,
49+
const tinyusb_vbus_monitor_config_t *vbus_monitor_cfg);
4650

4751
/**
4852
* @brief Stops TinyUSB Task
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#pragma once
8+
9+
#include "esp_err.h"
10+
#include "tinyusb.h"
11+
#include "driver/gpio.h"
12+
13+
#ifdef __cplusplus
14+
extern "C" {
15+
#endif
16+
17+
typedef struct {
18+
gpio_num_t gpio_num; /*!< GPIO number used for VBUS monitoring, 3.3 V tolerant */
19+
uint32_t debounce_delay_ms; /*!< Debounce delay in milliseconds */
20+
} tinyusb_vbus_monitor_config_t;
21+
22+
/**
23+
* @brief Initialize VBUS monitoring on the specified GPIO
24+
*
25+
* Note:
26+
* - This function should be called after tusb_init() when GOTGCTL register is initialized
27+
* - This is a single-threaded implementation, so only one instance of VBUS monitoring is supported
28+
*
29+
* @param config VBUS monitoring configuration
30+
*
31+
* @return
32+
* - ESP_ERR_INVALID_ARG if config is NULL
33+
* - ESP_OK if VBUS monitoring was initialized successfully
34+
*/
35+
esp_err_t tinyusb_vbus_monitor_init(tinyusb_vbus_monitor_config_t *config);
36+
37+
/**
38+
* @brief Deinitialize VBUS monitoring
39+
*/
40+
void tinyusb_vbus_monitor_deinit(void);
41+
42+
#ifdef __cplusplus
43+
}
44+
#endif

device/esp_tinyusb/tinyusb.c

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ static esp_err_t tinyusb_check_config(const tinyusb_config_t *config)
100100
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");
101101
#endif // ESP-IDF supports OTG1.1 peripheral
102102
#endif // CONFIG_IDF_TARGET_ESP32P4
103+
104+
// Device is self powered
105+
if (config->phy.self_powered) {
106+
// VBUS monitoring GPIO must be valid
107+
ESP_RETURN_ON_FALSE(config->phy.vbus_monitor_io >= GPIO_NUM_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");
108+
#if (CONFIG_IDF_TARGET_ESP32P4)
109+
if (config->port == TINYUSB_PORT_HIGH_SPEED_0) {
110+
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");
111+
}
112+
#endif // CONFIG_IDF_TARGET_ESP32P4
113+
}
103114
return ESP_OK;
104115
}
105116

@@ -110,32 +121,55 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
110121

111122
esp_err_t ret;
112123
usb_phy_handle_t phy_hdl = NULL;
124+
125+
tinyusb_vbus_monitor_config_t vbus_cfg = {
126+
.gpio_num = GPIO_NUM_NC, /*!< Default: Not connected */
127+
.debounce_delay_ms = 0, /*!< Default: No debounce */
128+
};
129+
113130
if (!config->phy.skip_setup) {
114131
// Configure USB PHY
115132
usb_phy_config_t phy_conf = {
116-
.controller = USB_PHY_CTRL_OTG,
117-
.target = USB_PHY_TARGET_INT,
118-
.otg_mode = USB_OTG_MODE_DEVICE,
119-
.otg_speed = USB_PHY_SPEED_FULL,
133+
.controller = USB_PHY_CTRL_OTG, /*!< OTG controller */
134+
.target = USB_PHY_TARGET_INT, /*!< Internal PHY */
135+
.otg_mode = USB_OTG_MODE_DEVICE, /*!< OTG mode: Device */
136+
.otg_speed = USB_PHY_SPEED_FULL, /*!< Default: Full speed */
137+
.otg_io_conf = NULL, /*!< Default: No OTG IOs configuration */
120138
};
139+
// Prepare the OTG IOs configuration
140+
usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io);
121141

122142
#if (SOC_USB_OTG_PERIPH_NUM > 1)
123143
if (config->port == TINYUSB_PORT_HIGH_SPEED_0) {
124-
// Default PHY for OTG2.0 is UTMI
144+
// Change the PHY parameters for USB OTG 2.0 High-speed
125145
phy_conf.target = USB_PHY_TARGET_UTMI;
126146
phy_conf.otg_speed = USB_PHY_SPEED_HIGH;
127147
}
128148
#endif // (SOC_USB_OTG_PERIPH_NUM > 1)
129149

130-
// OTG IOs config
131-
const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io);
132150
if (config->phy.self_powered) {
133-
phy_conf.otg_io_conf = &otg_io_conf;
151+
if (config->port == TINYUSB_PORT_FULL_SPEED_0) {
152+
// For USB OTG 1.1, mux VBUS monitor GPIO to BVALID signal
153+
phy_conf.otg_io_conf = &otg_io_conf;
154+
}
155+
#if (SOC_USB_OTG_PERIPH_NUM > 1)
156+
else if (config->port == TINYUSB_PORT_HIGH_SPEED_0) {
157+
// For USB OTG 2.0, use VBUS GPIO + debounce to drive BVALID via GOTGCTL
158+
#if CONFIG_IDF_TARGET_ESP32P4
159+
vbus_cfg.gpio_num = config->phy.vbus_monitor_io;
160+
vbus_cfg.debounce_delay_ms = config->phy.vbus_monitor_debounce_ms;
161+
#else
162+
ESP_LOGW(TAG, "VBUS monitoring GPIO for OTG 2.0 is not supported on this target yet");
163+
#endif
164+
}
165+
#endif // SOC_USB_OTG_PERIPH_NUM > 1
134166
}
167+
// Install PHY
135168
ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed");
136169
}
170+
137171
// Init TinyUSB stack in task
138-
ESP_GOTO_ON_ERROR(tinyusb_task_start(config->port, &config->task, &config->descriptor), del_phy, TAG, "Init TinyUSB task failed");
172+
ESP_GOTO_ON_ERROR(tinyusb_task_start(config->port, &config->task, &config->descriptor, &vbus_cfg), del_phy, TAG, "Init TinyUSB task failed");
139173

140174
s_ctx.port = config->port; // Save the port number
141175
s_ctx.phy_hdl = phy_hdl; // Save the PHY handle for uninstallation
@@ -155,9 +189,11 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
155189
esp_err_t tinyusb_driver_uninstall(void)
156190
{
157191
ESP_RETURN_ON_ERROR(tinyusb_task_stop(), TAG, "Deinit TinyUSB task failed");
192+
158193
if (s_ctx.phy_hdl) {
159194
ESP_RETURN_ON_ERROR(usb_del_phy(s_ctx.phy_hdl), TAG, "Unable to delete PHY");
160195
s_ctx.phy_hdl = NULL;
161196
}
197+
162198
return ESP_OK;
163199
}

device/esp_tinyusb/tinyusb_task.c

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
#include "esp_log.h"
1212
#include "esp_check.h"
1313
#include "tinyusb.h"
14+
#include "tinyusb_task.h"
15+
#include "tinyusb_vbus_monitor.h"
1416
#include "sdkconfig.h"
1517
#include "descriptors_control.h"
18+
#include "soc/usb_dwc_struct.h"
1619

1720
#if TUSB_VERSION_NUMBER < 1900 // < 0.19.0
1821
#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 {
4548
const tinyusb_desc_config_t *desc_cfg; /*!< USB Device descriptors configuration pointer */
4649
// Task related
4750
TaskHandle_t handle; /*!< Task handle */
48-
volatile TaskHandle_t awaiting_handle; /*!< Task handle, waiting to be notified after successful start of TinyUSB stack */
51+
volatile TaskHandle_t awaiting_handle; /*!< Task handle, waiting to be notified after successful start of TinyUSB stack */
52+
#if (CONFIG_IDF_TARGET_ESP32P4)
53+
tinyusb_vbus_monitor_config_t vbus_monitor_cfg; /*!< VBUS monitoring configuration */
54+
#endif // CONFIG_IDF_TARGET_ESP32P4
4955
} tinyusb_task_ctx_t;
5056

5157
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)
7783
goto desc_free;
7884
}
7985

86+
#if (CONFIG_IDF_TARGET_ESP32P4)
87+
if (task_ctx->vbus_monitor_cfg.gpio_num != GPIO_NUM_NC) {
88+
if (tinyusb_vbus_monitor_init(&task_ctx->vbus_monitor_cfg) != ESP_OK) {
89+
ESP_LOGE(TAG, "Init VBUS monitoring failed");
90+
goto desc_free;
91+
}
92+
}
93+
#endif // CONFIG_IDF_TARGET_ESP32P4
94+
8095
TINYUSB_TASK_ENTER_CRITICAL();
8196
task_ctx->handle = xTaskGetCurrentTaskHandle(); // Save task handle
8297
p_tusb_task_ctx = task_ctx; // Save global task context pointer
@@ -100,9 +115,9 @@ static void tinyusb_device_task(void *arg)
100115

101116
esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *config)
102117
{
103-
ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Task configuration can't be NULL");
104-
ESP_RETURN_ON_FALSE(config->size != 0, ESP_ERR_INVALID_ARG, TAG, "Task size can't be 0");
105-
ESP_RETURN_ON_FALSE(config->priority != 0, ESP_ERR_INVALID_ARG, TAG, "Task priority can't be 0");
118+
ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Task configuration cannot be NULL");
119+
ESP_RETURN_ON_FALSE(config->size != 0, ESP_ERR_INVALID_ARG, TAG, "Task size cannot be 0");
120+
ESP_RETURN_ON_FALSE(config->priority != 0, ESP_ERR_INVALID_ARG, TAG, "Task priority cannot be 0");
106121
#if CONFIG_FREERTOS_UNICORE
107122
ESP_RETURN_ON_FALSE(config->xCoreID == 0, ESP_ERR_INVALID_ARG, TAG, "Task affinity must be 0 only in uniprocessor mode");
108123
#else
@@ -111,9 +126,13 @@ esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *config)
111126
return ESP_OK;
112127
}
113128

114-
esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *config, const tinyusb_desc_config_t *desc_cfg)
129+
esp_err_t tinyusb_task_start(tinyusb_port_t port,
130+
const tinyusb_task_config_t *config,
131+
const tinyusb_desc_config_t *desc_cfg,
132+
const tinyusb_vbus_monitor_config_t *vbus_monitor_cfg)
115133
{
116134
ESP_RETURN_ON_ERROR(tinyusb_descriptors_check(port, desc_cfg), TAG, "TinyUSB descriptors check failed");
135+
ESP_RETURN_ON_FALSE(vbus_monitor_cfg != NULL, ESP_ERR_INVALID_ARG, TAG, "VBUS configuration cannot be NULL");
117136

118137
TINYUSB_TASK_ENTER_CRITICAL();
119138
TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx == NULL, ESP_ERR_INVALID_STATE); // Task shouldn't started
@@ -133,6 +152,11 @@ esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *c
133152
task_ctx->rhport_init.role = TUSB_ROLE_DEVICE; // Role selection: esp_tinyusb is always a device
134153
task_ctx->rhport_init.speed = (port == TINYUSB_PORT_FULL_SPEED_0) ? TUSB_SPEED_FULL : TUSB_SPEED_HIGH; // Speed selection
135154
task_ctx->desc_cfg = desc_cfg;
155+
#if (CONFIG_IDF_TARGET_ESP32P4)
156+
// VBUS monitor config
157+
task_ctx->vbus_monitor_cfg.gpio_num = vbus_monitor_cfg->gpio_num;
158+
task_ctx->vbus_monitor_cfg.debounce_delay_ms = vbus_monitor_cfg->debounce_delay_ms;
159+
#endif // CONFIG_IDF_TARGET_ESP32P4
136160

137161
TaskHandle_t task_hdl = NULL;
138162
ESP_LOGD(TAG, "Creating TinyUSB main task on CPU%d", config->xCoreID);
@@ -177,8 +201,23 @@ esp_err_t tinyusb_task_stop(void)
177201
vTaskDelete(task_ctx->handle);
178202
task_ctx->handle = NULL;
179203
}
204+
205+
/* TODO: Free descriptors and disable the VBUS monitor should be in the task itself
206+
* but currently we don't have a way to signal the task to do it and exit.
207+
* So we do it here for now.
208+
* Refer to https://github.com/espressif/esp-usb/pull/272
209+
*/
210+
180211
// Free descriptors
181212
tinyusb_descriptors_free();
213+
214+
#if (CONFIG_IDF_TARGET_ESP32P4)
215+
if (task_ctx->vbus_monitor_cfg.gpio_num != GPIO_NUM_NC) {
216+
// Deinit VBUS monitoring if it was enabled
217+
tinyusb_vbus_monitor_deinit();
218+
}
219+
#endif // CONFIG_IDF_TARGET_ESP32P4
220+
182221
// Stop TinyUSB stack
183222
ESP_RETURN_ON_FALSE(tusb_deinit(task_ctx->rhport), ESP_ERR_NOT_FINISHED, TAG, "Unable to teardown TinyUSB stack");
184223
// Cleanup

0 commit comments

Comments
 (0)