Skip to content

Commit 078dd26

Browse files
committed
feature(tinyusb): Added VBUS monitor feature
1 parent 64a036e commit 078dd26

File tree

5 files changed

+241
-3
lines changed

5 files changed

+241
-3
lines changed

device/esp_tinyusb/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ set(srcs
33
"tinyusb.c"
44
"usb_descriptors.c"
55
"tinyusb_task.c"
6+
"tinyusb_vbus_monitor.c"
67
)
78

89
set(priv_req "")
@@ -47,7 +48,7 @@ idf_component_register(SRCS ${srcs}
4748
INCLUDE_DIRS "include"
4849
PRIV_INCLUDE_DIRS "include_private"
4950
PRIV_REQUIRES ${priv_req}
50-
REQUIRES fatfs vfs
51+
REQUIRES fatfs vfs esp_timer
5152
)
5253

5354
# Determine whether tinyusb is fetched from component registry or from local path
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+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
/**
17+
* @brief Initialize VBUS monitoring on the specified GPIO
18+
*
19+
* @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).
20+
*
21+
* @return
22+
* - ESP_OK on success
23+
* - ESP_ERR_INVALID_STATE if already initialized
24+
*/
25+
esp_err_t tinyusb_vbus_monitor_init(int vbus_io_num);
26+
27+
/**
28+
* @brief Enable VBUS monitoring
29+
*
30+
* Note: This function should be called after tusb_init() when GOTGCTL register is initialized.
31+
*/
32+
void tinyusb_vbus_monitor_enable(void);
33+
34+
/**
35+
* @brief Deinitialize VBUS monitoring
36+
*
37+
* @param vbus_io_num GPIO number used for VBUS monitoring
38+
*/
39+
void tinyusb_vbus_monitor_deinit(int vbus_io_num);
40+
41+
#ifdef __cplusplus
42+
}
43+
#endif

device/esp_tinyusb/tinyusb.c

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "esp_private/usb_phy.h"
1212
#include "tinyusb.h"
1313
#include "tinyusb_task.h"
14+
#include "tinyusb_vbus_monitor.h"
1415
#include "tusb.h"
1516

1617
#if (CONFIG_TINYUSB_MSC_ENABLED)
@@ -25,6 +26,8 @@ const static char *TAG = "TinyUSB";
2526
*/
2627
typedef struct {
2728
tinyusb_port_t port; /*!< USB Peripheral hardware port number. Available when hardware has several available peripherals. */
29+
bool vbus_gpio_used; /*!< Flag indicating whether VBUS monitoring GPIO is used. */
30+
int vbus_io_num; /*!< GPIO number used for VBUS monitoring, if applicable. */
2831
usb_phy_handle_t phy_hdl; /*!< USB PHY handle */
2932
tinyusb_event_cb_t event_cb; /*!< Callback function that will be called when USB events occur. */
3033
void *event_arg; /*!< Pointer to the argument passed to the callback */
@@ -133,9 +136,23 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
133136
#endif // (SOC_USB_OTG_PERIPH_NUM > 1)
134137

135138
// OTG IOs config
136-
const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io);
139+
s_ctx.vbus_gpio_used = false;
140+
137141
if (config->phy.self_powered) {
138-
phy_conf.otg_io_conf = &otg_io_conf;
142+
143+
if (config->port == TINYUSB_PORT_FULL_SPEED_0) {
144+
// For USB OTG 1.1, we map VBUS monitor io signal to the BVALID while enable PHY
145+
const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io);
146+
phy_conf.otg_io_conf = &otg_io_conf;
147+
}
148+
#if (SOC_USB_OTG_PERIPH_NUM > 1)
149+
else if (config->port == TINYUSB_PORT_HIGH_SPEED_0) {
150+
// For USB OTG 2.0, we use VBUS monitoring GPIO to control BVALID value over GOTGCTL register
151+
ESP_RETURN_ON_ERROR(tinyusb_vbus_monitor_init(config->phy.vbus_monitor_io), TAG, "Init VBUS monitoring failed");
152+
s_ctx.vbus_gpio_used = true;
153+
s_ctx.vbus_io_num = config->phy.vbus_monitor_io;
154+
}
155+
#endif // SOC_USB_OTG_PERIPH_NUM > 1
139156
}
140157
ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed");
141158
}
@@ -160,9 +177,17 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
160177
esp_err_t tinyusb_driver_uninstall(void)
161178
{
162179
ESP_RETURN_ON_ERROR(tinyusb_task_stop(), TAG, "Deinit TinyUSB task failed");
180+
181+
if (s_ctx.vbus_gpio_used) {
182+
tinyusb_vbus_monitor_deinit(s_ctx.vbus_io_num);
183+
s_ctx.vbus_gpio_used = false;
184+
s_ctx.vbus_io_num = 0;
185+
}
186+
163187
if (s_ctx.phy_hdl) {
164188
ESP_RETURN_ON_ERROR(usb_del_phy(s_ctx.phy_hdl), TAG, "Unable to delete PHY");
165189
s_ctx.phy_hdl = NULL;
166190
}
191+
167192
return ESP_OK;
168193
}

device/esp_tinyusb/tinyusb_task.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
#include "esp_log.h"
1212
#include "esp_check.h"
1313
#include "tinyusb.h"
14+
#include "tinyusb_vbus_monitor.h"
1415
#include "sdkconfig.h"
1516
#include "descriptors_control.h"
1617

18+
1719
const static char *TAG = "tinyusb_task";
1820

1921
static portMUX_TYPE tusb_task_lock = portMUX_INITIALIZER_UNLOCKED;
@@ -73,6 +75,9 @@ static void tinyusb_device_task(void *arg)
7375
goto desc_free;
7476
}
7577

78+
// This should be called after tusb_init()
79+
tinyusb_vbus_monitor_enable();
80+
7681
TINYUSB_TASK_ENTER_CRITICAL();
7782
task_ctx->handle = xTaskGetCurrentTaskHandle(); // Save task handle
7883
p_tusb_task_ctx = task_ctx; // Save global task context pointer
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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_check.h"
10+
#include "driver/gpio.h"
11+
#include "esp_timer.h"
12+
13+
#include "tinyusb.h"
14+
#include "sdkconfig.h"
15+
16+
const static char *TAG = "VBUS mon";
17+
18+
#define DEBOUNCE_DELAY_MS 500 /* TODO: make configurable */
19+
20+
#if (CONFIG_IDF_TARGET_ESP32P4)
21+
// On ESP32-P4 OTG signals from USB-DWC are not wired to GPIO matrix
22+
// So we need to override the Bvalid signal from PHY
23+
#define ENABLE_BVALID_OVERRIDE 1
24+
#endif // CONFIG_IDF_TARGET_ESP32P4
25+
26+
#if (ENABLE_BVALID_OVERRIDE)
27+
#include "soc/usb_dwc_struct.h"
28+
#endif // ENABLE_BVALID_OVERRIDE
29+
30+
static esp_timer_handle_t _vbus_debounce_timer = NULL;
31+
32+
static void vbus_io_cb(void *arg)
33+
{
34+
int vbus_io_num = (int) arg;
35+
// disable interrupts for a while to debounce
36+
gpio_intr_disable(vbus_io_num);
37+
// enable debounce timer
38+
esp_timer_start_once(_vbus_debounce_timer, DEBOUNCE_DELAY_MS * 1000);
39+
}
40+
41+
static void vbus_debounce_timer_cb(void *arg)
42+
{
43+
int vbus_io_num = (int) arg;
44+
45+
if (gpio_get_level(vbus_io_num)) {
46+
// If the VBUS is still present
47+
#if (ENABLE_BVALID_OVERRIDE)
48+
USB_DWC_HS.gotgctl_reg.bvalidovval = 1;
49+
#endif // ENABLE_BVALID_OVERRIDE
50+
} else {
51+
// VBUS is not present
52+
#if (ENABLE_BVALID_OVERRIDE)
53+
USB_DWC_HS.gotgctl_reg.bvalidovval = 0;
54+
#endif // ENABLE_BVALID_OVERRIDE
55+
56+
// Note:
57+
// When device stayed connected in the USB Host port, we need to disable the pull-up resistor on D+ D- first
58+
// Disable pull-up resistor on D+ D-
59+
// But this creates a breaking change, so we can call it in the notification callback
60+
}
61+
62+
// Re-enable GPIO interrupt
63+
gpio_intr_enable(vbus_io_num);
64+
}
65+
66+
esp_err_t tinyusb_vbus_monitor_init(int vbus_io_num)
67+
{
68+
esp_err_t ret;
69+
70+
// There could be only one instance of VBUS monitoring
71+
if (_vbus_debounce_timer) {
72+
ESP_LOGE(TAG, "Already initialized");
73+
return ESP_ERR_INVALID_STATE;
74+
}
75+
76+
// VBUS Debounce timer
77+
const esp_timer_create_args_t vbus_timer_args = {
78+
.callback = &vbus_debounce_timer_cb,
79+
.arg = (void *) vbus_io_num,
80+
};
81+
ret = esp_timer_create(&vbus_timer_args, &_vbus_debounce_timer);
82+
if (ret != ESP_OK) {
83+
ESP_LOGE(TAG, "Create VBUS debounce timer failed");
84+
return ret;
85+
}
86+
87+
// Init gpio IRQ for VBUS monitoring
88+
const gpio_config_t vbus_io_cfg = {
89+
.pin_bit_mask = BIT64(vbus_io_num),
90+
.mode = GPIO_MODE_INPUT,
91+
.pull_down_en = GPIO_PULLDOWN_ENABLE,
92+
.intr_type = GPIO_INTR_ANYEDGE,
93+
};
94+
95+
ret = gpio_config(&vbus_io_cfg);
96+
if (ret != ESP_OK) {
97+
ESP_LOGE(TAG, "Config VBUS GPIO failed");
98+
goto gpio_fail;
99+
}
100+
101+
ret = gpio_install_isr_service(ESP_INTR_FLAG_LOWMED);
102+
if (ret != ESP_OK) {
103+
ESP_LOGE(TAG, "Install GPIO ISR service failed");
104+
goto isr_fail;
105+
}
106+
107+
ret = gpio_isr_handler_add(vbus_io_num, vbus_io_cb, (void *) vbus_io_num);
108+
if (ret != ESP_OK) {
109+
ESP_LOGE(TAG, "Add GPIO ISR handler failed");
110+
goto add_isr_hdl_fail;
111+
}
112+
113+
// Device could be already connected, check the status and start the timer if needed
114+
if (gpio_get_level(vbus_io_num)) {
115+
esp_timer_start_once(_vbus_debounce_timer, DEBOUNCE_DELAY_MS * 1000);
116+
}
117+
118+
ESP_LOGD(TAG, "Configured via GPIO%d", vbus_io_num);
119+
return ESP_OK;
120+
121+
add_isr_hdl_fail:
122+
gpio_uninstall_isr_service();
123+
isr_fail:
124+
gpio_reset_pin(vbus_io_num);
125+
gpio_fail:
126+
if (_vbus_debounce_timer) {
127+
esp_timer_delete(_vbus_debounce_timer);
128+
_vbus_debounce_timer = NULL;
129+
}
130+
return ret;
131+
}
132+
133+
void tinyusb_vbus_monitor_enable(void)
134+
{
135+
if (_vbus_debounce_timer == NULL) {
136+
return;
137+
}
138+
#if (ENABLE_BVALID_OVERRIDE)
139+
// Enable to override the signal from PHY
140+
USB_DWC_HS.gotgctl_reg.bvalidoven = 1;
141+
// Wait 1 microsecond (sufficient for >5 PHY clocks)
142+
esp_rom_delay_us(1);
143+
// Set Bvalid signal to 0 initially
144+
USB_DWC_HS.gotgctl_reg.bvalidovval = 0;
145+
#endif // ENABLE_BVALID_OVERRIDE
146+
ESP_LOGD(TAG, "Enabled");
147+
}
148+
149+
void tinyusb_vbus_monitor_deinit(int vbus_io_num)
150+
{
151+
// Deinit gpio IRQ for VBUS monitoring
152+
if (esp_timer_is_active(_vbus_debounce_timer)) {
153+
ESP_ERROR_CHECK(esp_timer_stop(_vbus_debounce_timer));
154+
}
155+
esp_timer_delete(_vbus_debounce_timer);
156+
_vbus_debounce_timer = NULL;
157+
158+
// Deinit gpio IRQ for VBUS monitoring
159+
ESP_ERROR_CHECK(gpio_isr_handler_remove(vbus_io_num));
160+
gpio_intr_disable(vbus_io_num);
161+
gpio_uninstall_isr_service();
162+
163+
ESP_LOGD(TAG, "Deinit");
164+
}

0 commit comments

Comments
 (0)