esp32: Use shared/tinyusb integration for S2 and S3 USB.

Uses newer TinyUSB synopsys/dwc2 driver for esp32s2 and esp32s3 rather than
the IDF tinyusb component.  This allows re-use of other tinyusb integration
code and features shared between ports.

Signed-off-by: Andrew Leech <andrew@alelec.net>
diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake
index 7c50896..e59e2dc 100644
--- a/ports/esp32/esp32_common.cmake
+++ b/ports/esp32/esp32_common.cmake
@@ -53,6 +53,37 @@
     ${MICROPY_DIR}/drivers/dht/dht.c
 )
 
+string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb)
+if(MICROPY_PY_TINYUSB)
+    set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src")
+    string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu)
+
+    list(APPEND MICROPY_DEF_TINYUSB
+        CFG_TUSB_MCU=${tusb_mcu}
+    )
+
+    list(APPEND MICROPY_SOURCE_TINYUSB
+        ${TINYUSB_SRC}/tusb.c
+        ${TINYUSB_SRC}/common/tusb_fifo.c
+        ${TINYUSB_SRC}/device/usbd.c
+        ${TINYUSB_SRC}/device/usbd_control.c
+        ${TINYUSB_SRC}/class/cdc/cdc_device.c
+        ${TINYUSB_SRC}/portable/synopsys/dwc2/dcd_dwc2.c
+        ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c
+        ${MICROPY_DIR}/shared/tinyusb/mp_usbd_cdc.c
+        ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c
+    )
+
+    list(APPEND MICROPY_INC_TINYUSB
+        ${TINYUSB_SRC}
+        ${MICROPY_DIR}/shared/tinyusb/
+    )
+
+    list(APPEND MICROPY_LINK_TINYUSB
+        -Wl,--wrap=dcd_event_handler
+    )
+endif()
+
 list(APPEND MICROPY_SOURCE_PORT
     panichandler.c
     adc.c
@@ -100,6 +131,7 @@
     ${MICROPY_SOURCE_LIB}
     ${MICROPY_SOURCE_PORT}
     ${MICROPY_SOURCE_BOARD}
+    ${MICROPY_SOURCE_TINYUSB}
 )
 
 list(APPEND IDF_COMPONENTS
@@ -134,6 +166,7 @@
     soc
     spi_flash
     ulp
+    usb
     vfs
 )
 
@@ -147,9 +180,11 @@
         ${MICROPY_SOURCE_DRIVERS}
         ${MICROPY_SOURCE_PORT}
         ${MICROPY_SOURCE_BOARD}
+        ${MICROPY_SOURCE_TINYUSB}
     INCLUDE_DIRS
         ${MICROPY_INC_CORE}
         ${MICROPY_INC_USERMOD}
+        ${MICROPY_INC_TINYUSB}
         ${MICROPY_PORT_DIR}
         ${MICROPY_BOARD_DIR}
         ${CMAKE_BINARY_DIR}
@@ -171,6 +206,7 @@
 target_compile_definitions(${MICROPY_TARGET} PUBLIC
     ${MICROPY_DEF_CORE}
     ${MICROPY_DEF_BOARD}
+    ${MICROPY_DEF_TINYUSB}
     MICROPY_ESP_IDF_4=1
     MICROPY_VFS_FAT=1
     MICROPY_VFS_LFS2=1
@@ -186,6 +222,10 @@
     -Wno-missing-field-initializers
 )
 
+target_link_options(${MICROPY_TARGET} PUBLIC
+     ${MICROPY_LINK_TINYUSB}
+)
+
 # Additional include directories needed for private NimBLE headers.
 target_include_directories(${MICROPY_TARGET} PUBLIC
     ${IDF_PATH}/components/bt/host/nimble/nimble
diff --git a/ports/esp32/main.c b/ports/esp32/main.c
index ca5a0e3..03dc080 100644
--- a/ports/esp32/main.c
+++ b/ports/esp32/main.c
@@ -52,6 +52,7 @@
 #include "shared/readline/readline.h"
 #include "shared/runtime/pyexec.h"
 #include "shared/timeutils/timeutils.h"
+#include "shared/tinyusb/mp_usbd.h"
 #include "mbedtls/platform_time.h"
 
 #include "uart.h"
@@ -101,7 +102,7 @@
     #endif
     #if MICROPY_HW_ESP_USB_SERIAL_JTAG
     usb_serial_jtag_init();
-    #elif MICROPY_HW_USB_CDC
+    #elif MICROPY_HW_ENABLE_USBDEV
     usb_init();
     #endif
     #if MICROPY_HW_ENABLE_UART_REPL
diff --git a/ports/esp32/main_esp32s2/CMakeLists.txt b/ports/esp32/main_esp32s2/CMakeLists.txt
index 40188ab..bc5ab93 100644
--- a/ports/esp32/main_esp32s2/CMakeLists.txt
+++ b/ports/esp32/main_esp32s2/CMakeLists.txt
@@ -8,4 +8,6 @@
     get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE)
 endif()
 
+set(MICROPY_PY_TINYUSB ON)
+
 include(${MICROPY_PORT_DIR}/esp32_common.cmake)
diff --git a/ports/esp32/main_esp32s3/CMakeLists.txt b/ports/esp32/main_esp32s3/CMakeLists.txt
index 40188ab..bc5ab93 100644
--- a/ports/esp32/main_esp32s3/CMakeLists.txt
+++ b/ports/esp32/main_esp32s3/CMakeLists.txt
@@ -8,4 +8,6 @@
     get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE)
 endif()
 
+set(MICROPY_PY_TINYUSB ON)
+
 include(${MICROPY_PORT_DIR}/esp32_common.cmake)
diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h
index 5dfb164..f1687cf 100644
--- a/ports/esp32/mpconfigport.h
+++ b/ports/esp32/mpconfigport.h
@@ -194,6 +194,69 @@
 
 #define MP_STATE_PORT MP_STATE_VM
 
+#ifndef MICROPY_HW_ENABLE_USBDEV
+#define MICROPY_HW_ENABLE_USBDEV            (SOC_USB_OTG_SUPPORTED)
+#endif
+
+#if MICROPY_HW_ENABLE_USBDEV
+#define MICROPY_SCHEDULER_STATIC_NODES      (1)
+
+#ifndef MICROPY_HW_USB_VID
+#define USB_ESPRESSIF_VID 0x303A
+#if CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID
+#define MICROPY_HW_USB_VID  (USB_ESPRESSIF_VID)
+#else
+#define MICROPY_HW_USB_VID  (CONFIG_TINYUSB_DESC_CUSTOM_VID)
+#endif
+#endif
+
+#ifndef MICROPY_HW_USB_PID
+#if CONFIG_TINYUSB_DESC_USE_DEFAULT_PID
+#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n))
+// A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
+// Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
+// Auto ProductID layout's Bitmap:
+//   [MSB]         HID | MSC | CDC          [LSB]
+#define USB_TUSB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
+    _PID_MAP(MIDI, 3))  // | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) )
+#define MICROPY_HW_USB_PID  (USB_TUSB_PID)
+#else
+#define MICROPY_HW_USB_PID  (CONFIG_TINYUSB_DESC_CUSTOM_PID)
+#endif
+#endif
+
+#ifndef MICROPY_HW_USB_MANUFACTURER_STRING
+#ifdef CONFIG_TINYUSB_DESC_MANUFACTURER_STRING
+#define MICROPY_HW_USB_MANUFACTURER_STRING CONFIG_TINYUSB_DESC_MANUFACTURER_STRING
+#else
+#define MICROPY_HW_USB_MANUFACTURER_STRING "MicroPython"
+#endif
+#endif
+
+#ifndef MICROPY_HW_USB_PRODUCT_FS_STRING
+#ifdef CONFIG_TINYUSB_DESC_PRODUCT_STRING
+#define MICROPY_HW_USB_PRODUCT_FS_STRING CONFIG_TINYUSB_DESC_PRODUCT_STRING
+#else
+#define MICROPY_HW_USB_PRODUCT_FS_STRING "Board in FS mode"
+#endif
+#endif
+
+#endif // MICROPY_HW_ENABLE_USBDEV
+
+// Enable stdio over native USB peripheral CDC via TinyUSB
+#ifndef MICROPY_HW_USB_CDC
+#define MICROPY_HW_USB_CDC                  (MICROPY_HW_ENABLE_USBDEV)
+#endif
+
+// Enable stdio over USB Serial/JTAG peripheral
+#ifndef MICROPY_HW_ESP_USB_SERIAL_JTAG
+#define MICROPY_HW_ESP_USB_SERIAL_JTAG      (SOC_USB_SERIAL_JTAG_SUPPORTED && !MICROPY_HW_USB_CDC)
+#endif
+
+#if MICROPY_HW_USB_CDC && MICROPY_HW_ESP_USB_SERIAL_JTAG
+#error "Invalid build config: Can't enable both native USB and USB Serial/JTAG peripheral"
+#endif
+
 // type definitions for the specific machine
 
 #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p)))
@@ -253,20 +316,6 @@
 // board specifics
 #define MICROPY_PY_SYS_PLATFORM "esp32"
 
-// Enable stdio over native USB peripheral CDC via TinyUSB
-#ifndef MICROPY_HW_USB_CDC
-#define MICROPY_HW_USB_CDC                  (SOC_USB_OTG_SUPPORTED)
-#endif
-
-// Enable stdio over USB Serial/JTAG peripheral
-#ifndef MICROPY_HW_ESP_USB_SERIAL_JTAG
-#define MICROPY_HW_ESP_USB_SERIAL_JTAG      (SOC_USB_SERIAL_JTAG_SUPPORTED && !MICROPY_HW_USB_CDC)
-#endif
-
-#if MICROPY_HW_USB_CDC && MICROPY_HW_ESP_USB_SERIAL_JTAG
-#error "Invalid build config: Can't enable both native USB and USB Serial/JTAG peripheral"
-#endif
-
 // ESP32-S3 extended IO for 47 & 48
 #ifndef MICROPY_HW_ESP32S3_EXTENDED_IO
 #define MICROPY_HW_ESP32S3_EXTENDED_IO      (1)
diff --git a/ports/esp32/mphalport.c b/ports/esp32/mphalport.c
index 7d0154c..3f26740 100644
--- a/ports/esp32/mphalport.c
+++ b/ports/esp32/mphalport.c
@@ -42,6 +42,8 @@
 #include "extmod/misc.h"
 #include "shared/timeutils/timeutils.h"
 #include "shared/runtime/pyexec.h"
+#include "shared/tinyusb/mp_usbd.h"
+#include "shared/tinyusb/mp_usbd_cdc.h"
 #include "mphalport.h"
 #include "usb.h"
 #include "usb_serial_jtag.h"
@@ -106,13 +108,19 @@
     uintptr_t ret = 0;
     #if MICROPY_HW_ESP_USB_SERIAL_JTAG
     usb_serial_jtag_poll_rx();
-    #endif
-    if ((poll_flags & MP_STREAM_POLL_RD) && stdin_ringbuf.iget != stdin_ringbuf.iput) {
+    if ((poll_flags & MP_STREAM_POLL_RD) && ringbuf_peek(&stdin_ringbuf) != -1) {
         ret |= MP_STREAM_POLL_RD;
     }
     if (poll_flags & MP_STREAM_POLL_WR) {
         ret |= MP_STREAM_POLL_WR;
     }
+    #endif
+    #if MICROPY_HW_USB_CDC
+    ret |= mp_usbd_cdc_poll_interfaces(poll_flags);
+    #endif
+    #if MICROPY_PY_OS_DUPTERM
+    ret |= mp_os_dupterm_poll(poll_flags);
+    #endif
     return ret;
 }
 
@@ -121,6 +129,9 @@
         #if MICROPY_HW_ESP_USB_SERIAL_JTAG
         usb_serial_jtag_poll_rx();
         #endif
+        #if MICROPY_HW_USB_CDC
+        mp_usbd_cdc_poll_interfaces(0);
+        #endif
         int c = ringbuf_get(&stdin_ringbuf);
         if (c != -1) {
             return c;
@@ -133,6 +144,7 @@
     // Only release the GIL if many characters are being sent
     mp_uint_t ret = len;
     bool did_write = false;
+    #if MICROPY_HW_ENABLE_UART_REPL || CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED
     bool release_gil = len > MICROPY_PY_STRING_TX_GIL_THRESHOLD;
     #if MICROPY_DEBUG_PRINTERS && MICROPY_DEBUG_VERBOSE && MICROPY_PY_THREAD_GIL
     // If verbose debug output is enabled some strings are printed before the
@@ -146,9 +158,6 @@
     #if MICROPY_HW_ESP_USB_SERIAL_JTAG
     usb_serial_jtag_tx_strn(str, len);
     did_write = true;
-    #elif MICROPY_HW_USB_CDC
-    usb_tx_strn(str, len);
-    did_write = true;
     #endif
     #if MICROPY_HW_ENABLE_UART_REPL
     uart_stdout_tx_strn(str, len);
@@ -157,6 +166,14 @@
     if (release_gil) {
         MP_THREAD_GIL_ENTER();
     }
+    #endif // MICROPY_HW_ENABLE_UART_REPL || CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED
+    #if MICROPY_HW_USB_CDC
+    mp_uint_t cdc_res = mp_usbd_cdc_tx_strn(str, len);
+    if (cdc_res > 0) {
+        did_write = true;
+        ret = MIN(cdc_res, ret);
+    }
+    #endif
     int dupterm_res = mp_os_dupterm_tx_strn(str, len);
     if (dupterm_res >= 0) {
         did_write = true;
diff --git a/ports/esp32/usb.c b/ports/esp32/usb.c
index 4207e77..d643a7b 100644
--- a/ports/esp32/usb.c
+++ b/ports/esp32/usb.c
@@ -29,75 +29,40 @@
 #include "usb.h"
 
 #if MICROPY_HW_USB_CDC
+#include "esp_rom_gpio.h"
+#include "esp_mac.h"
+#include "esp_private/usb_phy.h"
 
-#include "esp_timer.h"
-#ifndef NO_QSTR
-#include "tinyusb.h"
-#include "tusb_cdc_acm.h"
-#endif
+#include "shared/tinyusb/mp_usbd.h"
 
-#define CDC_ITF TINYUSB_CDC_ACM_0
+static usb_phy_handle_t phy_hdl;
 
-static uint8_t usb_rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE];
-
-// This is called from FreeRTOS task "tusb_tsk" in espressif__esp_tinyusb (not an ISR).
-static void usb_callback_rx(int itf, cdcacm_event_t *event) {
-    // espressif__esp_tinyusb places tinyusb rx data onto freertos ringbuffer which
-    // this function forwards onto our stdin_ringbuf.
-    for (;;) {
-        size_t len = 0;
-        esp_err_t ret = tinyusb_cdcacm_read(itf, usb_rx_buf, sizeof(usb_rx_buf), &len);
-        if (ret != ESP_OK) {
-            break;
-        }
-        if (len == 0) {
-            break;
-        }
-        for (size_t i = 0; i < len; ++i) {
-            if (usb_rx_buf[i] == mp_interrupt_char) {
-                mp_sched_keyboard_interrupt();
-            } else {
-                ringbuf_put(&stdin_ringbuf, usb_rx_buf[i]);
-            }
-        }
-        mp_hal_wake_main_task();
-    }
-}
 
 void usb_init(void) {
-    // Initialise the USB with defaults.
-    tinyusb_config_t tusb_cfg = {0};
-    ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
+    // ref: https://github.com/espressif/esp-usb/blob/4b6a798d0bed444fff48147c8dcdbbd038e92892/device/esp_tinyusb/tinyusb.c
 
-    // Initialise the USB serial interface.
-    tinyusb_config_cdcacm_t acm_cfg = {
-        .usb_dev = TINYUSB_USBDEV_0,
-        .cdc_port = CDC_ITF,
-        .rx_unread_buf_sz = 256,
-        .callback_rx = &usb_callback_rx,
-        #ifdef MICROPY_HW_USB_CUSTOM_RX_WANTED_CHAR_CB
-        .callback_rx_wanted_char = &MICROPY_HW_USB_CUSTOM_RX_WANTED_CHAR_CB,
-        #endif
-        #ifdef MICROPY_HW_USB_CUSTOM_LINE_STATE_CB
-        .callback_line_state_changed = (tusb_cdcacm_callback_t)&MICROPY_HW_USB_CUSTOM_LINE_STATE_CB,
-        #endif
-        #ifdef MICROPY_HW_USB_CUSTOM_LINE_CODING_CB
-        .callback_line_coding_changed = &MICROPY_HW_USB_CUSTOM_LINE_CODING_CB,
-        #endif
+    // Configure USB PHY
+    usb_phy_config_t phy_conf = {
+        .controller = USB_PHY_CTRL_OTG,
+        .otg_mode = USB_OTG_MODE_DEVICE,
     };
-    ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg));
+    // Internal USB PHY
+    phy_conf.target = USB_PHY_TARGET_INT;
+
+    // Init ESP USB Phy
+    usb_new_phy(&phy_conf, &phy_hdl);
+
+    // Init MicroPython / TinyUSB
+    mp_usbd_init();
 
 }
 
-void usb_tx_strn(const char *str, size_t len) {
-    // Write out the data to the CDC interface, but only while the USB host is connected.
-    uint64_t timeout = esp_timer_get_time() + (uint64_t)(MICROPY_HW_USB_CDC_TX_TIMEOUT_MS * 1000);
-    while (tud_cdc_n_connected(CDC_ITF) && len && esp_timer_get_time() < timeout) {
-        size_t l = tinyusb_cdcacm_write_queue(CDC_ITF, (uint8_t *)str, len);
-        str += l;
-        len -= l;
-        tud_cdc_n_write_flush(CDC_ITF);
-    }
+void mp_usbd_port_get_serial_number(char *serial_buf) {
+    // use factory default MAC as serial ID
+    uint8_t mac[8];
+    esp_efuse_mac_get_default(mac);
+    MP_STATIC_ASSERT(sizeof(mac) * 2 <= MICROPY_HW_USB_DESC_STR_MAX);
+    mp_usbd_hex_str(serial_buf, mac, sizeof(mac));
 }
 
 #endif // MICROPY_HW_USB_CDC
diff --git a/ports/esp32/usb.h b/ports/esp32/usb.h
index a4c7d40..5e5eea3 100644
--- a/ports/esp32/usb.h
+++ b/ports/esp32/usb.h
@@ -29,6 +29,5 @@
 #define MICROPY_HW_USB_CDC_TX_TIMEOUT_MS (500)
 
 void usb_init(void);
-void usb_tx_strn(const char *str, size_t len);
 
 #endif // MICROPY_INCLUDED_ESP32_USB_H