diff --git a/.circleci/config.yml b/.circleci/config.yml
index d5d401c51..7b36bed7c 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,31 +1,92 @@
-# Use the latest 2.1 version of CircleCI pipeline process engine.
-# See: https://circleci.com/docs/configuration-reference
 version: 2.1
 
-# Define a job to be invoked later in a workflow.
-# See: https://circleci.com/docs/jobs-steps/#jobs-overview & https://circleci.com/docs/configuration-reference/#jobs
-jobs:
-  say-hello:
-    # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub.
-    # See: https://circleci.com/docs/executor-intro/ & https://circleci.com/docs/configuration-reference/#executor-job
-    docker:
-      # Specify the version you desire here
-      # See: https://circleci.com/developer/images/image/cimg/base
-      - image: cimg/base:current
-
-    # Add steps to the job
-    # See: https://circleci.com/docs/jobs-steps/#steps-overview & https://circleci.com/docs/configuration-reference/#steps
+commands:
+  setup-toolchain:
+    parameters:
+        toolchain:
+            type: string
+        toolchain_url:
+            type: string
     steps:
-      # Checkout the code as the first step.
-      - checkout
+#      - run:
+#          name: Make toolchain cache key
+#          command: echo "<< parameters.toolchain >>-<< parameters.toolchain_url>>" > toolchain_key
+#      - restore_cache:
+#          name: Restore Toolchain Cache
+#          key: deps-{{ checksum "toolchain_key" }}
+#          paths:
+#            - ~/cache/<< parameters.toolchain >>
       - run:
-          name: "Say hello"
-          command: "echo Hello, World!"
+          name: Install Toolchain
+          command: |
+            # Only download if folder does not exist (not cached)
+            if [ ! -d ~/cache/<< parameters.toolchain >> ]; then
+              mkdir -p ~/cache/<< parameters.toolchain >>
+              wget << parameters.toolchain_url>> -O toolchain.tar.gz
+              tar -C ~/cache/<< parameters.toolchain >> -xaf toolchain.tar.gz
+            fi
+#      - save_cache:
+#          name: Save Toolchain Cache
+#          key: deps-{{ checksum "toolchain_key" }}
+#          paths:
+#            - ~/cache/<< parameters.toolchain >>
+      - run:
+          name: Setup build environment
+          command: |
+            echo "export PATH=$PATH:`echo ~/cache/<< parameters.toolchain >>/*/bin`" >> $BASH_ENV
+            # Install Ninja
+            NINJA_URL=https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip
+            wget $NINJA_URL -O ninja-linux.zip
+            unzip ninja-linux.zip -d ~/bin
+
+  get-deps:
+    parameters:
+      family:
+        type: string
+    steps:
+      - run:
+          name: Get Dependencies
+          command: |
+            python tools/get_deps.py << parameters.family >>
+
+jobs:
+  arm-clang:
+    parameters:
+      family:
+        type: string
+      build-system:
+        type: string
+
+    docker:
+      - image: cimg/base:current
+    resource_class: medium
+    environment:
+      TOOLCHAIN_URL: https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/releases/download/release-17.0.1/LLVMEmbeddedToolchainForArm-17.0.1-Linux-x86_64.tar.xz
+    steps:
+      - checkout
+      - setup-toolchain:
+          toolchain: clang
+          toolchain_url: $TOOLCHAIN_URL
+      - get-deps:
+          family: << parameters.family >>
+      - run:
+          name: Build
+          command: |
+            # Only build one board per family for non PRs i.e commit to master
+            ONE_PER_FAMILY=""
+            if [ -z "$CIRCLE_PULL_REQUEST" ]; then
+              ONE_PER_FAMILY="--one-per-family"
+            fi
+            python tools/build.py $ONE_PER_FAMILY -s << parameters.build-system >> --toolchain clang << parameters.family >>
 
-# Orchestrate jobs using workflows
-# See: https://circleci.com/docs/workflows/ & https://circleci.com/docs/configuration-reference/#workflows
 workflows:
-  say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow.
-    # Inside the workflow, you define the jobs you want to run.
+  build:
     jobs:
-      - say-hello
+      - arm-clang:
+          matrix:
+            parameters:
+              build-system:
+                - cmake
+              #family: ['stm32f1']
+              #family: ['stm32f1', 'stm32f2']
+              family: ['imxrt', 'kinetis_k kinetis_kl kinetis_k32l2', 'lpc11 lpc13 lpc15', 'lpc17 lpc18 lpc40 lpc43', 'lpc51 lpc54 lpc55', 'nrf', 'samd11 samd21 saml2x', 'samd5x_e5x samg', 'stm32f0 stm32f1 stm32f2 stm32f3', 'stm32f4', 'stm32f7', 'stm32g0 stm32g4 stm32h5', 'stm32h7', 'stm32l4 stm32u5 stm32wb']
diff --git a/.github/actions/get_deps/action.yml b/.github/actions/get_deps/action.yml
index 38b44a70e..eea241c6c 100644
--- a/.github/actions/get_deps/action.yml
+++ b/.github/actions/get_deps/action.yml
@@ -2,8 +2,8 @@ name: Get dependencies
 
 inputs:
   arg:
+    description: 'Arguments to get_deps.py'
     required: true
-    type: string
 
 runs:
   using: "composite"
diff --git a/.github/actions/setup_toolchain/action.yml b/.github/actions/setup_toolchain/action.yml
index e6c79e7dd..19fe28b0c 100644
--- a/.github/actions/setup_toolchain/action.yml
+++ b/.github/actions/setup_toolchain/action.yml
@@ -2,11 +2,11 @@ name: Setup Toolchain
 
 inputs:
   toolchain:
+    description: 'Toolchain name'
     required: true
-    type: string
   toolchain_url:
+    description: 'Toolchain URL or version'
     required: false
-    type: string
 
 outputs:
   build_option:
@@ -24,15 +24,19 @@ runs:
 
     - name: Pull ESP-IDF docker
       if: inputs.toolchain == 'esp-idf'
-      run: docker pull espressif/idf:${{ inputs.toolchain_url }}
-      shell: bash
+      uses: ./.github/actions/setup_toolchain/espressif
+      with:
+        toolchain: ${{ inputs.toolchain }}
+        toolchain_url: ${{ inputs.toolchain_url }}
 
     - name: Download Toolchain
       if: >-
         inputs.toolchain != 'arm-gcc' &&
+        inputs.toolchain != 'arm-iar' &&
         inputs.toolchain != 'esp-idf'
       uses: ./.github/actions/setup_toolchain/download
       with:
+        toolchain: ${{ inputs.toolchain }}
         toolchain_url: ${{ inputs.toolchain_url }}
 
     - name: Set toolchain option
@@ -41,6 +45,8 @@ runs:
           BUILD_OPTION=""
           if [[ "${{ inputs.toolchain }}" == *"clang"* ]]; then
             BUILD_OPTION="--toolchain clang"
+          elif [[ "${{ inputs.toolchain }}" == "arm-iar" ]]; then
+            BUILD_OPTION="--toolchain iar"
           fi
           echo "build_option=$BUILD_OPTION"
           echo "build_option=$BUILD_OPTION" >> $GITHUB_OUTPUT
diff --git a/.github/actions/setup_toolchain/download/action.yml b/.github/actions/setup_toolchain/download/action.yml
index db85e9027..2af456ef8 100644
--- a/.github/actions/setup_toolchain/download/action.yml
+++ b/.github/actions/setup_toolchain/download/action.yml
@@ -1,29 +1,33 @@
 name: Download Toolchain
 
 inputs:
-  toolchain_url:
+  toolchain:
+    description: 'Toolchain name'
+    required: true
+  toolchain_url:
+    description: 'Toolchain URL'
     required: true
-    type: string
 
 runs:
   using: "composite"
   steps:
     - name: Cache Toolchain
+      if: ${{ !startsWith(inputs.toolchain_url, 'https://github.com') }}
       uses: actions/cache@v4
-      id: cache-toolchain
+      id: cache-toolchain-download
       with:
-        path: ~/cache/toolchain
-        key: ${{ runner.os }}-${{ inputs.toolchain_url }}
+        path: ~/cache/${{ inputs.toolchain }}
+        key: ${{ runner.os }}-${{ inputs.toolchain }}-${{ inputs.toolchain_url }}
 
     - name: Install Toolchain
-      if: steps.cache-toolchain.outputs.cache-hit != 'true'
+      if: steps.cache-toolchain-download.outputs.cache-hit != 'true'
       run: |
-        mkdir -p ~/cache/toolchain
-        wget --progress=dot:mega ${{ inputs.toolchain_url }} -O toolchain.tar.gz
-        tar -C ~/cache/toolchain -xaf toolchain.tar.gz
+        mkdir -p ~/cache/${{ inputs.toolchain }}
+        wget --progress=dot:giga ${{ inputs.toolchain_url }} -O toolchain.tar.gz
+        tar -C ~/cache/${{ inputs.toolchain }} -xaf toolchain.tar.gz
       shell: bash
 
     - name: Set Toolchain Path
       run: |
-        echo >> $GITHUB_PATH `echo ~/cache/toolchain/*/bin`
+        echo >> $GITHUB_PATH `echo ~/cache/${{ inputs.toolchain }}/*/bin`
       shell: bash
diff --git a/.github/actions/setup_toolchain/espressif/action.yml b/.github/actions/setup_toolchain/espressif/action.yml
new file mode 100644
index 000000000..46da02911
--- /dev/null
+++ b/.github/actions/setup_toolchain/espressif/action.yml
@@ -0,0 +1,41 @@
+name: Setup ESP-IDF Toolchain
+
+inputs:
+  toolchain:
+    description: 'Toolchain name'
+    required: true
+  toolchain_url:
+    description: 'Toolchain URL or version'
+    required: true
+
+runs:
+  using: "composite"
+  steps:
+    - name: Set DOCKER_ESP_IDF
+      run: |
+        DOCKER_ESP_IDF=$HOME/cache/${{ inputs.toolchain }}/docker_image.tar
+        echo "DOCKER_ESP_IDF=$DOCKER_ESP_IDF" >> $GITHUB_ENV
+      shell: bash
+
+    - name: Cache Docker Image
+      uses: actions/cache@v4
+      id: cache-toolchain-espressif
+      with:
+        path: ${{ env.DOCKER_ESP_IDF }}
+        key: ${{ inputs.toolchain }}-${{ inputs.toolchain_url }}
+
+    - name: Pull and Save Docker Image
+      if: steps.cache-toolchain-espressif.outputs.cache-hit != 'true'
+      run: |
+        docker pull espressif/idf:${{ inputs.toolchain_url }}
+        mkdir -p $(dirname $DOCKER_ESP_IDF)
+        docker save -o $DOCKER_ESP_IDF espressif/idf:${{ inputs.toolchain_url }}
+        du -sh $DOCKER_ESP_IDF
+      shell: bash
+
+    - name: Load Docker Image
+      if: steps.cache-toolchain-espressif.outputs.cache-hit == 'true'
+      run: |
+        du -sh $DOCKER_ESP_IDF
+        docker load --input $DOCKER_ESP_IDF
+      shell: bash
diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build.yml
similarity index 70%
rename from .github/workflows/build_cmake.yml
rename to .github/workflows/build.yml
index 4723cf8d9..78d95fbbc 100644
--- a/.github/workflows/build_cmake.yml
+++ b/.github/workflows/build.yml
@@ -11,7 +11,7 @@ on:
       - 'tools/get_deps.py'
       - 'tools/build.py'
       - '.github/actions/**'
-      - '.github/workflows/build_cmake.yml'
+      - '.github/workflows/build.yml'
       - '.github/workflows/build_util.yml'
       - '.github/workflows/ci_set_matrix.py'
   pull_request:
@@ -24,7 +24,7 @@ on:
       - 'tools/get_deps.py'
       - 'tools/build.py'
       - '.github/actions/**'
-      - '.github/workflows/build_cmake.yml'
+      - '.github/workflows/build.yml'
       - '.github/workflows/build_util.yml'
       - '.github/workflows/ci_set_matrix.py'
 concurrency:
@@ -37,11 +37,6 @@ jobs:
     outputs:
       json: ${{ steps.set-matrix-json.outputs.matrix }}
     steps:
-      - name: Setup Python
-        uses: actions/setup-python@v5
-        with:
-          python-version: '3.x'
-
       - name: Checkout TinyUSB
         uses: actions/checkout@v4
 
@@ -63,7 +58,7 @@ jobs:
       matrix:
         toolchain:
           - 'aarch64-gcc'
-          - 'arm-clang'
+          # - 'arm-clang' # clang is built by circle-ci
           - 'arm-gcc'
           - 'msp430-gcc'
           - 'riscv-gcc'
@@ -72,11 +67,13 @@ jobs:
       toolchain: ${{ matrix.toolchain }}
       toolchain_url: ${{ fromJSON(needs.set-matrix.outputs.json)[matrix.toolchain].toolchain_url }}
       build-args: ${{ toJSON(fromJSON(needs.set-matrix.outputs.json)[matrix.toolchain].family) }}
+      one-per-family: ${{ github.event_name != 'pull_request' }}
 
   # ---------------------------------------
   # Build Make
   # ---------------------------------------
   make:
+    #if: github.event_name == 'pull_request'
     needs: set-matrix
     uses: ./.github/workflows/build_util.yml
     strategy:
@@ -84,7 +81,7 @@ jobs:
       matrix:
         toolchain:
           - 'aarch64-gcc'
-          #- 'arm-clang'
+          # - 'arm-clang' # clang is built by circle-ci
           - 'arm-gcc'
           - 'msp430-gcc'
           - 'riscv-gcc'
@@ -93,6 +90,7 @@ jobs:
       toolchain: ${{ matrix.toolchain }}
       toolchain_url: ${{ fromJSON(needs.set-matrix.outputs.json)[matrix.toolchain].toolchain_url }}
       build-args: ${{ toJSON(fromJSON(needs.set-matrix.outputs.json)[matrix.toolchain].family) }}
+      one-per-family: ${{ github.event_name != 'pull_request' }}
 
   # ---------------------------------------
   # Build Make on Windows/MacOS
@@ -107,7 +105,8 @@ jobs:
       os: ${{ matrix.os }}
       build-system: 'make'
       toolchain: 'arm-gcc'
-      build-args: '["-bstm32f411disco"]'
+      build-args: '["stm32h7"]'
+      one-per-family: true
 
   # ---------------------------------------
   # Build Espressif
@@ -127,3 +126,33 @@ jobs:
       toolchain: 'esp-idf'
       toolchain_url: 'v5.1.1'
       build-args: '["-b${{ matrix.board }}"]'
+
+  # ---------------------------------------
+  # Build IAR on HFP self-hosted
+  # ---------------------------------------
+  arm-iar:
+    if: github.repository_owner == 'hathach'
+    needs: set-matrix
+    runs-on: [self-hosted, Linux, X64, hifiphile]
+    env:
+      BUILD_ARGS: ${{ join(fromJSON(needs.set-matrix.outputs.json)['arm-iar'].family, ' ') }}
+    steps:
+      - name: Clean workspace
+        run: |
+          echo "Cleaning up previous run"
+          rm -rf "${{ github.workspace }}"
+          mkdir -p "${{ github.workspace }}"
+
+      - name: Checkout TinyUSB
+        uses: actions/checkout@v4
+
+      - name: Get Dependencies
+        run: python3 tools/get_deps.py $BUILD_ARGS
+
+      - name: Build
+        run: python3 tools/build.py --one-per-family --toolchain iar $BUILD_ARGS
+
+      - name: Test on actual hardware (hardware in the loop)
+        if: github.event_name == 'pull_request'
+        run: |
+          python3 test/hil/hil_test.py hfp.json
diff --git a/.github/workflows/build_iar.yml b/.github/workflows/build_iar.yml
deleted file mode 100644
index 34dbda192..000000000
--- a/.github/workflows/build_iar.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-name: Build IAR
-
-on:
-  workflow_dispatch:
-  push:
-    paths:
-      - 'src/**'
-      - 'examples/**'
-      - 'lib/**'
-      - 'hw/**'
-      - 'tools/get_deps.py'
-      - 'test/hil/**'
-      - '.github/workflows/build_iar.yml'
-  pull_request:
-    branches: [ master ]
-    paths:
-      - 'src/**'
-      - 'examples/**'
-      - 'lib/**'
-      - 'hw/**'
-      - 'tools/get_deps.py'
-      - 'test/hil/**'
-      - '.github/workflows/build_iar.yml'
-
-concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}
-  cancel-in-progress: true
-
-jobs:
-  cmake:
-    if: github.repository_owner == 'hathach'
-    runs-on: [self-hosted, Linux, X64, hifiphile]
-    strategy:
-      fail-fast: false
-      matrix:
-        family:
-          # Alphabetical order
-          # Note: bundle multiple families into a matrix since there is only one self-hosted instance can
-          # run IAR build. Too many matrix can hurt due to setup/teardown overhead.
-          - 'lpc43 stm32f0 stm32f1 stm32f7 stm32g0 stm32g4 stm32l4'
-    steps:
-      - name: Clean workspace
-        run: |
-          echo "Cleaning up previous run"
-          rm -rf "${{ github.workspace }}"
-          mkdir -p "${{ github.workspace }}"
-
-      - name: Checkout TinyUSB
-        uses: actions/checkout@v4
-
-      - name: Get Dependencies
-        run: python3 tools/get_deps.py ${{ matrix.family }}
-
-      - name: Build
-        run: python3 tools/build.py --toolchain iar ${{ matrix.family }}
-
-      - name: Test on actual hardware (hardware in the loop)
-        run: |
-          python3 test/hil/hil_test.py hfp.json
diff --git a/.github/workflows/build_renesas.yml b/.github/workflows/build_renesas.yml
index 1be49344f..3d2cbab28 100644
--- a/.github/workflows/build_renesas.yml
+++ b/.github/workflows/build_renesas.yml
@@ -34,11 +34,6 @@ jobs:
         # Alphabetical order
         - 'rx'
     steps:
-    - name: Setup Python
-      uses: actions/setup-python@v5
-      with:
-        python-version: '3.x'
-
     - name: Checkout TinyUSB
       uses: actions/checkout@v4
 
diff --git a/.github/workflows/build_util.yml b/.github/workflows/build_util.yml
index f8ad1900c..49a9feabd 100644
--- a/.github/workflows/build_util.yml
+++ b/.github/workflows/build_util.yml
@@ -15,6 +15,10 @@ on:
       build-args:
         required: true
         type: string
+      one-per-family:
+        required: false
+        default: false
+        type: boolean
       os:
         required: false
         type: string
@@ -31,11 +35,6 @@ jobs:
       - name: Checkout TinyUSB
         uses: actions/checkout@v4
 
-      - name: Setup Python
-        uses: actions/setup-python@v5
-        with:
-          python-version: '3.x'
-
       - name: Setup Toolchain
         id: setup-toolchain
         uses: ./.github/actions/setup_toolchain
@@ -48,10 +47,20 @@ jobs:
         with:
           arg: ${{ matrix.arg }}
 
+      - name: Set build one-per-family option
+        id: set-one-per-family
+        run: |
+          if [[ "${{ inputs.one-per-family }}" == "true" ]]; then
+            BUILD_OPTION="--one-per-family"
+          fi
+          echo "build_option=$BUILD_OPTION"
+          echo "build_option=$BUILD_OPTION" >> $GITHUB_OUTPUT
+        shell: bash
+
       - name: Build
         if: inputs.toolchain != 'esp-idf'
         run: |
-          python tools/build.py -s ${{ inputs.build-system }} ${{ steps.setup-toolchain.outputs.build_option }} ${{ matrix.arg }}
+          python tools/build.py -s ${{ inputs.build-system }} ${{ steps.setup-toolchain.outputs.build_option }} ${{ steps.set-one-per-family.outputs.build_option }} ${{ matrix.arg }}
 
       - name: Build using ESP-IDF docker
         if: inputs.toolchain == 'esp-idf'
diff --git a/.github/workflows/ci_set_matrix.py b/.github/workflows/ci_set_matrix.py
index ea758d917..c6f4e8fe2 100644
--- a/.github/workflows/ci_set_matrix.py
+++ b/.github/workflows/ci_set_matrix.py
@@ -4,16 +4,17 @@ import json
 toolchain_list = {
     "aarch64-gcc": "https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz",
     "arm-clang": "https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/releases/download/release-17.0.1/LLVMEmbeddedToolchainForArm-17.0.1-Linux-x86_64.tar.xz",
+    "arm-iar": "",
     "arm-gcc": "",
     "msp430-gcc": "http://software-dl.ti.com/msp430/msp430_public_sw/mcu/msp430/MSPGCC/9_2_0_0/export/msp430-gcc-9.2.0.50_linux64.tar.bz2",
-    "riscv-gcc": "https://github.com/xpack-dev-tools/riscv-none-embed-gcc-xpack/releases/download/v10.1.0-1.1/xpack-riscv-none-embed-gcc-10.1.0-1.1-linux-x64.tar.gz",
+    "riscv-gcc": "https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v13.2.0-2/xpack-riscv-none-elf-gcc-13.2.0-2-linux-x64.tar.gz"
 }
 
 # family: [supported toolchain]
 family_list = {
     "broadcom_32bit": ["arm-gcc"],
     "broadcom_64bit": ["aarch64-gcc"],
-    "ch32v307 fomu gd32vf103": ["riscv-gcc"],
+    "ch32v20x ch32v307 fomu gd32vf103": ["riscv-gcc"],
     "imxrt": ["arm-gcc", "arm-clang"],
     "kinetis_k kinetis_kl kinetis_k32l2": ["arm-gcc", "arm-clang"],
     "lpc11 lpc13 lpc15": ["arm-gcc", "arm-clang"],
@@ -28,12 +29,12 @@ family_list = {
     "rp2040": ["arm-gcc"],
     "samd11 samd21 saml2x": ["arm-gcc", "arm-clang"],
     "samd5x_e5x samg": ["arm-gcc", "arm-clang"],
-    "stm32f0 stm32f1 stm32f2 stm32f3": ["arm-gcc", "arm-clang"],
-    "stm32f4": ["arm-gcc", "arm-clang"],
-    "stm32f7": ["arm-gcc", "arm-clang"],
-    "stm32g0 stm32g4 stm32h5": ["arm-gcc", "arm-clang"],
-    "stm32h7": ["arm-gcc", "arm-clang"],
-    "stm32l4 stm32u5 stm32wb": ["arm-gcc", "arm-clang"],
+    "stm32f0 stm32f1 stm32f2 stm32f3": ["arm-gcc", "arm-clang", "arm-iar"],
+    "stm32f4": ["arm-gcc", "arm-clang", "arm-iar"],
+    "stm32f7": ["arm-gcc", "arm-clang", "arm-iar"],
+    "stm32g0 stm32g4 stm32h5": ["arm-gcc", "arm-clang", "arm-iar"],
+    "stm32h7": ["arm-gcc", "arm-clang", "arm-iar"],
+    "stm32l4 stm32u5 stm32wb": ["arm-gcc", "arm-clang", "arm-iar"],
     "xmc4000": ["arm-gcc"],
 }
 
@@ -43,7 +44,16 @@ def set_matrix_json():
     for toolchain in toolchain_list.keys():
         filtered_families = [family for family, supported_toolchain in family_list.items() if
                              toolchain in supported_toolchain]
+
+        # always add board in hfp.json for arm-iar
+        if toolchain == 'arm-iar':
+            with open('test/hil/hfp.json') as f:
+                hfp_data = json.load(f)
+            hfp_boards = [f"-b{board['name']}" for board in hfp_data['boards']]
+            filtered_families = filtered_families + hfp_boards
+
         matrix[toolchain] = {"family": filtered_families, "toolchain_url": toolchain_list[toolchain]}
+
     print(json.dumps(matrix))
 
 
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 1f7b60b9c..be4c2dd87 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -59,10 +59,10 @@ jobs:
     - name: Checkout repository
       uses: actions/checkout@v4
 
-    - name: Install ARM GCC
-      uses: carlosperate/arm-none-eabi-gcc-action@v1
+    - name: Setup Toolchain
+      uses: ./.github/actions/setup_toolchain
       with:
-        release: '11.2-2022.02'
+        toolchain: 'arm-gcc'
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
diff --git a/.github/workflows/hil_test.yml b/.github/workflows/hil_test.yml
index 796ff32dc..39a9060a2 100644
--- a/.github/workflows/hil_test.yml
+++ b/.github/workflows/hil_test.yml
@@ -2,16 +2,6 @@ name: Hardware Test
 
 on:
   workflow_dispatch:
-  push:
-    paths:
-      - 'src/**'
-      - 'examples/**'
-      - 'lib/**'
-      - 'hw/**'
-      - 'test/hil/**'
-      - 'tools/get_deps.py'
-      - '.github/actions/**'
-      - '.github/workflows/hil_test.yml'
   pull_request:
     branches: [ master ]
     paths:
@@ -40,11 +30,6 @@ jobs:
       - name: Checkout TinyUSB
         uses: actions/checkout@v4
 
-      - name: Setup Python
-        uses: actions/setup-python@v5
-        with:
-          python-version: '3.x'
-
       - name: Parse HIL json
         id: parse_hil_json
         run: |
@@ -56,7 +41,7 @@ jobs:
           echo "BOARDS_LIST=$BOARDS_LIST" >> $GITHUB_ENV
           echo "BOARDS_LIST=$BOARDS_LIST" >> $GITHUB_OUTPUT
 
-      - name: Setup ARM Toolchain
+      - name: Setup Toolchain
         uses: ./.github/actions/setup_toolchain
         with:
           toolchain: 'arm-gcc'
@@ -88,11 +73,6 @@ jobs:
       - name: Checkout TinyUSB
         uses: actions/checkout@v4
 
-      - name: Setup Python
-        uses: actions/setup-python@v5
-        with:
-          python-version: '3.x'
-
       - name: Parse HIL json
         id: parse_hil_json
         run: |
@@ -141,7 +121,7 @@ jobs:
       - build-esp
     runs-on: [self-hosted, rp2040, nrf52840, esp32s3, hardware-in-the-loop]
     env:
-        BOARDS_LIST: "${{ needs.build.outputs.BOARDS_LIST }} ${{ needs.build-esp.outputs.BOARDS_LIST }}"
+      BOARDS_LIST: "${{ needs.build.outputs.BOARDS_LIST }} ${{ needs.build-esp.outputs.BOARDS_LIST }}"
     steps:
       - name: Clean workspace
         run: |
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index d1ffe6ca1..379a22ee2 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -14,11 +14,6 @@ jobs:
   pre-commit:
     runs-on: ubuntu-latest
     steps:
-    - name: Setup Python
-      uses: actions/setup-python@v5
-      with:
-        python-version: '3.x'
-
     - name: Setup Ruby
       uses: ruby/setup-ruby@v1
       with:
diff --git a/.gitignore b/.gitignore
index 56ae7600f..7a37d65dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,6 +82,7 @@ hw/mcu/st/stm32l5xx_hal_driver
 hw/mcu/st/stm32u5xx_hal_driver
 hw/mcu/st/stm32wbxx_hal_driver
 hw/mcu/ti
+hw/mcu/wch/ch32v20x
 hw/mcu/wch/ch32v307
 hw/mcu/wch/ch32f20x
 lib/CMSIS_5
diff --git a/.idea/cmake.xml b/.idea/cmake.xml
index bb13a0466..24d4eafe9 100644
--- a/.idea/cmake.xml
+++ b/.idea/cmake.xml
@@ -66,6 +66,7 @@
       
       
       
+      
       
       
       
@@ -103,6 +104,7 @@
       
       
       
+      
       
       
       
@@ -120,16 +122,19 @@
       
       
       
-      
       
       
       
       
       
       
-      
       
       
+      
+      
+      
+      
+      
     
   
 
\ No newline at end of file
diff --git a/README.rst b/README.rst
index 8c3145d33..b5f9fc149 100644
--- a/README.rst
+++ b/README.rst
@@ -122,42 +122,45 @@ Following CPUs are supported, check out `Supported Devices`_ for comprehensive l
 | GigaDevice   | GD32VF103                                                  |
 +--------------+------------------------------------------------------------+
 | Infineon     | XMC4500                                                    |
-+--------------+-----+------------------------------------------------------+
-| MicroChip    | SAM | D11, D21, D51, E5x, G55, L2x, E7x, S7x, V7x          |
-|              +-----+------------------------------------------------------+
-|              | PIC | 24, 32mm, 32mk, 32mx, 32mz, dsPIC33                  |
-+--------------+-----+------------------------------------------------------+
++--------------+------------------------------------------------------------+
+|              | SAM:  D11, D21, D51, E5x, G55, L2x, E7x, S7x, V7x          |
+|  MicroChip   |                                                            |
+|              | PIC:  24, 32mm, 32mk, 32mx, 32mz, dsPIC33                  |
++--------------+------------------------------------------------------------+
 | Mind Montion | mm32                                                       |
 +--------------+------------------------------------------------------------+
 | NordicSemi   | nRF52833, nRF52840, nRF5340                                |
 +--------------+------------------------------------------------------------+
 | Nuvoton      | NUC 120, 121, 125, 126, 505                                |
-+--------------+---------+--------------------------------------------------+
-| NXP          | iMXRT   | RT10xx, RT11xx                                   |
-|              +---------+--------------------------------------------------+
-|              | Kinetis | KL, K32L2                                        |
-|              +---------+--------------------------------------------------+
-|              | LPC     | 11u, 13, 15, 17, 18, 40, 43, 51u, 54, 55         |
-|              +---------+--------------------------------------------------+
-|              | MCX     | A15, N9                                          |
-+--------------+---------+--------------------------------------------------+
++--------------+------------------------------------------------------------+
+| NXP          | iMXRT: RT10xx, RT11xx                                      |
+|              |                                                            |
+|              | Kinetis: KL, K32L2                                         |
+|              |                                                            |
+|              | LPC: 11u, 13, 15, 17, 18, 40, 43, 51u, 54, 55              |
+|              |                                                            |
+|              | MCX: A15, N9                                               |
++--------------+------------------------------------------------------------+
 | Raspberry Pi | RP2040                                                     |
 +--------------+-----+------------------------------------------------------+
-| Renesas      | RX  | 63N, 65N, 72N                                        |
-+--------------+-----+------------------------------------------------------+
-|              | RA  | 4M1, 4M3, 6M1, 6M5                                   |
+| Renesas      | RA: 4M1, 4M3, 6M1, 6M5                                     |
+|              |                                                            |
+|              | RX: 63N, 65N, 72N                                          |
 +--------------+-----+------------------------------------------------------+
 | Silabs       | EFM32GG12                                                  |
 +--------------+------------------------------------------------------------+
 | Sony         | CXD56                                                      |
 +--------------+------------------------------------------------------------+
-| ST STM32     | F0, F1, F2, F3, F4, F7, H7, G0, G4, L0, L1, L4, L4+ U5, WB |
+| ST STM32     | F0, F1, F2, F3, F4, F7, H5, H7, G0, G4, L0, L1, L4, L4+,   |
+|              | U5, WB                                                     |
 +--------------+------------------------------------------------------------+
 | TI           | MSP430, MSP432E4, TM4C123                                  |
 +--------------+------------------------------------------------------------+
 | ValentyUSB   | eptri                                                      |
 +--------------+------------------------------------------------------------+
-| WCH          | CH32F20x, CH32V307,                                        |
+| WCH          | CH32F: F20x                                                |
+|              |                                                            |
+|              | CH32V: V20x, V307                                          |
 +--------------+------------------------------------------------------------+
 
 License
diff --git a/docs/info/changelog.rst b/docs/info/changelog.rst
index b359ebb44..f7ccb39b9 100644
--- a/docs/info/changelog.rst
+++ b/docs/info/changelog.rst
@@ -2,6 +2,72 @@
 Changelog
 *********
 
+0.17.0 (WIP)
+============
+
+General
+-------
+
+- Improved continuous integration: build both cmake and make. Make use of circleci to build arm-clang
+
+
+Controller Driver (DCD & HCD)
+-----------------------------
+
+- WCH CH32
+
+  - Added support for USB OTG/FS and FSDev Driver. Update CH32V307 to allow manual select FS or HS driver.
+  - Fixed various bugs in CH32v307 usbhs driver: endpoint handling and data transfer management.
+
+- Fixed race conditions and other bugs in dcd_nrf5x and other drivers.
+- Implemented hcd abort transfer for Max3421 and rp2040
+- Added DWC2 Test Mode support.
+- stm32 fsdev: ISO EP buffer allocation improvements, implement dcd_edpt_close_all()
+- Added support for STM32G4 and STM32U5 microcontrollers.
+
+Device Stack
+------------
+
+- Added tud_deinit() to deinitialize TinyUSB device stack.
+- Added support for generic SOF callback.
+
+- Audio
+
+  - Add audio_test_freertos & audio_4_channel_mic_freertos
+  - Improved support for Audio Class 2.0 (UAC2) with various bug fixes.
+
+- HID
+
+  - Added missing key codes for keypad
+  - Added HID Lighting and Illumination functionality
+
+- Vendor: Added empty transfers for tud_vendor_n_write()
+- MSC: Added support for SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL
+
+- CDC
+
+  - Add option to make CDC TX buffer persistent
+  - Add missing capability bit for CDC ACM serial break support
+
+- Net
+
+  - Rewrite of NCM device driver
+  - removed obsolete tud_network_link_state_cb()
+
+- Enhanced CDC class with better handling of large data transmissions.
+- Fixed issues in the HID class for more reliable device enumeration.
+- Video Added support for USB Video Class (UVC) with MJPEG.
+- USBTMC Added notification support
+
+Host Stack
+----------
+
+- Added tuh_deinit() to deinitialize TinyUSB host stack.
+- Added support for new USB mass storage class APIs.
+- Enhanced stability of CDC-ACM devices during enumeration.
+- Improved error handling and retry mechanisms for unstable devices.
+- Added support for multiple interfaces in UVC.
+
 0.16.0
 ======
 
diff --git a/docs/reference/dependencies.rst b/docs/reference/dependencies.rst
index 6ba6692e9..fd895519e 100644
--- a/docs/reference/dependencies.rst
+++ b/docs/reference/dependencies.rst
@@ -4,21 +4,21 @@ Dependencies
 
 MCU low-level peripheral driver and external libraries for building TinyUSB examples
 
-========================================  ==============================================================  ========================================  =======================================================================================================================================================================================================
+========================================  ==============================================================  ========================================  ==========================================================================================================================================================================================================================================================================================================================
 Local Path                                Repo                                                            Commit                                    Required by
-========================================  ==============================================================  ========================================  =======================================================================================================================================================================================================
+========================================  ==============================================================  ========================================  ==========================================================================================================================================================================================================================================================================================================================
 hw/mcu/allwinner                          https://github.com/hathach/allwinner_driver.git                 8e5e89e8e132c0fd90e72d5422e5d3d68232b756  fc100s
 hw/mcu/bridgetek/ft9xx/ft90x-sdk          https://github.com/BRTSG-FOSS/ft90x-sdk.git                     91060164afe239fcb394122e8bf9eb24d3194eb1  brtmm90x
 hw/mcu/broadcom                           https://github.com/adafruit/broadcom-peripherals.git            08370086080759ed54ac1136d62d2ad24c6fa267  broadcom_32bit broadcom_64bit
 hw/mcu/gd/nuclei-sdk                      https://github.com/Nuclei-Software/nuclei-sdk.git               7eb7bfa9ea4fbeacfafe1d5f77d5a0e6ed3922e7  gd32vf103
 hw/mcu/infineon/mtb-xmclib-cat3           https://github.com/Infineon/mtb-xmclib-cat3.git                 daf5500d03cba23e68c2f241c30af79cd9d63880  xmc4000
-hw/mcu/microchip                          https://github.com/hathach/microchip_driver.git                 9e8b37e307d8404033bb881623a113931e1edf27  sam3x samd11 samd21 samd51 same5x same7x saml2x samg
-hw/mcu/mindmotion/mm32sdk                 https://github.com/hathach/mm32sdk.git                          0b79559eb411149d36e073c1635c620e576308d4  mm32
-hw/mcu/nordic/nrfx                        https://github.com/NordicSemiconductor/nrfx.git                 2527e3c8449cfd38aee41598e8af8492f410ed15  nrf
+hw/mcu/microchip                          https://github.com/hathach/microchip_driver.git                 9e8b37e307d8404033bb881623a113931e1edf27  sam3x samd11 samd21 samd51 samd5x_e5x same5x same7x saml2x samg
+hw/mcu/mindmotion/mm32sdk                 https://github.com/hathach/mm32sdk.git                          b93e856211060ae825216c6a1d6aa347ec758843  mm32
+hw/mcu/nordic/nrfx                        https://github.com/NordicSemiconductor/nrfx.git                 7c47cc0a56ce44658e6da2458e86cd8783ccc4a2  nrf
 hw/mcu/nuvoton                            https://github.com/majbthrd/nuc_driver.git                      2204191ec76283371419fbcec207da02e1bc22fa  nuc
-hw/mcu/nxp/lpcopen                        https://github.com/hathach/nxp_lpcopen.git                      84e0bd3e43910aaf71eefd62075cf57495418312  lpc11 lpc13 lpc15 lpc17 lpc18 lpc40 lpc43
-hw/mcu/nxp/mcux-sdk                       https://github.com/hathach/mcux-sdk.git                         950819b7de9b32f92c3edf396bc5ffb8d66e7009  kinetis_k32l2 kinetis_kl lpc51 lpc54 lpc55 mcx imxrt
-hw/mcu/raspberry_pi/Pico-PIO-USB          https://github.com/sekigon-gonnoc/Pico-PIO-USB.git              d00a10a8c425d0d40f81b87169102944b01f3bb3  rp2040
+hw/mcu/nxp/lpcopen                        https://github.com/hathach/nxp_lpcopen.git                      04bfe7a5f6ee74a89a28ad618d3367dcfcfb7d83  lpc11 lpc13 lpc15 lpc17 lpc18 lpc40 lpc43
+hw/mcu/nxp/mcux-sdk                       https://github.com/hathach/mcux-sdk.git                         144f1eb7ea8c06512e12f12b27383601c0272410  kinetis_k kinetis_k32l2 kinetis_kl lpc51 lpc54 lpc55 mcx imxrt
+hw/mcu/raspberry_pi/Pico-PIO-USB          https://github.com/sekigon-gonnoc/Pico-PIO-USB.git              0f747aaa0c16f750bdfa2ba37ec25d6c8e1bc117  rp2040
 hw/mcu/renesas/fsp                        https://github.com/renesas/fsp.git                              d52e5a6a59b7c638da860c2bb309b6e78e752ff8  ra
 hw/mcu/renesas/rx                         https://github.com/kkitayam/rx_device.git                       706b4e0cf485605c32351e2f90f5698267996023  rx
 hw/mcu/silabs/cmsis-dfp-efm32gg12b        https://github.com/cmsis-packs/cmsis-dfp-efm32gg12b.git         f1c31b7887669cb230b3ea63f9b56769078960bc  efm32
@@ -28,15 +28,16 @@ hw/mcu/st/cmsis_device_f1                 https://github.com/STMicroelectronics/
 hw/mcu/st/cmsis_device_f2                 https://github.com/STMicroelectronics/cmsis_device_f2.git       182fcb3681ce116816feb41b7764f1b019ce796f  stm32f2
 hw/mcu/st/cmsis_device_f3                 https://github.com/STMicroelectronics/cmsis_device_f3.git       5e4ee5ed7a7b6c85176bb70a9fd3c72d6eb99f1b  stm32f3
 hw/mcu/st/cmsis_device_f4                 https://github.com/STMicroelectronics/cmsis_device_f4.git       2615e866fa48fe1ff1af9e31c348813f2b19e7ec  stm32f4
-hw/mcu/st/cmsis_device_f7                 https://github.com/STMicroelectronics/cmsis_device_f7.git       fc676ef1ad177eb874eaa06444d3d75395fc51f4  stm32f7
+hw/mcu/st/cmsis_device_f7                 https://github.com/STMicroelectronics/cmsis_device_f7.git       25b0463439303b7a38f0d27b161f7d2f3c096e79  stm32f7
 hw/mcu/st/cmsis_device_g0                 https://github.com/STMicroelectronics/cmsis_device_g0.git       3a23e1224417f3f2d00300ecd620495e363f2094  stm32g0
 hw/mcu/st/cmsis_device_g4                 https://github.com/STMicroelectronics/cmsis_device_g4.git       ce822adb1dc552b3aedd13621edbc7fdae124878  stm32g4
+hw/mcu/st/cmsis_device_h5                 https://github.com/STMicroelectronics/cmsis_device_h5.git       cd2d1d579743de57b88ccaf61a968b9c05848ffc  stm32h5
 hw/mcu/st/cmsis_device_h7                 https://github.com/STMicroelectronics/cmsis_device_h7.git       60dc2c913203dc8629dc233d4384dcc41c91e77f  stm32h7
-hw/mcu/st/cmsis_device_l0                 https://github.com/STMicroelectronics/cmsis_device_l0.git       06748ca1f93827befdb8b794402320d94d02004f  stm32l0
+hw/mcu/st/cmsis_device_l0                 https://github.com/STMicroelectronics/cmsis_device_l0.git       69cd5999fd40ae6e546d4905b21635c6ca1bcb92  stm32l0
 hw/mcu/st/cmsis_device_l1                 https://github.com/STMicroelectronics/cmsis_device_l1.git       7f16ec0a1c4c063f84160b4cc6bf88ad554a823e  stm32l1
 hw/mcu/st/cmsis_device_l4                 https://github.com/STMicroelectronics/cmsis_device_l4.git       6ca7312fa6a5a460b5a5a63d66da527fdd8359a6  stm32l4
 hw/mcu/st/cmsis_device_l5                 https://github.com/STMicroelectronics/cmsis_device_l5.git       d922865fc0326a102c26211c44b8e42f52c1e53d  stm32l5
-hw/mcu/st/cmsis_device_u5                 https://github.com/STMicroelectronics/cmsis_device_u5.git       06d7edade7167b0eafdd550bf77cfc4fa98eae2e  stm32u5
+hw/mcu/st/cmsis_device_u5                 https://github.com/STMicroelectronics/cmsis_device_u5.git       5ad9797c54ec3e55eff770fc9b3cd4a1aefc1309  stm32u5
 hw/mcu/st/cmsis_device_wb                 https://github.com/STMicroelectronics/cmsis_device_wb.git       9c5d1920dd9fabbe2548e10561d63db829bb744f  stm32wb
 hw/mcu/st/stm32f0xx_hal_driver            https://github.com/STMicroelectronics/stm32f0xx_hal_driver.git  0e95cd88657030f640a11e690a8a5186c7712ea5  stm32f0
 hw/mcu/st/stm32f1xx_hal_driver            https://github.com/STMicroelectronics/stm32f1xx_hal_driver.git  1dd9d3662fb7eb2a7f7d3bc0a4c1dc7537915a29  stm32f1
@@ -46,6 +47,7 @@ hw/mcu/st/stm32f4xx_hal_driver            https://github.com/STMicroelectronics/
 hw/mcu/st/stm32f7xx_hal_driver            https://github.com/STMicroelectronics/stm32f7xx_hal_driver.git  f7ffdf6bf72110e58b42c632b0a051df5997e4ee  stm32f7
 hw/mcu/st/stm32g0xx_hal_driver            https://github.com/STMicroelectronics/stm32g0xx_hal_driver.git  e911b12c7f67084d7f6b76157a4c0d4e2ec3779c  stm32g0
 hw/mcu/st/stm32g4xx_hal_driver            https://github.com/STMicroelectronics/stm32g4xx_hal_driver.git  8b4518417706d42eef5c14e56a650005abf478a8  stm32g4
+hw/mcu/st/stm32h5xx_hal_driver            https://github.com/STMicroelectronics/stm32h5xx_hal_driver.git  2cf77de584196d619cec1b4586c3b9e2820a254e  stm32h5
 hw/mcu/st/stm32h7xx_hal_driver            https://github.com/STMicroelectronics/stm32h7xx_hal_driver.git  d8461b980b59b1625207d8c4f2ce0a9c2a7a3b04  stm32h7
 hw/mcu/st/stm32l0xx_hal_driver            https://github.com/STMicroelectronics/stm32l0xx_hal_driver.git  fbdacaf6f8c82a4e1eb9bd74ba650b491e97e17b  stm32l0
 hw/mcu/st/stm32l1xx_hal_driver            https://github.com/STMicroelectronics/stm32l1xx_hal_driver.git  44efc446fa69ed8344e7fd966e68ed11043b35d9  stm32l1
@@ -53,12 +55,13 @@ hw/mcu/st/stm32l4xx_hal_driver            https://github.com/STMicroelectronics/
 hw/mcu/st/stm32l5xx_hal_driver            https://github.com/STMicroelectronics/stm32l5xx_hal_driver.git  675c32a75df37f39d50d61f51cb0dcf53f07e1cb  stm32l5
 hw/mcu/st/stm32u5xx_hal_driver            https://github.com/STMicroelectronics/stm32u5xx_hal_driver.git  4d93097a67928e9377e655ddd14622adc31b9770  stm32u5
 hw/mcu/st/stm32wbxx_hal_driver            https://github.com/STMicroelectronics/stm32wbxx_hal_driver.git  2c5f06638be516c1b772f768456ba637f077bac8  stm32wb
-hw/mcu/ti                                 https://github.com/hathach/ti_driver.git                        143ed6cc20a7615d042b03b21e070197d473e6e5  msp430 msp432e4 tm4c123
+hw/mcu/ti                                 https://github.com/hathach/ti_driver.git                        143ed6cc20a7615d042b03b21e070197d473e6e5  msp430 msp432e4 tm4c
 hw/mcu/wch/ch32f20x                       https://github.com/openwch/ch32f20x.git                         77c4095087e5ed2c548ec9058e655d0b8757663b  ch32f20x
+hw/mcu/wch/ch32v20x                       https://github.com/openwch/ch32v20x.git                         de6d68c654340d7f27b00cebbfc9aa2740a1abc2  ch32v20x
 hw/mcu/wch/ch32v307                       https://github.com/openwch/ch32v307.git                         17761f5cf9dbbf2dcf665b7c04934188add20082  ch32v307
-lib/CMSIS_5                               https://github.com/ARM-software/CMSIS_5.git                     20285262657d1b482d132d20d755c8c330d55c1f  imxrt kinetis_k32l2 kinetis_kl lpc51 lpc54 lpc55 mcx mm32 msp432e4 nrf ra saml2xstm32f0 stm32f1 stm32f2 stm32f3 stm32f4 stm32f7 stm32g0 stm32g4 stm32h7 stm32l0 stm32l1 stm32l4 stm32l5 stm32u5 stm32wb
-lib/FreeRTOS-Kernel                       https://github.com/FreeRTOS/FreeRTOS-Kernel.git                 4ff01a7a4a51f53b44496aefee1e3c0071b7b173  all
+lib/CMSIS_5                               https://github.com/ARM-software/CMSIS_5.git                     20285262657d1b482d132d20d755c8c330d55c1f  imxrt kinetis_k32l2 kinetis_kl lpc51 lpc54 lpc55 mcx mm32 msp432e4 nrf ra saml2xlpc11 lpc13 lpc15 lpc17 lpc18 lpc40 lpc43stm32f0 stm32f1 stm32f2 stm32f3 stm32f4 stm32f7 stm32g0 stm32g4 stm32h5stm32h7 stm32l0 stm32l1 stm32l4 stm32l5 stm32u5 stm32wbsam3x samd11 samd21 samd51 samd5x_e5x same5x same7x saml2x samgtm4c
+lib/FreeRTOS-Kernel                       https://github.com/FreeRTOS/FreeRTOS-Kernel.git                 cc0e0707c0c748713485b870bb980852b210877f  all
 lib/lwip                                  https://github.com/lwip-tcpip/lwip.git                          159e31b689577dbf69cf0683bbaffbd71fa5ee10  all
 lib/sct_neopixel                          https://github.com/gsteiert/sct_neopixel.git                    e73e04ca63495672d955f9268e003cffe168fcd8  lpc55
 tools/uf2                                 https://github.com/microsoft/uf2.git                            19615407727073e36d81bf239c52108ba92e7660  all
-========================================  ==============================================================  ========================================  =======================================================================================================================================================================================================
+========================================  ==============================================================  ========================================  ==========================================================================================================================================================================================================================================================================================================================
diff --git a/docs/reference/supported.rst b/docs/reference/supported.rst
index 7e7be25a4..3ca203e88 100644
--- a/docs/reference/supported.rst
+++ b/docs/reference/supported.rst
@@ -128,9 +128,9 @@ Supported MCUs
 +--------------+-----------------------+--------+------+-----------+-------------------+--------------+
 | ValentyUSB   | eptri                 | ✔      | ✖    | ✖         | eptri             |              |
 +--------------+-----------------------+--------+------+-----------+-------------------+--------------+
-| WCH          | CH32V307              | ✔      |      | ✔         | ch32v307          |              |
-|              +-----------------------+--------+------+-----------+-------------------+--------------+
-|              | CH32F20x              | ✔      |      | ✔         | ch32f205          |              |
+| WCH          | CH32F20x              | ✔      |      | ✔         | ch32f205          |              |
+|              | CH32V20x              | ✔      |      | ✖         | ch32v20x          |              |
+|              | CH32V307              | ✔      |      | ✔         | ch32v307          |              |
 +--------------+-----------------------+--------+------+-----------+-------------------+--------------+
 
 
diff --git a/examples/build_system/cmake/cpu/rv32i-ilp32.cmake b/examples/build_system/cmake/cpu/rv32i-ilp32.cmake
index b4889e6ff..605c40ba1 100644
--- a/examples/build_system/cmake/cpu/rv32i-ilp32.cmake
+++ b/examples/build_system/cmake/cpu/rv32i-ilp32.cmake
@@ -1,13 +1,13 @@
 if (TOOLCHAIN STREQUAL "gcc")
   set(TOOLCHAIN_COMMON_FLAGS
-    -march=rv32i
+    -march=rv32i_zicsr
     -mabi=ilp32
     )
   set(FREERTOS_PORT GCC_RISC_V CACHE INTERNAL "")
 
 elseif (TOOLCHAIN STREQUAL "clang")
   set(TOOLCHAIN_COMMON_FLAGS
-    -march=rv32i
+    -march=rv32i_zicsr
     -mabi=ilp32
     )
   set(FREERTOS_PORT GCC_RISC_V CACHE INTERNAL "")
diff --git a/examples/build_system/cmake/cpu/rv32imac-ilp32.cmake b/examples/build_system/cmake/cpu/rv32imac-ilp32.cmake
index dd1bc0af7..584d90519 100644
--- a/examples/build_system/cmake/cpu/rv32imac-ilp32.cmake
+++ b/examples/build_system/cmake/cpu/rv32imac-ilp32.cmake
@@ -1,13 +1,13 @@
 if (TOOLCHAIN STREQUAL "gcc")
   set(TOOLCHAIN_COMMON_FLAGS
-    -march=rv32imac
+    -march=rv32imac_zicsr
     -mabi=ilp32
     )
   set(FREERTOS_PORT GCC_RISC_V CACHE INTERNAL "")
 
 elseif (TOOLCHAIN STREQUAL "clang")
   set(TOOLCHAIN_COMMON_FLAGS
-    -march=rv32imac
+    -march=rv32imac_zicsr
     -mabi=ilp32
     )
   set(FREERTOS_PORT GCC_RISC_V CACHE INTERNAL "")
diff --git a/examples/build_system/cmake/toolchain/riscv_gcc.cmake b/examples/build_system/cmake/toolchain/riscv_gcc.cmake
index 904b27294..d788df023 100644
--- a/examples/build_system/cmake/toolchain/riscv_gcc.cmake
+++ b/examples/build_system/cmake/toolchain/riscv_gcc.cmake
@@ -1,15 +1,24 @@
+# default Toolchain from https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack
+if (NOT DEFINED CROSS_COMPILE)
+  set(CROSS_COMPILE "riscv-none-elf-")
+endif ()
+
 if (NOT DEFINED CMAKE_C_COMPILER)
-  set(CMAKE_C_COMPILER "riscv-none-embed-gcc")
+  set(CMAKE_C_COMPILER ${CROSS_COMPILE}gcc)
+endif ()
+
+if (NOT DEFINED CMAKE_C_COMPILER)
+  set(CMAKE_C_COMPILER ${CROSS_COMPILE}gcc)
 endif ()
 
 if (NOT DEFINED CMAKE_CXX_COMPILER)
-  set(CMAKE_CXX_COMPILER "riscv-none-embed-g++")
+  set(CMAKE_CXX_COMPILER ${CROSS_COMPILE}g++)
 endif ()
 
 set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
-set(CMAKE_SIZE "riscv-none-embed-size" CACHE FILEPATH "")
-set(CMAKE_OBJCOPY "riscv-none-embed-objcopy" CACHE FILEPATH "")
-set(CMAKE_OBJDUMP "riscv-none-embed-objdump" CACHE FILEPATH "")
+set(CMAKE_SIZE ${CROSS_COMPILE}size CACHE FILEPATH "")
+set(CMAKE_OBJCOPY ${CROSS_COMPILE}objcopy CACHE FILEPATH "")
+set(CMAKE_OBJDUMP ${CROSS_COMPILE}objdump CACHE FILEPATH "")
 
 include(${CMAKE_CURRENT_LIST_DIR}/common.cmake)
 
diff --git a/examples/build_system/make/cpu/rv32i-ilp32.mk b/examples/build_system/make/cpu/rv32i-ilp32.mk
index a465baf4c..af764afc5 100644
--- a/examples/build_system/make/cpu/rv32i-ilp32.mk
+++ b/examples/build_system/make/cpu/rv32i-ilp32.mk
@@ -1,12 +1,15 @@
 ifeq ($(TOOLCHAIN),gcc)
   CFLAGS += \
-    -march=rv32i \
+    -march=rv32i_zicsr \
+    -mabi=ilp32 \
+
+else ifeq ($(TOOLCHAIN),clang)
+  CFLAGS += \
+    -march=rv32i_zicsr \
     -mabi=ilp32 \
 
 else ifeq ($(TOOLCHAIN),iar)
-	#CFLAGS += --cpu cortex-a53
-	#ASFLAGS += --cpu cortex-a53
-
+  $(error not support)
 endif
 
 # For freeRTOS port source
diff --git a/examples/build_system/make/cpu/rv32imac-ilp32.mk b/examples/build_system/make/cpu/rv32imac-ilp32.mk
index 2b6493e48..19c322ebc 100644
--- a/examples/build_system/make/cpu/rv32imac-ilp32.mk
+++ b/examples/build_system/make/cpu/rv32imac-ilp32.mk
@@ -1,11 +1,15 @@
 ifeq ($(TOOLCHAIN),gcc)
   CFLAGS += \
-    -march=rv32imac \
+    -march=rv32imac_zicsr \
+    -mabi=ilp32 \
+
+else ifeq ($(TOOLCHAIN),clang)
+  CFLAGS += \
+    -march=rv32imac_zicsr \
     -mabi=ilp32 \
 
 else ifeq ($(TOOLCHAIN),iar)
-	#CFLAGS += --cpu cortex-a53
-	#ASFLAGS += --cpu cortex-a53
+  $(error not support)
 
 endif
 
diff --git a/examples/build_system/make/rules.mk b/examples/build_system/make/rules.mk
index 7b6b339ed..102c6db0c 100644
--- a/examples/build_system/make/rules.mk
+++ b/examples/build_system/make/rules.mk
@@ -134,6 +134,17 @@ OPENOCD_OPTION ?=
 flash-openocd: $(BUILD)/$(PROJECT).elf
 	openocd $(OPENOCD_OPTION) -c "program $< verify reset exit"
 
+# --------------- openocd-wch -----------------
+# wch-linke is not supported yet in official openOCD yet. We need to either use
+# 1. download openocd as part of mounriver studio http://www.mounriver.com/download or
+# 2. compiled from https://github.com/hathach/riscv-openocd-wch or
+#    https://github.com/dragonlock2/miscboards/blob/main/wch/SDK/riscv-openocd.tar.xz
+#    with  ./configure --disable-werror --enable-wlinke --enable-ch347=no
+OPENOCD_WCH ?= /home/${USER}/app/riscv-openocd-wch/src/openocd
+OPENOCD_WCH_OPTION ?=
+flash-openocd-wch: $(BUILD)/$(PROJECT).elf
+	$(OPENOCD_WCH) $(OPENOCD_WCH_OPTION) -c init -c halt -c "flash write_image $<" -c reset -c exit
+
 # --------------- dfu-util -----------------
 DFU_UTIL_OPTION ?= -a 0
 flash-dfu-util: $(BUILD)/$(PROJECT).bin
diff --git a/examples/device/audio_4_channel_mic_freertos/skip.txt b/examples/device/audio_4_channel_mic_freertos/skip.txt
index 0b689192d..4769af009 100644
--- a/examples/device/audio_4_channel_mic_freertos/skip.txt
+++ b/examples/device/audio_4_channel_mic_freertos/skip.txt
@@ -1,3 +1,4 @@
+mcu:CH32V20X
 mcu:CH32V307
 mcu:CXD56
 mcu:F1C100S
diff --git a/examples/device/audio_test_freertos/skip.txt b/examples/device/audio_test_freertos/skip.txt
index a6f96b288..6aaa27661 100644
--- a/examples/device/audio_test_freertos/skip.txt
+++ b/examples/device/audio_test_freertos/skip.txt
@@ -1,3 +1,4 @@
+mcu:CH32V20X
 mcu:CH32V307
 mcu:CXD56
 mcu:F1C100S
diff --git a/examples/device/cdc_msc_freertos/skip.txt b/examples/device/cdc_msc_freertos/skip.txt
index eb434c23b..457ddb760 100644
--- a/examples/device/cdc_msc_freertos/skip.txt
+++ b/examples/device/cdc_msc_freertos/skip.txt
@@ -1,3 +1,4 @@
+mcu:CH32V20X
 mcu:CH32V307
 mcu:CXD56
 mcu:F1C100S
diff --git a/examples/device/hid_composite_freertos/skip.txt b/examples/device/hid_composite_freertos/skip.txt
index a6f96b288..6aaa27661 100644
--- a/examples/device/hid_composite_freertos/skip.txt
+++ b/examples/device/hid_composite_freertos/skip.txt
@@ -1,3 +1,4 @@
+mcu:CH32V20X
 mcu:CH32V307
 mcu:CXD56
 mcu:F1C100S
diff --git a/examples/device/msc_dual_lun/src/msc_disk_dual.c b/examples/device/msc_dual_lun/src/msc_disk_dual.c
index 4f0f6410f..b44b77c6c 100644
--- a/examples/device/msc_dual_lun/src/msc_disk_dual.c
+++ b/examples/device/msc_dual_lun/src/msc_disk_dual.c
@@ -34,9 +34,13 @@
 // Some MCU doesn't have enough 8KB SRAM to store the whole disk
 // We will use Flash as read-only disk with board that has
 // CFG_EXAMPLE_MSC_READONLY defined
+#if defined(CFG_EXAMPLE_MSC_READONLY) || defined(CFG_EXAMPLE_MSC_DUAL_READONLY)
+  #define MSC_CONST const
+#else
+  #define MSC_CONST
+#endif
 
-enum
-{
+enum {
   DISK_BLOCK_NUM  = 16, // 8KB is the smallest size that windows allow to mount
   DISK_BLOCK_SIZE = 512
 };
@@ -51,10 +55,7 @@ If you find any bugs or get any questions, feel free to file an\r\n\
 issue at github.com/hathach/tinyusb"
 
 
-#ifdef CFG_EXAMPLE_MSC_READONLY
-const
-#endif
-uint8_t msc_disk0[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] =
+MSC_CONST uint8_t msc_disk0[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] =
 {
   //------------- Block0: Boot Sector -------------//
   // byte_per_sector    = DISK_BLOCK_SIZE; fat12_sector_num_16  = DISK_BLOCK_NUM;
@@ -132,10 +133,7 @@ uint8_t msc_disk0[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] =
 If you find any bugs or get any questions, feel free to file an\r\n\
 issue at github.com/hathach/tinyusb"
 
-#ifdef CFG_EXAMPLE_MSC_READONLY
-const
-#endif
-uint8_t msc_disk1[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] =
+MSC_CONST uint8_t msc_disk1[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] =
 {
   //------------- Block0: Boot Sector -------------//
   // byte_per_sector    = DISK_BLOCK_SIZE; fat12_sector_num_16  = DISK_BLOCK_NUM;
@@ -206,15 +204,13 @@ uint8_t msc_disk1[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] =
 };
 
 // Invoked to determine max LUN
-uint8_t tud_msc_get_maxlun_cb(void)
-{
+uint8_t tud_msc_get_maxlun_cb(void) {
   return 2; // dual LUN
 }
 
 // Invoked when received SCSI_CMD_INQUIRY
 // Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
-void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
-{
+void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {
   (void) lun; // use same ID for both LUNs
 
   const char vid[] = "TinyUSB";
@@ -228,8 +224,7 @@ void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16
 
 // Invoked when received Test Unit Ready command.
 // return true allowing host to read/write this LUN e.g SD card inserted
-bool tud_msc_test_unit_ready_cb(uint8_t lun)
-{
+bool tud_msc_test_unit_ready_cb(uint8_t lun) {
   if ( lun == 1 && board_button_read() ) return false;
 
   return true; // RAM disk is always ready
@@ -237,8 +232,7 @@ bool tud_msc_test_unit_ready_cb(uint8_t lun)
 
 // Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
 // Application update block count and block size
-void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size)
-{
+void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) {
   (void) lun;
 
   *block_count = DISK_BLOCK_NUM;
@@ -248,18 +242,14 @@ void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_siz
 // Invoked when received Start Stop Unit command
 // - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
 // - Start = 1 : active mode, if load_eject = 1 : load disk storage
-bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
-{
+bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) {
   (void) lun;
   (void) power_condition;
 
-  if ( load_eject )
-  {
-    if (start)
-    {
+  if (load_eject) {
+    if (start) {
       // load disk storage
-    }else
-    {
+    } else {
       // unload disk storage
     }
   }
@@ -269,10 +259,9 @@ bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, boo
 
 // Callback invoked when received READ10 command.
 // Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
-int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
-{
+int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {
   // out of ramdisk
-  if ( lba >= DISK_BLOCK_NUM ) return -1;
+  if (lba >= DISK_BLOCK_NUM) return -1;
 
   uint8_t const* addr = (lun ? msc_disk1[lba] : msc_disk0[lba]) + offset;
   memcpy(buffer, addr, bufsize);
@@ -280,11 +269,10 @@ int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buff
   return (int32_t) bufsize;
 }
 
-bool tud_msc_is_writable_cb (uint8_t lun)
-{
+bool tud_msc_is_writable_cb(uint8_t lun) {
   (void) lun;
 
-#ifdef CFG_EXAMPLE_MSC_READONLY
+#if defined(CFG_EXAMPLE_MSC_READONLY) || defined(CFG_EXAMPLE_MSC_DUAL_READONLY)
   return false;
 #else
   return true;
@@ -293,16 +281,18 @@ bool tud_msc_is_writable_cb (uint8_t lun)
 
 // Callback invoked when received WRITE10 command.
 // Process data in buffer to disk's storage and return number of written bytes
-int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize)
-{
+int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) {
   // out of ramdisk
-  if ( lba >= DISK_BLOCK_NUM ) return -1;
+  if (lba >= DISK_BLOCK_NUM) return -1;
 
-#ifndef CFG_EXAMPLE_MSC_READONLY
+#if defined(CFG_EXAMPLE_MSC_READONLY) || defined(CFG_EXAMPLE_MSC_DUAL_READONLY)
+  (void) lun;
+  (void) lba;
+  (void) offset;
+  (void) buffer;
+#else
   uint8_t* addr = (lun ? msc_disk1[lba] : msc_disk0[lba])  + offset;
   memcpy(addr, buffer, bufsize);
-#else
-  (void) lun; (void) lba; (void) offset; (void) buffer;
 #endif
 
   return (int32_t) bufsize;
@@ -310,38 +300,30 @@ int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t*
 
 // Callback invoked when received an SCSI command not in built-in list below
 // - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
-// - READ10 and WRITE10 has their own callbacks
-int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize)
-{
-  // read10 & write10 has their own callback and MUST not be handled here
-
+// - READ10 and WRITE10 has their own callbacks (MUST not be handled here)
+int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) {
   void const* response = NULL;
   int32_t resplen = 0;
 
   // most scsi handled is input
   bool in_xfer = true;
 
-  switch (scsi_cmd[0])
-  {
+  switch (scsi_cmd[0]) {
     default:
       // Set Sense = Invalid Command Operation
       tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
 
       // negative means error -> tinyusb could stall and/or response with failed status
-      resplen = -1;
-    break;
+      return -1;
   }
 
   // return resplen must not larger than bufsize
-  if ( resplen > bufsize ) resplen = bufsize;
+  if (resplen > bufsize) resplen = bufsize;
 
-  if ( response && (resplen > 0) )
-  {
-    if(in_xfer)
-    {
+  if (response && (resplen > 0)) {
+    if (in_xfer) {
       memcpy(buffer, response, (size_t) resplen);
-    }else
-    {
+    } else {
       // SCSI output
     }
   }
diff --git a/examples/device/net_lwip_webserver/skip.txt b/examples/device/net_lwip_webserver/skip.txt
index 43cdab71a..51af58667 100644
--- a/examples/device/net_lwip_webserver/skip.txt
+++ b/examples/device/net_lwip_webserver/skip.txt
@@ -1,3 +1,4 @@
+mcu:CH32V20X
 mcu:LPC11UXX
 mcu:LPC13XX
 mcu:LPC15XX
diff --git a/examples/device/video_capture/skip.txt b/examples/device/video_capture/skip.txt
index 5898664a2..714cabeec 100644
--- a/examples/device/video_capture/skip.txt
+++ b/examples/device/video_capture/skip.txt
@@ -1,3 +1,4 @@
+mcu:CH32V20X
 mcu:MSP430x5xx
 mcu:NUC121
 mcu:SAMD11
diff --git a/examples/device/video_capture_2ch/skip.txt b/examples/device/video_capture_2ch/skip.txt
index 86697899b..1786297f9 100644
--- a/examples/device/video_capture_2ch/skip.txt
+++ b/examples/device/video_capture_2ch/skip.txt
@@ -2,6 +2,7 @@ mcu:MSP430x5xx
 mcu:NUC121
 mcu:SAMD11
 mcu:GD32VF103
+mcu:CH32V20X
 mcu:CH32V307
 mcu:STM32L0
 family:espressif
diff --git a/hw/bsp/ch32v20x/boards/ch32v203_r0_1v0/board.cmake b/hw/bsp/ch32v20x/boards/ch32v203_r0_1v0/board.cmake
new file mode 100644
index 000000000..8d3e0326e
--- /dev/null
+++ b/hw/bsp/ch32v20x/boards/ch32v203_r0_1v0/board.cmake
@@ -0,0 +1,10 @@
+set(MCU_VARIANT D6)
+
+set(LD_FLASH_SIZE 64K)
+set(LD_RAM_SIZE 20K)
+
+function(update_board TARGET)
+  target_compile_definitions(${TARGET} PUBLIC
+    CFG_EXAMPLE_MSC_DUAL_READONLY
+    )
+endfunction()
diff --git a/hw/bsp/ch32v20x/boards/ch32v203_r0_1v0/board.h b/hw/bsp/ch32v20x/boards/ch32v203_r0_1v0/board.h
new file mode 100644
index 000000000..3ed2aef04
--- /dev/null
+++ b/hw/bsp/ch32v20x/boards/ch32v203_r0_1v0/board.h
@@ -0,0 +1,16 @@
+#ifndef BOARD_H_
+#define BOARD_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LED_PORT       GPIOA
+#define LED_PIN        GPIO_Pin_15
+#define LED_STATE_ON   0
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/hw/bsp/ch32v20x/boards/ch32v203_r0_1v0/board.mk b/hw/bsp/ch32v20x/boards/ch32v203_r0_1v0/board.mk
new file mode 100644
index 000000000..7d7462312
--- /dev/null
+++ b/hw/bsp/ch32v20x/boards/ch32v203_r0_1v0/board.mk
@@ -0,0 +1,7 @@
+MCU_VARIANT = D6
+
+CFLAGS += -DCFG_EXAMPLE_MSC_DUAL_READONLY
+
+LDFLAGS += \
+  -Wl,--defsym=__flash_size=64K \
+  -Wl,--defsym=__ram_size=20K \
diff --git a/hw/bsp/ch32v20x/boards/nanoch32v203/board.cmake b/hw/bsp/ch32v20x/boards/nanoch32v203/board.cmake
new file mode 100644
index 000000000..8d3e0326e
--- /dev/null
+++ b/hw/bsp/ch32v20x/boards/nanoch32v203/board.cmake
@@ -0,0 +1,10 @@
+set(MCU_VARIANT D6)
+
+set(LD_FLASH_SIZE 64K)
+set(LD_RAM_SIZE 20K)
+
+function(update_board TARGET)
+  target_compile_definitions(${TARGET} PUBLIC
+    CFG_EXAMPLE_MSC_DUAL_READONLY
+    )
+endfunction()
diff --git a/hw/bsp/ch32v20x/boards/nanoch32v203/board.h b/hw/bsp/ch32v20x/boards/nanoch32v203/board.h
new file mode 100644
index 000000000..c8d28d90f
--- /dev/null
+++ b/hw/bsp/ch32v20x/boards/nanoch32v203/board.h
@@ -0,0 +1,17 @@
+#ifndef BOARD_H_
+#define BOARD_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LED_PORT       GPIOA
+#define LED_PIN        GPIO_Pin_15
+#define LED_STATE_ON   0
+#define LED_CLOCK_EN() RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/hw/bsp/ch32v20x/boards/nanoch32v203/board.mk b/hw/bsp/ch32v20x/boards/nanoch32v203/board.mk
new file mode 100644
index 000000000..7d7462312
--- /dev/null
+++ b/hw/bsp/ch32v20x/boards/nanoch32v203/board.mk
@@ -0,0 +1,7 @@
+MCU_VARIANT = D6
+
+CFLAGS += -DCFG_EXAMPLE_MSC_DUAL_READONLY
+
+LDFLAGS += \
+  -Wl,--defsym=__flash_size=64K \
+  -Wl,--defsym=__ram_size=20K \
diff --git a/hw/bsp/ch32v20x/ch32v20x_conf.h b/hw/bsp/ch32v20x/ch32v20x_conf.h
new file mode 100644
index 000000000..949297fe6
--- /dev/null
+++ b/hw/bsp/ch32v20x/ch32v20x_conf.h
@@ -0,0 +1,36 @@
+/********************************** (C) COPYRIGHT *******************************
+ * File Name          : ch32v20x_conf.h
+ * Author             : WCH
+ * Version            : V1.0.0
+ * Date               : 2021/06/06
+ * Description        : Library configuration file.
+*********************************************************************************
+* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
+* Attention: This software (modified or not) and binary are used for
+* microcontroller manufactured by Nanjing Qinheng Microelectronics.
+*******************************************************************************/
+#ifndef __CH32V20x_CONF_H
+#define __CH32V20x_CONF_H
+
+#include "ch32v20x_adc.h"
+#include "ch32v20x_bkp.h"
+#include "ch32v20x_can.h"
+#include "ch32v20x_crc.h"
+#include "ch32v20x_dbgmcu.h"
+#include "ch32v20x_dma.h"
+#include "ch32v20x_exti.h"
+#include "ch32v20x_flash.h"
+#include "ch32v20x_gpio.h"
+#include "ch32v20x_i2c.h"
+#include "ch32v20x_iwdg.h"
+#include "ch32v20x_pwr.h"
+#include "ch32v20x_rcc.h"
+#include "ch32v20x_rtc.h"
+#include "ch32v20x_spi.h"
+#include "ch32v20x_tim.h"
+#include "ch32v20x_usart.h"
+#include "ch32v20x_wwdg.h"
+#include "ch32v20x_it.h"
+#include "ch32v20x_misc.h"
+
+#endif /* __CH32V20x_CONF_H */
diff --git a/hw/bsp/ch32v20x/ch32v20x_it.h b/hw/bsp/ch32v20x/ch32v20x_it.h
new file mode 100644
index 000000000..e49c61ae2
--- /dev/null
+++ b/hw/bsp/ch32v20x/ch32v20x_it.h
@@ -0,0 +1,15 @@
+/********************************** (C) COPYRIGHT *******************************
+ * File Name          : ch32v20x_it.h
+ * Author             : WCH
+ * Version            : V1.0.0
+ * Date               : 2021/06/06
+ * Description        : This file contains the headers of the interrupt handlers.
+*********************************************************************************
+* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
+* Attention: This software (modified or not) and binary are used for
+* microcontroller manufactured by Nanjing Qinheng Microelectronics.
+*******************************************************************************/
+#ifndef __CH32V20x_IT_H
+#define __CH32V20x_IT_H
+
+#endif /* __CH32V20x_IT_H */
diff --git a/hw/bsp/ch32v20x/core_riscv.h b/hw/bsp/ch32v20x/core_riscv.h
new file mode 100644
index 000000000..be1b52a6c
--- /dev/null
+++ b/hw/bsp/ch32v20x/core_riscv.h
@@ -0,0 +1,572 @@
+/********************************** (C) COPYRIGHT  *******************************
+ * File Name          : core_riscv.h
+ * Author             : WCH
+ * Version            : V1.0.0
+ * Date               : 2021/06/06
+ * Description        : RISC-V Core Peripheral Access Layer Header File for CH32V20x
+*********************************************************************************
+* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
+* Attention: This software (modified or not) and binary are used for
+* microcontroller manufactured by Nanjing Qinheng Microelectronics.
+*******************************************************************************/
+#ifndef __CORE_RISCV_H__
+#define __CORE_RISCV_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* IO definitions */
+#ifdef __cplusplus
+  #define     __I     volatile                /*  defines 'read only' permissions      */
+#else
+  #define     __I     volatile const          /*  defines 'read only' permissions     */
+#endif
+#define       __O     volatile                /*  defines 'write only' permissions     */
+#define       __IO    volatile                /*  defines 'read / write' permissions   */
+
+/* Standard Peripheral Library old types (maintained for legacy purpose) */
+typedef __I uint64_t vuc64;   /* Read Only */
+typedef __I uint32_t vuc32;   /* Read Only */
+typedef __I uint16_t vuc16;   /* Read Only */
+typedef __I uint8_t  vuc8;    /* Read Only */
+
+typedef const uint64_t uc64;  /* Read Only */
+typedef const uint32_t uc32;  /* Read Only */
+typedef const uint16_t uc16;  /* Read Only */
+typedef const uint8_t  uc8;   /* Read Only */
+
+typedef __I int64_t vsc64;    /* Read Only */
+typedef __I int32_t vsc32;    /* Read Only */
+typedef __I int16_t vsc16;    /* Read Only */
+typedef __I int8_t  vsc8;     /* Read Only */
+
+typedef const int64_t sc64;   /* Read Only */
+typedef const int32_t sc32;   /* Read Only */
+typedef const int16_t sc16;   /* Read Only */
+typedef const int8_t  sc8;    /* Read Only */
+
+typedef __IO uint64_t  vu64;
+typedef __IO uint32_t  vu32;
+typedef __IO uint16_t  vu16;
+typedef __IO uint8_t   vu8;
+
+typedef uint64_t  u64;
+typedef uint32_t  u32;
+typedef uint16_t  u16;
+typedef uint8_t   u8;
+
+typedef __IO int64_t  vs64;
+typedef __IO int32_t  vs32;
+typedef __IO int16_t  vs16;
+typedef __IO int8_t   vs8;
+
+typedef int64_t  s64;
+typedef int32_t  s32;
+typedef int16_t  s16;
+typedef int8_t   s8;
+
+typedef enum {NoREADY = 0, READY = !NoREADY} ErrorStatus;
+
+typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState;
+
+typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;
+
+#define   RV_STATIC_INLINE  static  inline
+
+/* memory mapped structure for Program Fast Interrupt Controller (PFIC) */
+typedef struct{
+  __I  uint32_t ISR[8];
+  __I  uint32_t IPR[8];
+  __IO uint32_t ITHRESDR;
+  __IO uint32_t RESERVED;
+  __IO uint32_t CFGR;
+  __I  uint32_t GISR;
+  __IO uint8_t VTFIDR[4];
+  uint8_t RESERVED0[12];
+  __IO uint32_t VTFADDR[4];
+  uint8_t RESERVED1[0x90];
+  __O  uint32_t IENR[8];
+  uint8_t RESERVED2[0x60];
+  __O  uint32_t IRER[8];
+  uint8_t RESERVED3[0x60];
+  __O  uint32_t IPSR[8];
+  uint8_t RESERVED4[0x60];
+  __O  uint32_t IPRR[8];
+  uint8_t RESERVED5[0x60];
+  __IO uint32_t IACTR[8];
+  uint8_t RESERVED6[0xE0];
+  __IO uint8_t IPRIOR[256];
+  uint8_t RESERVED7[0x810];
+  __IO uint32_t SCTLR;
+}PFIC_Type;
+
+/* memory mapped structure for SysTick */
+typedef struct
+{
+    __IO uint32_t CTLR;
+    __IO uint32_t SR;
+    __IO uint64_t CNT;
+    __IO uint64_t CMP;
+}SysTick_Type;
+
+#define PFIC            ((PFIC_Type *) 0xE000E000 )
+#define NVIC            PFIC
+#define NVIC_KEY1       ((uint32_t)0xFA050000)
+#define	NVIC_KEY2		((uint32_t)0xBCAF0000)
+#define	NVIC_KEY3		((uint32_t)0xBEEF0000)
+
+#define SysTick         ((SysTick_Type *) 0xE000F000)
+
+/*********************************************************************
+ * @fn      __enable_irq
+ *
+ * @brief   Enable Global Interrupt
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void __enable_irq(void)
+{
+  __asm volatile ("csrw 0x800, %0" : : "r" (0x6088) );
+}
+
+/*********************************************************************
+ * @fn      __disable_irq
+ *
+ * @brief   Disable Global Interrupt
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void __disable_irq(void)
+{
+  __asm volatile ("csrw 0x800, %0" : : "r" (0x6000) );
+}
+
+/*********************************************************************
+ * @fn      __NOP
+ *
+ * @brief   nop
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void __NOP(void)
+{
+  __asm volatile ("nop");
+}
+
+/*********************************************************************
+ * @fn      NVIC_EnableIRQ
+ *
+ * @brief   Disable Interrupt
+ *
+ * @param   IRQn - Interrupt Numbers
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
+{
+  NVIC->IENR[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F));
+}
+
+/*********************************************************************
+ * @fn      NVIC_DisableIRQ
+ *
+ * @brief   Disable Interrupt
+ *
+ * @param   IRQn - Interrupt Numbers
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void NVIC_DisableIRQ(IRQn_Type IRQn)
+{
+  NVIC->IRER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F));
+}
+
+/*********************************************************************
+ * @fn      NVIC_GetStatusIRQ
+ *
+ * @brief   Get Interrupt Enable State
+ *
+ * @param   IRQn - Interrupt Numbers
+ *
+ * @return  1 - Interrupt Pending Enable
+ *          0 - Interrupt Pending Disable
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE uint32_t NVIC_GetStatusIRQ(IRQn_Type IRQn)
+{
+  return((uint32_t) ((NVIC->ISR[(uint32_t)(IRQn) >> 5] & (1 << ((uint32_t)(IRQn) & 0x1F)))?1:0));
+}
+
+/*********************************************************************
+ * @fn      NVIC_GetPendingIRQ
+ *
+ * @brief   Get Interrupt Pending State
+ *
+ * @param   IRQn - Interrupt Numbers
+ *
+ * @return  1 - Interrupt Pending Enable
+ *          0 - Interrupt Pending Disable
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)
+{
+  return((uint32_t) ((NVIC->IPR[(uint32_t)(IRQn) >> 5] & (1 << ((uint32_t)(IRQn) & 0x1F)))?1:0));
+}
+
+/*********************************************************************
+ * @fn      NVIC_SetPendingIRQ
+ *
+ * @brief   Set Interrupt Pending
+ *
+ * @param   IRQn - Interrupt Numbers
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn)
+{
+  NVIC->IPSR[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F));
+}
+
+/*********************************************************************
+ * @fn      NVIC_ClearPendingIRQ
+ *
+ * @brief   Clear Interrupt Pending
+ *
+ * @param   IRQn - Interrupt Numbers
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
+{
+  NVIC->IPRR[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F));
+}
+
+/*********************************************************************
+ * @fn      NVIC_GetActive
+ *
+ * @brief   Get Interrupt Active State
+ *
+ * @param   IRQn - Interrupt Numbers
+ *
+ * @return  1 - Interrupt Active
+ *          0 - Interrupt No Active
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE uint32_t NVIC_GetActive(IRQn_Type IRQn)
+{
+  return((uint32_t)((NVIC->IACTR[(uint32_t)(IRQn) >> 5] & (1 << ((uint32_t)(IRQn) & 0x1F)))?1:0));
+}
+
+/*********************************************************************
+ * @fn      NVIC_SetPriority
+ *
+ * @brief   Set Interrupt Priority
+ *
+ * @param   IRQn - Interrupt Numbers
+ *          priority - bit7 - Pre-emption Priority
+ *                     bit[6:5] - Subpriority
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint8_t priority)
+{
+  NVIC->IPRIOR[(uint32_t)(IRQn)] = priority;
+}
+
+/*********************************************************************
+ * @fn      __WFI
+ *
+ * @brief   Wait for Interrupt
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void __WFI(void)
+{
+  NVIC->SCTLR &= ~(1<<3);	// wfi
+  asm volatile ("wfi");
+}
+
+/*********************************************************************
+ * @fn      _SEV
+ *
+ * @brief   Set Event
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void _SEV(void)
+{
+  uint32_t t;
+
+  t = NVIC->SCTLR;
+  NVIC->SCTLR |= (1<<3)|(1<<5);
+  NVIC->SCTLR = (NVIC->SCTLR & ~(1<<5)) | ( t & (1<<5));
+}
+
+/*********************************************************************
+ * @fn      _WFE
+ *
+ * @brief   Wait for Events
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void _WFE(void)
+{
+  NVIC->SCTLR |= (1<<3);
+  asm volatile ("wfi");
+}
+
+/*********************************************************************
+ * @fn      __WFE
+ *
+ * @brief   Wait for Events
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void __WFE(void)
+{
+  _SEV();
+  _WFE();
+  _WFE();
+}
+
+/*********************************************************************
+ * @fn      SetVTFIRQ
+ *
+ * @brief   Set VTF Interrupt
+ *
+ * @param   addr - VTF interrupt service function base address.
+ *          IRQn - Interrupt Numbers
+ *          num - VTF Interrupt Numbers
+ *          NewState - DISABLE or ENABLE
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void SetVTFIRQ(uint32_t addr, IRQn_Type IRQn, uint8_t num, FunctionalState NewState){
+  if(num > 3)  return ;
+
+  if (NewState != DISABLE)
+  {
+      NVIC->VTFIDR[num] = IRQn;
+      NVIC->VTFADDR[num] = ((addr&0xFFFFFFFE)|0x1);
+  }
+  else{
+      NVIC->VTFIDR[num] = IRQn;
+      NVIC->VTFADDR[num] = ((addr&0xFFFFFFFE)&(~0x1));
+  }
+}
+
+/*********************************************************************
+ * @fn      NVIC_SystemReset
+ *
+ * @brief   Initiate a system reset request
+ *
+ * @return  none
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE void NVIC_SystemReset(void)
+{
+  NVIC->CFGR = NVIC_KEY3|(1<<7);
+}
+
+/*********************************************************************
+ * @fn      __AMOADD_W
+ *
+ * @brief   Atomic Add with 32bit value
+ *          Atomically ADD 32bit value with value in memory using amoadd.d.
+ *
+ * @param   addr - Address pointer to data, address need to be 4byte aligned
+ *          value - value to be ADDed
+ *
+ * @return  return memory value + add value
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE int32_t __AMOADD_W(volatile int32_t *addr, int32_t value)
+{
+    int32_t result;
+
+    __asm volatile ("amoadd.w %0, %2, %1" : \
+            "=r"(result), "+A"(*addr) : "r"(value) : "memory");
+    return *addr;
+}
+
+/*********************************************************************
+ * @fn      __AMOAND_W
+ *
+ * @brief   Atomic And with 32bit value
+ *          Atomically AND 32bit value with value in memory using amoand.d.
+ *
+ * @param   addr - Address pointer to data, address need to be 4byte aligned
+ *          value - value to be ANDed
+ *
+ * @return  return memory value & and value
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE int32_t __AMOAND_W(volatile int32_t *addr, int32_t value)
+{
+    int32_t result;
+
+    __asm volatile ("amoand.w %0, %2, %1" : \
+            "=r"(result), "+A"(*addr) : "r"(value) : "memory");
+    return *addr;
+}
+
+/*********************************************************************
+ * @fn      __AMOMAX_W
+ *
+ * @brief   Atomic signed MAX with 32bit value
+ *          Atomically signed max compare 32bit value with value in memory using amomax.d.
+ *
+ * @param   addr - Address pointer to data, address need to be 4byte aligned
+ *          value - value to be compared
+ *
+ * @return  the bigger value
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE int32_t __AMOMAX_W(volatile int32_t *addr, int32_t value)
+{
+    int32_t result;
+
+    __asm volatile ("amomax.w %0, %2, %1" : \
+            "=r"(result), "+A"(*addr) : "r"(value) : "memory");
+    return *addr;
+}
+
+/*********************************************************************
+ * @fn      __AMOMAXU_W
+ *
+ * @brief   Atomic unsigned MAX with 32bit value
+ *          Atomically unsigned max compare 32bit value with value in memory using amomaxu.d.
+ *
+ * @param   addr - Address pointer to data, address need to be 4byte aligned
+ *          value - value to be compared
+ *
+ * @return  return the bigger value
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE uint32_t __AMOMAXU_W(volatile uint32_t *addr, uint32_t value)
+{
+    uint32_t result;
+
+    __asm volatile ("amomaxu.w %0, %2, %1" : \
+            "=r"(result), "+A"(*addr) : "r"(value) : "memory");
+    return *addr;
+}
+
+/*********************************************************************
+ * @fn      __AMOMIN_W
+ *
+ * @brief   Atomic signed MIN with 32bit value
+ *          Atomically signed min compare 32bit value with value in memory using amomin.d.
+ *
+ * @param   addr - Address pointer to data, address need to be 4byte aligned
+ *          value - value to be compared
+ *
+ * @return  the smaller value
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE int32_t __AMOMIN_W(volatile int32_t *addr, int32_t value)
+{
+    int32_t result;
+
+    __asm volatile ("amomin.w %0, %2, %1" : \
+            "=r"(result), "+A"(*addr) : "r"(value) : "memory");
+    return *addr;
+}
+
+/*********************************************************************
+ * @fn      __AMOMINU_W
+ *
+ * @brief   Atomic unsigned MIN with 32bit value
+ *          Atomically unsigned min compare 32bit value with value in memory using amominu.d.
+ *
+ * @param   addr - Address pointer to data, address need to be 4byte aligned
+ *          value - value to be compared
+ *
+ * @return  the smaller value
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE uint32_t __AMOMINU_W(volatile uint32_t *addr, uint32_t value)
+{
+    uint32_t result;
+
+    __asm volatile ("amominu.w %0, %2, %1" : \
+            "=r"(result), "+A"(*addr) : "r"(value) : "memory");
+    return *addr;
+}
+
+/*********************************************************************
+ * @fn      __AMOOR_W
+ *
+ * @brief   Atomic OR with 32bit value
+ *          Atomically OR 32bit value with value in memory using amoor.d.
+ *
+ * @param   addr - Address pointer to data, address need to be 4byte aligned
+ *          value - value to be ORed
+ *
+ * @return  return memory value | and value
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE int32_t __AMOOR_W(volatile int32_t *addr, int32_t value)
+{
+    int32_t result;
+
+    __asm volatile ("amoor.w %0, %2, %1" : \
+            "=r"(result), "+A"(*addr) : "r"(value) : "memory");
+    return *addr;
+}
+
+/*********************************************************************
+ * @fn      __AMOSWAP_W
+ *
+ * @brief   Atomically swap new 32bit value into memory using amoswap.d.
+ *
+ * @param   addr - Address pointer to data, address need to be 4byte aligned
+ *          newval - New value to be stored into the address
+ *
+ * @return  return the original value in memory
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE uint32_t __AMOSWAP_W(volatile uint32_t *addr, uint32_t newval)
+{
+    uint32_t result;
+
+    __asm volatile ("amoswap.w %0, %2, %1" : \
+            "=r"(result), "+A"(*addr) : "r"(newval) : "memory");
+    return result;
+}
+
+/*********************************************************************
+ * @fn      __AMOXOR_W
+ *
+ * @brief   Atomic XOR with 32bit value
+ *          Atomically XOR 32bit value with value in memory using amoxor.d.
+ *
+ * @param   addr - Address pointer to data, address need to be 4byte aligned
+ *          value - value to be XORed
+ *
+ * @return  return memory value ^ and value
+ */
+__attribute__( ( always_inline ) ) RV_STATIC_INLINE int32_t __AMOXOR_W(volatile int32_t *addr, int32_t value)
+{
+    int32_t result;
+
+    __asm volatile ("amoxor.w %0, %2, %1" : \
+            "=r"(result), "+A"(*addr) : "r"(value) : "memory");
+    return *addr;
+}
+
+/* Core_Exported_Functions */
+extern uint32_t __get_MSTATUS(void);
+extern void __set_MSTATUS(uint32_t value);
+extern uint32_t __get_MISA(void);
+extern void __set_MISA(uint32_t value);
+extern uint32_t __get_MTVEC(void);
+extern void __set_MTVEC(uint32_t value);
+extern uint32_t __get_MSCRATCH(void);
+extern void __set_MSCRATCH(uint32_t value);
+extern uint32_t __get_MEPC(void);
+extern void __set_MEPC(uint32_t value);
+extern uint32_t __get_MCAUSE(void);
+extern void __set_MCAUSE(uint32_t value);
+extern uint32_t __get_MTVAL(void);
+extern void __set_MTVAL(uint32_t value);
+extern uint32_t __get_MVENDORID(void);
+extern uint32_t __get_MARCHID(void);
+extern uint32_t __get_MIMPID(void);
+extern uint32_t __get_MHARTID(void);
+extern uint32_t __get_SP(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/hw/bsp/ch32v20x/family.c b/hw/bsp/ch32v20x/family.c
new file mode 100644
index 000000000..be654e543
--- /dev/null
+++ b/hw/bsp/ch32v20x/family.c
@@ -0,0 +1,151 @@
+#include 
+#include "ch32v20x.h"
+
+#include "bsp/board_api.h"
+#include "board.h"
+
+/* CH32v203 depending on variants can support 2 USB IPs: FSDEV and USBFS.
+ * By default, we use FSDEV, but you can explicitly select by define:
+ * - CFG_TUD_WCH_USBIP_FSDEV
+ * - CFG_TUD_WCH_USBIP_USBFS
+ */
+
+// USBFS
+__attribute__((interrupt)) __attribute__((used))
+void USBHD_IRQHandler(void) {
+  #if CFG_TUD_WCH_USBIP_USBFS
+  tud_int_handler(0);
+  #endif
+}
+
+__attribute__((interrupt)) __attribute__((used))
+void USBHDWakeUp_IRQHandler(void) {
+  #if CFG_TUD_WCH_USBIP_USBFS
+  tud_int_handler(0);
+  #endif
+}
+
+// USBD (fsdev)
+__attribute__((interrupt)) __attribute__((used))
+void USB_LP_CAN1_RX0_IRQHandler(void) {
+  #if CFG_TUD_WCH_USBIP_FSDEV
+  tud_int_handler(0);
+  #endif
+}
+
+__attribute__((interrupt)) __attribute__((used))
+void USB_HP_CAN1_TX_IRQHandler(void) {
+  #if CFG_TUD_WCH_USBIP_FSDEV
+  tud_int_handler(0);
+  #endif
+
+}
+
+__attribute__((interrupt)) __attribute__((used))
+void USBWakeUp_IRQHandler(void) {
+  #if CFG_TUD_WCH_USBIP_FSDEV
+  tud_int_handler(0);
+  #endif
+}
+
+
+#if CFG_TUSB_OS == OPT_OS_NONE
+
+volatile uint32_t system_ticks = 0;
+
+__attribute__((interrupt))
+void SysTick_Handler(void) {
+  SysTick->SR = 0;
+  system_ticks++;
+}
+
+uint32_t SysTick_Config(uint32_t ticks) {
+  NVIC_EnableIRQ(SysTicK_IRQn);
+  SysTick->CTLR = 0;
+  SysTick->SR = 0;
+  SysTick->CNT = 0;
+  SysTick->CMP = ticks - 1;
+  SysTick->CTLR = 0xF;
+  return 0;
+}
+
+uint32_t board_millis(void) {
+  return system_ticks;
+}
+
+#endif
+
+void board_init(void) {
+  __disable_irq();
+
+#if CFG_TUSB_OS == OPT_OS_NONE
+  SysTick_Config(SystemCoreClock / 1000);
+#endif
+
+  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
+
+  uint8_t usb_div;
+  switch (SystemCoreClock) {
+    case 48000000: usb_div = RCC_USBCLKSource_PLLCLK_Div1; break;
+    case 96000000: usb_div = RCC_USBCLKSource_PLLCLK_Div2; break;
+    case 144000000: usb_div = RCC_USBCLKSource_PLLCLK_Div3; break;
+    default: TU_ASSERT(0,); break;
+  }
+  RCC_USBCLKConfig(usb_div);
+  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);  // FSDEV
+  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_OTG_FS, ENABLE); // USB FS
+
+  GPIO_InitTypeDef GPIO_InitStructure = {
+    .GPIO_Pin = LED_PIN,
+    .GPIO_Mode = GPIO_Mode_Out_OD,
+    .GPIO_Speed = GPIO_Speed_50MHz,
+  };
+  GPIO_Init(LED_PORT, &GPIO_InitStructure);
+
+  // UART TX is PA9
+  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
+  GPIO_InitTypeDef usart_init = {
+    .GPIO_Pin = GPIO_Pin_9,
+    .GPIO_Speed = GPIO_Speed_50MHz,
+    .GPIO_Mode = GPIO_Mode_AF_PP,
+  };
+  GPIO_Init(GPIOA, &usart_init);
+
+  USART_InitTypeDef usart = {
+    .USART_BaudRate = 115200,
+    .USART_WordLength = USART_WordLength_8b,
+    .USART_StopBits = USART_StopBits_1,
+    .USART_Parity = USART_Parity_No,
+    .USART_Mode = USART_Mode_Tx,
+    .USART_HardwareFlowControl = USART_HardwareFlowControl_None,
+  };
+  USART_Init(USART1, &usart);
+  USART_Cmd(USART1, ENABLE);
+
+  __enable_irq();
+  board_delay(2);
+}
+
+void board_led_write(bool state) {
+  GPIO_WriteBit(LED_PORT, LED_PIN, state);
+}
+
+uint32_t board_button_read(void) {
+  return false;
+}
+
+int board_uart_read(uint8_t *buf, int len) {
+  (void) buf;
+  (void) len;
+  return 0;
+}
+
+int board_uart_write(void const *buf, int len) {
+  const char *bufc = (const char *) buf;
+  for (int i = 0; i < len; i++) {
+    while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
+    USART_SendData(USART1, *bufc++);
+  }
+
+  return len;
+}
diff --git a/hw/bsp/ch32v20x/family.cmake b/hw/bsp/ch32v20x/family.cmake
new file mode 100644
index 000000000..3fb1c0e79
--- /dev/null
+++ b/hw/bsp/ch32v20x/family.cmake
@@ -0,0 +1,131 @@
+include_guard()
+
+set(CH32_FAMILY ch32v20x)
+set(SDK_DIR ${TOP}/hw/mcu/wch/${CH32_FAMILY})
+set(SDK_SRC_DIR ${SDK_DIR}/EVT/EXAM/SRC)
+
+# include board specific
+include(${CMAKE_CURRENT_LIST_DIR}/boards/${BOARD}/board.cmake)
+
+# toolchain set up
+set(CMAKE_SYSTEM_PROCESSOR rv32imac-ilp32 CACHE INTERNAL "System Processor")
+set(CMAKE_TOOLCHAIN_FILE ${TOP}/examples/build_system/cmake/toolchain/riscv_${TOOLCHAIN}.cmake)
+
+set(FAMILY_MCUS CH32V20X CACHE INTERNAL "")
+set(OPENOCD_OPTION "-f ${CMAKE_CURRENT_LIST_DIR}/wch-riscv.cfg")
+
+# Port0 use FSDev, Port1 use USBFS
+if (NOT DEFINED PORT)
+  set(PORT 0)
+endif()
+
+#------------------------------------
+# BOARD_TARGET
+#------------------------------------
+# only need to be built ONCE for all examples
+function(add_board_target BOARD_TARGET)
+  if (TARGET ${BOARD_TARGET})
+    return()
+  endif()
+
+  if (NOT DEFINED LD_FILE_GNU)
+    set(LD_FILE_GNU ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/linker/${CH32_FAMILY}.ld)
+  endif ()
+  set(LD_FILE_Clang ${LD_FILE_GNU})
+
+  if (NOT DEFINED STARTUP_FILE_GNU)
+    set(STARTUP_FILE_GNU ${SDK_SRC_DIR}/Startup/startup_${CH32_FAMILY}_${MCU_VARIANT}.S)
+  endif ()
+  set(STARTUP_FILE_Clang ${STARTUP_FILE_GNU})
+
+  add_library(${BOARD_TARGET} STATIC
+    ${SDK_SRC_DIR}/Core/core_riscv.c
+    ${SDK_SRC_DIR}/Peripheral/src/${CH32_FAMILY}_gpio.c
+    ${SDK_SRC_DIR}/Peripheral/src/${CH32_FAMILY}_misc.c
+    ${SDK_SRC_DIR}/Peripheral/src/${CH32_FAMILY}_rcc.c
+    ${SDK_SRC_DIR}/Peripheral/src/${CH32_FAMILY}_usart.c
+    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/system_${CH32_FAMILY}.c
+    ${STARTUP_FILE_${CMAKE_C_COMPILER_ID}}
+    )
+  target_include_directories(${BOARD_TARGET} PUBLIC
+    ${SDK_SRC_DIR}/Peripheral/inc
+    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}
+    )
+  target_compile_definitions(${BOARD_TARGET} PUBLIC
+    CH32V20x_${MCU_VARIANT}
+    )
+
+  if (PORT EQUAL 0)
+    target_compile_definitions(${BOARD_TARGET} PUBLIC
+      CFG_TUD_WCH_USBIP_FSDEV=1
+      )
+  elseif (PORT EQUAL 1)
+    target_compile_definitions(${BOARD_TARGET} PUBLIC
+      CFG_TUD_WCH_USBIP_USBFS=1
+      )
+  else()
+    message(FATAL_ERROR "Invalid PORT ${PORT}")
+  endif()
+
+  update_board(${BOARD_TARGET})
+
+  if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
+    target_compile_options(${BOARD_TARGET} PUBLIC
+      -mcmodel=medany
+      )
+    target_link_options(${BOARD_TARGET} PUBLIC
+      "LINKER:--script=${LD_FILE_GNU}"
+      -Wl,--defsym=__flash_size=${LD_FLASH_SIZE}
+      -Wl,--defsym=__ram_size=${LD_RAM_SIZE}
+      -nostartfiles
+      --specs=nosys.specs --specs=nano.specs
+      )
+  elseif (CMAKE_C_COMPILER_ID STREQUAL "Clang")
+    message(FATAL_ERROR "Clang is not supported for MSP432E4")
+  elseif (CMAKE_C_COMPILER_ID STREQUAL "IAR")
+    target_link_options(${BOARD_TARGET} PUBLIC
+      "LINKER:--config=${LD_FILE_IAR}"
+      )
+  endif ()
+endfunction()
+
+
+#------------------------------------
+# Functions
+#------------------------------------
+function(family_configure_example TARGET RTOS)
+  family_configure_common(${TARGET} ${RTOS})
+
+  # Board target
+  add_board_target(board_${BOARD})
+
+  #---------- Port Specific ----------
+  # These files are built for each example since it depends on example's tusb_config.h
+  target_sources(${TARGET} PUBLIC
+    # BSP
+    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/family.c
+    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../board.c
+    )
+  target_include_directories(${TARGET} PUBLIC
+    # family, hw, board
+    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}
+    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../
+    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/boards/${BOARD}
+    )
+
+  # Add TinyUSB target and port source
+  family_add_tinyusb(${TARGET} OPT_MCU_CH32V20X ${RTOS})
+
+  target_sources(${TARGET}-tinyusb PUBLIC
+    ${TOP}/src/portable/wch/dcd_ch32_usbfs.c
+    ${TOP}/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
+    )
+  target_link_libraries(${TARGET}-tinyusb PUBLIC board_${BOARD})
+
+  # Link dependencies
+  target_link_libraries(${TARGET} PUBLIC board_${BOARD} ${TARGET}-tinyusb)
+
+  # Flashing
+  family_add_bin_hex(${TARGET})
+  family_flash_openocd_wch(${TARGET})
+endfunction()
diff --git a/hw/bsp/ch32v20x/family.mk b/hw/bsp/ch32v20x/family.mk
new file mode 100644
index 000000000..49d4d2feb
--- /dev/null
+++ b/hw/bsp/ch32v20x/family.mk
@@ -0,0 +1,59 @@
+# https://www.embecosm.com/resources/tool-chain-downloads/#riscv-stable
+#CROSS_COMPILE ?= riscv32-unknown-elf-
+
+# Toolchain from https://nucleisys.com/download.php
+#CROSS_COMPILE ?= riscv-nuclei-elf-
+
+# Toolchain from https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack
+CROSS_COMPILE ?= riscv-none-elf-
+
+CH32_FAMILY = ch32v20x
+SDK_DIR = hw/mcu/wch/ch32v20x
+SDK_SRC_DIR = $(SDK_DIR)/EVT/EXAM/SRC
+
+include $(TOP)/$(BOARD_PATH)/board.mk
+CPU_CORE ?= rv32imac-ilp32
+
+# Port0 use FSDev, Port1 use USBFS
+PORT ?= 0
+
+CFLAGS += \
+	-mcmodel=medany \
+	-ffat-lto-objects \
+	-flto \
+	-DCH32V20x_${MCU_VARIANT} \
+	-DCFG_TUSB_MCU=OPT_MCU_CH32V20X
+
+ifeq ($(PORT),0)
+  $(info "Using FSDEV driver")
+  CFLAGS += -DCFG_TUD_WCH_USBIP_FSDEV=1
+else
+  $(info "Using USBFS driver")
+  CFLAGS += -DCFG_TUD_WCH_USBIP_USBFS=1
+endif
+
+LDFLAGS_GCC += \
+	-nostdlib -nostartfiles \
+	--specs=nosys.specs --specs=nano.specs \
+
+LD_FILE = $(FAMILY_PATH)/linker/${CH32_FAMILY}.ld
+
+SRC_C += \
+	src/portable/wch/dcd_ch32_usbfs.c \
+	src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c \
+	$(SDK_SRC_DIR)/Core/core_riscv.c \
+	$(SDK_SRC_DIR)/Peripheral/src/ch32v20x_gpio.c \
+	$(SDK_SRC_DIR)/Peripheral/src/ch32v20x_misc.c \
+	$(SDK_SRC_DIR)/Peripheral/src/ch32v20x_rcc.c \
+	$(SDK_SRC_DIR)/Peripheral/src/ch32v20x_usart.c \
+
+SRC_S += $(SDK_SRC_DIR)/Startup/startup_ch32v20x_${MCU_VARIANT}.S
+
+INC += \
+	$(TOP)/$(BOARD_PATH) \
+	$(TOP)/$(SDK_SRC_DIR)/Peripheral/inc \
+
+FREERTOS_PORTABLE_SRC = $(FREERTOS_PORTABLE_PATH)/RISC-V
+
+OPENOCD_WCH_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg
+flash: flash-openocd-wch
diff --git a/hw/bsp/ch32v20x/linker/ch32v20x.ld b/hw/bsp/ch32v20x/linker/ch32v20x.ld
new file mode 100644
index 000000000..cd5c8dc17
--- /dev/null
+++ b/hw/bsp/ch32v20x/linker/ch32v20x.ld
@@ -0,0 +1,165 @@
+/* Define default values if not already defined */
+__FLASH_SIZE = DEFINED(__flash_size) ? __flash_size : 64K;
+__RAM_SIZE = DEFINED(__ram_size) ? __ram_size : 20K;
+
+MEMORY
+{
+	FLASH (rx) : ORIGIN = 0x00000000, LENGTH = __FLASH_SIZE
+	RAM (xrw) : ORIGIN = 0x20000000, LENGTH = __RAM_SIZE
+}
+
+ENTRY( _start )
+
+__stack_size = 2048;
+
+PROVIDE( _stack_size = __stack_size );
+
+SECTIONS
+{
+	.init :
+	{
+		_sinit = .;
+		. = ALIGN(4);
+		KEEP(*(SORT_NONE(.init)))
+		. = ALIGN(4);
+		_einit = .;
+	} >FLASH AT>FLASH
+
+  .vector :
+  {
+      *(.vector);
+	  . = ALIGN(64);
+  } >FLASH AT>FLASH
+
+	.text :
+	{
+		. = ALIGN(4);
+		*(.text)
+		*(.text.*)
+		*(.rodata)
+		*(.rodata*)
+		*(.gnu.linkonce.t.*)
+		. = ALIGN(4);
+	} >FLASH AT>FLASH
+
+	.fini :
+	{
+		KEEP(*(SORT_NONE(.fini)))
+		. = ALIGN(4);
+	} >FLASH AT>FLASH
+
+	PROVIDE( _etext = . );
+	PROVIDE( _eitcm = . );
+
+	.preinit_array  :
+	{
+	  PROVIDE_HIDDEN (__preinit_array_start = .);
+	  KEEP (*(.preinit_array))
+	  PROVIDE_HIDDEN (__preinit_array_end = .);
+	} >FLASH AT>FLASH
+
+	.init_array     :
+	{
+	  PROVIDE_HIDDEN (__init_array_start = .);
+	  KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
+	  KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
+	  PROVIDE_HIDDEN (__init_array_end = .);
+	} >FLASH AT>FLASH
+
+	.fini_array     :
+	{
+	  PROVIDE_HIDDEN (__fini_array_start = .);
+	  KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
+	  KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
+	  PROVIDE_HIDDEN (__fini_array_end = .);
+	} >FLASH AT>FLASH
+
+	.ctors          :
+	{
+	  /* gcc uses crtbegin.o to find the start of
+	     the constructors, so we make sure it is
+	     first.  Because this is a wildcard, it
+	     doesn't matter if the user does not
+	     actually link against crtbegin.o; the
+	     linker won't look for a file to match a
+	     wildcard.  The wildcard also means that it
+	     doesn't matter which directory crtbegin.o
+	     is in.  */
+	  KEEP (*crtbegin.o(.ctors))
+	  KEEP (*crtbegin?.o(.ctors))
+	  /* We don't want to include the .ctor section from
+	     the crtend.o file until after the sorted ctors.
+	     The .ctor section from the crtend file contains the
+	     end of ctors marker and it must be last */
+	  KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
+	  KEEP (*(SORT(.ctors.*)))
+	  KEEP (*(.ctors))
+	} >FLASH AT>FLASH
+
+	.dtors          :
+	{
+	  KEEP (*crtbegin.o(.dtors))
+	  KEEP (*crtbegin?.o(.dtors))
+	  KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
+	  KEEP (*(SORT(.dtors.*)))
+	  KEEP (*(.dtors))
+	} >FLASH AT>FLASH
+
+	.dalign :
+	{
+		. = ALIGN(4);
+		PROVIDE(_data_vma = .);
+	} >RAM AT>FLASH
+
+	.dlalign :
+	{
+		. = ALIGN(4);
+		PROVIDE(_data_lma = .);
+	} >FLASH AT>FLASH
+
+	.data :
+	{
+    	*(.gnu.linkonce.r.*)
+    	*(.data .data.*)
+    	*(.gnu.linkonce.d.*)
+		. = ALIGN(8);
+    	PROVIDE( __global_pointer$ = . + 0x800 );
+    	*(.sdata .sdata.*)
+		*(.sdata2.*)
+    	*(.gnu.linkonce.s.*)
+    	. = ALIGN(8);
+    	*(.srodata.cst16)
+    	*(.srodata.cst8)
+    	*(.srodata.cst4)
+    	*(.srodata.cst2)
+    	*(.srodata .srodata.*)
+    	. = ALIGN(4);
+		PROVIDE( _edata = .);
+	} >RAM AT>FLASH
+
+	.bss :
+	{
+		. = ALIGN(4);
+		PROVIDE( _sbss = .);
+  	    *(.sbss*)
+        *(.gnu.linkonce.sb.*)
+		*(.bss*)
+     	*(.gnu.linkonce.b.*)
+		*(COMMON*)
+		. = ALIGN(4);
+		PROVIDE( _ebss = .);
+	} >RAM AT>FLASH
+
+	PROVIDE( _end = _ebss);
+	PROVIDE( end = . );
+
+    .stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
+    {
+        PROVIDE( _heap_end = . );
+        . = ALIGN(4);
+        PROVIDE(_susrstack = . );
+        . = . + __stack_size;
+        PROVIDE( _eusrstack = .);
+    } >RAM
+
+}
diff --git a/hw/bsp/ch32v20x/system_ch32v20x.c b/hw/bsp/ch32v20x/system_ch32v20x.c
new file mode 100644
index 000000000..bdc0d498f
--- /dev/null
+++ b/hw/bsp/ch32v20x/system_ch32v20x.c
@@ -0,0 +1,976 @@
+/********************************** (C) COPYRIGHT *******************************
+ * File Name          : system_ch32v20x.c
+ * Author             : WCH
+ * Version            : V1.0.0
+ * Date               : 2021/06/06
+ * Description        : CH32V20x Device Peripheral Access Layer System Source File.
+ *                      For HSE = 32Mhz (CH32V208x/CH32V203RBT6)
+ *                      For HSE = 8Mhz (other CH32V203x)
+*********************************************************************************
+* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
+* Attention: This software (modified or not) and binary are used for
+* microcontroller manufactured by Nanjing Qinheng Microelectronics.
+*******************************************************************************/
+#include "ch32v20x.h"
+
+/*
+* Uncomment the line corresponding to the desired System clock (SYSCLK) frequency (after
+* reset the HSI is used as SYSCLK source).
+* If none of the define below is enabled, the HSI is used as System clock source.
+*/
+//#define SYSCLK_FREQ_HSE    HSE_VALUE
+//#define SYSCLK_FREQ_48MHz_HSE  48000000
+//#define SYSCLK_FREQ_56MHz_HSE  56000000
+//#define SYSCLK_FREQ_72MHz_HSE  72000000
+// #define SYSCLK_FREQ_96MHz_HSE  96000000
+//#define SYSCLK_FREQ_120MHz_HSE  120000000
+#define SYSCLK_FREQ_144MHz_HSE  144000000
+//#define SYSCLK_FREQ_HSI    HSI_VALUE
+//#define SYSCLK_FREQ_48MHz_HSI  48000000
+//#define SYSCLK_FREQ_56MHz_HSI  56000000
+//#define SYSCLK_FREQ_72MHz_HSI  72000000
+//#define SYSCLK_FREQ_96MHz_HSI  96000000
+//#define SYSCLK_FREQ_120MHz_HSI  120000000
+//#define SYSCLK_FREQ_144MHz_HSI  144000000
+
+/* Clock Definitions */
+#ifdef SYSCLK_FREQ_HSE
+uint32_t SystemCoreClock         = SYSCLK_FREQ_HSE;              /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_48MHz_HSE
+uint32_t SystemCoreClock         = SYSCLK_FREQ_48MHz_HSE;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_56MHz_HSE
+uint32_t SystemCoreClock         = SYSCLK_FREQ_56MHz_HSE;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_72MHz_HSE
+uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz_HSE;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_96MHz_HSE
+uint32_t SystemCoreClock         = SYSCLK_FREQ_96MHz_HSE;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_120MHz_HSE
+uint32_t SystemCoreClock         = SYSCLK_FREQ_120MHz_HSE;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_144MHz_HSE
+uint32_t SystemCoreClock         = SYSCLK_FREQ_144MHz_HSE;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_48MHz_HSI
+uint32_t SystemCoreClock         = SYSCLK_FREQ_48MHz_HSI;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_56MHz_HSI
+uint32_t SystemCoreClock         = SYSCLK_FREQ_56MHz_HSI;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_72MHz_HSI
+uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz_HSI;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_96MHz_HSI
+uint32_t SystemCoreClock         = SYSCLK_FREQ_96MHz_HSI;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_120MHz_HSI
+uint32_t SystemCoreClock         = SYSCLK_FREQ_120MHz_HSI;        /* System Clock Frequency (Core Clock) */
+#elif defined SYSCLK_FREQ_144MHz_HSI
+uint32_t SystemCoreClock         = SYSCLK_FREQ_144MHz_HSI;        /* System Clock Frequency (Core Clock) */
+#else
+uint32_t SystemCoreClock         = HSI_VALUE;                    /* System Clock Frequency (Core Clock) */
+
+#endif
+
+__I uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};
+
+/* system_private_function_proto_types */
+static void SetSysClock(void);
+
+#ifdef SYSCLK_FREQ_HSE
+static void SetSysClockToHSE( void );
+#elif defined SYSCLK_FREQ_48MHz_HSE
+static void SetSysClockTo48_HSE( void );
+#elif defined SYSCLK_FREQ_56MHz_HSE
+static void SetSysClockTo56_HSE( void );
+#elif defined SYSCLK_FREQ_72MHz_HSE
+static void SetSysClockTo72_HSE( void );
+#elif defined SYSCLK_FREQ_96MHz_HSE
+static void SetSysClockTo96_HSE( void );
+#elif defined SYSCLK_FREQ_120MHz_HSE
+static void SetSysClockTo120_HSE( void );
+#elif defined SYSCLK_FREQ_144MHz_HSE
+static void SetSysClockTo144_HSE( void );
+#elif defined SYSCLK_FREQ_48MHz_HSI
+static void SetSysClockTo48_HSI( void );
+#elif defined SYSCLK_FREQ_56MHz_HSI
+static void SetSysClockTo56_HSI( void );
+#elif defined SYSCLK_FREQ_72MHz_HSI
+static void SetSysClockTo72_HSI( void );
+#elif defined SYSCLK_FREQ_96MHz_HSI
+static void SetSysClockTo96_HSI( void );
+#elif defined SYSCLK_FREQ_120MHz_HSI
+static void SetSysClockTo120_HSI( void );
+#elif defined SYSCLK_FREQ_144MHz_HSI
+static void SetSysClockTo144_HSI( void );
+
+#endif
+
+/*********************************************************************
+ * @fn      SystemInit
+ *
+ * @brief   Setup the microcontroller system Initialize the Embedded Flash Interface,
+ *        the PLL and update the SystemCoreClock variable.
+ *
+ * @return  none
+ */
+void SystemInit (void)
+{
+  RCC->CTLR |= (uint32_t)0x00000001;
+  RCC->CFGR0 &= (uint32_t)0xF8FF0000;
+  RCC->CTLR &= (uint32_t)0xFEF6FFFF;
+  RCC->CTLR &= (uint32_t)0xFFFBFFFF;
+  RCC->CFGR0 &= (uint32_t)0xFF80FFFF;
+  RCC->INTR = 0x009F0000;
+  SetSysClock();
+}
+
+/*********************************************************************
+ * @fn      SystemCoreClockUpdate
+ *
+ * @brief   Update SystemCoreClock variable according to Clock Register Values.
+ *
+ * @return  none
+ */
+void SystemCoreClockUpdate (void)
+{
+  uint32_t tmp = 0, pllmull = 0, pllsource = 0, Pll_6_5 = 0;
+
+  tmp = RCC->CFGR0 & RCC_SWS;
+
+  switch (tmp)
+  {
+    case 0x00:
+      SystemCoreClock = HSI_VALUE;
+      break;
+    case 0x04:
+      SystemCoreClock = HSE_VALUE;
+      break;
+    case 0x08:
+      pllmull = RCC->CFGR0 & RCC_PLLMULL;
+      pllsource = RCC->CFGR0 & RCC_PLLSRC;
+      pllmull = ( pllmull >> 18) + 2;
+
+      if(pllmull == 17) pllmull = 18;
+
+      if (pllsource == 0x00)
+      {
+          if(EXTEN->EXTEN_CTR & EXTEN_PLL_HSI_PRE){
+              SystemCoreClock = HSI_VALUE * pllmull;
+          }
+          else{
+              SystemCoreClock = (HSI_VALUE >> 1) * pllmull;
+          }
+      }
+      else
+      {
+#if defined (CH32V20x_D8W)
+        if((RCC->CFGR0 & (3<<22)) == (3<<22))
+        {
+          SystemCoreClock = ((HSE_VALUE>>1)) * pllmull;
+        }
+        else
+#endif
+        if ((RCC->CFGR0 & RCC_PLLXTPRE) != (uint32_t)RESET)
+        {
+#if defined (CH32V20x_D8) || defined (CH32V20x_D8W)
+          SystemCoreClock = ((HSE_VALUE>>2) >> 1) * pllmull;
+#else
+          SystemCoreClock = (HSE_VALUE >> 1) * pllmull;
+#endif
+        }
+        else
+        {
+#if defined (CH32V20x_D8) || defined (CH32V20x_D8W)
+            SystemCoreClock = (HSE_VALUE>>2) * pllmull;
+#else
+          SystemCoreClock = HSE_VALUE * pllmull;
+#endif
+        }
+      }
+
+      if(Pll_6_5 == 1) SystemCoreClock = (SystemCoreClock / 2);
+
+      break;
+    default:
+      SystemCoreClock = HSI_VALUE;
+      break;
+  }
+
+  tmp = AHBPrescTable[((RCC->CFGR0 & RCC_HPRE) >> 4)];
+  SystemCoreClock >>= tmp;
+}
+
+/*********************************************************************
+ * @fn      SetSysClock
+ *
+ * @brief   Configures the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClock(void)
+{
+#ifdef SYSCLK_FREQ_HSE
+    SetSysClockToHSE();
+#elif defined SYSCLK_FREQ_48MHz_HSE
+    SetSysClockTo48_HSE();
+#elif defined SYSCLK_FREQ_56MHz_HSE
+    SetSysClockTo56_HSE();
+#elif defined SYSCLK_FREQ_72MHz_HSE
+    SetSysClockTo72_HSE();
+#elif defined SYSCLK_FREQ_96MHz_HSE
+    SetSysClockTo96_HSE();
+#elif defined SYSCLK_FREQ_120MHz_HSE
+    SetSysClockTo120_HSE();
+#elif defined SYSCLK_FREQ_144MHz_HSE
+    SetSysClockTo144_HSE();
+#elif defined SYSCLK_FREQ_48MHz_HSI
+    SetSysClockTo48_HSI();
+#elif defined SYSCLK_FREQ_56MHz_HSI
+    SetSysClockTo56_HSI();
+#elif defined SYSCLK_FREQ_72MHz_HSI
+    SetSysClockTo72_HSI();
+#elif defined SYSCLK_FREQ_96MHz_HSI
+    SetSysClockTo96_HSI();
+#elif defined SYSCLK_FREQ_120MHz_HSI
+    SetSysClockTo120_HSI();
+#elif defined SYSCLK_FREQ_144MHz_HSI
+    SetSysClockTo144_HSI();
+
+#endif
+
+ /* If none of the define above is enabled, the HSI is used as System clock
+  * source (default after reset)
+	*/
+}
+
+#ifdef SYSCLK_FREQ_HSE
+
+/*********************************************************************
+ * @fn      SetSysClockToHSE
+ *
+ * @brief   Sets HSE as System clock source and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockToHSE(void)
+{
+  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+  RCC->CTLR |= ((uint32_t)RCC_HSEON);
+
+  /* Wait till HSE is ready and if Time out is reached exit */
+  do
+  {
+    HSEStatus = RCC->CTLR & RCC_HSERDY;
+    StartUpCounter++;
+  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+  if ((RCC->CTLR & RCC_HSERDY) != RESET)
+  {
+    HSEStatus = (uint32_t)0x01;
+  }
+  else
+  {
+    HSEStatus = (uint32_t)0x00;
+  }
+
+  if (HSEStatus == (uint32_t)0x01)
+  {
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV1;
+
+    /* Select HSE as system clock source
+     *  CH32V20x_D6 (HSE=8MHZ)
+     *  CH32V20x_D8 (HSE=32MHZ)
+     *  CH32V20x_D8W (HSE=32MHZ)
+     */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_HSE;
+
+    /* Wait till HSE is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x04)
+    {
+    }
+  }
+  else
+  {
+		/* If HSE fails to start-up, the application will have wrong clock
+     * configuration. User can add here some code to deal with this error
+		 */
+  }
+}
+
+#elif defined SYSCLK_FREQ_48MHz_HSE
+
+/*********************************************************************
+ * @fn      SetSysClockTo48_HSE
+ *
+ * @brief   Sets System clock frequency to 48MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo48_HSE(void)
+{
+  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+  RCC->CTLR |= ((uint32_t)RCC_HSEON);
+  /* Wait till HSE is ready and if Time out is reached exit */
+  do
+  {
+    HSEStatus = RCC->CTLR & RCC_HSERDY;
+    StartUpCounter++;
+  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+  if ((RCC->CTLR & RCC_HSERDY) != RESET)
+  {
+    HSEStatus = (uint32_t)0x01;
+  }
+  else
+  {
+    HSEStatus = (uint32_t)0x00;
+  }
+
+  if (HSEStatus == (uint32_t)0x01)
+  {
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  CH32V20x_D6-PLL configuration: PLLCLK = HSE * 6 = 48 MHz (HSE=8MHZ)
+     *  CH32V20x_D8-PLL configuration: PLLCLK = HSE/4 * 6 = 48 MHz (HSE=32MHZ)
+     *  CH32V20x_D8W-PLL configuration: PLLCLK = HSE/4 * 6 = 48 MHz (HSE=32MHZ)
+     */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE | RCC_PLLMULL));
+
+     RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE | RCC_PLLXTPRE_HSE | RCC_PLLMULL6);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+  }
+  else
+  {
+		/*
+		 * If HSE fails to start-up, the application will have wrong clock
+     * configuration. User can add here some code to deal with this error
+		 */
+  }
+}
+
+#elif defined SYSCLK_FREQ_56MHz_HSE
+
+/*********************************************************************
+ * @fn      SetSysClockTo56_HSE
+ *
+ * @brief   Sets System clock frequency to 56MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo56_HSE(void)
+{
+  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+  RCC->CTLR |= ((uint32_t)RCC_HSEON);
+
+  /* Wait till HSE is ready and if Time out is reached exit */
+  do
+  {
+    HSEStatus = RCC->CTLR & RCC_HSERDY;
+    StartUpCounter++;
+  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+  if ((RCC->CTLR & RCC_HSERDY) != RESET)
+  {
+    HSEStatus = (uint32_t)0x01;
+  }
+  else
+  {
+    HSEStatus = (uint32_t)0x00;
+  }
+
+  if (HSEStatus == (uint32_t)0x01)
+  {
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  CH32V20x_D6-PLL configuration: PLLCLK = HSE * 7 = 56 MHz (HSE=8MHZ)
+     *  CH32V20x_D8-PLL configuration: PLLCLK = HSE/4 * 7 = 56 MHz (HSE=32MHZ)
+     *  CH32V20x_D8W-PLL configuration: PLLCLK = HSE/4 * 7 = 56 MHz (HSE=32MHZ)
+     */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE | RCC_PLLMULL));
+
+    RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE | RCC_PLLXTPRE_HSE | RCC_PLLMULL7);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+  }
+  else
+  {
+		/*
+		 * If HSE fails to start-up, the application will have wrong clock
+     * configuration. User can add here some code to deal with this error
+		 */
+  }
+}
+
+#elif defined SYSCLK_FREQ_72MHz_HSE
+
+/*********************************************************************
+ * @fn      SetSysClockTo72_HSE
+ *
+ * @brief   Sets System clock frequency to 72MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo72_HSE(void)
+{
+  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+  RCC->CTLR |= ((uint32_t)RCC_HSEON);
+
+  /* Wait till HSE is ready and if Time out is reached exit */
+  do
+  {
+    HSEStatus = RCC->CTLR & RCC_HSERDY;
+    StartUpCounter++;
+  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+  if ((RCC->CTLR & RCC_HSERDY) != RESET)
+  {
+    HSEStatus = (uint32_t)0x01;
+  }
+  else
+  {
+    HSEStatus = (uint32_t)0x00;
+  }
+
+  if (HSEStatus == (uint32_t)0x01)
+  {
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  CH32V20x_D6-PLL configuration: PLLCLK = HSE * 9 = 72 MHz (HSE=8MHZ)
+     *  CH32V20x_D8-PLL configuration: PLLCLK = HSE/4 * 9 = 72 MHz (HSE=32MHZ)
+     *  CH32V20x_D8W-PLL configuration: PLLCLK = HSE/4 * 9 = 72 MHz (HSE=32MHZ)
+     */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE |
+                                        RCC_PLLMULL));
+
+    RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE | RCC_PLLXTPRE_HSE | RCC_PLLMULL9);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+  }
+  else
+  {
+		/*
+		 * If HSE fails to start-up, the application will have wrong clock
+     * configuration. User can add here some code to deal with this error
+		 */
+  }
+}
+
+#elif defined SYSCLK_FREQ_96MHz_HSE
+
+/*********************************************************************
+ * @fn      SetSysClockTo96_HSE
+ *
+ * @brief   Sets System clock frequency to 96MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo96_HSE(void)
+{
+  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+  RCC->CTLR |= ((uint32_t)RCC_HSEON);
+
+  /* Wait till HSE is ready and if Time out is reached exit */
+  do
+  {
+    HSEStatus = RCC->CTLR & RCC_HSERDY;
+    StartUpCounter++;
+  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+  if ((RCC->CTLR & RCC_HSERDY) != RESET)
+  {
+    HSEStatus = (uint32_t)0x01;
+  }
+  else
+  {
+    HSEStatus = (uint32_t)0x00;
+  }
+
+  if (HSEStatus == (uint32_t)0x01)
+  {
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  CH32V20x_D6-PLL configuration: PLLCLK = HSE * 12 = 96 MHz (HSE=8MHZ)
+     *  CH32V20x_D8-PLL configuration: PLLCLK = HSE/4 * 12 = 96 MHz (HSE=32MHZ)
+     *  CH32V20x_D8W-PLL configuration: PLLCLK = HSE/4 * 12 = 96 MHz (HSE=32MHZ)
+     */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE |
+                                        RCC_PLLMULL));
+
+    RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE | RCC_PLLXTPRE_HSE | RCC_PLLMULL12);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+  }
+  else
+  {
+        /*
+         * If HSE fails to start-up, the application will have wrong clock
+     * configuration. User can add here some code to deal with this error
+         */
+  }
+}
+
+#elif defined SYSCLK_FREQ_120MHz_HSE
+
+/*********************************************************************
+ * @fn      SetSysClockTo120_HSE
+ *
+ * @brief   Sets System clock frequency to 120MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo120_HSE(void)
+{
+    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+    RCC->CTLR |= ((uint32_t)RCC_HSEON);
+
+    /* Wait till HSE is ready and if Time out is reached exit */
+    do
+    {
+        HSEStatus = RCC->CTLR & RCC_HSERDY;
+        StartUpCounter++;
+    } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+    if((RCC->CTLR & RCC_HSERDY) != RESET)
+    {
+        HSEStatus = (uint32_t)0x01;
+    }
+    else
+    {
+        HSEStatus = (uint32_t)0x00;
+    }
+
+    if(HSEStatus == (uint32_t)0x01)
+    {
+#if defined (CH32V20x_D8W)
+        RCC->CFGR0 |= (uint32_t)(3<<22);
+        /* HCLK = SYSCLK/2 */
+        RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV2;
+#else
+        /* HCLK = SYSCLK */
+        RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+#endif
+        /* PCLK2 = HCLK */
+        RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+        /* PCLK1 = HCLK */
+        RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+        /*  CH32V20x_D6-PLL configuration: PLLCLK = HSE * 15 = 120 MHz (HSE=8MHZ)
+         *  CH32V20x_D8-PLL configuration: PLLCLK = HSE/4 * 15 = 120 MHz (HSE=32MHZ)
+         *  CH32V20x_D8W-PLL configuration: PLLCLK = HSE/2 * 15 = 240 MHz (HSE=32MHZ)
+         */
+        RCC->CFGR0 &= (uint32_t)((uint32_t) ~(RCC_PLLSRC | RCC_PLLXTPRE |
+                                              RCC_PLLMULL));
+
+        RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE | RCC_PLLXTPRE_HSE | RCC_PLLMULL15);
+
+        /* Enable PLL */
+        RCC->CTLR |= RCC_PLLON;
+        /* Wait till PLL is ready */
+        while((RCC->CTLR & RCC_PLLRDY) == 0)
+        {
+        }
+        /* Select PLL as system clock source */
+        RCC->CFGR0 &= (uint32_t)((uint32_t) ~(RCC_SW));
+        RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+        /* Wait till PLL is used as system clock source */
+        while((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+        {
+        }
+    }
+    else
+    {
+        /*
+         * If HSE fails to start-up, the application will have wrong clock
+         * configuration. User can add here some code to deal with this error
+         */
+    }
+}
+#elif defined SYSCLK_FREQ_144MHz_HSE
+
+/*********************************************************************
+ * @fn      SetSysClockTo144_HSE
+ *
+ * @brief   Sets System clock frequency to 144MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo144_HSE(void)
+{
+  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+  RCC->CTLR |= ((uint32_t)RCC_HSEON);
+
+  /* Wait till HSE is ready and if Time out is reached exit */
+  do
+  {
+    HSEStatus = RCC->CTLR & RCC_HSERDY;
+    StartUpCounter++;
+  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+  if ((RCC->CTLR & RCC_HSERDY) != RESET)
+  {
+    HSEStatus = (uint32_t)0x01;
+  }
+  else
+  {
+    HSEStatus = (uint32_t)0x00;
+  }
+
+  if (HSEStatus == (uint32_t)0x01)
+  {
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  CH32V20x_D6-PLL configuration: PLLCLK = HSE * 18 = 144 MHz (HSE=8MHZ)
+     *  CH32V20x_D8-PLL configuration: PLLCLK = HSE/4 * 18 = 144 MHz (HSE=32MHZ)
+     *  CH32V20x_D8W-PLL configuration: PLLCLK = HSE/4 * 18 = 144 MHz (HSE=32MHZ)
+     */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE |
+                                        RCC_PLLMULL));
+
+    RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE | RCC_PLLXTPRE_HSE | RCC_PLLMULL18);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+  }
+  else
+  {
+        /*
+         * If HSE fails to start-up, the application will have wrong clock
+     * configuration. User can add here some code to deal with this error
+         */
+  }
+}
+
+#elif defined SYSCLK_FREQ_48MHz_HSI
+
+/*********************************************************************
+ * @fn      SetSysClockTo48_HSI
+ *
+ * @brief   Sets System clock frequency to 48MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo48_HSI(void)
+{
+    EXTEN->EXTEN_CTR |= EXTEN_PLL_HSI_PRE;
+
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  PLL configuration: PLLCLK = HSI * 6 = 48 MHz */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE | RCC_PLLMULL));
+
+     RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSI_Div2 | RCC_PLLMULL6);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+}
+
+#elif defined SYSCLK_FREQ_56MHz_HSI
+
+/*********************************************************************
+ * @fn      SetSysClockTo56_HSI
+ *
+ * @brief   Sets System clock frequency to 56MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo56_HSI(void)
+{
+    EXTEN->EXTEN_CTR |= EXTEN_PLL_HSI_PRE;
+
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  PLL configuration: PLLCLK = HSI * 7 = 48 MHz */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE | RCC_PLLMULL));
+
+     RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSI_Div2 | RCC_PLLMULL7);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+}
+
+#elif defined SYSCLK_FREQ_72MHz_HSI
+
+/*********************************************************************
+ * @fn      SetSysClockTo72_HSI
+ *
+ * @brief   Sets System clock frequency to 72MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo72_HSI(void)
+{
+    EXTEN->EXTEN_CTR |= EXTEN_PLL_HSI_PRE;
+
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  PLL configuration: PLLCLK = HSI * 9 = 72 MHz */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE | RCC_PLLMULL));
+
+     RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSI_Div2 | RCC_PLLMULL9);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+}
+
+#elif defined SYSCLK_FREQ_96MHz_HSI
+
+/*********************************************************************
+ * @fn      SetSysClockTo96_HSI
+ *
+ * @brief   Sets System clock frequency to 96MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo96_HSI(void)
+{
+    EXTEN->EXTEN_CTR |= EXTEN_PLL_HSI_PRE;
+
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  PLL configuration: PLLCLK = HSI * 12 = 96 MHz */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE | RCC_PLLMULL));
+
+     RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSI_Div2 | RCC_PLLMULL12);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+}
+
+#elif defined SYSCLK_FREQ_120MHz_HSI
+
+/*********************************************************************
+ * @fn      SetSysClockTo120_HSI
+ *
+ * @brief   Sets System clock frequency to 120MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo120_HSI(void)
+{
+    EXTEN->EXTEN_CTR |= EXTEN_PLL_HSI_PRE;
+
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  PLL configuration: PLLCLK = HSI * 15 = 120 MHz */
+    RCC->CFGR0 &= (uint32_t)((uint32_t) ~(RCC_PLLSRC | RCC_PLLXTPRE |
+                                          RCC_PLLMULL));
+
+    RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSI_Div2 | RCC_PLLMULL15);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t) ~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+}
+#elif defined SYSCLK_FREQ_144MHz_HSI
+
+/*********************************************************************
+ * @fn      SetSysClockTo144_HSI
+ *
+ * @brief   Sets System clock frequency to 144MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
+ *
+ * @return  none
+ */
+static void SetSysClockTo144_HSI(void)
+{
+    EXTEN->EXTEN_CTR |= EXTEN_PLL_HSI_PRE;
+
+    /* HCLK = SYSCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
+    /* PCLK2 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE2_DIV1;
+    /* PCLK1 = HCLK */
+    RCC->CFGR0 |= (uint32_t)RCC_PPRE1_DIV2;
+
+    /*  PLL configuration: PLLCLK = HSI * 18 = 144 MHz */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC | RCC_PLLXTPRE | RCC_PLLMULL));
+
+     RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSI_Div2 | RCC_PLLMULL18);
+
+    /* Enable PLL */
+    RCC->CTLR |= RCC_PLLON;
+    /* Wait till PLL is ready */
+    while((RCC->CTLR & RCC_PLLRDY) == 0)
+    {
+    }
+    /* Select PLL as system clock source */
+    RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
+    RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
+    /* Wait till PLL is used as system clock source */
+    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
+    {
+    }
+}
+
+#endif
diff --git a/hw/bsp/ch32v20x/system_ch32v20x.h b/hw/bsp/ch32v20x/system_ch32v20x.h
new file mode 100644
index 000000000..eec7ccb15
--- /dev/null
+++ b/hw/bsp/ch32v20x/system_ch32v20x.h
@@ -0,0 +1,29 @@
+/********************************** (C) COPYRIGHT *******************************
+ * File Name          : system_ch32v20x.h
+ * Author             : WCH
+ * Version            : V1.0.0
+ * Date               : 2021/06/06
+ * Description        : CH32V20x Device Peripheral Access Layer System Header File.
+*********************************************************************************
+* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
+* Attention: This software (modified or not) and binary are used for
+* microcontroller manufactured by Nanjing Qinheng Microelectronics.
+*******************************************************************************/
+#ifndef __SYSTEM_ch32v20x_H
+#define __SYSTEM_ch32v20x_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+extern uint32_t SystemCoreClock;          /* System Clock Frequency (Core Clock) */
+
+/* System_Exported_Functions */
+extern void SystemInit(void);
+extern void SystemCoreClockUpdate(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*__CH32V20x_SYSTEM_H */
diff --git a/hw/bsp/ch32v20x/wch-riscv.cfg b/hw/bsp/ch32v20x/wch-riscv.cfg
new file mode 100644
index 000000000..aa35aa9c5
--- /dev/null
+++ b/hw/bsp/ch32v20x/wch-riscv.cfg
@@ -0,0 +1,17 @@
+adapter driver wlinke
+adapter speed 6000
+transport select sdi
+
+wlink_set_address 0x00000000
+set _CHIPNAME wch_riscv
+sdi newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00001
+
+set _TARGETNAME $_CHIPNAME.cpu
+
+target create $_TARGETNAME.0 wch_riscv -chain-position $_TARGETNAME
+$_TARGETNAME.0 configure  -work-area-phys 0x20000000 -work-area-size 10000 -work-area-backup 1
+set _FLASHNAME $_CHIPNAME.flash
+
+flash bank $_FLASHNAME wch_riscv 0x00000000 0 0 0 $_TARGETNAME.0
+
+echo "Ready for Remote Connections"
diff --git a/hw/bsp/ch32v307/debug_uart.c b/hw/bsp/ch32v307/debug_uart.c
index db3551ca7..fbabeeadc 100644
--- a/hw/bsp/ch32v307/debug_uart.c
+++ b/hw/bsp/ch32v307/debug_uart.c
@@ -28,7 +28,7 @@
 #include 
 
 
-#define UART_RINGBUFFER_SIZE_TX 64
+#define UART_RINGBUFFER_SIZE_TX 128
 #define UART_RINGBUFFER_MASK_TX (UART_RINGBUFFER_SIZE_TX-1)
 
 static char tx_buf[UART_RINGBUFFER_SIZE_TX];
diff --git a/hw/bsp/ch32v307/family.c b/hw/bsp/ch32v307/family.c
index 245fa5674..0846b10a9 100644
--- a/hw/bsp/ch32v307/family.c
+++ b/hw/bsp/ch32v307/family.c
@@ -35,29 +35,31 @@
 // Forward USB interrupt events to TinyUSB IRQ Handler
 //--------------------------------------------------------------------+
 
-void USBHS_IRQHandler (void) __attribute__((naked));
-void USBHS_IRQHandler (void)
-{
-  __asm volatile ("call USBHS_IRQHandler_impl; mret");
+// TODO maybe having FS as port0, HS as port1
+
+__attribute__((interrupt)) void USBHS_IRQHandler(void) {
+  #if CFG_TUD_WCH_USBIP_USBHS
+  tud_int_handler(0);
+  #endif
 }
 
-__attribute__ ((used)) void USBHS_IRQHandler_impl (void)
-{
+__attribute__((interrupt)) void OTG_FS_IRQHandler(void) {
+  #if CFG_TUD_WCH_USBIP_USBFS
   tud_int_handler(0);
+  #endif
 }
 
 //--------------------------------------------------------------------+
 // MACRO TYPEDEF CONSTANT ENUM
 //--------------------------------------------------------------------+
 
-uint32_t SysTick_Config(uint32_t ticks)
-{
+uint32_t SysTick_Config(uint32_t ticks) {
   NVIC_EnableIRQ(SysTicK_IRQn);
-  SysTick->CTLR=0;
-  SysTick->SR=0;
-  SysTick->CNT=0;
-  SysTick->CMP=ticks-1;
-  SysTick->CTLR=0xF;
+  SysTick->CTLR = 0;
+  SysTick->SR = 0;
+  SysTick->CNT = 0;
+  SysTick->CMP = ticks - 1;
+  SysTick->CTLR = 0xF;
   return 0;
 }
 
@@ -70,14 +72,28 @@ void board_init(void) {
   SysTick_Config(SystemCoreClock / 1000);
 #endif
 
-	usart_printf_init(115200);
+  usart_printf_init(CFG_BOARD_UART_BAUDRATE);
 
+#ifdef CH32V30x_D8C
+  // v305/v307: Highspeed USB
   RCC_USBCLK48MConfig(RCC_USBCLK48MCLKSource_USBPHY);
   RCC_USBHSPLLCLKConfig(RCC_HSBHSPLLCLKSource_HSE);
   RCC_USBHSConfig(RCC_USBPLL_Div2);
   RCC_USBHSPLLCKREFCLKConfig(RCC_USBHSPLLCKREFCLK_4M);
   RCC_USBHSPHYPLLALIVEcmd(ENABLE);
   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_USBHS, ENABLE);
+#endif
+
+  // Fullspeed USB
+  uint8_t otg_div;
+  switch (SystemCoreClock) {
+    case 48000000:  otg_div = RCC_OTGFSCLKSource_PLLCLK_Div1; break;
+    case 96000000:  otg_div = RCC_OTGFSCLKSource_PLLCLK_Div2; break;
+    case 144000000: otg_div = RCC_OTGFSCLKSource_PLLCLK_Div3; break;
+    default: TU_ASSERT(0,); break;
+  }
+  RCC_OTGFSCLKConfig(otg_div);
+  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_OTG_FS, ENABLE);
 
   GPIO_InitTypeDef GPIO_InitStructure = {0};
 
@@ -102,24 +118,14 @@ void board_init(void) {
 }
 
 #if CFG_TUSB_OS == OPT_OS_NONE
-
 volatile uint32_t system_ticks = 0;
 
-/* Small workaround to support HW stack save/restore */
-void SysTick_Handler (void) __attribute__((naked));
-void SysTick_Handler (void)
-{
-  __asm volatile ("call SysTick_Handler_impl; mret");
-}
-
-__attribute__((used)) void SysTick_Handler_impl (void)
-{
+__attribute__((interrupt)) void SysTick_Handler(void) {
   SysTick->SR = 0;
   system_ticks++;
 }
 
-uint32_t board_millis (void)
-{
+uint32_t board_millis(void) {
   return system_ticks;
 }
 
@@ -129,36 +135,30 @@ uint32_t board_millis (void)
 // Board porting API
 //--------------------------------------------------------------------+
 
-void board_led_write (bool state)
-{
+void board_led_write(bool state) {
   GPIO_WriteBit(LED_PORT, LED_PIN, state);
 }
 
-uint32_t board_button_read (void)
-{
+uint32_t board_button_read(void) {
   return BUTTON_STATE_ACTIVE == GPIO_ReadInputDataBit(BUTTON_PORT, BUTTON_PIN);
 }
 
-int board_uart_read (uint8_t *buf, int len)
-{
+int board_uart_read(uint8_t* buf, int len) {
   (void) buf;
   (void) len;
   return 0;
 }
 
-int board_uart_write (void const *buf, int len)
-{
+int board_uart_write(void const* buf, int len) {
   int txsize = len;
-  while ( txsize-- )
-  {
-    uart_write(*(uint8_t const*) buf);
-    buf++;
+  const char* bufc = (const char*) buf;
+  while (txsize--) {
+    uart_write(*bufc++);
   }
+  uart_sync();
   return len;
 }
 
-
-
 #ifdef USE_FULL_ASSERT
 /**
  * @brief  Reports the name of the source file and the source line number
diff --git a/hw/bsp/ch32v307/family.cmake b/hw/bsp/ch32v307/family.cmake
index 87a0f2eba..af26bfc31 100644
--- a/hw/bsp/ch32v307/family.cmake
+++ b/hw/bsp/ch32v307/family.cmake
@@ -1,6 +1,8 @@
 include_guard()
 
-set(SDK_DIR ${TOP}/hw/mcu/wch/ch32v307/EVT/EXAM/SRC)
+set(CH32_FAMILY ch32v30x)
+set(SDK_DIR ${TOP}/hw/mcu/wch/ch32v307)
+set(SDK_SRC_DIR ${SDK_DIR}/EVT/EXAM/SRC)
 
 # include board specific
 include(${CMAKE_CURRENT_LIST_DIR}/boards/${BOARD}/board.cmake)
@@ -11,7 +13,11 @@ set(CMAKE_TOOLCHAIN_FILE ${TOP}/examples/build_system/cmake/toolchain/riscv_${TO
 
 set(FAMILY_MCUS CH32V307 CACHE INTERNAL "")
 set(OPENOCD_OPTION "-f ${CMAKE_CURRENT_LIST_DIR}/wch-riscv.cfg")
-set(OPENOCD_OPTION2 "-c wlink_reset_resume")
+
+# default to highspeed, used to select USBFS / USBHS driver
+if (NOT DEFINED SPEED)
+  set(SPEED high)
+endif()
 
 #------------------------------------
 # BOARD_TARGET
@@ -28,27 +34,34 @@ function(add_board_target BOARD_TARGET)
   set(LD_FILE_Clang ${LD_FILE_GNU})
 
   if (NOT DEFINED STARTUP_FILE_GNU)
-    set(STARTUP_FILE_GNU ${SDK_DIR}/Startup/startup_ch32v30x_D8C.S)
+    set(STARTUP_FILE_GNU ${SDK_SRC_DIR}/Startup/startup_${CH32_FAMILY}_D8C.S)
   endif ()
   set(STARTUP_FILE_Clang ${STARTUP_FILE_GNU})
 
   add_library(${BOARD_TARGET} STATIC
-    ${SDK_DIR}/Core/core_riscv.c
-    ${SDK_DIR}/Peripheral/src/ch32v30x_gpio.c
-    ${SDK_DIR}/Peripheral/src/ch32v30x_misc.c
-    ${SDK_DIR}/Peripheral/src/ch32v30x_rcc.c
-    ${SDK_DIR}/Peripheral/src/ch32v30x_usart.c
-    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ch32v30x_it.c
-    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/system_ch32v30x.c
+    ${SDK_SRC_DIR}/Core/core_riscv.c
+    ${SDK_SRC_DIR}/Peripheral/src/${CH32_FAMILY}_gpio.c
+    ${SDK_SRC_DIR}/Peripheral/src/${CH32_FAMILY}_misc.c
+    ${SDK_SRC_DIR}/Peripheral/src/${CH32_FAMILY}_rcc.c
+    ${SDK_SRC_DIR}/Peripheral/src/${CH32_FAMILY}_usart.c
+    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/${CH32_FAMILY}_it.c
+    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/system_${CH32_FAMILY}.c
     ${STARTUP_FILE_${CMAKE_C_COMPILER_ID}}
     )
   target_include_directories(${BOARD_TARGET} PUBLIC
-    ${SDK_DIR}/Peripheral/inc
+    ${SDK_SRC_DIR}/Peripheral/inc
     ${CMAKE_CURRENT_FUNCTION_LIST_DIR}
     )
-  target_compile_definitions(${BOARD_TARGET} PUBLIC
-    BOARD_TUD_MAX_SPEED=OPT_MODE_HIGH_SPEED
-    )
+  if (SPEED STREQUAL high)
+    target_compile_definitions(${BOARD_TARGET} PUBLIC
+      CFG_TUD_WCH_USBIP_USBHS=1
+#      BOARD_TUD_MAX_SPEED=OPT_MODE_HIGH_SPEED
+      )
+  else ()
+    target_compile_definitions(${BOARD_TARGET} PUBLIC
+      CFG_TUD_WCH_USBIP_USBFS=1
+      )
+  endif ()
 
   update_board(${BOARD_TARGET})
 
@@ -102,6 +115,7 @@ function(family_configure_example TARGET RTOS)
   family_add_tinyusb(${TARGET} OPT_MCU_CH32V307 ${RTOS})
   target_sources(${TARGET}-tinyusb PUBLIC
     ${TOP}/src/portable/wch/dcd_ch32_usbhs.c
+    ${TOP}/src/portable/wch/dcd_ch32_usbfs.c
     )
   target_link_libraries(${TARGET}-tinyusb PUBLIC board_${BOARD})
 
diff --git a/hw/bsp/ch32v307/family.mk b/hw/bsp/ch32v307/family.mk
index 003b90539..30a070a9b 100644
--- a/hw/bsp/ch32v307/family.mk
+++ b/hw/bsp/ch32v307/family.mk
@@ -1,66 +1,60 @@
 # https://www.embecosm.com/resources/tool-chain-downloads/#riscv-stable
 #CROSS_COMPILE ?= riscv32-unknown-elf-
 
-# Toolchain from https://github.com/xpack-dev-tools/riscv-none-embed-gcc-xpack
-CROSS_COMPILE ?= riscv-none-embed-
+# Toolchain from https://nucleisys.com/download.php
+#CROSS_COMPILE ?= riscv-nuclei-elf-
 
-# Submodules
-CH32V307_SDK = hw/mcu/wch/ch32v307
+# Toolchain from https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack
+CROSS_COMPILE ?= riscv-none-elf-
 
-# WCH-SDK paths
-CH32V307_SDK_SRC = $(CH32V307_SDK)/EVT/EXAM/SRC
+CH32_FAMILY = ch32v30x
+SDK_DIR = hw/mcu/wch/ch32v307
+SDK_SRC_DIR = $(SDK_DIR)/EVT/EXAM/SRC
 
 include $(TOP)/$(BOARD_PATH)/board.mk
 CPU_CORE ?= rv32imac-ilp32
 
+# default to use high speed port, unless specified in board.mk or command line
+SPEED ?= high
+
 CFLAGS += \
 	-flto \
 	-msmall-data-limit=8 \
-	-mno-save-restore -Os \
+	-mno-save-restore \
 	-fmessage-length=0 \
 	-fsigned-char \
-	-ffunction-sections \
-	-fdata-sections \
 	-DCFG_TUSB_MCU=OPT_MCU_CH32V307 \
-	-Xlinker --gc-sections \
-	-DBOARD_TUD_MAX_SPEED=OPT_MODE_HIGH_SPEED
+
+ifeq ($(SPEED),high)
+  $(info "Using USBHS driver for HighSpeed mode")
+  CFLAGS += -DCFG_TUD_WCH_USBIP_USBHS=1
+else
+  $(info "Using USBFS driver for FullSpeed mode")
+  CFLAGS += -DCFG_TUD_WCH_USBIP_USBFS=1
+endif
 
 LDFLAGS_GCC += \
 	-nostdlib -nostartfiles \
-  --specs=nosys.specs --specs=nano.specs
+  --specs=nosys.specs --specs=nano.specs \
 
 SRC_C += \
 	src/portable/wch/dcd_ch32_usbhs.c \
-	$(CH32V307_SDK_SRC)/Core/core_riscv.c \
-	$(CH32V307_SDK_SRC)/Peripheral/src/ch32v30x_gpio.c \
-	$(CH32V307_SDK_SRC)/Peripheral/src/ch32v30x_misc.c \
-	$(CH32V307_SDK_SRC)/Peripheral/src/ch32v30x_rcc.c \
-	$(CH32V307_SDK_SRC)/Peripheral/src/ch32v30x_usart.c
+	src/portable/wch/dcd_ch32_usbfs.c \
+	$(SDK_SRC_DIR)/Core/core_riscv.c \
+	$(SDK_SRC_DIR)/Peripheral/src/${CH32_FAMILY}_gpio.c \
+	$(SDK_SRC_DIR)/Peripheral/src/${CH32_FAMILY}_misc.c \
+	$(SDK_SRC_DIR)/Peripheral/src/${CH32_FAMILY}_rcc.c \
+	$(SDK_SRC_DIR)/Peripheral/src/${CH32_FAMILY}_usart.c
 
 SRC_S += \
-	$(CH32V307_SDK_SRC)/Startup/startup_ch32v30x_D8C.S
+	$(SDK_SRC_DIR)/Startup/startup_${CH32_FAMILY}_D8C.S
 
 INC += \
 	$(TOP)/$(BOARD_PATH) \
-	$(TOP)/$(CH32V307_SDK_SRC)/Peripheral/inc
+	$(TOP)/$(SDK_SRC_DIR)/Peripheral/inc
 
 # For freeRTOS port source
 FREERTOS_PORTABLE_SRC = $(FREERTOS_PORTABLE_PATH)/RISC-V
 
-# wch-link is not supported yet in official openOCD yet. We need to either use
-# 1. download openocd as part of mounriver studio http://www.mounriver.com/download or
-# 2. compiled from modified source https://github.com/kprasadvnsi/riscv-openocd-wch
-#
-# Note: For Linux, somehow openocd in mounriver studio does not seem to have wch-link enable,
-# therefore we need to compile it from source as follows:
-# 	git clone https://github.com/kprasadvnsi/riscv-openocd-wch
-# 	cd riscv-openocd-wch
-#		./bootstrap
-#		./configure CFLAGS="-Wno-error" --enable-wlink
-#		make
-# openocd binaries will be generated in riscv-openocd-wch/src
-
-# flash target ROM bootloader
-OPENOCD_WCH = /home/${USER}/app/riscv-openocd-wch/src/openocd
-flash: $(BUILD)/$(PROJECT).elf
-	$(OPENOCD_WCH) -f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg -c init -c halt -c "program $<" -c wlink_reset_resume -c exit
+OPENOCD_WCH_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg
+flash: flash-openocd-wch
diff --git a/hw/bsp/ch32v307/wch-riscv.cfg b/hw/bsp/ch32v307/wch-riscv.cfg
index 0d24d16ca..aa35aa9c5 100644
--- a/hw/bsp/ch32v307/wch-riscv.cfg
+++ b/hw/bsp/ch32v307/wch-riscv.cfg
@@ -1,13 +1,15 @@
-#interface wlink
-adapter driver wlink
-wlink_set
-set _CHIPNAME riscv
-jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00001
+adapter driver wlinke
+adapter speed 6000
+transport select sdi
+
+wlink_set_address 0x00000000
+set _CHIPNAME wch_riscv
+sdi newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00001
 
 set _TARGETNAME $_CHIPNAME.cpu
 
-target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME
-$_TARGETNAME.0 configure  -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1
+target create $_TARGETNAME.0 wch_riscv -chain-position $_TARGETNAME
+$_TARGETNAME.0 configure  -work-area-phys 0x20000000 -work-area-size 10000 -work-area-backup 1
 set _FLASHNAME $_CHIPNAME.flash
 
 flash bank $_FLASHNAME wch_riscv 0x00000000 0 0 0 $_TARGETNAME.0
diff --git a/hw/bsp/family_support.cmake b/hw/bsp/family_support.cmake
index df4f616ef..6eef5b88b 100644
--- a/hw/bsp/family_support.cmake
+++ b/hw/bsp/family_support.cmake
@@ -440,6 +440,7 @@ function(family_flash_openocd TARGET)
 endfunction()
 
 # Add flash openocd-wch target
+# compiled from https://github.com/hathach/riscv-openocd-wch or https://github.com/dragonlock2/miscboards/blob/main/wch/SDK/riscv-openocd.tar.xz
 function(family_flash_openocd_wch TARGET)
   if (NOT DEFINED OPENOCD)
     set(OPENOCD $ENV{HOME}/app/riscv-openocd-wch/src/openocd)
diff --git a/hw/bsp/fomu/family.mk b/hw/bsp/fomu/family.mk
index d4b6eaea6..69a546964 100644
--- a/hw/bsp/fomu/family.mk
+++ b/hw/bsp/fomu/family.mk
@@ -1,5 +1,5 @@
-# Toolchain from https://github.com/xpack-dev-tools/riscv-none-embed-gcc-xpack
-CROSS_COMPILE = riscv-none-embed-
+# Toolchain from https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack
+CROSS_COMPILE = riscv-none-elf-
 
 CPU_CORE ?= rv32i-ilp32
 
diff --git a/hw/bsp/gd32vf103/family.mk b/hw/bsp/gd32vf103/family.mk
index 28e48a6b5..48588886c 100644
--- a/hw/bsp/gd32vf103/family.mk
+++ b/hw/bsp/gd32vf103/family.mk
@@ -4,8 +4,8 @@
 # Toolchain from https://nucleisys.com/download.php
 #CROSS_COMPILE ?= riscv-nuclei-elf-
 
-# Toolchain from https://github.com/xpack-dev-tools/riscv-none-embed-gcc-xpack
-CROSS_COMPILE ?= riscv-none-embed-
+# Toolchain from https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack
+CROSS_COMPILE ?= riscv-none-elf-
 
 # Submodules
 NUCLEI_SDK = hw/mcu/gd/nuclei-sdk
diff --git a/hw/bsp/nrf/family.cmake b/hw/bsp/nrf/family.cmake
index 5de69a8a3..9e86374dc 100644
--- a/hw/bsp/nrf/family.cmake
+++ b/hw/bsp/nrf/family.cmake
@@ -101,6 +101,15 @@ endfunction()
 #------------------------------------
 # Functions
 #------------------------------------
+
+#function(family_flash_adafruit_nrfutil TARGET)
+#  add_custom_target(${TARGET}-adafruit-nrfutil
+#    DEPENDS ${TARGET}
+#    COMMAND adafruit-nrfutil --verbose dfu serial --package $^ -p /dev/ttyACM0 -b 115200 --singlebank --touch 1200
+#    )
+#endfunction()
+
+
 function(family_configure_example TARGET RTOS)
   family_configure_common(${TARGET} ${RTOS})
 
@@ -133,4 +142,5 @@ function(family_configure_example TARGET RTOS)
 
   # Flashing
   family_flash_jlink(${TARGET})
+#  family_flash_adafruit_nrfutil(${TARGET})
 endfunction()
diff --git a/hw/bsp/stm32f0/family.c b/hw/bsp/stm32f0/family.c
index 7ef126ae6..3079a1ed3 100644
--- a/hw/bsp/stm32f0/family.c
+++ b/hw/bsp/stm32f0/family.c
@@ -115,7 +115,7 @@ void board_init(void) {
 //--------------------------------------------------------------------+
 
 void board_led_write(bool state) {
-  GPIO_PinState pin_state = (GPIO_PinState)(state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  GPIO_PinState pin_state = (GPIO_PinState) (state ? LED_STATE_ON : (1 - LED_STATE_ON));
   HAL_GPIO_WritePin(LED_PORT, LED_PIN, pin_state);
 }
 
diff --git a/hw/bsp/stm32f1/family.c b/hw/bsp/stm32f1/family.c
index 0c1b362ab..70f81e7fc 100644
--- a/hw/bsp/stm32f1/family.c
+++ b/hw/bsp/stm32f1/family.c
@@ -124,7 +124,7 @@ void board_init(void) {
 //--------------------------------------------------------------------+
 
 void board_led_write(bool state) {
-  GPIO_PinState pin_state = (GPIO_PinState)(state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  GPIO_PinState pin_state = (GPIO_PinState) (state ? LED_STATE_ON : (1 - LED_STATE_ON));
   HAL_GPIO_WritePin(LED_PORT, LED_PIN, pin_state);
 }
 
diff --git a/hw/bsp/stm32f2/family.c b/hw/bsp/stm32f2/family.c
index 8b1c56423..62cca327b 100644
--- a/hw/bsp/stm32f2/family.c
+++ b/hw/bsp/stm32f2/family.c
@@ -108,7 +108,8 @@ void board_init(void) {
 //--------------------------------------------------------------------+
 
 void board_led_write(bool state) {
-  HAL_GPIO_WritePin(LED_PORT, LED_PIN, state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  GPIO_PinState pin_state = (GPIO_PinState) (state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  HAL_GPIO_WritePin(LED_PORT, LED_PIN, pin_state);
 }
 
 uint32_t board_button_read(void) {
diff --git a/hw/bsp/stm32f3/family.c b/hw/bsp/stm32f3/family.c
index 7c194a694..e7488ba84 100644
--- a/hw/bsp/stm32f3/family.c
+++ b/hw/bsp/stm32f3/family.c
@@ -108,7 +108,8 @@ void board_init(void) {
 //--------------------------------------------------------------------+
 
 void board_led_write(bool state) {
-  HAL_GPIO_WritePin(LED_PORT, LED_PIN, state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  GPIO_PinState pin_state = (GPIO_PinState) (state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  HAL_GPIO_WritePin(LED_PORT, LED_PIN, pin_state);
 }
 
 uint32_t board_button_read(void) {
@@ -142,7 +143,7 @@ uint32_t board_millis(void) {
 #endif
 
 void HardFault_Handler(void) {
-  asm("bkpt");
+  asm("bkpt 0");
 }
 
 // Required by __libc_init_array in startup code if we are compiling using
diff --git a/hw/bsp/stm32u5/family.c b/hw/bsp/stm32u5/family.c
index ec64f7622..d779b5c96 100644
--- a/hw/bsp/stm32u5/family.c
+++ b/hw/bsp/stm32u5/family.c
@@ -203,7 +203,8 @@ void board_init(void) {
 //--------------------------------------------------------------------+
 
 void board_led_write(bool state) {
-  HAL_GPIO_WritePin(LED_PORT, LED_PIN, state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  GPIO_PinState pin_state = (GPIO_PinState) (state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  HAL_GPIO_WritePin(LED_PORT, LED_PIN, pin_state);
 }
 
 uint32_t board_button_read(void) {
diff --git a/hw/bsp/stm32wb/family.c b/hw/bsp/stm32wb/family.c
index 1dc789407..6051388a7 100644
--- a/hw/bsp/stm32wb/family.c
+++ b/hw/bsp/stm32wb/family.c
@@ -136,7 +136,8 @@ void board_init(void) {
 //--------------------------------------------------------------------+
 
 void board_led_write(bool state) {
-  HAL_GPIO_WritePin(LED_PORT, LED_PIN, state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  GPIO_PinState pin_state = (GPIO_PinState) (state ? LED_STATE_ON : (1 - LED_STATE_ON));
+  HAL_GPIO_WritePin(LED_PORT, LED_PIN, pin_state);
 }
 
 uint32_t board_button_read(void) {
@@ -174,7 +175,7 @@ uint32_t board_millis(void) {
 #endif
 
 void HardFault_Handler(void) {
-  asm("bkpt");
+  asm("bkpt 1");
 }
 
 // Required by __libc_init_array in startup code if we are compiling using
diff --git a/lib/rt-thread/SConscript b/lib/rt-thread/SConscript
index 482f6d7b2..34399fd45 100644
--- a/lib/rt-thread/SConscript
+++ b/lib/rt-thread/SConscript
@@ -34,6 +34,8 @@ if GetDepend(["PKG_TINYUSB_DEVICE_ENABLE"]):
         src += ["../../src/class/cdc/cdc_device.c"]
     if GetDepend(["PKG_TINYUSB_DEVICE_MSC"]):
         src += ["../../src/class/msc/msc_device.c", "port/msc_device_port.c"]
+    if GetDepend(["PKG_TINYUSB_DEVICE_HID"]):
+        src += ["../../src/class/hid/hid_device.c"]
 
 # for host stack
 if GetDepend(["PKG_TINYUSB_HOST_ENABLE"]):
diff --git a/lib/rt-thread/tusb_config.h b/lib/rt-thread/tusb_config.h
index b3c3bf43f..11dc21983 100644
--- a/lib/rt-thread/tusb_config.h
+++ b/lib/rt-thread/tusb_config.h
@@ -152,12 +152,12 @@ extern "C" {
   #define CFG_TUH_ENABLED             (0)
 #endif
 
-#if (PKG_TINYUSB_HOST_PORT == 0)
+#if (PKG_TINYUSB_HOST_PORT == 0) && defined(PKG_TINYUSB_HOST_ENABLE)
 #undef CFG_TUSB_RHPORT0_MODE
 #define CFG_TUSB_RHPORT0_MODE     (OPT_MODE_HOST | PKG_TINYUSB_HOST_PORT_SPEED)
 #endif
 
-#if (PKG_TINYUSB_HOST_PORT == 1)
+#if (PKG_TINYUSB_HOST_PORT == 1) && defined(PKG_TINYUSB_HOST_ENABLE)
 #undef CFG_TUSB_RHPORT1_MODE
 #define CFG_TUSB_RHPORT1_MODE     (OPT_MODE_HOST | PKG_TINYUSB_HOST_PORT_SPEED)
 #endif
diff --git a/src/common/tusb_compiler.h b/src/common/tusb_compiler.h
index 0d5570b1c..ce5566ffe 100644
--- a/src/common/tusb_compiler.h
+++ b/src/common/tusb_compiler.h
@@ -128,6 +128,7 @@
   #define TU_ATTR_SECTION(sec_name)     __attribute__ ((section(#sec_name)))
   #define TU_ATTR_PACKED                __attribute__ ((packed))
   #define TU_ATTR_WEAK                  __attribute__ ((weak))
+  // #define TU_ATTR_WEAK_ALIAS(f)         __attribute__ ((weak, alias(#f))
   #ifndef TU_ATTR_ALWAYS_INLINE // allow to override for debug
     #define TU_ATTR_ALWAYS_INLINE       __attribute__ ((always_inline))
   #endif
diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index 4e3b89262..c66996c4f 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -195,6 +195,7 @@
 #elif TU_CHECK_MCU(OPT_MCU_STM32F4)
   #define TUP_USBIP_DWC2
   #define TUP_USBIP_DWC2_STM32
+  #define TUP_USBIP_DWC2_TEST_MODE
 
   // For most mcu, FS has 4, HS has 6. TODO 446/469/479 HS has 9
   #define TUP_DCD_ENDPOINT_MAX    6
@@ -209,6 +210,7 @@
   // MCU with on-chip HS Phy
   #if defined(STM32F723xx) || defined(STM32F730xx) || defined(STM32F733xx)
     #define TUP_RHPORT_HIGHSPEED  1 // Port0: FS, Port1: HS
+    #define TUP_USBIP_DWC2_TEST_MODE
   #endif
 
 #elif TU_CHECK_MCU(OPT_MCU_STM32H7)
@@ -277,6 +279,7 @@
       defined(STM32U5F7xx) || defined(STM32U5F9xx) || defined(STM32U5G7xx) || defined(STM32U5G9xx)
     #define TUP_DCD_ENDPOINT_MAX  9
     #define TUP_RHPORT_HIGHSPEED  1
+    #define TUP_USBIP_DWC2_TEST_MODE
   #else
     #define TUP_DCD_ENDPOINT_MAX  6
   #endif
@@ -329,7 +332,7 @@
   #define TUP_USBIP_DWC2
   #define TUP_DCD_ENDPOINT_MAX    6
 
-#elif TU_CHECK_MCU(OPT_MCU_ESP32) && (CFG_TUD_ENABLED || !(defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421))
+#elif TU_CHECK_MCU(OPT_MCU_ESP32, OPT_MCU_ESP32C2, OPT_MCU_ESP32C3, OPT_MCU_ESP32C6, OPT_MCU_ESP32H2) && (CFG_TUD_ENABLED || !(defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421))
   #error "MCUs are only supported with CFG_TUH_MAX3421 enabled"
 
 //--------------------------------------------------------------------+
@@ -399,16 +402,57 @@
 #elif TU_CHECK_MCU(OPT_MCU_F1C100S)
   #define TUP_DCD_ENDPOINT_MAX    4
 
-//------------- WCH -------------//
-#elif TU_CHECK_MCU(OPT_MCU_CH32V307)
-  #define TUP_DCD_ENDPOINT_MAX    16
-  #define TUP_RHPORT_HIGHSPEED    1
-
+//--------------------------------------------------------------------+
+// WCH
+//--------------------------------------------------------------------+
 #elif TU_CHECK_MCU(OPT_MCU_CH32F20X)
-  #define TUP_DCD_ENDPOINT_MAX    16
-  #define TUP_RHPORT_HIGHSPEED    1
-#endif
+  #define TUP_USBIP_WCH_USBHS
+  #define TUP_USBIP_WCH_USBFS
 
+  #if !defined(CFG_TUD_WCH_USBIP_USBFS)
+  #define CFG_TUD_WCH_USBIP_USBFS 0
+  #endif
+
+  #if !defined(CFG_TUD_WCH_USBIP_USBHS)
+  #define CFG_TUD_WCH_USBIP_USBHS (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1)
+  #endif
+
+  #define TUP_RHPORT_HIGHSPEED    CFG_TUD_WCH_USBIP_USBHS
+  #define TUP_DCD_ENDPOINT_MAX    (CFG_TUD_WCH_USBIP_USBHS ? 16 : 8)
+
+#elif TU_CHECK_MCU(OPT_MCU_CH32V20X)
+  // v20x support both FSDEV (USBD) and USBFS, default to FSDEV
+  #define TUP_USBIP_WCH_USBFS
+  #define TUP_USBIP_FSDEV
+  #define TUP_USBIP_FSDEV_CH32
+
+  #if !defined(CFG_TUD_WCH_USBIP_USBFS)
+  #define CFG_TUD_WCH_USBIP_USBFS 0
+  #endif
+
+  #if !defined(CFG_TUD_WCH_USBIP_FSDEV)
+  #define CFG_TUD_WCH_USBIP_FSDEV  (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1)
+  #endif
+
+  #define TUP_DCD_ENDPOINT_MAX    8
+
+#elif TU_CHECK_MCU(OPT_MCU_CH32V307)
+  // v307 support both FS and HS, default to HS
+  #define TUP_USBIP_WCH_USBHS
+  #define TUP_USBIP_WCH_USBFS
+
+  #if !defined(CFG_TUD_WCH_USBIP_USBFS)
+  #define CFG_TUD_WCH_USBIP_USBFS 0
+  #endif
+
+  #if !defined(CFG_TUD_WCH_USBIP_USBHS)
+  #define CFG_TUD_WCH_USBIP_USBHS (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1)
+  #endif
+
+  #define TUP_RHPORT_HIGHSPEED    CFG_TUD_WCH_USBIP_USBHS
+  #define TUP_DCD_ENDPOINT_MAX    (CFG_TUD_WCH_USBIP_USBHS ? 16 : 8)
+
+#endif
 
 //--------------------------------------------------------------------+
 // External USB controller
diff --git a/src/device/dcd.h b/src/device/dcd.h
index d4f105aa3..f6735b077 100644
--- a/src/device/dcd.h
+++ b/src/device/dcd.h
@@ -24,8 +24,8 @@
  * This file is part of the TinyUSB stack.
  */
 
-#ifndef _TUSB_DCD_H_
-#define _TUSB_DCD_H_
+#ifndef TUSB_DCD_H_
+#define TUSB_DCD_H_
 
 #include "common/tusb_common.h"
 #include "osal/osal.h"
@@ -35,14 +35,6 @@
  extern "C" {
 #endif
 
-//--------------------------------------------------------------------+
-// Configuration
-//--------------------------------------------------------------------+
-
-#ifndef CFG_TUD_ENDPPOINT_MAX
-  #define CFG_TUD_ENDPPOINT_MAX   TUP_DCD_ENDPOINT_MAX
-#endif
-
 //--------------------------------------------------------------------+
 // MACRO CONSTANT TYPEDEF PROTYPES
 //--------------------------------------------------------------------+
@@ -97,6 +89,14 @@ typedef struct TU_ATTR_ALIGNED(4) {
   };
 } dcd_event_t;
 
+typedef enum {
+  TEST_J = 1,
+  TEST_K,
+  TEST_SE0_NAK,
+  TEST_PACKET,
+  TEST_FORCE_ENABLE,
+} test_mode_t;
+
 //TU_VERIFY_STATIC(sizeof(dcd_event_t) <= 12, "size is not correct");
 
 //--------------------------------------------------------------------+
@@ -141,14 +141,21 @@ void dcd_set_address(uint8_t rhport, uint8_t dev_addr);
 void dcd_remote_wakeup(uint8_t rhport);
 
 // Connect by enabling internal pull-up resistor on D+/D-
-void dcd_connect(uint8_t rhport) TU_ATTR_WEAK;
+void dcd_connect(uint8_t rhport);
 
 // Disconnect by disabling internal pull-up resistor on D+/D-
-void dcd_disconnect(uint8_t rhport) TU_ATTR_WEAK;
+void dcd_disconnect(uint8_t rhport);
 
 // Enable/Disable Start-of-frame interrupt. Default is disabled
 void dcd_sof_enable(uint8_t rhport, bool en);
 
+#if CFG_TUD_TEST_MODE
+// Check if the test mode is supported, returns true is test mode selector is supported
+bool dcd_check_test_mode_support(test_mode_t test_selector) TU_ATTR_WEAK;
+
+// Put device into a test mode (needs power cycle to quit)
+void dcd_enter_test_mode(uint8_t rhport, test_mode_t test_selector) TU_ATTR_WEAK;
+#endif
 //--------------------------------------------------------------------+
 // Endpoint API
 //--------------------------------------------------------------------+
@@ -239,4 +246,4 @@ TU_ATTR_ALWAYS_INLINE static inline void dcd_event_sof(uint8_t rhport, uint32_t
  }
 #endif
 
-#endif /* _TUSB_DCD_H_ */
+#endif
diff --git a/src/device/usbd.c b/src/device/usbd.c
index e33f39bcf..800d9c824 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -45,11 +45,6 @@
 //--------------------------------------------------------------------+
 // Weak stubs: invoked if no strong implementation is available
 //--------------------------------------------------------------------+
-TU_ATTR_WEAK bool dcd_deinit(uint8_t rhport) {
-  (void) rhport;
-  return false;
-}
-
 TU_ATTR_WEAK void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) {
   (void)rhport;
   (void)eventid;
@@ -60,6 +55,19 @@ TU_ATTR_WEAK void tud_sof_cb(uint32_t frame_count) {
   (void)frame_count;
 }
 
+TU_ATTR_WEAK bool dcd_deinit(uint8_t rhport) {
+  (void) rhport;
+  return false;
+}
+
+TU_ATTR_WEAK void dcd_connect(uint8_t rhport) {
+  (void) rhport;
+}
+
+TU_ATTR_WEAK void dcd_disconnect(uint8_t rhport) {
+  (void) rhport;
+}
+
 //--------------------------------------------------------------------+
 // Device Data
 //--------------------------------------------------------------------+
@@ -313,7 +321,9 @@ TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event,
 static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request);
 static bool process_set_config(uint8_t rhport, uint8_t cfg_num);
 static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request);
-
+#if CFG_TUD_TEST_MODE
+static bool process_test_mode_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
+#endif
 // from usbd_control.c
 void usbd_control_reset(void);
 void usbd_control_set_request(tusb_control_request_t const *request);
@@ -377,21 +387,17 @@ bool tud_remote_wakeup(void) {
 }
 
 bool tud_disconnect(void) {
-  TU_VERIFY(dcd_disconnect);
   dcd_disconnect(_usbd_rhport);
   return true;
 }
 
 bool tud_connect(void) {
-  TU_VERIFY(dcd_connect);
   dcd_connect(_usbd_rhport);
   return true;
 }
 
-bool tud_sof_cb_enable(bool en)
-{
+void tud_sof_cb_enable(bool en) {
   usbd_sof_enable(_usbd_rhport, SOF_CONSUMER_USER, en);
-  return true;
 }
 
 //--------------------------------------------------------------------+
@@ -405,7 +411,7 @@ bool tud_init(uint8_t rhport) {
   // skip if already initialized
   if (tud_inited()) return true;
 
-  TU_LOG_USBD("USBD init on controller %u\r\n", rhport);
+  TU_LOG_USBD("USBD init on controller %u, Highspeed = %u\r\n", rhport, TUD_OPT_HIGH_SPEED);
   TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(usbd_device_t));
   TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(dcd_event_t));
   TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(tu_fifo_t));
@@ -753,14 +759,47 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const
         break;
 
         case TUSB_REQ_SET_FEATURE:
-          // Only support remote wakeup for device feature
-          TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue);
+          // Handle the feature selector
+          switch(p_request->wValue)
+          {
+            // Support for remote wakeup
+            case TUSB_REQ_FEATURE_REMOTE_WAKEUP:
+              TU_LOG_USBD("    Enable Remote Wakeup\r\n");
 
-          TU_LOG_USBD("    Enable Remote Wakeup\r\n");
+              // Host may enable remote wake up before suspending especially HID device
+              _usbd_dev.remote_wakeup_en = true;
+              tud_control_status(rhport, p_request);
+            break;
 
-          // Host may enable remote wake up before suspending especially HID device
-          _usbd_dev.remote_wakeup_en = true;
-          tud_control_status(rhport, p_request);
+#if CFG_TUD_TEST_MODE
+            // Support for TEST_MODE
+            case TUSB_REQ_FEATURE_TEST_MODE: {
+              // Only handle the test mode if supported and valid
+              TU_VERIFY(dcd_enter_test_mode && dcd_check_test_mode_support && 0 == tu_u16_low(p_request->wIndex));
+
+              uint8_t selector = tu_u16_high(p_request->wIndex);
+
+              // Stall request if the selected test mode isn't supported
+              if (!dcd_check_test_mode_support((test_mode_t)selector))
+              {
+                TU_LOG_USBD("    Unsupported Test Mode (test selector index: %d)\r\n", selector);
+
+                return false;
+              }
+
+              // Acknowledge request
+              tud_control_status(rhport, p_request);
+
+              TU_LOG_USBD("    Enter Test Mode (test selector index: %d)\r\n", selector);
+
+              usbd_control_set_complete_callback(process_test_mode_cb);
+              break;
+            }
+#endif /* CFG_TUD_TEST_MODE */
+
+            // Stall unsupported feature selector
+            default: return false;
+          }
         break;
 
         case TUSB_REQ_CLEAR_FEATURE:
@@ -1088,6 +1127,20 @@ static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const
   }
 }
 
+#if CFG_TUD_TEST_MODE
+static bool process_test_mode_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
+{
+  // At this point it should already be ensured that dcd_enter_test_mode() is defined
+
+  // Only enter the test mode after the request for it has completed
+  TU_VERIFY(CONTROL_STAGE_ACK == stage);
+
+  dcd_enter_test_mode(rhport, (test_mode_t)tu_u16_high(request->wIndex));
+
+  return true;
+}
+#endif /* CFG_TUD_TEST_MODE */
+
 //--------------------------------------------------------------------+
 // DCD Event Handler
 //--------------------------------------------------------------------+
@@ -1140,7 +1193,7 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr)
       }
 
       if (tu_bit_test(_usbd_dev.sof_consumer, SOF_CONSUMER_USER)) {
-        dcd_event_t const event_sof = {.rhport = event->rhport, .event_id = DCD_EVENT_SOF};
+        dcd_event_t const event_sof = {.rhport = event->rhport, .event_id = DCD_EVENT_SOF, .sof.frame_count = event->sof.frame_count};
         queue_event(&event_sof, in_isr);
       }
       break;
diff --git a/src/device/usbd.h b/src/device/usbd.h
index 6216d97ac..26e857d36 100644
--- a/src/device/usbd.h
+++ b/src/device/usbd.h
@@ -60,7 +60,7 @@ void tud_task (void) {
 // Check if there is pending events need processing by tud_task()
 bool tud_task_event_ready(void);
 
-#ifndef _TUSB_DCD_H_
+#ifndef TUSB_DCD_H_
 extern void dcd_int_handler(uint8_t rhport);
 #endif
 
@@ -98,7 +98,7 @@ bool tud_disconnect(void);
 bool tud_connect(void);
 
 // Enable or disable the Start Of Frame callback support
-bool tud_sof_cb_enable(bool en);
+void tud_sof_cb_enable(bool en);
 
 // Carry out Data and Status stage of control transfer
 // - If len = 0, it is equivalent to sending status only
diff --git a/src/portable/analog/max3421/hcd_max3421.c b/src/portable/analog/max3421/hcd_max3421.c
index 4dc93d2d8..d9b8e1fc3 100644
--- a/src/portable/analog/max3421/hcd_max3421.c
+++ b/src/portable/analog/max3421/hcd_max3421.c
@@ -174,7 +174,8 @@ enum {
 enum {
   EP_STATE_IDLE        = 0,
   EP_STATE_COMPLETE    = 1,
-  EP_STATE_ATTEMPT_1   = 2, // pending 1st attempt
+  EP_STATE_ABORTING    = 2,
+  EP_STATE_ATTEMPT_1   = 3, // pending 1st attempt
   EP_STATE_ATTEMPT_MAX = 15
 };
 
@@ -459,6 +460,7 @@ bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) {
 
   tuh_configure_param_t const* cfg = (tuh_configure_param_t const*) cfg_param;
   _tuh_cfg = cfg->max3421;
+  _tuh_cfg.max_nak = tu_min8(_tuh_cfg.max_nak, EP_STATE_ATTEMPT_MAX-EP_STATE_ATTEMPT_1);
   return true;
 }
 
@@ -678,7 +680,6 @@ static void xact_generic(uint8_t rhport, max3421_ep_t *ep, bool switch_ep, bool
 bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) {
   uint8_t const ep_num = tu_edpt_number(ep_addr);
   uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr);
-
   max3421_ep_t* ep = find_opened_ep(daddr, ep_num, ep_dir);
   TU_VERIFY(ep);
 
@@ -702,14 +703,19 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buf
   return true;
 }
 
-// Abort a queued transfer. Note: it can only abort transfer that has not been started
-// Return true if a queued transfer is aborted, false if there is no transfer to abort
-bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
-  (void) rhport;
-  (void) dev_addr;
-  (void) ep_addr;
+bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) {
+  uint8_t const ep_num = tu_edpt_number(ep_addr);
+  uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr);
+  max3421_ep_t* ep = find_opened_ep(daddr, ep_num, ep_dir);
+  TU_VERIFY(ep);
 
-  return false;
+  if (EP_STATE_ATTEMPT_1 <= ep->state && ep->state < EP_STATE_ATTEMPT_MAX) {
+    hcd_int_disable(rhport);
+    ep->state = EP_STATE_ABORTING;
+    hcd_int_enable(rhport);
+  }
+
+  return true;
 }
 
 // Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked
@@ -819,7 +825,6 @@ static void xfer_complete_isr(uint8_t rhport, max3421_ep_t *ep, xfer_result_t re
 static void handle_xfer_done(uint8_t rhport, bool in_isr) {
   uint8_t const hrsl = reg_read(rhport, HRSL_ADDR, in_isr);
   uint8_t const hresult = hrsl & HRSL_RESULT_MASK;
-
   uint8_t const ep_num = _hcd_data.hxfr & HXFR_EPNUM_MASK;
   uint8_t const hxfr_type = _hcd_data.hxfr & 0xf0;
   uint8_t const ep_dir = ((hxfr_type & HXFR_SETUP) || (hxfr_type & HXFR_OUT_NIN)) ? 0 : 1;
@@ -829,6 +834,37 @@ static void handle_xfer_done(uint8_t rhport, bool in_isr) {
 
   xfer_result_t xfer_result;
   switch(hresult) {
+    case HRSL_NAK:
+      if (ep->state == EP_STATE_ABORTING) {
+        ep->state = EP_STATE_IDLE;
+      } else {
+        if (ep_num == 0) {
+          // control endpoint -> retry immediately and return
+          hxfr_write(rhport, _hcd_data.hxfr, in_isr);
+          return;
+        } else if (EP_STATE_ATTEMPT_1 <= ep->state && ep->state < EP_STATE_ATTEMPT_MAX) {
+          ep->state++;
+        }
+      }
+
+      max3421_ep_t * next_ep = find_next_pending_ep(ep);
+      if (ep == next_ep) {
+        // this endpoint is only one pending -> retry immediately
+        hxfr_write(rhport, _hcd_data.hxfr, in_isr);
+      } else if (next_ep) {
+        // switch to next pending endpoint
+        // TODO could have issue with double buffered if not clear previously out data
+        xact_generic(rhport, next_ep, true, in_isr);
+      } else {
+        // no more pending in this frame -> clear busy
+        atomic_flag_clear(&_hcd_data.busy);
+      }
+      return;
+
+    case HRSL_BAD_REQ:
+      // occurred when initialized without any pending transfer. Skip for now
+      return;
+
     case HRSL_SUCCESS:
       xfer_result = XFER_RESULT_SUCCESS;
       break;
@@ -837,33 +873,6 @@ static void handle_xfer_done(uint8_t rhport, bool in_isr) {
       xfer_result = XFER_RESULT_STALLED;
       break;
 
-    case HRSL_NAK:
-      if (ep_num == 0) {
-        // control endpoint -> retry immediately
-        hxfr_write(rhport, _hcd_data.hxfr, in_isr);
-      } else {
-        if (ep->state < EP_STATE_ATTEMPT_MAX) {
-          ep->state++;
-        }
-
-        max3421_ep_t * next_ep = find_next_pending_ep(ep);
-        if (ep == next_ep) {
-          // this endpoint is only one pending -> retry immediately
-          hxfr_write(rhport, _hcd_data.hxfr, in_isr);
-        } else if (next_ep) {
-          // switch to next pending endpoint TODO could have issue with double buffered if not clear previously out data
-          xact_generic(rhport, next_ep, true, in_isr);
-        } else {
-          // no more pending in this frame -> clear busy
-          atomic_flag_clear(&_hcd_data.busy);
-        }
-      }
-      return;
-
-    case HRSL_BAD_REQ:
-      // occurred when initialized without any pending transfer. Skip for now
-      return;
-
     default:
       TU_LOG3("HRSL: %02X\r\n", hrsl);
       xfer_result = XFER_RESULT_FAILED;
@@ -942,9 +951,8 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) {
   if (hirq & HIRQ_FRAME_IRQ) {
     _hcd_data.frame_count++;
 
+    // reset all endpoints nak counter, retry with 1st pending ep.
     max3421_ep_t* ep_retry = NULL;
-
-    // reset all endpoints attempt counter
     for (size_t i = 0; i < CFG_TUH_MAX3421_ENDPOINT_TOTAL; i++) {
       max3421_ep_t* ep = &_hcd_data.ep[i];
       if (ep->packet_size && ep->state > EP_STATE_ATTEMPT_1) {
@@ -1004,7 +1012,7 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) {
 
   // clear all interrupt except SNDBAV_IRQ (never clear by us). Note RCVDAV_IRQ, HXFRDN_IRQ already clear while processing
   hirq &= (uint8_t) ~HIRQ_SNDBAV_IRQ;
-  if ( hirq ) {
+  if (hirq) {
     hirq_write(rhport, hirq, in_isr);
   }
 }
diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c
index f9ec666e5..93e1d78dd 100644
--- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c
+++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c
@@ -309,10 +309,12 @@ void dcd_disconnect(uint8_t rhport)
 
 void dcd_sof_enable(uint8_t rhport, bool en)
 {
-  (void) rhport;
-  (void) en;
-
-  // TODO implement later
+  ci_hs_regs_t* dcd_reg = CI_HS_REG(rhport);
+  if (en) {
+      dcd_reg->USBINTR |= INTR_SOF;
+  } else {
+      dcd_reg->USBINTR &= ~INTR_SOF;
+  }
 }
 
 //--------------------------------------------------------------------+
@@ -679,7 +681,8 @@ void dcd_int_handler(uint8_t rhport)
 
   if (int_status & INTR_SOF)
   {
-    dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true);
+    const uint32_t frame = dcd_reg->FRINDEX;
+    dcd_event_sof(rhport, frame, true);
   }
 }
 
diff --git a/src/portable/espressif/esp32sx/dcd_esp32sx.c b/src/portable/espressif/esp32sx/dcd_esp32sx.c
index bfc0baa56..f912bef89 100644
--- a/src/portable/espressif/esp32sx/dcd_esp32sx.c
+++ b/src/portable/espressif/esp32sx/dcd_esp32sx.c
@@ -31,7 +31,8 @@
 #if (((CFG_TUSB_MCU == OPT_MCU_ESP32S2) ||  (CFG_TUSB_MCU == OPT_MCU_ESP32S3)) && CFG_TUD_ENABLED)
 
 // Espressif
-#include "xtensa_api.h"
+#include "xtensa/xtensa_api.h"
+
 #include "esp_intr_alloc.h"
 #include "esp_log.h"
 #include "soc/dport_reg.h"
diff --git a/src/portable/microchip/samd/dcd_samd.c b/src/portable/microchip/samd/dcd_samd.c
index 976d3dfd0..005b63faf 100644
--- a/src/portable/microchip/samd/dcd_samd.c
+++ b/src/portable/microchip/samd/dcd_samd.c
@@ -183,9 +183,12 @@ void dcd_connect(uint8_t rhport)
 void dcd_sof_enable(uint8_t rhport, bool en)
 {
   (void) rhport;
-  (void) en;
 
-  // TODO implement later
+  if (en) {
+    USB->DEVICE.INTENSET.bit.SOF = 1;
+  } else {
+    USB->DEVICE.INTENCLR.bit.SOF = 1;
+  }
 }
 
 /*------------------------------------------------------------------*/
@@ -374,7 +377,9 @@ void dcd_int_handler (uint8_t rhport)
   if ( int_status & USB_DEVICE_INTFLAG_SOF )
   {
     USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_SOF;
-    dcd_event_bus_signal(0, DCD_EVENT_SOF, true);
+    const uint32_t frame = USB->DEVICE.FNUM.bit.FNUM;
+    dcd_event_sof(0, frame, true);
+    //dcd_event_bus_signal(0, DCD_EVENT_SOF, true);
   }
 
   // SAMD doesn't distinguish between Suspend and Disconnect state.
diff --git a/src/portable/nordic/nrf5x/dcd_nrf5x.c b/src/portable/nordic/nrf5x/dcd_nrf5x.c
index 2fe721d6b..abce35245 100644
--- a/src/portable/nordic/nrf5x/dcd_nrf5x.c
+++ b/src/portable/nordic/nrf5x/dcd_nrf5x.c
@@ -120,6 +120,9 @@ static struct {
 
   // nRF can only carry one DMA at a time, this is used to guard the access to EasyDMA
   atomic_flag dma_running;
+
+  // Track whether sof has been manually enabled
+  bool sof_enabled;
 } _dcd;
 
 /*------------------------------------------------------------------*/
@@ -147,7 +150,7 @@ static void start_dma(volatile uint32_t* reg_startep) {
 
 static void edpt_dma_start(volatile uint32_t* reg_startep) {
   if (atomic_flag_test_and_set(&_dcd.dma_running)) {
-    usbd_defer_func((osal_task_func_t)(uintptr_t ) edpt_dma_start, (void*) (uintptr_t) reg_startep, true);
+    usbd_defer_func((osal_task_func_t)(uintptr_t ) edpt_dma_start, (void*) (uintptr_t) reg_startep, is_in_isr());
   } else {
     start_dma(reg_startep);
   }
@@ -283,9 +286,13 @@ void dcd_connect(uint8_t rhport) {
 
 void dcd_sof_enable(uint8_t rhport, bool en) {
   (void) rhport;
-  (void) en;
-
-  // TODO implement later
+  if (en) {
+    _dcd.sof_enabled = true;
+    NRF_USBD->INTENSET = USBD_INTENSET_SOF_Msk;
+  } else {
+    _dcd.sof_enabled = false;
+    NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk;
+  }
 }
 
 //--------------------------------------------------------------------+
@@ -607,13 +614,16 @@ void dcd_int_handler(uint8_t rhport) {
       }
     }
 
-    if (!iso_enabled) {
-      // ISO endpoint is not used, SOF is only enabled one-time for remote wakeup
-      // so we disable it now
-      NRF_USBD->INTENCLR = USBD_INTENSET_SOF_Msk;
+    if (!iso_enabled && !_dcd.sof_enabled) {
+      // SOF interrupt not manually enabled and ISO endpoint is not used,
+      // SOF is only enabled one-time for remote wakeup so we disable it now
+
+      NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk;
     }
 
-    dcd_event_bus_signal(0, DCD_EVENT_SOF, true);
+    const uint32_t frame = NRF_USBD->FRAMECNTR;
+    dcd_event_sof(0, frame, true);
+    //dcd_event_bus_signal(0, DCD_EVENT_SOF, true);
   }
 
   if (int_status & USBD_INTEN_USBEVENT_Msk) {
diff --git a/src/portable/renesas/rusb2/dcd_rusb2.c b/src/portable/renesas/rusb2/dcd_rusb2.c
index 50400d1f5..7c6044ca0 100644
--- a/src/portable/renesas/rusb2/dcd_rusb2.c
+++ b/src/portable/renesas/rusb2/dcd_rusb2.c
@@ -29,10 +29,6 @@
 
 #if CFG_TUD_ENABLED && defined(TUP_USBIP_RUSB2)
 
-// Since TinyUSB doesn't use SOF for now, and this interrupt too often (1ms interval)
-// We disable SOF for now until needed later on
-#define USE_SOF     0
-
 #include "device/dcd.h"
 #include "rusb2_type.h"
 
@@ -74,6 +70,8 @@ typedef struct
 {
   pipe_state_t pipe[PIPE_COUNT];
   uint8_t ep[2][16];   /* a lookup table for a pipe index from an endpoint address */
+  // Track whether sof has been manually enabled
+  bool sof_enabled;
 } dcd_data_t;
 
 static dcd_data_t _dcd;
@@ -664,6 +662,10 @@ void dcd_init(uint8_t rhport)
   rusb2_reg_t* rusb = RUSB2_REG(rhport);
   rusb2_module_start(rhport, true);
 
+// We disable SOF for now until needed later on.
+// Since TinyUSB doesn't use SOF for now, and this interrupt often (1ms interval)
+_dcd.sof_enabled = false;
+
 #ifdef RUSB2_SUPPORT_HIGHSPEED
   if ( rusb2_is_highspeed_rhport(rhport) ) {
     rusb->SYSCFG_b.HSE = 1;
@@ -708,7 +710,7 @@ void dcd_init(uint8_t rhport)
 
   rusb->INTSTS0 = 0;
   rusb->INTENB0 = RUSB2_INTSTS0_VBINT_Msk | RUSB2_INTSTS0_BRDY_Msk | RUSB2_INTSTS0_BEMP_Msk |
-                  RUSB2_INTSTS0_DVST_Msk | RUSB2_INTSTS0_CTRT_Msk | (USE_SOF ? RUSB2_INTSTS0_SOFR_Msk : 0) |
+                  RUSB2_INTSTS0_DVST_Msk | RUSB2_INTSTS0_CTRT_Msk | (_dcd.sof_enabled ? RUSB2_INTSTS0_SOFR_Msk : 0) |
                   RUSB2_INTSTS0_RESM_Msk;
   rusb->BEMPENB = 1;
   rusb->BRDYENB = 1;
@@ -756,10 +758,9 @@ void dcd_disconnect(uint8_t rhport)
 
 void dcd_sof_enable(uint8_t rhport, bool en)
 {
-  (void) rhport;
-  (void) en;
-
-  // TODO implement later
+  rusb2_reg_t* rusb = RUSB2_REG(rhport);
+  _dcd.sof_enabled = en;
+  rusb->INTENB0_b.SOFE = en ? 1: 0;
 }
 
 //--------------------------------------------------------------------+
@@ -949,18 +950,19 @@ void dcd_int_handler(uint8_t rhport)
   // Resumed
   if ( is0 & RUSB2_INTSTS0_RESM_Msk ) {
     dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true);
-#if (0 == USE_SOF)
-    rusb->INTENB0_b.SOFE = 0;
-#endif
+    if (!_dcd.sof_enabled) {
+      rusb->INTENB0_b.SOFE = 0;
+    }
   }
 
   // SOF received
   if ( (is0 & RUSB2_INTSTS0_SOFR_Msk) && rusb->INTENB0_b.SOFE ) {
     // USBD will exit suspended mode when SOF event is received
-    dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true);
-#if (0 == USE_SOF)
-    rusb->INTENB0_b.SOFE = 0;
-#endif
+    const uint32_t frame = rusb->FRMNUM_b.FRNM;
+    dcd_event_sof(rhport, frame, true);
+    if (!_dcd.sof_enabled) {
+      rusb->INTENB0_b.SOFE = 0;
+    }
   }
 
   // Device state changes
@@ -979,9 +981,9 @@ void dcd_int_handler(uint8_t rhport)
       case RUSB2_INTSTS0_DVSQ_STATE_SUSP2:
       case RUSB2_INTSTS0_DVSQ_STATE_SUSP3:
         dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true);
-#if (0 == USE_SOF)
-        rusb->INTENB0_b.SOFE = 1;
-#endif
+        if (!_dcd.sof_enabled) {
+          rusb->INTENB0_b.SOFE = 1;
+        }
 
       default: break;
     }
diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
index a26c66892..9ce37f992 100644
--- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
@@ -102,25 +102,29 @@
 
 #include "tusb_option.h"
 
-#if CFG_TUD_ENABLED && defined(TUP_USBIP_FSDEV)
+#if CFG_TUD_ENABLED && defined(TUP_USBIP_FSDEV) && \
+    !(defined(TUP_USBIP_FSDEV_CH32) && CFG_TUD_WCH_USBIP_FSDEV == 0)
 
 #include "device/dcd.h"
 
-#ifdef TUP_USBIP_FSDEV_STM32
-// Undefine to reduce the dependence on HAL
-#undef USE_HAL_DRIVER
-#include "portable/st/stm32_fsdev/dcd_stm32_fsdev.h"
+#if defined(TUP_USBIP_FSDEV_STM32)
+  // Undefine to reduce the dependence on HAL
+  #undef USE_HAL_DRIVER
+  #include "fsdev_stm32.h"
+#elif defined(TUP_USBIP_FSDEV_CH32)
+  #include "fsdev_ch32.h"
+#else
+  #error "Unknown USB IP"
 #endif
 
-/*****************************************************
- * Configuration
- *****************************************************/
+#include "fsdev_common.h"
 
-// HW supports max of 8 bidirectional endpoints, but this can be reduced to save RAM
-// (8u here would mean 8 IN and 8 OUT)
-#ifndef MAX_EP_COUNT
-#define MAX_EP_COUNT 8U
-#endif
+//--------------------------------------------------------------------+
+// Configuration
+//--------------------------------------------------------------------+
+
+// hardware limit endpoint
+#define FSDEV_EP_COUNT 8
 
 // If sharing with CAN, one can set this to be non-zero to give CAN space where it wants it
 // Both of these MUST be a multiple of 2, and are in byte units.
@@ -132,11 +136,6 @@
 #define DCD_STM32_BTABLE_SIZE (FSDEV_PMA_SIZE - DCD_STM32_BTABLE_BASE)
 #endif
 
-/***************************************************
- * Checks, structs, defines, function definitions, etc.
- */
-
-TU_VERIFY_STATIC((MAX_EP_COUNT) <= STFSDEV_EP_COUNT, "Only 8 endpoints supported on the hardware");
 TU_VERIFY_STATIC(((DCD_STM32_BTABLE_BASE) + (DCD_STM32_BTABLE_SIZE)) <= (FSDEV_PMA_SIZE), "BTABLE does not fit in PMA RAM");
 TU_VERIFY_STATIC(((DCD_STM32_BTABLE_BASE) % 8) == 0, "BTABLE base must be aligned to 8 bytes");
 
@@ -162,9 +161,9 @@ typedef struct {
   bool allocated[2];
 } ep_alloc_t;
 
-static xfer_ctl_t xfer_status[MAX_EP_COUNT][2];
+static xfer_ctl_t xfer_status[CFG_TUD_ENDPPOINT_MAX][2];
 
-static ep_alloc_t ep_alloc_status[STFSDEV_EP_COUNT];
+static ep_alloc_t ep_alloc_status[FSDEV_EP_COUNT];
 
 static TU_ATTR_ALIGNED(4) uint32_t _setup_packet[6];
 
@@ -199,7 +198,7 @@ TU_ATTR_ALWAYS_INLINE static inline xfer_ctl_t *xfer_ctl_ptr(uint32_t ep_addr)
   uint8_t epnum = tu_edpt_number(ep_addr);
   uint8_t dir = tu_edpt_dir(ep_addr);
   // Fix -Werror=null-dereference
-  TU_ASSERT(epnum < MAX_EP_COUNT, &xfer_status[0][0]);
+  TU_ASSERT(epnum < CFG_TUD_ENDPPOINT_MAX, &xfer_status[0][0]);
 
   return &xfer_status[epnum][dir];
 }
@@ -239,7 +238,7 @@ void dcd_init(uint8_t rhport)
   USB->ISTR = 0; // Clear pending interrupts
 
   // Reset endpoints to disabled
-  for (uint32_t i = 0; i < STFSDEV_EP_COUNT; i++) {
+  for (uint32_t i = 0; i < FSDEV_EP_COUNT; i++) {
     // This doesn't clear all bits since some bits are "toggle", but does set the type to DISABLED.
     pcd_set_endpoint(USB, i, 0u);
   }
@@ -248,43 +247,9 @@ void dcd_init(uint8_t rhport)
   dcd_handle_bus_reset();
 
   // Enable pull-up if supported
-  if (dcd_connect) {
-    dcd_connect(rhport);
-  }
+  dcd_connect(rhport);
 }
 
-// Define only on MCU with internal pull-up. BSP can define on MCU without internal PU.
-#if defined(USB_BCDR_DPPU)
-
-// Disable internal D+ PU
-void dcd_disconnect(uint8_t rhport)
-{
-  (void)rhport;
-  USB->BCDR &= ~(USB_BCDR_DPPU);
-}
-
-// Enable internal D+ PU
-void dcd_connect(uint8_t rhport)
-{
-  (void)rhport;
-  USB->BCDR |= USB_BCDR_DPPU;
-}
-
-#elif defined(SYSCFG_PMC_USB_PU) // works e.g. on STM32L151
-// Disable internal D+ PU
-void dcd_disconnect(uint8_t rhport)
-{
-  (void)rhport;
-  SYSCFG->PMC &= ~(SYSCFG_PMC_USB_PU);
-}
-
-// Enable internal D+ PU
-void dcd_connect(uint8_t rhport)
-{
-  (void)rhport;
-  SYSCFG->PMC |= SYSCFG_PMC_USB_PU;
-}
-#endif
 
 void dcd_sof_enable(uint8_t rhport, bool en)
 {
@@ -298,126 +263,6 @@ void dcd_sof_enable(uint8_t rhport, bool en)
   }
 }
 
-// Enable device interrupt
-void dcd_int_enable(uint8_t rhport)
-{
-  (void)rhport;
-  // Member here forces write to RAM before allowing ISR to execute
-  __DSB();
-  __ISB();
-#if CFG_TUSB_MCU == OPT_MCU_STM32F0 || CFG_TUSB_MCU == OPT_MCU_STM32L0 || CFG_TUSB_MCU == OPT_MCU_STM32L4
-  NVIC_EnableIRQ(USB_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32L1
-  NVIC_EnableIRQ(USB_LP_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32F3
-// Some STM32F302/F303 devices allow to remap the USB interrupt vectors from
-// shared USB/CAN IRQs to separate CAN and USB IRQs.
-// This dynamically checks if this remap is active to enable the right IRQs.
-#ifdef SYSCFG_CFGR1_USB_IT_RMP
-  if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) {
-    NVIC_EnableIRQ(USB_HP_IRQn);
-    NVIC_EnableIRQ(USB_LP_IRQn);
-    NVIC_EnableIRQ(USBWakeUp_RMP_IRQn);
-  } else
-#endif
-  {
-    NVIC_EnableIRQ(USB_HP_CAN_TX_IRQn);
-    NVIC_EnableIRQ(USB_LP_CAN_RX0_IRQn);
-    NVIC_EnableIRQ(USBWakeUp_IRQn);
-  }
-#elif CFG_TUSB_MCU == OPT_MCU_STM32F1
-  NVIC_EnableIRQ(USB_HP_CAN1_TX_IRQn);
-  NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
-  NVIC_EnableIRQ(USBWakeUp_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32G4
-  NVIC_EnableIRQ(USB_HP_IRQn);
-  NVIC_EnableIRQ(USB_LP_IRQn);
-  NVIC_EnableIRQ(USBWakeUp_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
-#ifdef STM32G0B0xx
-  NVIC_EnableIRQ(USB_IRQn);
-#else
-  NVIC_EnableIRQ(USB_UCPD1_2_IRQn);
-#endif
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32H5
-  NVIC_EnableIRQ(USB_DRD_FS_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32WB
-  NVIC_EnableIRQ(USB_HP_IRQn);
-  NVIC_EnableIRQ(USB_LP_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32L5
-  NVIC_EnableIRQ(USB_FS_IRQn);
-
-#else
-#error Unknown arch in USB driver
-#endif
-}
-
-// Disable device interrupt
-void dcd_int_disable(uint8_t rhport)
-{
-  (void)rhport;
-
-#if CFG_TUSB_MCU == OPT_MCU_STM32F0 || CFG_TUSB_MCU == OPT_MCU_STM32L0 || CFG_TUSB_MCU == OPT_MCU_STM32L4
-  NVIC_DisableIRQ(USB_IRQn);
-#elif CFG_TUSB_MCU == OPT_MCU_STM32L1
-  NVIC_DisableIRQ(USB_LP_IRQn);
-#elif CFG_TUSB_MCU == OPT_MCU_STM32F3
-// Some STM32F302/F303 devices allow to remap the USB interrupt vectors from
-// shared USB/CAN IRQs to separate CAN and USB IRQs.
-// This dynamically checks if this remap is active to disable the right IRQs.
-#ifdef SYSCFG_CFGR1_USB_IT_RMP
-  if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) {
-    NVIC_DisableIRQ(USB_HP_IRQn);
-    NVIC_DisableIRQ(USB_LP_IRQn);
-    NVIC_DisableIRQ(USBWakeUp_RMP_IRQn);
-  } else
-#endif
-  {
-    NVIC_DisableIRQ(USB_HP_CAN_TX_IRQn);
-    NVIC_DisableIRQ(USB_LP_CAN_RX0_IRQn);
-    NVIC_DisableIRQ(USBWakeUp_IRQn);
-  }
-#elif CFG_TUSB_MCU == OPT_MCU_STM32F1
-  NVIC_DisableIRQ(USB_HP_CAN1_TX_IRQn);
-  NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn);
-  NVIC_DisableIRQ(USBWakeUp_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32G4
-  NVIC_DisableIRQ(USB_HP_IRQn);
-  NVIC_DisableIRQ(USB_LP_IRQn);
-  NVIC_DisableIRQ(USBWakeUp_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
-#ifdef STM32G0B0xx
-  NVIC_DisableIRQ(USB_IRQn);
-#else
-  NVIC_DisableIRQ(USB_UCPD1_2_IRQn);
-#endif
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32H5
-  NVIC_DisableIRQ(USB_DRD_FS_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32WB
-  NVIC_DisableIRQ(USB_HP_IRQn);
-  NVIC_DisableIRQ(USB_LP_IRQn);
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32L5
-  NVIC_DisableIRQ(USB_FS_IRQn);
-
-#else
-#error Unknown arch in USB driver
-#endif
-
-  // CMSIS has a membar after disabling interrupts
-}
-
 // Receive Set Address request, mcu port must also include status IN response
 void dcd_set_address(uint8_t rhport, uint8_t dev_addr)
 {
@@ -461,7 +306,7 @@ static void dcd_handle_bus_reset(void)
 {
   USB->DADDR = 0u; // disable USB peripheral by clearing the EF flag
 
-  for (uint32_t i = 0; i < STFSDEV_EP_COUNT; i++) {
+  for (uint32_t i = 0; i < FSDEV_EP_COUNT; i++) {
     // Clear EP allocation status
     ep_alloc_status[i].ep_num = 0xFF;
     ep_alloc_status[i].ep_type = 0xFF;
@@ -470,7 +315,7 @@ static void dcd_handle_bus_reset(void)
   }
 
   // Reset PMA allocation
-  ep_buf_ptr = DCD_STM32_BTABLE_BASE + 8 * MAX_EP_COUNT;
+  ep_buf_ptr = DCD_STM32_BTABLE_BASE + 8 * CFG_TUD_ENDPPOINT_MAX;
 
   dcd_edpt_open(0, &ep0OUT_desc);
   dcd_edpt_open(0, &ep0IN_desc);
@@ -653,7 +498,6 @@ static void dcd_ep_ctr_handler(void)
 
 void dcd_int_handler(uint8_t rhport)
 {
-
   (void)rhport;
 
   uint32_t int_status = USB->ISTR;
@@ -774,7 +618,7 @@ static uint8_t dcd_ep_alloc(uint8_t ep_addr, uint8_t ep_type)
   uint8_t const epnum = tu_edpt_number(ep_addr);
   uint8_t const dir = tu_edpt_dir(ep_addr);
 
-  for (uint8_t i = 0; i < STFSDEV_EP_COUNT; i++) {
+  for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) {
     // Check if already allocated
     if (ep_alloc_status[i].allocated[dir] &&
         ep_alloc_status[i].ep_type == ep_type &&
@@ -818,7 +662,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *p_endpoint_desc)
   uint16_t pma_addr;
   uint32_t wType;
 
-  TU_ASSERT(ep_idx < STFSDEV_EP_COUNT);
+  TU_ASSERT(ep_idx < FSDEV_EP_COUNT);
   TU_ASSERT(buffer_size <= 64);
 
   // Set type
@@ -865,7 +709,7 @@ void dcd_edpt_close_all(uint8_t rhport)
 {
   (void)rhport;
 
-  for (uint32_t i = 1; i < STFSDEV_EP_COUNT; i++) {
+  for (uint32_t i = 1; i < FSDEV_EP_COUNT; i++) {
     // Reset endpoint
     pcd_set_endpoint(USB, i, 0);
     // Clear EP allocation status
@@ -876,7 +720,7 @@ void dcd_edpt_close_all(uint8_t rhport)
   }
 
   // Reset PMA allocation
-  ep_buf_ptr = DCD_STM32_BTABLE_BASE + 8 * MAX_EP_COUNT + 2 * CFG_TUD_ENDPOINT0_SIZE;
+  ep_buf_ptr = DCD_STM32_BTABLE_BASE + 8 * CFG_TUD_ENDPPOINT_MAX + 2 * CFG_TUD_ENDPOINT0_SIZE;
 }
 
 /**
@@ -1155,7 +999,7 @@ static bool dcd_write_packet_memory(uint16_t dst, const void *__restrict src, ui
   }
 
   if (wNBytes) {
-    temp1 = *srcVal;
+    temp1 = (uint16_t) *srcVal;
     *pdwVal = temp1;
   }
 
diff --git a/src/portable/st/stm32_fsdev/fsdev_ch32.h b/src/portable/st/stm32_fsdev/fsdev_ch32.h
new file mode 100644
index 000000000..85fe3266c
--- /dev/null
+++ b/src/portable/st/stm32_fsdev/fsdev_ch32.h
@@ -0,0 +1,232 @@
+/*
+* The MIT License (MIT)
+ *
+ * Copyright (c) 2024, hathach (tinyusb.org)
+ *
+ * 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.
+ *
+ */
+/** © Copyright (c) 2016 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software component is licensed by ST under BSD 3-Clause license,
+  * the "License"; You may not use this file except in compliance with the
+  * License. You may obtain a copy of the License at:
+  *                        opensource.org/licenses/BSD-3-Clause
+  */
+
+#ifndef TUSB_FSDEV_CH32_H
+#define TUSB_FSDEV_CH32_H
+
+#include "common/tusb_compiler.h"
+
+#if CFG_TUSB_MCU == OPT_MCU_CH32V20X
+  #include 
+
+#elif CFG_TUSB_MCU == OPT_MCU_CH32F20X
+  #include 
+#endif
+
+#define FSDEV_PMA_SIZE (512u)
+
+// volatile 32-bit aligned
+#define _va32     volatile TU_ATTR_ALIGNED(4)
+
+typedef struct {
+  _va32 uint16_t EP0R;            // 00: USB Endpoint 0 register
+  _va32 uint16_t EP1R;            // 04: USB Endpoint 1 register
+  _va32 uint16_t EP2R;            // 08: USB Endpoint 2 register
+  _va32 uint16_t EP3R;            // 0C: USB Endpoint 3 register
+  _va32 uint16_t EP4R;            // 10: USB Endpoint 4 register
+  _va32 uint16_t EP5R;            // 14: USB Endpoint 5 register
+  _va32 uint16_t EP6R;            // 18: USB Endpoint 6 register
+  _va32 uint16_t EP7R;            // 1C: USB Endpoint 7 register
+  _va32 uint16_t RESERVED7[16];   // Reserved
+  _va32 uint16_t CNTR;            // 40: Control register
+  _va32 uint16_t ISTR;            // 44: Interrupt status register
+  _va32 uint16_t FNR;             // 48: Frame number register
+  _va32 uint16_t DADDR;           // 4C: Device address register
+  _va32 uint16_t BTABLE;          // 50: Buffer Table address register
+} USB_TypeDef;
+
+TU_VERIFY_STATIC(sizeof(USB_TypeDef) == 0x54, "Size is not correct");
+TU_VERIFY_STATIC(offsetof(USB_TypeDef, CNTR) == 0x40, "Wrong offset");
+
+#define USB_BASE            (APB1PERIPH_BASE + 0x00005C00UL) /*!< USB_IP Peripheral Registers base address */
+#define USB_PMAADDR         (APB1PERIPH_BASE + 0x00006000UL) /*!< USB_IP Packet Memory Area base address */
+#define USB                 ((USB_TypeDef *)USB_BASE)
+
+/******************************************************************************/
+/*                                                                            */
+/*                         USB Device General registers                       */
+/*                                                                            */
+/******************************************************************************/
+#define USB_CNTR                             (USB_BASE + 0x40U)             /*!< Control register */
+#define USB_ISTR                             (USB_BASE + 0x44U)             /*!< Interrupt status register */
+#define USB_FNR                              (USB_BASE + 0x48U)             /*!< Frame number register */
+#define USB_DADDR                            (USB_BASE + 0x4CU)             /*!< Device address register */
+#define USB_BTABLE                           (USB_BASE + 0x50U)             /*!< Buffer Table address register */
+
+/****************************  ISTR interrupt events  *************************/
+#define USB_ISTR_CTR                         ((uint16_t)0x8000U)               /*!< Correct TRansfer (clear-only bit) */
+#define USB_ISTR_PMAOVR                      ((uint16_t)0x4000U)               /*!< DMA OVeR/underrun (clear-only bit) */
+#define USB_ISTR_ERR                         ((uint16_t)0x2000U)               /*!< ERRor (clear-only bit) */
+#define USB_ISTR_WKUP                        ((uint16_t)0x1000U)               /*!< WaKe UP (clear-only bit) */
+#define USB_ISTR_SUSP                        ((uint16_t)0x0800U)               /*!< SUSPend (clear-only bit) */
+#define USB_ISTR_RESET                       ((uint16_t)0x0400U)               /*!< RESET (clear-only bit) */
+#define USB_ISTR_SOF                         ((uint16_t)0x0200U)               /*!< Start Of Frame (clear-only bit) */
+#define USB_ISTR_ESOF                        ((uint16_t)0x0100U)               /*!< Expected Start Of Frame (clear-only bit) */
+#define USB_ISTR_DIR                         ((uint16_t)0x0010U)               /*!< DIRection of transaction (read-only bit)  */
+#define USB_ISTR_EP_ID                       ((uint16_t)0x000FU)               /*!< EndPoint IDentifier (read-only bit)  */
+
+/* Legacy defines */
+#define USB_ISTR_PMAOVRM USB_ISTR_PMAOVR
+
+#define USB_CLR_CTR                          (~USB_ISTR_CTR)             /*!< clear Correct TRansfer bit */
+#define USB_CLR_PMAOVR                       (~USB_ISTR_PMAOVR)          /*!< clear DMA OVeR/underrun bit*/
+#define USB_CLR_ERR                          (~USB_ISTR_ERR)             /*!< clear ERRor bit */
+#define USB_CLR_WKUP                         (~USB_ISTR_WKUP)            /*!< clear WaKe UP bit */
+#define USB_CLR_SUSP                         (~USB_ISTR_SUSP)            /*!< clear SUSPend bit */
+#define USB_CLR_RESET                        (~USB_ISTR_RESET)           /*!< clear RESET bit */
+#define USB_CLR_SOF                          (~USB_ISTR_SOF)             /*!< clear Start Of Frame bit */
+#define USB_CLR_ESOF                         (~USB_ISTR_ESOF)            /*!< clear Expected Start Of Frame bit */
+
+/* Legacy defines */
+#define USB_CLR_PMAOVRM USB_CLR_PMAOVR
+
+/*************************  CNTR control register bits definitions  ***********/
+#define USB_CNTR_CTRM                        ((uint16_t)0x8000U)               /*!< Correct TRansfer Mask */
+#define USB_CNTR_PMAOVR                      ((uint16_t)0x4000U)               /*!< DMA OVeR/underrun Mask */
+#define USB_CNTR_ERRM                        ((uint16_t)0x2000U)               /*!< ERRor Mask */
+#define USB_CNTR_WKUPM                       ((uint16_t)0x1000U)               /*!< WaKe UP Mask */
+#define USB_CNTR_SUSPM                       ((uint16_t)0x0800U)               /*!< SUSPend Mask */
+#define USB_CNTR_RESETM                      ((uint16_t)0x0400U)               /*!< RESET Mask   */
+#define USB_CNTR_SOFM                        ((uint16_t)0x0200U)               /*!< Start Of Frame Mask */
+#define USB_CNTR_ESOFM                       ((uint16_t)0x0100U)               /*!< Expected Start Of Frame Mask */
+#define USB_CNTR_RESUME                      ((uint16_t)0x0010U)               /*!< RESUME request */
+#define USB_CNTR_FSUSP                       ((uint16_t)0x0008U)               /*!< Force SUSPend */
+#define USB_CNTR_LPMODE                      ((uint16_t)0x0004U)               /*!< Low-power MODE */
+#define USB_CNTR_PDWN                        ((uint16_t)0x0002U)               /*!< Power DoWN */
+#define USB_CNTR_FRES                        ((uint16_t)0x0001U)               /*!< Force USB RESet */
+
+/* Legacy defines */
+#define USB_CNTR_PMAOVRM USB_CNTR_PMAOVR
+#define USB_CNTR_LP_MODE USB_CNTR_LPMODE
+
+/********************  FNR Frame Number Register bit definitions   ************/
+#define USB_FNR_RXDP                         ((uint16_t)0x8000U)               /*!< status of D+ data line */
+#define USB_FNR_RXDM                         ((uint16_t)0x4000U)               /*!< status of D- data line */
+#define USB_FNR_LCK                          ((uint16_t)0x2000U)               /*!< LoCKed */
+#define USB_FNR_LSOF                         ((uint16_t)0x1800U)               /*!< Lost SOF */
+#define USB_FNR_FN                           ((uint16_t)0x07FFU)               /*!< Frame Number */
+
+/********************  DADDR Device ADDRess bit definitions    ****************/
+#define USB_DADDR_EF                         ((uint8_t)0x80U)                  /*!< USB device address Enable Function */
+#define USB_DADDR_ADD                        ((uint8_t)0x7FU)                  /*!< USB device address */
+
+/******************************  Endpoint register    *************************/
+#define USB_EP0R                             USB_BASE                    /*!< endpoint 0 register address */
+#define USB_EP1R                             (USB_BASE + 0x04U)           /*!< endpoint 1 register address */
+#define USB_EP2R                             (USB_BASE + 0x08U)           /*!< endpoint 2 register address */
+#define USB_EP3R                             (USB_BASE + 0x0CU)           /*!< endpoint 3 register address */
+#define USB_EP4R                             (USB_BASE + 0x10U)           /*!< endpoint 4 register address */
+#define USB_EP5R                             (USB_BASE + 0x14U)           /*!< endpoint 5 register address */
+#define USB_EP6R                             (USB_BASE + 0x18U)           /*!< endpoint 6 register address */
+#define USB_EP7R                             (USB_BASE + 0x1CU)           /*!< endpoint 7 register address */
+/* bit positions */
+#define USB_EP_CTR_RX                        ((uint16_t)0x8000U)               /*!<  EndPoint Correct TRansfer RX */
+#define USB_EP_DTOG_RX                       ((uint16_t)0x4000U)               /*!<  EndPoint Data TOGGLE RX */
+#define USB_EPRX_STAT                        ((uint16_t)0x3000U)               /*!<  EndPoint RX STATus bit field */
+#define USB_EP_SETUP                         ((uint16_t)0x0800U)               /*!<  EndPoint SETUP */
+#define USB_EP_T_FIELD                       ((uint16_t)0x0600U)               /*!<  EndPoint TYPE */
+#define USB_EP_KIND                          ((uint16_t)0x0100U)               /*!<  EndPoint KIND */
+#define USB_EP_CTR_TX                        ((uint16_t)0x0080U)               /*!<  EndPoint Correct TRansfer TX */
+#define USB_EP_DTOG_TX                       ((uint16_t)0x0040U)               /*!<  EndPoint Data TOGGLE TX */
+#define USB_EPTX_STAT                        ((uint16_t)0x0030U)               /*!<  EndPoint TX STATus bit field */
+#define USB_EPADDR_FIELD                     ((uint16_t)0x000FU)               /*!<  EndPoint ADDRess FIELD */
+
+/* EndPoint REGister MASK (no toggle fields) */
+#define USB_EPREG_MASK     (USB_EP_CTR_RX|USB_EP_SETUP|USB_EP_T_FIELD|USB_EP_KIND|USB_EP_CTR_TX|USB_EPADDR_FIELD)
+                                                                               /*!< EP_TYPE[1:0] EndPoint TYPE */
+#define USB_EP_TYPE_MASK                     ((uint16_t)0x0600U)               /*!< EndPoint TYPE Mask */
+#define USB_EP_BULK                          ((uint16_t)0x0000U)               /*!< EndPoint BULK */
+#define USB_EP_CONTROL                       ((uint16_t)0x0200U)               /*!< EndPoint CONTROL */
+#define USB_EP_ISOCHRONOUS                   ((uint16_t)0x0400U)               /*!< EndPoint ISOCHRONOUS */
+#define USB_EP_INTERRUPT                     ((uint16_t)0x0600U)               /*!< EndPoint INTERRUPT */
+#define USB_EP_T_MASK                        ((uint16_t) ~USB_EP_T_FIELD & USB_EPREG_MASK)
+
+#define USB_EPKIND_MASK                      ((uint16_t) ~USB_EP_KIND & USB_EPREG_MASK)            /*!< EP_KIND EndPoint KIND */
+                                                                               /*!< STAT_TX[1:0] STATus for TX transfer */
+#define USB_EP_TX_DIS                        ((uint16_t)0x0000U)               /*!< EndPoint TX DISabled */
+#define USB_EP_TX_STALL                      ((uint16_t)0x0010U)               /*!< EndPoint TX STALLed */
+#define USB_EP_TX_NAK                        ((uint16_t)0x0020U)               /*!< EndPoint TX NAKed */
+#define USB_EP_TX_VALID                      ((uint16_t)0x0030U)               /*!< EndPoint TX VALID */
+#define USB_EPTX_DTOG1                       ((uint16_t)0x0010U)               /*!< EndPoint TX Data TOGgle bit1 */
+#define USB_EPTX_DTOG2                       ((uint16_t)0x0020U)               /*!< EndPoint TX Data TOGgle bit2 */
+#define USB_EPTX_DTOGMASK  (USB_EPTX_STAT|USB_EPREG_MASK)
+                                                                               /*!< STAT_RX[1:0] STATus for RX transfer */
+#define USB_EP_RX_DIS                        ((uint16_t)0x0000U)               /*!< EndPoint RX DISabled */
+#define USB_EP_RX_STALL                      ((uint16_t)0x1000U)               /*!< EndPoint RX STALLed */
+#define USB_EP_RX_NAK                        ((uint16_t)0x2000U)               /*!< EndPoint RX NAKed */
+#define USB_EP_RX_VALID                      ((uint16_t)0x3000U)               /*!< EndPoint RX VALID */
+#define USB_EPRX_DTOG1                       ((uint16_t)0x1000U)               /*!< EndPoint RX Data TOGgle bit1 */
+#define USB_EPRX_DTOG2                       ((uint16_t)0x2000U)               /*!< EndPoint RX Data TOGgle bit1 */
+#define USB_EPRX_DTOGMASK  (USB_EPRX_STAT|USB_EPREG_MASK)
+
+
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
+#if CFG_TUSB_MCU == OPT_MCU_CH32V20X
+static const IRQn_Type fsdev_irq[] = {
+  USB_HP_CAN1_TX_IRQn,
+  USB_LP_CAN1_RX0_IRQn,
+  USBWakeUp_IRQn
+};
+enum { FSDEV_IRQ_NUM = TU_ARRAY_SIZE(fsdev_irq) };
+#else
+  #error "Unsupported MCU"
+#endif
+
+void dcd_int_enable(uint8_t rhport) {
+  (void)rhport;
+  for(uint8_t i=0; i < FSDEV_IRQ_NUM; i++) {
+    NVIC_EnableIRQ(fsdev_irq[i]);
+  }
+}
+
+void dcd_int_disable(uint8_t rhport) {
+  (void)rhport;
+  for(uint8_t i=0; i < FSDEV_IRQ_NUM; i++) {
+    NVIC_DisableIRQ(fsdev_irq[i]);
+  }
+}
+
+void dcd_disconnect(uint8_t rhport) {
+  (void) rhport;
+  EXTEN->EXTEN_CTR &= ~EXTEN_USBD_PU_EN;
+}
+
+void dcd_connect(uint8_t rhport) {
+  (void) rhport;
+  EXTEN->EXTEN_CTR |= EXTEN_USBD_PU_EN;
+}
+
+#endif
diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.h b/src/portable/st/stm32_fsdev/fsdev_common.h
similarity index 58%
rename from src/portable/st/stm32_fsdev/dcd_stm32_fsdev.h
rename to src/portable/st/stm32_fsdev/fsdev_common.h
index 7992f34a1..af5d8afab 100644
--- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.h
+++ b/src/portable/st/stm32_fsdev/fsdev_common.h
@@ -1,174 +1,43 @@
 /*
+ * The MIT License (MIT)
+ *
  * Copyright(c) 2016 STMicroelectronics
  * Copyright(c) N Conrad
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ * Copyright (c) 2024, hathach (tinyusb.org)
  *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *   1. Redistributions of source code must retain the above copyright notice,
- *      this list of conditions and the following disclaimer.
- *   2. Redistributions in binary form must reproduce the above copyright notice,
- *      this list of conditions and the following disclaimer in the documentation
- *      and/or other materials provided with the distribution.
- *   3. Neither the name of STMicroelectronics nor the names of its contributors
- *      may be used to endorse or promote products derived from this software
- *      without specific prior written permission.
+ * 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:
  *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 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.
  */
 
-// This file contains source copied from ST's HAL, and thus should have their copyright statement.
+#ifndef TUSB_FSDEV_COMMON_H
+#define TUSB_FSDEV_COMMON_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "stdint.h"
 
 // FSDEV_PMA_SIZE is PMA buffer size in bytes.
 // On 512-byte devices, access with a stride of two words (use every other 16-bit address)
 // On 1024-byte devices, access with a stride of one word (use every 16-bit address)
 
-#ifndef PORTABLE_ST_STM32F0_DCD_STM32F0_FSDEV_PVT_ST_H_
-#define PORTABLE_ST_STM32F0_DCD_STM32F0_FSDEV_PVT_ST_H_
-
-#if CFG_TUSB_MCU == OPT_MCU_STM32F0
-  #include "stm32f0xx.h"
-  #define FSDEV_PMA_SIZE (1024u)
-  // F0x2 models are crystal-less
-  // All have internal D+ pull-up
-  // 070RB:    2 x 16 bits/word memory     LPM Support, BCD Support
-  // PMA dedicated to USB (no sharing with CAN)
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32F1
-  #include "stm32f1xx.h"
-  #define FSDEV_PMA_SIZE (512u)
-  // NO internal Pull-ups
-  //         *B, and *C:    2 x 16 bits/word
-
-  // F1 names this differently from the rest
-  #define USB_CNTR_LPMODE   USB_CNTR_LP_MODE
-
-#elif defined(STM32F302xB) || defined(STM32F302xC) || \
-      defined(STM32F303xB) || defined(STM32F303xC) || \
-      defined(STM32F373xC)
-  #include "stm32f3xx.h"
-  #define FSDEV_PMA_SIZE (512u)
-  // NO internal Pull-ups
-  //         *B, and *C:    1 x 16 bits/word
-  // PMA dedicated to USB (no sharing with CAN)
-
-#elif defined(STM32F302x6) || defined(STM32F302x8) || \
-      defined(STM32F302xD) || defined(STM32F302xE) || \
-      defined(STM32F303xD) || defined(STM32F303xE)
-  #include "stm32f3xx.h"
-  #define FSDEV_PMA_SIZE (1024u)
-  // NO internal Pull-ups
-  // *6, *8, *D, and *E:    2 x 16 bits/word     LPM Support
-  // When CAN clock is enabled, USB can use first 768 bytes ONLY.
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32L0
-  #include "stm32l0xx.h"
-  #define FSDEV_PMA_SIZE (1024u)
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32L1
-  #include "stm32l1xx.h"
-  #define FSDEV_PMA_SIZE (512u)
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32G4
-  #include "stm32g4xx.h"
-  #define FSDEV_PMA_SIZE (1024u)
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
-  #include "stm32g0xx.h"
-  #define FSDEV_BUS_32BIT
-  #define FSDEV_PMA_SIZE (2048u)
-  #undef USB_PMAADDR
-  #define USB_PMAADDR USB_DRD_PMAADDR
-  #define USB_TypeDef USB_DRD_TypeDef
-  #define EP0R CHEP0R
-  #define USB_EP_CTR_RX USB_EP_VTRX
-  #define USB_EP_CTR_TX USB_EP_VTTX
-  #define USB_EP_T_FIELD USB_CHEP_UTYPE
-  #define USB_EPREG_MASK USB_CHEP_REG_MASK
-  #define USB_EPTX_DTOGMASK USB_CHEP_TX_DTOGMASK
-  #define USB_EPRX_DTOGMASK USB_CHEP_RX_DTOGMASK
-  #define USB_EPTX_DTOG1 USB_CHEP_TX_DTOG1
-  #define USB_EPTX_DTOG2 USB_CHEP_TX_DTOG2
-  #define USB_EPRX_DTOG1 USB_CHEP_RX_DTOG1
-  #define USB_EPRX_DTOG2 USB_CHEP_RX_DTOG2
-  #define USB_EPRX_STAT USB_CH_RX_VALID
-  #define USB_EPKIND_MASK USB_EP_KIND_MASK
-  #define USB USB_DRD_FS
-  #define USB_CNTR_FRES USB_CNTR_USBRST
-  #define USB_CNTR_RESUME USB_CNTR_L2RES
-  #define USB_ISTR_EP_ID USB_ISTR_IDN
-  #define USB_EPADDR_FIELD USB_CHEP_ADDR
-  #define USB_CNTR_LPMODE USB_CNTR_SUSPRDY
-  #define USB_CNTR_FSUSP USB_CNTR_SUSPEN
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32H5
-  #include "stm32h5xx.h"
-  #define FSDEV_BUS_32BIT
-
-  #if !defined(USB_DRD_BASE) && defined(USB_DRD_FS_BASE)
-  #define USB_DRD_BASE USB_DRD_FS_BASE
-  #endif
-
-  #define FSDEV_PMA_SIZE (2048u)
-  #undef USB_PMAADDR
-  #define USB_PMAADDR USB_DRD_PMAADDR
-  #define USB_TypeDef USB_DRD_TypeDef
-  #define EP0R CHEP0R
-  #define USB_EP_CTR_RX USB_EP_VTRX
-  #define USB_EP_CTR_TX USB_EP_VTTX
-  #define USB_EP_T_FIELD USB_CHEP_UTYPE
-  #define USB_EPREG_MASK USB_CHEP_REG_MASK
-  #define USB_EPTX_DTOGMASK USB_CHEP_TX_DTOGMASK
-  #define USB_EPRX_DTOGMASK USB_CHEP_RX_DTOGMASK
-  #define USB_EPTX_DTOG1 USB_CHEP_TX_DTOG1
-  #define USB_EPTX_DTOG2 USB_CHEP_TX_DTOG2
-  #define USB_EPRX_DTOG1 USB_CHEP_RX_DTOG1
-  #define USB_EPRX_DTOG2 USB_CHEP_RX_DTOG2
-  #define USB_EPRX_STAT USB_CH_RX_VALID
-  #define USB_EPKIND_MASK USB_EP_KIND_MASK
-  #define USB USB_DRD_FS
-  #define USB_CNTR_FRES USB_CNTR_USBRST
-  #define USB_CNTR_RESUME USB_CNTR_L2RES
-  #define USB_ISTR_EP_ID USB_ISTR_IDN
-  #define USB_EPADDR_FIELD USB_CHEP_ADDR
-  #define USB_CNTR_LPMODE USB_CNTR_SUSPRDY
-  #define USB_CNTR_FSUSP USB_CNTR_SUSPEN
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32WB
-  #include "stm32wbxx.h"
-  #define FSDEV_PMA_SIZE (1024u)
-  /* ST provided header has incorrect value */
-  #undef USB_PMAADDR
-  #define USB_PMAADDR USB1_PMAADDR
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32L4
-  #include "stm32l4xx.h"
-  #define FSDEV_PMA_SIZE (1024u)
-
-#elif CFG_TUSB_MCU == OPT_MCU_STM32L5
-  #include "stm32l5xx.h"
-  #define FSDEV_PMA_SIZE (1024u)
-
-  #ifndef USB_PMAADDR
-    #define USB_PMAADDR (USB_BASE + (USB_PMAADDR_NS - USB_BASE_NS))
-  #endif
-
-#else
-  #error You are using an untested or unimplemented STM32 variant. Please update the driver.
-  // This includes L1x0, L1x1, L1x2, L4x2 and L4x3, G1x1, G1x3, and G1x4
-#endif
-
 // For purposes of accessing the packet
 #if ((FSDEV_PMA_SIZE) == 512u)
   #define FSDEV_PMA_STRIDE  (2u)
@@ -181,24 +50,24 @@
 // The compiler should warn us if we cast it to a non-volatile type?
 #ifdef FSDEV_BUS_32BIT
 typedef uint32_t fsdev_bus_t;
-static __IO uint32_t * const pma32 = (__IO uint32_t*)USB_PMAADDR;
+static volatile uint32_t * const pma32 = (volatile uint32_t*)USB_PMAADDR;
 
 #else
 typedef uint16_t fsdev_bus_t;
 // Volatile is also needed to prevent the optimizer from changing access to 32-bit (as 32-bit access is forbidden)
-static __IO uint16_t * const pma = (__IO uint16_t*)USB_PMAADDR;
+static volatile uint16_t * const pma = (volatile uint16_t*)USB_PMAADDR;
 
-TU_ATTR_ALWAYS_INLINE static inline __IO uint16_t * pcd_btable_word_ptr(USB_TypeDef * USBx, size_t x) {
+TU_ATTR_ALWAYS_INLINE static inline volatile uint16_t * pcd_btable_word_ptr(USB_TypeDef * USBx, size_t x) {
   size_t total_word_offset = (((USBx)->BTABLE)>>1) + x;
   total_word_offset *= FSDEV_PMA_STRIDE;
   return &(pma[total_word_offset]);
 }
 
-TU_ATTR_ALWAYS_INLINE static inline __IO uint16_t* pcd_ep_tx_cnt_ptr(USB_TypeDef * USBx, uint32_t bEpIdx) {
+TU_ATTR_ALWAYS_INLINE static inline volatile uint16_t* pcd_ep_tx_cnt_ptr(USB_TypeDef * USBx, uint32_t bEpIdx) {
   return pcd_btable_word_ptr(USBx,(bEpIdx)*4u + 1u);
 }
 
-TU_ATTR_ALWAYS_INLINE static inline __IO uint16_t* pcd_ep_rx_cnt_ptr(USB_TypeDef * USBx, uint32_t bEpIdx) {
+TU_ATTR_ALWAYS_INLINE static inline volatile uint16_t* pcd_ep_rx_cnt_ptr(USB_TypeDef * USBx, uint32_t bEpIdx) {
   return pcd_btable_word_ptr(USBx,(bEpIdx)*4u + 3u);
 }
 #endif
@@ -218,10 +87,10 @@ TU_ATTR_ALWAYS_INLINE static inline uint16_t pcd_aligned_buffer_size(uint16_t si
 TU_ATTR_ALWAYS_INLINE static inline void pcd_set_endpoint(USB_TypeDef * USBx, uint32_t bEpIdx, uint32_t wRegValue) {
 #ifdef FSDEV_BUS_32BIT
   (void) USBx;
-  __O uint32_t *reg = (__O uint32_t *)(USB_DRD_BASE + bEpIdx*4);
+  volatile uint32_t *reg = (volatile uint32_t *)(USB_DRD_BASE + bEpIdx*4);
   *reg = wRegValue;
 #else
-  __O uint16_t *reg = (__O uint16_t *)((&USBx->EP0R) + bEpIdx*2u);
+  volatile uint16_t *reg = (volatile uint16_t *)((&USBx->EP0R) + bEpIdx*2u);
   *reg = (uint16_t)wRegValue;
 #endif
 }
@@ -229,9 +98,9 @@ TU_ATTR_ALWAYS_INLINE static inline void pcd_set_endpoint(USB_TypeDef * USBx, ui
 TU_ATTR_ALWAYS_INLINE static inline uint32_t pcd_get_endpoint(USB_TypeDef * USBx, uint32_t bEpIdx) {
 #ifdef FSDEV_BUS_32BIT
   (void) USBx;
-  __I uint32_t *reg = (__I uint32_t *)(USB_DRD_BASE + bEpIdx*4);
+  volatile const uint32_t *reg = (volatile const uint32_t *)(USB_DRD_BASE + bEpIdx*4);
 #else
-  __I uint16_t *reg = (__I uint16_t *)((&USBx->EP0R) + bEpIdx*2u);
+  volatile const uint16_t *reg = (volatile const uint16_t *)((&USBx->EP0R) + bEpIdx*2u);
 #endif
   return *reg;
 }
@@ -283,7 +152,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t pcd_get_ep_tx_cnt(USB_TypeDef * USB
   (void) USBx;
   return (pma32[2*bEpIdx] & 0x03FF0000) >> 16;
 #else
-  __I uint16_t *regPtr = pcd_ep_tx_cnt_ptr(USBx, bEpIdx);
+  volatile const uint16_t *regPtr = pcd_ep_tx_cnt_ptr(USBx, bEpIdx);
   return *regPtr & 0x3ffU;
 #endif
 }
@@ -293,7 +162,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t pcd_get_ep_rx_cnt(USB_TypeDef * USB
   (void) USBx;
   return (pma32[2*bEpIdx + 1] & 0x03FF0000) >> 16;
 #else
-  __I uint16_t *regPtr = pcd_ep_rx_cnt_ptr(USBx, bEpIdx);
+  volatile const uint16_t *regPtr = pcd_ep_rx_cnt_ptr(USBx, bEpIdx);
   return *regPtr & 0x3ffU;
 #endif
 }
@@ -363,7 +232,7 @@ TU_ATTR_ALWAYS_INLINE static inline void pcd_set_ep_tx_cnt(USB_TypeDef * USBx, u
   (void) USBx;
   pma32[2*bEpIdx] = (pma32[2*bEpIdx] & ~0x03FF0000u) | ((wCount & 0x3FFu) << 16);
 #else
-  __IO uint16_t * reg = pcd_ep_tx_cnt_ptr(USBx, bEpIdx);
+  volatile uint16_t * reg = pcd_ep_tx_cnt_ptr(USBx, bEpIdx);
   *reg = (uint16_t) (*reg & (uint16_t) ~0x3FFU) | (wCount & 0x3FFU);
 #endif
 }
@@ -375,7 +244,7 @@ TU_ATTR_ALWAYS_INLINE static inline void pcd_set_ep_tx_dbuf1_cnt(USB_TypeDef * U
   (void) USBx;
   pma32[2*bEpIdx + 1] = (pma32[2*bEpIdx + 1] & ~0x03FF0000u) | ((wCount & 0x3FFu) << 16);
 #else
-  __IO uint16_t * reg = pcd_ep_rx_cnt_ptr(USBx, bEpIdx);
+  volatile uint16_t * reg = pcd_ep_rx_cnt_ptr(USBx, bEpIdx);
   *reg = (uint16_t) (*reg & (uint16_t) ~0x3FFU) | (wCount & 0x3FFU);
 #endif
 }
@@ -387,7 +256,7 @@ TU_ATTR_ALWAYS_INLINE static inline void pcd_set_ep_blsize_num_blocks(USB_TypeDe
   (void) USBx;
   pma32[rxtx_idx] = (pma32[rxtx_idx] & 0x0000FFFFu) | (blocksize << 31) | ((numblocks - blocksize) << 26);
 #else
-  __IO uint16_t *pdwReg = pcd_btable_word_ptr(USBx, rxtx_idx*2u + 1u);
+  volatile uint16_t *pdwReg = pcd_btable_word_ptr(USBx, rxtx_idx*2u + 1u);
   *pdwReg = (blocksize << 15) | ((numblocks - blocksize) << 10);
 #endif
 }
@@ -472,13 +341,6 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t pcd_get_ep_rx_status(USB_TypeDef *
   return (regVal & USB_EPRX_STAT) >> (12u);
 }
 
-
-/**
-  * @brief  Toggles DTOG_RX / DTOG_TX bit in the endpoint register.
-  * @param  USBx USB peripheral instance register address.
-  * @param  bEpIdx Endpoint Number.
-  * @retval None
-  */
 TU_ATTR_ALWAYS_INLINE static inline void pcd_rx_dtog(USB_TypeDef * USBx,  uint32_t bEpIdx) {
   uint32_t regVal = pcd_get_endpoint(USBx, bEpIdx);
   regVal &= USB_EPREG_MASK;
@@ -493,12 +355,6 @@ TU_ATTR_ALWAYS_INLINE static inline void pcd_tx_dtog(USB_TypeDef * USBx,  uint32
   pcd_set_endpoint(USBx, bEpIdx, regVal);
 }
 
-/**
-  * @brief  Clears DTOG_RX / DTOG_TX bit in the endpoint register.
-  * @param  USBx USB peripheral instance register address.
-  * @param  bEpIdx Endpoint Number.
-  * @retval None
-  */
 TU_ATTR_ALWAYS_INLINE static inline void pcd_clear_rx_dtog(USB_TypeDef * USBx,  uint32_t bEpIdx) {
   uint32_t regVal = pcd_get_endpoint(USBx, bEpIdx);
   if((regVal & USB_EP_DTOG_RX) != 0) {
@@ -513,12 +369,6 @@ TU_ATTR_ALWAYS_INLINE static inline void pcd_clear_tx_dtog(USB_TypeDef * USBx,
   }
 }
 
-/**
-  * @brief  set & clear EP_KIND bit.
-  * @param  USBx USB peripheral instance register address.
-  * @param  bEpIdx Endpoint Number.
-  * @retval None
-  */
 TU_ATTR_ALWAYS_INLINE static inline void pcd_set_ep_kind(USB_TypeDef * USBx,  uint32_t bEpIdx) {
   uint32_t regVal = pcd_get_endpoint(USBx, bEpIdx);
   regVal |= USB_EP_KIND;
@@ -534,18 +384,8 @@ TU_ATTR_ALWAYS_INLINE static inline void pcd_clear_ep_kind(USB_TypeDef * USBx, u
   pcd_set_endpoint(USBx, bEpIdx, regVal);
 }
 
-// This checks if the device has "LPM"
-#if defined(USB_ISTR_L1REQ)
-#define USB_ISTR_L1REQ_FORCED (USB_ISTR_L1REQ)
-#else
-#define USB_ISTR_L1REQ_FORCED ((uint16_t)0x0000U)
+#ifdef __cplusplus
+ }
 #endif
 
-#define USB_ISTR_ALL_EVENTS (USB_ISTR_PMAOVR | USB_ISTR_ERR | USB_ISTR_WKUP | USB_ISTR_SUSP | \
-     USB_ISTR_RESET | USB_ISTR_SOF | USB_ISTR_ESOF | USB_ISTR_L1REQ_FORCED )
-
-// Number of endpoints in hardware
-// TODO should use TUP_DCD_ENDPOINT_MAX
-#define STFSDEV_EP_COUNT (8u)
-
-#endif /* PORTABLE_ST_STM32F0_DCD_STM32F0_FSDEV_PVT_ST_H_ */
+#endif
diff --git a/src/portable/st/stm32_fsdev/fsdev_stm32.h b/src/portable/st/stm32_fsdev/fsdev_stm32.h
new file mode 100644
index 000000000..b3fa11b88
--- /dev/null
+++ b/src/portable/st/stm32_fsdev/fsdev_stm32.h
@@ -0,0 +1,292 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright(c) N Conrad
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *   1. Redistributions of source code must retain the above copyright notice,
+ *      this list of conditions and the following disclaimer.
+ *   2. Redistributions in binary form must reproduce the above copyright notice,
+ *      this list of conditions and the following disclaimer in the documentation
+ *      and/or other materials provided with the distribution.
+ *   3. Neither the name of STMicroelectronics nor the names of its contributors
+ *      may be used to endorse or promote products derived from this software
+ *      without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_FSDEV_STM32_H
+#define TUSB_FSDEV_STM32_H
+
+#if CFG_TUSB_MCU == OPT_MCU_STM32F0
+  #include "stm32f0xx.h"
+  #define FSDEV_PMA_SIZE (1024u)
+  // F0x2 models are crystal-less
+  // All have internal D+ pull-up
+  // 070RB:    2 x 16 bits/word memory     LPM Support, BCD Support
+  // PMA dedicated to USB (no sharing with CAN)
+
+#elif CFG_TUSB_MCU == OPT_MCU_STM32F1
+  #include "stm32f1xx.h"
+  #define FSDEV_PMA_SIZE (512u)
+  // NO internal Pull-ups
+  //         *B, and *C:    2 x 16 bits/word
+
+  // F1 names this differently from the rest
+  #define USB_CNTR_LPMODE   USB_CNTR_LP_MODE
+
+#elif defined(STM32F302xB) || defined(STM32F302xC) || \
+      defined(STM32F303xB) || defined(STM32F303xC) || \
+      defined(STM32F373xC)
+  #include "stm32f3xx.h"
+  #define FSDEV_PMA_SIZE (512u)
+  // NO internal Pull-ups
+  //         *B, and *C:    1 x 16 bits/word
+  // PMA dedicated to USB (no sharing with CAN)
+
+#elif defined(STM32F302x6) || defined(STM32F302x8) || \
+      defined(STM32F302xD) || defined(STM32F302xE) || \
+      defined(STM32F303xD) || defined(STM32F303xE)
+  #include "stm32f3xx.h"
+  #define FSDEV_PMA_SIZE (1024u)
+  // NO internal Pull-ups
+  // *6, *8, *D, and *E:    2 x 16 bits/word     LPM Support
+  // When CAN clock is enabled, USB can use first 768 bytes ONLY.
+
+#elif CFG_TUSB_MCU == OPT_MCU_STM32L0
+  #include "stm32l0xx.h"
+  #define FSDEV_PMA_SIZE (1024u)
+
+#elif CFG_TUSB_MCU == OPT_MCU_STM32L1
+  #include "stm32l1xx.h"
+  #define FSDEV_PMA_SIZE (512u)
+
+#elif CFG_TUSB_MCU == OPT_MCU_STM32G4
+  #include "stm32g4xx.h"
+  #define FSDEV_PMA_SIZE (1024u)
+
+#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
+  #include "stm32g0xx.h"
+  #define FSDEV_BUS_32BIT
+  #define FSDEV_PMA_SIZE (2048u)
+  #undef USB_PMAADDR
+  #define USB_PMAADDR USB_DRD_PMAADDR
+  #define USB_TypeDef USB_DRD_TypeDef
+  #define EP0R CHEP0R
+  #define USB_EP_CTR_RX USB_EP_VTRX
+  #define USB_EP_CTR_TX USB_EP_VTTX
+  #define USB_EP_T_FIELD USB_CHEP_UTYPE
+  #define USB_EPREG_MASK USB_CHEP_REG_MASK
+  #define USB_EPTX_DTOGMASK USB_CHEP_TX_DTOGMASK
+  #define USB_EPRX_DTOGMASK USB_CHEP_RX_DTOGMASK
+  #define USB_EPTX_DTOG1 USB_CHEP_TX_DTOG1
+  #define USB_EPTX_DTOG2 USB_CHEP_TX_DTOG2
+  #define USB_EPRX_DTOG1 USB_CHEP_RX_DTOG1
+  #define USB_EPRX_DTOG2 USB_CHEP_RX_DTOG2
+  #define USB_EPRX_STAT USB_CH_RX_VALID
+  #define USB_EPKIND_MASK USB_EP_KIND_MASK
+  #define USB USB_DRD_FS
+  #define USB_CNTR_FRES USB_CNTR_USBRST
+  #define USB_CNTR_RESUME USB_CNTR_L2RES
+  #define USB_ISTR_EP_ID USB_ISTR_IDN
+  #define USB_EPADDR_FIELD USB_CHEP_ADDR
+  #define USB_CNTR_LPMODE USB_CNTR_SUSPRDY
+  #define USB_CNTR_FSUSP USB_CNTR_SUSPEN
+
+#elif CFG_TUSB_MCU == OPT_MCU_STM32H5
+  #include "stm32h5xx.h"
+  #define FSDEV_BUS_32BIT
+
+  #if !defined(USB_DRD_BASE) && defined(USB_DRD_FS_BASE)
+  #define USB_DRD_BASE USB_DRD_FS_BASE
+  #endif
+
+  #define FSDEV_PMA_SIZE (2048u)
+  #undef USB_PMAADDR
+  #define USB_PMAADDR USB_DRD_PMAADDR
+  #define USB_TypeDef USB_DRD_TypeDef
+  #define EP0R CHEP0R
+  #define USB_EP_CTR_RX USB_EP_VTRX
+  #define USB_EP_CTR_TX USB_EP_VTTX
+  #define USB_EP_T_FIELD USB_CHEP_UTYPE
+  #define USB_EPREG_MASK USB_CHEP_REG_MASK
+  #define USB_EPTX_DTOGMASK USB_CHEP_TX_DTOGMASK
+  #define USB_EPRX_DTOGMASK USB_CHEP_RX_DTOGMASK
+  #define USB_EPTX_DTOG1 USB_CHEP_TX_DTOG1
+  #define USB_EPTX_DTOG2 USB_CHEP_TX_DTOG2
+  #define USB_EPRX_DTOG1 USB_CHEP_RX_DTOG1
+  #define USB_EPRX_DTOG2 USB_CHEP_RX_DTOG2
+  #define USB_EPRX_STAT USB_CH_RX_VALID
+  #define USB_EPKIND_MASK USB_EP_KIND_MASK
+  #define USB USB_DRD_FS
+  #define USB_CNTR_FRES USB_CNTR_USBRST
+  #define USB_CNTR_RESUME USB_CNTR_L2RES
+  #define USB_ISTR_EP_ID USB_ISTR_IDN
+  #define USB_EPADDR_FIELD USB_CHEP_ADDR
+  #define USB_CNTR_LPMODE USB_CNTR_SUSPRDY
+  #define USB_CNTR_FSUSP USB_CNTR_SUSPEN
+
+#elif CFG_TUSB_MCU == OPT_MCU_STM32WB
+  #include "stm32wbxx.h"
+  #define FSDEV_PMA_SIZE (1024u)
+  /* ST provided header has incorrect value */
+  #undef USB_PMAADDR
+  #define USB_PMAADDR USB1_PMAADDR
+
+#elif CFG_TUSB_MCU == OPT_MCU_STM32L4
+  #include "stm32l4xx.h"
+  #define FSDEV_PMA_SIZE (1024u)
+
+#elif CFG_TUSB_MCU == OPT_MCU_STM32L5
+  #include "stm32l5xx.h"
+  #define FSDEV_PMA_SIZE (1024u)
+
+  #ifndef USB_PMAADDR
+    #define USB_PMAADDR (USB_BASE + (USB_PMAADDR_NS - USB_BASE_NS))
+  #endif
+
+#else
+  #error You are using an untested or unimplemented STM32 variant. Please update the driver.
+  // This includes L1x0, L1x1, L1x2, L4x2 and L4x3, G1x1, G1x3, and G1x4
+#endif
+
+// This checks if the device has "LPM"
+#if defined(USB_ISTR_L1REQ)
+#define USB_ISTR_L1REQ_FORCED (USB_ISTR_L1REQ)
+#else
+#define USB_ISTR_L1REQ_FORCED ((uint16_t)0x0000U)
+#endif
+
+#define USB_ISTR_ALL_EVENTS (USB_ISTR_PMAOVR | USB_ISTR_ERR | USB_ISTR_WKUP | USB_ISTR_SUSP | \
+     USB_ISTR_RESET | USB_ISTR_SOF | USB_ISTR_ESOF | USB_ISTR_L1REQ_FORCED )
+
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
+#if TU_CHECK_MCU(OPT_MCU_STM32L1) && !defined(USBWakeUp_IRQn)
+  #define USBWakeUp_IRQn USB_FS_WKUP_IRQn
+#endif
+
+static const IRQn_Type fsdev_irq[] = {
+  #if TU_CHECK_MCU(OPT_MCU_STM32F0, OPT_MCU_STM32L0, OPT_MCU_STM32L4)
+    USB_IRQn,
+  #elif CFG_TUSB_MCU == OPT_MCU_STM32F1
+    USB_HP_CAN1_TX_IRQn,
+    USB_LP_CAN1_RX0_IRQn,
+    USBWakeUp_IRQn,
+  #elif CFG_TUSB_MCU == OPT_MCU_STM32F3
+    // USB remap handles dcd functions
+    USB_HP_CAN_TX_IRQn,
+    USB_LP_CAN_RX0_IRQn,
+    USBWakeUp_IRQn,
+  #elif CFG_TUSB_MCU == OPT_MCU_STM32G0
+    #ifdef STM32G0B0xx
+    USB_IRQn,
+    #else
+    USB_UCPD1_2_IRQn,
+    #endif
+  #elif TU_CHECK_MCU(OPT_MCU_STM32G4, OPT_MCU_STM32L1)
+    USB_HP_IRQn,
+    USB_LP_IRQn,
+    USBWakeUp_IRQn,
+  #elif CFG_TUSB_MCU == OPT_MCU_STM32H5
+    USB_DRD_FS_IRQn,
+  #elif CFG_TUSB_MCU == OPT_MCU_STM32L5
+    USB_FS_IRQn,
+  #elif CFG_TUSB_MCU == OPT_MCU_STM32WB
+    USB_HP_IRQn,
+    USB_LP_IRQn,
+  #else
+    #error Unknown arch in USB driver
+  #endif
+};
+enum { FSDEV_IRQ_NUM = TU_ARRAY_SIZE(fsdev_irq) };
+
+void dcd_int_enable(uint8_t rhport) {
+  (void)rhport;
+
+  // forces write to RAM before allowing ISR to execute
+  __DSB(); __ISB();
+
+  #if CFG_TUSB_MCU == OPT_MCU_STM32F3 && defined(SYSCFG_CFGR1_USB_IT_RMP)
+  // Some STM32F302/F303 devices allow to remap the USB interrupt vectors from
+  // shared USB/CAN IRQs to separate CAN and USB IRQs.
+  // This dynamically checks if this remap is active to enable the right IRQs.
+  if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) {
+    NVIC_EnableIRQ(USB_HP_IRQn);
+    NVIC_EnableIRQ(USB_LP_IRQn);
+    NVIC_EnableIRQ(USBWakeUp_RMP_IRQn);
+  } else
+  #endif
+  {
+    for (uint8_t i = 0; i < FSDEV_IRQ_NUM; i++) {
+      NVIC_EnableIRQ(fsdev_irq[i]);
+    }
+  }
+}
+
+void dcd_int_disable(uint8_t rhport) {
+  (void)rhport;
+
+  #if CFG_TUSB_MCU == OPT_MCU_STM32F3 && defined(SYSCFG_CFGR1_USB_IT_RMP)
+  // Some STM32F302/F303 devices allow to remap the USB interrupt vectors from
+  // shared USB/CAN IRQs to separate CAN and USB IRQs.
+  // This dynamically checks if this remap is active to enable the right IRQs.
+  if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) {
+    NVIC_DisableIRQ(USB_HP_IRQn);
+    NVIC_DisableIRQ(USB_LP_IRQn);
+    NVIC_DisableIRQ(USBWakeUp_RMP_IRQn);
+  } else
+  #endif
+  {
+    for (uint8_t i = 0; i < FSDEV_IRQ_NUM; i++) {
+      NVIC_DisableIRQ(fsdev_irq[i]);
+    }
+  }
+
+  // CMSIS has a membar after disabling interrupts
+}
+
+// Define only on MCU with internal pull-up. BSP can define on MCU without internal PU.
+#if defined(USB_BCDR_DPPU)
+
+void dcd_disconnect(uint8_t rhport) {
+  (void)rhport;
+  USB->BCDR &= ~(USB_BCDR_DPPU);
+}
+
+void dcd_connect(uint8_t rhport) {
+  (void)rhport;
+  USB->BCDR |= USB_BCDR_DPPU;
+}
+
+#elif defined(SYSCFG_PMC_USB_PU) // works e.g. on STM32L151
+
+void dcd_disconnect(uint8_t rhport) {
+  (void)rhport;
+  SYSCFG->PMC &= ~(SYSCFG_PMC_USB_PU);
+}
+
+void dcd_connect(uint8_t rhport) {
+  (void)rhport;
+  SYSCFG->PMC |= SYSCFG_PMC_USB_PU;
+}
+#endif
+
+
+#endif /* TUSB_FSDEV_STM32_H */
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 692096fc8..dbcd586c5 100644
--- a/src/portable/synopsys/dwc2/dcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/dcd_dwc2.c
@@ -1195,4 +1195,25 @@ void dcd_int_handler(uint8_t rhport) {
   //  }
 }
 
+#if defined(TUP_USBIP_DWC2_TEST_MODE) && CFG_TUD_TEST_MODE
+
+bool dcd_check_test_mode_support(test_mode_t test_selector) {
+  // Check if test mode selector is unsupported
+  if (TEST_FORCE_ENABLE < test_selector || TEST_J > test_selector) {
+    return false;
+  }
+
+  return true;
+}
+
+void dcd_enter_test_mode(uint8_t rhport, test_mode_t test_selector) {
+  // Get port address...
+  dwc2_regs_t* dwc2 = DWC2_REG(rhport);
+
+  // Enable the test mode
+  dwc2->dctl = (dwc2->dctl & ~DCTL_TCTL_Msk) | (test_selector << DCTL_TCTL_Pos);
+}
+
+#endif /* TUP_USBIP_DWC2_TEST_MODE && CFG_TUD_TEST_MODE */
+
 #endif
diff --git a/src/portable/synopsys/dwc2/dwc2_type.h b/src/portable/synopsys/dwc2/dwc2_type.h
index c15771237..cb694b326 100644
--- a/src/portable/synopsys/dwc2/dwc2_type.h
+++ b/src/portable/synopsys/dwc2/dwc2_type.h
@@ -1,17 +1,34 @@
-/**
-  * @author  MCD Application Team
-  *          Ha Thach (tinyusb.org)
-  *
-  * @attention
-  *
-  * © Copyright (c) 2019 STMicroelectronics.
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024, hathach (tinyusb.org)
+ *
+ * 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.
+ *
+ */
+/** © Copyright (c) 2019 STMicroelectronics.
   * All rights reserved.
   *
   * This software component is licensed by ST under BSD 3-Clause license,
   * the "License"; You may not use this file except in compliance with the
   * License. You may obtain a copy of the License at:
   *                        opensource.org/licenses/BSD-3-Clause
-  *
   */
 
 #ifndef _TUSB_DWC2_TYPES_H_
diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h
new file mode 100644
index 000000000..0a50a6169
--- /dev/null
+++ b/src/portable/wch/ch32_usbfs_reg.h
@@ -0,0 +1,111 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Matthew Tran
+ * Copyright (c) 2024 hathach
+ *
+ * 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.
+ */
+
+#ifndef USB_CH32_USBFS_REG_H
+#define USB_CH32_USBFS_REG_H
+
+#if CFG_TUSB_MCU == OPT_MCU_CH32V307
+  #include 
+  #define USBHD_IRQn OTG_FS_IRQn
+
+#elif CFG_TUSB_MCU == OPT_MCU_CH32V20X
+  #include 
+
+#elif CFG_TUSB_MCU == OPT_MCU_CH32F20X
+  #include 
+#endif
+
+// CTRL
+#define USBFS_CTRL_DMA_EN    (1 << 0)
+#define USBFS_CTRL_CLR_ALL   (1 << 1)
+#define USBFS_CTRL_RESET_SIE (1 << 2)
+#define USBFS_CTRL_INT_BUSY  (1 << 3)
+#define USBFS_CTRL_SYS_CTRL  (1 << 4)
+#define USBFS_CTRL_DEV_PUEN  (1 << 5)
+#define USBFS_CTRL_LOW_SPEED (1 << 6)
+#define USBFS_CTRL_HOST_MODE (1 << 7)
+
+// INT_EN
+#define USBFS_INT_EN_BUS_RST  (1 << 0)
+#define USBFS_INT_EN_DETECT   (1 << 0)
+#define USBFS_INT_EN_TRANSFER (1 << 1)
+#define USBFS_INT_EN_SUSPEND  (1 << 2)
+#define USBFS_INT_EN_HST_SOF  (1 << 3)
+#define USBFS_INT_EN_FIFO_OV  (1 << 4)
+#define USBFS_INT_EN_DEV_NAK  (1 << 6)
+#define USBFS_INT_EN_DEV_SOF  (1 << 7)
+
+// INT_FG
+#define USBFS_INT_FG_BUS_RST  (1 << 0)
+#define USBFS_INT_FG_DETECT   (1 << 0)
+#define USBFS_INT_FG_TRANSFER (1 << 1)
+#define USBFS_INT_FG_SUSPEND  (1 << 2)
+#define USBFS_INT_FG_HST_SOF  (1 << 3)
+#define USBFS_INT_FG_FIFO_OV  (1 << 4)
+#define USBFS_INT_FG_SIE_FREE (1 << 5)
+#define USBFS_INT_FG_TOG_OK   (1 << 6)
+#define USBFS_INT_FG_IS_NAK   (1 << 7)
+
+// INT_ST
+#define USBFS_INT_ST_MASK_UIS_ENDP(x)  (((x) >> 0) & 0x0F)
+#define USBFS_INT_ST_MASK_UIS_TOKEN(x) (((x) >> 4) & 0x03)
+
+// UDEV_CTRL
+#define USBFS_UDEV_CTRL_PORT_EN   (1 << 0)
+#define USBFS_UDEV_CTRL_GP_BIT    (1 << 1)
+#define USBFS_UDEV_CTRL_LOW_SPEED (1 << 2)
+#define USBFS_UDEV_CTRL_DM_PIN    (1 << 4)
+#define USBFS_UDEV_CTRL_DP_PIN    (1 << 5)
+#define USBFS_UDEV_CTRL_PD_DIS    (1 << 7)
+
+// TX_CTRL
+#define USBFS_EP_T_RES_MASK (3 << 0)
+#define USBFS_EP_T_TOG      (1 << 2)
+#define USBFS_EP_T_AUTO_TOG (1 << 3)
+
+#define USBFS_EP_T_RES_ACK   (0 << 0)
+#define USBFS_EP_T_RES_NYET  (1 << 0)
+#define USBFS_EP_T_RES_NAK   (2 << 0)
+#define USBFS_EP_T_RES_STALL (3 << 0)
+
+// RX_CTRL
+#define USBFS_EP_R_RES_MASK (3 << 0)
+#define USBFS_EP_R_TOG      (1 << 2)
+#define USBFS_EP_R_AUTO_TOG (1 << 3)
+
+#define USBFS_EP_R_RES_ACK   (0 << 0)
+#define USBFS_EP_R_RES_NYET  (1 << 0)
+#define USBFS_EP_R_RES_NAK   (2 << 0)
+#define USBFS_EP_R_RES_STALL (3 << 0)
+
+// token PID
+#define PID_OUT   0
+#define PID_SOF   1
+#define PID_IN    2
+#define PID_SETUP 3
+
+#endif // USB_CH32_USBFS_REG_H
diff --git a/src/portable/wch/ch32_usbhs_reg.h b/src/portable/wch/ch32_usbhs_reg.h
index 9b956231f..130e4f223 100644
--- a/src/portable/wch/ch32_usbhs_reg.h
+++ b/src/portable/wch/ch32_usbhs_reg.h
@@ -1,10 +1,37 @@
-#ifndef _USB_CH32_USBHS_REG_H
-#define _USB_CH32_USBHS_REG_H
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Matthew Tran
+ * Copyright (c) 2024 hathach
+ *
+ * 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.
+ */
 
-#if (CFG_TUSB_MCU == OPT_MCU_CH32V307)
-#include 
-#elif (CFG_TUSB_MCU == OPT_MCU_CH32F20X)
-#include 
+#ifndef USB_CH32_USBHS_REG_H
+#define USB_CH32_USBHS_REG_H
+
+#if CFG_TUSB_MCU == OPT_MCU_CH32V307
+  #include 
+#elif CFG_TUSB_MCU == OPT_MCU_CH32F20X
+  #include 
 #endif
 
 /******************* GLOBAL ******************/
@@ -36,8 +63,13 @@
 
 // USB DEV AD
 #define USBHS_DEV_AD_OFFSET 0x03
+
 // USB FRAME_NO
 #define USBHS_FRAME_NO_OFFSET 0x04
+#define USBHS_FRAME_NO_NUM_MASK (0x7FF)
+#define USBHS_FRAME_NO_MICROFRAME_SHIFT (11)
+#define USBHS_FRAME_NO_MICROFRAME_MASK  (0x7 << USBHS_FRAME_NO_MICROFRAME_SHIFT)
+
 // USB SUSPEND
 #define USBHS_SUSPEND_OFFSET    0x06
 #define USBHS_DEV_REMOTE_WAKEUP (1 << 2)
@@ -47,7 +79,10 @@
 
 // USB SPEED TYPE
 #define USBHS_SPEED_TYPE_OFFSET 0x08
-#define USBSPEED_MASK           (0x03)
+#define USBHS_SPEED_TYPE_MASK    0x03
+#define USBHS_SPEED_TYPE_FULL    0
+#define USBHS_SPEED_TYPE_HIGH    1
+#define USBHS_SPEED_TYPE_LOW     2
 
 // USB_MIS_ST
 #define USBHS_MIS_ST_OFFSET 0x09
@@ -72,12 +107,16 @@
 #define USBHS_ISO_ACT_FLAG    (1 << 6)
 
 // INT_ST
-#define USBHS_INT_ST_OFFSET  0x0B
-#define USBHS_DEV_UIS_IS_NAK (1 << 7)
-#define USBHS_DEV_UIS_TOG_OK (1 << 6)
-#define MASK_UIS_TOKEN       (3 << 4)
-#define MASK_UIS_ENDP        (0x0F)
-#define MASK_UIS_H_RES       (0x0F)
+#define USBHS_INT_ST_OFFSET   0x0B
+#define USBHS_DEV_UIS_IS_NAK  (1 << 7)
+#define USBHS_DEV_UIS_TOG_OK  (1 << 6)
+#define MASK_UIS_TOKEN        (3 << 4)
+#define USBHS_TOKEN_PID_OUT   (0 << 4)
+#define USBHS_TOKEN_PID_SOF   (1 << 4)
+#define USBHS_TOKEN_PID_IN    (2 << 4)
+#define USBHS_TOKEN_PID_SETUP (3 << 4)
+#define MASK_UIS_ENDP         (0x0F)
+#define MASK_UIS_H_RES        (0x0F)
 
 #define USBHS_TOGGLE_OK (0x40)
 #define USBHS_HOST_RES  (0x0f)
@@ -340,10 +379,5 @@
 #define USBHS_UH_T_TOG_AUTO (1 << 5)
 #define USBHS_UH_T_DATA_NO  (1 << 6)
 
-// 00: OUT, 01:SOF, 10:IN, 11:SETUP
-#define PID_OUT   0
-#define PID_SOF   1
-#define PID_IN    2
-#define PID_SETUP 3
 
 #endif
diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c
new file mode 100644
index 000000000..9ed370b40
--- /dev/null
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -0,0 +1,344 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Matthew Tran
+ * Copyright (c) 2024 hathach
+ *
+ * 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_WCH_USBFS) && CFG_TUD_WCH_USBIP_USBFS
+
+#include "device/dcd.h"
+#include "ch32_usbfs_reg.h"
+
+/* private defines */
+#define EP_MAX (8)
+
+#define EP_DMA(ep)     ((&USBOTG_FS->UEP0_DMA)[ep])
+#define EP_TX_LEN(ep)  ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep])
+#define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep])
+#define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep])
+
+/* private data */
+struct usb_xfer {
+  bool valid;
+  uint8_t* buffer;
+  size_t len;
+  size_t processed_len;
+  size_t max_size;
+};
+
+static struct {
+  bool ep0_tog;
+  bool isochronous[EP_MAX];
+  struct usb_xfer xfer[EP_MAX][2];
+  TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
+  TU_ATTR_ALIGNED(4) struct {
+    // OUT transfers >64 bytes will overwrite queued IN data!
+    uint8_t out[64];
+    uint8_t in[1023];
+    uint8_t pad;
+  } ep3_buffer;
+} data;
+
+/* private helpers */
+static void update_in(uint8_t rhport, uint8_t ep, bool force) {
+  struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_IN];
+  if (xfer->valid) {
+    if (force || xfer->len) {
+      size_t len = TU_MIN(xfer->max_size, xfer->len);
+      if (ep == 0) {
+        memcpy(data.buffer[ep][TUSB_DIR_OUT], xfer->buffer, len); // ep0 uses same chunk
+      } else if (ep == 3) {
+        memcpy(data.ep3_buffer.in, xfer->buffer, len);
+      } else {
+        memcpy(data.buffer[ep][TUSB_DIR_IN], xfer->buffer, len);
+      }
+      xfer->buffer += len;
+      xfer->len -= len;
+      xfer->processed_len += len;
+
+      EP_TX_LEN(ep) = len;
+      if (ep == 0) {
+        EP_TX_CTRL(0) = USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0);
+        data.ep0_tog = !data.ep0_tog;
+      } else if (data.isochronous[ep]) {
+        EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NYET;
+      } else {
+        EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_ACK;
+      }
+    } else {
+      xfer->valid = false;
+      EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK;
+      dcd_event_xfer_complete(
+          rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len,
+          XFER_RESULT_SUCCESS, true);
+    }
+  }
+}
+
+static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
+  struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_OUT];
+  if (xfer->valid) {
+    size_t len = TU_MIN(xfer->max_size, TU_MIN(xfer->len, rx_len));
+    if (ep == 3) {
+      memcpy(xfer->buffer, data.ep3_buffer.out, len);
+    } else {
+      memcpy(xfer->buffer, data.buffer[ep][TUSB_DIR_OUT], len);
+    }
+    xfer->buffer += len;
+    xfer->len -= len;
+    xfer->processed_len += len;
+
+    if (xfer->len == 0 || len < xfer->max_size) {
+      xfer->valid = false;
+      dcd_event_xfer_complete(rhport, ep, xfer->processed_len, XFER_RESULT_SUCCESS, true);
+    }
+
+    if (ep == 0) {
+      EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+    }
+  }
+}
+
+/* public functions */
+void dcd_init(uint8_t rhport) {
+  // init registers
+  USBOTG_FS->BASE_CTRL = USBFS_CTRL_SYS_CTRL | USBFS_CTRL_INT_BUSY | USBFS_CTRL_DMA_EN;
+  USBOTG_FS->UDEV_CTRL = USBFS_UDEV_CTRL_PD_DIS | USBFS_UDEV_CTRL_PORT_EN;
+  USBOTG_FS->DEV_ADDR = 0x00;
+
+  USBOTG_FS->INT_FG = 0xFF;
+  USBOTG_FS->INT_EN = USBFS_INT_EN_BUS_RST | USBFS_INT_EN_TRANSFER | USBFS_INT_EN_SUSPEND;
+
+  // setup endpoint 0
+  EP_DMA(0) = (uint32_t) &data.buffer[0][0];
+  EP_TX_LEN(0) = 0;
+  EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
+  EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+
+  // enable other endpoints but NAK everything
+  USBOTG_FS->UEP4_1_MOD = 0xCC;
+  USBOTG_FS->UEP2_3_MOD = 0xCC;
+  USBOTG_FS->UEP5_6_MOD = 0xCC;
+  USBOTG_FS->UEP7_MOD = 0x0C;
+
+  for (uint8_t ep = 1; ep < EP_MAX; ep++) {
+    EP_DMA(ep) = (uint32_t) &data.buffer[ep][0];
+    EP_TX_LEN(ep) = 0;
+    EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK;
+    EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK;
+  }
+  EP_DMA(3) = (uint32_t) &data.ep3_buffer.out[0];
+
+  dcd_connect(rhport);
+}
+
+void dcd_int_handler(uint8_t rhport) {
+  (void) rhport;
+  uint8_t status = USBOTG_FS->INT_FG;
+  if (status & USBFS_INT_FG_TRANSFER) {
+    uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST);
+    uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST);
+
+    switch (token) {
+      case PID_OUT: {
+        uint16_t rx_len = USBOTG_FS->RX_LEN;
+        update_out(rhport, ep, rx_len);
+        break;
+      }
+
+      case PID_IN:
+        update_in(rhport, ep, false);
+        break;
+
+      case PID_SETUP:
+        // setup clears stall
+        EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
+        EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+
+        data.ep0_tog = true;
+        dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true);
+        break;
+    }
+
+    USBOTG_FS->INT_FG = USBFS_INT_FG_TRANSFER;
+  } else if (status & USBFS_INT_FG_BUS_RST) {
+    data.ep0_tog = true;
+    data.xfer[0][TUSB_DIR_OUT].max_size = 64;
+    data.xfer[0][TUSB_DIR_IN].max_size = 64;
+
+    dcd_event_bus_signal(rhport, DCD_EVENT_BUS_RESET, true);
+
+    USBOTG_FS->DEV_ADDR = 0x00;
+    EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+
+    USBOTG_FS->INT_FG = USBFS_INT_FG_BUS_RST;
+  } else if (status & USBFS_INT_FG_SUSPEND) {
+    dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SUSPEND};
+    dcd_event_handler(&event, true);
+    USBOTG_FS->INT_FG = USBFS_INT_FG_SUSPEND;
+  }
+}
+
+void dcd_int_enable(uint8_t rhport) {
+  (void) rhport;
+  NVIC_EnableIRQ(USBHD_IRQn);
+}
+
+void dcd_int_disable(uint8_t rhport) {
+  (void) rhport;
+  NVIC_DisableIRQ(USBHD_IRQn);
+}
+
+void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
+  (void) dev_addr;
+  dcd_edpt_xfer(rhport, 0x80, NULL, 0); // zlp status response
+}
+
+void dcd_remote_wakeup(uint8_t rhport) {
+  (void) rhport;
+  // TODO optional
+}
+
+void dcd_connect(uint8_t rhport) {
+  (void) rhport;
+  USBOTG_FS->BASE_CTRL |= USBFS_CTRL_DEV_PUEN;
+}
+
+void dcd_disconnect(uint8_t rhport) {
+  (void) rhport;
+  USBOTG_FS->BASE_CTRL &= ~USBFS_CTRL_DEV_PUEN;
+}
+
+void dcd_sof_enable(uint8_t rhport, bool en) {
+  (void) rhport;
+  (void) en;
+
+  // TODO implement later
+}
+
+void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) {
+  (void) rhport;
+  if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
+      request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
+      request->bRequest == TUSB_REQ_SET_ADDRESS) {
+    USBOTG_FS->DEV_ADDR = (uint8_t) request->wValue;
+  }
+  EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
+  EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+}
+
+bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) {
+  (void) rhport;
+  uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress);
+  uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress);
+  TU_ASSERT(ep < EP_MAX);
+
+  data.isochronous[ep] = desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS;
+  data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep);
+
+  if (ep != 0) {
+    if (dir == TUSB_DIR_OUT) {
+      if (data.isochronous[ep]) {
+        EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET;
+      } else {
+        EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_ACK;
+      }
+    } else {
+      EP_TX_LEN(ep) = 0;
+      EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK;
+    }
+  }
+  return true;
+}
+
+void dcd_edpt_close_all(uint8_t rhport) {
+  (void) rhport;
+  // TODO optional
+}
+
+void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) {
+  (void) rhport;
+  (void) ep_addr;
+  // TODO optional
+}
+
+bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) {
+  (void) rhport;
+  uint8_t ep = tu_edpt_number(ep_addr);
+  uint8_t dir = tu_edpt_dir(ep_addr);
+
+  struct usb_xfer* xfer = &data.xfer[ep][dir];
+  dcd_int_disable(rhport);
+  xfer->valid = true;
+  xfer->buffer = buffer;
+  xfer->len = total_bytes;
+  xfer->processed_len = 0;
+  dcd_int_enable(rhport);
+
+  if (dir == TUSB_DIR_IN) {
+    update_in(rhport, ep, true);
+  }
+  return true;
+}
+
+void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
+  (void) rhport;
+  uint8_t ep = tu_edpt_number(ep_addr);
+  uint8_t dir = tu_edpt_dir(ep_addr);
+  if (ep == 0) {
+    if (dir == TUSB_DIR_OUT) {
+      EP_RX_CTRL(0) = USBFS_EP_R_RES_STALL;
+    } else {
+      EP_TX_LEN(0) = 0;
+      EP_TX_CTRL(0) = USBFS_EP_T_RES_STALL;
+    }
+  } else {
+    if (dir == TUSB_DIR_OUT) {
+      EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | USBFS_EP_R_RES_STALL;
+    } else {
+      EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~USBFS_EP_T_RES_MASK) | USBFS_EP_T_RES_STALL;
+    }
+  }
+}
+
+void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
+  (void) rhport;
+  uint8_t ep = tu_edpt_number(ep_addr);
+  uint8_t dir = tu_edpt_dir(ep_addr);
+  if (ep == 0) {
+    if (dir == TUSB_DIR_OUT) {
+      EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+    }
+  } else {
+    if (dir == TUSB_DIR_OUT) {
+      EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~(USBFS_EP_R_RES_MASK | USBFS_EP_R_TOG)) | USBFS_EP_R_RES_ACK;
+    } else {
+      EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK | USBFS_EP_T_TOG)) | USBFS_EP_T_RES_NAK;
+    }
+  }
+}
+
+#endif
diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c
index 68e2179e9..622f9c508 100644
--- a/src/portable/wch/dcd_ch32_usbhs.c
+++ b/src/portable/wch/dcd_ch32_usbhs.c
@@ -2,6 +2,7 @@
  * The MIT License (MIT)
  *
  * Copyright (c) 2022 Greg Davill
+ * Copyright (c) 2023 Denis Krasutski
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -26,379 +27,394 @@
 
 #include "tusb_option.h"
 
-#if CFG_TUD_ENABLED && ((CFG_TUSB_MCU == OPT_MCU_CH32V307) || (CFG_TUSB_MCU == OPT_MCU_CH32F20X))
-#include "device/dcd.h"
-
+#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBHS) && CFG_TUD_WCH_USBIP_USBHS
 #include "ch32_usbhs_reg.h"
 
+#include "device/dcd.h"
 
 // Max number of bi-directional endpoints including EP0
-#define EP_MAX 16
+#define EP_MAX  16
 
 typedef struct {
-    uint8_t *buffer;
-    // tu_fifo_t * ff; // TODO support dcd_edpt_xfer_fifo API
-    uint16_t total_len;
-    uint16_t queued_len;
-    uint16_t max_size;
-    bool short_packet;
+  uint8_t* buffer;
+  uint16_t total_len;
+  uint16_t queued_len;
+  uint16_t max_size;
+  bool is_last_packet;
+  bool is_iso;
 } xfer_ctl_t;
 
+typedef enum {
+  EP_RESPONSE_ACK,
+  EP_RESPONSE_NAK,
+} ep_response_list_t;
+
 #define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir]
 static xfer_ctl_t xfer_status[EP_MAX][2];
 
-#define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep)*2)
-#define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep)*4)
-#define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep)*4)
-#define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep)*2)
+#define EP_TX_LEN(ep)     *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2)
+#define EP_TX_CTRL(ep)    *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4)
+#define EP_RX_CTRL(ep)    *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4)
+#define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2)
 
 #define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1))
 #define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1))
 
 /* Endpoint Buffer */
-TU_ATTR_ALIGNED(4) uint8_t EP0_DatabufHD[64];  // ep0(64)
+TU_ATTR_ALIGNED(4) static uint8_t ep0_buffer[CFG_TUD_ENDPOINT0_SIZE];
 
-volatile uint8_t USBHS_Dev_Endp0_Tog = 0x01;
+static void ep_set_response_and_toggle(uint8_t ep_num, tusb_dir_t ep_dir, ep_response_list_t response_type) {
+  if (ep_dir == TUSB_DIR_IN) {
+    uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_T_RES_ACK : USBHS_EP_T_RES_NAK;
+    if (ep_num == 0) {
+      if (response_type == EP_RESPONSE_ACK) {
+        if (EP_TX_LEN(ep_num) == 0) {
+          EP_TX_CTRL(ep_num) |= USBHS_EP_T_TOG_1;
+        } else {
+          EP_TX_CTRL(ep_num) ^= USBHS_EP_T_TOG_1;
+        }
+      }
+    }
+    if (xfer_status[ep_num][TUSB_DIR_IN].is_iso == true) {
+      EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG;
+    } else {
+      EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | response;
+    }
+  } else {
+    uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK;
+    if (ep_num == 0) {
+      if (response_type == EP_RESPONSE_ACK) {
+        if (xfer_status[ep_num][TUSB_DIR_OUT].queued_len == 0) {
+          EP_RX_CTRL(ep_num) |= USBHS_EP_R_TOG_1;
+        }
+      } else {
+        EP_RX_CTRL(ep_num) ^= USBHS_EP_R_TOG_1;
+      }
+    }
+    EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | response;
+  }
+}
 
-void dcd_init(uint8_t rhport) {
-    (void)rhport;
+static void xfer_data_packet(uint8_t ep_num, tusb_dir_t ep_dir, xfer_ctl_t* xfer) {
+  if (ep_dir == TUSB_DIR_IN) {
+    uint16_t remaining = xfer->total_len - xfer->queued_len;
+    uint16_t next_tx_size = TU_MIN(remaining, xfer->max_size);
 
-    memset(&xfer_status, 0, sizeof(xfer_status));
-
-    USBHSD->HOST_CTRL = 0x00;
-    USBHSD->HOST_CTRL = USBHS_PHY_SUSPENDM;
-
-    USBHSD->CONTROL = 0;
-
-#if TUD_OPT_HIGH_SPEED
-    USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED;
-#else
-    #error OPT_MODE_FULL_SPEED not currently supported on CH32
-    USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED;
-#endif
-
-    USBHSD->INT_EN = 0;
-    USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_DETECT_EN | USBHS_SUSPEND_EN;
-
-    /* ALL endpoint enable */
-    USBHSD->ENDP_CONFIG = 0xffffffff;
-
-    USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN;
-    USBHSD->ENDP_TYPE = 0x00;
-    USBHSD->BUF_MODE = 0x00;
-
-    USBHSD->UEP0_MAX_LEN = 64;
-
-    USBHSD->UEP0_DMA = (uint32_t)EP0_DatabufHD;
-
-    USBHSD->UEP0_TX_LEN = 0;
-    USBHSD->UEP0_TX_CTRL = USBHS_EP_T_RES_NAK;
-    USBHSD->UEP0_RX_CTRL = USBHS_EP_R_RES_ACK;
-
-    for (int ep = 1; ep < EP_MAX; ep++) {
-        EP_TX_LEN(ep) = 0;
-        EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK;
-        EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
-
-        EP_RX_MAX_LEN(ep) = 512;
+    if (ep_num == 0) {
+      memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], next_tx_size);
+    } else {
+      EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
     }
 
-    USBHSD->DEV_AD = 0;
-    USBHSD->CONTROL |= USBHS_DEV_PU_EN;
+    EP_TX_LEN(ep_num) = next_tx_size;
+    xfer->queued_len += next_tx_size;
+    if (xfer->queued_len == xfer->total_len) {
+      xfer->is_last_packet = true;
+    }
+    if (xfer->is_iso == true) {
+      /* Enable EP to generate ISA_ACT interrupt */
+      USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
+    }
+  } else { /* TUSB_DIR_OUT */
+    uint16_t left_to_receive = xfer->total_len - xfer->queued_len;
+    uint16_t max_possible_rx_size = TU_MIN(xfer->max_size, left_to_receive);
+
+    if (max_possible_rx_size == left_to_receive) {
+      xfer->is_last_packet = true;
+    }
+
+    if (ep_num > 0) {
+      EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+      EP_RX_MAX_LEN(ep_num) = max_possible_rx_size;
+    }
+  }
+  ep_set_response_and_toggle(ep_num, ep_dir, USBHS_EP_R_RES_ACK);
+}
+
+void dcd_init(uint8_t rhport) {
+  (void) rhport;
+
+  memset(&xfer_status, 0, sizeof(xfer_status));
+
+  USBHSD->HOST_CTRL = 0x00;
+  USBHSD->HOST_CTRL = USBHS_PHY_SUSPENDM;
+
+  USBHSD->CONTROL = 0;
+
+#if TUD_OPT_HIGH_SPEED
+  USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED;
+#else
+  #error OPT_MODE_FULL_SPEED not currently supported on CH32
+  USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED;
+#endif
+
+  USBHSD->INT_EN = 0;
+  USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN | USBHS_ISO_ACT_EN;
+
+  USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN;
+  USBHSD->ENDP_TYPE = 0x00;
+  USBHSD->BUF_MODE = 0x00;
+
+  for (int ep = 0; ep < EP_MAX; ep++) {
+    EP_TX_LEN(ep) = 0;
+    EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK;
+    EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+
+    EP_RX_MAX_LEN(ep) = 0;
+  }
+
+  USBHSD->UEP0_DMA = (uint32_t) ep0_buffer;
+  USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE;
+  xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE;
+  xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE;
+
+  USBHSD->DEV_AD = 0;
+  USBHSD->CONTROL |= USBHS_DEV_PU_EN;
 }
 
 void dcd_int_enable(uint8_t rhport) {
-    (void)rhport;
-
-    NVIC_EnableIRQ(USBHS_IRQn);
+  (void) rhport;
+  NVIC_EnableIRQ(USBHS_IRQn);
 }
 
 void dcd_int_disable(uint8_t rhport) {
-    (void)rhport;
-
-    NVIC_DisableIRQ(USBHS_IRQn);
+  (void) rhport;
+  NVIC_DisableIRQ(USBHS_IRQn);
 }
 
 void dcd_edpt_close_all(uint8_t rhport) {
-    (void)rhport;
+  (void) rhport;
+
+  for (size_t ep = 1; ep < EP_MAX; ep++) {
+    EP_TX_LEN(ep) = 0;
+    EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK;
+    EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+
+    EP_RX_MAX_LEN(ep) = 0;
+  }
+
+  USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN;
 }
 
 void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
-    (void)dev_addr;
+  (void) dev_addr;
 
-    // Response with zlp status
-    dcd_edpt_xfer(rhport, 0x80, NULL, 0);
+  // Response with zlp status
+  dcd_edpt_xfer(rhport, 0x80, NULL, 0);
 }
 
-void dcd_remote_wakeup(uint8_t rhport)
-{
+void dcd_remote_wakeup(uint8_t rhport) {
   (void) rhport;
 }
 
-void dcd_sof_enable(uint8_t rhport, bool en)
-{
-    (void) rhport;
-    if (en) {
-        USBHSD->INT_EN |= USBHS_SOF_ACT_EN;
-    } else {
-        USBHSD->INT_EN &= ~(USBHS_SOF_ACT_EN);
-    }
+void dcd_sof_enable(uint8_t rhport, bool en) {
+  (void) rhport;
+  if (en) {
+    USBHSD->INT_EN |= USBHS_SOF_ACT_EN;
+  } else {
+    USBHSD->INT_EN &= ~(USBHS_SOF_ACT_EN);
+  }
 }
 
-void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const *request) {
-    (void)rhport;
+void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) {
+  (void) rhport;
 
-    if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
-        request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
-        request->bRequest == TUSB_REQ_SET_ADDRESS) {
-        USBHSD->DEV_AD = (uint8_t)request->wValue;
-    }
+  if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
+      request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
+      request->bRequest == TUSB_REQ_SET_ADDRESS) {
+    USBHSD->DEV_AD = (uint8_t) request->wValue;
+  }
 
-    EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK;
-    EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK;
+  EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+  EP_RX_CTRL(0) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
 }
 
-bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_edpt) {
-    (void)rhport;
+bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
+  (void) rhport;
 
-    uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress);
-    uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress);
+  uint8_t const ep_num = tu_edpt_number(desc_edpt->bEndpointAddress);
+  tusb_dir_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress);
 
-    TU_ASSERT(epnum < EP_MAX);
-
-    xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, dir);
-    xfer->max_size = tu_edpt_packet_size(desc_edpt);
-
-    if (epnum != 0) {
-        if (tu_edpt_dir(desc_edpt->bEndpointAddress) == TUSB_DIR_OUT) {
-            EP_RX_CTRL(epnum) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_ACK;
-        } else {
-            EP_TX_LEN(epnum) = 0;
-            EP_TX_CTRL(epnum) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
-        }
-    }
+  TU_ASSERT(ep_num < EP_MAX);
 
+  if (ep_num == 0) {
     return true;
-}
+  }
 
-int usbd_ep_close(const uint8_t ep) {
-    (void)ep;
+  xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir);
+  xfer->max_size = tu_edpt_packet_size(desc_edpt);
 
-    return 0;
-}
-void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
-    (void)rhport;
-
-    uint8_t const epnum = tu_edpt_number(ep_addr);
-    uint8_t const dir = tu_edpt_dir(ep_addr);
-
-    if (epnum == 0) {
-        if (dir == TUSB_DIR_OUT) {
-            USBHSD->UEP0_RX_CTRL = USBHS_EP_R_RES_STALL;
-        } else {
-            USBHSD->UEP0_TX_LEN = 0;
-            USBHSD->UEP0_TX_CTRL = USBHS_EP_T_RES_STALL;
-        }
-    } else {
-        if (dir == TUSB_DIR_OUT) {
-            EP_RX_CTRL(epnum) = (EP_RX_CTRL(epnum) & ~USBHS_EP_R_RES_MASK) | USBHS_EP_R_RES_STALL;
-
-        } else {
-            EP_TX_CTRL(epnum) = (EP_TX_CTRL(epnum) & ~USBHS_EP_T_RES_MASK) | USBHS_EP_T_RES_STALL;
-        }
+  xfer->is_iso = (desc_edpt->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS);
+  if (dir == TUSB_DIR_OUT) {
+    USBHSD->ENDP_CONFIG |= (USBHS_EP0_R_EN << ep_num);
+    EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+    if (xfer->is_iso == true) {
+      USBHSD->ENDP_TYPE |= (USBHS_EP0_R_TYP << ep_num);
     }
+    EP_RX_MAX_LEN(ep_num) = xfer->max_size;
+  } else {
+    if (xfer->is_iso == true) {
+      USBHSD->ENDP_TYPE |= (USBHS_EP0_T_TYP << ep_num);
+    } else {
+      /* Enable all types except Isochronous to avoid ISO_ACT interrupt generation */
+      USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
+    }
+    EP_TX_LEN(ep_num) = 0;
+    EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+  }
+
+  return true;
+}
+
+void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) {
+  (void) rhport;
+
+  uint8_t const ep_num = tu_edpt_number(ep_addr);
+  tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+
+  if (dir == TUSB_DIR_OUT) {
+    EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+    EP_RX_MAX_LEN(ep_num) = 0;
+    USBHSD->ENDP_TYPE &= ~(USBHS_EP0_R_TYP << ep_num);
+    USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_R_EN << ep_num);
+  } else {  // TUSB_DIR_IN
+    EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+    EP_TX_LEN(ep_num) = 0;
+    USBHSD->ENDP_TYPE &= ~(USBHS_EP0_T_TYP << ep_num);
+    USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num);
+  }
+}
+
+void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
+  (void) rhport;
+
+  uint8_t const ep_num = tu_edpt_number(ep_addr);
+  tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+
+  if (dir == TUSB_DIR_OUT) {
+    EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_STALL;
+  } else {
+    EP_TX_LEN(0) = 0;
+    EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_STALL;
+  }
 }
 
 void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
-    (void)rhport;
+  (void) rhport;
 
-    uint8_t const epnum = tu_edpt_number(ep_addr);
-    uint8_t const dir = tu_edpt_dir(ep_addr);
+  uint8_t const ep_num = tu_edpt_number(ep_addr);
+  tusb_dir_t const dir = tu_edpt_dir(ep_addr);
 
-    if (epnum == 0) {
-        if (dir == TUSB_DIR_OUT) {
-            USBHSD->UEP0_RX_CTRL = USBHS_EP_R_RES_ACK;
-        } else {
-        }
-    } else {
-        if (dir == TUSB_DIR_OUT) {
-            EP_RX_CTRL(epnum) = (EP_RX_CTRL(epnum) & ~(USBHS_EP_R_RES_MASK | USBHS_EP_T_TOG_MASK)) | USBHS_EP_T_RES_ACK;
-
-        } else {
-            EP_TX_CTRL(epnum) = (EP_TX_CTRL(epnum) & ~(USBHS_EP_T_RES_MASK | USBHS_EP_T_TOG_MASK)) | USBHS_EP_T_RES_NAK;
-        }
-    }
+  if (dir == TUSB_DIR_OUT) {
+    EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+  } else {
+    EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_R_RES_NAK;
+  }
 }
 
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) {
-    (void)rhport;
-    uint8_t const epnum = tu_edpt_number(ep_addr);
-    uint8_t const dir = tu_edpt_dir(ep_addr);
+bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) {
+  (void) rhport;
+  uint8_t const ep_num = tu_edpt_number(ep_addr);
+  tusb_dir_t const dir = tu_edpt_dir(ep_addr);
 
-    xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, dir);
-    xfer->buffer = buffer;
-    // xfer->ff           = NULL; // TODO support dcd_edpt_xfer_fifo API
-    xfer->total_len = total_bytes;
-    xfer->queued_len = 0;
-    xfer->short_packet = false;
+  xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir);
+  xfer->buffer = buffer;
+  xfer->total_len = total_bytes;
+  xfer->queued_len = 0;
+  xfer->is_last_packet = false;
 
-    // uint16_t num_packets = (total_bytes / xfer->max_size);
-    uint16_t short_packet_size = total_bytes % (xfer->max_size + 1);
+  xfer_data_packet(ep_num, dir, xfer);
 
-    // Zero-size packet is special case.
-    if (short_packet_size == 0 || (total_bytes == 0)) {
-        xfer->short_packet = true;
-    }
-
-    if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
-        if (!total_bytes) {
-            xfer->short_packet = true;
-            if (epnum == 0) {
-                USBHSD->UEP0_TX_LEN = 0;
-                USBHSD->UEP0_TX_CTRL = USBHS_EP_T_RES_ACK | (USBHS_Dev_Endp0_Tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
-                USBHS_Dev_Endp0_Tog ^= 1;
-            } else {
-                EP_TX_LEN(epnum) = 0;
-                EP_TX_CTRL(epnum) = (EP_TX_CTRL(epnum) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_ACK;
-            }
-        } else {
-            if (epnum == 0) {
-                xfer->queued_len += short_packet_size;
-                memcpy(&EP0_DatabufHD[0], buffer, short_packet_size);
-
-                USBHSD->UEP0_TX_LEN = short_packet_size;
-                USBHSD->UEP0_TX_CTRL = USBHS_EP_T_RES_ACK | (USBHS_Dev_Endp0_Tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
-                USBHS_Dev_Endp0_Tog ^= 1;
-            } else {
-                xfer->queued_len += short_packet_size;
-
-                EP_TX_DMA_ADDR(epnum) = (uint32_t)buffer;
-                USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << epnum);
-                EP_TX_LEN(epnum) = short_packet_size;
-                EP_TX_CTRL(epnum) = (EP_TX_CTRL(epnum) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_ACK;
-            }
-        }
-    } else { /* TUSB_DIR_OUT */
-        if (epnum == 0) {
-            uint32_t read_count = USBHSD->RX_LEN;
-            read_count = TU_MIN(read_count, total_bytes);
-
-            if ((total_bytes == 8)) {
-                read_count = 8;
-                memcpy(buffer, &EP0_DatabufHD[0], 8);
-            } else {
-                memcpy(buffer, &EP0_DatabufHD[0], read_count);
-            }
-        } else {
-            EP_RX_DMA_ADDR(epnum) = (uint32_t)xfer->buffer;
-            USBHSD->ENDP_CONFIG |= (USBHS_EP0_R_EN << epnum);
-        }
-
-        // usbd_ep_read(ep_addr, buffer, total_bytes, &ret_bytes);
-    }
-    return true;
-}
-
-
-static void receive_packet(xfer_ctl_t *xfer, uint16_t xfer_size) {
-    // xfer->queued_len = xfer->total_len - remaining;
-
-    uint16_t remaining = xfer->total_len - xfer->queued_len;
-    uint16_t to_recv_size;
-
-    if (remaining <= xfer->max_size) {
-        // Avoid buffer overflow.
-        to_recv_size = (xfer_size > remaining) ? remaining : xfer_size;
-    } else {
-        // Room for full packet, choose recv_size based on what the microcontroller
-        // claims.
-        to_recv_size = (xfer_size > xfer->max_size) ? xfer->max_size : xfer_size;
-    }
-
-    if (to_recv_size) {
-    }
-
-    xfer->queued_len += xfer_size;
-
-    // Per USB spec, a short OUT packet (including length 0) is always
-    // indicative of the end of a transfer (at least for ctl, bulk, int).
-    xfer->short_packet = (xfer_size < xfer->max_size);
+  return true;
 }
 
 void dcd_int_handler(uint8_t rhport) {
-    (void)rhport;
+  (void) rhport;
 
-    uint32_t end_num, rx_token;
-    uint8_t intflag = 0;
+  uint8_t int_flag = USBHSD->INT_FG;
+  uint8_t int_status = USBHSD->INT_ST;
 
-    intflag = USBHSD->INT_FG;
+  if (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)) {
+    uint8_t const token = int_status & MASK_UIS_TOKEN;
 
-    if (intflag & USBHS_TRANSFER_FLAG) {
+    if (token == USBHS_TOKEN_PID_SOF) {
+      uint32_t frame_count = USBHSD->FRAME_NO & USBHS_FRAME_NO_NUM_MASK;
+      dcd_event_sof(rhport, frame_count, true);
+    }else {
+      uint8_t const ep_num = int_status & MASK_UIS_ENDP;
+      tusb_dir_t const ep_dir = (token == USBHS_TOKEN_PID_IN) ? TUSB_DIR_IN : TUSB_DIR_OUT;
+      uint8_t const ep_addr = tu_edpt_addr(ep_num, ep_dir);
+      xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, ep_dir);
 
-        end_num = (USBHSD->INT_ST) & MASK_UIS_ENDP;
-        rx_token = (((USBHSD->INT_ST) & MASK_UIS_TOKEN) >> 4) & 0x03;
+      if (token == USBHS_TOKEN_PID_OUT) {
+        uint16_t rx_len = USBHSD->RX_LEN;
 
-        uint8_t endp = end_num | (rx_token == PID_IN ? TUSB_DIR_IN_MASK : 0);
-
-        xfer_ctl_t *xfer = XFER_CTL_BASE(end_num, tu_edpt_dir(endp));
-
-        if (rx_token == PID_SOF) {
-            dcd_event_sof(rhport, USBHSD->FRAME_NO, true);
-
-        } else if (rx_token == PID_OUT) {
-            uint16_t rx_len = USBHSD->RX_LEN;
-
-            receive_packet(xfer, rx_len);
-
-            if (xfer->short_packet || (xfer->queued_len == xfer->total_len)) {
-                xfer->short_packet = false;
-
-                dcd_event_xfer_complete(0, endp, xfer->queued_len, XFER_RESULT_SUCCESS, true);
-            }
-
-            if (end_num == 0) {
-                USBHSD->UEP0_RX_CTRL = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0;
-            }
-
-        } else if (rx_token == PID_IN) {
-            if (xfer->short_packet || (xfer->queued_len == xfer->total_len)) {
-                xfer->short_packet = false;
-                xfer->total_len = 0;
-                dcd_event_xfer_complete(0, endp, xfer->queued_len, XFER_RESULT_SUCCESS, true);
-
-                EP_TX_CTRL(end_num) = (EP_TX_CTRL(end_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NAK;
-
-                if (end_num == 0) {
-                }
-            } else {
-                dcd_edpt_xfer(0, endp, xfer->buffer + xfer->queued_len, xfer->total_len - xfer->queued_len);
-            }
+        if (ep_num == 0) {
+          memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, rx_len);
         }
 
-        USBHSD->INT_FG = USBHS_TRANSFER_FLAG; /* Clear flag */
-    } else if (intflag & USBHS_SETUP_FLAG) {
-        USBHS_Dev_Endp0_Tog = 1;
-        dcd_event_setup_received(0, EP0_DatabufHD, true);
+        xfer->queued_len += rx_len;
+        if (rx_len < xfer->max_size) {
+          xfer->is_last_packet = true;
+        }
+      } else if (token == USBHS_TOKEN_PID_IN) {
+        if (xfer->is_iso && xfer->is_last_packet) {
+          /* Disable EP to avoid ISO_ACT interrupt generation */
+          USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num);
+        } else {
+          // Do nothing, no need to update xfer->is_last_packet, it is already updated in xfer_data_packet
+        }
+      }
 
-        USBHSD->INT_FG = USBHS_SETUP_FLAG; /* Clear flag */
-    } else if (intflag & USBHS_DETECT_FLAG) {
-        USBHS_Dev_Endp0_Tog = 1;
-
-        xfer_status[0][TUSB_DIR_OUT].max_size = 64;
-        xfer_status[0][TUSB_DIR_IN].max_size = 64;
-
-        dcd_event_bus_reset(0, TUSB_SPEED_HIGH, true);
-
-        USBHSD->DEV_AD = 0;
-        USBHSD->UEP0_RX_CTRL = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0;
-
-        USBHSD->INT_FG = USBHS_DETECT_FLAG; /* Clear flag */
-    } else if (intflag & USBHS_SUSPEND_FLAG) {
-        dcd_event_t event = { .rhport = rhport, .event_id = DCD_EVENT_SUSPEND };
-        dcd_event_handler(&event, true);
-
-        USBHSD->INT_FG = USBHS_SUSPEND_FLAG; /* Clear flag */
+      if (xfer->is_last_packet == true) {
+        ep_set_response_and_toggle(ep_num, ep_dir, EP_RESPONSE_NAK);
+        dcd_event_xfer_complete(0, ep_addr, xfer->queued_len, XFER_RESULT_SUCCESS, true);
+      } else {
+        /* prepare next part of packet to xref */
+        xfer_data_packet(ep_num, ep_dir, xfer);
+      }
     }
+
+    USBHSD->INT_FG = (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)); /* Clear flag */
+  } else if (int_flag & USBHS_SETUP_FLAG) {
+    ep_set_response_and_toggle(0, TUSB_DIR_IN, EP_RESPONSE_NAK);
+    ep_set_response_and_toggle(0, TUSB_DIR_OUT, EP_RESPONSE_NAK);
+    dcd_event_setup_received(0, ep0_buffer, true);
+
+    USBHSD->INT_FG = USBHS_SETUP_FLAG; /* Clear flag */
+  } else if (int_flag & USBHS_BUS_RST_FLAG) {
+    // TODO CH32 does not detect actual speed at this time (should be known at end of reset)
+    // This interrupt probably triggered at start of bus reset
+//    tusb_speed_t actual_speed;
+//    switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){
+//      case USBHS_SPEED_TYPE_HIGH:
+//        actual_speed = TUSB_SPEED_HIGH;
+//        break;
+//      case USBHS_SPEED_TYPE_FULL:
+//        actual_speed = TUSB_SPEED_FULL;
+//        break;
+//      case USBHS_SPEED_TYPE_LOW:
+//        actual_speed = TUSB_SPEED_LOW;
+//        break;
+//      default:
+//        TU_ASSERT(0,);
+//        break;
+//    }
+//    dcd_event_bus_reset(0, actual_speed, true);
+
+    dcd_event_bus_reset(0, TUSB_SPEED_HIGH, true);
+
+    USBHSD->DEV_AD = 0;
+    EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0;
+    EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+
+    USBHSD->INT_FG = USBHS_BUS_RST_FLAG; /* Clear flag */
+  } else if (int_flag & USBHS_SUSPEND_FLAG) {
+    dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SUSPEND};
+    dcd_event_handler(&event, true);
+
+    USBHSD->INT_FG = USBHS_SUSPEND_FLAG; /* Clear flag */
+  }
 }
 
 #endif
diff --git a/src/tusb_option.h b/src/tusb_option.h
index 8d5527936..db8b94580 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -29,14 +29,12 @@
 
 #include "common/tusb_compiler.h"
 
-// Version is release as major.minor.revision eg 1.0.0. though there could be notable APIs before a new release.
-// For notable API changes within a release, we increase the build number.
+// Version is release as major.minor.revision eg 1.0.0
 #define TUSB_VERSION_MAJOR     0
-#define TUSB_VERSION_MINOR     16
+#define TUSB_VERSION_MINOR     17
 #define TUSB_VERSION_REVISION  0
-#define TUSB_VERSION_BUILD     3
 
-#define TUSB_VERSION_NUMBER    (TUSB_VERSION_MAJOR << 24 | TUSB_VERSION_MINOR << 16 | TUSB_VERSION_REVISION << 8 | TUSB_VERSION_BUILD)
+#define TUSB_VERSION_NUMBER    (TUSB_VERSION_MAJOR * 10000 + TUSB_VERSION_MINOR * 100 + TUSB_VERSION_REVISION)
 #define TUSB_VERSION_STRING    TU_STRING(TUSB_VERSION_MAJOR) "." TU_STRING(TUSB_VERSION_MINOR) "." TU_STRING(TUSB_VERSION_REVISION)
 
 //--------------------------------------------------------------------+
@@ -123,6 +121,8 @@
 #define OPT_MCU_ESP32             902 ///< Espressif ESP32 (for host max3421e)
 #define OPT_MCU_ESP32C3           903 ///< Espressif ESP32-C3
 #define OPT_MCU_ESP32C6           904 ///< Espressif ESP32-C6
+#define OPT_MCU_ESP32C2           905 ///< Espressif ESP32-C2
+#define OPT_MCU_ESP32H2           906 ///< Espressif ESP32-H2
 #define TUP_MCU_ESPRESSIF         (CFG_TUSB_MCU >= 900 && CFG_TUSB_MCU < 1000) // check if Espressif MCU
 
 // Dialog
@@ -181,6 +181,7 @@
 // WCH
 #define OPT_MCU_CH32V307         2200 ///< WCH CH32V307
 #define OPT_MCU_CH32F20X         2210 ///< WCH CH32F20x
+#define OPT_MCU_CH32V20X         2220 ///< WCH CH32V20X
 
 
 // NXP LPC MCX
@@ -204,19 +205,9 @@
 #define OPT_OS_RTTHREAD   6  ///< RT-Thread
 #define OPT_OS_RTX4       7  ///< Keil RTX 4
 
-// Allow to use command line to change the config name/location
-#ifdef CFG_TUSB_CONFIG_FILE
-  #include CFG_TUSB_CONFIG_FILE
-#else
-  #include "tusb_config.h"
-#endif
-
-#include "common/tusb_mcu.h"
-
-//--------------------------------------------------------------------
-// RootHub Mode Configuration
-// CFG_TUSB_RHPORTx_MODE contains operation mode and speed for that port
-//--------------------------------------------------------------------
+//--------------------------------------------------------------------+
+// Mode and Speed
+//--------------------------------------------------------------------+
 
 // Low byte is operational mode
 #define OPT_MODE_NONE           0x0000 ///< Disabled
@@ -230,7 +221,24 @@
 #define OPT_MODE_HIGH_SPEED     0x0400 ///< High Speed
 #define OPT_MODE_SPEED_MASK     0xff00
 
-//------------- Roothub as Device -------------//
+//--------------------------------------------------------------------+
+// Include tusb_config.h and tusb_mcu.h
+//--------------------------------------------------------------------+
+
+// Allow to use command line to change the config name/location
+#ifdef CFG_TUSB_CONFIG_FILE
+  #include CFG_TUSB_CONFIG_FILE
+#else
+  #include "tusb_config.h"
+#endif
+
+#include "common/tusb_mcu.h"
+
+//--------------------------------------------------------------------
+// RootHub Mode detection
+//--------------------------------------------------------------------
+
+//------------- Root hub as Device -------------//
 
 #if defined(CFG_TUSB_RHPORT0_MODE) && ((CFG_TUSB_RHPORT0_MODE) & OPT_MODE_DEVICE)
   #define TUD_RHPORT_MODE     (CFG_TUSB_RHPORT0_MODE)
@@ -258,7 +266,7 @@
 // highspeed support indicator
 #define TUD_OPT_HIGH_SPEED    (CFG_TUD_MAX_SPEED ? (CFG_TUD_MAX_SPEED & OPT_MODE_HIGH_SPEED) : TUP_RHPORT_HIGHSPEED)
 
-//------------- Roothub as Host -------------//
+//------------- Root hub as Host -------------//
 
 #if defined(CFG_TUSB_RHPORT0_MODE) && ((CFG_TUSB_RHPORT0_MODE) & OPT_MODE_HOST)
   #define TUH_RHPORT_MODE  (CFG_TUSB_RHPORT0_MODE)
@@ -364,6 +372,20 @@
   #define CFG_TUD_INTERFACE_MAX   16
 #endif
 
+// default to max hardware endpoint, but can be smaller to save RAM
+#ifndef CFG_TUD_ENDPPOINT_MAX
+  #define CFG_TUD_ENDPPOINT_MAX   TUP_DCD_ENDPOINT_MAX
+#endif
+
+#if CFG_TUD_ENDPPOINT_MAX > TUP_DCD_ENDPOINT_MAX
+  #error "CFG_TUD_ENDPPOINT_MAX must be less than or equal to TUP_DCD_ENDPOINT_MAX"
+#endif
+
+// USB 2.0 compliance test mode support
+#ifndef CFG_TUD_TEST_MODE
+  #define CFG_TUD_TEST_MODE       0
+#endif
+
 //------------- Device Class Driver -------------//
 #ifndef CFG_TUD_BTH
   #define CFG_TUD_BTH             0
@@ -459,26 +481,26 @@
   #define CFG_TUH_CDC    0
 #endif
 
+// FTDI is not part of CDC class, only to re-use CDC driver API
 #ifndef CFG_TUH_CDC_FTDI
-  // FTDI is not part of CDC class, only to re-use CDC driver API
   #define CFG_TUH_CDC_FTDI 0
 #endif
 
+// List of product IDs that can use the FTDI CDC driver. 0x0403 is FTDI's VID
 #ifndef CFG_TUH_CDC_FTDI_VID_PID_LIST
-  // List of product IDs that can use the FTDI CDC driver. 0x0403 is FTDI's VID
   #define CFG_TUH_CDC_FTDI_VID_PID_LIST \
     {0x0403, 0x6001}, {0x0403, 0x6006}, {0x0403, 0x6010}, {0x0403, 0x6011}, \
     {0x0403, 0x6014}, {0x0403, 0x6015}, {0x0403, 0x8372}, {0x0403, 0xFBFA}, \
     {0x0403, 0xCD18}
 #endif
 
+// CP210X is not part of CDC class, only to re-use CDC driver API
 #ifndef CFG_TUH_CDC_CP210X
-  // CP210X is not part of CDC class, only to re-use CDC driver API
   #define CFG_TUH_CDC_CP210X 0
 #endif
 
+// List of product IDs that can use the CP210X CDC driver. 0x10C4 is Silicon Labs' VID
 #ifndef CFG_TUH_CDC_CP210X_VID_PID_LIST
-  // List of product IDs that can use the CP210X CDC driver. 0x10C4 is Silicon Labs' VID
   #define CFG_TUH_CDC_CP210X_VID_PID_LIST \
     {0x10C4, 0xEA60}, {0x10C4, 0xEA70}
 #endif
@@ -488,8 +510,8 @@
   #define CFG_TUH_CDC_CH34X 0
 #endif
 
+// List of product IDs that can use the CH34X CDC driver
 #ifndef CFG_TUH_CDC_CH34X_VID_PID_LIST
-  // List of product IDs that can use the CH34X CDC driver
   #define CFG_TUH_CDC_CH34X_VID_PID_LIST \
     { 0x1a86, 0x5523 }, /* ch341 chip */ \
     { 0x1a86, 0x7522 }, /* ch340k chip */ \
diff --git a/tools/build.py b/tools/build.py
index 967f7c95e..b937a7342 100644
--- a/tools/build.py
+++ b/tools/build.py
@@ -1,4 +1,5 @@
 import argparse
+import random
 import os
 import sys
 import time
@@ -96,32 +97,61 @@ def build_board_cmake(board, toolchain):
     return ret
 
 
-def build_family(family, toolchain, build_system):
+def build_board_make_all_examples(board, toolchain, all_examples):
+    start_time = time.monotonic()
+    ret = [0, 0, 0]
+
+    with Pool(processes=os.cpu_count()) as pool:
+        pool_args = list((map(lambda e, b=board, o=f"TOOLCHAIN={toolchain}": [e, b, o], all_examples)))
+        r = pool.starmap(build_utils.build_example, pool_args)
+        # sum all element of same index (column sum)
+        rsum = list(map(sum, list(zip(*r))))
+        ret[0] += rsum[0]
+        ret[1] += rsum[1]
+        ret[2] += rsum[2]
+    duration = time.monotonic() - start_time
+    if ret[1] == 0:
+        status = SUCCEEDED
+    else:
+        status = FAILED
+
+    flash_size = "-"
+    sram_size = "-"
+    example = 'all'
+    title = build_utils.build_format.format(example, board, status, "{:.2f}s".format(duration), flash_size, sram_size)
+    print(title)
+    return ret
+
+
+def build_family(family, toolchain, build_system, one_per_family, boards):
     all_boards = []
     for entry in os.scandir(f"hw/bsp/{family}/boards"):
         if entry.is_dir() and entry.name != 'pico_sdk':
             all_boards.append(entry.name)
     all_boards.sort()
 
-    # success, failed, skipped
     ret = [0, 0, 0]
-    if build_system == 'cmake':
-        for board in all_boards:
-            if build_board_cmake(board, toolchain):
-                ret[0] += 1
-            else:
-                ret[1] += 1
-    elif build_system == 'make':
-        all_examples = get_examples(family)
-        for example in all_examples:
-            with Pool(processes=os.cpu_count()) as pool:
-                pool_args = list((map(lambda b, e=example, o=f"TOOLCHAIN={toolchain}": [e, b, o], all_boards)))
-                r = pool.starmap(build_utils.build_example, pool_args)
-                # sum all element of same index (column sum)
-                rsum = list(map(sum, list(zip(*r))))
-                ret[0] += rsum[0]
-                ret[1] += rsum[1]
-                ret[2] += rsum[2]
+
+    # If only-one flag is set, select one random board
+    if one_per_family:
+        for b in boards:
+            # skip if -b already specify one in this family
+            if find_family(b) == family:
+                return ret
+        all_boards = [random.choice(all_boards)]
+
+    # success, failed, skipped
+    all_examples = get_examples(family)
+    for board in all_boards:
+        r = [0, 0, 0]
+        if build_system == 'cmake':
+            r = build_board_cmake(board, toolchain)
+        elif build_system == 'make':
+            r = build_board_make_all_examples(board, toolchain, all_examples)
+        ret[0] += r[0]
+        ret[1] += r[1]
+        ret[2] += r[2]
+
     return ret
 
 
@@ -131,12 +161,14 @@ def main():
     parser.add_argument('-b', '--board', action='append', default=[], help='Boards to build')
     parser.add_argument('-t', '--toolchain', default='gcc', help='Toolchain to use, default is gcc')
     parser.add_argument('-s', '--build-system', default='cmake', help='Build system to use, default is cmake')
+    parser.add_argument('-1', '--one-per-family', action='store_true', default=False, help='Build only one random board inside a family')
     args = parser.parse_args()
 
     families = args.families
     boards = args.board
     toolchain = args.toolchain
     build_system = args.build_system
+    one_per_family = args.one_per_family
 
     if len(families) == 0 and len(boards) == 0:
         print("Please specify families or board to build")
@@ -145,50 +177,42 @@ def main():
     print(build_separator)
     print(build_utils.build_format.format('Example', 'Board', '\033[39mResult\033[0m', 'Time', 'Flash', 'SRAM'))
     total_time = time.monotonic()
-    total_result = [0, 0, 0]
+    result = [0, 0, 0]
 
-    # build families: cmake, make
-    if families is not None:
-        all_families = []
-        if 'all' in families:
-            for entry in os.scandir("hw/bsp"):
-                if entry.is_dir() and entry.name != 'espressif' and os.path.isfile(entry.path + "/family.cmake"):
-                    all_families.append(entry.name)
-        else:
-            all_families = list(families)
-        all_families.sort()
+    # build families
+    all_families = []
+    if 'all' in families:
+        for entry in os.scandir("hw/bsp"):
+            if entry.is_dir() and entry.name != 'espressif' and os.path.isfile(entry.path + "/family.cmake"):
+                all_families.append(entry.name)
+    else:
+        all_families = list(families)
+    all_families.sort()
 
-        # succeeded, failed
-        for f in all_families:
-            fret = build_family(f, toolchain, build_system)
-            total_result[0] += fret[0]
-            total_result[1] += fret[1]
-            total_result[2] += fret[2]
+    # succeeded, failed
+    for f in all_families:
+        fret = build_family(f, toolchain, build_system, one_per_family, boards)
+        result[0] += fret[0]
+        result[1] += fret[1]
+        result[2] += fret[2]
 
-    # build board (only cmake)
-    if boards is not None:
-        for b in boards:
-            if build_system == 'cmake':
-                r = build_board_cmake(b, toolchain)
-                total_result[0] += r[0]
-                total_result[1] += r[1]
-                total_result[2] += r[2]
-            elif build_system == 'make':
-                all_examples = get_examples(find_family(b))
-                with Pool(processes=os.cpu_count()) as pool:
-                    pool_args = list((map(lambda e, bb=b, o=f"TOOLCHAIN={toolchain}": [e, bb, o], all_examples)))
-                    r = pool.starmap(build_utils.build_example, pool_args)
-                    # sum all element of same index (column sum)
-                    rsum = list(map(sum, list(zip(*r))))
-                    total_result[0] += rsum[0]
-                    total_result[1] += rsum[1]
-                    total_result[2] += rsum[2]
+    # build boards
+    for b in boards:
+        r = [0, 0, 0]
+        if build_system == 'cmake':
+            r = build_board_cmake(b, toolchain)
+        elif build_system == 'make':
+            all_examples = get_examples(find_family(b))
+            r = build_board_make_all_examples(b, toolchain, all_examples)
+        result[0] += r[0]
+        result[1] += r[1]
+        result[2] += r[2]
 
     total_time = time.monotonic() - total_time
     print(build_separator)
-    print(f"Build Summary: {total_result[0]} {SUCCEEDED}, {total_result[1]} {FAILED} and took {total_time:.2f}s")
+    print(f"Build Summary: {result[0]} {SUCCEEDED}, {result[1]} {FAILED} and took {total_time:.2f}s")
     print(build_separator)
-    return total_result[1]
+    return result[1]
 
 
 if __name__ == '__main__':
diff --git a/tools/gen_doc.py b/tools/gen_doc.py
index c63294588..668c77ef6 100644
--- a/tools/gen_doc.py
+++ b/tools/gen_doc.py
@@ -7,9 +7,9 @@ from get_deps import deps_all
 TOP = Path(__file__).parent.parent.resolve()
 
 
-###########################################
+# -----------------------------------------
 # Dependencies
-###########################################
+# -----------------------------------------
 
 def gen_deps_doc():
     deps_rst = Path(TOP) / "docs/reference/dependencies.rst"
diff --git a/tools/get_deps.py b/tools/get_deps.py
index 20cbe64c7..cf15126b3 100644
--- a/tools/get_deps.py
+++ b/tools/get_deps.py
@@ -168,6 +168,9 @@ deps_optional = {
     'hw/mcu/ti': ['https://github.com/hathach/ti_driver.git',
                   '143ed6cc20a7615d042b03b21e070197d473e6e5',
                   'msp430 msp432e4 tm4c'],
+    'hw/mcu/wch/ch32v20x': ['https://github.com/openwch/ch32v20x.git',
+                            'de6d68c654340d7f27b00cebbfc9aa2740a1abc2',
+                            'ch32v20x'],
     'hw/mcu/wch/ch32v307': ['https://github.com/openwch/ch32v307.git',
                             '17761f5cf9dbbf2dcf665b7c04934188add20082',
                             'ch32v307'],
@@ -242,13 +245,12 @@ def main():
     parser = argparse.ArgumentParser()
     parser.add_argument('families', nargs='*', default=[], help='Families to fetch')
     parser.add_argument('-b', '--board', action='append', default=[], help='Boards to fetch')
+    parser.add_argument('--print', action='store_true', help='Print commit hash only')
     args = parser.parse_args()
 
     families = args.families
     boards = args.board
-
-    if len(families) == 0 and len(boards) == 0:
-        print("Warning: family and board are not specified, only fetching mandatory dependencies.")
+    print_only = args.print
 
     status = 0
     deps = list(deps_mandatory.keys())
@@ -265,11 +267,21 @@ def main():
 
         for f in families:
             for d in deps_optional:
-                if f in deps_optional[d][2]:
+                if d not in deps and f in deps_optional[d][2]:
                     deps.append(d)
 
-    with Pool() as pool:
-        status = sum(pool.map(get_a_dep, deps))
+    if print_only:
+        pvalue = {}
+        # print only without arguments, always add CMSIS_5
+        if len(families) == 0 and len(boards) == 0:
+            deps.append('lib/CMSIS_5')
+        for d in deps:
+            commit = deps_all[d][1]
+            pvalue[d] = commit
+        print(pvalue)
+    else:
+        with Pool() as pool:
+            status = sum(pool.map(get_a_dep, deps))
     return status
 
 
diff --git a/tools/iar_template.ipcf b/tools/iar_template.ipcf
index e72ec0ce2..33a6ef045 100644
--- a/tools/iar_template.ipcf
+++ b/tools/iar_template.ipcf
@@ -239,6 +239,7 @@
             $TUSB_DIR$/src/portable/valentyusb/eptri/dcd_eptri.h
         
         
+            $TUSB_DIR$/src/portable/wch/dcd_ch32_usbfs.c
             $TUSB_DIR$/src/portable/wch/dcd_ch32_usbhs.c
             $TUSB_DIR$/src/portable/wch/ch32_usbhs_reg.h
         
diff --git a/tools/make_release.py b/tools/make_release.py
index 256ca8f21..126e07292 100644
--- a/tools/make_release.py
+++ b/tools/make_release.py
@@ -1,4 +1,5 @@
 import re
+import gen_doc
 
 version = '0.16.0'
 
@@ -46,4 +47,6 @@ with open(f_library_json, 'w') as f:
 # docs/info/changelog.rst
 ###################
 
+gen_doc.gen_deps_doc()
+
 print("Update docs/info/changelog.rst")