88#include "esp_log.h"
99#include "esp_err.h"
1010#include "esp_check.h"
11+ #include "freertos/FreeRTOS.h"
12+ #include "freertos/timers.h"
13+ #include "driver/gpio.h"
1114#include "tinyusb_vbus_monitor.h"
1215
1316const static char * TAG = "VBUS mon" ;
1417
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+
1525/**
1626 * @brief VBUS monitoring context
1727 *
@@ -23,16 +33,213 @@ typedef struct {
2333 TimerHandle_t debounce_timer ; /*!< Debounce timer handle */
2434} vbus_monitor_context_t ;
2535
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+
26139// -------------- Public API ------------------
27140
28141esp_err_t tinyusb_vbus_monitor_init (tinyusb_vbus_monitor_config_t * config )
29142{
30143 ESP_RETURN_ON_FALSE (config != NULL , ESP_ERR_INVALID_ARG , TAG , "Invalid argument: config is NULL" );
144+
145+ esp_err_t ret ;
146+ // There could be only one instance of VBUS monitoring
147+ if (_vbus_ctx .debounce_timer != NULL ) {
148+ ESP_LOGE (TAG , "Already initialized" );
149+ return ESP_ERR_INVALID_STATE ;
150+ }
151+
152+ _vbus_ctx .gpio_num = config -> gpio_num ;
153+ _vbus_ctx .prev_state = false;
154+
155+ // VBUS Debounce timer
156+ _vbus_ctx .debounce_timer = xTimerCreate ("vbus_debounce_timer" ,
157+ pdMS_TO_TICKS (config -> debounce_delay_ms ),
158+ pdFALSE ,
159+ NULL ,
160+ vbus_debounce_timer_cb );
161+
162+ if (_vbus_ctx .debounce_timer == NULL ) {
163+ ESP_LOGE (TAG , "Create VBUS debounce timer failed" );
164+ return ESP_ERR_NO_MEM ;
165+ }
166+
167+ // Init gpio IRQ for VBUS monitoring
168+ const gpio_config_t vbus_io_cfg = {
169+ .pin_bit_mask = BIT64 (_vbus_ctx .gpio_num ),
170+ .mode = GPIO_MODE_INPUT ,
171+ .pull_down_en = GPIO_PULLDOWN_ENABLE ,
172+ .intr_type = GPIO_INTR_ANYEDGE ,
173+ };
174+
175+ ret = gpio_config (& vbus_io_cfg );
176+ if (ret != ESP_OK ) {
177+ ESP_LOGE (TAG , "Config VBUS GPIO failed" );
178+ goto gpio_fail ;
179+ }
180+
181+ ret = gpio_isr_handler_add (_vbus_ctx .gpio_num , vbus_io_cb , (void * ) NULL );
182+ if (ret != ESP_OK ) {
183+ ESP_LOGE (TAG , "Add GPIO ISR handler failed" );
184+ goto isr_fail ;
185+ }
186+ // Disable GPIO interrupt
187+ gpio_intr_disable (_vbus_ctx .gpio_num );
188+ // Set initial Bvalid override value and enable override
189+ usb_dwc_ll_gotgctl_set_bvalid_override_value (& USB_DWC_REG , 0 );
190+ // Wait 1 microsecond (sufficient for >5 PHY clocks)
191+ esp_rom_delay_us (1 );
192+ // Enable to override the signal from PHY
193+ usb_dwc_ll_gotgctl_enable_bvalid_override (& USB_DWC_REG , true);
194+
195+ // Device could be already connected, check the status and start the timer if needed
196+ if (gpio_get_level (_vbus_ctx .gpio_num )) {
197+ _vbus_ctx .prev_state = true;
198+ // Start debounce timer
199+ if (xTimerStart (_vbus_ctx .debounce_timer , 0 ) != pdPASS ) {
200+ ESP_LOGE (TAG , "Failed to start VBUS debounce timer" );
201+ }
202+ } else {
203+ // Enable GPIO interrupt
204+ gpio_intr_enable (_vbus_ctx .gpio_num );
205+ }
206+
31207 ESP_LOGD (TAG , "Init GPIO%d, debounce delay: %" PRIu32 " ms" , config -> gpio_num , config -> debounce_delay_ms );
32- return ESP_OK ; // Return success to unblock the usb_host_msc test, where the vbus monitor is used
208+ return ESP_OK ;
209+
210+ isr_fail :
211+ gpio_reset_pin (_vbus_ctx .gpio_num );
212+ gpio_fail :
213+ if (_vbus_ctx .debounce_timer ) {
214+ xTimerDelete (_vbus_ctx .debounce_timer , 0 );
215+ _vbus_ctx .debounce_timer = NULL ;
216+ }
217+ return ret ;
33218}
34219
35- void tinyusb_vbus_monitor_deinit (void )
220+ esp_err_t tinyusb_vbus_monitor_deinit (void )
36221{
222+ if (_vbus_ctx .debounce_timer == NULL ) {
223+ return ESP_ERR_INVALID_STATE ;
224+ }
225+ // Disable interrupts for VBUS monitoring io
226+ gpio_intr_disable (_vbus_ctx .gpio_num );
227+ // Deinit gpio IRQ for VBUS monitoring
228+ if (xTimerIsTimerActive (_vbus_ctx .debounce_timer ) == pdTRUE ) {
229+ xTimerStop (_vbus_ctx .debounce_timer , 0 );
230+ }
231+ xTimerDelete (_vbus_ctx .debounce_timer , 0 );
232+ _vbus_ctx .debounce_timer = NULL ;
233+
234+ // Disable to override the signal from PHY
235+ usb_dwc_ll_gotgctl_enable_bvalid_override (& USB_DWC_REG , false);
236+ // Deinit gpio IRQ for VBUS monitoring
237+ esp_err_t ret = gpio_isr_handler_remove (_vbus_ctx .gpio_num );
238+ if (ret != ESP_OK ) {
239+ ESP_LOGE (TAG , "Remove GPIO ISR handler failed" );
240+ return ret ;
241+ }
242+
37243 ESP_LOGD (TAG , "Deinit" );
244+ return ESP_OK ;
38245}
0 commit comments