Skip to content

Commit f804977

Browse files
committed
feature(tinyusb): Added VBUS monitor api and structs
1 parent 00c2ef9 commit f804977

File tree

8 files changed

+214
-49
lines changed

8 files changed

+214
-49
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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 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).
30+
*
31+
* @return
32+
* - ESP_ERR_NOT_SUPPORTED: VBUS monitoring is not supported yet
33+
*/
34+
esp_err_t tinyusb_vbus_monitor_init(tinyusb_vbus_monitor_config_t *config);
35+
36+
/**
37+
* @brief Deinitialize VBUS monitoring
38+
*/
39+
void tinyusb_vbus_monitor_deinit(void);
40+
41+
#ifdef __cplusplus
42+
}
43+
#endif

device/esp_tinyusb/tinyusb.c

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ 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+
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");
110+
#endif // CONFIG_IDF_TARGET_ESP32P4
111+
}
103112
return ESP_OK;
104113
}
105114

@@ -110,6 +119,11 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
110119

111120
esp_err_t ret;
112121
usb_phy_handle_t phy_hdl = NULL;
122+
tinyusb_vbus_monitor_config_t vbus_cfg = {
123+
.gpio_num = GPIO_NUM_NC,
124+
.debounce_delay_ms = config->phy.vbus_monitor_debounce_ms,
125+
};
126+
113127
if (!config->phy.skip_setup) {
114128
// Configure USB PHY
115129
usb_phy_config_t phy_conf = {
@@ -128,14 +142,28 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
128142
#endif // (SOC_USB_OTG_PERIPH_NUM > 1)
129143

130144
// OTG IOs config
131-
const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io);
132145
if (config->phy.self_powered) {
133-
phy_conf.otg_io_conf = &otg_io_conf;
146+
if (config->port == TINYUSB_PORT_FULL_SPEED_0) {
147+
// For USB OTG 1.1, we map VBUS monitor io signal to the BVALID while enable PHY
148+
const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io);
149+
phy_conf.otg_io_conf = &otg_io_conf;
150+
}
151+
#if (SOC_USB_OTG_PERIPH_NUM > 1)
152+
else if (config->port == TINYUSB_PORT_HIGH_SPEED_0) {
153+
// For USB OTG 2.0, we use VBUS monitoring GPIO to control BVALID value over GOTGCTL register
154+
#if (CONFIG_IDF_TARGET_ESP32P4)
155+
vbus_cfg.gpio_num = config->phy.vbus_monitor_io;
156+
vbus_cfg.debounce_delay_ms = config->phy.vbus_monitor_debounce_ms;
157+
#else
158+
ESP_LOGW(TAG, "VBUS monitoring GPIO for OTG 2.0 is not supported on this target yet");
159+
#endif // CONFIG_IDF_TARGET_ESP32P4
160+
}
161+
#endif // SOC_USB_OTG_PERIPH_NUM > 1
134162
}
135163
ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed");
136164
}
137165
// 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");
166+
ESP_GOTO_ON_ERROR(tinyusb_task_start(config->port, &config->task, &config->descriptor, &vbus_cfg), del_phy, TAG, "Init TinyUSB task failed");
139167

140168
s_ctx.port = config->port; // Save the port number
141169
s_ctx.phy_hdl = phy_hdl; // Save the PHY handle for uninstallation
@@ -155,9 +183,11 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
155183
esp_err_t tinyusb_driver_uninstall(void)
156184
{
157185
ESP_RETURN_ON_ERROR(tinyusb_task_stop(), TAG, "Deinit TinyUSB task failed");
186+
158187
if (s_ctx.phy_hdl) {
159188
ESP_RETURN_ON_ERROR(usb_del_phy(s_ctx.phy_hdl), TAG, "Unable to delete PHY");
160189
s_ctx.phy_hdl = NULL;
161190
}
191+
162192
return ESP_OK;
163193
}

device/esp_tinyusb/tinyusb_task.c

Lines changed: 40 additions & 2 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
@@ -111,7 +126,10 @@ 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");
117135

@@ -133,6 +151,11 @@ esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *c
133151
task_ctx->rhport_init.role = TUSB_ROLE_DEVICE; // Role selection: esp_tinyusb is always a device
134152
task_ctx->rhport_init.speed = (port == TINYUSB_PORT_FULL_SPEED_0) ? TUSB_SPEED_FULL : TUSB_SPEED_HIGH; // Speed selection
135153
task_ctx->desc_cfg = desc_cfg;
154+
#if (CONFIG_IDF_TARGET_ESP32P4)
155+
// VBUS monitor config
156+
task_ctx->vbus_monitor_cfg.gpio_num = vbus_monitor_cfg->gpio_num;
157+
task_ctx->vbus_monitor_cfg.debounce_delay_ms = vbus_monitor_cfg->debounce_delay_ms;
158+
#endif // CONFIG_IDF_TARGET_ESP32P4
136159

137160
TaskHandle_t task_hdl = NULL;
138161
ESP_LOGD(TAG, "Creating TinyUSB main task on CPU%d", config->xCoreID);
@@ -177,8 +200,23 @@ esp_err_t tinyusb_task_stop(void)
177200
vTaskDelete(task_ctx->handle);
178201
task_ctx->handle = NULL;
179202
}
203+
204+
/* TODO: Free descriptors and disable the VBUS monitor should be in the task itself
205+
* but currently we don't have a way to signal the task to do it and exit.
206+
* So we do it here for now.
207+
* Refer to https://github.com/espressif/esp-usb/pull/272
208+
*/
209+
180210
// Free descriptors
181211
tinyusb_descriptors_free();
212+
213+
#if (CONFIG_IDF_TARGET_ESP32P4)
214+
if (task_ctx->vbus_monitor_cfg.gpio_num != GPIO_NUM_NC) {
215+
// Deinit VBUS monitoring if it was enabled
216+
tinyusb_vbus_monitor_deinit();
217+
}
218+
#endif // CONFIG_IDF_TARGET_ESP32P4
219+
182220
// Stop TinyUSB stack
183221
ESP_RETURN_ON_FALSE(tusb_deinit(task_ctx->rhport), ESP_ERR_NOT_FINISHED, TAG, "Unable to teardown TinyUSB stack");
184222
// Cleanup
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdlib.h>
8+
#include "esp_log.h"
9+
#include "esp_err.h"
10+
#include "tinyusb_vbus_monitor.h"
11+
12+
const static char *TAG = "VBUS mon";
13+
14+
/**
15+
* @brief VBUS monitoring context
16+
*
17+
* Note: This is a single-threaded implementation, so only one instance of VBUS monitoring is supported
18+
*/
19+
typedef struct {
20+
gpio_num_t gpio_num; /*!< GPIO number used for VBUS monitoring */
21+
bool prev_state; /*!< Previous VBUS IO state: true - HIGH, false - LOW */
22+
TimerHandle_t debounce_timer; /*!< Debounce timer handle */
23+
} vbus_monitor_context_t;
24+
25+
// -------------- Public API ------------------
26+
27+
esp_err_t tinyusb_vbus_monitor_init(tinyusb_vbus_monitor_config_t *config)
28+
{
29+
ESP_LOGD(TAG, "Init GPIO%d, debounce delay: %"PRIu32" ms", config->gpio_num, config->debounce_delay_ms);
30+
return ESP_ERR_NOT_SUPPORTED;
31+
}
32+
33+
void tinyusb_vbus_monitor_deinit(void)
34+
{
35+
ESP_LOGD(TAG, "Deinit");
36+
}

0 commit comments

Comments
 (0)