diff --git a/k5viewer/README.md b/tools/k5viewer/README.md
similarity index 100%
rename from k5viewer/README.md
rename to tools/k5viewer/README.md
diff --git a/k5viewer/k5viewer.py b/tools/k5viewer/k5viewer.py
similarity index 100%
rename from k5viewer/k5viewer.py
rename to tools/k5viewer/k5viewer.py
diff --git a/utils/clean.bat b/tools/misc/clean.bat
similarity index 100%
rename from utils/clean.bat
rename to tools/misc/clean.bat
diff --git a/utils/main.cpp b/tools/misc/main.cpp
similarity index 100%
rename from utils/main.cpp
rename to tools/misc/main.cpp
diff --git a/utils/misc.bpf b/tools/misc/misc.bpf
similarity index 100%
rename from utils/misc.bpf
rename to tools/misc/misc.bpf
diff --git a/utils/misc.bpr b/tools/misc/misc.bpr
similarity index 100%
rename from utils/misc.bpr
rename to tools/misc/misc.bpr
diff --git a/utils/misc.exe b/tools/misc/misc.exe
similarity index 100%
rename from utils/misc.exe
rename to tools/misc/misc.exe
diff --git a/utils/misc.res b/tools/misc/misc.res
similarity index 100%
rename from utils/misc.res
rename to tools/misc/misc.res
diff --git a/utils/uv-k5_small.bin b/tools/misc/uv-k5_small.bin
similarity index 100%
rename from utils/uv-k5_small.bin
rename to tools/misc/uv-k5_small.bin
diff --git a/utils/uv-k5_small.fon b/tools/misc/uv-k5_small.fon
similarity index 100%
rename from utils/uv-k5_small.fon
rename to tools/misc/uv-k5_small.fon
diff --git a/utils/uv-k5_small_bold.bin b/tools/misc/uv-k5_small_bold.bin
similarity index 100%
rename from utils/uv-k5_small_bold.bin
rename to tools/misc/uv-k5_small_bold.bin
diff --git a/utils/uv-k5_small_bold.fon b/tools/misc/uv-k5_small_bold.fon
similarity index 100%
rename from utils/uv-k5_small_bold.fon
rename to tools/misc/uv-k5_small_bold.fon
diff --git a/serialtool/__init__.py b/tools/serialtool/__init__.py
similarity index 100%
rename from serialtool/__init__.py
rename to tools/serialtool/__init__.py
diff --git a/serialtool/_dump.py b/tools/serialtool/_dump.py
similarity index 100%
rename from serialtool/_dump.py
rename to tools/serialtool/_dump.py
diff --git a/serialtool/_prog.py b/tools/serialtool/_prog.py
similarity index 100%
rename from serialtool/_prog.py
rename to tools/serialtool/_prog.py
diff --git a/serialtool/_restore.py b/tools/serialtool/_restore.py
similarity index 100%
rename from serialtool/_restore.py
rename to tools/serialtool/_restore.py
diff --git a/serialtool/cli.py b/tools/serialtool/cli.py
similarity index 100%
rename from serialtool/cli.py
rename to tools/serialtool/cli.py
diff --git a/serialtool/msg.py b/tools/serialtool/msg.py
similarity index 100%
rename from serialtool/msg.py
rename to tools/serialtool/msg.py
diff --git a/tools/unbrick_k5_v1/README.md b/tools/unbrick_k5_v1/README.md
new file mode 100644
index 00000000..738aae4f
--- /dev/null
+++ b/tools/unbrick_k5_v1/README.md
@@ -0,0 +1,119 @@
+# Unbricking the UV-K5 V1
+
+
+
+Restoring a UV-K5 V1 accidentally flashed with the F4HWN 4.3 Fusion 🔥 Edition firmware, using OpenOCD and a ST-LINK programmer.
+
+## Introduction
+
+Some users have accidentally flashed a UV-K5 V1 with the F4HWN 4.3 Fusion 🔥 Edition firmware. This edition **is only** compatible with:
+
+- UV-K5 V3
+- UV-K1
+
+When flashed onto a UV-K5 V1, it causes a complete brick, such as:
+
+- The radio does not power on anymore
+- DFU mode is unavailable
+- No LED activity
+- Flashing tools cannot detect the device
+
+Fortunately, the UV-K5 V1 can be fully restored by using OpenOCD and a ST-LINK programmer.
+
+## Requirements
+
+You will need:
+
+- A ST-LINK V2 programmer (original or clone)
+- Four Dupont jumper wires
+- A small screwdriver to open the radio
+- A computer with OpenOCD installed (Windows / macOS / Linux).
+
+## SWD connection points on the UV-K5 V1
+
+The radio must be opened to access the front side of the PCB.
+The UV-K5 V1 exposes a 4-pin SWD (Serial Wire Debug) interface:
+
+|Signal| UV-K5 V1 Pad| ST-LINK Pin|
+|:-------- |:--------:| --------:|
+|GND |GND pad | GND |
+|SWCLK |SWCLK pad | SWCLK
+|SWDIO |SWDIO pad | SWDIO
+|3.3V | VCC pad | 3.3V |
+
+
+
+> [!WARNING]
+> Do not connect the battery while using the ST-LINK.
+The ST-LINK provides 3.3V to the board.
+
+## Installing OpenOCD
+
+### macOS (Homebrew)
+
+`brew install openocd`
+
+### Windows
+
+Download the official build from:
+
+- [Website](https://openocd.org/pages/getting-openocd.html)
+- [GitHub](https://github.com/openocd-org/openocd/releases)
+
+### Linux (Ubuntu / Debian)
+
+`sudo apt install openocd`
+
+### Verify installation
+
+`openocd --version`
+
+## Download unbrick toolkit
+
+Download the [unbrick toolkit archive](https://github.com/user-attachments/files/24002834/unbrick_k5_v1.zip) and extract it on your PC.
+
+## Unbrick procedure
+
+### Connect the ST-LINK
+
+1. Connect the ST-LINK pins to the SWD pads:
+
+ * 3.3V → 3.3V
+ * SWDIO → SWDIO
+ * SWCLK → SWCLK
+ * GND → GND
+
+2. Plug the ST-LINK into your computer.
+3. Power on your UV-K5 V1 (normal mode).
+
+### Flash the bootloader
+
+From the `unbrick_k5_v1` directory:
+
+#### Option A — Use the helper script
+
+`
+./unbrick_k5_v1.sh
+`
+
+#### Option B — Run the OpenOCD command manually
+
+`
+openocd -f ./interface/stlink.cfg -f ./target/dp32g030.cfg -c "init; reset halt; uv_flash_bl bootloader.bin; shutdown"
+`
+
+
+https://github.com/user-attachments/assets/a511fdb3-a3a3-4fe1-91cc-31e765221b22
+
+
+If no errors appear, the bootloader has been successfully restored.
+
+- Disconnect and remove the ST-LINK from your UV-K5 V1 SWD port
+- Reinstall the battery
+- Power on the radio on DFU mode
+
+The device should now start correctly on DFU mode again. You can then flash stock firmware, or the [F4HWN firmware for UV-K5 V1](https://github.com/armel/uv-k5-firmware-custom) (not the Fusion 🔥 Edition).
+
+# Disclaimer
+
+This procedure requires opening the device and directly manipulating its microcontroller over SWD. Incorrect use may permanently damage the radio. Proceed at your own risk.
diff --git a/tools/unbrick_k5_v1/bootloader.bin b/tools/unbrick_k5_v1/bootloader.bin
new file mode 100644
index 00000000..11fdf1d8
Binary files /dev/null and b/tools/unbrick_k5_v1/bootloader.bin differ
diff --git a/tools/unbrick_k5_v1/interface/stlink-dap.cfg b/tools/unbrick_k5_v1/interface/stlink-dap.cfg
new file mode 100644
index 00000000..2103a298
--- /dev/null
+++ b/tools/unbrick_k5_v1/interface/stlink-dap.cfg
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+#
+# STMicroelectronics ST-LINK/V1, ST-LINK/V2, ST-LINK/V2-1, STLINK-V3 in-circuit
+# debugger/programmer
+#
+# This new interface driver creates a ST-Link wrapper for ARM-DAP named "dapdirect"
+# Old ST-LINK/V1 and ST-LINK/V2 pre version V2J24 don't support "dapdirect"
+#
+# SWIM transport is natively supported
+#
+
+adapter driver st-link
+st-link vid_pid 0x0483 0x3744 0x0483 0x3748 0x0483 0x374b 0x0483 0x374d 0x0483 0x374e 0x0483 0x374f 0x0483 0x3752 0x0483 0x3753 0x0483 0x3754 0x0483 0x3755 0x0483 0x3757
+
+# transport select dapdirect_jtag
+# transport select dapdirect_swd
+# transport select swim
+
+# Optionally specify the serial number of usb device
+# e.g.
+# adapter serial "\xaa\xbc\x6e\x06\x50\x75\xff\x55\x17\x42\x19\x3f"
diff --git a/tools/unbrick_k5_v1/interface/stlink-v1.cfg b/tools/unbrick_k5_v1/interface/stlink-v1.cfg
new file mode 100644
index 00000000..9319fc35
--- /dev/null
+++ b/tools/unbrick_k5_v1/interface/stlink-v1.cfg
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+echo "WARNING: interface/stlink-v1.cfg is deprecated, please switch to interface/stlink.cfg"
+source [find interface/stlink.cfg]
diff --git a/tools/unbrick_k5_v1/interface/stlink-v2-1.cfg b/tools/unbrick_k5_v1/interface/stlink-v2-1.cfg
new file mode 100644
index 00000000..dd03e1b4
--- /dev/null
+++ b/tools/unbrick_k5_v1/interface/stlink-v2-1.cfg
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+echo "WARNING: interface/stlink-v2-1.cfg is deprecated, please switch to interface/stlink.cfg"
+source [find interface/stlink.cfg]
diff --git a/tools/unbrick_k5_v1/interface/stlink-v2.cfg b/tools/unbrick_k5_v1/interface/stlink-v2.cfg
new file mode 100644
index 00000000..c52eec9b
--- /dev/null
+++ b/tools/unbrick_k5_v1/interface/stlink-v2.cfg
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+echo "WARNING: interface/stlink-v2.cfg is deprecated, please switch to interface/stlink.cfg"
+source [find interface/stlink.cfg]
diff --git a/tools/unbrick_k5_v1/interface/stlink.cfg b/tools/unbrick_k5_v1/interface/stlink.cfg
new file mode 100644
index 00000000..0a00581f
--- /dev/null
+++ b/tools/unbrick_k5_v1/interface/stlink.cfg
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+#
+# STMicroelectronics ST-LINK/V1, ST-LINK/V2, ST-LINK/V2-1, STLINK-V3 in-circuit
+# debugger/programmer
+#
+
+adapter driver hla
+hla layout stlink
+hla device_desc "ST-LINK"
+hla vid_pid 0x0483 0x3744 0x0483 0x3748 0x0483 0x374b 0x0483 0x374d 0x0483 0x374e 0x0483 0x374f 0x0483 0x3752 0x0483 0x3753 0x0483 0x3754 0x0483 0x3755 0x0483 0x3757
+
+# Optionally specify the serial number of ST-LINK/V2 usb device. ST-LINK/V2
+# devices seem to have serial numbers with unreadable characters. ST-LINK/V2
+# firmware version >= V2.J21.S4 recommended to avoid issues with adapter serial
+# number reset issues.
+# eg.
+#adapter serial "\xaa\xbc\x6e\x06\x50\x75\xff\x55\x17\x42\x19\x3f"
diff --git a/tools/unbrick_k5_v1/media/bottom.png b/tools/unbrick_k5_v1/media/bottom.png
new file mode 100644
index 00000000..07a8b7d3
Binary files /dev/null and b/tools/unbrick_k5_v1/media/bottom.png differ
diff --git a/tools/unbrick_k5_v1/media/openocd.png b/tools/unbrick_k5_v1/media/openocd.png
new file mode 100644
index 00000000..f15a1ca0
Binary files /dev/null and b/tools/unbrick_k5_v1/media/openocd.png differ
diff --git a/tools/unbrick_k5_v1/media/pcbite.png b/tools/unbrick_k5_v1/media/pcbite.png
new file mode 100644
index 00000000..e2c02b2c
Binary files /dev/null and b/tools/unbrick_k5_v1/media/pcbite.png differ
diff --git a/tools/unbrick_k5_v1/media/pinout.png b/tools/unbrick_k5_v1/media/pinout.png
new file mode 100644
index 00000000..2551cc36
Binary files /dev/null and b/tools/unbrick_k5_v1/media/pinout.png differ
diff --git a/tools/unbrick_k5_v1/media/unbrick.mov b/tools/unbrick_k5_v1/media/unbrick.mov
new file mode 100644
index 00000000..45838867
Binary files /dev/null and b/tools/unbrick_k5_v1/media/unbrick.mov differ
diff --git a/tools/unbrick_k5_v1/target/dp32g030.cfg b/tools/unbrick_k5_v1/target/dp32g030.cfg
new file mode 100644
index 00000000..fe9e6963
--- /dev/null
+++ b/tools/unbrick_k5_v1/target/dp32g030.cfg
@@ -0,0 +1,359 @@
+#OpenOCD script for Action Dynamic DP32G030 ARM Cortex M0 CPU (UV-5R, UV-k5 Ham HTs)
+#For use with cheap ST-Link USB debug probe
+source target/swj-dp.tcl
+
+set _CHIP_NAME DP32G0xx
+set _ENDIAN little
+set _WORKAREASIZE 0x1000
+set _FLASH_SIZE 0x10000
+set _CPUTAPID 0x0BB11477
+set _TARGETNAME $_CHIP_NAME.cpu
+set _FLASHNAME $_CHIP_NAME.flash
+set _SECTOR_SIZE 512
+set _MASKING_CFG 2 ;#1:2kB, 2:4kB, 3:8kB
+
+adapter speed 960
+adapter srst delay 100
+reset_config srst_nogate
+
+# Create a new dap, with name chip and role CPU, -enable let's OpenOCD to know to add it to the scan
+swj_newdap $_CHIP_NAME cpu -expected-id $_CPUTAPID -enable
+
+# Create the DAP instance, this must be explicitly created according to the OpenOCD docs
+dap create $_CHIP_NAME.dap -chain-position $_CHIP_NAME.cpu
+
+# Set up the GDB target for the CPU
+target create $_CHIP_NAME.cpu cortex_m -endian $_ENDIAN -dap $_CHIP_NAME.dap
+$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
+
+# Declare internal bank
+flash bank $_FLASHNAME stm32f1x 0x08000000 $_FLASH_SIZE 0 0 $_TARGETNAME
+
+proc check_readiness {} {
+ while {[read_memory 0x4006F014 32 1] & 0x2} {}
+}
+
+proc rom_mask_off {} {
+ echo "\nChecking ROM masking"
+ check_readiness
+ set status [read_memory 0x4006F020 32 1]
+ if {($status & 0x3) != 0} {
+ echo [format "\nROM masking is set to 0b%03b. Unsetting..." $status]
+ write_memory 0x4006F020 32 [expr {[read_memory 0x4006F020 32 1] & 0x3}]
+ check_readiness
+ write_memory 0x4006F020 32 0
+ check_readiness
+ write_memory 0x4006F020 32 4
+ }
+ return [read_memory 0x4006F020 32 1]
+}
+
+proc rom_mask_on {} {
+ global _MASKING_CFG
+ echo "\nChecking ROM masking"
+ check_readiness
+ set status [read_memory 0x4006F020 32 1]
+ if {($status & 0x3) != $_MASKING_CFG} {
+ echo [format "\nROM masking is set to 0b%03b. Setting ON..." $status]
+ write_memory 0x4006F020 32 [expr {[read_memory 0x4006F020 32 1] & 0x3}]
+ check_readiness
+ write_memory 0x4006F020 32 $_MASKING_CFG
+ check_readiness
+ write_memory 0x4006F020 32 [expr {4 | $_MASKING_CFG}]
+ }
+ return [read_memory 0x4006F020 32 1]
+}
+
+proc unlock_rom {} {
+ write_memory 0x4006F01c 32 0xAA
+ check_readiness
+}
+
+proc lock_rom {} {
+ write_memory 0x4006F018 32 0x55
+ check_readiness
+}
+
+proc select_region {target_r} {
+ #Region 0 is main user ROM area, 1 is NVRAM area
+ write_memory 0x4006F000 32 [expr {(0x31 & [read_memory 0x4006F000 32 1]) | (($target_r & 0x1) << 1)}]
+ check_readiness
+}
+
+proc wipe_sector_range {st_sec sec_count} {
+ set last [expr {$st_sec + $sec_count}]
+ set reg [expr {[read_memory 0x4006F000 32 1] & 0x7FFFFFFF}]
+ write_memory 0x4006F000 32 [expr {$reg | 0x8}] ;#set writing mode ERASE
+
+ for {set i $st_sec} {$i < $last} {incr i} {
+ check_readiness
+ echo -n [format "\rErasing sector 0x%02x = offset 0x%04x" [expr {$i}] [expr {$i*512}] ]
+ write_memory 0x4006F004 32 [expr {$i << 7}] ;#set address in flash
+ write_memory 0x4006F010 32 0x01 ;#do it
+ }
+ check_readiness
+ write_memory 0x4006F000 32 $reg
+}
+
+proc wipe_rom {} {
+ #This will wipe everything including bootloader
+ global _SECTOR_SIZE
+ global _FLASH_SIZE
+ unlock_rom
+ select_region 0
+ if {[rom_mask_off] != 4} {
+ echo "\nROM Masking failed to disable!"
+ close $fd
+ return
+ }
+ wipe_sector_range 0 [expr {$_FLASH_SIZE / $_SECTOR_SIZE}]
+}
+
+proc binary_to_int {data} {
+ # Complète la chaîne à 4 octets si nécessaire
+ set data $data[string repeat \xFF [expr {4 - [string length $data]}]]
+
+ # Initialise le résultat à 0
+ set result 0
+
+ # Parcourt les octets et assemble l'entier
+ for {set i 0} {$i < 4} {incr i} {
+ # Récupère l'octet à la position $i
+ set byte [scan [string index $data $i] %c]
+ # Décale l'octet en fonction de l'ordre Little Endian
+ set result [expr {$result | ($byte & 0xFF) << (8 * $i)}]
+ }
+
+ return $result
+}
+
+proc write_image {filename offset} {
+ global _SECTOR_SIZE
+ global _FLASH_SIZE
+ set fs [file size $filename]
+ set fd [open $filename "rb"]
+ set reg [expr {[read_memory 0x4006F000 32 1] & 0x7FFFFFFF}]
+ write_memory 0x4006F000 32 [expr {$reg | 0x4}] ;#set writing mode PROGRAM
+ while {![eof $fd]} {
+ if {($offset+4) > $_FLASH_SIZE} {
+ echo "\nData exceeds main storage capacity!"
+ write_memory 0x4006F000 32 $reg
+ lock_rom
+ close $fd
+ return
+ }
+ check_readiness
+ set data [read $fd 4]
+ set data $data[string repeat \xFF [expr {4-[string length $data]}]] ;#padding
+ #binary scan $data i i_data
+ set i_data [binary_to_int $data]
+ write_memory 0x4006F004 32 [expr {($offset>>2)+0xC000}] ;#set destination offset
+ write_memory 0x4006F008 32 $i_data ;#set word
+ write_memory 0x4006F010 32 0x01 ;#set OPSTART=1
+ while {([read_memory 0x4006F014 32 1] & 4) == 0} {}
+ echo -n [format "\rProgrammed up to 0x%04x (FLASH_ADDR=0x%04x)" $offset [expr {($offset>>2)+0xC000}]]
+ incr offset 4
+ }
+ check_readiness
+ write_memory 0x4006F000 32 $reg ;#reset writing mode to OFF
+}
+
+proc flash_blocks {filename address nblocks offset} {
+ #Intended for speed. Due to tight timings, sometimes it works, sometimes it does not. Needs clocks adjusting there.
+ global _SECTOR_SIZE
+ global _FLASH_SIZE
+
+ if {($nblocks != 0) & [expr {$nblocks & 1}]} {
+ set nblocks [expr {$nblocks + 1}]
+ }
+ set addr [expr {$_SECTOR_SIZE * ($address >> 9)}]
+ set fs [expr {((($_SECTOR_SIZE*$nblocks)/2 + $_SECTOR_SIZE-1)&(0x10000000-$_SECTOR_SIZE))}]
+ set fd [open $filename "rb"]
+
+ read $fd $addr
+ set addr [expr {$addr + $offset}] ;#apply ROM offset
+ set reg [expr {[read_memory 0x4006F000 32 1] & 0x7FFFFFFF}]
+
+ echo -n [format "\tWiping %02d sectors, starting at %02d " [expr {$nblocks / 2}] [expr {$addr >> 9}]]
+
+ wipe_sector_range [expr {$addr >> 9}] [expr {$nblocks / 2}] ;#wipe related sectors
+ echo "\nRegion cleared OK"
+
+ echo [format "%02d bytes to push" $fs]; ##DEBUG
+ write_memory 0x4006F000 32 [expr {$reg | 0x4}] ;#set writing mode PROGRAM
+
+ while {$fs > 0} {
+ write_memory 0x4006F004 32 [expr {0xC000+(($addr)>>2)}] ;#set block starting offset
+ set i_buffer {}
+ for {set blk 0} {$blk < $_SECTOR_SIZE/2} {incr blk 4} {
+ set data [read $fd 4]
+ set data $data[string repeat \xFF [expr {4-[string length $data]}]] ;#padding to desired ending block
+ if {($addr+$_SECTOR_SIZE/2) >= $_FLASH_SIZE} {
+ echo [format "\nMain firmware image upper boundary reached (%d)!" $addr]
+ write_memory 0x4006F000 32 $reg ;#reset writing mode to OFF
+ close $fd
+ return
+ }
+ binary scan $data i i_data
+ lappend i_buffer [expr {$i_data & 0xFFFFFFFF}]
+ incr fs -4
+ }
+ echo [format "\nWriting at offset 0x%04x" [expr {$addr}]]
+ ##for {set bi 0} {$bi < 64} {incr bi} {echo -n [format "%08x" [lindex $i_buffer $bi]]}; #DEBUG
+ write_memory 0x4006F008 32 [lindex $i_buffer 0] ;#prepare 1st word: we need to be quick beyond this point
+ check_readiness
+ write_memory 0x4006F010 32 0x01
+ for {set bi 1} {$bi < 64} {incr bi} {while {([read_memory 0x4006F014 32 1] & 0x4) == 4} {write_memory 0x4006F008 32 [lindex $i_buffer $bi]}}
+ check_readiness
+ incr addr $_SECTOR_SIZE/2 ;# Next block
+ }
+ write_memory 0x4006F000 32 $reg ;#reset writing mode to OFF
+ echo [format "\nLast write was 0x%08x " [lindex $i_buffer 63]]
+ close $fd
+ return
+}
+
+proc toggle_pin_gpioa {pin} {
+ write_memory 0x40060000 16 [expr {[read_memory 0x40060000 16 1] ^(1<<$pin) }]
+}
+
+proc toggle_pin_gpiob {pin} {
+ write_memory 0x40060800 16 [expr {[read_memory 0x40060800 16 1] ^(1<<$pin) }]
+}
+
+proc toggle_pin_gpioc {pin} {
+ write_memory 0x40061000 16 [expr {[read_memory 0x40061000 16 1] ^(1<<$pin) }]
+}
+
+proc set_pin_gpioa {pin value} {
+ if {$value == 0} {
+ write_memory 0x40060000 16 [expr {[read_memory 0x40060000 16 1] &~(1<<$pin) }]
+ } else {
+ write_memory 0x40060000 16 [expr {[read_memory 0x40060000 16 1] |(1<<$pin) }]
+ }
+}
+
+proc set_pin_gpiob {pin value} {
+ if {$value == 0} {
+ write_memory 0x40060800 16 [expr {[read_memory 0x40060800 16 1] &~(1<<$pin) }]
+ } else {
+ write_memory 0x40060800 16 [expr {[read_memory 0x40060800 16 1] |(1<<$pin) }]
+ }
+}
+
+proc set_pin_gpioc {pin value} {
+ if {$value == 0} {
+ write_memory 0x40061000 16 [expr {[read_memory 0x40061000 16 1] &~(1<<$pin) }]
+ } else {
+ write_memory 0x40061000 16 [expr {[read_memory 0x40061000 16 1] |(1<<$pin) }]
+ }
+}
+
+##Quansheng UVK5-specific snippets
+
+proc uv_fastflash_bl {filename} {
+ write_memory 0x4006F024 32 0x4E02A300 ;#force stock timings, just in case
+ write_memory 0x4006F028 32 0x210360
+ write_memory 0x4006F000 32 0x1
+ check_readiness
+ select_region 0
+ if {[rom_mask_off] != 4} {
+ echo "\nROM Masking failed to disable!"
+ close $fd
+ return
+ }
+ reset halt
+ unlock_rom
+
+ flash_blocks $filename 0 16 0
+
+ #just relock flashROM
+ lock_rom
+}
+
+proc uv_fastflash_fw {filename} {
+ #Make sure bootloader is hidden
+ if {[rom_mask_on] != 6} {
+ echo "\nROM Masking failed to enable!"
+ close $fd
+ return
+ }
+ reset halt
+ unlock_rom
+
+ flash_blocks $filename 0 [expr {[file size $filename] >> 8}] 0
+ reset
+ echo "\nCPU reset: Transceiver should boot now."
+}
+
+proc uv_flash_bl {filename} {
+ #Securely rewrites bootloader (slowly)
+
+ if {[file size $filename] > 0x1000} {
+ echo [format "Bootloader image is too large to fit!]
+ return
+ }
+ select_region 0
+ if {[rom_mask_off] != 4} {
+ echo "\nROM Masking failed to disable!"
+ return
+ }
+ reset halt
+ unlock_rom
+
+ wipe_sector_range 0 8
+ echo "\nRegion cleared OK"
+
+ write_image $filename 0
+
+ if {[rom_mask_on] != 6} {
+ echo "\nROM Masking failed to enable!"
+ lock_rom
+ return
+ }
+ #relock flashROM, in case conventional method for fw is preferred
+ lock_rom
+ echo "\nBootloader code programmed.\nYou can use uv_flash_fw to program main firmware, or just use stock tool to do it."
+}
+
+proc uv_flash_fw {filename} {
+ #Securely rewrites main firmware (slowly)
+
+ select_region 0
+ #Make sure bootloader is hidden
+ if {[rom_mask_on] != 6} {
+ echo "\nROM Masking failed to enable!"
+ return
+ }
+ reset halt
+ unlock_rom
+
+ wipe_sector_range 0 120
+ echo "\nRegion cleared OK"
+
+ write_image $filename 0
+
+ #relock flashROM, then reset CPU
+ lock_rom
+ reset
+ echo "\nCPU reset: Transceiver should boot now."
+}
+
+proc uv_flashlight_toggle {} {
+ toggle_pin_gpioc 3 ;# toggles PORTC.3
+}
+
+proc uv_flashlight_on {} {
+ set_pin_gpioc 3 1 ;# set PORTC.3 high
+}
+
+proc uv_flashlight_off {} {
+ set_pin_gpioc 3 0 ;# set PORTC.3 to low
+}
+
+proc uv_backlight_toggle {} {
+ toggle_pin_gpiob 6 ;# toggles PORTB.6
+}
+
+init
+#reset halt
diff --git a/tools/unbrick_k5_v1/target/swj-dp-legacy.tcl b/tools/unbrick_k5_v1/target/swj-dp-legacy.tcl
new file mode 100644
index 00000000..8d2464a6
--- /dev/null
+++ b/tools/unbrick_k5_v1/target/swj-dp-legacy.tcl
@@ -0,0 +1,34 @@
+# ARM Debug Interface V5 (ADI_V5) utility
+# ... Mostly for SWJ-DP (not SW-DP or JTAG-DP, since
+# SW-DP and JTAG-DP targets don't need to switch based
+# on which transport is active.
+#
+# declare a JTAG or SWD Debug Access Point (DAP)
+# based on the transport in use with this session.
+# You can't access JTAG ops when SWD is active, etc.
+
+# params are currently what "jtag newtap" uses
+# because OpenOCD internals are still strongly biased
+# to JTAG .... but for SWD, "irlen" etc are ignored,
+# and the internals work differently
+
+# for now, ignore non-JTAG and non-SWD transports
+# (e.g. initial flash programming via SPI or UART)
+
+# split out "chip" and "tag" so we can someday handle
+# them more uniformly irlen too...)
+
+if [catch {transport select}] {
+ echo "Error: unable to select a session transport. Can't continue."
+ shutdown
+}
+
+proc swj_newdap {chip tag args} {
+ if [using_hla] {
+ eval hla newtap $chip $tag $args
+ } elseif [using_jtag] {
+ eval jtag newtap $chip $tag $args
+ } elseif [using_swd] {
+ eval swd newdap $chip $tag $args
+ }
+}
diff --git a/tools/unbrick_k5_v1/target/swj-dp.tcl b/tools/unbrick_k5_v1/target/swj-dp.tcl
new file mode 100644
index 00000000..ee28b6fe
--- /dev/null
+++ b/tools/unbrick_k5_v1/target/swj-dp.tcl
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# ARM Debug Interface V5 (ADI_V5) utility
+# ... Mostly for SWJ-DP (not SW-DP or JTAG-DP, since
+# SW-DP and JTAG-DP targets don't need to switch based
+# on which transport is active.
+#
+# declare a JTAG or SWD Debug Access Point (DAP)
+# based on the transport in use with this session.
+# You can't access JTAG ops when SWD is active, etc.
+
+# params are currently what "jtag newtap" uses
+# because OpenOCD internals are still strongly biased
+# to JTAG .... but for SWD, "irlen" etc are ignored,
+# and the internals work differently
+
+# for now, ignore non-JTAG and non-SWD transports
+# (e.g. initial flash programming via SPI or UART)
+
+# split out "chip" and "tag" so we can someday handle
+# them more uniformly irlen too...)
+
+if [catch {transport select}] {
+ echo "Error: unable to select a session transport. Can't continue."
+ shutdown
+}
+
+proc swj_newdap {chip tag args} {
+ if [using_jtag] {
+ eval jtag newtap $chip $tag $args
+ } elseif [using_swd] {
+ eval swd newdap $chip $tag $args
+ } else {
+ echo "Error: transport '[ transport select ]' not supported by swj_newdap"
+ shutdown
+ }
+}
diff --git a/tools/unbrick_k5_v1/unbrick_k5_v1.sh b/tools/unbrick_k5_v1/unbrick_k5_v1.sh
new file mode 100755
index 00000000..d6638418
--- /dev/null
+++ b/tools/unbrick_k5_v1/unbrick_k5_v1.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+openocd -f ./interface/stlink.cfg -f ./target/dp32g030.cfg -c "init; reset halt; uv_flash_bl bootloader.bin; shutdown"
\ No newline at end of file