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
1216const 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
27141esp_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