summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHa Thach <[email protected]>2026-06-22 22:20:29 +0700
committerGitHub <[email protected]>2026-06-22 22:20:29 +0700
commitcd3561bf158afd5a5718904b8139a338d1e3b67c (patch)
tree4b9d46e8b6397ba050d7fba159a67efc0d511eca
parent299c0a55629691f6bbded895a4be377633e815ab (diff)
parent706e4a5daaf8bbb55f0a7d00b69d4bc3c4cd70eb (diff)
Merge pull request #3515 from alt-0191/ch58x
Add support for WCH ch582/583 series
-rwxr-xr-x.github/workflows/ci_set_matrix.py1
-rw-r--r--README.rst4
-rw-r--r--docs/reference/boards.rst1
-rw-r--r--examples/device/audio_4_channel_mic/skip.txt1
-rw-r--r--examples/device/audio_4_channel_mic_freertos/skip.txt1
-rw-r--r--examples/device/audio_test/skip.txt1
-rw-r--r--examples/device/audio_test_freertos/skip.txt1
-rw-r--r--examples/device/audio_test_multi_rate/skip.txt1
-rw-r--r--examples/device/cdc_msc_freertos/skip.txt1
-rw-r--r--examples/device/cdc_uac2/skip.txt1
-rw-r--r--examples/device/dfu_runtime/skip.txt1
-rw-r--r--examples/device/hid_boot_interface/skip.txt1
-rw-r--r--examples/device/hid_composite/skip.txt1
-rw-r--r--examples/device/hid_composite_freertos/skip.txt1
-rw-r--r--examples/device/hid_generic_inout/skip.txt1
-rw-r--r--examples/device/hid_multiple_interface/skip.txt1
-rw-r--r--examples/device/midi_test/skip.txt1
-rw-r--r--examples/device/midi_test_freertos/skip.txt1
-rw-r--r--examples/device/uac2_headset/skip.txt1
-rw-r--r--examples/device/uac2_speaker_fb/skip.txt1
-rw-r--r--examples/device/video_capture/skip.txt1
-rw-r--r--examples/device/video_capture_2ch/skip.txt1
-rw-r--r--examples/device/webusb_serial/skip.txt1
-rw-r--r--hw/bsp/ch583/boards/ch582m_evt/board.cmake5
-rw-r--r--hw/bsp/ch583/boards/ch582m_evt/board.h61
-rw-r--r--hw/bsp/ch583/boards/ch582m_evt/board.mk3
-rw-r--r--hw/bsp/ch583/boards/yd-ch582m/board.cmake5
-rw-r--r--hw/bsp/ch583/boards/yd-ch582m/board.h61
-rw-r--r--hw/bsp/ch583/boards/yd-ch582m/board.mk3
-rw-r--r--hw/bsp/ch583/ch583_it.c37
-rw-r--r--hw/bsp/ch583/ch583_it.h46
-rw-r--r--hw/bsp/ch583/debug_uart.c86
-rw-r--r--hw/bsp/ch583/debug_uart.h44
-rw-r--r--hw/bsp/ch583/family.c194
-rw-r--r--hw/bsp/ch583/family.cmake108
-rw-r--r--hw/bsp/ch583/family.mk59
-rw-r--r--hw/bsp/ch583/linker/ch582.ld167
-rw-r--r--hw/bsp/ch583/system_ch583.c39
-rw-r--r--hw/bsp/ch583/system_ch583.h43
-rw-r--r--hw/bsp/ch583/wch-riscv.cfg17
-rw-r--r--src/common/tusb_mcu.h13
-rw-r--r--src/portable/wch/ch32_usbfs_reg.h71
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c221
-rw-r--r--src/tusb_option.h2
-rwxr-xr-xtest/hil/hil_test.py6
-rw-r--r--test/hil/tinyusb.json15
-rwxr-xr-xtools/get_deps.py3
47 files changed, 1303 insertions, 32 deletions
diff --git a/.github/workflows/ci_set_matrix.py b/.github/workflows/ci_set_matrix.py
index f4d25fd37..dc0d3871f 100755
--- a/.github/workflows/ci_set_matrix.py
+++ b/.github/workflows/ci_set_matrix.py
@@ -30,6 +30,7 @@ family_list = {
"ch32v10x": ["riscv-gcc"],
"ch32v20x": ["riscv-gcc"],
"ch32v30x": ["riscv-gcc"],
+ "ch583": ["riscv-gcc"],
"da1469x": ["arm-gcc"],
"fomu": ["riscv-gcc"],
"ft9xx": ["ft9xx-gcc"],
diff --git a/README.rst b/README.rst
index 38a77f8f6..ea71e1adc 100644
--- a/README.rst
+++ b/README.rst
@@ -310,6 +310,10 @@ Supported CPUs
| | CH32V20x | 🟡 | | ❌ | stm32_fsdev/ch32_usbfs | ISO data loss [2]_ |
| +-----------------------------+--------+------+-----------+------------------------+--------------------+
| | CH32V305, CH32V307 | 🟡 | | ✅ | ch32_usbfs/hs | ISO data loss [2]_ |
+| +-----------------------------+--------+------+-----------+------------------------+--------------------+
+| | CH32V103 | 🟡 | | ❌ | ch32_usbfs | ISO data loss [2]_ |
+| +-----------------------------+--------+------+-----------+------------------------+--------------------+
+| | CH583, CH582 | 🟡 | | ❌ | ch32_usbfs | Device only |
+--------------+-----------------------------+--------+------+-----------+------------------------+--------------------+
Table Legend
diff --git a/docs/reference/boards.rst b/docs/reference/boards.rst
index b0e8bffaa..8a83496a4 100644
--- a/docs/reference/boards.rst
+++ b/docs/reference/boards.rst
@@ -370,4 +370,5 @@ ch32v203g_r0_1v0 CH32V203G-R0-1v0 ch32v20x https://github.com/openwch/ch32v20
nanoch32v203 nanoCH32V203 ch32v20x https://github.com/wuxx/nanoCH32V203
ch32v307v_r1_1v0 CH32V307V-R1-1v0 ch32v30x https://github.com/openwch/ch32v307/tree/main/SCHPCB/CH32V307V-R1-1v0
nanoch32v305 nanoCH32V305 ch32v30x https://github.com/wuxx/nanoCH32V305
+yd-ch582m yd-ch582m ch583 http://vcc-gnd.com
================ ================ ======== ===================================================================== ======
diff --git a/examples/device/audio_4_channel_mic/skip.txt b/examples/device/audio_4_channel_mic/skip.txt
index 157605df1..3ca433c08 100644
--- a/examples/device/audio_4_channel_mic/skip.txt
+++ b/examples/device/audio_4_channel_mic/skip.txt
@@ -3,3 +3,4 @@ mcu:SAME5X
mcu:SAMG
family:broadcom_64bit
family:espressif
+mcu:CH583
diff --git a/examples/device/audio_4_channel_mic_freertos/skip.txt b/examples/device/audio_4_channel_mic_freertos/skip.txt
index 61a7b0605..1fd6b4b8a 100644
--- a/examples/device/audio_4_channel_mic_freertos/skip.txt
+++ b/examples/device/audio_4_channel_mic_freertos/skip.txt
@@ -2,6 +2,7 @@ mcu:CH32F20X
mcu:CH32V103
mcu:CH32V20X
mcu:CH32V307
+mcu:CH583
mcu:CXD56
mcu:F1C100S
mcu:GD32VF103
diff --git a/examples/device/audio_test/skip.txt b/examples/device/audio_test/skip.txt
index 65b137814..42394bb11 100644
--- a/examples/device/audio_test/skip.txt
+++ b/examples/device/audio_test/skip.txt
@@ -2,3 +2,4 @@ mcu:SAMD11
mcu:SAME5X
mcu:SAMG
family:espressif
+mcu:CH583
diff --git a/examples/device/audio_test_freertos/skip.txt b/examples/device/audio_test_freertos/skip.txt
index 386a0cdfb..660bacd25 100644
--- a/examples/device/audio_test_freertos/skip.txt
+++ b/examples/device/audio_test_freertos/skip.txt
@@ -2,6 +2,7 @@ mcu:CH32F20X
mcu:CH32V103
mcu:CH32V20X
mcu:CH32V307
+mcu:CH583
mcu:CXD56
mcu:F1C100S
mcu:GD32VF103
diff --git a/examples/device/audio_test_multi_rate/skip.txt b/examples/device/audio_test_multi_rate/skip.txt
index 65b137814..42394bb11 100644
--- a/examples/device/audio_test_multi_rate/skip.txt
+++ b/examples/device/audio_test_multi_rate/skip.txt
@@ -2,3 +2,4 @@ mcu:SAMD11
mcu:SAME5X
mcu:SAMG
family:espressif
+mcu:CH583
diff --git a/examples/device/cdc_msc_freertos/skip.txt b/examples/device/cdc_msc_freertos/skip.txt
index 429c62d93..48781de84 100644
--- a/examples/device/cdc_msc_freertos/skip.txt
+++ b/examples/device/cdc_msc_freertos/skip.txt
@@ -2,6 +2,7 @@ mcu:CH32F20X
mcu:CH32V103
mcu:CH32V20X
mcu:CH32V307
+mcu:CH583
mcu:CXD56
mcu:F1C100S
mcu:GD32VF103
diff --git a/examples/device/cdc_uac2/skip.txt b/examples/device/cdc_uac2/skip.txt
index a2a76af0e..db1d5b80b 100644
--- a/examples/device/cdc_uac2/skip.txt
+++ b/examples/device/cdc_uac2/skip.txt
@@ -6,3 +6,4 @@ mcu:SAME5X
mcu:SAMG
board:stm32l052dap52
family:espressif
+mcu:CH583
diff --git a/examples/device/dfu_runtime/skip.txt b/examples/device/dfu_runtime/skip.txt
new file mode 100644
index 000000000..2c6c2a64c
--- /dev/null
+++ b/examples/device/dfu_runtime/skip.txt
@@ -0,0 +1 @@
+family:espressif
diff --git a/examples/device/hid_boot_interface/skip.txt b/examples/device/hid_boot_interface/skip.txt
new file mode 100644
index 000000000..2c6c2a64c
--- /dev/null
+++ b/examples/device/hid_boot_interface/skip.txt
@@ -0,0 +1 @@
+family:espressif
diff --git a/examples/device/hid_composite/skip.txt b/examples/device/hid_composite/skip.txt
new file mode 100644
index 000000000..2c6c2a64c
--- /dev/null
+++ b/examples/device/hid_composite/skip.txt
@@ -0,0 +1 @@
+family:espressif
diff --git a/examples/device/hid_composite_freertos/skip.txt b/examples/device/hid_composite_freertos/skip.txt
index d2ee8d636..97d8e168b 100644
--- a/examples/device/hid_composite_freertos/skip.txt
+++ b/examples/device/hid_composite_freertos/skip.txt
@@ -2,6 +2,7 @@ mcu:CH32F20X
mcu:CH32V103
mcu:CH32V20X
mcu:CH32V307
+mcu:CH583
mcu:CXD56
mcu:F1C100S
mcu:GD32VF103
diff --git a/examples/device/hid_generic_inout/skip.txt b/examples/device/hid_generic_inout/skip.txt
new file mode 100644
index 000000000..2c6c2a64c
--- /dev/null
+++ b/examples/device/hid_generic_inout/skip.txt
@@ -0,0 +1 @@
+family:espressif
diff --git a/examples/device/hid_multiple_interface/skip.txt b/examples/device/hid_multiple_interface/skip.txt
new file mode 100644
index 000000000..2c6c2a64c
--- /dev/null
+++ b/examples/device/hid_multiple_interface/skip.txt
@@ -0,0 +1 @@
+family:espressif
diff --git a/examples/device/midi_test/skip.txt b/examples/device/midi_test/skip.txt
new file mode 100644
index 000000000..2c6c2a64c
--- /dev/null
+++ b/examples/device/midi_test/skip.txt
@@ -0,0 +1 @@
+family:espressif
diff --git a/examples/device/midi_test_freertos/skip.txt b/examples/device/midi_test_freertos/skip.txt
index d2ee8d636..97d8e168b 100644
--- a/examples/device/midi_test_freertos/skip.txt
+++ b/examples/device/midi_test_freertos/skip.txt
@@ -2,6 +2,7 @@ mcu:CH32F20X
mcu:CH32V103
mcu:CH32V20X
mcu:CH32V307
+mcu:CH583
mcu:CXD56
mcu:F1C100S
mcu:GD32VF103
diff --git a/examples/device/uac2_headset/skip.txt b/examples/device/uac2_headset/skip.txt
index a2a76af0e..db1d5b80b 100644
--- a/examples/device/uac2_headset/skip.txt
+++ b/examples/device/uac2_headset/skip.txt
@@ -6,3 +6,4 @@ mcu:SAME5X
mcu:SAMG
board:stm32l052dap52
family:espressif
+mcu:CH583
diff --git a/examples/device/uac2_speaker_fb/skip.txt b/examples/device/uac2_speaker_fb/skip.txt
index 234f1ebed..0c7339c65 100644
--- a/examples/device/uac2_speaker_fb/skip.txt
+++ b/examples/device/uac2_speaker_fb/skip.txt
@@ -6,3 +6,4 @@ mcu:SAME5X
mcu:SAMG
board:stm32l052dap52
family:broadcom_64bit
+mcu:CH583
diff --git a/examples/device/video_capture/skip.txt b/examples/device/video_capture/skip.txt
index cb0c7d2e6..5a7a1d00e 100644
--- a/examples/device/video_capture/skip.txt
+++ b/examples/device/video_capture/skip.txt
@@ -1,5 +1,6 @@
mcu:CH32V103
mcu:CH32V20X
+mcu:CH583
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 af3b0de04..c37205b8c 100644
--- a/examples/device/video_capture_2ch/skip.txt
+++ b/examples/device/video_capture_2ch/skip.txt
@@ -5,6 +5,7 @@ mcu:GD32VF103
mcu:CH32V103
mcu:CH32V20X
mcu:CH32V307
+mcu:CH583
mcu:STM32L0
family:espressif
board:curiosity_nano
diff --git a/examples/device/webusb_serial/skip.txt b/examples/device/webusb_serial/skip.txt
new file mode 100644
index 000000000..2c6c2a64c
--- /dev/null
+++ b/examples/device/webusb_serial/skip.txt
@@ -0,0 +1 @@
+family:espressif
diff --git a/hw/bsp/ch583/boards/ch582m_evt/board.cmake b/hw/bsp/ch583/boards/ch582m_evt/board.cmake
new file mode 100644
index 000000000..4129c4550
--- /dev/null
+++ b/hw/bsp/ch583/boards/ch582m_evt/board.cmake
@@ -0,0 +1,5 @@
+set(LD_FLASH_SIZE 448K)
+set(LD_RAM_SIZE 32K)
+
+function(update_board TARGET)
+endfunction()
diff --git a/hw/bsp/ch583/boards/ch582m_evt/board.h b/hw/bsp/ch583/boards/ch582m_evt/board.h
new file mode 100644
index 000000000..c3483bf17
--- /dev/null
+++ b/hw/bsp/ch583/boards/ch582m_evt/board.h
@@ -0,0 +1,61 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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.
+ */
+
+/* metadata:
+ name: CH582M-EVT evaluation board
+ url: https://www.wch-ic.com/products/CH582.html
+*/
+
+#ifndef BOARD_H_
+#define BOARD_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// LED: PB4 on CH582M-EVT
+#define LED_PIN GPIO_Pin_4
+#define LED_STATE_ON 0
+
+// Directly reuse BOOT pin as user button
+#define BUTTON_PIN GPIO_Pin_22
+#define BUTTON_STATE_ACTIVE 0
+
+// UART: UART1 TX=PA9, RX=PA8
+#define CFG_BOARD_UART_BAUDRATE 115200
+
+// Device only on USB1 (rhport 0). CH58x host / USB2 is not supported by the shared usbfs dcd;
+// BOARD_TUH_RHPORT is kept commented out to ease re-adding host later.
+#ifndef BOARD_TUD_RHPORT
+#define BOARD_TUD_RHPORT 0
+#endif
+// #ifndef BOARD_TUH_RHPORT
+// #define BOARD_TUH_RHPORT 1
+// #endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/hw/bsp/ch583/boards/ch582m_evt/board.mk b/hw/bsp/ch583/boards/ch582m_evt/board.mk
new file mode 100644
index 000000000..a13979799
--- /dev/null
+++ b/hw/bsp/ch583/boards/ch582m_evt/board.mk
@@ -0,0 +1,3 @@
+LDFLAGS += \
+ -Wl,--defsym=__FLASH_SIZE=448K \
+ -Wl,--defsym=__RAM_SIZE=32K \
diff --git a/hw/bsp/ch583/boards/yd-ch582m/board.cmake b/hw/bsp/ch583/boards/yd-ch582m/board.cmake
new file mode 100644
index 000000000..4129c4550
--- /dev/null
+++ b/hw/bsp/ch583/boards/yd-ch582m/board.cmake
@@ -0,0 +1,5 @@
+set(LD_FLASH_SIZE 448K)
+set(LD_RAM_SIZE 32K)
+
+function(update_board TARGET)
+endfunction()
diff --git a/hw/bsp/ch583/boards/yd-ch582m/board.h b/hw/bsp/ch583/boards/yd-ch582m/board.h
new file mode 100644
index 000000000..0da5747bc
--- /dev/null
+++ b/hw/bsp/ch583/boards/yd-ch582m/board.h
@@ -0,0 +1,61 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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.
+ */
+
+/* metadata:
+ name: yd-ch582m from vcc-gnd studio
+ url: http://vcc-gnd.com/
+*/
+
+#ifndef BOARD_H_
+#define BOARD_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// LED: PB4 on yd-ch582m board
+#define LED_PIN GPIO_Pin_4
+#define LED_STATE_ON 0
+
+// Directly reuse BOOT pin as user button
+#define BUTTON_PIN GPIO_Pin_22
+#define BUTTON_STATE_ACTIVE 0
+
+// UART: UART1 TX=PA9, RX=PA8
+#define CFG_BOARD_UART_BAUDRATE 115200
+
+// Device only on USB1 (rhport 0). CH58x host / USB2 is not supported by the shared usbfs dcd;
+// BOARD_TUH_RHPORT is kept commented out to ease re-adding host later.
+#ifndef BOARD_TUD_RHPORT
+#define BOARD_TUD_RHPORT 0
+#endif
+// #ifndef BOARD_TUH_RHPORT
+// #define BOARD_TUH_RHPORT 1
+// #endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/hw/bsp/ch583/boards/yd-ch582m/board.mk b/hw/bsp/ch583/boards/yd-ch582m/board.mk
new file mode 100644
index 000000000..a13979799
--- /dev/null
+++ b/hw/bsp/ch583/boards/yd-ch582m/board.mk
@@ -0,0 +1,3 @@
+LDFLAGS += \
+ -Wl,--defsym=__FLASH_SIZE=448K \
+ -Wl,--defsym=__RAM_SIZE=32K \
diff --git a/hw/bsp/ch583/ch583_it.c b/hw/bsp/ch583/ch583_it.c
new file mode 100644
index 000000000..8479c537a
--- /dev/null
+++ b/hw/bsp/ch583/ch583_it.c
@@ -0,0 +1,37 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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 "ch583_it.h"
+
+// NMI exception handler
+__INTERRUPT __HIGH_CODE void NMI_Handler(void) {
+ while (1) {}
+}
+
+// Hard Fault exception handler
+__INTERRUPT __HIGH_CODE void HardFault_Handler(void) {
+ while (1) {}
+}
diff --git a/hw/bsp/ch583/ch583_it.h b/hw/bsp/ch583/ch583_it.h
new file mode 100644
index 000000000..29d175403
--- /dev/null
+++ b/hw/bsp/ch583/ch583_it.h
@@ -0,0 +1,46 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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 CH583_IT_H_
+#define CH583_IT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "CH58x_common.h"
+
+void NMI_Handler(void);
+void HardFault_Handler(void);
+void USB_IRQHandler(void);
+// void USB2_IRQHandler(void); // host on USB2 (rhport 1) — re-add together with the host driver
+void SysTick_Handler(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CH583_IT_H_ */
diff --git a/hw/bsp/ch583/debug_uart.c b/hw/bsp/ch583/debug_uart.c
new file mode 100644
index 000000000..850e40718
--- /dev/null
+++ b/hw/bsp/ch583/debug_uart.c
@@ -0,0 +1,86 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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 "debug_uart.h"
+#include "CH58x_common.h"
+
+//--------------------------------------------------------------------+
+// Ring buffer based UART TX for non-blocking writes
+//--------------------------------------------------------------------+
+
+#define UART_RINGBUFFER_SIZE_TX 128
+#define UART_RINGBUFFER_MASK_TX (UART_RINGBUFFER_SIZE_TX - 1)
+
+static char tx_buf[UART_RINGBUFFER_SIZE_TX];
+static uint32_t tx_produce;
+static volatile uint32_t tx_consume;
+
+void uart_write(char c) {
+ uint32_t tx_produce_next = (tx_produce + 1) & UART_RINGBUFFER_MASK_TX;
+
+ // If the ring buffer is full, drain it here as the FIFO frees up: nothing else advances
+ // tx_consume between uart_write() calls, so a plain spin would deadlock on a >buffer-size burst.
+ while (tx_produce_next == tx_consume) {
+ if (R8_UART1_LSR & RB_LSR_TX_FIFO_EMP) {
+ R8_UART1_THR = tx_buf[tx_consume];
+ tx_consume = (tx_consume + 1) & UART_RINGBUFFER_MASK_TX;
+ }
+ }
+
+ // If UART TX FIFO is empty and no pending data, send directly
+ if ((tx_consume == tx_produce) && (R8_UART1_LSR & RB_LSR_TX_FIFO_EMP)) {
+ R8_UART1_THR = c;
+ } else {
+ tx_buf[tx_produce] = c;
+ tx_produce = tx_produce_next;
+ }
+}
+
+void uart_sync(void) {
+ // Wait for ring buffer to drain
+ while (tx_consume != tx_produce) {
+ if (R8_UART1_LSR & RB_LSR_TX_FIFO_EMP) {
+ R8_UART1_THR = tx_buf[tx_consume];
+ tx_consume = (tx_consume + 1) & UART_RINGBUFFER_MASK_TX;
+ }
+ }
+ // Wait for last byte to finish transmitting
+ while (!(R8_UART1_LSR & RB_LSR_TX_ALL_EMP)) {}
+}
+
+void usart_printf_init(uint32_t baudrate) {
+ tx_produce = 0;
+ tx_consume = 0;
+
+ // Configure UART1 pins: TX=PA9, RX=PA8
+ GPIOA_SetBits(GPIO_Pin_9);
+ GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
+ GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
+
+ // Init UART1 with specified baud rate
+ UART1_DefInit();
+ UART1_BaudRateCfg(baudrate);
+}
diff --git a/hw/bsp/ch583/debug_uart.h b/hw/bsp/ch583/debug_uart.h
new file mode 100644
index 000000000..44c3e7948
--- /dev/null
+++ b/hw/bsp/ch583/debug_uart.h
@@ -0,0 +1,44 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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 DEBUG_UART_H_
+#define DEBUG_UART_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void uart_write(char c);
+void uart_sync(void);
+void usart_printf_init(uint32_t baudrate);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DEBUG_UART_H_ */
diff --git a/hw/bsp/ch583/family.c b/hw/bsp/ch583/family.c
new file mode 100644
index 000000000..716e2b76b
--- /dev/null
+++ b/hw/bsp/ch583/family.c
@@ -0,0 +1,194 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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.
+ */
+
+/* metadata:
+ manufacturer: WCH
+*/
+
+// WCH SDK's DEBUG macro enables a _write() that conflicts with TinyUSB's.
+// TinyUSB uses UART1 for printf via debug_uart.c, so DEBUG is not needed.
+// If you need a different UART or want to keep SDK's DEBUG, modify
+// debug_uart.c (TinyUSB side) or CH58x_sys.c (SDK side) to remove one _write().
+// If done, remove this #error to continue.
+#ifdef DEBUG
+ #error "Remove the DEBUG macro from preprocessor defines to avoid " \
+ "duplicate _write() between WCH SDK and TinyUSB. " \
+ "TinyUSB uses UART1 by default (see debug_uart.c)."
+#endif
+
+#include "debug_uart.h"
+#include "CH58x_common.h"
+#include "ch583_it.h"
+
+#include "bsp/board_api.h"
+#include "board.h"
+
+//--------------------------------------------------------------------+
+// Forward USB interrupt events to TinyUSB IRQ Handler
+//--------------------------------------------------------------------+
+
+// Device only: the shared dcd_ch32_usbfs.c drives USB0 (rhport 0). CH58x's second controller
+// (USB2 / rhport 1) was driven by the now-removed ch58x host driver; its handler is kept
+// commented out below to ease re-adding host support.
+__INTERRUPT __HIGH_CODE void USB_IRQHandler(void) {
+ tusb_int_handler(0, true);
+}
+
+// __INTERRUPT __HIGH_CODE void USB2_IRQHandler(void) {
+// tusb_int_handler(1, true);
+// }
+
+//--------------------------------------------------------------------+
+// SysTick
+//--------------------------------------------------------------------+
+
+#if CFG_TUSB_OS == OPT_OS_NONE
+volatile uint32_t system_ticks = 0;
+
+__INTERRUPT __HIGH_CODE void SysTick_Handler(void) {
+ SysTick->SR = 0;
+ system_ticks++;
+}
+
+uint32_t tusb_time_millis_api(void) {
+ return system_ticks;
+}
+#endif
+
+//--------------------------------------------------------------------+
+// Board Init
+//--------------------------------------------------------------------+
+
+void board_init(void) {
+ // Disable interrupts during init
+ PFIC_DisableAllIRQ();
+
+ // Set system clock to PLL 60MHz (default for CH582)
+ SetSysClock(CLK_SOURCE_PLL_60MHz);
+
+#if CFG_TUSB_OS == OPT_OS_NONE
+ SysTick_Config(GetSysClock() / 1000);
+#endif
+
+ // UART1 init for debug output
+#ifdef CFG_BOARD_UART_BAUDRATE
+ usart_printf_init(CFG_BOARD_UART_BAUDRATE);
+#endif
+
+ // LED
+#ifdef LED_PORT_IS_A
+ GPIOA_ModeCfg(LED_PIN, GPIO_ModeOut_PP_5mA);
+#else
+ GPIOB_ModeCfg(LED_PIN, GPIO_ModeOut_PP_5mA);
+#endif
+
+ // Button
+#ifdef BUTTON_PIN
+ #ifdef BUTTON_PORT_IS_A
+ GPIOA_ModeCfg(BUTTON_PIN, GPIO_ModeIN_PU);
+ #else
+ GPIOB_ModeCfg(BUTTON_PIN, GPIO_ModeIN_PU);
+ #endif
+#endif
+
+ // Device only on USB0 (rhport 0): enable analog function for USB1 D+/D- and assert its D+
+ // pull-up. The shared dcd_ch32_usbfs.c drives USB0; CH58x host / USB2 (rhport 1) is unsupported
+ // here — to re-add host, also OR in RB_PIN_USB2_IE below.
+ R16_PIN_ANALOG_IE |= RB_PIN_USB_IE; // | RB_PIN_USB2_IE (re-add for host on USB2)
+#if CFG_TUD_ENABLED
+ R16_PIN_ANALOG_IE |= RB_PIN_USB_DP_PU;
+#endif
+
+ // Keep USB clock active during sleep
+ R8_SLP_CLK_OFF1 &= ~RB_SLP_CLK_USB;
+
+ // Enable interrupts globally
+ PFIC_EnableAllIRQ();
+
+ board_delay(2);
+}
+
+//--------------------------------------------------------------------+
+// Board porting API
+//--------------------------------------------------------------------+
+
+void board_led_write(bool state) {
+#ifdef LED_PORT_IS_A
+ if (state ^ LED_STATE_ON) {
+ GPIOA_ResetBits(LED_PIN);
+ } else {
+ GPIOA_SetBits(LED_PIN);
+ }
+#else
+ if (state ^ LED_STATE_ON) {
+ GPIOB_ResetBits(LED_PIN);
+ } else {
+ GPIOB_SetBits(LED_PIN);
+ }
+#endif
+}
+
+uint32_t board_button_read(void) {
+#ifdef BUTTON_PIN
+ #ifdef BUTTON_PORT_IS_A
+ return BUTTON_STATE_ACTIVE == (GPIOA_ReadPortPin(BUTTON_PIN) ? 1 : 0);
+ #else
+ return BUTTON_STATE_ACTIVE == (GPIOB_ReadPortPin(BUTTON_PIN) ? 1 : 0);
+ #endif
+#else
+ return 0;
+#endif
+}
+
+// CH58x has no memory-mapped unique-ID register (unlike ch32v10x/v20x at 0x1FFFF7E8), but the
+// factory programs a unique 6-byte MAC address into FlashROM (it is a BLE part). GetMACAddress()
+// reads it via the ISP ROM command FLASH_EEPROM_CMD, which is provided by libISP583.a.
+size_t board_get_unique_id(uint8_t id[], size_t max_len) {
+ // FLASH_EEPROM_CMD writes word-granular and requires a 4-byte-aligned buffer (CH58x_flash.c);
+ // size 8 matches the SDK's GET_UNIQUE_ID buffer (6 MAC bytes + 2 it pads), so the read can't
+ // run past the end whether it returns 6 or a full 8.
+ TU_ATTR_ALIGNED(4) uint8_t mac[8];
+ GetMACAddress(mac);
+ size_t len = TU_MIN(max_len, (size_t) 6); // the 6-byte MAC is the unique part
+ memcpy(id, mac, len);
+ return 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 txsize = len;
+ const char* bufc = (const char*) buf;
+ while (txsize--) {
+ uart_write(*bufc++);
+ }
+ uart_sync();
+ return len;
+}
diff --git a/hw/bsp/ch583/family.cmake b/hw/bsp/ch583/family.cmake
new file mode 100644
index 000000000..a379298e5
--- /dev/null
+++ b/hw/bsp/ch583/family.cmake
@@ -0,0 +1,108 @@
+include_guard()
+
+set(SDK_DIR ${TOP}/hw/mcu/wch/ch583)
+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_CPU rv32imac-ilp32 CACHE INTERNAL "System Processor")
+set(CMAKE_TOOLCHAIN_FILE ${TOP}/examples/build_system/cmake/toolchain/riscv_${TOOLCHAIN}.cmake)
+
+set(FAMILY_MCUS CH583 CACHE INTERNAL "")
+set(OPENOCD_OPTION "-f ${CMAKE_CURRENT_LIST_DIR}/wch-riscv.cfg")
+
+#------------------------------------
+# Startup & Linker script
+#------------------------------------
+if (NOT DEFINED LD_FILE_GNU)
+ set(LD_FILE_GNU ${CMAKE_CURRENT_LIST_DIR}/linker/ch582.ld)
+endif ()
+set(LD_FILE_Clang ${LD_FILE_GNU})
+if (NOT DEFINED STARTUP_FILE_GNU)
+ set(STARTUP_FILE_GNU ${SDK_SRC_DIR}/Startup/startup_CH583.S)
+endif ()
+set(STARTUP_FILE_Clang ${STARTUP_FILE_GNU})
+
+#------------------------------------
+# Board Target
+#------------------------------------
+function(family_add_board BOARD_TARGET)
+ add_library(${BOARD_TARGET} STATIC
+ ${SDK_SRC_DIR}/StdPeriphDriver/CH58x_gpio.c
+ ${SDK_SRC_DIR}/StdPeriphDriver/CH58x_clk.c
+ ${SDK_SRC_DIR}/StdPeriphDriver/CH58x_uart1.c
+ ${SDK_SRC_DIR}/StdPeriphDriver/CH58x_sys.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ch583_it.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/system_ch583.c
+ )
+ target_include_directories(${BOARD_TARGET} PUBLIC
+ ${SDK_SRC_DIR}/RVMSIS
+ ${SDK_SRC_DIR}/StdPeriphDriver/inc
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}
+ )
+ target_link_libraries(${BOARD_TARGET} PUBLIC
+ ${SDK_SRC_DIR}/StdPeriphDriver/libISP583.a
+ )
+ target_compile_definitions(${BOARD_TARGET} PUBLIC
+ CFG_TUD_WCH_USBIP_USBFS=1
+ FREQ_SYS=60000000
+ DISK_LIB_ENABLE=0
+ INT_SOFT
+ )
+
+ update_board(${BOARD_TARGET})
+
+ if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
+ target_compile_options(${BOARD_TARGET} PUBLIC
+ -flto
+ -msmall-data-limit=16
+ -mno-save-restore
+ -fmessage-length=0
+ -fsigned-char
+ -Wno-error=strict-prototypes
+ )
+ endif ()
+endfunction()
+
+#------------------------------------
+# Functions
+#------------------------------------
+function(family_configure_example TARGET RTOS)
+ family_configure_common(${TARGET} ${RTOS})
+ family_add_tinyusb(${TARGET} OPT_MCU_CH583)
+
+ target_sources(${TARGET} PUBLIC
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/family.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/debug_uart.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../board.c
+ ${TOP}/src/portable/wch/dcd_ch32_usbfs.c
+ ${STARTUP_FILE_${CMAKE_C_COMPILER_ID}}
+ )
+ target_include_directories(${TARGET} PUBLIC
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/boards/${BOARD}
+ )
+
+ if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
+ target_link_options(${TARGET} PUBLIC
+ -nostartfiles
+ --specs=nosys.specs --specs=nano.specs
+ -Wl,--defsym=__FLASH_SIZE=${LD_FLASH_SIZE}
+ -Wl,--defsym=__RAM_SIZE=${LD_RAM_SIZE}
+ "LINKER:--script=${LD_FILE_GNU}"
+ )
+ elseif (CMAKE_C_COMPILER_ID STREQUAL "Clang")
+ message(FATAL_ERROR "Clang is not supported for CH58x")
+ endif ()
+
+ set_source_files_properties(${STARTUP_FILE_${CMAKE_C_COMPILER_ID}} PROPERTIES
+ SKIP_LINTING ON
+ COMPILE_OPTIONS -w)
+
+ # Flashing
+ family_add_bin_hex(${TARGET})
+ family_flash_openocd_wch(${TARGET})
+endfunction()
diff --git a/hw/bsp/ch583/family.mk b/hw/bsp/ch583/family.mk
new file mode 100644
index 000000000..98d0f9337
--- /dev/null
+++ b/hw/bsp/ch583/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-
+
+SDK_DIR = hw/mcu/wch/ch583
+SDK_SRC_DIR = $(SDK_DIR)/EVT/EXAM/SRC
+
+include $(TOP)/$(BOARD_PATH)/board.mk
+CPU_CORE ?= rv32imac-ilp32
+
+CFLAGS += \
+ -flto \
+ -msmall-data-limit=16 \
+ -mno-save-restore \
+ -fmessage-length=0 \
+ -fsigned-char \
+ -DCFG_TUSB_MCU=OPT_MCU_CH583 \
+ -DCFG_TUD_WCH_USBIP_USBFS=1 \
+ -DFREQ_SYS=60000000 \
+ -DDISK_LIB_ENABLE=0 \
+ -DINT_SOFT \
+ -Wno-error=strict-prototypes
+
+LDFLAGS += \
+ -nostartfiles \
+ --specs=nosys.specs --specs=nano.specs
+
+LIBS += $(TOP)/$(SDK_SRC_DIR)/StdPeriphDriver/libISP583.a
+
+SRC_C += \
+ src/portable/wch/dcd_ch32_usbfs.c \
+ $(SDK_SRC_DIR)/StdPeriphDriver/CH58x_gpio.c \
+ $(SDK_SRC_DIR)/StdPeriphDriver/CH58x_clk.c \
+ $(SDK_SRC_DIR)/StdPeriphDriver/CH58x_uart1.c \
+ $(SDK_SRC_DIR)/StdPeriphDriver/CH58x_sys.c \
+ $(FAMILY_PATH)/debug_uart.c \
+ $(FAMILY_PATH)/ch583_it.c \
+ $(FAMILY_PATH)/system_ch583.c
+
+SRC_S += \
+ $(SDK_SRC_DIR)/Startup/startup_CH583.S
+
+INC += \
+ $(TOP)/$(BOARD_PATH) \
+ $(TOP)/$(SDK_SRC_DIR)/RVMSIS \
+ $(TOP)/$(SDK_SRC_DIR)/StdPeriphDriver/inc
+
+LD_FILE ?= $(FAMILY_PATH)/linker/ch582.ld
+
+OPENOCD_WCH_OPTION=-f $(TOP)/$(FAMILY_PATH)/wch-riscv.cfg
+flash: flash-openocd-wch
+
+# For freeRTOS port source
+FREERTOS_PORTABLE_SRC = $(FREERTOS_PORTABLE_PATH)/RISC-V
diff --git a/hw/bsp/ch583/linker/ch582.ld b/hw/bsp/ch583/linker/ch582.ld
new file mode 100644
index 000000000..821998a49
--- /dev/null
+++ b/hw/bsp/ch583/linker/ch582.ld
@@ -0,0 +1,167 @@
+/* CH582 Linker Script for TinyUSB
+ * Based on WCH CH583 SDK Link.ld
+ * Supports parameterized flash/ram sizes via --defsym
+ */
+
+/* Default sizes if not provided via --defsym */
+__flash_size = DEFINED(__FLASH_SIZE) ? __FLASH_SIZE : 448K;
+__ram_size = DEFINED(__RAM_SIZE) ? __RAM_SIZE : 32K;
+__stack_size = DEFINED(__STACK_SIZE) ? __STACK_SIZE : 2048;
+
+ENTRY( _start )
+
+PROVIDE( _stack_size = __stack_size );
+
+MEMORY
+{
+ FLASH (rx) : ORIGIN = 0x00000000, LENGTH = __flash_size
+ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = __ram_size
+}
+
+SECTIONS
+{
+ .init :
+ {
+ _sinit = .;
+ . = ALIGN(4);
+ KEEP(*(SORT_NONE(.init)))
+ . = ALIGN(4);
+ _einit = .;
+ } >FLASH AT>FLASH
+
+ .highcodelalign :
+ {
+ . = ALIGN(4);
+ PROVIDE(_highcode_lma = .);
+ } >FLASH AT>FLASH
+
+ .highcode :
+ {
+ . = ALIGN(4);
+ PROVIDE(_highcode_vma_start = .);
+ *(.vector);
+ KEEP(*(SORT_NONE(.vector_handler)))
+ *(.highcode);
+ *(.highcode.*);
+ . = ALIGN(4);
+ PROVIDE(_highcode_vma_end = .);
+ } >RAM AT>FLASH
+
+ .text :
+ {
+ . = ALIGN(4);
+ KEEP(*(SORT_NONE(.handle_reset)))
+ *(.text)
+ *(.text.*)
+ *(.rodata)
+ *(.rodata*)
+ *(.sdata2.*)
+ *(.glue_7)
+ *(.glue_7t)
+ *(.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 :
+ {
+ KEEP (*crtbegin.o(.ctors))
+ KEEP (*crtbegin?.o(.ctors))
+ 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
+
+ .dlalign :
+ {
+ . = ALIGN(4);
+ PROVIDE(_data_lma = .);
+ } >FLASH AT>FLASH
+
+ .data :
+ {
+ . = ALIGN(4);
+ PROVIDE(_data_vma = .);
+ *(.gnu.linkonce.r.*)
+ *(.data .data.*)
+ *(.gnu.linkonce.d.*)
+ . = ALIGN(8);
+ PROVIDE( __global_pointer$ = . + 0x800 );
+ *(.sdata .sdata.*)
+ *(.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 = .);
+ __freertos_irq_stack_top = .;
+ } >RAM
+}
diff --git a/hw/bsp/ch583/system_ch583.c b/hw/bsp/ch583/system_ch583.c
new file mode 100644
index 000000000..d3724f0ce
--- /dev/null
+++ b/hw/bsp/ch583/system_ch583.c
@@ -0,0 +1,39 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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 "CH58x_common.h"
+#include "system_ch583.h"
+
+uint32_t SystemCoreClock = FREQ_SYS;
+
+void SystemInit(void) {
+ SetSysClock(CLK_SOURCE_PLL_60MHz);
+ SystemCoreClock = GetSysClock();
+}
+
+void SystemCoreClockUpdate(void) {
+ SystemCoreClock = GetSysClock();
+}
diff --git a/hw/bsp/ch583/system_ch583.h b/hw/bsp/ch583/system_ch583.h
new file mode 100644
index 000000000..c174c9f5e
--- /dev/null
+++ b/hw/bsp/ch583/system_ch583.h
@@ -0,0 +1,43 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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 SYSTEM_CH583_H_
+#define SYSTEM_CH583_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern uint32_t SystemCoreClock; // System Clock Frequency (Core Clock)
+
+extern void SystemInit(void);
+extern void SystemCoreClockUpdate(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SYSTEM_CH583_H_ */
diff --git a/hw/bsp/ch583/wch-riscv.cfg b/hw/bsp/ch583/wch-riscv.cfg
new file mode 100644
index 000000000..64d595d8e
--- /dev/null
+++ b/hw/bsp/ch583/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 0x8000 -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/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index cc9837dcd..b5390a59d 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -659,6 +659,19 @@
#define TUP_DCD_EDPT_CLOSE_API
#endif
+#elif TU_CHECK_MCU(OPT_MCU_CH583)
+ // CH582/583 USBFS: older WCH USBFS IP with a single combined per-endpoint control register
+ // (like CH32V103), driven by the shared dcd_ch32_usbfs.c on USB0 (rhport 0). Device only:
+ // the shared hcd_ch32_usbfs.c is CH32V20x-specific and does not support CH58x, so host /
+ // USB2 (rhport 1) is not provided here.
+ #define TUP_USBIP_WCH_USBFS
+
+ #ifndef CFG_TUD_WCH_USBIP_USBFS
+ #define CFG_TUD_WCH_USBIP_USBFS 1
+ #endif
+
+ #define TUP_DCD_ENDPOINT_MAX 8
+
//--------------------------------------------------------------------+
// Analog Devices
//--------------------------------------------------------------------+
diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h
index 7ffdc6cef..415a015dc 100644
--- a/src/portable/wch/ch32_usbfs_reg.h
+++ b/src/portable/wch/ch32_usbfs_reg.h
@@ -130,6 +130,72 @@
#elif CFG_TUSB_MCU == OPT_MCU_CH32V307
#include <ch32v30x.h>
#define USBHD_IRQn OTG_FS_IRQn
+#elif CFG_TUSB_MCU == OPT_MCU_CH583
+ #include "CH58x_common.h"
+ // CH582/583 USBFS device controller: same combined per-endpoint control register as
+ // CH32V103 (IN response bits[1:0], OUT response bits[3:2]) but a different register map -
+ // the EP control/length block sits lower (EP0_CTRL @ +0x22), EP5-7 are split out, EP4
+ // shares EP0's DMA buffer, and EP5/6/7 mode bits live in one UEP567_MOD. The control/status
+ // block matches CH32. Two FS controllers exist (USB @ 0x40008000, USB2 @ 0x40008400); the
+ // device uses USB0. Full register map per CH583/582 datasheet Table 17-2; the parameterized
+ // EP_* macros below index off these named fields.
+ #define CH58X_USBFS_BASE 0x40008000u
+ // Per-endpoint register slots, 4-byte stride each; the EP_* macros index arrays of these.
+ typedef struct {
+ __IO uint16_t DMA; // R16_UEPn_DMA: endpoint n buffer start address
+ __IO uint16_t reserved;
+ } ch58x_ep_dma_t;
+ typedef struct {
+ __IO uint8_t T_LEN; // R8_UEPn_T_LEN (+0): transmit length
+ __IO uint8_t reserved0;
+ __IO uint8_t CTRL; // R8_UEPn_CTRL (+2): endpoint control
+ __IO uint8_t reserved1;
+ } ch58x_ep_ctrl_t;
+ typedef struct {
+ __IO uint8_t BASE_CTRL; // 0x00 R8_USB_CTRL
+ __IO uint8_t UDEV_CTRL; // 0x01 R8_UDEV_CTRL
+ __IO uint8_t INT_EN; // 0x02 R8_USB_INT_EN
+ __IO uint8_t DEV_ADDR; // 0x03 R8_USB_DEV_AD
+ __IO uint8_t Reserve0; // 0x04
+ __IO uint8_t MIS_ST; // 0x05 R8_USB_MIS_ST
+ __IO uint8_t INT_FG; // 0x06 R8_USB_INT_FG
+ __IO uint8_t INT_ST; // 0x07 R8_USB_INT_ST
+ __IO uint8_t RX_LEN; // 0x08 R8_USB_RX_LEN (8-bit on CH58X)
+ __IO uint8_t Reserve1[3]; // 0x09..0x0B
+ __IO uint8_t UEP4_1_MOD; // 0x0C R8_UEP4_1_MOD
+ __IO uint8_t UEP2_3_MOD; // 0x0D R8_UEP2_3_MOD
+ __IO uint8_t UEP567_MOD; // 0x0E R8_UEP567_MOD
+ __IO uint8_t Reserve2; // 0x0F
+ ch58x_ep_dma_t EP_DMA_0_3[4]; // 0x10 EP0-3 DMA (EP4 has no DMA reg; it shares EP0's, index 0)
+ ch58x_ep_ctrl_t EP_CTRL_0_4[5]; // 0x20 EP0-4 length/control
+ __IO uint8_t Reserve3[0x54u - 0x34u]; // 0x34..0x53
+ ch58x_ep_dma_t EP_DMA_5_7[3]; // 0x54 EP5-7 DMA
+ __IO uint8_t Reserve4[0x64u - 0x60u]; // 0x60..0x63
+ ch58x_ep_ctrl_t EP_CTRL_5_7[3]; // 0x64 EP5-7 length/control
+ } USBOTG_FS_TypeDef;
+ #define USBOTG_FS ((USBOTG_FS_TypeDef *) CH58X_USBFS_BASE)
+
+ // 4-byte slot stride + these block offsets pin every EP register to its datasheet address.
+ TU_VERIFY_STATIC(sizeof(ch58x_ep_dma_t) == 4, "CH58x EP DMA slot must be 4 bytes");
+ TU_VERIFY_STATIC(sizeof(ch58x_ep_ctrl_t) == 4, "CH58x EP ctrl slot must be 4 bytes");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_0_3) == 0x10, "CH58x EP_DMA_0_3 @0x10");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_0_4) == 0x20, "CH58x EP_CTRL_0_4 @0x20");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_5_7) == 0x54, "CH58x EP_DMA_5_7 @0x54");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_5_7) == 0x64, "CH58x EP_CTRL_5_7 @0x64");
+
+ #define CH32_USBFS_EP_CTRL_COMBINED 1
+ // CH58x's hardware AUTO_TOG does not stay in sync (notably across clear-stall and multi-packet
+ // bulk transfers), causing data-toggle mismatch and bus resets. Drive the toggle manually in
+ // the ISR instead. CH32V103/V20x/V307 keep AUTO_TOG (this macro is undefined for them).
+ #define CH32_USBFS_EP_MANUAL_TOG 1
+ // CH58x EP4 has no DMA register of its own: it overlays EP0's DMA region as
+ // EP0[0:63] + EP4_OUT[64:127] + EP4_IN[128:191], so EP0 needs a 192-byte buffer.
+ #define CH32_USBFS_EP4_SHARES_EP0 1
+ #define USBHD_IRQn USB_IRQn
+ #ifndef NVIC_EnableIRQ
+ #define NVIC_EnableIRQ(n) PFIC_EnableIRQ(n)
+ #define NVIC_DisableIRQ(n) PFIC_DisableIRQ(n)
+ #endif
#endif
#ifdef __GNUC__
@@ -167,9 +233,14 @@
#define USBFS_INT_FG_TOG_OK (1 << 6)
#define USBFS_INT_FG_IS_NAK (1 << 7)
+// MIS_ST: the SUSPEND interrupt fires on both suspend and resume; this bit (R8_USB_MIS_ST) is 1
+// while the bus is suspended and 0 once it has resumed, so it tells the two apart.
+#define USBFS_MIS_ST_SUSPEND (1 << 2)
+
// INT_ST
#define USBFS_INT_ST_MASK_UIS_ENDP(x) (((x) >> 0) & 0x0F)
#define USBFS_INT_ST_MASK_UIS_TOKEN(x) (((x) >> 4) & 0x03)
+#define USBFS_INT_ST_TOG_OK (1 << 6) // received packet's data toggle matched expectation
// UDEV_CTRL
#define USBFS_UDEV_CTRL_PORT_EN (1 << 0)
diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c
index dae31da91..ece9cde07 100644
--- a/src/portable/wch/dcd_ch32_usbfs.c
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -35,17 +35,35 @@
/* 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])
+ // Struct-based EP register access (uniform layout). CH58X has a different register map and
+ // defines EP_DMA/EP_TX_LEN/EP_CTRL itself in ch32_usbfs_reg.h.
+ #if CFG_TUSB_MCU == OPT_MCU_CH583
+ // CH58X EP registers split into a low block (EP0-4) and a high block (EP5-7). Walk from each
+ // block's first slot by the 4-byte slot stride (pointer arithmetic off slot 0, so the unused
+ // ternary branch's index can't trip -Warray-bounds). EP4 has no DMA register of its own (it
+ // shares EP0's, slot 0) and is never written (see ep_shares_ep0_dma()).
+ #define EP_TX_LEN(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].T_LEN + (ep) * 4u \
+ : &USBOTG_FS->EP_CTRL_5_7[0].T_LEN + ((ep) - 5u) * 4u))
+ #define EP_CTRL(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].CTRL + (ep) * 4u \
+ : &USBOTG_FS->EP_CTRL_5_7[0].CTRL + ((ep) - 5u) * 4u))
+ #define EP_DMA(ep) (*((ep) <= 3u ? &USBOTG_FS->EP_DMA_0_3[0].DMA + (ep) * 2u \
+ : (ep) == 4u ? &USBOTG_FS->EP_DMA_0_3[0].DMA \
+ : &USBOTG_FS->EP_DMA_5_7[0].DMA + ((ep) - 5u) * 2u))
+ #else
+ #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])
+ #endif
// Endpoint control register access. The newer USBFS IP (CH32V20x/V307/X035) has separate
// TX_CTRL and RX_CTRL bytes per endpoint; the older IP (CH32V103) has a single combined
// UEPn_CTRL register. These helpers hide the difference so the rest of the driver is shared.
// Values use the newer-IP encoding (USBFS_EP_T_*/USBFS_EP_R_*); the combined path remaps them.
#ifdef CH32_USBFS_EP_CTRL_COMBINED
+ #ifndef EP_CTRL // parts with a custom register map (CH58X) define EP_CTRL directly in reg.h
#define EP_CTRL(ep) EP_TX_CTRL(ep) // UEPn_TX_CTRL field aliases the combined UEPn_CTRL register
+ #endif
static inline uint8_t ep_tx_to_comb(uint8_t v) {
uint8_t c = v & USBFS_EP_T_RES_MASK; // IN response: bits [1:0] in both encodings
@@ -86,6 +104,17 @@
#define EP0_SETUP_RX_TOG 0
#endif
+// Hardware auto data-toggle flag. Parts whose AUTO_TOG is reliable OR it into the EP setup so the
+// controller flips DATA0/DATA1 itself; CH58x (CH32_USBFS_EP_MANUAL_TOG) leaves it clear and the
+// ISR flips the toggle bit after each packet instead.
+#ifdef CH32_USBFS_EP_MANUAL_TOG
+ #define EP_T_AUTO_TOG 0
+ #define EP_R_AUTO_TOG 0
+#else
+ #define EP_T_AUTO_TOG USBFS_EP_T_AUTO_TOG
+ #define EP_R_AUTO_TOG USBFS_EP_R_AUTO_TOG
+#endif
+
/* private data */
struct usb_xfer {
bool valid;
@@ -99,28 +128,105 @@ static struct {
bool ep0_tog;
bool isochronous[EP_MAX];
struct usb_xfer xfer[EP_MAX][2];
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ // CH58X buffers laid out by hand so EP0/EP4 don't burn two unused buffer[] slots. EP0 and EP4
+ // share one contiguous 192-byte DMA region (EP4 has no DMA register of its own):
+ // EP0 [0:63] (half-duplex OUT+IN) + EP4 OUT [64:127] + EP4 IN [128:191]. Every other endpoint
+ // (incl. EP3, which is bulk-only here — CH58X has no isochronous support) gets a plain 128-byte
+ // OUT+IN buffer, so no oversized EP3 buffer is needed.
+ TU_ATTR_ALIGNED(4) uint8_t ep0_ep4_buffer[3 * 64];
+ TU_ATTR_ALIGNED(4) uint8_t ep1_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep2_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep3_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep5_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep6_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep7_buffer[2][64];
+#else
TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
+ // EP3 IN gets an enlarged buffer for full-speed isochronous (packets up to 1023 B).
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;
+#endif
} data;
+// DMA / copy buffer pointers per endpoint. The WCH USBFS buffer holds OUT (RX) at offset 0 and
+// IN (TX) at +64; EP0 is half-duplex and reuses its OUT chunk for IN; EP3 has an enlarged IN
+// buffer for throughput. On CH58X, EP0/EP4 share ep0_ep4_buffer and the regular endpoints use
+// their own named buffer (see the struct above).
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+// OUT base of the regular CH58X endpoints (EP1/2/3/5/6/7; EP0/EP4 share ep0_ep4_buffer).
+static inline uint8_t* ch58x_ep_buffer(uint8_t ep) {
+ switch (ep) {
+ case 1: return data.ep1_buffer[0];
+ case 2: return data.ep2_buffer[0];
+ case 3: return data.ep3_buffer[0];
+ case 5: return data.ep5_buffer[0];
+ case 6: return data.ep6_buffer[0];
+ default: return data.ep7_buffer[0]; // ep == 7
+ }
+}
+#endif
+
+static inline uint32_t ep_dma_addr(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0 || ep == 4) { return (uint32_t) &data.ep0_ep4_buffer[0]; } // EP4 shares EP0's DMA
+ return (uint32_t) ch58x_ep_buffer(ep);
+#else
+ if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; }
+ return (uint32_t) &data.buffer[ep][0];
+#endif
+}
+
+static inline uint8_t* ep_out_buf(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0) { return &data.ep0_ep4_buffer[0]; }
+ if (ep == 4) { return &data.ep0_ep4_buffer[64]; }
+ return ch58x_ep_buffer(ep);
+#else
+ if (ep == 3) { return data.ep3_buffer.out; }
+ return data.buffer[ep][TUSB_DIR_OUT];
+#endif
+}
+
+static inline uint8_t* ep_in_buf(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0) { return &data.ep0_ep4_buffer[0]; } // EP0 half-duplex: IN reuses OUT chunk
+ if (ep == 4) { return &data.ep0_ep4_buffer[128]; }
+ return ch58x_ep_buffer(ep) + 64; // IN at +64 within the endpoint's 128-byte buffer
+#else
+ if (ep == 0) { return data.buffer[0][TUSB_DIR_OUT]; } // EP0 half-duplex: IN reuses OUT chunk
+ if (ep == 3) { return data.ep3_buffer.in; }
+ return data.buffer[ep][TUSB_DIR_IN];
+#endif
+}
+
+// EP4 on CH58X has no DMA register (shares EP0's); skip its EP_DMA() write.
+static inline bool ep_shares_ep0_dma(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ return ep == 4;
+#else
+ (void) ep;
+ return false;
+#endif
+}
+
/* 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);
- }
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // Every CH58x endpoint buffer is 64 bytes. Isochronous (which would push max_size up to 1023)
+ // is refused in dcd_edpt_iso_alloc(), but some classes (e.g. video) ignore that result, so cap
+ // the copy here to guarantee we never write past the buffer into a neighbouring endpoint's.
+ len = TU_MIN(len, 64u);
+#endif
+ memcpy(ep_in_buf(ep), xfer->buffer, len);
xfer->buffer += len;
xfer->len -= len;
xfer->processed_len += len;
@@ -150,11 +256,10 @@ 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);
- }
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ len = TU_MIN(len, 64u); // cap to the 64-byte EP buffer (see update_in)
+#endif
+ memcpy(xfer->buffer, ep_out_buf(ep), len);
xfer->buffer += len;
xfer->len -= len;
xfer->processed_len += len;
@@ -176,12 +281,11 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
static void reset_ep_ctrls(void) {
for (uint8_t ep = 1; ep < EP_MAX; ep++) {
- EP_DMA(ep) = (uint32_t)&data.buffer[ep][0];
+ if (!ep_shares_ep0_dma(ep)) { EP_DMA(ep) = ep_dma_addr(ep); }
EP_TX_LEN(ep) = 0;
- ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET);
- ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET);
+ ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET);
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET);
}
- EP_DMA(3) = (uint32_t)&data.ep3_buffer.out[0];
}
/* public functions */
@@ -195,8 +299,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
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];
+ // setup endpoint 0 (also backs EP4's buffer on CH58X via the shared DMA region)
+ EP_DMA(0) = ep_dma_addr(0);
EP_TX_LEN(0) = 0;
ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK);
ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK);
@@ -204,8 +308,14 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
// enable other endpoints but NAK everything
USBOTG_FS->UEP4_1_MOD = 0xCC;
USBOTG_FS->UEP2_3_MOD = 0xCC;
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // CH58X: a single mode register enables EP5/6/7 RX+TX (different bit layout than CH32).
+ USBOTG_FS->UEP567_MOD = RB_UEP5_RX_EN | RB_UEP5_TX_EN | RB_UEP6_RX_EN | RB_UEP6_TX_EN |
+ RB_UEP7_RX_EN | RB_UEP7_TX_EN;
+#else
USBOTG_FS->UEP5_6_MOD = 0xCC;
USBOTG_FS->UEP7_MOD = 0x0C;
+#endif
reset_ep_ctrls();
@@ -218,17 +328,32 @@ 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);
+ uint8_t int_st = USBOTG_FS->INT_ST;
+ uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(int_st);
+ uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(int_st);
uint16_t rx_len = USBOTG_FS->RX_LEN;
switch (token) {
case PID_OUT: {
+ // Drop an OUT packet whose data toggle doesn't match what we expect -- a host retransmit
+ // after a lost ACK, or a host that doesn't alternate DATA0/DATA1. The hardware auto-toggle
+ // does not reject these on its own, so the check is needed on every variant. EP0 keeps its
+ // own toggle via the SETUP/status flow and is exempt.
+ if (ep != 0 && !(int_st & USBFS_INT_ST_TOG_OK)) { break; }
+#ifdef CH32_USBFS_EP_MANUAL_TOG
+ // CH58x has no hardware auto-toggle: advance the expected RX toggle after each accepted packet
+ // (EP0 included -- it also has no auto-toggle and a control-OUT data stage can span packets).
+ EP_CTRL(ep) ^= USBFS_EPC_R_TOG;
+#endif
update_out(rhport, ep, rx_len);
break;
}
case PID_IN:
+#ifdef CH32_USBFS_EP_MANUAL_TOG
+ // Manual toggle: flip the TX toggle after each ACK'd IN packet (EP0 manages its own).
+ if (ep != 0) { EP_CTRL(ep) ^= USBFS_EPC_T_TOG; }
+#endif
update_in(rhport, ep, false);
break;
@@ -236,12 +361,17 @@ void dcd_int_handler(uint8_t rhport) {
// setup clears stall
ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK);
data.ep0_tog = true;
+ // A new SETUP supersedes any control transfer still in flight; drop its stale EP0 state so a
+ // spurious EP0 IN/OUT can't run update_in()/update_out() against the previous request.
+ data.xfer[0][TUSB_DIR_OUT].valid = false;
+ data.xfer[0][TUSB_DIR_IN].valid = false;
- const tusb_control_request_t *setup = (const tusb_control_request_t *)&data.buffer[0][TUSB_DIR_OUT][0];
+ uint8_t *ep0_out = ep_out_buf(0);
+ const tusb_control_request_t *setup = (const tusb_control_request_t *)ep0_out;
// EP0_SETUP_RX_TOG arms the data/status stage at DATA1 on the combined-control IP
ep_rx_ctrl_set(0, ((setup->wLength == 0) ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK) | EP0_SETUP_RX_TOG);
- dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true);
+ dcd_event_setup_received(rhport, ep0_out, true);
break;
}
@@ -263,7 +393,14 @@ void dcd_int_handler(uint8_t rhport) {
USBOTG_FS->INT_FG = USBFS_INT_FG_BUS_RST;
} else if (status & USBFS_INT_FG_SUSPEND) {
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // CH58x raises this single interrupt for both suspend and resume; MIS_ST's suspend bit tells
+ // them apart (set while suspended, clear once resumed) so tud_resume_cb() actually fires.
+ dcd_event_t event = {.rhport = rhport,
+ .event_id = (USBOTG_FS->MIS_ST & USBFS_MIS_ST_SUSPEND) ? DCD_EVENT_SUSPEND : DCD_EVENT_RESUME};
+#else
dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SUSPEND};
+#endif
dcd_event_handler(&event, true);
USBOTG_FS->INT_FG = USBFS_INT_FG_SUSPEND;
}
@@ -310,7 +447,12 @@ void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *req
(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) {
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // On CH58x R8_USB_DEV_AD bit 7 is a user general-purpose flag; only bits [6:0] are the address.
+ USBOTG_FS->DEV_ADDR = (uint8_t)((USBOTG_FS->DEV_ADDR & 0x80u) | (request->wValue & 0x7Fu));
+#else
USBOTG_FS->DEV_ADDR = (uint8_t)request->wValue;
+#endif
}
}
@@ -323,10 +465,12 @@ bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep);
if (ep != 0) {
+ // Opening clears the toggle to DATA0 (ep_*_ctrl_set writes the toggle bit clear since v has no
+ // R/T_TOG); with manual toggle EP_*_AUTO_TOG is 0 so the ISR owns subsequent toggling.
if (dir == TUSB_DIR_OUT) {
- ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
+ ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
}
}
return true;
@@ -341,18 +485,29 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet
(void)rhport;
(void)ep_addr;
(void)largest_packet_size;
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // No isochronous support on CH58x: its 8-bit T_LEN caps a packet at 255B and the endpoints use
+ // plain 64-byte buffers, so accepting an iso max_size (up to 1023) would let update_in()/
+ // update_out() run off the end of the buffer into neighbouring ones. Refuse it outright.
+ return false;
+#else
uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
data.isochronous[ep] = true;
data.xfer[ep][dir].max_size = largest_packet_size;
return true;
+#endif
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
(void)rhport;
(void)desc_ep;
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ return false; // CH58x has no isochronous support (see dcd_edpt_iso_alloc)
+#else
return true;
+#endif
}
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
@@ -362,12 +517,14 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
uint8_t dir = tu_edpt_dir(ep_addr);
struct usb_xfer *xfer = &data.xfer[ep][dir];
+ // Keep the IRQ masked across the whole arming sequence: update_in()/ep_rx_set_response() do a
+ // read-modify-write of the (combined) EP control register, which the ISR also RMWs to flip the
+ // manual data toggle; re-enabling before they run lets a transfer IRQ clobber that toggle.
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);
@@ -375,6 +532,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
uint8_t rx_res = data.isochronous[ep] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK;
ep_rx_set_response(ep, rx_res);
}
+ dcd_int_enable(rhport);
return true;
}
@@ -407,10 +565,11 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK);
}
} else {
+ // clear-stall resets the toggle to DATA0 (USB spec); manual-toggle parts then re-sync via ISR
if (dir == TUSB_DIR_OUT) {
- ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
+ ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
}
}
}
diff --git a/src/tusb_option.h b/src/tusb_option.h
index 415e083c9..b5457fe8a 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -195,6 +195,8 @@
#define OPT_MCU_CH32F20X 2210 ///< WCH CH32F20x
#define OPT_MCU_CH32V20X 2220 ///< WCH CH32V20X
#define OPT_MCU_CH32V103 2230 ///< WCH CH32V103
+#define OPT_MCU_CH583 2240 ///< WCH CH583
+#define OPT_MCU_CH582 OPT_MCU_CH583 ///< WCH CH582 (alias, same USB IP as CH583)
// NXP LPC MCX
#define OPT_MCU_MCXN9 2300 ///< NXP MCX N9 Series
diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py
index 055618e3c..07375c1ad 100755
--- a/test/hil/hil_test.py
+++ b/test/hil/hil_test.py
@@ -1485,9 +1485,13 @@ def test_device_hid_generic_inout(board):
# device tests
# note don't test 2 examples with cdc or 2 msc next to each other
device_tests = [
+ # Order matters: cdc_msc and cdc_msc_throughput share the same VID:PID (cafe:4003), so keep a
+ # differently-PID'd example (dfu, cafe:4000) between them. Boards whose CPU-reset does not drop
+ # D+ (e.g. WCH CH58x via openocd) only re-enumerate when the PID changes; back-to-back same-PID
+ # firmware would otherwise leave the host on the previous example's cached descriptors.
'device/cdc_dual_ports',
- 'device/dfu',
'device/cdc_msc',
+ 'device/dfu',
'device/cdc_msc_throughput',
'device/audio_test_freertos',
'device/dfu_runtime',
diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json
index a534f2601..2ea910c4c 100644
--- a/test/hil/tinyusb.json
+++ b/test/hil/tinyusb.json
@@ -494,6 +494,21 @@
"uid": "BC4954081051",
"args": ""
}
+ },
+ {
+ "name": "ch582m_evt",
+ "uid": "D443627B5450",
+ "toolchain": "riscv-gcc",
+ "tests": {
+ "device": true,
+ "host": false,
+ "dual": false
+ },
+ "flasher": {
+ "name": "openocd_wch",
+ "uid": "7FD88F0604B5",
+ "args": ""
+ }
}
],
"boards-skip": [
diff --git a/tools/get_deps.py b/tools/get_deps.py
index 60be5d89e..ebbf9b871 100755
--- a/tools/get_deps.py
+++ b/tools/get_deps.py
@@ -253,6 +253,9 @@ deps_optional = {
'hw/mcu/wch/ch32f20x': ['https://github.com/openwch/ch32f20x.git',
'77c4095087e5ed2c548ec9058e655d0b8757663b',
'ch32f20x'],
+ 'hw/mcu/wch/ch583': ['https://github.com/openwch/ch583.git',
+ 'bd508ad7ceed48377619837051412a651952857f',
+ 'ch583'],
'hw/mcu/artery/at32f403a_407': ['https://github.com/ArteryTek/AT32F403A_407_Firmware_Library.git',
'f2cb360c3d28fada76b374308b8c4c61d37a090b',
'at32f403a_407'],