diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml
index 49afcdc29..b5948811b 100644
--- a/.github/workflows/cron.yml
+++ b/.github/workflows/cron.yml
@@ -13,6 +13,10 @@ on:
 #             * * * * *
     - cron:  '0 */6 * * *'
 
+defaults:
+  run:
+    shell: bash
+
 jobs:
   run:
     name: Build with IDF ${{ matrix.idf_branch }}
@@ -22,7 +26,7 @@ jobs:
       matrix:
         idf_branch: [release/v5.1, release/v4.4] #, release/v3.3]
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v3
     - name: Install dependencies
       run: bash ./tools/prepare-ci.sh
     - name: Build
@@ -31,9 +35,101 @@ jobs:
         GIT_AUTHOR_EMAIL: ${{ secrets.PUSH_EMAIL }}
         GIT_COMMITTER_EMAIL: ${{ secrets.PUSH_EMAIL }}
         IDF_BRANCH: ${{ matrix.idf_branch }}
-      run: bash ./tools/cron.sh
+      run: |
+        git checkout ${{ matrix.idf_branch }}
+        bash ./tools/cron.sh
     - name: Upload archive
-      uses: actions/upload-artifact@v1
+      uses: actions/upload-artifact@v3
       with:
         name: artifacts
         path: dist
+
+
+  # check:
+  #   name: Check if result should be deployed
+  #   runs-on: ubuntu-latest
+  #   strategy:
+  #     matrix:
+  #       branch: [release/v5.1, release/v4.4] #, release/v3.3]
+  #   outputs:
+  #     idf_branch: ${{ steps.check.outputs.idf_branch }}
+  #     idf_commit: ${{ steps.check.outputs.idf_commit }}
+  #     ar_branch: ${{ steps.check.outputs.ar_branch }}
+  #     ar_new_commit_message: ${{ steps.check.outputs.ar_new_commit_message }}
+  #     ar_new_branch_name: ${{ steps.check.outputs.ar_new_branch_name }}
+  #     ar_new_pr_title: ${{ steps.check.outputs.ar_new_pr_title }}
+  #     ar_has_commit: ${{ steps.check.outputs.ar_has_commit }}
+  #     ar_has_branch: ${{ steps.check.outputs.ar_has_branch }}
+  #     ar_has_pr: ${{ steps.check.outputs.ar_has_pr }}
+  #     libs_version: ${{ steps.check.outputs.libs_version }}
+  #     libs_has_commit: ${{ steps.check.outputs.libs_has_commit }}
+  #     libs_has_branch: ${{ steps.check.outputs.libs_has_branch }}
+  #   steps:
+  #   - uses: actions/checkout@v3
+  #   - id: check
+  #     env:
+  #       GITHUB_TOKEN: ${{ secrets.PUSH_TOKEN }}
+  #       GIT_AUTHOR_EMAIL: ${{ secrets.PUSH_EMAIL }}
+  #       GIT_COMMITTER_EMAIL: ${{ secrets.PUSH_EMAIL }}
+  #       IDF_BRANCH: ${{ matrix.idf_branch }}
+  #     run: bash ./tools/check-deploy-needed.sh
+
+  # build:
+  #   name: Build Libs for ${{ matrix.target }}
+  #   runs-on: ubuntu-latest
+  #   needs: check
+  #   if: needs.check.outputs.libs_has_commit == '0' || needs.check.outputs.ar_has_commit == '0'
+  #   strategy:
+  #     matrix:
+  #       target: [esp32, esp32s2, esp32s3, esp32c3, esp32c6, esp32h2]
+  #     fail-fast: false
+  #   steps:
+  #   - uses: actions/checkout@v3
+  #   # - name: Install dependencies
+  #   #   run: bash ./tools/prepare-ci.sh
+  #   - shell: bash
+  #     name: Build Libs for ${{ matrix.target }}
+  #     run: echo ${{ matrix.target }}
+  #   #   run: bash ./build.sh -t ${{ matrix.target }}
+  #   # - name: Upload archive
+  #   #   uses: actions/upload-artifact@v3
+  #   #   with:
+  #   #     name: artifacts
+  #   #     path: dist
+
+  # deploy:
+  #   name: Deploy build
+  #   runs-on: ubuntu-latest
+  #   needs: [check, build]
+  #   steps:
+  #   - uses: actions/checkout@v3
+  #   - shell: bash
+  #     env:
+  #       GITHUB_TOKEN: ${{ secrets.PUSH_TOKEN }}
+  #       GIT_AUTHOR_EMAIL: ${{ secrets.PUSH_EMAIL }}
+  #       GIT_COMMITTER_EMAIL: ${{ secrets.PUSH_EMAIL }}
+  #       IDF_BRANCH: ${{ needs.check.outputs.idf_branch }}
+  #       IDF_COMMIT: ${{ needs.check.outputs.idf_commit }}
+  #       AR_BRANCH: ${{ needs.check.outputs.ar_branch }}
+  #       AR_NEW_COMMIT_MESSAGE: ${{ needs.check.outputs.ar_new_commit_message }}
+  #       AR_NEW_BRANCH_NAME: ${{ needs.check.outputs.ar_new_branch_name }}
+  #       AR_NEW_PR_TITLE: ${{ needs.check.outputs.ar_new_pr_title }}
+  #       AR_HAS_COMMIT: ${{ needs.check.outputs.ar_has_commit }}
+  #       AR_HAS_BRANCH: ${{ needs.check.outputs.ar_has_branch }}
+  #       AR_HAS_PR: ${{ needs.check.outputs.ar_has_pr }}
+  #       LIBS_VERSION: ${{ needs.check.outputs.libs_version }}
+  #       LIBS_HAS_COMMIT: ${{ needs.check.outputs.libs_has_commit }}
+  #       LIBS_HAS_BRANCH: ${{ needs.check.outputs.libs_has_branch }}
+  #     run: |
+  #       echo "IDF_COMMIT: $IDF_COMMIT"
+  #       echo "AR_BRANCH: $AR_BRANCH"
+  #       echo "AR_NEW_COMMIT_MESSAGE: $AR_NEW_COMMIT_MESSAGE"
+  #       echo "AR_NEW_BRANCH_NAME: $AR_NEW_BRANCH_NAME"
+  #       echo "AR_NEW_PR_TITLE: $AR_NEW_PR_TITLE"
+  #       echo "AR_HAS_COMMIT: $AR_HAS_COMMIT"
+  #       echo "AR_HAS_BRANCH: $AR_HAS_BRANCH"
+  #       echo "AR_HAS_PR: $AR_HAS_PR"
+  #       echo "LIBS_VERSION: $LIBS_VERSION"
+  #       echo "LIBS_HAS_COMMIT: $LIBS_HAS_COMMIT"
+  #       echo "LIBS_HAS_BRANCH: $LIBS_HAS_BRANCH"
+
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index b461e3b17..56cc7e9f7 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -11,21 +11,50 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
+
   build-libs:
     name: Build Libs for ${{ matrix.target }}
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        target: [esp32, esp32s2, esp32s3, esp32c3]
+        target: [esp32, esp32s2, esp32s3, esp32c3, esp32c6, esp32h2]
       fail-fast: false
     steps:
     - uses: actions/checkout@v3
     - name: Install dependencies
       run: bash ./tools/prepare-ci.sh
     - name: Build Libs for ${{ matrix.target }}
-      run: bash ./build.sh -t ${{ matrix.target }}
+      run: bash ./build.sh -e -t ${{ matrix.target }}
     - name: Upload archive
-      uses: actions/upload-artifact@v1
+      uses: actions/upload-artifact@v3
       with:
-        name: artifacts-${{ matrix.target }}
+        name: artifacts
         path: dist
+
+  combine-artifacts:
+    name: Combine artifacts
+    needs: build-libs
+    runs-on: ubuntu-latest
+    steps:
+      - name: Download artifacts
+        uses: actions/download-artifact@v3
+        with:
+          name: artifacts
+          path: dist
+      - shell: bash
+        run: |
+          mkdir -p out
+          find dist -name 'arduino-esp32-libs-esp*.tar.gz' -exec tar zxvf {} -C out \;
+          cd out/tools/esp32-arduino-libs && tar zcf ../../../dist/esp32-arduino-libs.tar.gz * && cd ../../..
+          cp out/package_esp32_index.template.json dist/package_esp32_index.template.json
+      - name: Upload full esp32-arduino-libs archive
+        uses: actions/upload-artifact@v3
+        with:
+          name: esp32-arduino-libs
+          path: dist/esp32-arduino-libs.tar.gz
+      - name: Upload package_esp32_index.template.json
+        uses: actions/upload-artifact@v3
+        with:
+          name: package-esp32-index-json
+          path: dist/package_esp32_index.template.json
+
diff --git a/.github/workflows/repository_dispatch.yml b/.github/workflows/repository_dispatch.yml
index 016b84831..678deb4e8 100644
--- a/.github/workflows/repository_dispatch.yml
+++ b/.github/workflows/repository_dispatch.yml
@@ -7,7 +7,7 @@ jobs:
     name: Dispatch Event
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v3
     - name: Install dependencies
       run: bash ./tools/prepare-ci.sh
     - name: Handle Event
@@ -17,7 +17,7 @@ jobs:
         GIT_COMMITTER_EMAIL: ${{ secrets.PUSH_EMAIL }}
       run: bash ./tools/repository_dispatch.sh
     - name: Upload archive
-      uses: actions/upload-artifact@v1
+      uses: actions/upload-artifact@v3
       with:
         name: artifacts
         path: dist
diff --git a/.gitignore b/.gitignore
index 4cd4f7804..b0749543f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ components/esp-rainmaker/
 components/espressif__esp-dsp/
 components/esp-insights/
 components/arduino_tinyusb/tinyusb/
+components/tflite-micro/
 esp-idf/
 out/
 build/
@@ -19,3 +20,5 @@ sdkconfig
 sdkconfig.old
 version.txt
 dependencies.lock
+managed_components/
+target/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 558857ce5..f0d4f40d3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,10 @@
 cmake_minimum_required(VERSION 3.5)
 
 set(RMAKER_PATH ${CMAKE_SOURCE_DIR}/components/esp-rainmaker)
-set(EXTRA_COMPONENT_DIRS ${RMAKER_PATH}/components/esp-insights/components ${RMAKER_PATH}/components ${CMAKE_SOURCE_DIR}/components/esp-insights/components)
+set(INSIGHTS_PATH ${RMAKER_PATH}/components/esp-insights)
+set(TFLITE_PATH ${CMAKE_SOURCE_DIR}/components/tflite-micro)
+
+set(EXTRA_COMPONENT_DIRS ${INSIGHTS_PATH}/components ${RMAKER_PATH}/components ${TFLITE_PATH}/components)
 
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
 project(arduino-lib-builder)
@@ -13,7 +16,7 @@ idf_build_get_property(elf EXECUTABLE GENERATOR_EXPRESSION)
 add_custom_command(
 	OUTPUT "idf_libs" 
 	COMMAND ${CMAKE_SOURCE_DIR}/tools/copy-libs.sh ${IDF_TARGET} "${CONFIG_LIB_BUILDER_FLASHMODE}" "${CONFIG_SPIRAM_MODE_OCT}" "${CONFIG_IDF_TARGET_ARCH_XTENSA}"
-	DEPENDS ${elf} 
+	DEPENDS all 
 	WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 
 	VERBATIM
 )
diff --git a/build.sh b/build.sh
index 7e4b1c190..5987b78b3 100755
--- a/build.sh
+++ b/build.sh
@@ -14,14 +14,18 @@ TARGET="all"
 BUILD_TYPE="all"
 SKIP_ENV=0
 COPY_OUT=0
-DEPLOY_OUT=0
+ARCHIVE_OUT=0
+if [ -z $DEPLOY_OUT ]; then
+    DEPLOY_OUT=0
+fi
 
 function print_help() {
-    echo "Usage: build.sh [-s] [-A <arduino_branch>] [-I <idf_branch>] [-i <idf_commit>] [-c <path>] [-t <target>] [-b <build|menuconfig|idf_libs|copy_bootloader|mem_variant>] [config ...]"
+    echo "Usage: build.sh [-s] [-A <arduino_branch>] [-I <idf_branch>] [-i <idf_commit>] [-c <path>] [-t <target>] [-b <build|menuconfig|reconfigure|idf_libs|copy_bootloader|mem_variant>] [config ...]"
     echo "       -s     Skip installing/updating of ESP-IDF and all components"
     echo "       -A     Set which branch of arduino-esp32 to be used for compilation"
     echo "       -I     Set which branch of ESP-IDF to be used for compilation"
     echo "       -i     Set which commit of ESP-IDF to be used for compilation"
+    echo "       -e     Archive the build to dist"
     echo "       -d     Deploy the build to github arduino-esp32"
     echo "       -c     Set the arduino-esp32 folder to copy the result to. ex. '$HOME/Arduino/hardware/espressif/esp32'"
     echo "       -t     Set the build target(chip). ex. 'esp32s3'"
@@ -30,7 +34,7 @@ function print_help() {
     exit 1
 }
 
-while getopts ":A:I:i:c:t:b:sd" opt; do
+while getopts ":A:I:i:c:t:b:sde" opt; do
     case ${opt} in
         s )
             SKIP_ENV=1
@@ -38,6 +42,9 @@ while getopts ":A:I:i:c:t:b:sd" opt; do
         d )
             DEPLOY_OUT=1
             ;;
+        e )
+            ARCHIVE_OUT=1
+            ;;
         c )
             export ESP32_ARDUINO="$OPTARG"
             COPY_OUT=1
@@ -58,6 +65,7 @@ while getopts ":A:I:i:c:t:b:sd" opt; do
             b=$OPTARG
             if [ "$b" != "build" ] && 
                [ "$b" != "menuconfig" ] && 
+               [ "$b" != "reconfigure" ] && 
                [ "$b" != "idf_libs" ] && 
                [ "$b" != "copy_bootloader" ] && 
                [ "$b" != "mem_variant" ]; then
@@ -78,19 +86,31 @@ done
 shift $((OPTIND -1))
 CONFIGS=$@
 
+mkdir -p dist
+
 if [ $SKIP_ENV -eq 0 ]; then
     echo "* Installing/Updating ESP-IDF and all components..."
     # update components from git
     ./tools/update-components.sh
     if [ $? -ne 0 ]; then exit 1; fi
 
+    # install arduino component
+    ./tools/install-arduino.sh
+    if [ $? -ne 0 ]; then exit 1; fi
+
     # install esp-idf
     source ./tools/install-esp-idf.sh
     if [ $? -ne 0 ]; then exit 1; fi
 else
+    # $IDF_PATH/install.sh
+    # source $IDF_PATH/export.sh
     source ./tools/config.sh
 fi
 
+if [ -f "./managed_components/espressif__esp-sr/.component_hash" ]; then
+    rm -rf ./managed_components/espressif__esp-sr/.component_hash
+fi
+
 if [ "$BUILD_TYPE" != "all" ]; then
     if [ "$TARGET" = "all" ]; then
         echo "ERROR: You need to specify target for non-default builds"
@@ -123,17 +143,17 @@ fi
 rm -rf build sdkconfig out
 
 # Add components version info
-mkdir -p "$AR_TOOLS/sdk" && rm -rf version.txt && rm -rf "$AR_TOOLS/sdk/versions.txt"
+mkdir -p "$AR_TOOLS/esp32-arduino-libs" && rm -rf version.txt && rm -rf "$AR_TOOLS/esp32-arduino-libs/versions.txt"
 component_version="esp-idf: "$(git -C "$IDF_PATH" symbolic-ref --short HEAD || git -C "$IDF_PATH" tag --points-at HEAD)" "$(git -C "$IDF_PATH" rev-parse --short HEAD)
-echo $component_version >> version.txt && echo $component_version >> "$AR_TOOLS/sdk/versions.txt"
+echo $component_version >> version.txt && echo $component_version >> "$AR_TOOLS/esp32-arduino-libs/versions.txt"
 for component in `ls "$AR_COMPS"`; do
     if [ -d "$AR_COMPS/$component/.git" ] || [ -d "$AR_COMPS/$component/.github" ]; then
         component_version="$component: "$(git -C "$AR_COMPS/$component" symbolic-ref --short HEAD || git -C "$AR_COMPS/$component" tag --points-at HEAD)" "$(git -C "$AR_COMPS/$component" rev-parse --short HEAD)
-        echo $component_version >> version.txt && echo $component_version >> "$AR_TOOLS/sdk/versions.txt"
+        echo $component_version >> version.txt && echo $component_version >> "$AR_TOOLS/esp32-arduino-libs/versions.txt"
     fi
 done
 component_version="tinyusb: "$(git -C "$AR_COMPS/arduino_tinyusb/tinyusb" symbolic-ref --short HEAD || git -C "$AR_COMPS/arduino_tinyusb/tinyusb" tag --points-at HEAD)" "$(git -C "$AR_COMPS/arduino_tinyusb/tinyusb" rev-parse --short HEAD)
-echo $component_version >> version.txt && echo $component_version >> "$AR_TOOLS/sdk/versions.txt"
+echo $component_version >> version.txt && echo $component_version >> "$AR_TOOLS/esp32-arduino-libs/versions.txt"
 
 #targets_count=`jq -c '.targets[] | length' configs/builds.json`
 for target_json in `jq -c '.targets[]' configs/builds.json`; do
@@ -157,6 +177,11 @@ for target_json in `jq -c '.targets[]' configs/builds.json`; do
     for defconf in `echo "$target_json" | jq -c '.idf_libs[]' | tr -d '"'`; do
         idf_libs_configs="$idf_libs_configs;configs/defconfig.$defconf"
     done
+
+    if [ -f "./managed_components/espressif__esp-sr/.component_hash" ]; then
+        rm -rf ./managed_components/espressif__esp-sr/.component_hash
+    fi
+
     echo "* Build IDF-Libs: $idf_libs_configs"
     rm -rf build sdkconfig
     idf.py -DIDF_TARGET="$target" -DSDKCONFIG_DEFAULTS="$idf_libs_configs" idf_libs
@@ -168,6 +193,11 @@ for target_json in `jq -c '.targets[]' configs/builds.json`; do
         for defconf in `echo "$boot_conf" | jq -c '.[]' | tr -d '"'`; do
             bootloader_configs="$bootloader_configs;configs/defconfig.$defconf";
         done
+
+        if [ -f "./managed_components/espressif__esp-sr/.component_hash" ]; then
+            rm -rf ./managed_components/espressif__esp-sr/.component_hash
+        fi
+
         echo "* Build BootLoader: $bootloader_configs"
         rm -rf build sdkconfig
         idf.py -DIDF_TARGET="$target" -DSDKCONFIG_DEFAULTS="$bootloader_configs" copy_bootloader
@@ -180,6 +210,11 @@ for target_json in `jq -c '.targets[]' configs/builds.json`; do
         for defconf in `echo "$mem_conf" | jq -c '.[]' | tr -d '"'`; do
             mem_configs="$mem_configs;configs/defconfig.$defconf";
         done
+
+        if [ -f "./managed_components/espressif__esp-sr/.component_hash" ]; then
+            rm -rf ./managed_components/espressif__esp-sr/.component_hash
+        fi
+
         echo "* Build Memory Variant: $mem_configs"
         rm -rf build sdkconfig
         idf.py -DIDF_TARGET="$target" -DSDKCONFIG_DEFAULTS="$mem_configs" mem_variant
@@ -190,20 +225,30 @@ done
 # update package_esp32_index.template.json
 if [ "$BUILD_TYPE" = "all" ]; then
     python3 ./tools/gen_tools_json.py -i "$IDF_PATH" -j "$AR_COMPS/arduino/package/package_esp32_index.template.json" -o "$AR_OUT/"
+    python3 ./tools/gen_tools_json.py -i "$IDF_PATH" -o "$TOOLS_JSON_OUT/"
     if [ $? -ne 0 ]; then exit 1; fi
 fi
 
-# archive the build
+# Generate PlatformIO manifest file
 if [ "$BUILD_TYPE" = "all" ]; then
-    ./tools/archive-build.sh
+    python3 ./tools/gen_platformio_manifest.py -o "$TOOLS_JSON_OUT/" -s $(git -C "$IDF_PATH" symbolic-ref --short HEAD || git -C "$IDF_PATH" tag --points-at HEAD) -c $(git -C "$IDF_PATH" rev-parse --short HEAD)
     if [ $? -ne 0 ]; then exit 1; fi
 fi
 
 # copy everything to arduino-esp32 installation
 if [ $COPY_OUT -eq 1 ] && [ -d "$ESP32_ARDUINO" ]; then
     ./tools/copy-to-arduino.sh
+    if [ $? -ne 0 ]; then exit 1; fi
 fi
 
+# push changes to esp32-arduino-libs and create pull request into arduino-esp32
 if [ $DEPLOY_OUT -eq 1 ]; then
     ./tools/push-to-arduino.sh
+    if [ $? -ne 0 ]; then exit 1; fi
+fi
+
+# archive the build
+if [ $ARCHIVE_OUT -eq 1 ]; then
+    ./tools/archive-build.sh "$TARGET"
+    if [ $? -ne 0 ]; then exit 1; fi
 fi
diff --git a/components/arduino_tinyusb/CMakeLists.txt b/components/arduino_tinyusb/CMakeLists.txt
index 041eeec0f..792b2a39f 100755
--- a/components/arduino_tinyusb/CMakeLists.txt
+++ b/components/arduino_tinyusb/CMakeLists.txt
@@ -1,62 +1,74 @@
-idf_component_register(REQUIRES esp_rom freertos soc PRIV_REQUIRES arduino main)
-
 if(CONFIG_TINYUSB_ENABLED)
 
   ### variables ###
   #################
-  # if(IDF_TARGET STREQUAL "esp32s2")
+
+  if(IDF_TARGET STREQUAL "esp32s2")
     set(compile_options
         "-DCFG_TUSB_MCU=OPT_MCU_ESP32S2"
         "-DCFG_TUSB_DEBUG=${CONFIG_TINYUSB_DEBUG_LEVEL}"
         "-Wno-type-limits" # needed for the vanila tinyusb with turned off classes
     )
-  # elseif(IDF_TARGET STREQUAL "esp32s3")
-  #   set(compile_options
-  #       "-DCFG_TUSB_MCU=OPT_MCU_ESP32S2"
-  #       "-DCFG_TUSB_DEBUG=${CONFIG_TINYUSB_DEBUG_LEVEL}"
-  #       "-Wno-type-limits" # needed for the vanila tinyusb with turned off classes
-  #   )
-  # endif()
-  idf_component_get_property(FREERTOS_ORIG_INCLUDE_PATH freertos
-                             ORIG_INCLUDE_PATH)
-  set(includes_private
-      # tusb:
-      "${COMPONENT_DIR}/tinyusb/hw/bsp/"
-      "${COMPONENT_DIR}/tinyusb/src/"
-      "${COMPONENT_DIR}/tinyusb/src/device"
-      )
+  elseif(IDF_TARGET STREQUAL "esp32s3")
+    set(compile_options
+        "-DCFG_TUSB_MCU=OPT_MCU_ESP32S3"
+        "-DCFG_TUSB_DEBUG=${CONFIG_TINYUSB_DEBUG_LEVEL}"
+        "-Wno-type-limits" # needed for the vanila tinyusb with turned off classes
+    )
+  endif()
 
-  set(includes_public
-      # tusb:
-      "${FREERTOS_ORIG_INCLUDE_PATH}"
-      "${COMPONENT_DIR}/tinyusb/src/"
-      # espressif:
-      "${COMPONENT_DIR}/include")
   set(srcs
       # espressif:
       "${COMPONENT_DIR}/src/dcd_esp32sx.c"
+      #"${COMPONENT_DIR}/src/dcd_dwc2.c"
       # tusb:
       #"${COMPONENT_DIR}/tinyusb/src/portable/espressif/esp32sx/dcd_esp32sx.c"
+      #"{COMPONENT_DIR}/tinyusb/src/portable/synopsys/dwc2/dcd_dwc2.c"
       "${COMPONENT_DIR}/tinyusb/src/class/cdc/cdc_device.c"
       "${COMPONENT_DIR}/tinyusb/src/class/hid/hid_device.c"
       "${COMPONENT_DIR}/tinyusb/src/class/midi/midi_device.c"
       "${COMPONENT_DIR}/tinyusb/src/class/msc/msc_device.c"
       "${COMPONENT_DIR}/tinyusb/src/class/video/video_device.c"
       "${COMPONENT_DIR}/tinyusb/src/class/dfu/dfu_rt_device.c"
+      "${COMPONENT_DIR}/tinyusb/src/class/dfu/dfu_device.c"
       "${COMPONENT_DIR}/tinyusb/src/class/vendor/vendor_device.c"
       "${COMPONENT_DIR}/tinyusb/src/common/tusb_fifo.c"
       "${COMPONENT_DIR}/tinyusb/src/device/usbd_control.c"
       "${COMPONENT_DIR}/tinyusb/src/device/usbd.c"
       "${COMPONENT_DIR}/tinyusb/src/tusb.c")
 
+  set(includes_private
+      # tusb:
+      "${COMPONENT_DIR}/tinyusb/hw/bsp/"
+      "${COMPONENT_DIR}/tinyusb/src/"
+      "${COMPONENT_DIR}/tinyusb/src/device"
+      "${COMPONENT_DIR}/tinyusb/src/portable/synopsys/dwc2"
+      )
+
+  idf_component_get_property(FREERTOS_ORIG_INCLUDE_PATH freertos
+                             ORIG_INCLUDE_PATH)
+  set(includes_public
+      # tusb:
+      "${FREERTOS_ORIG_INCLUDE_PATH}"
+      "${COMPONENT_DIR}/tinyusb/src/"
+      # espressif:
+      "${COMPONENT_DIR}/include")
+
+  set(requires esp_rom freertos soc)
+  set(priv_requires arduino main)
   ### tinyusb lib ###
   ###################
-  add_library(arduino_tinyusb STATIC ${srcs})
-  target_include_directories(
-    arduino_tinyusb
-    PUBLIC ${includes_public}
-    PRIVATE ${includes_private})
-  target_compile_options(arduino_tinyusb PRIVATE ${compile_options})
-  target_link_libraries(${COMPONENT_TARGET} INTERFACE arduino_tinyusb)
+  idf_component_register(INCLUDE_DIRS ${includes_public} PRIV_INCLUDE_DIRS ${includes_private} SRCS ${srcs} REQUIRES ${requires} PRIV_REQUIRES ${priv_requires})
+  # add_library(${COMPONENT_TARGET} STATIC ${srcs})
+  # target_include_directories(
+  #   ${COMPONENT_TARGET}
+  #   PUBLIC ${includes_public}
+  #   PRIVATE ${includes_private})
+  target_compile_options(${COMPONENT_TARGET} PRIVATE ${compile_options})
+  #target_link_libraries(${COMPONENT_TARGET} INTERFACE ${COMPONENT_TARGET})
+
+else()
+
+  idf_component_register()
 
 endif()
diff --git a/components/arduino_tinyusb/Kconfig.projbuild b/components/arduino_tinyusb/Kconfig.projbuild
index a6abd4d8a..80983657f 100755
--- a/components/arduino_tinyusb/Kconfig.projbuild
+++ b/components/arduino_tinyusb/Kconfig.projbuild
@@ -175,6 +175,31 @@ menu "Arduino TinyUSB"
 
     endmenu
 
+    menu "DFU driver"
+        depends on TINYUSB_ENABLED
+
+        config TINYUSB_DFU_ENABLED
+            bool "Enable USB DFU TinyUSB driver"
+            default y
+            help
+                Enable USB DFU TinyUSB driver.
+
+        config TINYUSB_DESC_DFU_STRING
+            string "DFU Device String"
+            default "Espressif DFU Device"
+            depends on TINYUSB_DFU_ENABLED
+            help
+                Specify name of the DFU device
+
+        config TINYUSB_DFU_BUFSIZE
+            int "DFU buffer size"
+            default 4096
+            depends on TINYUSB_DFU_ENABLED
+            help
+                DFU buffer size
+
+    endmenu
+
     menu "VENDOR driver"
         depends on TINYUSB_ENABLED
 
diff --git a/components/arduino_tinyusb/include/tusb_config.h b/components/arduino_tinyusb/include/tusb_config.h
index a5a0afd32..ee1e5d270 100755
--- a/components/arduino_tinyusb/include/tusb_config.h
+++ b/components/arduino_tinyusb/include/tusb_config.h
@@ -64,6 +64,10 @@ extern "C" {
 #   define CONFIG_TINYUSB_DFU_RT_ENABLED 0
 #endif
 
+#ifndef CONFIG_TINYUSB_DFU_ENABLED
+#   define CONFIG_TINYUSB_DFU_ENABLED 0
+#endif
+
 #ifndef CONFIG_TINYUSB_VENDOR_ENABLED
 #   define CONFIG_TINYUSB_VENDOR_ENABLED 0
 #endif
@@ -106,6 +110,7 @@ extern "C" {
 #define CFG_TUD_VIDEO               CONFIG_TINYUSB_VIDEO_ENABLED
 #define CFG_TUD_CUSTOM_CLASS 		CONFIG_TINYUSB_CUSTOM_CLASS_ENABLED
 #define CFG_TUD_DFU_RUNTIME			CONFIG_TINYUSB_DFU_RT_ENABLED
+#define CFG_TUD_DFU					CONFIG_TINYUSB_DFU_ENABLED
 #define CFG_TUD_VENDOR 				CONFIG_TINYUSB_VENDOR_ENABLED
 
 // CDC FIFO size of TX and RX
@@ -126,6 +131,9 @@ extern "C" {
 #define CFG_TUD_VIDEO_STREAMING     CONFIG_TINYUSB_VIDEO_STREAMING_IFS
 #define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE  CONFIG_TINYUSB_VIDEO_STREAMING_BUFSIZE
 
+// DFU buffer size
+#define CFG_TUD_DFU_XFER_BUFSIZE	CONFIG_TINYUSB_DFU_BUFSIZE
+
 // VENDOR FIFO size of TX and RX
 #define CFG_TUD_VENDOR_RX_BUFSIZE 	CONFIG_TINYUSB_VENDOR_RX_BUFSIZE
 #define CFG_TUD_VENDOR_TX_BUFSIZE 	CONFIG_TINYUSB_VENDOR_TX_BUFSIZE
diff --git a/components/arduino_tinyusb/src/dcd_dwc2.c b/components/arduino_tinyusb/src/dcd_dwc2.c
new file mode 100644
index 000000000..67653fda0
--- /dev/null
+++ b/components/arduino_tinyusb/src/dcd_dwc2.c
@@ -0,0 +1,1389 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 William D. Jones
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ * Copyright (c) 2020 Jan Duempelmann
+ * Copyright (c) 2020 Reinhard Panhuber
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if CFG_TUD_ENABLED && defined(TUP_USBIP_DWC2)
+
+#include "device/dcd.h"
+#include "dwc2_type.h"
+
+// Following symbols must be defined by port header
+// - _dwc2_controller[]: array of controllers
+// - DWC2_EP_MAX: largest EP counts of all controllers
+// - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset
+// - dwc2_dcd_int_enable/dwc2_dcd_int_disable
+// - dwc2_remote_wakeup_delay
+
+#if defined(TUP_USBIP_DWC2_STM32)
+  #include "dwc2_stm32.h"
+#elif TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
+  #include "dwc2_esp32.h"
+#elif TU_CHECK_MCU(OPT_MCU_GD32VF103)
+  #include "dwc2_gd32.h"
+#elif TU_CHECK_MCU(OPT_MCU_BCM2711, OPT_MCU_BCM2835, OPT_MCU_BCM2837)
+  #include "dwc2_bcm.h"
+#elif TU_CHECK_MCU(OPT_MCU_EFM32GG)
+  #include "dwc2_efm32.h"
+#elif TU_CHECK_MCU(OPT_MCU_XMC4000)
+  #include "dwc2_xmc.h"
+#else
+  #error "Unsupported MCUs"
+#endif
+
+//--------------------------------------------------------------------+
+// MACRO TYPEDEF CONSTANT ENUM
+//--------------------------------------------------------------------+
+
+// DWC2 registers
+#define DWC2_REG(_port)       ((dwc2_regs_t*) _dwc2_controller[_port].reg_base)
+
+// Debug level for DWC2
+#define DWC2_DEBUG    2
+
+#ifndef dcache_clean
+#define dcache_clean(_addr, _size)
+#endif
+
+#ifndef dcache_invalidate
+#define dcache_invalidate(_addr, _size)
+#endif
+
+#ifndef dcache_clean_invalidate
+#define dcache_clean_invalidate(_addr, _size)
+#endif
+
+static TU_ATTR_ALIGNED(4) uint32_t _setup_packet[2];
+
+typedef struct {
+  uint8_t * buffer;
+  tu_fifo_t * ff;
+  uint16_t total_len;
+  uint16_t max_size;
+  uint8_t interval;
+} xfer_ctl_t;
+
+static xfer_ctl_t xfer_status[DWC2_EP_MAX][2];
+#define XFER_CTL_BASE(_ep, _dir) (&xfer_status[_ep][_dir])
+
+// EP0 transfers are limited to 1 packet - larger sizes has to be split
+static uint16_t ep0_pending[2];                   // Index determines direction as tusb_dir_t type
+
+// TX FIFO RAM allocation so far in words - RX FIFO size is readily available from dwc2->grxfsiz
+static uint16_t _allocated_fifo_words_tx;         // TX FIFO size in words (IN EPs)
+static bool     _out_ep_closed;                   // Flag to check if RX FIFO size needs an update (reduce its size)
+
+// SOF enabling flag - required for SOF to not get disabled in ISR when SOF was enabled by
+static bool _sof_en;
+
+// Calculate the RX FIFO size according to recommendations from reference manual
+static inline uint16_t calc_grxfsiz(uint16_t max_ep_size, uint8_t ep_count)
+{
+  return 15 + 2*(max_ep_size/4) + 2*ep_count;
+}
+
+static void update_grxfsiz(uint8_t rhport)
+{
+  dwc2_regs_t * dwc2     = DWC2_REG(rhport);
+  uint8_t const ep_count = _dwc2_controller[rhport].ep_count;
+
+  // Determine largest EP size for RX FIFO
+  uint16_t max_epsize = 0;
+  for (uint8_t epnum = 0; epnum < ep_count; epnum++)
+  {
+    max_epsize = tu_max16(max_epsize, xfer_status[epnum][TUSB_DIR_OUT].max_size);
+  }
+
+  // Update size of RX FIFO
+  dwc2->grxfsiz = calc_grxfsiz(max_epsize, ep_count);
+}
+
+// Start of Bus Reset
+static void bus_reset(uint8_t rhport)
+{
+  dwc2_regs_t * dwc2     = DWC2_REG(rhport);
+  uint8_t const ep_count = _dwc2_controller[rhport].ep_count;
+
+  tu_memclr(xfer_status, sizeof(xfer_status));
+  _out_ep_closed = false;
+
+  _sof_en = false;
+
+  // clear device address
+  dwc2->dcfg &= ~DCFG_DAD_Msk;
+
+  // 1. NAK for all OUT endpoints
+  for ( uint8_t n = 0; n < ep_count; n++ )
+  {
+    dwc2->epout[n].doepctl |= DOEPCTL_SNAK;
+  }
+
+  // 2. Set up interrupt mask
+  dwc2->daintmsk = TU_BIT(DAINTMSK_OEPM_Pos) | TU_BIT(DAINTMSK_IEPM_Pos);
+  dwc2->doepmsk  = DOEPMSK_STUPM | DOEPMSK_XFRCM;
+  dwc2->diepmsk  = DIEPMSK_TOM   | DIEPMSK_XFRCM;
+
+  // "USB Data FIFOs" section in reference manual
+  // Peripheral FIFO architecture
+  //
+  // The FIFO is split up in a lower part where the RX FIFO is located and an upper part where the TX FIFOs start.
+  // We do this to allow the RX FIFO to grow dynamically which is possible since the free space is located
+  // between the RX and TX FIFOs. This is required by ISO OUT EPs which need a bigger FIFO than the standard
+  // configuration done below.
+  //
+  // Dynamically FIFO sizes are of interest only for ISO EPs since all others are usually not opened and closed.
+  // All EPs other than ISO are opened as soon as the driver starts up i.e. when the host sends a
+  // configure interface command. Hence, all IN EPs other the ISO will be located at the top. IN ISO EPs are usually
+  // opened when the host sends an additional command: setInterface. At this point in time
+  // the ISO EP will be located next to the free space and can change its size. In case more IN EPs change its size
+  // an additional memory
+  //
+  // --------------- 320 or 1024 ( 1280 or 4096 bytes )
+  // | IN FIFO 0   |
+  // --------------- (320 or 1024) - 16
+  // | IN FIFO 1   |
+  // --------------- (320 or 1024) - 16 - x
+  // |   . . . .   |
+  // --------------- (320 or 1024) - 16 - x - y - ... - z
+  // | IN FIFO MAX |
+  // ---------------
+  // |    FREE     |
+  // --------------- GRXFSIZ
+  // | OUT FIFO    |
+  // | ( Shared )  |
+  // --------------- 0
+  //
+  // According to "FIFO RAM allocation" section in RM, FIFO RAM are allocated as follows (each word 32-bits):
+  // - Each EP IN needs at least max packet size, 16 words is sufficient for EP0 IN
+  //
+  // - All EP OUT shared a unique OUT FIFO which uses
+  //   - 13 for setup packets + control words (up to 3 setup packets).
+  //   - 1 for global NAK (not required/used here).
+  //   - Largest-EPsize / 4 + 1. ( FS: 64 bytes, HS: 512 bytes). Recommended is  "2 x (Largest-EPsize/4) + 1"
+  //   - 2 for each used OUT endpoint
+  //
+  //   Therefore GRXFSIZ = 13 + 1 + 1 + 2 x (Largest-EPsize/4) + 2 x EPOUTnum
+  //   - FullSpeed (64 Bytes ): GRXFSIZ = 15 + 2 x  16 + 2 x ep_count = 47  + 2 x ep_count
+  //   - Highspeed (512 bytes): GRXFSIZ = 15 + 2 x 128 + 2 x ep_count = 271 + 2 x ep_count
+  //
+  //   NOTE: Largest-EPsize & EPOUTnum is actual used endpoints in configuration. Since DCD has no knowledge
+  //   of the overall picture yet. We will use the worst scenario: largest possible + ep_count
+  //
+  //   For Isochronous, largest EP size can be 1023/1024 for FS/HS respectively. In addition if multiple ISO
+  //   are enabled at least "2 x (Largest-EPsize/4) + 1" are recommended.  Maybe provide a macro for application to
+  //   overwrite this.
+
+  // EP0 out max is 64
+  dwc2->grxfsiz = calc_grxfsiz(64, ep_count);
+
+  // Setup the control endpoint 0
+  _allocated_fifo_words_tx = 16;
+
+  // Control IN uses FIFO 0 with 64 bytes ( 16 32-bit word )
+  dwc2->dieptxf0 = (16 << DIEPTXF0_TX0FD_Pos) | (_dwc2_controller[rhport].ep_fifo_size/4 - _allocated_fifo_words_tx);
+
+  // Fixed control EP0 size to 64 bytes
+  dwc2->epin[0].diepctl &= ~(0x03 << DIEPCTL_MPSIZ_Pos);
+  xfer_status[0][TUSB_DIR_OUT].max_size = 64;
+  xfer_status[0][TUSB_DIR_IN ].max_size = 64;
+
+  dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos);
+
+  dwc2->gintmsk |= GINTMSK_OEPINT | GINTMSK_IEPINT;
+}
+
+static void edpt_schedule_packets(uint8_t rhport, uint8_t const epnum, uint8_t const dir, uint16_t const num_packets, uint16_t total_bytes)
+{
+  (void) rhport;
+
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+
+  // EP0 is limited to one packet each xfer
+  // We use multiple transaction of xfer->max_size length to get a whole transfer done
+  if ( epnum == 0 )
+  {
+    xfer_ctl_t *const xfer = XFER_CTL_BASE(epnum, dir);
+    total_bytes = tu_min16(ep0_pending[dir], xfer->max_size);
+    ep0_pending[dir] -= total_bytes;
+  }
+
+  // IN and OUT endpoint xfers are interrupt-driven, we just schedule them here.
+  if ( dir == TUSB_DIR_IN )
+  {
+    dwc2_epin_t* epin = dwc2->epin;
+
+    // A full IN transfer (multiple packets, possibly) triggers XFRC.
+    epin[epnum].dieptsiz = (num_packets << DIEPTSIZ_PKTCNT_Pos) |
+                           ((total_bytes << DIEPTSIZ_XFRSIZ_Pos) & DIEPTSIZ_XFRSIZ_Msk);
+
+    epin[epnum].diepctl |= DIEPCTL_EPENA | DIEPCTL_CNAK;
+
+    // For ISO endpoint set correct odd/even bit for next frame.
+    if ( (epin[epnum].diepctl & DIEPCTL_EPTYP) == DIEPCTL_EPTYP_0 && (XFER_CTL_BASE(epnum, dir))->interval == 1 )
+    {
+      // Take odd/even bit from frame counter.
+      uint32_t const odd_frame_now = (dwc2->dsts & (1u << DSTS_FNSOF_Pos));
+      epin[epnum].diepctl |= (odd_frame_now ? DIEPCTL_SD0PID_SEVNFRM_Msk : DIEPCTL_SODDFRM_Msk);
+    }
+    // Enable fifo empty interrupt only if there are something to put in the fifo.
+    if ( total_bytes != 0 )
+    {
+      dwc2->diepempmsk |= (1 << epnum);
+    }
+  }
+  else
+  {
+    dwc2_epout_t* epout = dwc2->epout;
+
+    // A full OUT transfer (multiple packets, possibly) triggers XFRC.
+    epout[epnum].doeptsiz &= ~(DOEPTSIZ_PKTCNT_Msk | DOEPTSIZ_XFRSIZ);
+    epout[epnum].doeptsiz |= (num_packets << DOEPTSIZ_PKTCNT_Pos) |
+                             ((total_bytes << DOEPTSIZ_XFRSIZ_Pos) & DOEPTSIZ_XFRSIZ_Msk);
+
+    epout[epnum].doepctl |= DOEPCTL_EPENA | DOEPCTL_CNAK;
+    if ( (epout[epnum].doepctl & DOEPCTL_EPTYP) == DOEPCTL_EPTYP_0 &&
+         XFER_CTL_BASE(epnum, dir)->interval == 1 )
+    {
+      // Take odd/even bit from frame counter.
+      uint32_t const odd_frame_now = (dwc2->dsts & (1u << DSTS_FNSOF_Pos));
+      epout[epnum].doepctl |= (odd_frame_now ? DOEPCTL_SD0PID_SEVNFRM_Msk : DOEPCTL_SODDFRM_Msk);
+    }
+  }
+}
+
+/*------------------------------------------------------------------*/
+/* Controller API
+ *------------------------------------------------------------------*/
+#if CFG_TUSB_DEBUG >= DWC2_DEBUG
+void print_dwc2_info(dwc2_regs_t * dwc2)
+{
+  dwc2_ghwcfg2_t const * hw_cfg2 = &dwc2->ghwcfg2_bm;
+  dwc2_ghwcfg3_t const * hw_cfg3 = &dwc2->ghwcfg3_bm;
+  dwc2_ghwcfg4_t const * hw_cfg4 = &dwc2->ghwcfg4_bm;
+
+//  TU_LOG_HEX(DWC2_DEBUG, dwc2->gotgctl);
+//  TU_LOG_HEX(DWC2_DEBUG, dwc2->gusbcfg);
+//  TU_LOG_HEX(DWC2_DEBUG, dwc2->dcfg);
+  TU_LOG_HEX(DWC2_DEBUG, dwc2->guid);
+  TU_LOG_HEX(DWC2_DEBUG, dwc2->gsnpsid);
+  TU_LOG_HEX(DWC2_DEBUG, dwc2->ghwcfg1);
+
+  // HW configure 2
+  TU_LOG(DWC2_DEBUG, "\r\n");
+  TU_LOG_HEX(DWC2_DEBUG, dwc2->ghwcfg2);
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->op_mode                );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->arch                   );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->point2point            );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->hs_phy_type            );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->fs_phy_type            );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->num_dev_ep             );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->num_host_ch            );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->period_channel_support );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->enable_dynamic_fifo    );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->mul_cpu_int            );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->nperiod_tx_q_depth     );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->host_period_tx_q_depth );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->dev_token_q_depth      );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg2->otg_enable_ic_usb      );
+
+  // HW configure 3
+  TU_LOG(DWC2_DEBUG, "\r\n");
+  TU_LOG_HEX(DWC2_DEBUG, dwc2->ghwcfg3);
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->xfer_size_width          );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->packet_size_width        );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->otg_enable               );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->i2c_enable               );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->vendor_ctrl_itf          );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->optional_feature_removed );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->synch_reset              );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->otg_adp_support          );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->otg_enable_hsic          );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->battery_charger_support  );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->lpm_mode                 );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg3->total_fifo_size          );
+
+  // HW configure 4
+  TU_LOG(DWC2_DEBUG, "\r\n");
+  TU_LOG_HEX(DWC2_DEBUG, dwc2->ghwcfg4);
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->num_dev_period_in_ep      );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->power_optimized           );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->ahb_freq_min              );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->hibernation               );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->service_interval_mode     );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->ipg_isoc_en               );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->acg_enable                );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->utmi_phy_data_width       );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->dev_ctrl_ep_num           );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->iddg_filter_enabled       );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->vbus_valid_filter_enabled );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->a_valid_filter_enabled    );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->b_valid_filter_enabled    );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->dedicated_fifos           );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->num_dev_in_eps            );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->dma_desc_enable           );
+  TU_LOG_INT(DWC2_DEBUG, hw_cfg4->dma_dynamic               );
+}
+#endif
+
+static void reset_core(dwc2_regs_t * dwc2)
+{
+  // reset core
+  dwc2->grstctl |= GRSTCTL_CSRST;
+
+  // wait for reset bit is cleared
+  // TODO version 4.20a should wait for RESET DONE mask
+  while (dwc2->grstctl & GRSTCTL_CSRST) { }
+
+  // wait for AHB master IDLE
+  while ( !(dwc2->grstctl & GRSTCTL_AHBIDL) ) { }
+
+  // wait for device mode ?
+}
+
+static bool phy_hs_supported(dwc2_regs_t * dwc2)
+{
+  // note: esp32 incorrect report its hs_phy_type as utmi
+#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
+  return false;
+#else
+  return TUD_OPT_HIGH_SPEED && dwc2->ghwcfg2_bm.hs_phy_type != HS_PHY_TYPE_NONE;
+#endif
+}
+
+static void phy_fs_init(dwc2_regs_t * dwc2)
+{
+  TU_LOG(DWC2_DEBUG, "Fullspeed PHY init\r\n");
+
+  // Select FS PHY
+  dwc2->gusbcfg |= GUSBCFG_PHYSEL;
+
+  // MCU specific PHY init before reset
+  dwc2_phy_init(dwc2, HS_PHY_TYPE_NONE);
+
+  // Reset core after selecting PHY
+  reset_core(dwc2);
+
+  // USB turnaround time is critical for certification where long cables and 5-Hubs are used.
+  // So if you need the AHB to run at less than 30 MHz, and if USB turnaround time is not critical,
+  // these bits can be programmed to a larger value. Default is 5
+  dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_TRDT_Msk) | (5u << GUSBCFG_TRDT_Pos);
+
+  // MCU specific PHY update post reset
+  dwc2_phy_update(dwc2, HS_PHY_TYPE_NONE);
+
+  // set max speed
+  dwc2->dcfg = (dwc2->dcfg & ~DCFG_DSPD_Msk) | (DCFG_DSPD_FS << DCFG_DSPD_Pos);
+}
+
+static void phy_hs_init(dwc2_regs_t * dwc2)
+{
+  uint32_t gusbcfg = dwc2->gusbcfg;
+
+  // De-select FS PHY
+  gusbcfg &= ~GUSBCFG_PHYSEL;
+
+  if (dwc2->ghwcfg2_bm.hs_phy_type == HS_PHY_TYPE_ULPI)
+  {
+    TU_LOG(DWC2_DEBUG, "Highspeed ULPI PHY init\r\n");
+
+    // Select ULPI
+    gusbcfg |= GUSBCFG_ULPI_UTMI_SEL;
+
+    // ULPI 8-bit interface, single data rate
+    gusbcfg &= ~(GUSBCFG_PHYIF16 | GUSBCFG_DDRSEL);
+
+    // default internal VBUS Indicator and Drive
+    gusbcfg &= ~(GUSBCFG_ULPIEVBUSD | GUSBCFG_ULPIEVBUSI);
+
+    // Disable FS/LS ULPI
+    gusbcfg &= ~(GUSBCFG_ULPIFSLS | GUSBCFG_ULPICSM);
+  }else
+  {
+    TU_LOG(DWC2_DEBUG, "Highspeed UTMI+ PHY init\r\n");
+
+    // Select UTMI+ with 8-bit interface
+    gusbcfg &= ~(GUSBCFG_ULPI_UTMI_SEL | GUSBCFG_PHYIF16);
+
+    // Set 16-bit interface if supported
+    if (dwc2->ghwcfg4_bm.utmi_phy_data_width) gusbcfg |= GUSBCFG_PHYIF16;
+  }
+
+  // Apply config
+  dwc2->gusbcfg = gusbcfg;
+
+  // mcu specific phy init
+  dwc2_phy_init(dwc2, dwc2->ghwcfg2_bm.hs_phy_type);
+
+  // Reset core after selecting PHY
+  reset_core(dwc2);
+
+  // Set turn-around, must after core reset otherwise it will be clear
+  // - 9 if using 8-bit PHY interface
+  // - 5 if using 16-bit PHY interface
+  gusbcfg &= ~GUSBCFG_TRDT_Msk;
+  gusbcfg |= (dwc2->ghwcfg4_bm.utmi_phy_data_width ? 5u : 9u) << GUSBCFG_TRDT_Pos;
+  dwc2->gusbcfg = gusbcfg;
+
+  // MCU specific PHY update post reset
+  dwc2_phy_update(dwc2, dwc2->ghwcfg2_bm.hs_phy_type);
+
+  // Set max speed
+  uint32_t dcfg = dwc2->dcfg;
+  dcfg &= ~DCFG_DSPD_Msk;
+  dcfg |= DCFG_DSPD_HS << DCFG_DSPD_Pos;
+
+  // XCVRDLY: transceiver delay between xcvr_sel and txvalid during device chirp is required
+  // when using with some PHYs such as USB334x (USB3341, USB3343, USB3346, USB3347)
+  if (dwc2->ghwcfg2_bm.hs_phy_type == HS_PHY_TYPE_ULPI) dcfg |= DCFG_XCVRDLY;
+
+  dwc2->dcfg = dcfg;
+}
+
+static bool check_dwc2(dwc2_regs_t * dwc2)
+{
+#if CFG_TUSB_DEBUG >= DWC2_DEBUG
+  print_dwc2_info(dwc2);
+#endif
+
+  // For some reasons: GD32VF103 snpsid and all hwcfg register are always zero (skip it)
+  (void) dwc2;
+#if !TU_CHECK_MCU(OPT_MCU_GD32VF103)
+  uint32_t const gsnpsid = dwc2->gsnpsid & GSNPSID_ID_MASK;
+  TU_ASSERT(gsnpsid == DWC2_OTG_ID || gsnpsid == DWC2_FS_IOT_ID || gsnpsid == DWC2_HS_IOT_ID);
+#endif
+
+  return true;
+}
+
+void dcd_init (uint8_t rhport)
+{
+  // Programming model begins in the last section of the chapter on the USB
+  // peripheral in each Reference Manual.
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+
+  // Check Synopsys ID register, failed if controller clock/power is not enabled
+  TU_VERIFY(check_dwc2(dwc2), );
+
+  dcd_disconnect(rhport);
+
+  // max number of endpoints & total_fifo_size are:
+  // hw_cfg2->num_dev_ep, hw_cfg2->total_fifo_size
+
+  if( phy_hs_supported(dwc2) )
+  {
+    // Highspeed
+    phy_hs_init(dwc2);
+  }else
+  {
+    // core does not support highspeed or hs-phy is not present
+    phy_fs_init(dwc2);
+  }
+
+  // Restart PHY clock
+  dwc2->pcgctl &= ~(PCGCTL_STOPPCLK | PCGCTL_GATEHCLK | PCGCTL_PWRCLMP | PCGCTL_RSTPDWNMODULE);
+
+	/* Set HS/FS Timeout Calibration to 7 (max available value).
+	 * The number of PHY clocks that the application programs in
+	 * this field is added to the high/full speed interpacket timeout
+	 * duration in the core to account for any additional delays
+	 * introduced by the PHY. This can be required, because the delay
+	 * introduced by the PHY in generating the linestate condition
+	 * can vary from one PHY to another.
+	 */
+  dwc2->gusbcfg |= (7ul << GUSBCFG_TOCAL_Pos);
+
+  // Force device mode
+  dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_FHMOD) | GUSBCFG_FDMOD;
+
+  // Clear A override, force B Valid
+  dwc2->gotgctl = (dwc2->gotgctl & ~GOTGCTL_AVALOEN) | GOTGCTL_BVALOEN | GOTGCTL_BVALOVAL;
+
+  // If USB host misbehaves during status portion of control xfer
+  // (non zero-length packet), send STALL back and discard.
+  dwc2->dcfg |= DCFG_NZLSOHSK;
+
+  // Clear all interrupts
+  uint32_t int_mask = dwc2->gintsts;
+  dwc2->gintsts |= int_mask;
+  int_mask = dwc2->gotgint;
+  dwc2->gotgint |= int_mask;
+
+  // Required as part of core initialization.
+  // TODO: How should mode mismatch be handled? It will cause
+  // the core to stop working/require reset.
+  dwc2->gintmsk = GINTMSK_OTGINT   | GINTMSK_MMISM  | GINTMSK_RXFLVLM  |
+                  GINTMSK_USBSUSPM | GINTMSK_USBRST | GINTMSK_ENUMDNEM | GINTMSK_WUIM;
+
+  // Enable global interrupt
+  dwc2->gahbcfg |= GAHBCFG_GINT;
+
+  // make sure we are in device mode
+//  TU_ASSERT(!(dwc2->gintsts & GINTSTS_CMOD), );
+
+//  TU_LOG_HEX(DWC2_DEBUG, dwc2->gotgctl);
+//  TU_LOG_HEX(DWC2_DEBUG, dwc2->gusbcfg);
+//  TU_LOG_HEX(DWC2_DEBUG, dwc2->dcfg);
+//  TU_LOG_HEX(DWC2_DEBUG, dwc2->gahbcfg);
+
+  dcd_connect(rhport);
+}
+
+void dcd_int_enable (uint8_t rhport)
+{
+  dwc2_dcd_int_enable(rhport);
+}
+
+void dcd_int_disable (uint8_t rhport)
+{
+  dwc2_dcd_int_disable(rhport);
+}
+
+void dcd_set_address (uint8_t rhport, uint8_t dev_addr)
+{
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+  dwc2->dcfg = (dwc2->dcfg & ~DCFG_DAD_Msk) | (dev_addr << DCFG_DAD_Pos);
+
+  // Response with status after changing device address
+  dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0);
+}
+
+void dcd_remote_wakeup(uint8_t rhport)
+{
+  (void) rhport;
+
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+
+  // set remote wakeup
+  dwc2->dctl |= DCTL_RWUSIG;
+
+  // enable SOF to detect bus resume
+  dwc2->gintsts = GINTSTS_SOF;
+  dwc2->gintmsk |= GINTMSK_SOFM;
+
+  // Per specs: remote wakeup signal bit must be clear within 1-15ms
+  dwc2_remote_wakeup_delay();
+
+  dwc2->dctl &= ~DCTL_RWUSIG;
+}
+
+void dcd_connect(uint8_t rhport)
+{
+  (void) rhport;
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+  dwc2->dctl &= ~DCTL_SDIS;
+}
+
+void dcd_disconnect(uint8_t rhport)
+{
+  (void) rhport;
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+  dwc2->dctl |= DCTL_SDIS;
+}
+
+// Be advised: audio, video and possibly other iso-ep classes use dcd_sof_enable() to enable/disable its corresponding ISR on purpose!
+void dcd_sof_enable(uint8_t rhport, bool en)
+{
+  (void) rhport;
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+
+  _sof_en = en;
+
+  if (en)
+  {
+    dwc2->gintsts = GINTSTS_SOF;
+    dwc2->gintmsk |= GINTMSK_SOFM;
+  }
+  else
+  {
+    dwc2->gintmsk &= ~GINTMSK_SOFM;
+  }
+}
+
+/*------------------------------------------------------------------*/
+/* DCD Endpoint port
+ *------------------------------------------------------------------*/
+#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
+// Keep count of how many FIFOs are in use
+static uint8_t _allocated_fifos = 1; //FIFO0 is always in use
+
+// Will either return an unused FIFO number, or 0 if all are used.
+static uint8_t get_free_fifo(void)
+{
+  if (_allocated_fifos < 5) return _allocated_fifos++;
+  return 0;
+}
+#endif
+
+bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt)
+{
+  (void) rhport;
+
+  dwc2_regs_t * dwc2     = DWC2_REG(rhport);
+  uint8_t const ep_count = _dwc2_controller[rhport].ep_count;
+
+  uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress);
+  uint8_t const dir   = tu_edpt_dir(desc_edpt->bEndpointAddress);
+
+  TU_ASSERT(epnum < ep_count);
+
+  xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir);
+  xfer->max_size = tu_edpt_packet_size(desc_edpt);
+  xfer->interval = desc_edpt->bInterval;
+
+  uint16_t const fifo_size = tu_div_ceil(xfer->max_size, 4);
+
+  if(dir == TUSB_DIR_OUT)
+  {
+    // Calculate required size of RX FIFO
+    uint16_t const sz = calc_grxfsiz(4*fifo_size, ep_count);
+
+    // If size_rx needs to be extended check if possible and if so enlarge it
+    if (dwc2->grxfsiz < sz)
+    {
+      TU_ASSERT(sz + _allocated_fifo_words_tx <= _dwc2_controller[rhport].ep_fifo_size/4);
+
+      // Enlarge RX FIFO
+      dwc2->grxfsiz = sz;
+    }
+
+    dwc2->epout[epnum].doepctl |= (1 << DOEPCTL_USBAEP_Pos) |
+                                  (desc_edpt->bmAttributes.xfer << DOEPCTL_EPTYP_Pos) |
+                                  (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? DOEPCTL_SD0PID_SEVNFRM : 0) |
+                                  (xfer->max_size << DOEPCTL_MPSIZ_Pos);
+
+    dwc2->daintmsk |= TU_BIT(DAINTMSK_OEPM_Pos + epnum);
+  }
+  else
+  {
+    // "USB Data FIFOs" section in reference manual
+    // Peripheral FIFO architecture
+    //
+    // --------------- 320 or 1024 ( 1280 or 4096 bytes )
+    // | IN FIFO 0   |
+    // --------------- (320 or 1024) - 16
+    // | IN FIFO 1   |
+    // --------------- (320 or 1024) - 16 - x
+    // |   . . . .   |
+    // --------------- (320 or 1024) - 16 - x - y - ... - z
+    // | IN FIFO MAX |
+    // ---------------
+    // |    FREE     |
+    // --------------- GRXFSIZ
+    // | OUT FIFO    |
+    // | ( Shared )  |
+    // --------------- 0
+    //
+    // In FIFO is allocated by following rules:
+    // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n".
+
+    uint8_t fifo_num = epnum;
+#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
+    // Special Case for EP5, which is used by CDC but not actually called by the driver
+    // we can give it a fake FIFO
+    if (epnum == 5) {
+      fifo_num = epnum;
+    } else {
+      fifo_num = get_free_fifo();
+    }
+    TU_ASSERT(fifo_num != 0);
+#endif
+    // Check if free space is available
+    TU_ASSERT(_allocated_fifo_words_tx + fifo_size + dwc2->grxfsiz <= _dwc2_controller[rhport].ep_fifo_size/4);
+
+    _allocated_fifo_words_tx += fifo_size;
+
+    TU_LOG(DWC2_DEBUG, "    Allocated %u bytes at offset %lu", fifo_size*4, _dwc2_controller[rhport].ep_fifo_size-_allocated_fifo_words_tx*4);
+
+    // DIEPTXF starts at FIFO #1.
+    // Both TXFD and TXSA are in unit of 32-bit words.
+    dwc2->dieptxf[epnum - 1] = (fifo_size << DIEPTXF_INEPTXFD_Pos) | (_dwc2_controller[rhport].ep_fifo_size/4 - _allocated_fifo_words_tx);
+
+    dwc2->epin[epnum].diepctl |= (1 << DIEPCTL_USBAEP_Pos) |
+                                 (fifo_num << DIEPCTL_TXFNUM_Pos) |
+                                 (desc_edpt->bmAttributes.xfer << DIEPCTL_EPTYP_Pos) |
+                                 (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? DIEPCTL_SD0PID_SEVNFRM : 0) |
+                                 (xfer->max_size << DIEPCTL_MPSIZ_Pos);
+
+    dwc2->daintmsk |= (1 << (DAINTMSK_IEPM_Pos + epnum));
+  }
+
+  return true;
+}
+
+// Close all non-control endpoints, cancel all pending transfers if any.
+void dcd_edpt_close_all (uint8_t rhport)
+{
+  dwc2_regs_t * dwc2     = DWC2_REG(rhport);
+  uint8_t const ep_count = _dwc2_controller[rhport].ep_count;
+
+  // Disable non-control interrupt
+  dwc2->daintmsk = (1 << DAINTMSK_OEPM_Pos) | (1 << DAINTMSK_IEPM_Pos);
+
+  for(uint8_t n = 1; n < ep_count; n++)
+  {
+    // disable OUT endpoint
+    dwc2->epout[n].doepctl = 0;
+    xfer_status[n][TUSB_DIR_OUT].max_size = 0;
+
+    // disable IN endpoint
+    dwc2->epin[n].diepctl = 0;
+    xfer_status[n][TUSB_DIR_IN].max_size = 0;
+  }
+
+  // reset allocated fifo IN
+  _allocated_fifo_words_tx = 16;
+#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
+  _allocated_fifos = 1;
+#endif
+}
+
+bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes)
+{
+  uint8_t const epnum = tu_edpt_number(ep_addr);
+  uint8_t const dir   = tu_edpt_dir(ep_addr);
+
+  xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir);
+  xfer->buffer      = buffer;
+  xfer->ff          = NULL;
+  xfer->total_len   = total_bytes;
+
+  // EP0 can only handle one packet
+  if(epnum == 0)
+  {
+    ep0_pending[dir] = total_bytes;
+
+    // Schedule the first transaction for EP0 transfer
+    edpt_schedule_packets(rhport, epnum, dir, 1, ep0_pending[dir]);
+  }
+  else
+  {
+    uint16_t num_packets = (total_bytes / xfer->max_size);
+    uint16_t const short_packet_size = total_bytes % xfer->max_size;
+
+    // Zero-size packet is special case.
+    if ( (short_packet_size > 0) || (total_bytes == 0) ) num_packets++;
+
+    // Schedule packets to be sent within interrupt
+    edpt_schedule_packets(rhport, epnum, dir, num_packets, total_bytes);
+  }
+
+  return true;
+}
+
+// The number of bytes has to be given explicitly to allow more flexible control of how many
+// bytes should be written and second to keep the return value free to give back a boolean
+// success message. If total_bytes is too big, the FIFO will copy only what is available
+// into the USB buffer!
+bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes)
+{
+  // USB buffers always work in bytes so to avoid unnecessary divisions we demand item_size = 1
+  TU_ASSERT(ff->item_size == 1);
+
+  uint8_t const epnum = tu_edpt_number(ep_addr);
+  uint8_t const dir   = tu_edpt_dir(ep_addr);
+
+  xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir);
+  xfer->buffer      = NULL;
+  xfer->ff          = ff;
+  xfer->total_len   = total_bytes;
+
+  uint16_t num_packets = (total_bytes / xfer->max_size);
+  uint16_t const short_packet_size = total_bytes % xfer->max_size;
+
+  // Zero-size packet is special case.
+  if ( short_packet_size > 0 || (total_bytes == 0) ) num_packets++;
+
+  // Schedule packets to be sent within interrupt
+  edpt_schedule_packets(rhport, epnum, dir, num_packets, total_bytes);
+
+  return true;
+}
+
+static void dcd_edpt_disable (uint8_t rhport, uint8_t ep_addr, bool stall)
+{
+  (void) rhport;
+
+  dwc2_regs_t *dwc2 = DWC2_REG(rhport);
+
+  uint8_t const epnum = tu_edpt_number(ep_addr);
+  uint8_t const dir   = tu_edpt_dir(ep_addr);
+
+  if ( dir == TUSB_DIR_IN )
+  {
+    dwc2_epin_t* epin = dwc2->epin;
+
+    // Only disable currently enabled non-control endpoint
+    if ( (epnum == 0) || !(epin[epnum].diepctl & DIEPCTL_EPENA) )
+    {
+      epin[epnum].diepctl |= DIEPCTL_SNAK | (stall ? DIEPCTL_STALL : 0);
+    }
+    else
+    {
+      // Stop transmitting packets and NAK IN xfers.
+      epin[epnum].diepctl |= DIEPCTL_SNAK;
+      while ( (epin[epnum].diepint & DIEPINT_INEPNE) == 0 ) {}
+
+      // Disable the endpoint.
+      epin[epnum].diepctl |= DIEPCTL_EPDIS | (stall ? DIEPCTL_STALL : 0);
+      while ( (epin[epnum].diepint & DIEPINT_EPDISD_Msk) == 0 ) {}
+
+      epin[epnum].diepint = DIEPINT_EPDISD;
+    }
+
+    // Flush the FIFO, and wait until we have confirmed it cleared.
+    dwc2->grstctl = ((epnum << GRSTCTL_TXFNUM_Pos) | GRSTCTL_TXFFLSH);
+    while ( (dwc2->grstctl & GRSTCTL_TXFFLSH_Msk) != 0 ) {}
+  }
+  else
+  {
+    dwc2_epout_t* epout = dwc2->epout;
+
+    // Only disable currently enabled non-control endpoint
+    if ( (epnum == 0) || !(epout[epnum].doepctl & DOEPCTL_EPENA) )
+    {
+      epout[epnum].doepctl |= stall ? DOEPCTL_STALL : 0;
+    }
+    else
+    {
+      // Asserting GONAK is required to STALL an OUT endpoint.
+      // Simpler to use polling here, we don't use the "B"OUTNAKEFF interrupt
+      // anyway, and it can't be cleared by user code. If this while loop never
+      // finishes, we have bigger problems than just the stack.
+      dwc2->dctl |= DCTL_SGONAK;
+      while ( (dwc2->gintsts & GINTSTS_BOUTNAKEFF_Msk) == 0 ) {}
+
+      // Ditto here- disable the endpoint.
+      epout[epnum].doepctl |= DOEPCTL_EPDIS | (stall ? DOEPCTL_STALL : 0);
+      while ( (epout[epnum].doepint & DOEPINT_EPDISD_Msk) == 0 ) {}
+
+      epout[epnum].doepint = DOEPINT_EPDISD;
+
+      // Allow other OUT endpoints to keep receiving.
+      dwc2->dctl |= DCTL_CGONAK;
+    }
+  }
+}
+
+/**
+ * Close an endpoint.
+ */
+void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr)
+{
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+
+  uint8_t const epnum = tu_edpt_number(ep_addr);
+  uint8_t const dir   = tu_edpt_dir(ep_addr);
+
+  dcd_edpt_disable(rhport, ep_addr, false);
+
+  // Update max_size
+  xfer_status[epnum][dir].max_size = 0;  // max_size = 0 marks a disabled EP - required for changing FIFO allocation
+
+  if (dir == TUSB_DIR_IN)
+  {
+    uint16_t const fifo_size = (dwc2->dieptxf[epnum - 1] & DIEPTXF_INEPTXFD_Msk) >> DIEPTXF_INEPTXFD_Pos;
+    uint16_t const fifo_start = (dwc2->dieptxf[epnum - 1] & DIEPTXF_INEPTXSA_Msk) >> DIEPTXF_INEPTXSA_Pos;
+
+    // For now only the last opened endpoint can be closed without fuss.
+    TU_ASSERT(fifo_start == _dwc2_controller[rhport].ep_fifo_size/4 - _allocated_fifo_words_tx,);
+    _allocated_fifo_words_tx -= fifo_size;
+  }
+  else
+  {
+    _out_ep_closed = true;     // Set flag such that RX FIFO gets reduced in size once RX FIFO is empty
+  }
+}
+
+void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr)
+{
+  dcd_edpt_disable(rhport, ep_addr, true);
+}
+
+void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr)
+{
+  (void) rhport;
+
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+
+  uint8_t const epnum = tu_edpt_number(ep_addr);
+  uint8_t const dir   = tu_edpt_dir(ep_addr);
+
+  // Clear stall and reset data toggle
+  if ( dir == TUSB_DIR_IN )
+  {
+    dwc2->epin[epnum].diepctl &= ~DIEPCTL_STALL;
+    dwc2->epin[epnum].diepctl |= DIEPCTL_SD0PID_SEVNFRM;
+  }
+  else
+  {
+    dwc2->epout[epnum].doepctl &= ~DOEPCTL_STALL;
+    dwc2->epout[epnum].doepctl |= DOEPCTL_SD0PID_SEVNFRM;
+  }
+}
+
+/*------------------------------------------------------------------*/
+
+// Read a single data packet from receive FIFO
+static void read_fifo_packet(uint8_t rhport, uint8_t * dst, uint16_t len)
+{
+  (void) rhport;
+
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+  volatile const uint32_t * rx_fifo = dwc2->fifo[0];
+
+  // Reading full available 32 bit words from fifo
+  uint16_t full_words = len >> 2;
+  while(full_words--)
+  {
+    tu_unaligned_write32(dst, *rx_fifo);
+    dst += 4;
+  }
+
+  // Read the remaining 1-3 bytes from fifo
+  uint8_t const bytes_rem = len & 0x03;
+  if ( bytes_rem != 0 )
+  {
+    uint32_t const tmp = *rx_fifo;
+    dst[0] = tu_u32_byte0(tmp);
+    if ( bytes_rem > 1 ) dst[1] = tu_u32_byte1(tmp);
+    if ( bytes_rem > 2 ) dst[2] = tu_u32_byte2(tmp);
+  }
+}
+
+// Write a single data packet to EPIN FIFO
+static void write_fifo_packet(uint8_t rhport, uint8_t fifo_num, uint8_t const * src, uint16_t len)
+{
+  (void) rhport;
+
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+  volatile uint32_t * tx_fifo = dwc2->fifo[fifo_num];
+
+  // Pushing full available 32 bit words to fifo
+  uint16_t full_words = len >> 2;
+  while(full_words--)
+  {
+    *tx_fifo = tu_unaligned_read32(src);
+    src += 4;
+  }
+
+  // Write the remaining 1-3 bytes into fifo
+  uint8_t const bytes_rem = len & 0x03;
+  if ( bytes_rem )
+  {
+    uint32_t tmp_word = src[0];
+    if ( bytes_rem > 1 ) tmp_word |= (src[1] << 8);
+    if ( bytes_rem > 2 ) tmp_word |= (src[2] << 16);
+
+    *tx_fifo = tmp_word;
+  }
+}
+
+static void handle_rxflvl_irq(uint8_t rhport)
+{
+  dwc2_regs_t * dwc2 = DWC2_REG(rhport);
+  volatile uint32_t const * rx_fifo = dwc2->fifo[0];
+
+  // Pop control word off FIFO
+  uint32_t const ctl_word = dwc2->grxstsp;
+  uint8_t  const pktsts   = (ctl_word & GRXSTSP_PKTSTS_Msk ) >> GRXSTSP_PKTSTS_Pos;
+  uint8_t  const epnum    = (ctl_word & GRXSTSP_EPNUM_Msk  ) >> GRXSTSP_EPNUM_Pos;
+  uint16_t const bcnt     = (ctl_word & GRXSTSP_BCNT_Msk   ) >> GRXSTSP_BCNT_Pos;
+
+  dwc2_epout_t* epout = &dwc2->epout[epnum];
+
+//#if CFG_TUSB_DEBUG >= DWC2_DEBUG
+//  const char * pktsts_str[] =
+//  {
+//    "ASSERT", "Global NAK (ISR)", "Out Data Received", "Out Transfer Complete (ISR)",
+//    "Setup Complete (ISR)", "ASSERT", "Setup Data Received"
+//  };
+//  TU_LOG_LOCATION();
+//  TU_LOG(DWC2_DEBUG, "  EP %02X, Byte Count %u, %s\r\n", epnum, bcnt, pktsts_str[pktsts]);
+//  TU_LOG(DWC2_DEBUG, "  daint = %08lX, doepint = %04X\r\n", (unsigned long) dwc2->daint, (unsigned int) epout->doepint);
+//#endif
+
+  switch ( pktsts )
+  {
+    // Global OUT NAK: do nothing
+    case GRXSTS_PKTSTS_GLOBALOUTNAK: break;
+
+    case GRXSTS_PKTSTS_SETUPRX:
+      // Setup packet received
+
+      // We can receive up to three setup packets in succession, but
+      // only the last one is valid.
+      _setup_packet[0] = (*rx_fifo);
+      _setup_packet[1] = (*rx_fifo);
+    break;
+
+    case GRXSTS_PKTSTS_SETUPDONE:
+      // Setup packet done (Interrupt)
+      epout->doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos);
+    break;
+
+    case GRXSTS_PKTSTS_OUTRX:
+    {
+      // Out packet received
+      xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
+
+      // Read packet off RxFIFO
+      if ( xfer->ff )
+      {
+        // Ring buffer
+        tu_fifo_write_n_const_addr_full_words(xfer->ff, (const void*) (uintptr_t) rx_fifo, bcnt);
+      }
+      else
+      {
+        // Linear buffer
+        read_fifo_packet(rhport, xfer->buffer, bcnt);
+
+        // Increment pointer to xfer data
+        xfer->buffer += bcnt;
+      }
+
+      // Truncate transfer length in case of short packet
+      if ( bcnt < xfer->max_size )
+      {
+        xfer->total_len -= (epout->doeptsiz & DOEPTSIZ_XFRSIZ_Msk) >> DOEPTSIZ_XFRSIZ_Pos;
+        if ( epnum == 0 )
+        {
+          xfer->total_len -= ep0_pending[TUSB_DIR_OUT];
+          ep0_pending[TUSB_DIR_OUT] = 0;
+        }
+      }
+    }
+    break;
+
+    // Out packet done (Interrupt)
+    case GRXSTS_PKTSTS_OUTDONE:
+        // Occurred on STM32L47 with dwc2 version 3.10a but not found on other version like 2.80a or 3.30a
+        // May (or not) be 3.10a specific feature/bug or depending on MCU configuration
+        // XFRC complete is additionally generated when
+        // - setup packet is received
+        // - complete the data stage of control write is complete
+        if ((epnum == 0) && (bcnt == 0) && (dwc2->gsnpsid >= DWC2_CORE_REV_3_00a))
+        {
+          uint32_t doepint = epout->doepint;
+
+          if (doepint & (DOEPINT_STPKTRX | DOEPINT_OTEPSPR))
+          {
+            // skip this "no-data" transfer complete event
+            // Note: STPKTRX will be clear later by setup received handler
+            uint32_t clear_flags = DOEPINT_XFRC;
+
+            if (doepint & DOEPINT_OTEPSPR) clear_flags |= DOEPINT_OTEPSPR;
+
+            epout->doepint = clear_flags;
+
+            // TU_LOG(DWC2_DEBUG, "  FIX extra transfer complete on setup/data compete\r\n");
+          }
+        }
+    break;
+
+    default:    // Invalid
+      TU_BREAKPOINT();
+    break;
+  }
+}
+
+static void handle_epout_irq (uint8_t rhport)
+{
+  dwc2_regs_t * dwc2     = DWC2_REG(rhport);
+  uint8_t const ep_count = _dwc2_controller[rhport].ep_count;
+
+  // DAINT for a given EP clears when DOEPINTx is cleared.
+  // OEPINT will be cleared when DAINT's out bits are cleared.
+  for ( uint8_t n = 0; n < ep_count; n++ )
+  {
+    if ( dwc2->daint & TU_BIT(DAINT_OEPINT_Pos + n) )
+    {
+      dwc2_epout_t* epout = &dwc2->epout[n];
+
+      uint32_t const doepint = epout->doepint;
+
+      // SETUP packet Setup Phase done.
+      if ( doepint & DOEPINT_STUP )
+      {
+        uint32_t clear_flag = DOEPINT_STUP;
+
+        // STPKTRX is only available for version from 3_00a
+        if ((doepint & DOEPINT_STPKTRX) && (dwc2->gsnpsid >= DWC2_CORE_REV_3_00a))
+        {
+          clear_flag |= DOEPINT_STPKTRX;
+        }
+
+        epout->doepint = clear_flag;
+        dcd_event_setup_received(rhport, (uint8_t*) _setup_packet, true);
+      }
+
+      // OUT XFER complete
+      if ( epout->doepint & DOEPINT_XFRC )
+      {
+        epout->doepint = DOEPINT_XFRC;
+
+        xfer_ctl_t *xfer = XFER_CTL_BASE(n, TUSB_DIR_OUT);
+
+        // EP0 can only handle one packet
+        if ( (n == 0) && ep0_pending[TUSB_DIR_OUT] )
+        {
+          // Schedule another packet to be received.
+          edpt_schedule_packets(rhport, n, TUSB_DIR_OUT, 1, ep0_pending[TUSB_DIR_OUT]);
+        }
+        else
+        {
+          dcd_event_xfer_complete(rhport, n, xfer->total_len, XFER_RESULT_SUCCESS, true);
+        }
+      }
+    }
+  }
+}
+
+static void handle_epin_irq (uint8_t rhport)
+{
+  dwc2_regs_t * dwc2     = DWC2_REG(rhport);
+  uint8_t const ep_count = _dwc2_controller[rhport].ep_count;
+  dwc2_epin_t* epin      = dwc2->epin;
+
+  // DAINT for a given EP clears when DIEPINTx is cleared.
+  // IEPINT will be cleared when DAINT's out bits are cleared.
+  for ( uint8_t n = 0; n < ep_count; n++ )
+  {
+    if ( dwc2->daint & TU_BIT(DAINT_IEPINT_Pos + n) )
+    {
+      // IN XFER complete (entire xfer).
+      xfer_ctl_t *xfer = XFER_CTL_BASE(n, TUSB_DIR_IN);
+
+      if ( epin[n].diepint & DIEPINT_XFRC )
+      {
+        epin[n].diepint = DIEPINT_XFRC;
+
+        // EP0 can only handle one packet
+        if ( (n == 0) && ep0_pending[TUSB_DIR_IN] )
+        {
+          // Schedule another packet to be transmitted.
+          edpt_schedule_packets(rhport, n, TUSB_DIR_IN, 1, ep0_pending[TUSB_DIR_IN]);
+        }
+        else
+        {
+          dcd_event_xfer_complete(rhport, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true);
+        }
+      }
+
+      // XFER FIFO empty
+      if ( (epin[n].diepint & DIEPINT_TXFE) && (dwc2->diepempmsk & (1 << n)) )
+      {
+        // diepint's TXFE bit is read-only, software cannot clear it.
+        // It will only be cleared by hardware when written bytes is more than
+        // - 64 bytes or
+        // - Half of TX FIFO size (configured by DIEPTXF)
+
+        uint16_t remaining_packets = (epin[n].dieptsiz & DIEPTSIZ_PKTCNT_Msk) >> DIEPTSIZ_PKTCNT_Pos;
+
+        // Process every single packet (only whole packets can be written to fifo)
+        for ( uint16_t i = 0; i < remaining_packets; i++ )
+        {
+          uint16_t const remaining_bytes = (epin[n].dieptsiz & DIEPTSIZ_XFRSIZ_Msk) >> DIEPTSIZ_XFRSIZ_Pos;
+
+          // Packet can not be larger than ep max size
+          uint16_t const packet_size = tu_min16(remaining_bytes, xfer->max_size);
+
+          // It's only possible to write full packets into FIFO. Therefore DTXFSTS register of current
+          // EP has to be checked if the buffer can take another WHOLE packet
+          if ( packet_size > ((epin[n].dtxfsts & DTXFSTS_INEPTFSAV_Msk) << 2) ) break;
+
+          // Push packet to Tx-FIFO
+          if ( xfer->ff )
+          {
+            volatile uint32_t *tx_fifo = dwc2->fifo[n];
+            tu_fifo_read_n_const_addr_full_words(xfer->ff, (void*) (uintptr_t) tx_fifo, packet_size);
+          }
+          else
+          {
+            write_fifo_packet(rhport, n, xfer->buffer, packet_size);
+
+            // Increment pointer to xfer data
+            xfer->buffer += packet_size;
+          }
+        }
+
+        // Turn off TXFE if all bytes are written.
+        if ( ((epin[n].dieptsiz & DIEPTSIZ_XFRSIZ_Msk) >> DIEPTSIZ_XFRSIZ_Pos) == 0 )
+        {
+          dwc2->diepempmsk &= ~(1 << n);
+        }
+      }
+    }
+  }
+}
+
+void dcd_int_handler(uint8_t rhport)
+{
+  dwc2_regs_t *dwc2 = DWC2_REG(rhport);
+
+  uint32_t const int_mask = dwc2->gintmsk;
+  uint32_t const int_status = dwc2->gintsts & int_mask;
+
+  if(int_status & GINTSTS_USBRST)
+  {
+    // USBRST is start of reset.
+    dwc2->gintsts = GINTSTS_USBRST;
+#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
+    _allocated_fifos = 1;
+#endif
+    bus_reset(rhport);
+  }
+
+  if(int_status & GINTSTS_ENUMDNE)
+  {
+    // ENUMDNE is the end of reset where speed of the link is detected
+
+    dwc2->gintsts = GINTSTS_ENUMDNE;
+
+    tusb_speed_t speed;
+    switch ((dwc2->dsts & DSTS_ENUMSPD_Msk) >> DSTS_ENUMSPD_Pos)
+    {
+      case DSTS_ENUMSPD_HS:
+        speed = TUSB_SPEED_HIGH;
+      break;
+
+      case DSTS_ENUMSPD_LS:
+        speed = TUSB_SPEED_LOW;
+      break;
+
+      case DSTS_ENUMSPD_FS_HSPHY:
+      case DSTS_ENUMSPD_FS:
+      default:
+        speed = TUSB_SPEED_FULL;
+      break;
+    }
+
+    dcd_event_bus_reset(rhport, speed, true);
+  }
+
+  if(int_status & GINTSTS_USBSUSP)
+  {
+    dwc2->gintsts = GINTSTS_USBSUSP;
+    //dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true);
+    dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true);
+#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
+    _allocated_fifos = 1;
+#endif
+  }
+
+  if(int_status & GINTSTS_WKUINT)
+  {
+    dwc2->gintsts = GINTSTS_WKUINT;
+    dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true);
+  }
+
+  // TODO check GINTSTS_DISCINT for disconnect detection
+  // if(int_status & GINTSTS_DISCINT)
+
+  if(int_status & GINTSTS_OTGINT)
+  {
+    // OTG INT bit is read-only
+    uint32_t const otg_int = dwc2->gotgint;
+
+    if (otg_int & GOTGINT_SEDET)
+    {
+      dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true);
+#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
+      _allocated_fifos = 1;
+#endif
+    }
+
+    dwc2->gotgint = otg_int;
+  }
+
+  if(int_status & GINTSTS_SOF)
+  {
+    dwc2->gotgint = GINTSTS_SOF;
+
+    if (_sof_en)
+    {
+      uint32_t frame = (dwc2->dsts & (DSTS_FNSOF)) >> 8;
+      dcd_event_sof(rhport, frame, true);
+    }
+    else
+    {
+      // Disable SOF interrupt if SOF was not explicitly enabled. SOF was used for remote wakeup detection
+      dwc2->gintmsk &= ~GINTMSK_SOFM;
+    }
+
+    dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true);
+  }
+
+  // RxFIFO non-empty interrupt handling.
+  if(int_status & GINTSTS_RXFLVL)
+  {
+    // RXFLVL bit is read-only
+
+    // Mask out RXFLVL while reading data from FIFO
+    dwc2->gintmsk &= ~GINTMSK_RXFLVLM;
+
+    // Loop until all available packets were handled
+    do
+    {
+      handle_rxflvl_irq(rhport);
+    } while(dwc2->gotgint & GINTSTS_RXFLVL);
+
+    // Manage RX FIFO size
+    if (_out_ep_closed)
+    {
+      update_grxfsiz(rhport);
+
+      // Disable flag
+      _out_ep_closed = false;
+    }
+
+    dwc2->gintmsk |= GINTMSK_RXFLVLM;
+  }
+
+  // OUT endpoint interrupt handling.
+  if(int_status & GINTSTS_OEPINT)
+  {
+    // OEPINT is read-only, clear using DOEPINTn
+    handle_epout_irq(rhport);
+  }
+
+  // IN endpoint interrupt handling.
+  if(int_status & GINTSTS_IEPINT)
+  {
+    // IEPINT bit read-only, clear using DIEPINTn
+    handle_epin_irq(rhport);
+  }
+
+  //  // Check for Incomplete isochronous IN transfer
+  //  if(int_status & GINTSTS_IISOIXFR) {
+  //    printf("      IISOIXFR!\r\n");
+  ////    TU_LOG(DWC2_DEBUG, "      IISOIXFR!\r\n");
+  //  }
+}
+
+#endif
diff --git a/components/arduino_tinyusb/src/dcd_esp32sx.c b/components/arduino_tinyusb/src/dcd_esp32sx.c
index 048b44e61..cfccf95ae 100755
--- a/components/arduino_tinyusb/src/dcd_esp32sx.c
+++ b/components/arduino_tinyusb/src/dcd_esp32sx.c
@@ -28,17 +28,16 @@
 
 #include "tusb_option.h"
 
-#if (((CFG_TUSB_MCU == OPT_MCU_ESP32S2) ||  (CFG_TUSB_MCU == OPT_MCU_ESP32S3)) && TUSB_OPT_DEVICE_ENABLED)
+#if (((CFG_TUSB_MCU == OPT_MCU_ESP32S2) ||  (CFG_TUSB_MCU == OPT_MCU_ESP32S3)) && CFG_TUD_ENABLED)
 
 // Espressif
-#include "driver/periph_ctrl.h"
 #include "freertos/xtensa_api.h"
 #include "esp_intr_alloc.h"
 #include "esp_log.h"
-#include "driver/gpio.h"
 #include "soc/dport_reg.h"
 #include "soc/gpio_sig_map.h"
 #include "soc/usb_periph.h"
+#include "soc/periph_defs.h" // for interrupt source
 
 #include "device/dcd.h"
 
@@ -60,6 +59,7 @@ typedef struct {
     uint16_t queued_len;
     uint16_t max_size;
     bool short_packet;
+    uint8_t interval;
 } xfer_ctl_t;
 
 static const char *TAG = "TUSB:DCD";
@@ -284,6 +284,14 @@ void dcd_disconnect(uint8_t rhport)
   USB0.dctl |= USB_SFTDISCON_M;
 }
 
+void dcd_sof_enable(uint8_t rhport, bool en)
+{
+  (void) rhport;
+  (void) en;
+
+  // TODO implement later
+}
+
 /*------------------------------------------------------------------*/
 /* DCD Endpoint port
  *------------------------------------------------------------------*/
@@ -303,6 +311,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_edpt)
 
   xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, dir);
   xfer->max_size = tu_edpt_packet_size(desc_edpt);
+  xfer->interval = desc_edpt->bInterval;
 
   if (dir == TUSB_DIR_OUT) {
     out_ep[epnum].doepctl &= ~(USB_D_EPTYPE0_M | USB_D_MPS0_M);
@@ -423,6 +432,13 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
     USB0.in_ep_reg[epnum].dieptsiz = (num_packets << USB_D_PKTCNT0_S) | total_bytes;
     USB0.in_ep_reg[epnum].diepctl |= USB_D_EPENA1_M | USB_D_CNAK1_M; // Enable | CNAK
 
+    // For ISO endpoint with interval=1 set correct DATA0/DATA1 bit for next frame
+    if ((USB0.in_ep_reg[epnum].diepctl & USB_D_EPTYPE0_M) == (1 << USB_D_EPTYPE1_S) && xfer->interval == 1) {
+      // Take odd/even bit from frame counter.
+      uint32_t const odd_frame_now = (USB0.dsts & (1u << USB_SOFFN_S));
+      USB0.in_ep_reg[epnum].diepctl |= (odd_frame_now ? USB_DI_SETD0PID1 : USB_DI_SETD1PID1);
+    }
+
     // Enable fifo empty interrupt only if there are something to put in the fifo.
     if(total_bytes != 0) {
       USB0.dtknqr4_fifoemptymsk |= (1 << epnum);
@@ -431,6 +447,13 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
     // Each complete packet for OUT xfers triggers XFRC.
     USB0.out_ep_reg[epnum].doeptsiz |= USB_PKTCNT0_M | ((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S);
     USB0.out_ep_reg[epnum].doepctl  |= USB_EPENA0_M | USB_CNAK0_M;
+
+    // For ISO endpoint with interval=1 set correct DATA0/DATA1 bit for next frame
+    if ((USB0.out_ep_reg[epnum].doepctl & USB_D_EPTYPE0_M) == (1 << USB_D_EPTYPE1_S) && xfer->interval == 1) {
+      // Take odd/even bit from frame counter.
+      uint32_t const odd_frame_now = (USB0.dsts & (1u << USB_SOFFN_S));
+      USB0.out_ep_reg[epnum].doepctl |= (odd_frame_now ? USB_DO_SETD0PID1 : USB_DO_SETD1PID1);
+    }
   }
   return true;
 }
@@ -459,7 +482,8 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
     } else {
       // Stop transmitting packets and NAK IN xfers.
       in_ep[epnum].diepctl |= USB_DI_SNAK1_M;
-      while ((in_ep[epnum].diepint & USB_DI_SNAK1_M) == 0) ;
+      // while ((in_ep[epnum].diepint & USB_DI_SNAK1_M) == 0) ;
+      while ((in_ep[epnum].diepint & USB_D_INEPNAKEFF1_M) == 0) ;
 
       // Disable the endpoint. Note that both SNAK and STALL are set here.
       in_ep[epnum].diepctl |= (USB_DI_SNAK1_M | USB_D_STALL1_M | USB_D_EPDIS1_M);
@@ -469,9 +493,16 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
 
     // Flush the FIFO, and wait until we have confirmed it cleared.
     uint8_t const fifo_num = ((in_ep[epnum].diepctl >> USB_D_TXFNUM1_S) & USB_D_TXFNUM1_V);
-    USB0.grstctl |= (fifo_num << USB_TXFNUM_S);
-    USB0.grstctl |= USB_TXFFLSH_M;
+    // USB0.grstctl |= (fifo_num << USB_TXFNUM_S);
+    // USB0.grstctl |= USB_TXFFLSH_M;
+    // while ((USB0.grstctl & USB_TXFFLSH_M) != 0) ;
+    uint32_t rstctl_last = USB0.grstctl;
+    uint32_t rstctl = USB_TXFFLSH_M;
+    rstctl |= (fifo_num << USB_TXFNUM_S);
+    USB0.grstctl = rstctl;
     while ((USB0.grstctl & USB_TXFFLSH_M) != 0) ;
+    USB0.grstctl = rstctl_last;
+    // TODO: Clear grstctl::fifo_num after fifo flsh
   } else {
     // Only disable currently enabled non-control endpoint
     if ((epnum == 0) || !(out_ep[epnum].doepctl & USB_EPENA0_M)) {
diff --git a/configs/builds.json b/configs/builds.json
index c32e465e4..ef0943054 100644
--- a/configs/builds.json
+++ b/configs/builds.json
@@ -4,7 +4,13 @@
 			"file":"libspi_flash.a",
 			"src":"build/esp-idf/spi_flash/libspi_flash.a",
 			"out":"lib/libspi_flash.a",
-			"targets":["esp32","esp32c3","esp32s2","esp32s3"]
+			"targets":["esp32","esp32c3","esp32s2","esp32s3","esp32c6","esp32h2"]
+		},
+		{
+			"file":"libesp_psram.a",
+			"src":"build/esp-idf/esp_psram/libesp_psram.a",
+			"out":"lib/libesp_psram.a",
+			"targets":["esp32s3"]
 		},
 		{
 			"file":"libesp_system.a",
@@ -39,41 +45,31 @@
 	],
 	"targets":[
 		{
-			"target": "esp32s3",
+			"target": "esp32h2",
 			"features":[],
-			"idf_libs":["qio","80m","qio_ram"],
+			"idf_libs":["qio","64m"],
 			"bootloaders":[
-				["qio","120m","qio_ram"],
-				["qio","80m","qio_ram"],
-				["dio","80m","qio_ram"],
-				["opi","80m","opi_ram"]
+				["qio","64m"],
+				["dio","64m"],
+				["qio","16m"],
+				["dio","16m"]
 			],
 			"mem_variants":[
-				["qio","80m","opi_ram"],
-				["dio","80m","qio_ram"],
-				["dio","80m","opi_ram"],
-				["opi","80m","opi_ram"],
-				["opi","80m","qio_ram"]
+				["dio","64m"]
 			]
 		},
 		{
-			"target": "esp32s2",
-			"features":["qio_ram"],
+			"target": "esp32c6",
+			"features":[],
 			"idf_libs":["qio","80m"],
 			"bootloaders":[
 				["qio","80m"],
-				["qout","80m"],
 				["dio","80m"],
-				["dout","80m"],
 				["qio","40m"],
-				["qout","40m"],
-				["dio","40m"],
-				["dout","40m"]
+				["dio","40m"]
 			],
 			"mem_variants":[
-				["qout","80m"],
-				["dio","80m"],
-				["dout","80m"]
+				["dio","80m"]
 			]
 		},
 		{
@@ -82,18 +78,12 @@
 			"idf_libs":["qio","80m"],
 			"bootloaders":[
 				["qio","80m"],
-				["qout","80m"],
 				["dio","80m"],
-				["dout","80m"],
 				["qio","40m"],
-				["qout","40m"],
-				["dio","40m"],
-				["dout","40m"]
+				["dio","40m"]
 			],
 			"mem_variants":[
-				["qout","80m"],
-				["dio","80m"],
-				["dout","80m"]
+				["dio","80m"]
 			]
 		},
 		{
@@ -102,18 +92,44 @@
 			"idf_libs":["qio","80m"],
 			"bootloaders":[
 				["qio","80m"],
-				["qout","80m"],
 				["dio","80m"],
-				["dout","80m"],
 				["qio","40m"],
-				["qout","40m"],
-				["dio","40m"],
-				["dout","40m"]
+				["dio","40m"]
 			],
 			"mem_variants":[
-				["qout","80m"],
+				["dio","80m"]
+			]
+		},
+		{
+			"target": "esp32s2",
+			"features":["qio_ram"],
+			"idf_libs":["qio","80m"],
+			"bootloaders":[
+				["qio","80m"],
 				["dio","80m"],
-				["dout","80m"]
+				["qio","40m"],
+				["dio","40m"]
+			],
+			"mem_variants":[
+				["dio","80m"]
+			]
+		},
+		{
+			"target": "esp32s3",
+			"features":["esp_sr"],
+			"idf_libs":["qio","80m","qio_ram"],
+			"bootloaders":[
+				["qio","120m","qio_ram"],
+				["qio","80m","qio_ram"],
+				["dio","80m","qio_ram"],
+				["opi","80m","opi_ram"]
+			],
+			"mem_variants":[
+				["qio","80m","opi_ram"],
+				["dio","80m","qio_ram"],
+				["dio","80m","opi_ram"],
+				["opi","80m","opi_ram"],
+				["opi","80m","qio_ram"]
 			]
 		}
 	]
diff --git a/configs/defconfig.16m b/configs/defconfig.16m
new file mode 100644
index 000000000..b7916fbce
--- /dev/null
+++ b/configs/defconfig.16m
@@ -0,0 +1 @@
+CONFIG_ESPTOOLPY_FLASHFREQ_16M=y
\ No newline at end of file
diff --git a/configs/defconfig.64m b/configs/defconfig.64m
new file mode 100644
index 000000000..c33173f13
--- /dev/null
+++ b/configs/defconfig.64m
@@ -0,0 +1 @@
+CONFIG_ESPTOOLPY_FLASHFREQ_64M=y
\ No newline at end of file
diff --git a/configs/defconfig.common b/configs/defconfig.common
index cbcfd8d2c..68ba0ddb0 100644
--- a/configs/defconfig.common
+++ b/configs/defconfig.common
@@ -21,15 +21,16 @@ CONFIG_ESP_TASK_WDT_PANIC=y
 CONFIG_ESP_TIMER_TASK_STACK_SIZE=4096
 CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
 CONFIG_ESP_WIFI_FTM_ENABLE=y
-CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=8
-CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=8
-CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=16
-CONFIG_ESP32_WIFI_CSI_ENABLED=y
-CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y
-# CONFIG_ESP32_WIFI_IRAM_OPT is not set
-# CONFIG_ESP32_WIFI_RX_IRAM_OPT is not set
+CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=8
+CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=8
+CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM=16
+CONFIG_ESP_WIFI_CSI_ENABLED=y
+CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y
+# CONFIG_ESP_WIFI_IRAM_OPT is not set
+# CONFIG_ESP_WIFI_RX_IRAM_OPT is not set
 CONFIG_ETH_SPI_ETHERNET_DM9051=y
 CONFIG_ETH_SPI_ETHERNET_W5500=y
+CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL=y
 CONFIG_FATFS_CODEPAGE_850=y
 CONFIG_FATFS_LFN_STACK=y
 # CONFIG_FATFS_API_ENCODING_ANSI_OEM is not set
@@ -37,6 +38,7 @@ CONFIG_FATFS_API_ENCODING_UTF_8=y
 # CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT is not set
 CONFIG_FMB_TIMER_PORT_ENABLED=y
 CONFIG_FREERTOS_HZ=1000
+CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y
 # CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION is not set
 CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1024
 CONFIG_HEAP_POISONING_LIGHT=y
@@ -75,7 +77,7 @@ CONFIG_LWIP_IPV6_AUTOCONFIG=y
 CONFIG_ESP_RMAKER_SKIP_VERSION_CHECK=y
 CONFIG_ESP_RMAKER_USER_ID_CHECK=y
 CONFIG_ESP_INSIGHTS_ENABLED=y
-CONFIG_ESP_INSIGHTS_COREDUMP_ENABLE=y
+CONFIG_ESP_INSIGHTS_COREDUMP_ENABLE=n
 CONFIG_ESP_INSIGHTS_TRANSPORT_HTTPS=y
 CONFIG_DIAG_LOG_DROP_WIFI_LOGS=y
 CONFIG_DIAG_ENABLE_METRICS=y
@@ -84,7 +86,7 @@ CONFIG_DIAG_ENABLE_WIFI_METRICS=y
 CONFIG_DIAG_ENABLE_VARIABLES=y
 CONFIG_DIAG_ENABLE_NETWORK_VARIABLES=y
 CONFIG_ESP_COREDUMP_ENABLE=y
-CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y
+CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n
 CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y
 CONFIG_ESP_COREDUMP_CHECKSUM_CRC32=y
 CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=64
diff --git a/configs/defconfig.esp32 b/configs/defconfig.esp32
index bd38b4829..65bc3f0c3 100644
--- a/configs/defconfig.esp32
+++ b/configs/defconfig.esp32
@@ -8,10 +8,9 @@ CONFIG_BT_SPP_ENABLED=y
 CONFIG_BT_HFP_ENABLE=y
 CONFIG_BT_STACK_NO_LOG=y
 CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y
-CONFIG_ESP32_SPIRAM_SUPPORT=y
+CONFIG_SPIRAM=y
 CONFIG_SPIRAM_OCCUPY_HSPI_HOST=y
-CONFIG_ESP32_ULP_COPROC_ENABLED=y
-CONFIG_ESP32_XTAL_FREQ_AUTO=y
+CONFIG_ULP_COPROC_ENABLED=y
 # CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 is not set
 CONFIG_FREERTOS_FPU_IN_ISR=y
 # CONFIG_USE_WAKENET is not set
diff --git a/configs/defconfig.esp32c3 b/configs/defconfig.esp32c3
index 31c42f7a0..ced594733 100644
--- a/configs/defconfig.esp32c3
+++ b/configs/defconfig.esp32c3
@@ -1,4 +1,4 @@
 CONFIG_BT_BLE_BLUFI_ENABLE=y
-CONFIG_ESP32C3_RTC_CLK_CAL_CYCLES=576
+CONFIG_RTC_CLK_CAL_CYCLES=576
 # CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 is not set
 CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=2304
diff --git a/configs/defconfig.esp32c6 b/configs/defconfig.esp32c6
new file mode 100644
index 000000000..ced594733
--- /dev/null
+++ b/configs/defconfig.esp32c6
@@ -0,0 +1,4 @@
+CONFIG_BT_BLE_BLUFI_ENABLE=y
+CONFIG_RTC_CLK_CAL_CYCLES=576
+# CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 is not set
+CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=2304
diff --git a/configs/defconfig.esp32h2 b/configs/defconfig.esp32h2
new file mode 100644
index 000000000..ced594733
--- /dev/null
+++ b/configs/defconfig.esp32h2
@@ -0,0 +1,4 @@
+CONFIG_BT_BLE_BLUFI_ENABLE=y
+CONFIG_RTC_CLK_CAL_CYCLES=576
+# CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 is not set
+CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=2304
diff --git a/configs/defconfig.esp32s2 b/configs/defconfig.esp32s2
index 6dc619a9f..43568afcc 100644
--- a/configs/defconfig.esp32s2
+++ b/configs/defconfig.esp32s2
@@ -1,8 +1,9 @@
 CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
-CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y
-CONFIG_ESP32S2_SPIRAM_SUPPORT=y
+CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
+CONFIG_SPIRAM=y
 CONFIG_ESP32S2_KEEP_USB_ALIVE=y
 # CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 is not set
 # CONFIG_USE_WAKENET is not set
 # CONFIG_USE_MULTINET is not set
-CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
\ No newline at end of file
+CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
+CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n
\ No newline at end of file
diff --git a/configs/defconfig.esp32s3 b/configs/defconfig.esp32s3
index 981e224fd..df73126e2 100644
--- a/configs/defconfig.esp32s3
+++ b/configs/defconfig.esp32s3
@@ -1,13 +1,10 @@
 CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
-CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
-CONFIG_ESP32S3_SPIRAM_SUPPORT=y
-CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=576
+CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
+CONFIG_SPIRAM=y
+CONFIG_RTC_CLK_CAL_CYCLES=576
 CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_TWO=y
 # CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND is not set
 # CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 is not set
-CONFIG_SR_WN_MODEL_WN8_QUANT=y
-CONFIG_SR_WN_WN8_HIESP=y
-CONFIG_SR_MN_ENGLISH=y
-CONFIG_SR_MN_EN_MULTINET5_SINGLE_RECOGNITION_QUANT8=y
 CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
-CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120
\ No newline at end of file
+CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120
+CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n
\ No newline at end of file
diff --git a/configs/defconfig.esp_sr b/configs/defconfig.esp_sr
new file mode 100644
index 000000000..03b7c462e
--- /dev/null
+++ b/configs/defconfig.esp_sr
@@ -0,0 +1,37 @@
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_SR_WN_WN9_HIESP=y
+CONFIG_SR_MN_CN_NONE=y
+CONFIG_SR_MN_EN_MULTINET5_SINGLE_RECOGNITION_QUANT8=y
+CONFIG_EN_SPEECH_COMMAND_ID0=""
+CONFIG_EN_SPEECH_COMMAND_ID1=""
+CONFIG_EN_SPEECH_COMMAND_ID2=""
+CONFIG_EN_SPEECH_COMMAND_ID3=""
+CONFIG_EN_SPEECH_COMMAND_ID4=""
+CONFIG_EN_SPEECH_COMMAND_ID5=""
+CONFIG_EN_SPEECH_COMMAND_ID6=""
+CONFIG_EN_SPEECH_COMMAND_ID7=""
+CONFIG_EN_SPEECH_COMMAND_ID8=""
+CONFIG_EN_SPEECH_COMMAND_ID9=""
+CONFIG_EN_SPEECH_COMMAND_ID10=""
+CONFIG_EN_SPEECH_COMMAND_ID11=""
+CONFIG_EN_SPEECH_COMMAND_ID12=""
+CONFIG_EN_SPEECH_COMMAND_ID13=""
+CONFIG_EN_SPEECH_COMMAND_ID14=""
+CONFIG_EN_SPEECH_COMMAND_ID15=""
+CONFIG_EN_SPEECH_COMMAND_ID16=""
+CONFIG_EN_SPEECH_COMMAND_ID17=""
+CONFIG_EN_SPEECH_COMMAND_ID18=""
+CONFIG_EN_SPEECH_COMMAND_ID19=""
+CONFIG_EN_SPEECH_COMMAND_ID20=""
+CONFIG_EN_SPEECH_COMMAND_ID21=""
+CONFIG_EN_SPEECH_COMMAND_ID22=""
+CONFIG_EN_SPEECH_COMMAND_ID23=""
+CONFIG_EN_SPEECH_COMMAND_ID24=""
+CONFIG_EN_SPEECH_COMMAND_ID25=""
+CONFIG_EN_SPEECH_COMMAND_ID26=""
+CONFIG_EN_SPEECH_COMMAND_ID27=""
+CONFIG_EN_SPEECH_COMMAND_ID28=""
+CONFIG_EN_SPEECH_COMMAND_ID29=""
+CONFIG_EN_SPEECH_COMMAND_ID30=""
+CONFIG_EN_SPEECH_COMMAND_ID31=""
diff --git a/configs/pio_end.txt b/configs/pio_end.txt
new file mode 100644
index 000000000..e7544f1dd
--- /dev/null
+++ b/configs/pio_end.txt
@@ -0,0 +1,10 @@
+        "ARDUINO_ARCH_ESP32",
+        "ESP32",
+        ("F_CPU", "$BOARD_F_CPU"),
+        ("ARDUINO", 10812),
+        ("ARDUINO_VARIANT", '\\"%s\\"' % board_config.get("build.variant").replace('"', "")),
+        ("ARDUINO_BOARD", '\\"%s\\"' % board_config.get("name").replace('"', "")),
+        "ARDUINO_PARTITION_%s" % basename(board_config.get(
+            "build.partitions", "default.csv")).replace(".csv", "").replace("-", "_")
+    ]
+)
diff --git a/configs/pio_start.txt b/configs/pio_start.txt
new file mode 100644
index 000000000..9a38020fb
--- /dev/null
+++ b/configs/pio_start.txt
@@ -0,0 +1,40 @@
+# Copyright 2014-present PlatformIO <contact@platformio.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Arduino
+
+Arduino Wiring-based Framework allows writing cross-platform software to
+control devices attached to a wide range of Arduino boards to create all
+kinds of creative coding, interactive objects, spaces or physical experiences.
+
+http://arduino.cc/en/Reference/HomePage
+"""
+
+# Extends: https://github.com/platformio/platform-espressif32/blob/develop/builder/main.py
+
+from os.path import basename, join
+
+from SCons.Script import DefaultEnvironment
+
+env = DefaultEnvironment()
+
+FRAMEWORK_DIR = env.PioPlatform().get_package_dir("framework-arduinoespressif32")
+FRAMEWORK_SDK_DIR = env.PioPlatform().get_package_dir(
+    "framework-arduinoespressif32-libs"
+)
+
+board_config = env.BoardConfig()
+
+env.Append(
diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild
index 8e6ddef14..a5832f914 100644
--- a/main/Kconfig.projbuild
+++ b/main/Kconfig.projbuild
@@ -10,9 +10,12 @@ config LIB_BUILDER_FLASHFREQ
     string
     default "120m" if ESPTOOLPY_FLASHFREQ_120M
     default "80m" if ESPTOOLPY_FLASHFREQ_80M
+    default "64m" if ESPTOOLPY_FLASHFREQ_64M
     default "40m" if ESPTOOLPY_FLASHFREQ_40M
+    default "32m" if ESPTOOLPY_FLASHFREQ_32M
     default "26m" if ESPTOOLPY_FLASHFREQ_26M
     default "20m" if ESPTOOLPY_FLASHFREQ_20M
+    default "16m" if ESPTOOLPY_FLASHFREQ_16M
 
 config LIB_BUILDER_COMPILE
     bool
diff --git a/main/idf_component.yml b/main/idf_component.yml
new file mode 100644
index 000000000..2ba047577
--- /dev/null
+++ b/main/idf_component.yml
@@ -0,0 +1,48 @@
+dependencies:
+  # Required IDF version
+  idf: ">=5.1"
+
+  mdns: "^1.1.0"
+  chmorgan/esp-libhelix-mp3: "1.0.3"
+  esp-dsp: "^1.3.4"
+
+  # esp-sr: "^1.3.1"
+  # esp32-camera: "^2.0.4"
+  # esp-dl:
+  #   git: https://github.com/espressif/esp-dl.git
+  # espressif/esp_rainmaker:
+  #   path: components/esp_rainmaker
+  #   git: https://github.com/espressif/esp-rainmaker.git
+
+  # # Defining a dependency from the registry:
+  # # https://components.espressif.com/component/example/cmp
+  # example/cmp: "^3.3.3" # Automatically update minor releases
+  #
+  # # Other ways to define dependencies
+  #
+  # # For components maintained by Espressif only name can be used.
+  # # Same as `espressif/cmp`
+  # component: "~1.0.0" # Automatically update bugfix releases
+  #
+  # # Or in a longer form with extra parameters
+  # component2:
+  #   version: ">=2.0.0"
+  #
+  #   # For transient dependencies `public` flag can be set.
+  #   # `public` flag doesn't have an effect for the `main` component.
+  #   # All dependencies of `main` are public by default.
+  #   public: true
+  #
+  #   # For components hosted on non-default registry:
+  #   service_url: "https://componentregistry.company.com"
+  #
+  # # For components in git repository:
+  # test_component:
+  #   path: test_component
+  #   git: ssh://git@gitlab.com/user/components.git
+  #
+  # # For test projects during component development
+  # # components can be used from a local directory
+  # # with relative or absolute path
+  # some_local_component:
+  #   path: ../../projects/component
diff --git a/partitions.csv b/partitions.csv
new file mode 100644
index 000000000..97e41c452
--- /dev/null
+++ b/partitions.csv
@@ -0,0 +1,8 @@
+# Name,   Type, SubType, Offset,  Size, Flags
+nvs,      data, nvs,       0x9000,   0x5000,
+otadata,  data, ota,       0xe000,   0x2000,
+app0,     app,  ota_0,    0x10000, 0x300000,
+app1,     app,  ota_1,   0x310000, 0x300000,
+spiffs,   data, spiffs,  0x610000, 0x700000,
+model,    data, spiffs,  0xD10000, 0x2E0000,
+coredump, data, coredump,0xFF0000,  0x10000,
diff --git a/patches/spi_eth.diff b/patches/spi_eth.diff
new file mode 100644
index 000000000..1e57dbef1
--- /dev/null
+++ b/patches/spi_eth.diff
@@ -0,0 +1,1139 @@
+diff --git a/components/esp_eth/include/esp_eth_mac.h b/components/esp_eth/include/esp_eth_mac.h
+index dfc1af2c73b5e6a872a5c0583e0223ca86b49ae2..885d332d4363e093d84af1a196c532450386b8ce 100644
+--- a/components/esp_eth/include/esp_eth_mac.h
++++ b/components/esp_eth/include/esp_eth_mac.h
+@@ -1,5 +1,5 @@
+ /*
+- * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
++ * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD
+  *
+  * SPDX-License-Identifier: Apache-2.0
+  */
+@@ -470,15 +470,113 @@ typedef struct {
+ esp_eth_mac_t *esp_eth_mac_new_esp32(const eth_esp32_emac_config_t *esp32_config, const eth_mac_config_t *config);
+ #endif // CONFIG_ETH_USE_ESP32_EMAC
+ 
++#if CONFIG_ETH_USE_SPI_ETHERNET
++/**
++ * @brief Custom SPI Driver Configuration.
++ * This structure declares configuration and callback functions to access Ethernet SPI module via
++ * user's custom SPI driver.
++ *
++ */
++typedef struct
++{
++    /**
++     * @brief Custom driver specific configuration data used by `init()` function.
++     *
++     * @note Type and its content is fully under user's control
++     *
++     */
++    void *config;
++
++    /**
++     * @brief Custom driver SPI Initialization
++     *
++     * @param[in] spi_config: Custom driver specific configuration
++     *
++     * @return
++     *      - spi_ctx: when initialization is successful, a pointer to context structure holding all variables
++     *          needed for subsequent SPI access operations (e.g. SPI bus identification, mutexes, etc.)
++     *      - NULL: driver initialization failed
++     *
++     * @note return type and its content is fully under user's control
++     */
++    void *(*init)(const void *spi_config);
++
++    /**
++     * @brief Custom driver De-initialization
++     *
++     * @param[in] spi_ctx: a pointer to driver specific context structure
++     *
++     * @return
++     *      - ESP_OK: driver de-initialization was successful
++     *      - ESP_FAIL: driver de-initialization failed
++     *      - any other failure codes are allowed to be used to provide failure isolation
++     */
++    esp_err_t (*deinit)(void *spi_ctx);
++
++    /**
++     * @brief Custom driver SPI read
++     *
++     * @note The read function is responsible to construct command, address and data fields
++     * of the SPI frame in format expected by particular SPI Ethernet module
++     *
++     * @param[in] spi_ctx: a pointer to driver specific context structure
++     * @param[in] cmd: command
++     * @param[in] addr: register address
++     * @param[out] data: read data
++     * @param[in] data_len: read data length in bytes
++     *
++     * @return
++     *      - ESP_OK: read was successful
++     *      - ESP_FAIL: read failed
++     *      - any other failure codes are allowed to be used to provide failure isolation
++     */
++    esp_err_t (*read)(void *spi_ctx, uint32_t cmd, uint32_t addr, void *data, uint32_t data_len);
++
++    /**
++     * @brief Custom driver SPI write
++     *
++     * @note The write function is responsible to construct command, address and data fields
++     * of the SPI frame in format expected by particular SPI Ethernet module
++     *
++     * @param[in] spi_ctx: a pointer to driver specific context structure
++     * @param[in] cmd: command
++     * @param[in] addr: register address
++     * @param[in] data: data to write
++     * @param[in] data_len: length of data to write in bytes
++     *
++     * @return
++     *      - ESP_OK: write was successful
++     *      - ESP_FAIL: write failed
++     *      - any other failure codes are allowed to be used to provide failure isolation
++     */
++    esp_err_t (*write)(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *data, uint32_t data_len);
++} eth_spi_custom_driver_t;
++
++/**
++ * @brief Default configuration of the custom SPI driver.
++ * Internal ESP-IDF SPI Master driver is used by default.
++ *
++ */
++#define ETH_DEFAULT_SPI  \
++    {                    \
++        .config = NULL,  \
++        .init = NULL,    \
++        .deinit = NULL,  \
++        .read = NULL,    \
++        .write = NULL    \
++    }
++#endif // CONFIG_ETH_USE_SPI_ETHERNET
++
+ #if CONFIG_ETH_SPI_ETHERNET_DM9051
+ /**
+  * @brief DM9051 specific configuration
+  *
+  */
+ typedef struct {
+-    spi_host_device_t spi_host_id;              /*!< SPI peripheral */
+-    spi_device_interface_config_t *spi_devcfg;  /*!< SPI device configuration */
+     int int_gpio_num;                           /*!< Interrupt GPIO number */
++    spi_host_device_t spi_host_id;              /*!< SPI peripheral (this field is invalid when custom SPI driver is defined) */
++    spi_device_interface_config_t *spi_devcfg;  /*!< SPI device configuration (this field is invalid when custom SPI driver is defined) */
++    eth_spi_custom_driver_t custom_spi_driver;  /*!< Custom SPI driver definitions */
+ } eth_dm9051_config_t;
+ 
+ /**
+@@ -487,9 +585,10 @@ typedef struct {
+  */
+ #define ETH_DM9051_DEFAULT_CONFIG(spi_host, spi_devcfg_p) \
+     {                                           \
++        .int_gpio_num = 4,                      \
+         .spi_host_id = spi_host,                \
+         .spi_devcfg = spi_devcfg_p,             \
+-        .int_gpio_num = 4,                      \
++        .custom_spi_driver = ETH_DEFAULT_SPI, \
+     }
+ 
+ /**
+@@ -511,9 +610,10 @@ esp_eth_mac_t *esp_eth_mac_new_dm9051(const eth_dm9051_config_t *dm9051_config,
+  *
+  */
+ typedef struct {
+-    spi_host_device_t spi_host_id;              /*!< SPI peripheral */
+-    spi_device_interface_config_t *spi_devcfg;  /*!< SPI device configuration */
+     int int_gpio_num;                           /*!< Interrupt GPIO number */
++    spi_host_device_t spi_host_id;              /*!< SPI peripheral (this field is invalid when custom SPI driver is defined)*/
++    spi_device_interface_config_t *spi_devcfg;  /*!< SPI device configuration (this field is invalid when custom SPI driver is defined)*/
++    eth_spi_custom_driver_t custom_spi_driver;  /*!< Custom SPI driver definitions */
+ } eth_w5500_config_t;
+ 
+ /**
+@@ -521,10 +621,11 @@ typedef struct {
+  *
+  */
+ #define ETH_W5500_DEFAULT_CONFIG(spi_host, spi_devcfg_p) \
+-    {                                          \
+-        .spi_host_id = spi_host,               \
+-        .spi_devcfg = spi_devcfg_p,            \
+-        .int_gpio_num = 4,                     \
++    {                                           \
++        .int_gpio_num = 4,                      \
++        .spi_host_id = spi_host,                \
++        .spi_devcfg = spi_devcfg_p,             \
++        .custom_spi_driver = ETH_DEFAULT_SPI, \
+     }
+ 
+ /**
+@@ -546,9 +647,10 @@ esp_eth_mac_t *esp_eth_mac_new_w5500(const eth_w5500_config_t *w5500_config, con
+  *
+  */
+ typedef struct {
+-    spi_host_device_t spi_host_id;              /*!< SPI peripheral */
+-    spi_device_interface_config_t *spi_devcfg;  /*!< SPI device configuration */
+     int int_gpio_num;                           /*!< Interrupt GPIO number */
++    spi_host_device_t spi_host_id;              /*!< SPI peripheral (this field is invalid when custom SPI driver is defined) */
++    spi_device_interface_config_t *spi_devcfg;  /*!< SPI device configuration (this field is invalid when custom SPI driver is defined) */
++    eth_spi_custom_driver_t custom_spi_driver;  /*!< Custom SPI driver definitions */
+ } eth_ksz8851snl_config_t;
+ 
+ /**
+@@ -557,9 +659,10 @@ typedef struct {
+  */
+ #define ETH_KSZ8851SNL_DEFAULT_CONFIG(spi_host, spi_devcfg_p) \
+     {                                               \
++        .int_gpio_num = 4,                          \
+         .spi_host_id = spi_host,                    \
+         .spi_devcfg = spi_devcfg_p,                 \
+-        .int_gpio_num = 14,                         \
++        .custom_spi_driver = ETH_DEFAULT_SPI,     \
+     }
+ 
+ /**
+diff --git a/components/esp_eth/src/esp_eth_mac_dm9051.c b/components/esp_eth/src/esp_eth_mac_dm9051.c
+index b0d339cc93d1d6ef39b1e819102c220d431114b2..ba796121b7065ba984cfbc683283e82a6702a04a 100644
+--- a/components/esp_eth/src/esp_eth_mac_dm9051.c
++++ b/components/esp_eth/src/esp_eth_mac_dm9051.c
+@@ -1,5 +1,5 @@
+ /*
+- * SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
++ * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD
+  *
+  * SPDX-License-Identifier: Apache-2.0
+  */
+@@ -47,11 +47,23 @@ typedef struct {
+     uint8_t length_high;
+ } dm9051_rx_header_t;
+ 
++typedef struct {
++    spi_device_handle_t hdl;
++    SemaphoreHandle_t lock;
++} spi_info_t;
++
++typedef struct {
++    void *ctx;
++    void *(*init)(const void *spi_config);
++    esp_err_t (*deinit)(void *spi_ctx);
++    esp_err_t (*read)(void *spi_ctx, uint32_t cmd, uint32_t addr, void *data, uint32_t data_len);
++    esp_err_t (*write)(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *data, uint32_t data_len);
++} spi_interface_t;
++
+ typedef struct {
+     esp_eth_mac_t parent;
+     esp_eth_mediator_t *eth;
+-    spi_device_handle_t spi_hdl;
+-    SemaphoreHandle_t spi_lock;
++    spi_interface_t spi;
+     TaskHandle_t rx_task_hdl;
+     uint32_t sw_reset_timeout_ms;
+     int int_gpio_num;
+@@ -61,89 +73,137 @@ typedef struct {
+     uint8_t *rx_buffer;
+ } emac_dm9051_t;
+ 
+-static inline bool dm9051_lock(emac_dm9051_t *emac)
++static void *dm9051_spi_init(const void *spi_config)
++{
++    void *ret = NULL;
++    eth_dm9051_config_t *dm9051_config = (eth_dm9051_config_t *)spi_config;
++    spi_info_t *spi = calloc(1, sizeof(spi_info_t));
++    ESP_GOTO_ON_FALSE(spi, NULL, err, TAG, "no memory for SPI context data");
++
++    /* SPI device init */
++    spi_device_interface_config_t spi_devcfg;
++    spi_devcfg = *(dm9051_config->spi_devcfg);
++    if (dm9051_config->spi_devcfg->command_bits == 0 && dm9051_config->spi_devcfg->address_bits == 0) {
++        /* configure default SPI frame format */
++        spi_devcfg.command_bits = 1;
++        spi_devcfg.address_bits = 7;
++    } else {
++        ESP_GOTO_ON_FALSE(dm9051_config->spi_devcfg->command_bits == 1 && dm9051_config->spi_devcfg->address_bits == 7,
++                            NULL, err, TAG, "incorrect SPI frame format (command_bits/address_bits)");
++    }
++    ESP_GOTO_ON_FALSE(spi_bus_add_device(dm9051_config->spi_host_id, &spi_devcfg, &spi->hdl) == ESP_OK,
++                                            NULL, err, TAG, "adding device to SPI host #%d failed", dm9051_config->spi_host_id + 1);
++
++    /* create mutex */
++    spi->lock = xSemaphoreCreateMutex();
++    ESP_GOTO_ON_FALSE(spi->lock, NULL, err, TAG, "create lock failed");
++
++    ret = spi;
++    return ret;
++err:
++    if (spi) {
++        if (spi->lock) {
++            vSemaphoreDelete(spi->lock);
++        }
++        free(spi);
++    }
++    return ret;
++}
++
++static esp_err_t dm9051_spi_deinit(void *spi_ctx)
+ {
+-    return xSemaphoreTake(emac->spi_lock, pdMS_TO_TICKS(DM9051_SPI_LOCK_TIMEOUT_MS)) == pdTRUE;
++    esp_err_t ret = ESP_OK;
++    spi_info_t *spi = (spi_info_t *)spi_ctx;
++
++    spi_bus_remove_device(spi->hdl);
++    vSemaphoreDelete(spi->lock);
++
++    free(spi);
++    return ret;
+ }
+ 
+-static inline bool dm9051_unlock(emac_dm9051_t *emac)
++static inline bool dm9051_spi_lock(spi_info_t *spi)
+ {
+-    return xSemaphoreGive(emac->spi_lock) == pdTRUE;
++    return xSemaphoreTake(spi->lock, pdMS_TO_TICKS(DM9051_SPI_LOCK_TIMEOUT_MS)) == pdTRUE;
+ }
+ 
+-/**
+- * @brief write value to dm9051 internal register
+- */
+-static esp_err_t dm9051_register_write(emac_dm9051_t *emac, uint8_t reg_addr, uint8_t value)
++static inline bool dm9051_spi_unlock(spi_info_t *spi)
++{
++    return xSemaphoreGive(spi->lock) == pdTRUE;
++}
++
++static esp_err_t dm9051_spi_write(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *value, uint32_t len)
+ {
+     esp_err_t ret = ESP_OK;
++    spi_info_t *spi = (spi_info_t *)spi_ctx;
++
+     spi_transaction_t trans = {
+-        .cmd = DM9051_SPI_WR,
+-        .addr = reg_addr,
+-        .length = 8,
+-        .flags = SPI_TRANS_USE_TXDATA
++        .cmd = cmd,
++        .addr = addr,
++        .length = 8 * len,
++        .tx_buffer = value
+     };
+-    trans.tx_data[0] = value;
+-    if (dm9051_lock(emac)) {
+-        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
++    if (dm9051_spi_lock(spi)) {
++        if (spi_device_polling_transmit(spi->hdl, &trans) != ESP_OK) {
+             ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+             ret = ESP_FAIL;
+         }
+-        dm9051_unlock(emac);
++        dm9051_spi_unlock(spi);
+     } else {
+         ret = ESP_ERR_TIMEOUT;
+     }
+     return ret;
+ }
+ 
+-/**
+- * @brief read value from dm9051 internal register
+- */
+-static esp_err_t dm9051_register_read(emac_dm9051_t *emac, uint8_t reg_addr, uint8_t *value)
++static esp_err_t dm9051_spi_read(void *spi_ctx, uint32_t cmd, uint32_t addr, void *value, uint32_t len)
+ {
+     esp_err_t ret = ESP_OK;
++    spi_info_t *spi = (spi_info_t *)spi_ctx;
++
+     spi_transaction_t trans = {
+-        .cmd = DM9051_SPI_RD,
+-        .addr = reg_addr,
+-        .length = 8,
+-        .flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA
++        .flags = len <= 4 ? SPI_TRANS_USE_RXDATA : 0, // use direct reads for registers to prevent overwrites by 4-byte boundary writes
++        .cmd = cmd,
++        .addr = addr,
++        .length = 8 * len,
++        .rx_buffer = value
+     };
+-    if (dm9051_lock(emac)) {
+-        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
++    if (dm9051_spi_lock(spi)) {
++        if (spi_device_polling_transmit(spi->hdl, &trans) != ESP_OK) {
+             ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+             ret = ESP_FAIL;
+-        } else {
+-            *value = trans.rx_data[0];
+         }
+-        dm9051_unlock(emac);
++        dm9051_spi_unlock(spi);
+     } else {
+         ret = ESP_ERR_TIMEOUT;
+     }
++    if ((trans.flags&SPI_TRANS_USE_RXDATA) && len <= 4) {
++        memcpy(value, trans.rx_data, len);  // copy register values to output
++    }
+     return ret;
+ }
+ 
++/**
++ * @brief write value to dm9051 internal register
++ */
++static esp_err_t dm9051_register_write(emac_dm9051_t *emac, uint8_t reg_addr, uint8_t value)
++{
++    return emac->spi.write(emac->spi.ctx, DM9051_SPI_WR, reg_addr, &value, 1);
++}
++
++/**
++ * @brief read value from dm9051 internal register
++ */
++static esp_err_t dm9051_register_read(emac_dm9051_t *emac, uint8_t reg_addr, uint8_t *value)
++{
++    return emac->spi.read(emac->spi.ctx, DM9051_SPI_RD, reg_addr, value, 1);
++}
++
+ /**
+  * @brief write buffer to dm9051 internal memory
+  */
+ static esp_err_t dm9051_memory_write(emac_dm9051_t *emac, uint8_t *buffer, uint32_t len)
+ {
+-    esp_err_t ret = ESP_OK;
+-    spi_transaction_t trans = {
+-        .cmd = DM9051_SPI_WR,
+-        .addr = DM9051_MWCMD,
+-        .length = len * 8,
+-        .tx_buffer = buffer
+-    };
+-    if (dm9051_lock(emac)) {
+-        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
+-            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+-            ret = ESP_FAIL;
+-        }
+-        dm9051_unlock(emac);
+-    } else {
+-        ret = ESP_ERR_TIMEOUT;
+-    }
+-    return ret;
++    return emac->spi.write(emac->spi.ctx, DM9051_SPI_WR, DM9051_MWCMD, buffer, len);
+ }
+ 
+ /**
+@@ -151,23 +211,7 @@ static esp_err_t dm9051_memory_write(emac_dm9051_t *emac, uint8_t *buffer, uint3
+  */
+ static esp_err_t dm9051_memory_read(emac_dm9051_t *emac, uint8_t *buffer, uint32_t len)
+ {
+-    esp_err_t ret = ESP_OK;
+-    spi_transaction_t trans = {
+-        .cmd = DM9051_SPI_RD,
+-        .addr = DM9051_MRCMD,
+-        .length = len * 8,
+-        .rx_buffer = buffer
+-    };
+-    if (dm9051_lock(emac)) {
+-        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
+-            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+-            ret = ESP_FAIL;
+-        }
+-        dm9051_unlock(emac);
+-    } else {
+-        ret = ESP_ERR_TIMEOUT;
+-    }
+-    return ret;
++    return emac->spi.read(emac->spi.ctx, DM9051_SPI_RD, DM9051_MRCMD, buffer, len);
+ }
+ 
+ /**
+@@ -175,23 +219,7 @@ static esp_err_t dm9051_memory_read(emac_dm9051_t *emac, uint8_t *buffer, uint32
+  */
+ static esp_err_t dm9051_memory_peek(emac_dm9051_t *emac, uint8_t *buffer, uint32_t len)
+ {
+-    esp_err_t ret = ESP_OK;
+-    spi_transaction_t trans = {
+-        .cmd = DM9051_SPI_RD,
+-        .addr = DM9051_MRCMDX1,
+-        .length = len * 8,
+-        .rx_buffer = buffer
+-    };
+-    if (dm9051_lock(emac)) {
+-        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
+-            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+-            ret = ESP_FAIL;
+-        }
+-        dm9051_unlock(emac);
+-    } else {
+-        ret = ESP_ERR_TIMEOUT;
+-    }
+-    return ret;
++    return emac->spi.read(emac->spi.ctx, DM9051_SPI_RD, DM9051_MRCMDX1, buffer, len);
+ }
+ 
+ /**
+@@ -840,8 +868,7 @@ static esp_err_t emac_dm9051_del(esp_eth_mac_t *mac)
+ {
+     emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+     vTaskDelete(emac->rx_task_hdl);
+-    spi_bus_remove_device(emac->spi_hdl);
+-    vSemaphoreDelete(emac->spi_lock);
++    emac->spi.deinit(emac->spi.ctx);
+     heap_caps_free(emac->rx_buffer);
+     free(emac);
+     return ESP_OK;
+@@ -857,19 +884,6 @@ esp_eth_mac_t *esp_eth_mac_new_dm9051(const eth_dm9051_config_t *dm9051_config,
+     ESP_GOTO_ON_FALSE(emac, NULL, err, TAG, "calloc emac failed");
+     /* dm9051 receive is driven by interrupt only for now*/
+     ESP_GOTO_ON_FALSE(dm9051_config->int_gpio_num >= 0, NULL, err, TAG, "error interrupt gpio number");
+-    /* SPI device init */
+-    spi_device_interface_config_t spi_devcfg;
+-    memcpy(&spi_devcfg, dm9051_config->spi_devcfg, sizeof(spi_device_interface_config_t));
+-    if (dm9051_config->spi_devcfg->command_bits == 0 && dm9051_config->spi_devcfg->address_bits == 0) {
+-        /* configure default SPI frame format */
+-        spi_devcfg.command_bits = 1;
+-        spi_devcfg.address_bits = 7;
+-    } else {
+-        ESP_GOTO_ON_FALSE(dm9051_config->spi_devcfg->command_bits == 1 || dm9051_config->spi_devcfg->address_bits == 7,
+-                            NULL, err, TAG, "incorrect SPI frame format (command_bits/address_bits)");
+-    }
+-    ESP_GOTO_ON_FALSE(spi_bus_add_device(dm9051_config->spi_host_id, &spi_devcfg, &emac->spi_hdl) == ESP_OK,
+-                                            NULL, err, TAG, "adding device to SPI host #%d failed", dm9051_config->spi_host_id + 1);
+     /* bind methods and attributes */
+     emac->sw_reset_timeout_ms = mac_config->sw_reset_timeout_ms;
+     emac->int_gpio_num = dm9051_config->int_gpio_num;
+@@ -891,9 +905,26 @@ esp_eth_mac_t *esp_eth_mac_new_dm9051(const eth_dm9051_config_t *dm9051_config,
+     emac->parent.enable_flow_ctrl = emac_dm9051_enable_flow_ctrl;
+     emac->parent.transmit = emac_dm9051_transmit;
+     emac->parent.receive = emac_dm9051_receive;
+-    /* create mutex */
+-    emac->spi_lock = xSemaphoreCreateMutex();
+-    ESP_GOTO_ON_FALSE(emac->spi_lock, NULL, err, TAG, "create lock failed");
++
++    if (dm9051_config->custom_spi_driver.init != NULL && dm9051_config->custom_spi_driver.deinit != NULL
++        && dm9051_config->custom_spi_driver.read != NULL && dm9051_config->custom_spi_driver.write != NULL) {
++        ESP_LOGD(TAG, "Using user's custom SPI Driver");
++        emac->spi.init = dm9051_config->custom_spi_driver.init;
++        emac->spi.deinit = dm9051_config->custom_spi_driver.deinit;
++        emac->spi.read = dm9051_config->custom_spi_driver.read;
++        emac->spi.write = dm9051_config->custom_spi_driver.write;
++        /* Custom SPI driver device init */
++        ESP_GOTO_ON_FALSE((emac->spi.ctx = emac->spi.init(dm9051_config->custom_spi_driver.config)) != NULL, NULL, err, TAG, "SPI initialization failed");
++    } else {
++        ESP_LOGD(TAG, "Using default SPI Driver");
++        emac->spi.init = dm9051_spi_init;
++        emac->spi.deinit = dm9051_spi_deinit;
++        emac->spi.read = dm9051_spi_read;
++        emac->spi.write = dm9051_spi_write;
++        /* SPI device init */
++        ESP_GOTO_ON_FALSE((emac->spi.ctx = emac->spi.init(dm9051_config)) != NULL, NULL, err, TAG, "SPI initialization failed");
++    }
++
+     /* create dm9051 task */
+     BaseType_t core_num = tskNO_AFFINITY;
+     if (mac_config->flags & ETH_MAC_FLAG_PIN_TO_CORE) {
+@@ -913,8 +944,8 @@ err:
+         if (emac->rx_task_hdl) {
+             vTaskDelete(emac->rx_task_hdl);
+         }
+-        if (emac->spi_lock) {
+-            vSemaphoreDelete(emac->spi_lock);
++        if (emac->spi.ctx) {
++            emac->spi.deinit(emac->spi.ctx);
+         }
+         heap_caps_free(emac->rx_buffer);
+         free(emac);
+diff --git a/components/esp_eth/src/esp_eth_mac_ksz8851snl.c b/components/esp_eth/src/esp_eth_mac_ksz8851snl.c
+index b6aea4df90ead8dc2162e0735bdc310717881c97..cda12f0159835ea1cb818736bdc2611056b131ff 100644
+--- a/components/esp_eth/src/esp_eth_mac_ksz8851snl.c
++++ b/components/esp_eth/src/esp_eth_mac_ksz8851snl.c
+@@ -3,7 +3,7 @@
+  *
+  * SPDX-License-Identifier: MIT
+  *
+- * SPDX-FileContributor: 2021-2022 Espressif Systems (Shanghai) CO LTD
++ * SPDX-FileContributor: 2021-2023 Espressif Systems (Shanghai) CO LTD
+  */
+ 
+ #include <string.h>
+@@ -22,10 +22,22 @@
+ 
+ #define KSZ8851_ETH_MAC_RX_BUF_SIZE_AUTO (0)
+ 
++typedef struct {
++    spi_device_handle_t hdl;
++} spi_info_t;
++
++typedef struct {
++    void *ctx;
++    void *(*init)(const void *spi_config);
++    esp_err_t (*deinit)(void *spi_ctx);
++    esp_err_t (*read)(void *spi_ctx, uint32_t cmd,uint32_t addr, void *data, uint32_t data_len);
++    esp_err_t (*write)(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *data, uint32_t data_len);
++} spi_interface_t;
++
+ typedef struct {
+     esp_eth_mac_t parent;
+     esp_eth_mediator_t *eth;
+-    spi_device_handle_t spi_hdl;
++    spi_interface_t spi;
+     SemaphoreHandle_t spi_lock;
+     TaskHandle_t rx_task_hdl;
+     uint32_t sw_reset_timeout_ms;
+@@ -73,6 +85,95 @@ IRAM_ATTR static void ksz8851_isr_handler(void *arg)
+     }
+ }
+ 
++static void *ksz8851_spi_init(const void *spi_config)
++{
++    void *ret = NULL;
++    eth_ksz8851snl_config_t *ksz8851snl_config = (eth_ksz8851snl_config_t *)spi_config;
++    spi_info_t *spi = calloc(1, sizeof(spi_info_t));
++    ESP_GOTO_ON_FALSE(spi, NULL, err, TAG, "no memory for SPI context data");
++
++    // SPI device init
++    ESP_GOTO_ON_FALSE(spi_bus_add_device(ksz8851snl_config->spi_host_id, ksz8851snl_config->spi_devcfg, &spi->hdl) == ESP_OK, NULL,
++                                            err, TAG, "adding device to SPI host #%d failed", ksz8851snl_config->spi_host_id + 1);
++    ret = spi;
++    return ret;
++err:
++    if (spi) {
++        free(spi);
++    }
++    return ret;
++}
++
++static esp_err_t ksz8851_spi_deinit(void *spi_ctx)
++{
++    esp_err_t ret = ESP_OK;
++    spi_info_t *spi = (spi_info_t *)spi_ctx;
++
++    spi_bus_remove_device(spi->hdl);
++
++    free(spi);
++    return ret;
++}
++
++static esp_err_t ksz8851_spi_read(void *spi_ctx, uint32_t cmd, uint32_t addr, void *value, uint32_t len)
++{
++    esp_err_t ret = ESP_OK;
++    spi_info_t *spi = (spi_info_t *)spi_ctx;
++
++    spi_transaction_ext_t trans = {
++        .base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY | (len <= 4 ? SPI_TRANS_USE_RXDATA : 0),
++        .base.cmd = cmd,
++        .base.addr = addr,
++        .base.length = 8 * len,
++        .base.rx_buffer = value,
++        .command_bits = KSZ8851_SPI_COMMAND_BITS
++    };
++    if (cmd >= KSZ8851_SPI_COMMAND_READ_FIFO) {
++        trans.address_bits = 8 - KSZ8851_SPI_COMMAND_BITS;
++    } else {
++        trans.address_bits = 16 - KSZ8851_SPI_COMMAND_BITS;
++    }
++
++    // No need for mutex here since SPI access is protected at higher layer of this driver
++    if (spi_device_polling_transmit(spi->hdl, &trans.base) != ESP_OK) {
++        ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
++        ret = ESP_FAIL;
++    }
++
++    if ((trans.base.flags & SPI_TRANS_USE_RXDATA) && len <= 4) {
++        memcpy(value, trans.base.rx_data, len);  // copy register values to output
++    }
++    return ret;
++}
++
++static esp_err_t ksz8851_spi_write(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *value, uint32_t len)
++{
++    esp_err_t ret = ESP_OK;
++    spi_info_t *spi = (spi_info_t *)spi_ctx;
++
++    spi_transaction_ext_t trans = {
++        .base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY,
++        .base.cmd = cmd,
++        .base.addr = addr,
++        .base.length = 8 * len,
++        .base.tx_buffer = value,
++        .command_bits = KSZ8851_SPI_COMMAND_BITS
++    };
++    if (cmd >= KSZ8851_SPI_COMMAND_READ_FIFO) {
++        trans.address_bits = 8 - KSZ8851_SPI_COMMAND_BITS;
++    } else {
++        trans.address_bits = 16 - KSZ8851_SPI_COMMAND_BITS;
++    }
++
++    // No need for mutex here since SPI access is protected at higher layer of this driver
++    if (spi_device_polling_transmit(spi->hdl, &trans.base) != ESP_OK) {
++        ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
++        ret = ESP_FAIL;
++    }
++
++    return ret;
++}
++
+ static inline bool ksz8851_mutex_lock(emac_ksz8851snl_t *emac)
+ {
+     return xSemaphoreTakeRecursive(emac->spi_lock, pdMS_TO_TICKS(KSZ8851_SPI_LOCK_TIMEOUT_MS)) == pdTRUE;
+@@ -83,70 +184,49 @@ static inline bool ksz8851_mutex_unlock(emac_ksz8851snl_t *emac)
+     return xSemaphoreGiveRecursive(emac->spi_lock) == pdTRUE;
+ }
+ 
+-static esp_err_t ksz8851_read_reg(emac_ksz8851snl_t *emac, uint32_t address, uint16_t *value)
++static esp_err_t ksz8851_read_reg(emac_ksz8851snl_t *emac, uint32_t reg_addr, uint16_t *value)
+ {
+     esp_err_t ret = ESP_OK;
+     ESP_GOTO_ON_FALSE(value != NULL, ESP_ERR_INVALID_ARG, err, TAG, "out pointer must not be null");
+-    ESP_GOTO_ON_FALSE((address & ~KSZ8851_VALID_ADDRESS_MASK) == 0U, ESP_ERR_INVALID_ARG, err, TAG, "address is out of bounds");
++    ESP_GOTO_ON_FALSE((reg_addr & ~KSZ8851_VALID_ADDRESS_MASK) == 0U, ESP_ERR_INVALID_ARG, err, TAG, "address is out of bounds");
+ 
+-    const unsigned data_size = 16U; // NOTE(v.chistyakov): bits
+     // NOTE(v.chistyakov): select upper or lower word inside a dword
+-    const unsigned byte_mask = 0x3U << (KSZ8851_SPI_BYTE_MASK_SHIFT + (address & 0x2U));
+-    address <<= KSZ8851_SPI_ADDR_SHIFT;
++    const unsigned byte_mask = 0x3U << (KSZ8851_SPI_BYTE_MASK_SHIFT + (reg_addr & 0x2U));
++    reg_addr <<= KSZ8851_SPI_ADDR_SHIFT;
+ 
+-    spi_transaction_ext_t trans = {
+-        .base.flags   = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY | SPI_TRANS_USE_RXDATA,
+-        .base.cmd     = KSZ8851_SPI_COMMAND_READ_REG,
+-        .base.addr    = address | byte_mask,
+-        .base.length  = data_size,
+-        .command_bits = KSZ8851_SPI_COMMAND_BITS,
+-        .address_bits = 16 - KSZ8851_SPI_COMMAND_BITS,
+-    };
++    // Need to protect SPI access at higher layer of the driver since once packet transmit/receive is started (`SDA Start DMA Access` bit is set),
++    // all registers access are disabled.
+     if (ksz8851_mutex_lock(emac)) {
+-        if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) {
+-            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+-            ret = ESP_FAIL;
+-        }
+-        ksz8851_mutex_unlock(emac);
+-        memcpy(value, trans.base.rx_data, data_size >> 3U);
+-        ESP_LOGV(TAG, "reading reg 0x%02x == 0x%04x", address, *value);
++        ret = emac->spi.read(emac->spi.ctx, KSZ8851_SPI_COMMAND_READ_REG, reg_addr | byte_mask, value, 2);
+     } else {
+         ret = ESP_ERR_TIMEOUT;
+     }
++    ksz8851_mutex_unlock(emac);
++    ESP_LOGV(TAG, "reading reg 0x%02x == 0x%02x", reg_addr, *value);
++
+ err:
+     return ret;
+ }
+ 
+-static esp_err_t ksz8851_write_reg(emac_ksz8851snl_t *emac, uint32_t address, uint16_t value)
++static esp_err_t ksz8851_write_reg(emac_ksz8851snl_t *emac, uint32_t reg_addr, uint16_t value)
+ {
+     esp_err_t ret = ESP_OK;
+-    ESP_GOTO_ON_FALSE((address & ~KSZ8851_VALID_ADDRESS_MASK) == 0U, ESP_ERR_INVALID_ARG, err, TAG, "address is out of bounds");
+-    ESP_LOGV(TAG, "writing reg 0x%02x = 0x%04x", address, value);
++    ESP_GOTO_ON_FALSE((reg_addr & ~KSZ8851_VALID_ADDRESS_MASK) == 0U, ESP_ERR_INVALID_ARG, err, TAG, "address is out of bounds");
++    ESP_LOGV(TAG, "writing reg 0x%02x = 0x%02x", reg_addr, value);
+ 
+-    const unsigned data_size = 16U; // NOTE(v.chistyakov): bits
+     // NOTE(v.chistyakov): select upper or lower word inside a dword
+-    const unsigned byte_mask = 0x3U << (KSZ8851_SPI_BYTE_MASK_SHIFT + (address & 0x2U));
+-    address <<= KSZ8851_SPI_ADDR_SHIFT;
++    const unsigned byte_mask = 0x3U << (KSZ8851_SPI_BYTE_MASK_SHIFT + (reg_addr & 0x2U));
++    reg_addr <<= KSZ8851_SPI_ADDR_SHIFT;
+ 
+-    spi_transaction_ext_t trans = {
+-        .base.flags   = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY | SPI_TRANS_USE_TXDATA,
+-        .base.cmd     = KSZ8851_SPI_COMMAND_WRITE_REG,
+-        .base.addr    = address | byte_mask,
+-        .base.length  = data_size,
+-        .command_bits = KSZ8851_SPI_COMMAND_BITS,
+-        .address_bits = 16 - KSZ8851_SPI_COMMAND_BITS,
+-    };
+-
+-    memcpy(trans.base.tx_data, &value, data_size >> 3U);
++    // Need to protect SPI access at higher layer of the driver since once packet transmit/receive is started (`SDA Start DMA Access` bit is set),
++    // all registers access are disabled.
+     if (ksz8851_mutex_lock(emac)) {
+-        if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) {
+-            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+-            ret = ESP_FAIL;
+-        }
+-        ksz8851_mutex_unlock(emac);
++        ret = emac->spi.write(emac->spi.ctx, KSZ8851_SPI_COMMAND_WRITE_REG, reg_addr | byte_mask, &value, 2);
+     } else {
+         ret = ESP_ERR_TIMEOUT;
+     }
++    ksz8851_mutex_unlock(emac);
++
+ err:
+     return ret;
+ }
+@@ -313,6 +393,7 @@ static esp_err_t emac_ksz8851snl_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint
+     ESP_LOGV(TAG, "transmitting frame of size %u", length);
+     esp_err_t ret           = ESP_OK;
+     emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent);
++    // Lock SPI since once `SDA Start DMA Access` bit is set, all registers access are disabled.
+     if (!ksz8851_mutex_lock(emac)) {
+         return ESP_ERR_TIMEOUT;
+     }
+@@ -333,24 +414,12 @@ static esp_err_t emac_ksz8851snl_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint
+     emac->tx_buffer[3] = (length >> 8U) & 0xFFU;
+     memcpy(emac->tx_buffer + 4U, buf, length);
+ 
+-    spi_transaction_ext_t trans = {
+-        .base.flags     = SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_CMD,
+-        .base.cmd       = KSZ8851_SPI_COMMAND_WRITE_FIFO,
+-        .base.length    = transmit_length * 8U, // NOTE(v.chistyakov): bits
+-        .base.tx_buffer = emac->tx_buffer,
+-        .command_bits   = 2U,
+-        .address_bits   = 6U,
+-    };
+-
+     uint16_t ier;
+     ESP_GOTO_ON_ERROR(ksz8851_read_reg(emac, KSZ8851_IER, &ier), err, TAG, "IER read failed");
+     ESP_GOTO_ON_ERROR(ksz8851_write_reg(emac, KSZ8851_IER, 0), err, TAG, "IER write failed");
+ 
+     ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_SDA), err, TAG, "RXQCR write failed");
+-    if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) {
+-        ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+-        ret = ESP_FAIL;
+-    }
++    ret = emac->spi.write(emac->spi.ctx, KSZ8851_SPI_COMMAND_WRITE_FIFO, 0, emac->tx_buffer, transmit_length);
+     ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_RXQCR, RXQCR_SDA), err, TAG, "RXQCR write failed");
+ 
+     ESP_GOTO_ON_ERROR(ksz8851_write_reg(emac, KSZ8851_IER, ier), err, TAG, "IER write failed");
+@@ -438,23 +507,13 @@ static esp_err_t emac_ksz8851_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t
+ 
+     // NOTE(v.chistyakov): 4 dummy + 4 header + alignment
+     const unsigned receive_size = 8U + ((byte_count + 3U) & ~0x3U);
+-    spi_transaction_ext_t trans = {
+-        .base.flags     = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY,
+-        .base.cmd       = KSZ8851_SPI_COMMAND_READ_FIFO,
+-        .base.length    = receive_size * 8U, // NOTE(v.chistyakov): bits
+-        .base.rx_buffer = emac->rx_buffer,
+-        .command_bits   = 2U,
+-        .address_bits   = 6U,
+-    };
++    // Lock SPI since once `SDA Start DMA Access` bit is set, all registers access are disabled.
+     if (!ksz8851_mutex_lock(emac)) {
+         return ESP_ERR_TIMEOUT;
+     }
+     ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_RXFDPR, RXFDPR_RXFP_MASK), err, TAG, "RXFDPR write failed");
+     ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_SDA), err, TAG, "RXQCR write failed");
+-    if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) {
+-        ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+-        ret = ESP_FAIL;
+-    }
++    ret = emac->spi.read(emac->spi.ctx, KSZ8851_SPI_COMMAND_READ_FIFO, 0, emac->rx_buffer, receive_size);
+     ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_RXQCR, RXQCR_SDA), err, TAG, "RXQCR write failed");
+     ksz8851_mutex_unlock(emac);
+     // NOTE(v.chistyakov): skip 4 dummy, 4 header
+@@ -728,7 +787,7 @@ static esp_err_t emac_ksz8851_del(esp_eth_mac_t *mac)
+ {
+     emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent);
+     vTaskDelete(emac->rx_task_hdl);
+-    spi_bus_remove_device(emac->spi_hdl);
++    emac->spi.deinit(emac->spi.ctx);
+     vSemaphoreDelete(emac->spi_lock);
+     heap_caps_free(emac->rx_buffer);
+     heap_caps_free(emac->tx_buffer);
+@@ -748,10 +807,6 @@ esp_eth_mac_t *esp_eth_mac_new_ksz8851snl(const eth_ksz8851snl_config_t *ksz8851
+     emac = calloc(1, sizeof(emac_ksz8851snl_t));
+     ESP_GOTO_ON_FALSE(emac, NULL, err, TAG, "no mem for MAC instance");
+ 
+-    /* SPI device init */
+-    ESP_GOTO_ON_FALSE(spi_bus_add_device(ksz8851snl_config->spi_host_id, ksz8851snl_config->spi_devcfg, &emac->spi_hdl) == ESP_OK,
+-                                            NULL, err, TAG, "adding device to SPI host #%d failed", ksz8851snl_config->spi_host_id + 1);
+-
+     emac->sw_reset_timeout_ms           = mac_config->sw_reset_timeout_ms;
+     emac->int_gpio_num                  = ksz8851snl_config->int_gpio_num;
+     emac->parent.set_mediator           = emac_ksz8851_set_mediator;
+@@ -772,8 +827,6 @@ esp_eth_mac_t *esp_eth_mac_new_ksz8851snl(const eth_ksz8851snl_config_t *ksz8851
+     emac->parent.enable_flow_ctrl       = emac_ksz8851_enable_flow_ctrl;
+     emac->parent.set_peer_pause_ability = emac_ksz8851_set_peer_pause_ability;
+     emac->parent.del                    = emac_ksz8851_del;
+-    emac->spi_lock                      = xSemaphoreCreateRecursiveMutex();
+-    ESP_GOTO_ON_FALSE(emac->spi_lock, NULL, err, TAG, "create lock failed");
+     emac->rx_buffer = NULL;
+     emac->tx_buffer = NULL;
+     emac->rx_buffer = heap_caps_malloc(KSZ8851_QMU_PACKET_LENGTH + KSZ8851_QMU_PACKET_PADDING, MALLOC_CAP_DMA);
+@@ -781,6 +834,29 @@ esp_eth_mac_t *esp_eth_mac_new_ksz8851snl(const eth_ksz8851snl_config_t *ksz8851
+     ESP_GOTO_ON_FALSE(emac->rx_buffer, NULL, err, TAG, "RX buffer allocation failed");
+     ESP_GOTO_ON_FALSE(emac->tx_buffer, NULL, err, TAG, "TX buffer allocation failed");
+ 
++    /* create mutex */
++    emac->spi_lock = xSemaphoreCreateRecursiveMutex();
++    ESP_GOTO_ON_FALSE(emac->spi_lock, NULL, err, TAG, "create lock failed");
++
++    if (ksz8851snl_config->custom_spi_driver.init != NULL && ksz8851snl_config->custom_spi_driver.deinit != NULL
++        && ksz8851snl_config->custom_spi_driver.read != NULL && ksz8851snl_config->custom_spi_driver.write != NULL) {
++        ESP_LOGD(TAG, "Using user's custom SPI Driver");
++        emac->spi.init = ksz8851snl_config->custom_spi_driver.init;
++        emac->spi.deinit = ksz8851snl_config->custom_spi_driver.deinit;
++        emac->spi.read = ksz8851snl_config->custom_spi_driver.read;
++        emac->spi.write = ksz8851snl_config->custom_spi_driver.write;
++        /* Custom SPI driver device init */
++        ESP_GOTO_ON_FALSE((emac->spi.ctx = emac->spi.init(ksz8851snl_config->custom_spi_driver.config)) != NULL, NULL, err, TAG, "SPI initialization failed");
++    } else {
++        ESP_LOGD(TAG, "Using default SPI Driver");
++        emac->spi.init = ksz8851_spi_init;
++        emac->spi.deinit = ksz8851_spi_deinit;
++        emac->spi.read = ksz8851_spi_read;
++        emac->spi.write = ksz8851_spi_write;
++        /* SPI device init */
++        ESP_GOTO_ON_FALSE((emac->spi.ctx = emac->spi.init(ksz8851snl_config)) != NULL, NULL, err, TAG, "SPI initialization failed");
++    }
++
+     BaseType_t core_num = tskNO_AFFINITY;
+     if (mac_config->flags & ETH_MAC_FLAG_PIN_TO_CORE) {
+         core_num = esp_cpu_get_core_id();
+@@ -798,6 +874,9 @@ err:
+         if (emac->spi_lock) {
+             vSemaphoreDelete(emac->spi_lock);
+         }
++        if (emac->spi.ctx) {
++            emac->spi.deinit(emac->spi.ctx);
++        }
+         // NOTE(v.chistyakov): safe to call with NULL
+         heap_caps_free(emac->rx_buffer);
+         heap_caps_free(emac->tx_buffer);
+diff --git a/components/esp_eth/src/esp_eth_mac_w5500.c b/components/esp_eth/src/esp_eth_mac_w5500.c
+index 7a7708d0ca8bbbf77b32d9cb01ce34e57cc1f242..5b19d5cfcdfc67105809cbca33e780850b9d4fea 100644
+--- a/components/esp_eth/src/esp_eth_mac_w5500.c
++++ b/components/esp_eth/src/esp_eth_mac_w5500.c
+@@ -37,11 +37,23 @@ typedef struct {
+     uint32_t remain;
+ }__attribute__((packed)) emac_w5500_auto_buf_info_t;
+ 
++typedef struct {
++    spi_device_handle_t hdl;
++    SemaphoreHandle_t lock;
++} spi_info_t;
++
++typedef struct {
++    void *ctx;
++    void *(*init)(const void *spi_config);
++    esp_err_t (*deinit)(void *spi_ctx);
++    esp_err_t (*read)(void *spi_ctx, uint32_t cmd,uint32_t addr, void *data, uint32_t data_len);
++    esp_err_t (*write)(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *data, uint32_t data_len);
++} spi_interface_t;
++
+ typedef struct {
+     esp_eth_mac_t parent;
+     esp_eth_mediator_t *eth;
+-    spi_device_handle_t spi_hdl;
+-    SemaphoreHandle_t spi_lock;
++    spi_interface_t spi;
+     TaskHandle_t rx_task_hdl;
+     uint32_t sw_reset_timeout_ms;
+     int int_gpio_num;
+@@ -50,64 +62,132 @@ typedef struct {
+     uint8_t *rx_buffer;
+ } emac_w5500_t;
+ 
+-static inline bool w5500_lock(emac_w5500_t *emac)
++static void *w5500_spi_init(const void *spi_config)
+ {
+-    return xSemaphoreTake(emac->spi_lock, pdMS_TO_TICKS(W5500_SPI_LOCK_TIMEOUT_MS)) == pdTRUE;
++    void *ret = NULL;
++    eth_w5500_config_t *w5500_config = (eth_w5500_config_t *)spi_config;
++    spi_info_t *spi = calloc(1, sizeof(spi_info_t));
++    ESP_GOTO_ON_FALSE(spi, NULL, err, TAG, "no memory for SPI context data");
++
++    /* SPI device init */
++    spi_device_interface_config_t spi_devcfg;
++    spi_devcfg = *(w5500_config->spi_devcfg);
++    if (w5500_config->spi_devcfg->command_bits == 0 && w5500_config->spi_devcfg->address_bits == 0) {
++        /* configure default SPI frame format */
++        spi_devcfg.command_bits = 16; // Actually it's the address phase in W5500 SPI frame
++        spi_devcfg.address_bits = 8;  // Actually it's the control phase in W5500 SPI frame
++    } else {
++        ESP_GOTO_ON_FALSE(w5500_config->spi_devcfg->command_bits == 16 && w5500_config->spi_devcfg->address_bits == 8,
++                            NULL, err, TAG, "incorrect SPI frame format (command_bits/address_bits)");
++    }
++    ESP_GOTO_ON_FALSE(spi_bus_add_device(w5500_config->spi_host_id, &spi_devcfg, &spi->hdl) == ESP_OK, NULL,
++                                            err, TAG, "adding device to SPI host #%d failed", w5500_config->spi_host_id + 1);
++    /* create mutex */
++    spi->lock = xSemaphoreCreateMutex();
++    ESP_GOTO_ON_FALSE(spi->lock, NULL, err, TAG, "create lock failed");
++
++    ret = spi;
++    return ret;
++err:
++    if (spi) {
++        if (spi->lock) {
++            vSemaphoreDelete(spi->lock);
++        }
++        free(spi);
++    }
++    return ret;
+ }
+ 
+-static inline bool w5500_unlock(emac_w5500_t *emac)
++static esp_err_t w5500_spi_deinit(void *spi_ctx)
+ {
+-    return xSemaphoreGive(emac->spi_lock) == pdTRUE;
++    esp_err_t ret = ESP_OK;
++    spi_info_t *spi = (spi_info_t *)spi_ctx;
++
++    spi_bus_remove_device(spi->hdl);
++    vSemaphoreDelete(spi->lock);
++
++    free(spi);
++    return ret;
+ }
+ 
+-static esp_err_t w5500_write(emac_w5500_t *emac, uint32_t address, const void *value, uint32_t len)
++static inline bool w5500_spi_lock(spi_info_t *spi)
++{
++    return xSemaphoreTake(spi->lock, pdMS_TO_TICKS(W5500_SPI_LOCK_TIMEOUT_MS)) == pdTRUE;
++}
++
++static inline bool w5500_spi_unlock(spi_info_t *spi)
++{
++    return xSemaphoreGive(spi->lock) == pdTRUE;
++}
++
++static esp_err_t w5500_spi_write(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *value, uint32_t len)
+ {
+     esp_err_t ret = ESP_OK;
++    spi_info_t *spi = (spi_info_t *)spi_ctx;
+ 
+     spi_transaction_t trans = {
+-        .cmd = (address >> W5500_ADDR_OFFSET),
+-        .addr = ((address & 0xFFFF) | (W5500_ACCESS_MODE_WRITE << W5500_RWB_OFFSET) | W5500_SPI_OP_MODE_VDM),
++        .cmd = cmd,
++        .addr = addr,
+         .length = 8 * len,
+         .tx_buffer = value
+     };
+-    if (w5500_lock(emac)) {
+-        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
++    if (w5500_spi_lock(spi)) {
++        if (spi_device_polling_transmit(spi->hdl, &trans) != ESP_OK) {
+             ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+             ret = ESP_FAIL;
+         }
+-        w5500_unlock(emac);
++        w5500_spi_unlock(spi);
+     } else {
+         ret = ESP_ERR_TIMEOUT;
+     }
+     return ret;
+ }
+ 
+-static esp_err_t w5500_read(emac_w5500_t *emac, uint32_t address, void *value, uint32_t len)
++static esp_err_t w5500_spi_read(void *spi_ctx, uint32_t cmd, uint32_t addr, void *value, uint32_t len)
+ {
+     esp_err_t ret = ESP_OK;
++    spi_info_t *spi = (spi_info_t *)spi_ctx;
+ 
+     spi_transaction_t trans = {
+         .flags = len <= 4 ? SPI_TRANS_USE_RXDATA : 0, // use direct reads for registers to prevent overwrites by 4-byte boundary writes
+-        .cmd = (address >> W5500_ADDR_OFFSET),
+-        .addr = ((address & 0xFFFF) | (W5500_ACCESS_MODE_READ << W5500_RWB_OFFSET) | W5500_SPI_OP_MODE_VDM),
++        .cmd = cmd,
++        .addr = addr,
+         .length = 8 * len,
+         .rx_buffer = value
+     };
+-    if (w5500_lock(emac)) {
+-        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
++    if (w5500_spi_lock(spi)) {
++        if (spi_device_polling_transmit(spi->hdl, &trans) != ESP_OK) {
+             ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+             ret = ESP_FAIL;
+         }
+-        w5500_unlock(emac);
++        w5500_spi_unlock(spi);
+     } else {
+         ret = ESP_ERR_TIMEOUT;
+     }
+-    if ((trans.flags&SPI_TRANS_USE_RXDATA) && len <= 4) {
++    if ((trans.flags & SPI_TRANS_USE_RXDATA) && len <= 4) {
+         memcpy(value, trans.rx_data, len);  // copy register values to output
+     }
+     return ret;
+ }
+ 
++static esp_err_t w5500_read(emac_w5500_t *emac, uint32_t address, void *data, uint32_t len)
++{
++    uint32_t cmd = (address >> W5500_ADDR_OFFSET); // Actually it's the address phase in W5500 SPI frame
++    uint32_t addr = ((address & 0xFFFF) | (W5500_ACCESS_MODE_READ << W5500_RWB_OFFSET)
++                    | W5500_SPI_OP_MODE_VDM); // Actually it's the command phase in W5500 SPI frame
++
++    return emac->spi.read(emac->spi.ctx, cmd, addr, data, len);
++}
++
++static esp_err_t w5500_write(emac_w5500_t *emac, uint32_t address, const void *data, uint32_t len)
++{
++    uint32_t cmd = (address >> W5500_ADDR_OFFSET); // Actually it's the address phase in W5500 SPI frame
++    uint32_t addr = ((address & 0xFFFF) | (W5500_ACCESS_MODE_WRITE << W5500_RWB_OFFSET)
++                    | W5500_SPI_OP_MODE_VDM); // Actually it's the command phase in W5500 SPI frame
++
++    return emac->spi.write(emac->spi.ctx, cmd, addr, data, len);
++}
++
+ static esp_err_t w5500_send_command(emac_w5500_t *emac, uint8_t command, uint32_t timeout_ms)
+ {
+     esp_err_t ret = ESP_OK;
+@@ -738,8 +818,7 @@ static esp_err_t emac_w5500_del(esp_eth_mac_t *mac)
+ {
+     emac_w5500_t *emac = __containerof(mac, emac_w5500_t, parent);
+     vTaskDelete(emac->rx_task_hdl);
+-    spi_bus_remove_device(emac->spi_hdl);
+-    vSemaphoreDelete(emac->spi_lock);
++    emac->spi.deinit(emac->spi.ctx);
+     heap_caps_free(emac->rx_buffer);
+     free(emac);
+     return ESP_OK;
+@@ -754,19 +833,6 @@ esp_eth_mac_t *esp_eth_mac_new_w5500(const eth_w5500_config_t *w5500_config, con
+     ESP_GOTO_ON_FALSE(emac, NULL, err, TAG, "no mem for MAC instance");
+     /* w5500 driver is interrupt driven */
+     ESP_GOTO_ON_FALSE(w5500_config->int_gpio_num >= 0, NULL, err, TAG, "invalid interrupt gpio number");
+-    /* SPI device init */
+-    spi_device_interface_config_t spi_devcfg;
+-    memcpy(&spi_devcfg, w5500_config->spi_devcfg, sizeof(spi_device_interface_config_t));
+-    if (w5500_config->spi_devcfg->command_bits == 0 && w5500_config->spi_devcfg->address_bits == 0) {
+-        /* configure default SPI frame format */
+-        spi_devcfg.command_bits = 16; // Actually it's the address phase in W5500 SPI frame
+-        spi_devcfg.address_bits = 8;  // Actually it's the control phase in W5500 SPI frame
+-    } else {
+-        ESP_GOTO_ON_FALSE(w5500_config->spi_devcfg->command_bits == 16 || w5500_config->spi_devcfg->address_bits == 8,
+-                            NULL, err, TAG, "incorrect SPI frame format (command_bits/address_bits)");
+-    }
+-    ESP_GOTO_ON_FALSE(spi_bus_add_device(w5500_config->spi_host_id, &spi_devcfg, &emac->spi_hdl) == ESP_OK,
+-                                            NULL, err, TAG, "adding device to SPI host #%d failed", w5500_config->spi_host_id + 1);
+     /* bind methods and attributes */
+     emac->sw_reset_timeout_ms = mac_config->sw_reset_timeout_ms;
+     emac->int_gpio_num = w5500_config->int_gpio_num;
+@@ -788,9 +854,26 @@ esp_eth_mac_t *esp_eth_mac_new_w5500(const eth_w5500_config_t *w5500_config, con
+     emac->parent.enable_flow_ctrl = emac_w5500_enable_flow_ctrl;
+     emac->parent.transmit = emac_w5500_transmit;
+     emac->parent.receive = emac_w5500_receive;
+-    /* create mutex */
+-    emac->spi_lock = xSemaphoreCreateMutex();
+-    ESP_GOTO_ON_FALSE(emac->spi_lock, NULL, err, TAG, "create lock failed");
++
++    if (w5500_config->custom_spi_driver.init != NULL && w5500_config->custom_spi_driver.deinit != NULL
++        && w5500_config->custom_spi_driver.read != NULL && w5500_config->custom_spi_driver.write != NULL) {
++        ESP_LOGD(TAG, "Using user's custom SPI Driver");
++        emac->spi.init = w5500_config->custom_spi_driver.init;
++        emac->spi.deinit = w5500_config->custom_spi_driver.deinit;
++        emac->spi.read = w5500_config->custom_spi_driver.read;
++        emac->spi.write = w5500_config->custom_spi_driver.write;
++        /* Custom SPI driver device init */
++        ESP_GOTO_ON_FALSE((emac->spi.ctx = emac->spi.init(w5500_config->custom_spi_driver.config)) != NULL, NULL, err, TAG, "SPI initialization failed");
++    } else {
++        ESP_LOGD(TAG, "Using default SPI Driver");
++        emac->spi.init = w5500_spi_init;
++        emac->spi.deinit = w5500_spi_deinit;
++        emac->spi.read = w5500_spi_read;
++        emac->spi.write = w5500_spi_write;
++        /* SPI device init */
++        ESP_GOTO_ON_FALSE((emac->spi.ctx = emac->spi.init(w5500_config)) != NULL, NULL, err, TAG, "SPI initialization failed");
++    }
++
+     /* create w5500 task */
+     BaseType_t core_num = tskNO_AFFINITY;
+     if (mac_config->flags & ETH_MAC_FLAG_PIN_TO_CORE) {
+@@ -810,8 +893,8 @@ err:
+         if (emac->rx_task_hdl) {
+             vTaskDelete(emac->rx_task_hdl);
+         }
+-        if (emac->spi_lock) {
+-            vSemaphoreDelete(emac->spi_lock);
++        if (emac->spi.ctx) {
++            emac->spi.deinit(emac->spi.ctx);
+         }
+         heap_caps_free(emac->rx_buffer);
+         free(emac);
diff --git a/tools/add_sdk_json.py b/tools/add_sdk_json.py
new file mode 100644
index 000000000..d2deb4a87
--- /dev/null
+++ b/tools/add_sdk_json.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+
+__author__ = "Hristo Gochkov"
+__version__ = "2023"
+
+import os
+import shutil
+import errno
+import os.path
+import json
+import platform
+import sys
+import stat
+import argparse
+
+if sys.version_info[0] == 3:
+    unicode = lambda s: str(s)
+
+def add_system(systems, host, url, filename, sha, size):
+    system = {
+        "host": host,
+        "url": url,
+        "archiveFileName": filename,
+        "checksum": "SHA-256:"+sha,
+        "size": str(size)
+    }
+    systems.append(system)
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(
+        prog = 'add_sdk_json',
+        description = 'Update SDK in Arduino package index')
+    parser.add_argument('-j', '--pkg-json', dest='arduino_json',  required=True, help='path to package json')
+    parser.add_argument('-n', '--name',     dest='tool_name',     required=True, help='name of the SDK package')
+    parser.add_argument('-v', '--version',  dest='tool_version',  required=True, help='version of the new SDK')
+    parser.add_argument('-u', '--url',      dest='tool_url',      required=True, help='url to the zip of the new SDK')
+    parser.add_argument('-f', '--filename', dest='tool_filename', required=True, help='filename of the zip of the new SDK')
+    parser.add_argument('-s', '--size',     dest='tool_size',     required=True, help='size of the zip of the new SDK')
+    parser.add_argument('-c', '--sha',      dest='tool_sha',      required=True, help='sha256 of the zip of the new SDK')
+    args = parser.parse_args()
+
+    print('Destination   : {0}.'.format(args.arduino_json))
+    print('Tool Name     : {0}.'.format(args.tool_name))
+    print('Tool Version  : {0}.'.format(args.tool_version))
+    print('Tool URL      : {0}.'.format(args.tool_url))
+    print('Tool File Name: {0}.'.format(args.tool_filename))
+    print('Tool Size     : {0}.'.format(args.tool_size))
+    print('Tool SHA256   : {0}.'.format(args.tool_sha))
+
+    arduino_json = args.arduino_json;
+    tool_name = args.tool_name;
+    tool_version = args.tool_version;
+    tool_url = args.tool_url;
+    tool_filename = args.tool_filename;
+    tool_size = args.tool_size;
+    tool_sha = args.tool_sha;
+
+    # code start
+    farray = {"packages":[{"platforms":[{"toolsDependencies":[]}],"tools":[]}]}
+    if os.path.isfile(arduino_json) == True:
+        farray = json.load(open(arduino_json))
+
+    dep_found = False
+    dep_skip = False
+    for dep in farray['packages'][0]['platforms'][0]['toolsDependencies']:
+        if dep['name'] == tool_name:
+            if dep['version'] == tool_version:
+                print('Skipping {0}. Same version {1}'.format(tool_name, tool_version))
+                dep_skip = True
+                break
+            print('Updating dependency version of {0} from {1} to {2}'.format(tool_name, dep['version'], tool_version))
+            dep['version'] = tool_version
+            dep_found = True
+            break
+
+    if dep_skip == False:
+        if dep_found == False:
+            print('Adding new dependency: {0} version {1}'.format(tool_name, tool_version))
+            deps = {
+                "packager": "esp32",
+                "name": tool_name,
+                "version": tool_version
+            }
+            farray['packages'][0]['platforms'][0]['toolsDependencies'].append(deps)
+
+        systems = []
+        add_system(systems, "i686-mingw32", tool_url, tool_filename, tool_sha, tool_size)
+        add_system(systems, "x86_64-mingw32", tool_url, tool_filename, tool_sha, tool_size)
+        add_system(systems, "arm64-apple-darwin", tool_url, tool_filename, tool_sha, tool_size)
+        add_system(systems, "x86_64-apple-darwin", tool_url, tool_filename, tool_sha, tool_size)
+        add_system(systems, "x86_64-pc-linux-gnu", tool_url, tool_filename, tool_sha, tool_size)
+        add_system(systems, "i686-pc-linux-gnu", tool_url, tool_filename, tool_sha, tool_size)
+        add_system(systems, "aarch64-linux-gnu", tool_url, tool_filename, tool_sha, tool_size)
+        add_system(systems, "arm-linux-gnueabihf", tool_url, tool_filename, tool_sha, tool_size)
+
+        tool_found = False
+        for t in farray['packages'][0]['tools']:
+            if t['name'] == tool_name:
+                t['version'] = tool_version
+                t['systems'] = systems
+                tool_found = True
+                print('Updating systems of {0} to version {1}'.format(tool_name, tool_version))
+                break
+
+        if tool_found == False:
+            print('Adding new tool: {0} version {1}'.format(tool_name, tool_version))
+            tools = {
+                "name": tool_name,
+                "version": tool_version,
+                "systems": systems
+            }
+            farray['packages'][0]['tools'].append(tools)
+
+        json_str = json.dumps(farray, indent=2)
+        with open(arduino_json, "w") as f:
+            f.write(json_str+"\n")
+            f.close()
+        # print(json_str)
+        print('{0} generated'.format(arduino_json))
diff --git a/tools/archive-build.sh b/tools/archive-build.sh
index 432f69031..f973a42ef 100755
--- a/tools/archive-build.sh
+++ b/tools/archive-build.sh
@@ -2,15 +2,11 @@
 
 IDF_COMMIT=$(git -C "$IDF_PATH" rev-parse --short HEAD || echo "")
 IDF_BRANCH=$(git -C "$IDF_PATH" symbolic-ref --short HEAD || git -C "$IDF_PATH" tag --points-at HEAD || echo "")
-
 idf_version_string=${IDF_BRANCH//\//_}"-$IDF_COMMIT"
-archive_path="dist/arduino-esp32-libs-$idf_version_string.tar.gz"
-build_archive_path="dist/arduino-esp32-build-$idf_version_string.tar.gz"
 
-mkdir -p dist && rm -rf "$archive_path" "$build_archive_path"
+archive_path="dist/arduino-esp32-libs-$1-$idf_version_string.tar.gz"
+
+mkdir -p dist && rm -rf "$archive_path"
 if [ -d "out" ]; then
 	cd out && tar zcf "../$archive_path" * && cd ..
 fi
-if [ -d "build" ]; then
-	cd build && tar zcf "../$build_archive_path" * && cd ..
-fi
diff --git a/tools/check-deploy-needed.sh b/tools/check-deploy-needed.sh
new file mode 100755
index 000000000..52401942a
--- /dev/null
+++ b/tools/check-deploy-needed.sh
@@ -0,0 +1,85 @@
+#/bin/bash
+
+source ./tools/config.sh
+
+IDF_COMMIT=`github_last_commit "$IDF_REPO" "$IDF_BRANCH"`
+
+if [ -z $GITHUB_HEAD_REF ]; then
+	current_branch=`git branch --show-current`
+else
+	current_branch="$GITHUB_HEAD_REF"
+fi
+
+AR_BRANCH="master"
+if [[ "$current_branch" != "master" && `github_branch_exists "$AR_REPO" "$current_branch"` == "1" ]]; then
+	AR_BRANCH="$current_branch"
+else
+	AR_BRANCH_NAME="idf-$IDF_BRANCH"
+	has_ar_branch=`github_branch_exists "$AR_REPO" "$AR_BRANCH_NAME"`
+	if [ "$has_ar_branch" == "1" ]; then
+		AR_BRANCH="$AR_BRANCH_NAME"
+	else
+		has_ar_branch=`github_branch_exists "$AR_REPO" "$AR_PR_TARGET_BRANCH"`
+		if [ "$has_ar_branch" == "1" ]; then
+			AR_BRANCH="$AR_PR_TARGET_BRANCH"
+		fi
+	fi
+fi
+
+# format new branch name and pr title
+AR_NEW_BRANCH_NAME="idf-$IDF_BRANCH"
+AR_NEW_COMMIT_MESSAGE="IDF $IDF_BRANCH $IDF_COMMIT"
+AR_NEW_PR_TITLE="IDF $IDF_BRANCH"
+
+LIBS_VERSION="idf-"${IDF_BRANCH//\//_}"-$IDF_COMMIT"
+
+AR_HAS_BRANCH=`github_branch_exists "$AR_REPO" "$AR_NEW_BRANCH_NAME"`
+if [ "$AR_HAS_BRANCH" == "1" ]; then
+	AR_HAS_COMMIT=`github_commit_exists "$AR_REPO" "$AR_NEW_BRANCH_NAME" "$IDF_COMMIT"`
+else
+	AR_HAS_COMMIT=`github_commit_exists "$AR_REPO" "$AR_BRANCH" "$IDF_COMMIT"`
+fi
+AR_HAS_PR=`github_pr_exists "$AR_REPO" "$AR_NEW_BRANCH_NAME"`
+
+LIBS_HAS_BRANCH=`github_branch_exists "$AR_LIBS_REPO" "$AR_NEW_BRANCH_NAME"`
+LIBS_HAS_COMMIT=`github_commit_exists "$AR_LIBS_REPO" "$AR_NEW_BRANCH_NAME" "$IDF_COMMIT"`
+
+export IDF_COMMIT
+
+export AR_NEW_BRANCH_NAME
+export AR_NEW_COMMIT_MESSAGE
+export AR_NEW_PR_TITLE
+
+export AR_HAS_COMMIT
+export AR_HAS_BRANCH
+export AR_HAS_PR
+
+export LIBS_VERSION
+export LIBS_HAS_COMMIT
+export LIBS_HAS_BRANCH
+
+echo "IDF_COMMIT: $IDF_COMMIT"
+echo "AR_BRANCH: $AR_BRANCH"
+echo "AR_NEW_COMMIT_MESSAGE: $AR_NEW_COMMIT_MESSAGE"
+echo "AR_NEW_BRANCH_NAME: $AR_NEW_BRANCH_NAME"
+echo "AR_NEW_PR_TITLE: $AR_NEW_PR_TITLE"
+echo "AR_HAS_COMMIT: $AR_HAS_COMMIT"
+echo "AR_HAS_BRANCH: $AR_HAS_BRANCH"
+echo "AR_HAS_PR: $AR_HAS_PR"
+echo "LIBS_VERSION: $LIBS_VERSION"
+echo "LIBS_HAS_COMMIT: $LIBS_HAS_COMMIT"
+echo "LIBS_HAS_BRANCH: $LIBS_HAS_BRANCH"
+
+if [ ! -x $GITHUB_OUTPUT ]; then
+	echo "idf_commit=$IDF_COMMIT" >> "$GITHUB_OUTPUT"
+	echo "ar_branch=$AR_BRANCH" >> "$GITHUB_OUTPUT"
+	echo "ar_new_commit_message=$AR_NEW_COMMIT_MESSAGE" >> "$GITHUB_OUTPUT"
+	echo "ar_new_branch_name=$AR_NEW_BRANCH_NAME" >> "$GITHUB_OUTPUT"
+	echo "ar_new_pr_title=$AR_NEW_PR_TITLE" >> "$GITHUB_OUTPUT"
+	echo "ar_has_commit=$AR_HAS_COMMIT" >> "$GITHUB_OUTPUT"
+	echo "ar_has_branch=$AR_HAS_BRANCH" >> "$GITHUB_OUTPUT"
+	echo "ar_has_pr=$AR_HAS_PR" >> "$GITHUB_OUTPUT"
+	echo "libs_version=$LIBS_VERSION" >> "$GITHUB_OUTPUT"
+	echo "libs_has_commit=$LIBS_HAS_COMMIT" >> "$GITHUB_OUTPUT"
+	echo "libs_has_branch=$LIBS_HAS_BRANCH" >> "$GITHUB_OUTPUT"
+fi
diff --git a/tools/config.sh b/tools/config.sh
index e3f739800..54faa1f93 100755
--- a/tools/config.sh
+++ b/tools/config.sh
@@ -2,40 +2,42 @@
 
 
 if [ -z $IDF_PATH ]; then
-	export IDF_PATH="$PWD/esp-idf"
+    export IDF_PATH="$PWD/esp-idf"
 fi
 
 if [ -z $IDF_BRANCH ]; then
-	IDF_BRANCH="release/v4.4"
+    IDF_BRANCH="release/v5.1"
 fi
 
 if [ -z $AR_PR_TARGET_BRANCH ]; then
-	AR_PR_TARGET_BRANCH="release/v2.x"
+    AR_PR_TARGET_BRANCH="master"
 fi
 
 if [ -z $IDF_TARGET ]; then
-	if [ -f sdkconfig ]; then
-		IDF_TARGET=`cat sdkconfig | grep CONFIG_IDF_TARGET= | cut -d'"' -f2`
-		if [ "$IDF_TARGET" = "" ]; then
-			IDF_TARGET="esp32"
-		fi
-	else
-		IDF_TARGET="esp32"
-	fi
+    if [ -f sdkconfig ]; then
+        IDF_TARGET=`cat sdkconfig | grep CONFIG_IDF_TARGET= | cut -d'"' -f2`
+        if [ "$IDF_TARGET" = "" ]; then
+            IDF_TARGET="esp32"
+        fi
+    else
+        IDF_TARGET="esp32"
+    fi
 fi
 
-IDF_COMPS="$IDF_PATH/components"
-IDF_TOOLCHAIN="xtensa-$IDF_TARGET-elf"
-
 # Owner of the target ESP32 Arduino repository
 AR_USER="espressif"
 
 # The full name of the repository
 AR_REPO="$AR_USER/arduino-esp32"
+IDF_REPO="$AR_USER/esp-idf"
+AR_LIBS_REPO="$AR_USER/esp32-arduino-libs"
 
 AR_REPO_URL="https://github.com/$AR_REPO.git"
+IDF_REPO_URL="https://github.com/$IDF_REPO.git"
+AR_LIBS_REPO_URL="https://github.com/$AR_LIBS_REPO.git"
 if [ -n $GITHUB_TOKEN ]; then
-	AR_REPO_URL="https://$GITHUB_TOKEN@github.com/$AR_REPO.git"
+    AR_REPO_URL="https://$GITHUB_TOKEN@github.com/$AR_REPO.git"
+    AR_LIBS_REPO_URL="https://$GITHUB_TOKEN@github.com/$AR_LIBS_REPO.git"
 fi
 
 AR_ROOT="$PWD"
@@ -44,30 +46,38 @@ AR_OUT="$AR_ROOT/out"
 AR_TOOLS="$AR_OUT/tools"
 AR_PLATFORM_TXT="$AR_OUT/platform.txt"
 AR_GEN_PART_PY="$AR_TOOLS/gen_esp32part.py"
-AR_SDK="$AR_TOOLS/sdk/$IDF_TARGET"
+AR_SDK="$AR_TOOLS/esp32-arduino-libs/$IDF_TARGET"
+PIO_SDK="FRAMEWORK_SDK_DIR, \"$IDF_TARGET\""
+TOOLS_JSON_OUT="$AR_TOOLS/esp32-arduino-libs"
+IDF_LIBS_DIR="$AR_ROOT/../esp32-arduino-libs"
+
+if [ -d "$IDF_PATH" ]; then
+    export IDF_COMMIT=$(git -C "$IDF_PATH" rev-parse --short HEAD)
+    export IDF_BRANCH=$(git -C "$IDF_PATH" symbolic-ref --short HEAD || git -C "$IDF_PATH" tag --points-at HEAD)
+fi
 
 function get_os(){
-  	OSBITS=`arch`
-  	if [[ "$OSTYPE" == "linux"* ]]; then
+    OSBITS=`arch`
+    if [[ "$OSTYPE" == "linux"* ]]; then
         if [[ "$OSBITS" == "i686" ]]; then
-        	echo "linux32"
+            echo "linux32"
         elif [[ "$OSBITS" == "x86_64" ]]; then
-        	echo "linux64"
+            echo "linux64"
         elif [[ "$OSBITS" == "armv7l" ]]; then
-        	echo "linux-armel"
+            echo "linux-armel"
         else
-        	echo "unknown"
-	    	return 1
+            echo "unknown"
+            return 1
         fi
-	elif [[ "$OSTYPE" == "darwin"* ]]; then
-	    echo "macos"
-	elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
-	    echo "win32"
-	else
-	    echo "$OSTYPE"
-	    return 1
-	fi
-	return 0
+    elif [[ "$OSTYPE" == "darwin"* ]]; then
+        echo "macos"
+    elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
+        echo "win32"
+    else
+        echo "$OSTYPE"
+        return 1
+    fi
+    return 0
 }
 
 AR_OS=`get_os`
@@ -76,54 +86,84 @@ export SED="sed"
 export SSTAT="stat -c %s"
 
 if [[ "$AR_OS" == "macos" ]]; then
-	if ! [ -x "$(command -v gsed)" ]; then
-		echo "ERROR: gsed is not installed! Please install gsed first. ex. brew install gsed"
-		exit 1
-	fi
-	if ! [ -x "$(command -v gawk)" ]; then
-		echo "ERROR: gawk is not installed! Please install gawk first. ex. brew install gawk"
-		exit 1
-	fi
-	export SED="gsed"
-	export SSTAT="stat -f %z"
+    if ! [ -x "$(command -v gsed)" ]; then
+        echo "ERROR: gsed is not installed! Please install gsed first. ex. brew install gsed"
+        exit 1
+    fi
+    if ! [ -x "$(command -v gawk)" ]; then
+        echo "ERROR: gawk is not installed! Please install gawk first. ex. brew install gawk"
+        exit 1
+    fi
+    export SED="gsed"
+    export SSTAT="stat -f %z"
 fi
 
-function git_commit_exists(){ #git_commit_exists <repo-path> <commit-message>
-	local repo_path="$1"
-	local commit_message="$2"
-	local commits_found=`git -C "$repo_path" log --all --grep="$commit_message" | grep commit`
-	if [ -n "$commits_found" ]; then echo 1; else echo 0; fi
+function github_commit_exists(){ #github_commit_exists <repo-path> <branch-name> <commit-message>
+    local repo_path="$1"
+    local branch_name="$2"
+    local commit_message="$3"
+    local commits_found=`curl -s -k -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3.raw+json" "https://api.github.com/repos/$repo_path/commits?sha=$branch_name" | jq -r '.[].commit.message' | grep "$commit_message" | wc -l`
+    if [ ! "$commits_found" == "" ] && [ ! "$commits_found" == "null" ] && [ ! "$commits_found" == "0" ]; then echo $commits_found; else echo 0; fi
+}
+
+function github_last_commit(){ # github_last_commit <repo-path> <branch-name>
+    local repo_path="$1"
+    local branch_name="$2"
+    local commit=`curl -s -k -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3.raw+json" "https://api.github.com/repos/$repo_path/commits/heads/$branch_name" | jq -r '.sha'`
+    if [ ! "$commit" == "" ] && [ ! "$commit" == "null" ]; then
+        echo ${commit:0:8}
+    else
+        echo ""
+    fi
+}
+
+function github_branch_exists(){ # github_branch_exists <repo-path> <branch-name>
+    local repo_path="$1"
+    local branch_name="$2"
+    local branch=`curl -s -k -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3.raw+json" "https://api.github.com/repos/$repo_path/branches/$branch_name" | jq -r '.name'`
+    if [ "$branch" == "$branch_name" ]; then echo 1; else echo 0; fi
+}
+
+function github_pr_exists(){ # github_pr_exists <repo-path> <branch-name>
+    local repo_path="$1"
+    local branch_name="$2"
+    local pr_num=`curl -s -k -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3.raw+json" "https://api.github.com/repos/$repo_path/pulls?head=$AR_USER:$branch_name&state=open" | jq -r '.[].number'`
+    if [ ! "$pr_num" == "" ] && [ ! "$pr_num" == "null" ]; then echo 1; else echo 0; fi
 }
 
+
+
 function git_branch_exists(){ # git_branch_exists <repo-path> <branch-name>
-	local repo_path="$1"
-	local branch_name="$2"
-	local branch_found=`git -C "$repo_path" ls-remote --heads origin "$branch_name"`
-	if [ -n "$branch_found" ]; then echo 1; else echo 0; fi
+    local repo_path="$1"
+    local branch_name="$2"
+    local branch_found=`git -C "$repo_path" ls-remote --heads origin "$branch_name"`
+    if [ -n "$branch_found" ]; then echo 1; else echo 0; fi
 }
 
-function git_pr_exists(){ # git_pr_exists <branch-name>
-	local pr_num=`curl -s -k -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3.raw+json" "https://api.github.com/repos/$AR_REPO/pulls?head=$AR_USER:$1&state=open" | jq -r '.[].number'`
-	if [ ! "$pr_num" == "" ] && [ ! "$pr_num" == "null" ]; then echo 1; else echo 0; fi
+function git_commit_exists(){ #git_commit_exists <repo-path> <commit-message>
+    local repo_path="$1"
+    local commit_message="$2"
+    local commits_found=`git -C "$repo_path" log --all --grep="$commit_message" | grep commit`
+    if [ -n "$commits_found" ]; then echo 1; else echo 0; fi
 }
 
 function git_create_pr(){ # git_create_pr <branch> <title>
-	local pr_branch="$1"
-	local pr_title="$2"
-	local pr_target="$3"
-	local pr_body=""
-	pr_body+="esp-idf: "$(git -C "$IDF_PATH" symbolic-ref --short HEAD || git -C "$IDF_PATH" tag --points-at HEAD)" "$(git -C "$IDF_PATH" rev-parse --short HEAD)"\r\n"
-	for component in `ls "$AR_COMPS"`; do
-		if [ ! $component == "arduino" ]; then
-			if [ -d "$AR_COMPS/$component/.git" ] || [ -d "$AR_COMPS/$component/.github" ]; then
-				pr_body+="$component: "$(git -C "$AR_COMPS/$component" symbolic-ref --short HEAD || git -C "$AR_COMPS/$component" tag --points-at HEAD)" "$(git -C "$AR_COMPS/$component" rev-parse --short HEAD)"\r\n"
-			fi
-		fi
-	done
-	pr_body+="tinyusb: "$(git -C "$AR_COMPS/arduino_tinyusb/tinyusb" symbolic-ref --short HEAD || git -C "$AR_COMPS/arduino_tinyusb/tinyusb" tag --points-at HEAD)" "$(git -C "$AR_COMPS/arduino_tinyusb/tinyusb" rev-parse --short HEAD)"\r\n"
-	local pr_data="{\"title\": \"$pr_title\", \"body\": \"$pr_body\", \"head\": \"$AR_USER:$pr_branch\", \"base\": \"$pr_target\"}"
-	git_create_pr_res=`echo "$pr_data" | curl -k -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3.raw+json" --data @- "https://api.github.com/repos/$AR_REPO/pulls"`
-	local done_pr=`echo "$git_create_pr_res" | jq -r '.title'`
-	if [ ! "$done_pr" == "" ] && [ ! "$done_pr" == "null" ]; then echo 1; else echo 0; fi
+    local pr_branch="$1"
+    local pr_title="$2"
+    local pr_target="$3"
+    local pr_body=""
+    pr_body+="esp-idf: "$(git -C "$IDF_PATH" symbolic-ref --short HEAD || git -C "$IDF_PATH" tag --points-at HEAD)" "$(git -C "$IDF_PATH" rev-parse --short HEAD)"\r\n"
+    for component in `ls "$AR_COMPS"`; do
+        if [ ! $component == "arduino" ]; then
+            if [ -d "$AR_COMPS/$component/.git" ] || [ -d "$AR_COMPS/$component/.github" ]; then
+                pr_body+="$component: "$(git -C "$AR_COMPS/$component" symbolic-ref --short HEAD || git -C "$AR_COMPS/$component" tag --points-at HEAD)" "$(git -C "$AR_COMPS/$component" rev-parse --short HEAD)"\r\n"
+            fi
+        fi
+    done
+    pr_body+="tinyusb: "$(git -C "$AR_COMPS/arduino_tinyusb/tinyusb" symbolic-ref --short HEAD || git -C "$AR_COMPS/arduino_tinyusb/tinyusb" tag --points-at HEAD)" "$(git -C "$AR_COMPS/arduino_tinyusb/tinyusb" rev-parse --short HEAD)"\r\n"
+    local pr_data="{\"title\": \"$pr_title\", \"body\": \"$pr_body\", \"head\": \"$AR_USER:$pr_branch\", \"base\": \"$pr_target\"}"
+    git_create_pr_res=`echo "$pr_data" | curl -k -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3.raw+json" --data @- "https://api.github.com/repos/$AR_REPO/pulls"`
+    local done_pr=`echo "$git_create_pr_res" | jq -r '.title'`
+    if [ ! "$done_pr" == "" ] && [ ! "$done_pr" == "null" ]; then echo 1; else echo 0; fi
 }
 
diff --git a/tools/copy-libs.sh b/tools/copy-libs.sh
index e132124c3..9cec8e5ab 100755
--- a/tools/copy-libs.sh
+++ b/tools/copy-libs.sh
@@ -30,9 +30,15 @@ fi
 if [ -e "$AR_SDK/include" ]; then
 	rm -rf "$AR_SDK/include"
 fi
+if [ -e "$AR_SDK/flags" ]; then
+	rm -rf "$AR_SDK/flags"
+fi
 if [ -e "$AR_SDK/$MEMCONF" ]; then
 	rm -rf "$AR_SDK/$MEMCONF"
 fi
+if [ -e "$AR_SDK/platformio-build.py" ]; then
+	rm -rf "$AR_SDK/platformio-build.py"
+fi
 mkdir -p "$AR_SDK"
 
 function get_actual_path(){
@@ -95,7 +101,7 @@ for item in "${@:2:${#@}-5}"; do
 	elif [ "$prefix" = "-O" ]; then
 		PIO_CC_FLAGS+="$item "
 	elif [[ "$item" != "-Wall" && "$item" != "-Werror=all"  && "$item" != "-Wextra" ]]; then
-		if [[ "${item:0:23}" != "-mfix-esp32-psram-cache" && "${item:0:18}" != "-fmacro-prefix-map" ]]; then
+		if [[ "${item:0:23}" != "-mfix-esp32-psram-cache" && "${item:0:18}" != "-fmacro-prefix-map" && "${item:0:20}" != "-fdiagnostics-color=" ]]; then
 			C_FLAGS+="$item "
 		fi
 	fi
@@ -109,7 +115,7 @@ set -- $str
 for item in "${@:2:${#@}-5}"; do
 	prefix="${item:0:2}"
 	if [[ "$prefix" != "-I" && "$prefix" != "-D" && "$item" != "-Wall" && "$item" != "-Werror=all"  && "$item" != "-Wextra" && "$prefix" != "-O" ]]; then
-		if [[ "${item:0:23}" != "-mfix-esp32-psram-cache" && "${item:0:18}" != "-fmacro-prefix-map" ]]; then
+		if [[ "${item:0:23}" != "-mfix-esp32-psram-cache" && "${item:0:18}" != "-fmacro-prefix-map" && "${item:0:20}" != "-fdiagnostics-color=" ]]; then
 			AS_FLAGS+="$item "
 			if [[ $C_FLAGS == *"$item"* ]]; then
 				PIO_CC_FLAGS+="$item "
@@ -128,7 +134,7 @@ set -- $str
 for item in "${@:2:${#@}-5}"; do
 	prefix="${item:0:2}"
 	if [[ "$prefix" != "-I" && "$prefix" != "-D" && "$item" != "-Wall" && "$item" != "-Werror=all"  && "$item" != "-Wextra" && "$prefix" != "-O" ]]; then
-		if [[ "${item:0:23}" != "-mfix-esp32-psram-cache" && "${item:0:18}" != "-fmacro-prefix-map" ]]; then
+		if [[ "${item:0:23}" != "-mfix-esp32-psram-cache" && "${item:0:18}" != "-fmacro-prefix-map" && "${item:0:20}" != "-fdiagnostics-color=" ]]; then
 			CPP_FLAGS+="$item "
 			if [[ $PIO_CC_FLAGS != *"$item"* ]]; then
 				PIO_CXX_FLAGS+="$item "
@@ -155,13 +161,15 @@ else
 	libs="${libs:19:${#libs}-1}"
 	flags=`cat build/build.ninja | grep LINK_FLAGS`
 	flags="${flags:15:${#flags}-1}"
+	paths=`cat build/build.ninja | grep LINK_PATH`
+	paths="${paths:14:${#paths}-1}"
 	if [ "$IDF_TARGET" = "esp32" ]; then
 		flags="-Wno-frame-address $flags"
 	fi
-	if [ "$IDF_TARGET" != "esp32c3" ]; then
+	if [ "$IS_XTENSA" = "y" ]; then
 		flags="-mlongcalls $flags"
 	fi
-	str="$flags $libs"
+	str="$flags $libs $paths"
 fi
 if [ "$IDF_TARGET" = "esp32" ]; then
 	LD_SCRIPTS+="-T esp32.rom.redefined.ld "
@@ -271,13 +279,11 @@ done
 # END OF DATA EXTRACTION FROM CMAKE
 #
 
-AR_PLATFORMIO_PY="$AR_TOOLS/platformio-build-$IDF_TARGET.py"
+mkdir -p "$AR_SDK"
 
 # start generation of platformio-build.py
-awk "/ASFLAGS=\[/{n++}{print>n\"pio_start.txt\"}" $AR_COMPS/arduino/tools/platformio-build-$IDF_TARGET.py
-awk "/\"ARDUINO_ARCH_ESP32\"/{n++}{print>n\"pio_end.txt\"}" 1pio_start.txt
-cat pio_start.txt > "$AR_PLATFORMIO_PY"
-rm pio_end.txt 1pio_start.txt pio_start.txt
+AR_PLATFORMIO_PY="$AR_SDK/platformio-build.py"
+cat configs/pio_start.txt > "$AR_PLATFORMIO_PY"
 
 echo "    ASFLAGS=[" >> "$AR_PLATFORMIO_PY"
 if [ "$IS_XTENSA" = "y" ]; then
@@ -348,8 +354,8 @@ echo "        '-Wl,-Map=\"%s\"' % join(\"\${BUILD_DIR}\", \"\${PROGNAME}.map\")"
 echo "    ]," >> "$AR_PLATFORMIO_PY"
 echo "" >> "$AR_PLATFORMIO_PY"
 
-# # include dirs
-AR_INC=""
+# include dirs
+REL_INC=""
 echo "    CPPPATH=[" >> "$AR_PLATFORMIO_PY"
 
 set -- $INCLUDES
@@ -376,13 +382,13 @@ for item; do
 
 		out_sub="${item#*$ipath}"
 		out_cpath="$AR_SDK/include/$fname$out_sub"
-		AR_INC+=" \"-I{compiler.sdk.path}/include/$fname$out_sub\""
+		REL_INC+="-iwithprefixbefore $fname$out_sub "
 		if [ "$out_sub" = "" ]; then
-			echo "        join(FRAMEWORK_DIR, \"tools\", \"sdk\", \"$IDF_TARGET\", \"include\", \"$fname\")," >> "$AR_PLATFORMIO_PY"
+			echo "        join($PIO_SDK, \"include\", \"$fname\")," >> "$AR_PLATFORMIO_PY"
 		else
 			pio_sub="${out_sub:1}"
 			pio_sub=`echo $pio_sub | sed 's/\//\\", \\"/g'`
-			echo "        join(FRAMEWORK_DIR, \"tools\", \"sdk\", \"$IDF_TARGET\", \"include\", \"$fname\", \"$pio_sub\")," >> "$AR_PLATFORMIO_PY"
+			echo "        join($PIO_SDK, \"include\", \"$fname\", \"$pio_sub\")," >> "$AR_PLATFORMIO_PY"
 		fi
 		for f in `find "$item" -name '*.h'`; do
 			rel_f=${f#*$item}
@@ -396,10 +402,15 @@ for item; do
 			mkdir -p "$out_cpath$rel_p"
 			cp -n $f "$out_cpath$rel_p/"
 		done
+		# Temporary measure to fix issues caused by https://github.com/espressif/esp-idf/commit/dc4731101dd567cc74bbe4d0f03afe52b7db9afb#diff-1d2ce0d3989a80830fdf230bcaafb3117f32046d16cf46616ac3d55b4df2a988R17
+		if [[ "$fname" == "bt" && "$out_sub" == "/include/$IDF_TARGET/include" && -f "$ipath/controller/$IDF_TARGET/esp_bt_cfg.h" ]]; then
+			mkdir -p "$AR_SDK/include/$fname/controller/$IDF_TARGET"
+			cp -n "$ipath/controller/$IDF_TARGET/esp_bt_cfg.h" "$AR_SDK/include/$fname/controller/$IDF_TARGET/esp_bt_cfg.h"
+		fi
 	fi
 done
-echo "        join(FRAMEWORK_DIR, \"tools\", \"sdk\", \"$IDF_TARGET\", env.BoardConfig().get(\"build.arduino.memory_type\", (env.BoardConfig().get(\"build.flash_mode\", \"dio\") + \"_$OCT_PSRAM\")), \"include\")," >> "$AR_PLATFORMIO_PY"
-echo "        join(FRAMEWORK_DIR, \"cores\", env.BoardConfig().get(\"build.core\"))" >> "$AR_PLATFORMIO_PY"
+echo "        join($PIO_SDK, board_config.get(\"build.arduino.memory_type\", (board_config.get(\"build.flash_mode\", \"dio\") + \"_$OCT_PSRAM\")), \"include\")," >> "$AR_PLATFORMIO_PY"
+echo "        join(FRAMEWORK_DIR, \"cores\", board_config.get(\"build.core\"))" >> "$AR_PLATFORMIO_PY"
 echo "    ]," >> "$AR_PLATFORMIO_PY"
 echo "" >> "$AR_PLATFORMIO_PY"
 
@@ -421,9 +432,9 @@ for item; do
 done
 
 echo "    LIBPATH=[" >> "$AR_PLATFORMIO_PY"
-echo "        join(FRAMEWORK_DIR, \"tools\", \"sdk\", \"$IDF_TARGET\", \"lib\")," >> "$AR_PLATFORMIO_PY"
-echo "        join(FRAMEWORK_DIR, \"tools\", \"sdk\", \"$IDF_TARGET\", \"ld\")," >> "$AR_PLATFORMIO_PY"
-echo "        join(FRAMEWORK_DIR, \"tools\", \"sdk\", \"$IDF_TARGET\", env.BoardConfig().get(\"build.arduino.memory_type\", (env.BoardConfig().get(\"build.flash_mode\", \"dio\") + \"_$OCT_PSRAM\")))" >> "$AR_PLATFORMIO_PY"
+echo "        join($PIO_SDK, \"lib\")," >> "$AR_PLATFORMIO_PY"
+echo "        join($PIO_SDK, \"ld\")," >> "$AR_PLATFORMIO_PY"
+echo "        join($PIO_SDK, board_config.get(\"build.arduino.memory_type\", (board_config.get(\"build.flash_mode\", \"dio\") + \"_$OCT_PSRAM\")))" >> "$AR_PLATFORMIO_PY"
 echo "    ]," >> "$AR_PLATFORMIO_PY"
 echo "" >> "$AR_PLATFORMIO_PY"
 
@@ -449,40 +460,36 @@ for item; do
 	fi
 done
 
-# remove backslashes for Arduino
-DEFINES=`echo "$DEFINES" | tr -d '\\'`
-
-
 # end generation of platformio-build.py
-cat 1pio_end.txt >> "$AR_PLATFORMIO_PY"
-rm 1pio_end.txt
-
-# arduino platform.txt
-platform_file="$AR_COMPS/arduino/platform.txt"
-if [ -f "$AR_PLATFORM_TXT" ]; then
-	# use the file we have already compiled for other chips
-	platform_file="$AR_PLATFORM_TXT"
+cat configs/pio_end.txt >> "$AR_PLATFORMIO_PY"
+
+# replace double backslashes with single one
+DEFINES=`echo "$DEFINES" | tr -s '\'`
+
+# target flags files
+FLAGS_DIR="$AR_SDK/flags"
+mkdir -p "$FLAGS_DIR"
+echo -n "$DEFINES" > "$FLAGS_DIR/defines"
+echo -n "$REL_INC" > "$FLAGS_DIR/includes"
+echo -n "$C_FLAGS" > "$FLAGS_DIR/c_flags"
+echo -n "$CPP_FLAGS" > "$FLAGS_DIR/cpp_flags"
+echo -n "$AS_FLAGS" > "$FLAGS_DIR/S_flags"
+echo -n "$LD_FLAGS" > "$FLAGS_DIR/ld_flags"
+echo -n "$LD_SCRIPTS" > "$FLAGS_DIR/ld_scripts"
+echo -n "$AR_LIBS" > "$FLAGS_DIR/ld_libs"
+
+# sr model.bin
+if [ -f "build/srmodels/srmodels.bin" ]; then
+	mkdir -p "$AR_SDK/esp_sr"
+	cp -f "build/srmodels/srmodels.bin" "$AR_SDK/esp_sr/"
+	cp -f "partitions.csv" "$AR_SDK/esp_sr/"
 fi
-awk "/compiler.cpreprocessor.flags.$IDF_TARGET=/{n++}{print>n\"platform_start.txt\"}" "$platform_file"
-$SED -i "/compiler.cpreprocessor.flags.$IDF_TARGET\=/d" 1platform_start.txt
-awk "/compiler.ar.flags.$IDF_TARGET=/{n++}{print>n\"platform_mid.txt\"}" 1platform_start.txt
-rm -rf 1platform_start.txt
-
-cat platform_start.txt > "$AR_PLATFORM_TXT"
-echo "compiler.cpreprocessor.flags.$IDF_TARGET=$DEFINES $AR_INC" >> "$AR_PLATFORM_TXT"
-echo "compiler.c.elf.libs.$IDF_TARGET=$AR_LIBS" >> "$AR_PLATFORM_TXT"
-echo "compiler.c.flags.$IDF_TARGET=$C_FLAGS -MMD -c" >> "$AR_PLATFORM_TXT"
-echo "compiler.cpp.flags.$IDF_TARGET=$CPP_FLAGS -MMD -c" >> "$AR_PLATFORM_TXT"
-echo "compiler.S.flags.$IDF_TARGET=$AS_FLAGS -x assembler-with-cpp -MMD -c" >> "$AR_PLATFORM_TXT"
-echo "compiler.c.elf.flags.$IDF_TARGET=$LD_SCRIPTS $LD_FLAGS" >> "$AR_PLATFORM_TXT"
-cat 1platform_mid.txt >> "$AR_PLATFORM_TXT"
-rm -rf platform_start.txt platform_mid.txt 1platform_mid.txt
 
 # sdkconfig
 cp -f "sdkconfig" "$AR_SDK/sdkconfig"
 
 # gen_esp32part.py
-cp "$IDF_COMPS/partition_table/gen_esp32part.py" "$AR_GEN_PART_PY"
+# cp "$IDF_PATH/components/partition_table/gen_esp32part.py" "$AR_GEN_PART_PY"
 
 # copy precompiled libs (if we need them)
 function copy_precompiled_lib(){
diff --git a/tools/copy-to-arduino.sh b/tools/copy-to-arduino.sh
index ef8b9074c..96092e068 100755
--- a/tools/copy-to-arduino.sh
+++ b/tools/copy-to-arduino.sh
@@ -16,10 +16,8 @@ fi
 
 echo "Installing new libraries to $ESP32_ARDUINO"
 
-rm -rf $ESP32_ARDUINO/tools/sdk $ESP32_ARDUINO/tools/gen_esp32part.py $ESP32_ARDUINO/tools/platformio-build-*.py $ESP32_ARDUINO/platform.txt
-
-cp -f $AR_OUT/platform.txt $ESP32_ARDUINO/
+rm -rf $ESP32_ARDUINO/package/package_esp32_index.template.json && \
 cp -f $AR_OUT/package_esp32_index.template.json $ESP32_ARDUINO/package/package_esp32_index.template.json
-cp -Rf $AR_TOOLS/sdk $ESP32_ARDUINO/tools/
-cp -f $AR_TOOLS/gen_esp32part.py $ESP32_ARDUINO/tools/
-cp -f $AR_TOOLS/platformio-build-*.py $ESP32_ARDUINO/tools/
+
+rm -rf $ESP32_ARDUINO/tools/esp32-arduino-libs && \
+cp -Rf $AR_TOOLS/esp32-arduino-libs $ESP32_ARDUINO/tools/
diff --git a/tools/cron.sh b/tools/cron.sh
old mode 100644
new mode 100755
index 2a3fb88cd..000fdc4dc
--- a/tools/cron.sh
+++ b/tools/cron.sh
@@ -5,7 +5,4 @@ if [ ! "$GITHUB_EVENT_NAME" == "schedule" ]; then
     exit 1
 fi
 
-git checkout "$IDF_BRANCH" #local branches should match what the matrix wants to build
-DEPLOY_OUT=1
-source ./build.sh
-# bash ./tools/push-to-arduino.sh
+bash ./build.sh -d
diff --git a/tools/gen_platformio_manifest.py b/tools/gen_platformio_manifest.py
new file mode 100644
index 000000000..2d031b687
--- /dev/null
+++ b/tools/gen_platformio_manifest.py
@@ -0,0 +1,86 @@
+import argparse
+import json
+import os
+import re
+import sys
+
+MANIFEST_DATA = {
+    "name": "framework-arduinoespressif32-libs",
+    "description": "Precompiled libraries for Arduino Wiring-based Framework for the Espressif ESP32 series of SoCs",
+    "keywords": ["framework", "arduino", "espressif", "esp32"],
+    "license": "LGPL-2.1-or-later",
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/espressif/esp32-arduino-libs",
+    },
+}
+
+
+def convert_version(version_string):
+    """A helper function that converts a custom IDF version string
+    extracted from a Git repository to a suitable SemVer alternative. For example:
+    'release/v5.1' becomes '5.1.0',
+    'v7.7.7' becomes '7.7.7'
+    """
+
+    regex_pattern = (
+        r"v(?P<MAJOR>0|[1-9]\d*)\.(?P<MINOR>0|[1-9]\d*)\.*(?P<PATCH>0|[1-9]\d*)*"
+    )
+    match = re.search(regex_pattern, version_string)
+    if not match:
+        sys.stderr.write(
+            f"Failed to find a regex match for '{regex_pattern}' in '{version_string}'\n"
+        )
+        return ""
+
+    major, minor, patch = match.groups()
+    if not patch:
+        patch = "0"
+
+    return ".".join((major, minor, patch))
+
+
+def main(dst_dir, version_string, commit_hash):
+
+    converted_version = convert_version(version_string)
+    if not converted_version:
+        sys.stderr.write(f"Failed to convert version '{version_string}'\n")
+        return -1
+
+    manifest_file_path = os.path.join(dst_dir, "package.json")
+    with open(manifest_file_path, "w", encoding="utf8") as fp:
+        MANIFEST_DATA["version"] = f"{converted_version}+sha.{commit_hash}"
+        json.dump(MANIFEST_DATA, fp, indent=2)
+
+    print(
+        f"Generated PlatformIO manifest file '{manifest_file_path}' with '{converted_version}' version"
+    )
+    return 0
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "-o",
+        "--dst-dir",
+        dest="dst_dir",
+        required=True,
+        help="Destination folder where the 'package.json' manifest will be located",
+    )
+    parser.add_argument(
+        "-s",
+        "--version-string",
+        dest="version_string",
+        required=True,
+        help="ESP-IDF version string used for compiling libraries",
+    )
+    parser.add_argument(
+        "-c",
+        "--commit-hash",
+        dest="commit_hash",
+        required=True,
+        help="ESP-IDF revision in form of a commit hash",
+    )
+    args = parser.parse_args()
+
+    sys.exit(main(args.dst_dir, args.version_string, args.commit_hash))
diff --git a/tools/gen_tools_json.py b/tools/gen_tools_json.py
index c63d1e962..ca53ec8cb 100644
--- a/tools/gen_tools_json.py
+++ b/tools/gen_tools_json.py
@@ -25,10 +25,17 @@
         prog = 'gen_tools_json',
         description = 'Update Arduino package index with the tolls found in ESP-IDF')
     parser.add_argument('-i', '--esp-idf', dest='idf_path', required=True, help='Path to ESP-IDF')
-    parser.add_argument('-j', '--pkg-json', dest='arduino_json', required=True, help='path to Arduino package json')
+    parser.add_argument('-j', '--pkg-json', dest='arduino_json', required=False, help='path to Arduino package json')
     parser.add_argument('-o', '--out-path', dest='out_path', required=True, help='Output path to store the update package json')
     args = parser.parse_args()
 
+    simple_output = False
+    if args.arduino_json == None:
+        print('Source was not selected')
+        simple_output = True
+    else:
+        print('Source {0}.'.format(args.arduino_json))
+
     idf_path = args.idf_path;
     arduino_json = args.arduino_json;
     out_path = args.out_path;
@@ -37,7 +44,9 @@
     arduino_tools = ["xtensa-esp32-elf","xtensa-esp32s2-elf","xtensa-esp32s3-elf","xtensa-esp-elf-gdb","riscv32-esp-elf","riscv32-esp-elf-gdb","openocd-esp32"]
 
     # code start
-    farray = json.load(open(arduino_json))
+    farray = {"packages":[{"platforms":[{"toolsDependencies":[]}],"tools":[]}]}
+    if simple_output == False:
+        farray = json.load(open(arduino_json))
 
     idf_tools = json.load(open(idf_path + '/tools/tools.json'))
     for tool in idf_tools['tools']:
@@ -51,21 +60,30 @@
             tool_name += '-gcc'
         print('Found {0}, version: {1}'.format(tool_name, tool_version))
         
-        dep_found = False
-        dep_skip = False
-        for dep in farray['packages'][0]['platforms'][0]['toolsDependencies']:
-            if dep['name'] == tool_name:
-                if dep['version'] == tool_version:
-                    print('Skipping {0}. Same version {1}'.format(tool_name, tool_version))
-                    dep_skip = True
-                    break
-                print('Updating dependency version of {0} from {1} to {2}'.format(tool_name, dep['version'], tool_version))
-                dep['version'] = tool_version
-                dep_found = True
-        if dep_skip == True:
-            continue
-        if dep_found == False:
-            print('Adding new dependency: {0} version {1}'.format(tool_name, tool_version))
+        if simple_output == False:
+            dep_found = False
+            dep_skip = False
+            for dep in farray['packages'][0]['platforms'][0]['toolsDependencies']:
+                if dep['name'] == tool_name:
+                    if dep['version'] == tool_version:
+                        print('Skipping {0}. Same version {1}'.format(tool_name, tool_version))
+                        dep_skip = True
+                        break
+                    print('Updating dependency version of {0} from {1} to {2}'.format(tool_name, dep['version'], tool_version))
+                    dep['version'] = tool_version
+                    dep_found = True
+            if dep_skip == True:
+                continue
+            if dep_found == False:
+                print('Adding new dependency: {0} version {1}'.format(tool_name, tool_version))
+                deps = {
+                    "packager": "esp32",
+                    "name": tool_name,
+                    "version": tool_version
+                }
+                farray['packages'][0]['platforms'][0]['toolsDependencies'].append(deps)
+        else:
+            print('Adding dependency: {0} version {1}'.format(tool_name, tool_version))
             deps = {
                 "packager": "esp32",
                 "name": tool_name,
@@ -84,7 +102,7 @@
                 "url": tool_data['url'],
                 "archiveFileName": os.path.basename(tool_data['url']),
                 "checksum": "SHA-256:"+tool_data['sha256'],
-                "size": tool_data['size']
+                "size": str(tool_data['size'])
             }
 
             if arch == "win32":
@@ -111,15 +129,24 @@
 
             systems.append(system)
 
-        tool_found = False
-        for t in farray['packages'][0]['tools']:
-            if t['name'] == tool_name:
-                t['version'] = tool_version
-                t['systems'] = systems
-                tool_found = True
-                print('Updating binaries of {0} to version {1}'.format(tool_name, tool_version))
-        if tool_found == False:
-            print('Adding new tool: {0} version {1}'.format(tool_name, tool_version))
+        if simple_output == False:
+            tool_found = False
+            for t in farray['packages'][0]['tools']:
+                if t['name'] == tool_name:
+                    t['version'] = tool_version
+                    t['systems'] = systems
+                    tool_found = True
+                    print('Updating binaries of {0} to version {1}'.format(tool_name, tool_version))
+            if tool_found == False:
+                print('Adding new tool: {0} version {1}'.format(tool_name, tool_version))
+                tools = {
+                    "name": tool_name,
+                    "version": tool_version,
+                    "systems": systems
+                }
+                farray['packages'][0]['tools'].append(tools)
+        else:
+            print('Adding tool: {0} version {1}'.format(tool_name, tool_version))
             tools = {
                 "name": tool_name,
                 "version": tool_version,
@@ -128,7 +155,10 @@
             farray['packages'][0]['tools'].append(tools)
 
     json_str = json.dumps(farray, indent=2)
-    out_file = out_path + os.path.basename(arduino_json)
+    out_file = out_path + "tools.json"
+    if simple_output == False:
+        out_file = out_path + os.path.basename(arduino_json)
+
     with open(out_file, "w") as f:
         f.write(json_str+"\n")
         f.close()
diff --git a/tools/get_projbuild_gitconfig.py b/tools/get_projbuild_gitconfig.py
new file mode 100644
index 000000000..305bb117c
--- /dev/null
+++ b/tools/get_projbuild_gitconfig.py
@@ -0,0 +1,124 @@
+# This file is expected to be present in ${COMPONENT_DIR}
+# accessed from components/esp_insights/CMakeLists.txt
+# Used in:
+# 1. Project ESP Insights build package tar file
+
+#from __future__ import unicode_literals
+import os
+import sys
+import json
+import subprocess
+from builtins import range, str
+from io import open
+
+# Input project directory from CMakeLists.txt
+PROJ_DIR=sys.argv[1]
+# Input project name
+PROJ_NAME=sys.argv[2]
+# Input project version
+PROJ_VER=sys.argv[3]
+# Input custom config filename from CMakeLists.txt
+FILENAME=sys.argv[4]
+# Input IDF_PATH from CMakeLists.txt
+IDF_PATH=sys.argv[5]
+# Input target
+TARGET=sys.argv[6]
+
+NEWLINE = "\n"
+
+CONFIG = {}
+
+# Set Config
+
+# Set current directory i.e Set ${COMPONENT_DIR} as current directory
+CURR_DIR = os.getcwd()
+
+def _change_dir(dirname):
+    # Change directory
+    os.chdir(dirname)
+
+
+def _set_submodule_cfg(submodules, repo_name):
+    # Set config for submodules
+    CFG_TITLE = "submodules"
+    NAME_STR = "name"
+    VERSION_STR = "version"
+    CONFIG[repo_name][CFG_TITLE] = []
+    
+    if submodules:
+        # Get the submodule name and version
+        submodules_list = submodules.strip().split(NEWLINE)
+        for i in range(0, len(submodules_list), 2):
+            name = submodules_list[i].split('\'')[1]
+            version = submodules_list[i+1]
+            submodule_json = { NAME_STR: name, VERSION_STR: version }
+            CONFIG[repo_name][CFG_TITLE].append(submodule_json)
+
+
+def run_cmd(command, get_basename=False):
+    try:
+        resp = subprocess.check_output(command, shell=True).strip().decode('utf-8')
+        if get_basename:
+            resp = os.path.basename(resp)
+        return resp
+    except subprocess.CalledProcessError:
+        raise Exception("ERROR: Please check command : {}".format(command))
+
+def set_cfg(config_name):
+    # Set config for ESP-IDF Repo
+    if config_name  == "esp-idf":
+        # Get repo name (for IDF repo)
+        REPO_CMD='git rev-parse --show-toplevel'
+        repo_name = run_cmd(REPO_CMD, get_basename=True)
+        CONFIG[repo_name] = {}
+
+        # Get commit HEAD
+        GITHEAD_STR = "HEAD"
+        HEAD='git describe --always --tags --dirty'
+        head_ver = run_cmd(HEAD)
+        CONFIG[repo_name][GITHEAD_STR] = head_ver
+
+        # Get submodule latest refs
+        SUBMODULE = 'git submodule foreach git describe --always --tags --dirty'
+        submodules = run_cmd(SUBMODULE)
+        _set_submodule_cfg(submodules, repo_name)
+    elif config_name == "toolchain":
+        # Set config for Toolchain Version
+        arch_target = "xtensa-" + TARGET
+        if TARGET == "esp32c3" or TARGET == "esp32c2" or TARGET == "esp32h2" or TARGET == "esp32c6":
+            arch_target = "riscv32-esp"
+        # Get toolchain version
+        TOOLCHAIN_STR = "toolchain"
+        TOOLCHAIN = arch_target + '-elf-gcc --version'
+        toolchain = run_cmd(TOOLCHAIN)
+        CONFIG[TOOLCHAIN_STR] = toolchain.strip().split(NEWLINE)[0]
+
+# Set project details - name and version
+def set_project_details():
+    # Set project name and version
+    CONFIG['project'] = {}
+    CONFIG['project']['name'] = PROJ_NAME
+    CONFIG['project']['version'] = PROJ_VER
+
+try:
+    with open(FILENAME, "w+", encoding="utf-8") as output_file:
+        # ESP-IDF REPO CONFIG
+        # Change to ESP-IDF Directory
+        _change_dir(IDF_PATH)
+        set_cfg("esp-idf")
+
+        # Change back to ${COMPONENT_DIR}
+        _change_dir(CURR_DIR)
+
+        # Set project name and version
+        set_project_details()
+
+        # GET TOOLCHAIN VERSION
+        set_cfg("toolchain")
+
+        output_file.write(str(json.dumps(CONFIG, indent=4, sort_keys=True)))
+
+except Exception as e:
+    # Remove config file created if error occurs
+    os.system("rm " + FILENAME)
+    sys.exit(e)
\ No newline at end of file
diff --git a/tools/install-arduino.sh b/tools/install-arduino.sh
new file mode 100755
index 000000000..51e1162a4
--- /dev/null
+++ b/tools/install-arduino.sh
@@ -0,0 +1,62 @@
+#/bin/bash
+
+source ./tools/config.sh
+
+#
+# CLONE/UPDATE ARDUINO
+#
+echo "Updating ESP32 Arduino..."
+if [ ! -d "$AR_COMPS/arduino" ]; then
+	git clone $AR_REPO_URL "$AR_COMPS/arduino"
+fi
+
+if [ -z $AR_BRANCH ]; then
+	if [ -z $GITHUB_HEAD_REF ]; then
+		current_branch=`git branch --show-current`
+	else
+		current_branch="$GITHUB_HEAD_REF"
+	fi
+	echo "Current Branch: $current_branch"
+	if [[ "$current_branch" != "master" && `git_branch_exists "$AR_COMPS/arduino" "$current_branch"` == "1" ]]; then
+		export AR_BRANCH="$current_branch"
+	else
+		if [ "$IDF_TAG" ]; then #tag was specified at build time
+			AR_BRANCH_NAME="idf-$IDF_TAG"
+		elif [ "$IDF_COMMIT" ]; then #commit was specified at build time
+			AR_BRANCH_NAME="idf-$IDF_COMMIT"
+		else
+			AR_BRANCH_NAME="idf-$IDF_BRANCH"
+		fi
+		has_ar_branch=`git_branch_exists "$AR_COMPS/arduino" "$AR_BRANCH_NAME"`
+		if [ "$has_ar_branch" == "1" ]; then
+			export AR_BRANCH="$AR_BRANCH_NAME"
+		else
+			has_ar_branch=`git_branch_exists "$AR_COMPS/arduino" "$AR_PR_TARGET_BRANCH"`
+			if [ "$has_ar_branch" == "1" ]; then
+				export AR_BRANCH="$AR_PR_TARGET_BRANCH"
+			fi
+		fi
+	fi
+fi
+
+if [ "$AR_BRANCH" ]; then
+	echo "AR_BRANCH='$AR_BRANCH'"
+	git -C "$AR_COMPS/arduino" checkout "$AR_BRANCH" && \
+	git -C "$AR_COMPS/arduino" fetch && \
+	git -C "$AR_COMPS/arduino" pull --ff-only
+fi
+if [ $? -ne 0 ]; then exit 1; fi
+
+#
+# CLONE/UPDATE ESP32-ARDUINO-LIBS
+#
+if [ ! -d "$IDF_LIBS_DIR" ]; then
+	echo "Cloning esp32-arduino-libs..."
+	git clone "$AR_LIBS_REPO_URL" "$IDF_LIBS_DIR"
+else
+	echo "Updating esp32-arduino-libs..."
+	git -C "$IDF_LIBS_DIR" fetch && \
+	git -C "$IDF_LIBS_DIR" pull --ff-only
+fi
+if [ $? -ne 0 ]; then exit 1; fi
+
diff --git a/tools/install-esp-idf.sh b/tools/install-esp-idf.sh
index 2b7a0f5fb..90f27fd0a 100755
--- a/tools/install-esp-idf.sh
+++ b/tools/install-esp-idf.sh
@@ -11,14 +11,16 @@ fi
 # CLONE ESP-IDF
 #
 
-IDF_REPO_URL="https://github.com/espressif/esp-idf.git"
 if [ ! -d "$IDF_PATH" ]; then
 	echo "ESP-IDF is not installed! Installing local copy"
 	git clone $IDF_REPO_URL -b $IDF_BRANCH
 	idf_was_installed="1"
 fi
 
-if [ "$IDF_COMMIT" ]; then
+if [ "$IDF_TAG" ]; then
+    git -C "$IDF_PATH" checkout "tags/$IDF_TAG"
+    idf_was_installed="1"
+elif [ "$IDF_COMMIT" ]; then
     git -C "$IDF_PATH" checkout "$IDF_COMMIT"
     commit_predefined="1"
 fi
@@ -30,6 +32,13 @@ fi
 if [ ! -x $idf_was_installed ] || [ ! -x $commit_predefined ]; then
 	git -C $IDF_PATH submodule update --init --recursive
 	$IDF_PATH/install.sh
+	export IDF_COMMIT=$(git -C "$IDF_PATH" rev-parse --short HEAD)
+	export IDF_BRANCH=$(git -C "$IDF_PATH" symbolic-ref --short HEAD || git -C "$IDF_PATH" tag --points-at HEAD)
+
+	# Temporarily patch the ETH driver to support custom SPI
+	cd $IDF_PATH
+	patch -p1 -i ../patches/spi_eth.diff
+	cd -
 fi
 
 #
@@ -37,8 +46,6 @@ fi
 #
 
 source $IDF_PATH/export.sh
-export IDF_COMMIT=$(git -C "$IDF_PATH" rev-parse --short HEAD)
-export IDF_BRANCH=$(git -C "$IDF_PATH" symbolic-ref --short HEAD || git -C "$IDF_PATH" tag --points-at HEAD)
 
 #
 # SETUP ARDUINO DEPLOY
@@ -55,41 +62,25 @@ if [ "$GITHUB_EVENT_NAME" == "schedule" ] || [ "$GITHUB_EVENT_NAME" == "reposito
 		AR_NEW_COMMIT_MESSAGE="IDF $IDF_COMMIT"
 		AR_NEW_PR_TITLE="$AR_NEW_COMMIT_MESSAGE"
 	fi
+	LIBS_VERSION="idf-"${IDF_BRANCH//\//_}"-$IDF_COMMIT"
 
 	AR_HAS_COMMIT=`git_commit_exists "$AR_COMPS/arduino" "$AR_NEW_COMMIT_MESSAGE"`
 	AR_HAS_BRANCH=`git_branch_exists "$AR_COMPS/arduino" "$AR_NEW_BRANCH_NAME"`
-	AR_HAS_PR=`git_pr_exists "$AR_NEW_BRANCH_NAME"`
+	AR_HAS_PR=`github_pr_exists "$AR_REPO" "$AR_NEW_BRANCH_NAME"`
 
-	if [ "$AR_HAS_COMMIT" == "1" ]; then
-		echo "Commit '$AR_NEW_COMMIT_MESSAGE' Already Exists"
-		mkdir -p dist && echo "Commit '$AR_NEW_COMMIT_MESSAGE' Already Exists" > dist/log.txt
-		exit 0
-	fi
+	LIBS_HAS_COMMIT=`git_commit_exists "$IDF_LIBS_DIR" "$AR_NEW_COMMIT_MESSAGE"`
+	LIBS_HAS_BRANCH=`git_branch_exists "$IDF_LIBS_DIR" "$AR_NEW_BRANCH_NAME"`
 
-	if [ "$AR_HAS_BRANCH" == "1" ]; then
-		echo "Branch '$AR_NEW_BRANCH_NAME' Already Exists"
+	if [ "$LIBS_HAS_COMMIT" == "1" ]; then
+		echo "Commit '$AR_NEW_COMMIT_MESSAGE' Already Exists in esp32-arduino-libs"
 	fi
 
-	if [ "$AR_HAS_PR" == "1" ]; then
-		echo "PR '$AR_NEW_PR_TITLE' Already Exists"
+	if [ "$AR_HAS_COMMIT" == "1" ]; then
+		echo "Commit '$AR_NEW_COMMIT_MESSAGE' Already Exists in arduino-esp32"
 	fi
 
-	# setup git for pushing
-	git config --global github.user "$GITHUB_ACTOR"
-	git config --global user.name "$GITHUB_ACTOR"
-	git config --global user.email "$GITHUB_ACTOR@github.com"
-
-	# create or checkout the branch
-	if [ ! $AR_HAS_BRANCH == "0" ]; then
-		echo "Switching to arduino branch '$AR_NEW_BRANCH_NAME'..."
-		git -C "$AR_COMPS/arduino" checkout $AR_NEW_BRANCH_NAME
-	else
-		echo "Creating arduino branch '$AR_NEW_BRANCH_NAME'..."
-		git -C "$AR_COMPS/arduino" checkout -b $AR_NEW_BRANCH_NAME
-	fi
-	if [ $? -ne 0 ]; then
-	    echo "ERROR: Checkout of branch '$AR_NEW_BRANCH_NAME' failed"
-		exit 1
+	if [ "$LIBS_HAS_COMMIT" == "1" ] && [ "$AR_HAS_COMMIT" == "1" ]; then
+		exit 0
 	fi
 
 	export AR_NEW_BRANCH_NAME
@@ -99,4 +90,8 @@ if [ "$GITHUB_EVENT_NAME" == "schedule" ] || [ "$GITHUB_EVENT_NAME" == "reposito
 	export AR_HAS_COMMIT
 	export AR_HAS_BRANCH
 	export AR_HAS_PR
+
+	export LIBS_VERSION
+	export LIBS_HAS_COMMIT
+	export LIBS_HAS_BRANCH
 fi
diff --git a/tools/push-to-arduino.sh b/tools/push-to-arduino.sh
index f9a2b9955..f4d794d0c 100755
--- a/tools/push-to-arduino.sh
+++ b/tools/push-to-arduino.sh
@@ -11,14 +11,117 @@ if ! [ -d "$AR_COMPS/arduino" ]; then
 	exit 1
 fi
 
+# setup git for pushing
+git config --global github.user "$GITHUB_ACTOR"
+git config --global user.name "$GITHUB_ACTOR"
+git config --global user.email "$GITHUB_ACTOR@github.com"
+
 #
 # UPDATE FILES
 #
 
+#
+# esp32-arduino-libs
+#
+
+if [ $LIBS_HAS_COMMIT == "0" ] || [ $AR_HAS_COMMIT == "0" ]; then
+	cd "$AR_ROOT"
+	# create branch if necessary
+	if [ "$LIBS_HAS_BRANCH" == "1" ]; then
+		echo "Branch '$AR_NEW_BRANCH_NAME' Already Exists"
+		echo "Switching to esp32-arduino-libs branch '$AR_NEW_BRANCH_NAME'..."
+		git -C "$IDF_LIBS_DIR" checkout $AR_NEW_BRANCH_NAME
+	else
+		echo "Creating esp32-arduino-libs branch '$AR_NEW_BRANCH_NAME'..."
+		git -C "$IDF_LIBS_DIR" checkout -b $AR_NEW_BRANCH_NAME
+	fi
+	if [ $? -ne 0 ]; then
+		echo "ERROR: Checkout of branch '$AR_NEW_BRANCH_NAME' failed"
+		exit 1
+	fi
+
+	# make changes to the files
+	echo "Patching files in esp32-arduino-libs branch '$AR_NEW_BRANCH_NAME'..."
+	rm -rf $IDF_LIBS_DIR/* && cp -Rf $AR_TOOLS/esp32-arduino-libs/* $IDF_LIBS_DIR/
+	
+	cd $IDF_LIBS_DIR
+	if [ -f "README.md" ]; then
+		rm -rf "README.md"
+	fi
+
+	# did any of the files change?
+	if [ -n "$(git status --porcelain)" ]; then
+		echo "Pushing changes to esp32-arduino-libs branch '$AR_NEW_BRANCH_NAME'..."
+	    git add . && git commit --message "$AR_NEW_COMMIT_MESSAGE" && git push -u origin $AR_NEW_BRANCH_NAME
+		if [ $? -ne 0 ]; then
+		    echo "ERROR: Pushing to branch '$AR_NEW_BRANCH_NAME' failed"
+			exit 1
+		fi
+		IDF_LIBS_COMMIT=`git rev-parse --verify HEAD`
+		IDF_LIBS_DL_URL="https://codeload.github.com/espressif/esp32-arduino-libs/zip/$IDF_LIBS_COMMIT"
+		# ToDo: this URL needs to get into Arduino's package.json
+
+		# Download the file
+		filename="esp32-arduino-libs-$IDF_LIBS_COMMIT.zip"
+		curl -s -o "$filename" "$IDF_LIBS_DL_URL"
+
+		# Check if the download was successful
+		if [ $? -ne 0 ]; then
+		  echo "Error downloading file from $IDF_LIBS_DL_URL"
+		  exit 1
+		fi
+
+		# Calculate the size in bytes and SHA-256 sum
+		size=$(stat -c%s "$filename")
+		sha256sum=$(sha256sum "$filename" | awk '{print $1}')
+
+		# Clean up the downloaded file
+		rm "$filename"
+
+		# Print the results
+		echo "Tool: esp32-arduino-libs"
+		echo "Version: $LIBS_VERSION"
+		echo "URL: $IDF_LIBS_DL_URL"
+		echo "File: $filename"
+		echo "Size: $size bytes"
+		echo "SHA-256: $sha256sum"
+		echo "JSON: $AR_OUT/package_esp32_index.template.json"
+		cd "$AR_ROOT"
+		python3 tools/add_sdk_json.py -j "$AR_OUT/package_esp32_index.template.json" -n "esp32-arduino-libs" -v "$LIBS_VERSION" -u "$IDF_LIBS_DL_URL" -f "$filename" -s "$size" -c "$sha256sum"
+		if [ $? -ne 0 ]; then exit 1; fi
+
+	else
+	    echo "No changes in esp32-arduino-libs branch '$AR_NEW_BRANCH_NAME'"
+	    if [ $LIBS_HAS_BRANCH == "0" ]; then
+	    	echo "Delete created branch '$AR_NEW_BRANCH_NAME'"
+	    	git branch -d $AR_NEW_BRANCH_NAME
+	    fi
+	    exit 0
+	fi
+fi
+
+#
+# esp32-arduino
+#
+
 if [ $AR_HAS_COMMIT == "0" ]; then
+	cd "$AR_ROOT"
+	# create or checkout the branch
+	if [ ! $AR_HAS_BRANCH == "0" ]; then
+		echo "Switching to arduino branch '$AR_NEW_BRANCH_NAME'..."
+		git -C "$AR_COMPS/arduino" checkout $AR_NEW_BRANCH_NAME
+	else
+		echo "Creating arduino branch '$AR_NEW_BRANCH_NAME'..."
+		git -C "$AR_COMPS/arduino" checkout -b $AR_NEW_BRANCH_NAME
+	fi
+	if [ $? -ne 0 ]; then
+	    echo "ERROR: Checkout of branch '$AR_NEW_BRANCH_NAME' failed"
+		exit 1
+	fi
+
 	# make changes to the files
 	echo "Patching files in branch '$AR_NEW_BRANCH_NAME'..."
-	ESP32_ARDUINO="$AR_COMPS/arduino" ./tools/copy-to-arduino.sh
+	rm -rf "$AR_COMPS/arduino/package/package_esp32_index.template.json" && cp -f "$AR_OUT/package_esp32_index.template.json" "$AR_COMPS/arduino/package/package_esp32_index.template.json"
 	
 	cd $AR_COMPS/arduino
 
@@ -38,17 +141,18 @@ if [ $AR_HAS_COMMIT == "0" ]; then
 	    fi
 	    exit 0
 	fi
-fi
 
-#
-# CREATE PULL REQUEST
-#
-
-if [ "$AR_HAS_PR" == "0" ]; then
-	pr_created=`git_create_pr "$AR_NEW_BRANCH_NAME" "$AR_NEW_PR_TITLE" "$AR_PR_TARGET_BRANCH"`
-	if [ $pr_created == "0" ]; then
-		echo "ERROR: Failed to create PR '$AR_NEW_PR_TITLE': "`echo "$git_create_pr_res" | jq -r '.message'`": "`echo "$git_create_pr_res" | jq -r '.errors[].message'`
-		exit 1
+	# CREATE PULL REQUEST
+	if [ "$AR_HAS_PR" == "0" ]; then
+		echo "Creating PR '$AR_NEW_PR_TITLE'..."
+		pr_created=`git_create_pr "$AR_NEW_BRANCH_NAME" "$AR_NEW_PR_TITLE" "$AR_PR_TARGET_BRANCH"`
+		if [ $pr_created == "0" ]; then
+			echo "ERROR: Failed to create PR '$AR_NEW_PR_TITLE': "`echo "$git_create_pr_res" | jq -r '.message'`": "`echo "$git_create_pr_res" | jq -r '.errors[].message'`
+			exit 1
+		fi
+	else
+		echo "PR '$AR_NEW_PR_TITLE' Already Exists"
 	fi
 fi
+
 exit 0
diff --git a/tools/repository_dispatch.sh b/tools/repository_dispatch.sh
old mode 100644
new mode 100755
diff --git a/tools/update-components.sh b/tools/update-components.sh
index c00b2853a..13bdd183f 100755
--- a/tools/update-components.sh
+++ b/tools/update-components.sh
@@ -6,52 +6,9 @@ CAMERA_REPO_URL="https://github.com/espressif/esp32-camera.git"
 DL_REPO_URL="https://github.com/espressif/esp-dl.git"
 SR_REPO_URL="https://github.com/espressif/esp-sr.git"
 RMAKER_REPO_URL="https://github.com/espressif/esp-rainmaker.git"
-INSIGHTS_REPO_URL="https://github.com/espressif/esp-insights.git"
-DSP_REPO_URL="https://github.com/espressif/esp-dsp.git"
 LITTLEFS_REPO_URL="https://github.com/joltwallet/esp_littlefs.git"
 TINYUSB_REPO_URL="https://github.com/hathach/tinyusb.git"
-
-#
-# CLONE/UPDATE ARDUINO
-#
-echo "Updating ESP32 Arduino..."
-if [ ! -d "$AR_COMPS/arduino" ]; then
-	git clone $AR_REPO_URL "$AR_COMPS/arduino"
-fi
-
-if [ -z $AR_BRANCH ]; then
-	if [ -z $GITHUB_HEAD_REF ]; then
-		current_branch=`git branch --show-current`
-	else
-		current_branch="$GITHUB_HEAD_REF"
-	fi
-	echo "Current Branch: $current_branch"
-	if [[ "$current_branch" != "master" && `git_branch_exists "$AR_COMPS/arduino" "$current_branch"` == "1" ]]; then
-		export AR_BRANCH="$current_branch"
-	else
-		if [ -z "$IDF_COMMIT" ]; then #commit was not specified at build time
-			AR_BRANCH_NAME="idf-$IDF_BRANCH"
-		else
-			AR_BRANCH_NAME="idf-$IDF_COMMIT"
-		fi
-		has_ar_branch=`git_branch_exists "$AR_COMPS/arduino" "$AR_BRANCH_NAME"`
-		if [ "$has_ar_branch" == "1" ]; then
-			export AR_BRANCH="$AR_BRANCH_NAME"
-		else
-			has_ar_branch=`git_branch_exists "$AR_COMPS/arduino" "$AR_PR_TARGET_BRANCH"`
-			if [ "$has_ar_branch" == "1" ]; then
-				export AR_BRANCH="$AR_PR_TARGET_BRANCH"
-			fi
-		fi
-	fi
-fi
-
-if [ "$AR_BRANCH" ]; then
-	git -C "$AR_COMPS/arduino" checkout "$AR_BRANCH" && \
-	git -C "$AR_COMPS/arduino" fetch && \
-	git -C "$AR_COMPS/arduino" pull --ff-only
-fi
-if [ $? -ne 0 ]; then exit 1; fi
+TFLITE_REPO_URL="https://github.com/espressif/tflite-micro-esp-examples.git"
 
 #
 # CLONE/UPDATE ESP32-CAMERA
@@ -63,10 +20,6 @@ else
 	git -C "$AR_COMPS/esp32-camera" fetch && \
 	git -C "$AR_COMPS/esp32-camera" pull --ff-only
 fi
-#this is a temp measure to fix build issue
-# if [ -f "$AR_COMPS/esp32-camera/idf_component.yml" ]; then
-# 	rm -rf "$AR_COMPS/esp32-camera/idf_component.yml"
-# fi
 if [ $? -ne 0 ]; then exit 1; fi
 
 #
@@ -75,11 +28,22 @@ if [ $? -ne 0 ]; then exit 1; fi
 echo "Updating ESP-DL..."
 if [ ! -d "$AR_COMPS/esp-dl" ]; then
 	git clone $DL_REPO_URL "$AR_COMPS/esp-dl"
+	#this is a temp measure to fix build issue
+	mv "$AR_COMPS/esp-dl/CMakeLists.txt" "$AR_COMPS/esp-dl/CMakeListsOld.txt"
+	echo "idf_build_get_property(target IDF_TARGET)" > "$AR_COMPS/esp-dl/CMakeLists.txt"
+	echo "if(NOT \${IDF_TARGET} STREQUAL \"esp32c6\" AND NOT \${IDF_TARGET} STREQUAL \"esp32h2\")" >> "$AR_COMPS/esp-dl/CMakeLists.txt"
+	cat "$AR_COMPS/esp-dl/CMakeListsOld.txt" >> "$AR_COMPS/esp-dl/CMakeLists.txt"
+	echo "endif()" >> "$AR_COMPS/esp-dl/CMakeLists.txt"
+	rm -rf "$AR_COMPS/esp-dl/CMakeListsOld.txt"
 else
 	git -C "$AR_COMPS/esp-dl" fetch && \
 	git -C "$AR_COMPS/esp-dl" pull --ff-only
 fi
 if [ $? -ne 0 ]; then exit 1; fi
+#this is a temp measure to fix build issue
+if [ -f "$AR_COMPS/esp-dl/idf_component.yml" ]; then
+	rm -rf "$AR_COMPS/esp-dl/idf_component.yml"
+fi
 
 #
 # CLONE/UPDATE ESP-SR
@@ -91,24 +55,6 @@ else
 	git -C "$AR_COMPS/esp-sr" fetch && \
 	git -C "$AR_COMPS/esp-sr" pull --ff-only
 fi
-#this is a temp measure to fix build issue
-if [ -f "$AR_COMPS/esp-sr/idf_component.yml" ]; then
-	rm -rf "$AR_COMPS/esp-sr/idf_component.yml"
-fi
-if [ $? -ne 0 ]; then exit 1; fi
-
-#
-# CLONE/UPDATE ESP-LITTLEFS
-#
-echo "Updating ESP-LITTLEFS..."
-if [ ! -d "$AR_COMPS/esp_littlefs" ]; then
-	git clone $LITTLEFS_REPO_URL "$AR_COMPS/esp_littlefs" && \
-    git -C "$AR_COMPS/esp_littlefs" submodule update --init --recursive
-else
-	git -C "$AR_COMPS/esp_littlefs" fetch && \
-	git -C "$AR_COMPS/esp_littlefs" pull --ff-only && \
-    git -C "$AR_COMPS/esp_littlefs" submodule update --init --recursive
-fi
 if [ $? -ne 0 ]; then exit 1; fi
 
 #
@@ -117,37 +63,32 @@ if [ $? -ne 0 ]; then exit 1; fi
 echo "Updating ESP-RainMaker..."
 if [ ! -d "$AR_COMPS/esp-rainmaker" ]; then
     git clone $RMAKER_REPO_URL "$AR_COMPS/esp-rainmaker" && \
+	git -C "$AR_COMPS/esp-rainmaker" reset --hard d8e93454f495bd8a414829ec5e86842b373ff555 && \
     git -C "$AR_COMPS/esp-rainmaker" submodule update --init --recursive
-else
-	git -C "$AR_COMPS/esp-rainmaker" fetch && \
-	git -C "$AR_COMPS/esp-rainmaker" pull --ff-only && \
-    git -C "$AR_COMPS/esp-rainmaker" submodule update --init --recursive
+# else
+# 	git -C "$AR_COMPS/esp-rainmaker" fetch && \
+# 	git -C "$AR_COMPS/esp-rainmaker" pull --ff-only && \
+#     git -C "$AR_COMPS/esp-rainmaker" submodule update --init --recursive
 fi
 if [ $? -ne 0 ]; then exit 1; fi
 
-#
-# CLONE/UPDATE ESP-INSIGHTS
-#
-echo "Updating ESP-Insights..."
-if [ ! -d "$AR_COMPS/esp-insights" ]; then
-    git clone $INSIGHTS_REPO_URL "$AR_COMPS/esp-insights" && \
-    git -C "$AR_COMPS/esp-insights" submodule update --init --recursive
-else
-	git -C "$AR_COMPS/esp-insights" fetch && \
-	git -C "$AR_COMPS/esp-insights" pull --ff-only && \
-    git -C "$AR_COMPS/esp-insights" submodule update --init --recursive
+#this is a temp measure to fix build issue
+if [ -f "$AR_COMPS/esp-rainmaker/components/esp-insights/components/esp_insights/scripts/get_projbuild_gitconfig.py" ] && [ `cat "$AR_COMPS/esp-rainmaker/components/esp-insights/components/esp_insights/scripts/get_projbuild_gitconfig.py" | grep esp32c6 | wc -l` == "0" ]; then
+	echo "Overwriting 'get_projbuild_gitconfig.py'"
+	cp -f "tools/get_projbuild_gitconfig.py" "$AR_COMPS/esp-rainmaker/components/esp-insights/components/esp_insights/scripts/get_projbuild_gitconfig.py"
 fi
-if [ $? -ne 0 ]; then exit 1; fi
 
 #
-# CLONE/UPDATE ESP-DSP
+# CLONE/UPDATE ESP-LITTLEFS
 #
-echo "Updating ESP-DSP..."
-if [ ! -d "$AR_COMPS/espressif__esp-dsp" ]; then
-	git clone $DSP_REPO_URL "$AR_COMPS/espressif__esp-dsp"
+echo "Updating ESP-LITTLEFS..."
+if [ ! -d "$AR_COMPS/esp_littlefs" ]; then
+	git clone $LITTLEFS_REPO_URL "$AR_COMPS/esp_littlefs" && \
+    git -C "$AR_COMPS/esp_littlefs" submodule update --init --recursive
 else
-	git -C "$AR_COMPS/espressif__esp-dsp" fetch && \
-	git -C "$AR_COMPS/espressif__esp-dsp" pull --ff-only
+	git -C "$AR_COMPS/esp_littlefs" fetch && \
+	git -C "$AR_COMPS/esp_littlefs" pull --ff-only && \
+    git -C "$AR_COMPS/esp_littlefs" submodule update --init --recursive
 fi
 if [ $? -ne 0 ]; then exit 1; fi
 
@@ -163,3 +104,16 @@ else
 fi
 if [ $? -ne 0 ]; then exit 1; fi
 
+#
+# CLONE/UPDATE TFLITE MICRO
+#
+echo "Updating TFLite Micro..."
+if [ ! -d "$AR_COMPS/tflite-micro" ]; then
+	git clone $TFLITE_REPO_URL "$AR_COMPS/tflite-micro"
+	git -C "$AR_COMPS/tflite-micro" submodule update --init --recursive
+else
+	git -C "$AR_COMPS/tflite-micro" fetch && \
+	git -C "$AR_COMPS/tflite-micro" pull --ff-only
+	git -C "$AR_COMPS/tflite-micro" submodule update --init --recursive
+fi
+if [ $? -ne 0 ]; then exit 1; fi