Skip to content

Commit 85e04ef

Browse files
committed
feature(esp_tinyusb): Added the vbus software monitoring for esp23p4
- added GPIO ISR, debounce timer, debounce logic - added GOTGCTL.BVALID driving on VBUS appear/disappear - added DCTL.SFTDISCON driving on VBUS appear/dissapear
1 parent f439723 commit 85e04ef

File tree

3 files changed

+227
-8
lines changed

3 files changed

+227
-8
lines changed

device/esp_tinyusb/include_private/tinyusb_vbus_monitor.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,23 @@ typedef struct {
2626
* - This function should be called after tusb_init() when GOTGCTL register is initialized
2727
* - This is a single-threaded implementation, so only one instance of VBUS monitoring is supported
2828
*
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).
29+
* @param config Pointer to VBUS monitoring configuration structure
3030
*
3131
* @return
32-
* - ESP_ERR_NOT_SUPPORTED: VBUS monitoring is not supported yet
32+
* - ESP_ERR_INVALID_STATE if VBUS monitoring is already initialized
33+
* - ESP_ERR_NO_MEM if debounce timer creation failed
34+
* - ESP_OK on success
3335
*/
3436
esp_err_t tinyusb_vbus_monitor_init(tinyusb_vbus_monitor_config_t *config);
3537

3638
/**
3739
* @brief Deinitialize VBUS monitoring
40+
*
41+
* @return
42+
* - ESP_ERR_INVALID_STATE if VBUS monitoring is not initialized
43+
* - ESP_OK on success
3844
*/
39-
void tinyusb_vbus_monitor_deinit(void);
45+
esp_err_t tinyusb_vbus_monitor_deinit(void);
4046

4147
#ifdef __cplusplus
4248
}

device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ TEST_CASE("Emulated VBUS USB OTG 2.0, verify attach/detach events callback (via
510510

511511
test_device_wait_event(TINYUSB_EVENT_ATTACHED);
512512

513-
uint8_t dev_mounted = 0; /* TODO: Expect to fail on run. Enable in VBUS monitor part2 */
513+
uint8_t dev_mounted = test_vbus_emulated_via_gotgctl_bvalid(&USB_DWC_HS);
514514

515515
// Cleanup
516516
TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver");
@@ -558,7 +558,7 @@ TEST_CASE("Controlled VBUS USB OTG 2.0, verify attach/detach events callback", "
558558
TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver");
559559
test_device_wait_event(TINYUSB_EVENT_ATTACHED);
560560

561-
uint8_t dev_mounted = 0; /* TODO: Expect to fail on run. Enable in VBUS monitor part2 */
561+
uint8_t dev_mounted = test_vbus_controlled_by_gpio();
562562

563563
// Cleanup
564564
TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver");
@@ -606,7 +606,7 @@ TEST_CASE("Real VBUS USB OTG 2.0, verify attach/detach events callback (requires
606606
TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver");
607607
test_device_wait_event(TINYUSB_EVENT_ATTACHED);
608608

609-
uint8_t dev_mounted = 0; /* TODO: Expect to fail on run. Enable in VBUS monitor part2 */
609+
uint8_t dev_mounted = test_vbus_manual_attach_detach();
610610

611611
// Cleanup
612612
TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver");

device/esp_tinyusb/tinyusb_vbus_monitor.c

Lines changed: 215 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,21 @@
77
#include <stdlib.h>
88
#include "esp_log.h"
99
#include "esp_err.h"
10+
#include "esp_check.h"
11+
#include "freertos/FreeRTOS.h"
12+
#include "freertos/timers.h"
13+
#include "driver/gpio.h"
1014
#include "tinyusb_vbus_monitor.h"
1115

1216
const static char *TAG = "VBUS mon";
1317

18+
#if (CONFIG_IDF_TARGET_ESP32P4)
19+
#include "soc/usb_dwc_struct.h"
20+
// On ESP32-P4 USB OTG 2.0 signals are not wired to GPIO matrix
21+
// So we need to override the Bvalid signal from PHY
22+
#define USB_DWC_REG USB_DWC_HS
23+
#endif // CONFIG_IDF_TARGET_ESP32P4
24+
1425
/**
1526
* @brief VBUS monitoring context
1627
*
@@ -22,15 +33,217 @@ typedef struct {
2233
TimerHandle_t debounce_timer; /*!< Debounce timer handle */
2334
} vbus_monitor_context_t;
2435

36+
static vbus_monitor_context_t _vbus_ctx = {
37+
.gpio_num = GPIO_NUM_NC,
38+
.prev_state = false,
39+
.debounce_timer = NULL,
40+
};
41+
42+
//
43+
// Additional low-level USB DWC functions, which are not present in the IDF USB DWC HAL
44+
//
45+
46+
// --------------- GOTGCTL register ------------------
47+
48+
static void usb_dwc_ll_gotgctl_set_bvalid_override_value(usb_dwc_dev_t *hw, uint8_t value)
49+
{
50+
hw->gotgctl_reg.bvalidovval = value;
51+
}
52+
53+
static void usb_dwc_ll_gotgctl_enable_bvalid_override(usb_dwc_dev_t *hw, bool enable)
54+
{
55+
hw->gotgctl_reg.bvalidoven = enable ? 1 : 0;
56+
}
57+
58+
// ------------------ DCTL register --------------------
59+
60+
static void usb_dwc_ll_dctl_set_soft_disconnect(usb_dwc_dev_t *hw, bool enable)
61+
{
62+
hw->dctl_reg.sftdiscon = enable ? 1 : 0;
63+
}
64+
65+
// -------------- VBUS Internal Logic ------------------
66+
67+
/**
68+
* @brief Handle VBUS appeared event
69+
*/
70+
static void vbus_appeared(void)
71+
{
72+
ESP_LOGD(TAG, "Appeared");
73+
usb_dwc_ll_gotgctl_set_bvalid_override_value(&USB_DWC_REG, 1);
74+
usb_dwc_ll_dctl_set_soft_disconnect(&USB_DWC_REG, false);
75+
}
76+
77+
/**
78+
* @brief Handle VBUS disappeared event
79+
*/
80+
static void vbus_disappeared(void)
81+
{
82+
ESP_LOGD(TAG, "Disappeared");
83+
usb_dwc_ll_gotgctl_set_bvalid_override_value(&USB_DWC_REG, 0);
84+
usb_dwc_ll_dctl_set_soft_disconnect(&USB_DWC_REG, true);
85+
}
86+
87+
/**
88+
* @brief GPIO interrupt handler for VBUS monitoring io
89+
*
90+
* @param arg GPIO number
91+
*/
92+
static void vbus_io_cb(void *arg)
93+
{
94+
(void) arg;
95+
// disable interrupts for a while to debounce
96+
gpio_intr_disable(_vbus_ctx.gpio_num);
97+
98+
bool vbus_curr_state = gpio_get_level(_vbus_ctx.gpio_num);
99+
100+
if (_vbus_ctx.prev_state != vbus_curr_state) {
101+
_vbus_ctx.prev_state = vbus_curr_state;
102+
// VBUS pin state has changed, start the debounce timer
103+
BaseType_t yield = pdFALSE;
104+
if (xTimerStartFromISR(_vbus_ctx.debounce_timer, &yield) != pdPASS) {
105+
ESP_EARLY_LOGE(TAG, "Failed to start VBUS debounce timer");
106+
}
107+
if (yield == pdTRUE) {
108+
portYIELD_FROM_ISR();
109+
}
110+
} else {
111+
// VBUS gpio glitch, ignore and re-enable interrupt
112+
gpio_intr_enable(_vbus_ctx.gpio_num);
113+
}
114+
}
115+
116+
/**
117+
* @brief VBUS debounce timer callback
118+
*
119+
* @param xTimer Timer handle
120+
*/
121+
static void vbus_debounce_timer_cb(TimerHandle_t xTimer)
122+
{
123+
bool vbus_prev_state = _vbus_ctx.prev_state;
124+
bool vbus_curr_state = gpio_get_level(_vbus_ctx.gpio_num);
125+
126+
if (vbus_curr_state && vbus_prev_state) {
127+
vbus_appeared();
128+
} else if (!vbus_curr_state && !vbus_prev_state) {
129+
vbus_disappeared();
130+
} else {
131+
// State changed again during debounce period, ignore
132+
}
133+
// Update the state
134+
_vbus_ctx.prev_state = vbus_curr_state;
135+
// Re-enable GPIO interrupt
136+
gpio_intr_enable(_vbus_ctx.gpio_num);
137+
}
138+
25139
// -------------- Public API ------------------
26140

27141
esp_err_t tinyusb_vbus_monitor_init(tinyusb_vbus_monitor_config_t *config)
28142
{
143+
esp_err_t ret;
144+
145+
// There could be only one instance of VBUS monitoring
146+
if (_vbus_ctx.debounce_timer != NULL) {
147+
ESP_LOGE(TAG, "Already initialized");
148+
return ESP_ERR_INVALID_STATE;
149+
}
150+
151+
_vbus_ctx.gpio_num = config->gpio_num;
152+
_vbus_ctx.prev_state = false;
153+
154+
// VBUS Debounce timer
155+
_vbus_ctx.debounce_timer = xTimerCreate("vbus_debounce_timer",
156+
pdMS_TO_TICKS(config->debounce_delay_ms),
157+
pdFALSE,
158+
NULL,
159+
vbus_debounce_timer_cb);
160+
161+
if (_vbus_ctx.debounce_timer == NULL) {
162+
ESP_LOGE(TAG, "Create VBUS debounce timer failed");
163+
return ESP_ERR_NO_MEM;
164+
}
165+
166+
// Init gpio IRQ for VBUS monitoring
167+
const gpio_config_t vbus_io_cfg = {
168+
.pin_bit_mask = BIT64(_vbus_ctx.gpio_num),
169+
.mode = GPIO_MODE_INPUT,
170+
.pull_down_en = GPIO_PULLDOWN_ENABLE,
171+
.intr_type = GPIO_INTR_ANYEDGE,
172+
};
173+
174+
ret = gpio_config(&vbus_io_cfg);
175+
if (ret != ESP_OK) {
176+
ESP_LOGE(TAG, "Config VBUS GPIO failed");
177+
goto gpio_fail;
178+
}
179+
180+
ret = gpio_install_isr_service(ESP_INTR_FLAG_LOWMED);
181+
// Service could be already installed, it is not an error
182+
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
183+
ESP_LOGE(TAG, "Install GPIO ISR service failed");
184+
goto isr_fail;
185+
}
186+
187+
ret = gpio_isr_handler_add(_vbus_ctx.gpio_num, vbus_io_cb, (void *) NULL);
188+
if (ret != ESP_OK) {
189+
ESP_LOGE(TAG, "Add GPIO ISR handler failed");
190+
goto add_isr_hdl_fail;
191+
}
192+
// Disable GPIO interrupt
193+
gpio_intr_disable(_vbus_ctx.gpio_num);
194+
// Set initial Bvalid override value and enable override
195+
usb_dwc_ll_gotgctl_set_bvalid_override_value(&USB_DWC_REG, 0);
196+
// Wait 1 microsecond (sufficient for >5 PHY clocks)
197+
esp_rom_delay_us(1);
198+
// Enable to override the signal from PHY
199+
usb_dwc_ll_gotgctl_enable_bvalid_override(&USB_DWC_REG, true);
200+
201+
// Device could be already connected, check the status and start the timer if needed
202+
if (gpio_get_level(_vbus_ctx.gpio_num)) {
203+
_vbus_ctx.prev_state = true;
204+
// Start debounce timer
205+
if (xTimerStart(_vbus_ctx.debounce_timer, 0) != pdPASS) {
206+
ESP_LOGE(TAG, "Failed to start VBUS debounce timer");
207+
}
208+
} else {
209+
// Enable GPIO interrupt
210+
gpio_intr_enable(_vbus_ctx.gpio_num);
211+
}
212+
29213
ESP_LOGD(TAG, "Init GPIO%d, debounce delay: %"PRIu32" ms", config->gpio_num, config->debounce_delay_ms);
30-
return ESP_ERR_NOT_SUPPORTED;
214+
return ESP_OK;
215+
216+
add_isr_hdl_fail:
217+
gpio_uninstall_isr_service();
218+
isr_fail:
219+
gpio_reset_pin(_vbus_ctx.gpio_num);
220+
gpio_fail:
221+
if (_vbus_ctx.debounce_timer) {
222+
xTimerDelete(_vbus_ctx.debounce_timer, 0);
223+
_vbus_ctx.debounce_timer = NULL;
224+
}
225+
return ret;
31226
}
32227

33-
void tinyusb_vbus_monitor_deinit(void)
228+
esp_err_t tinyusb_vbus_monitor_deinit(void)
34229
{
230+
if (_vbus_ctx.debounce_timer == NULL) {
231+
return ESP_ERR_INVALID_STATE;
232+
}
233+
// Deinit gpio IRQ for VBUS monitoring
234+
if (xTimerIsTimerActive(_vbus_ctx.debounce_timer) == pdTRUE) {
235+
xTimerStop(_vbus_ctx.debounce_timer, 0);
236+
}
237+
xTimerDelete(_vbus_ctx.debounce_timer, 0);
238+
_vbus_ctx.debounce_timer = NULL;
239+
240+
// Disable to override the signal from PHY
241+
usb_dwc_ll_gotgctl_enable_bvalid_override(&USB_DWC_REG, false);
242+
// Deinit gpio IRQ for VBUS monitoring
243+
ESP_ERROR_CHECK(gpio_isr_handler_remove(_vbus_ctx.gpio_num));
244+
gpio_intr_disable(_vbus_ctx.gpio_num);
245+
gpio_uninstall_isr_service();
246+
35247
ESP_LOGD(TAG, "Deinit");
248+
return ESP_OK;
36249
}

0 commit comments

Comments
 (0)