39161b9ee793f4c62836b209ec877acda457b88d

Author
HorrorTroll <sonicvipduc@gmail.com>
Committer
GitHub <noreply@github.com>
Date

Message

Added MCU support for ArteryTek AT32F415 (#23445)

Diff

This diff is truncated to protect this page.

   1diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
   2index f5b01de015d04de6f26bdaf9b17d0ee04a22f6c7..86ced30902916687e2274cf8dd61588283f57a36 100644
   3--- a/builddefs/common_features.mk
   4+++ b/builddefs/common_features.mk
   5@@ -215,7 +215,7 @@ else
   6         COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
   7         COMMON_VPATH += $(DRIVER_PATH)/flash
   8         SRC += eeprom_driver.c eeprom_legacy_emulated_flash.c legacy_flash_ops.c
   9-      else ifneq ($(filter $(MCU_SERIES),STM32F1xx STM32F3xx STM32F4xx STM32L4xx STM32G4xx WB32F3G71xx WB32FQ95xx GD32VF103),)
  10+      else ifneq ($(filter $(MCU_SERIES),STM32F1xx STM32F3xx STM32F4xx STM32L4xx STM32G4xx WB32F3G71xx WB32FQ95xx AT32F415 GD32VF103),)
  11         # Wear-leveling EEPROM implementation, backed by MCU flash
  12         OPT_DEFS += -DEEPROM_DRIVER -DEEPROM_WEAR_LEVELING
  13         SRC += eeprom_driver.c eeprom_wear_leveling.c
  14diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema
  15index 72a33f55ad156a85cb833e1c87aebd1265978e41..9f1f6dd74a58a03ad513642959bffb39e4924736 100644
  16--- a/data/schemas/keyboard.jsonschema
  17+++ b/data/schemas/keyboard.jsonschema
  18@@ -92,6 +92,7 @@
  19                 "GD32VF103",
  20                 "WB32F3G71",
  21                 "WB32FQ95",
  22+                "AT32F415",
  23                 "atmega16u2",
  24                 "atmega32u2",
  25                 "atmega16u4",
  26@@ -216,6 +217,7 @@
  27             "type": "string",
  28             "enum": [
  29                 "apm32-dfu",
  30+                "at32-dfu",
  31                 "atmel-dfu",
  32                 "bootloadhid",
  33                 "caterina",
  34diff --git a/docs/compatible_microcontrollers.md b/docs/compatible_microcontrollers.md
  35index e2d27e06f2f1de281fd38a72ff1b15063f498f4b..f148c39191e1d646dd337e514bffb0c212c9ad51 100644
  36--- a/docs/compatible_microcontrollers.md
  37+++ b/docs/compatible_microcontrollers.md
  38@@ -57,6 +57,10 @@ You can also use any ARM chip with USB that [ChibiOS](https://www.chibios.org) s
  39  * [WB32F3G71xx](http://www.westberrytech.com)
  40  * [WB32FQ95xx](http://www.westberrytech.com)
  41 
  42+### Artery (AT32)
  43+
  44+ * [AT32F415](https://www.arterychip.com/en/product/AT32F415.jsp)
  45+
  46 ### NXP (Kinetis)
  47 
  48  * [MKL26Z64](https://www.nxp.com/products/processors-and-microcontrollers/arm-microcontrollers/general-purpose-mcus/kl-series-cortex-m0-plus/kinetis-kl2x-72-96-mhz-usb-ultra-low-power-microcontrollers-mcus-based-on-arm-cortex-m0-plus-core:KL2x)
  49diff --git a/docs/driver_installation_zadig.md b/docs/driver_installation_zadig.md
  50index 9743c0adc2fb0db5124bfc04dc22802e8c07d69e..1a5bd1cc3464d6f19ea68a2ffab431799be3b7aa 100644
  51--- a/docs/driver_installation_zadig.md
  52+++ b/docs/driver_installation_zadig.md
  53@@ -98,6 +98,7 @@ The device name here is the name that appears in Zadig, and may not be what the
  54 |`bootloadhid` |HIDBoot                       |`16C0:05DF`   |HidUsb |
  55 |`usbasploader`|USBasp                        |`16C0:05DC`   |libusbK|
  56 |`apm32-dfu`   |APM32 DFU ISP Mode            |`314B:0106`   |WinUSB |
  57+|`at32-dfu`    |AT32 Bootloader DFU           |`2E3C:DF11`   |WinUSB |
  58 |`stm32-dfu`   |STM32 BOOTLOADER              |`0483:DF11`   |WinUSB |
  59 |`gd32v-dfu`   |GD32V BOOTLOADER              |`28E9:0189`   |WinUSB |
  60 |`kiibohd`     |Kiibohd DFU Bootloader        |`1C11:B007`   |WinUSB |
  61diff --git a/docs/flashing.md b/docs/flashing.md
  62index 2afb85886025214dbdd9a3a4331b4e3a2e813e9b..29dc780aaf035f6c5fc2d05257d81242b102109b 100644
  63--- a/docs/flashing.md
  64+++ b/docs/flashing.md
  65@@ -345,6 +345,39 @@ Flashing sequence:
  66 3. Flash a .bin file
  67 4. Reset the device into application mode (may be done automatically)
  68 
  69+## AT32 DFU
  70+
  71+All AT32 MCUs come preloaded with a factory bootloader that cannot be modified nor deleted.
  72+
  73+To ensure compatibility with the AT32-DFU bootloader, make sure this block is present in your `rules.mk`:
  74+
  75+```make
  76+# Bootloader selection
  77+BOOTLOADER = at32-dfu
  78+```
  79+
  80+Compatible flashers:
  81+
  82+* [dfu-util](https://dfu-util.sourceforge.net/) / `:dfu-util` target in QMK (recommended command line)
  83+  ```
  84+  dfu-util -a 0 -d 2E3C:DF11 -s 0x8000000:leave -D <filename>
  85+  ```
  86+
  87+Flashing sequence:
  88+
  89+1. Enter the bootloader using any of the following methods:
  90+    * Tap the `QK_BOOT` keycode
  91+    * If a reset circuit is present, tap the `RESET` button on the PCB; some boards may also have a toggle switch that must be flipped
  92+    * Otherwise, you need to bridge `BOOT0` to VCC (via `BOOT0` button or jumper), short `RESET` to GND (via `RESET` button or jumper), and then let go of the `BOOT0` bridge
  93+2. Wait for the OS to detect the device
  94+3. Flash a .bin file
  95+4. Reset the device into application mode (may be done automatically)
  96+
  97+### `make` Targets
  98+
  99+* `:dfu-util`: Waits until an AT32 bootloader device is available, and then flashes the firmware.
 100+* `:dfu-util-split-left` and `:dfu-util-split-right`: Flashes the firmware as with `:dfu-util`, but also sets the handedness setting in EEPROM.
 101+
 102 ## tinyuf2
 103 
 104 Keyboards may opt into supporting the tinyuf2 bootloader. This is currently only supported on F303/F401/F411.
 105diff --git a/keyboards/handwired/onekey/at_start_f415/board.h b/keyboards/handwired/onekey/at_start_f415/board.h
 106new file mode 100644
 107index 0000000000000000000000000000000000000000..f4adc2d4183abf3ad8453b1e06d50118a4db63fb
 108--- /dev/null
 109+++ b/keyboards/handwired/onekey/at_start_f415/board.h
 110@@ -0,0 +1,10 @@
 111+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
 112+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
 113+// SPDX-License-Identifier: GPL-2.0-or-later
 114+
 115+#pragma once
 116+
 117+#include_next <board.h>
 118+
 119+#undef AT32F415KB
 120+#define AT32F415RC
 121diff --git a/keyboards/handwired/onekey/at_start_f415/config.h b/keyboards/handwired/onekey/at_start_f415/config.h
 122new file mode 100644
 123index 0000000000000000000000000000000000000000..266e45dd0046361debba946bdbeafbf618bda8a1
 124--- /dev/null
 125+++ b/keyboards/handwired/onekey/at_start_f415/config.h
 126@@ -0,0 +1,10 @@
 127+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
 128+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
 129+// SPDX-License-Identifier: GPL-2.0-or-later
 130+
 131+#pragma once
 132+
 133+#define ADC_PIN A0
 134+
 135+#define BACKLIGHT_PWM_DRIVER PWMD5
 136+#define BACKLIGHT_PWM_CHANNEL 1
 137diff --git a/keyboards/handwired/onekey/at_start_f415/halconf.h b/keyboards/handwired/onekey/at_start_f415/halconf.h
 138new file mode 100644
 139index 0000000000000000000000000000000000000000..1423633d15fc75a045a787dfd27647e65b04e214
 140--- /dev/null
 141+++ b/keyboards/handwired/onekey/at_start_f415/halconf.h
 142@@ -0,0 +1,13 @@
 143+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
 144+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
 145+// SPDX-License-Identifier: GPL-2.0-or-later
 146+
 147+#pragma once
 148+
 149+#define HAL_USE_ADC TRUE
 150+
 151+#define HAL_USE_I2C TRUE
 152+
 153+#define HAL_USE_PWM TRUE
 154+
 155+#include_next <halconf.h>
 156diff --git a/keyboards/handwired/onekey/at_start_f415/keyboard.json b/keyboards/handwired/onekey/at_start_f415/keyboard.json
 157new file mode 100644
 158index 0000000000000000000000000000000000000000..f598923a6a55fb6fcdf8128f1b453c59946640a3
 159--- /dev/null
 160+++ b/keyboards/handwired/onekey/at_start_f415/keyboard.json
 161@@ -0,0 +1,20 @@
 162+{
 163+    "keyboard_name": "Onekey AT-START-F415",
 164+    "processor": "AT32F415",
 165+    "bootloader": "at32-dfu",
 166+    "usb": {
 167+        "shared_endpoint": {
 168+            "keyboard": true
 169+        }
 170+    },
 171+    "matrix_pins": {
 172+        "cols": ["B3"],
 173+        "rows": ["B4"]
 174+    },
 175+    "backlight": {
 176+        "pin": "A0"
 177+    },
 178+    "ws2812": {
 179+        "pin": "B0"
 180+    }
 181+}
 182diff --git a/keyboards/handwired/onekey/at_start_f415/mcuconf.h b/keyboards/handwired/onekey/at_start_f415/mcuconf.h
 183new file mode 100644
 184index 0000000000000000000000000000000000000000..9bc11d507cce9b5d612b66518ecedffbb3ad5c37
 185--- /dev/null
 186+++ b/keyboards/handwired/onekey/at_start_f415/mcuconf.h
 187@@ -0,0 +1,16 @@
 188+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
 189+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
 190+// SPDX-License-Identifier: GPL-2.0-or-later
 191+
 192+#pragma once
 193+
 194+#include_next <mcuconf.h>
 195+
 196+#undef AT32_ADC_USE_ADC1
 197+#define AT32_ADC_USE_ADC1 TRUE
 198+
 199+#undef AT32_I2C_USE_I2C1
 200+#define AT32_I2C_USE_I2C1 TRUE
 201+
 202+#undef AT32_PWM_USE_TMR5
 203+#define AT32_PWM_USE_TMR5 TRUE
 204diff --git a/keyboards/handwired/onekey/at_start_f415/readme.md b/keyboards/handwired/onekey/at_start_f415/readme.md
 205new file mode 100644
 206index 0000000000000000000000000000000000000000..499ef5fb1695ccfb01452e95260fc85f8af8136f
 207--- /dev/null
 208+++ b/keyboards/handwired/onekey/at_start_f415/readme.md
 209@@ -0,0 +1,3 @@
 210+# Artery AT-START-F415 Board Onekey
 211+
 212+To trigger keypress, short together pins *B3* and *B4*.
 213diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py
 214index b6f46180b29840180a698557c50d1d35a7955acb..e055d3fbc95e99a08a8761c53c0c32f8765856d6 100644
 215--- a/lib/python/qmk/constants.py
 216+++ b/lib/python/qmk/constants.py
 217@@ -22,7 +22,7 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware'
 218 MAX_KEYBOARD_SUBFOLDERS = 5
 219 
 220 # Supported processor types
 221diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.c b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.c
 222new file mode 100644
 223index 0000000000000000000000000000000000000000..28cf7c18e0164bda5d5e00ee3369c907ee49cb9c
 224--- /dev/null
 225+++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.c
 226@@ -0,0 +1,101 @@
 227+/*
 228+    ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
 229+    ChibiOS - Copyright (C) 2023..2024 HorrorTroll
 230+    ChibiOS - Copyright (C) 2023..2024 Zhaqian
 231+
 232+    Licensed under the Apache License, Version 2.0 (the "License");
 233+    you may not use this file except in compliance with the License.
 234+    You may obtain a copy of the License at
 235+
 236+        http://www.apache.org/licenses/LICENSE-2.0
 237+
 238+    Unless required by applicable law or agreed to in writing, software
 239+    distributed under the License is distributed on an "AS IS" BASIS,
 240+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 241+    See the License for the specific language governing permissions and
 242+    limitations under the License.
 243+*/
 244+
 245+#include "hal.h"
 246+
 247+/*===========================================================================*/
 248+/* Driver local definitions.                                                 */
 249+/*===========================================================================*/
 250+
 251+/*===========================================================================*/
 252+/* Driver exported variables.                                                */
 253+/*===========================================================================*/
 254+
 255+/*===========================================================================*/
 256+/* Driver local variables and types.                                         */
 257+/*===========================================================================*/
 258+
 259+/**
 260+ * @brief   PAL setup.
 261+ * @details Digital I/O ports static configuration as defined in @p board.h.
 262+ *          This variable is used by the HAL when initializing the PAL driver.
 263+ */
 264+#if HAL_USE_PAL || defined(__DOXYGEN__)
 265+const PALConfig pal_default_config =
 266+{
 267+  {VAL_GPIOAODT, VAL_GPIOACFGLR, VAL_GPIOACFGHR},
 268+  {VAL_GPIOBODT, VAL_GPIOBCFGLR, VAL_GPIOBCFGHR},
 269+#if AT32_HAS_GPIOC
 270+  {VAL_GPIOCODT, VAL_GPIOCCFGLR, VAL_GPIOCCFGHR},
 271+#endif
 272+  {VAL_GPIODODT, VAL_GPIODCFGLR, VAL_GPIODCFGHR},
 273+#if AT32_HAS_GPIOF
 274+  {VAL_GPIOFODT, VAL_GPIOFCFGLR, VAL_GPIOFCFGHR},
 275+#endif
 276+};
 277+#endif
 278+
 279+/*===========================================================================*/
 280+/* Driver local functions.                                                   */
 281+/*===========================================================================*/
 282+
 283+/*===========================================================================*/
 284+/* Driver interrupt handlers.                                                */
 285+/*===========================================================================*/
 286+
 287+/*===========================================================================*/
 288+/* Driver exported functions.                                                */
 289+/*===========================================================================*/
 290+
 291+/**
 292+ * @brief   Early initialization code.
 293+ * @details System clocks are initialized before everything else.
 294+ */
 295+void __early_init(void) {
 296+  at32_clock_init();
 297+}
 298+
 299+#if HAL_USE_SDC || defined(__DOXYGEN__)
 300+/**
 301+ * @brief   SDC card detection.
 302+ */
 303+bool sdc_lld_is_card_inserted(SDCDriver *sdcp) {
 304+  static bool last_status = false;
 305+
 306+  if (blkIsTransferring(sdcp))
 307+    return last_status;
 308+  return last_status = (bool)palReadPad(GPIOC, GPIOC_PIN11);
 309+}
 310+
 311+/**
 312+ * @brief   SDC card write protection detection.
 313+ */
 314+bool sdc_lld_is_write_protected(SDCDriver *sdcp) {
 315+
 316+  (void)sdcp;
 317+  return false;
 318+}
 319+#endif /* HAL_USE_SDC */
 320+
 321+/**
 322+ * @brief   Board-specific initialization code.
 323+ * @note    You can add your board-specific code here.
 324+ */
 325+void boardInit(void) {
 326diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.h b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.h
 327new file mode 100644
 328index 0000000000000000000000000000000000000000..c3ade198bae5f7bc2b4040d0b10ec11736c37bd1
 329--- /dev/null
 330+++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.h
 331@@ -0,0 +1,207 @@
 332+/*
 333+    ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
 334+    ChibiOS - Copyright (C) 2023..2024 HorrorTroll
 335+    ChibiOS - Copyright (C) 2023..2024 Zhaqian
 336+
 337+    Licensed under the Apache License, Version 2.0 (the "License");
 338+    you may not use this file except in compliance with the License.
 339+    You may obtain a copy of the License at
 340+
 341+        http://www.apache.org/licenses/LICENSE-2.0
 342+
 343+    Unless required by applicable law or agreed to in writing, software
 344+    distributed under the License is distributed on an "AS IS" BASIS,
 345+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 346+    See the License for the specific language governing permissions and
 347+    limitations under the License.
 348+*/
 349+
 350+#ifndef _BOARD_H_
 351+#define _BOARD_H_
 352+
 353+/*===========================================================================*/
 354+/* Driver constants.                                                         */
 355+/*===========================================================================*/
 356+
 357+/*
 358+ * Setup for a Generic AT32F415 board.
 359+ */
 360+
 361+/*
 362+ * Board identifier.
 363+ */
 364+#define BOARD_GENERIC_AT32_F415XX
 365+#define BOARD_NAME                  "GENERIC AT32F415 board"
 366+
 367+/*
 368+ * Board oscillators-related settings.
 369+ */
 370+#if !defined(AT32_LEXTCLK)
 371+#define AT32_LEXTCLK                32768
 372+#endif
 373+
 374+#if !defined(AT32_HEXTCLK)
 375+#define AT32_HEXTCLK                8000000
 376+#endif
 377+
 378+/*
 379+ * MCU type, supported types are defined in ./os/hal/platforms/hal_lld.h.
 380+ */
 381+#define AT32F415KB
 382+
 383+/*
 384+ * IO pins assignments.
 385+ */
 386+#define GPIOA_PIN0                  0U
 387+#define GPIOA_PIN1                  1U
 388+#define GPIOA_PIN2                  2U
 389+#define GPIOA_PIN3                  3U
 390+#define GPIOA_PIN4                  4U
 391+#define GPIOA_PIN5                  5U
 392+#define GPIOA_PIN6                  6U
 393+#define GPIOA_PIN7                  7U
 394+#define GPIOA_PIN8                  8U
 395+#define GPIOA_PIN9                  9U
 396+#define GPIOA_PIN10                 10U
 397+#define GPIOA_PIN11                 11U
 398+#define GPIOA_PIN12                 12U
 399+#define GPIOA_SWDIO                 13U
 400+#define GPIOA_SWCLK                 14U
 401+#define GPIOA_PIN15                 15U
 402+
 403+#define GPIOB_PIN0                  0U
 404+#define GPIOB_PIN1                  1U
 405+#define GPIOB_PIN2                  2U
 406+#define GPIOB_PIN3                  3U
 407+#define GPIOB_PIN4                  4U
 408+#define GPIOB_PIN5                  5U
 409+#define GPIOB_PIN6                  6U
 410+#define GPIOB_PIN7                  7U
 411+#define GPIOB_PIN8                  8U
 412+#define GPIOB_PIN9                  9U
 413+#define GPIOB_PIN10                 10U
 414+#define GPIOB_PIN11                 11U
 415+#define GPIOB_PIN12                 12U
 416+#define GPIOB_PIN13                 13U
 417+#define GPIOB_PIN14                 14U
 418+#define GPIOB_PIN15                 15U
 419+
 420+#define GPIOC_PIN0                  0U
 421+#define GPIOC_PIN1                  1U
 422+#define GPIOC_PIN2                  2U
 423+#define GPIOC_PIN3                  3U
 424+#define GPIOC_PIN4                  4U
 425+#define GPIOC_PIN5                  5U
 426+#define GPIOC_PIN6                  6U
 427+#define GPIOC_PIN7                  7U
 428+#define GPIOC_PIN8                  8U
 429+#define GPIOC_PIN9                  9U
 430+#define GPIOC_PIN10                 10U
 431diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.mk b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.mk
 432new file mode 100644
 433index 0000000000000000000000000000000000000000..842e33590594b7c78ccf1ef5205aec6860e4ea94
 434--- /dev/null
 435+++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.mk
 436@@ -0,0 +1,9 @@
 437+# List of all the board related files.
 438+BOARDSRC = $(BOARD_PATH)/board/board.c
 439+
 440+# Required include directories
 441+BOARDINC = $(BOARD_PATH)/board
 442+
 443+# Shared variables
 444+ALLCSRC += $(BOARDSRC)
 445+ALLINC  += $(BOARDINC)
 446diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/config.h b/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/config.h
 447new file mode 100644
 448index 0000000000000000000000000000000000000000..da60447a0af0598685562bd0a4ae805ff8076d10
 449--- /dev/null
 450+++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/config.h
 451@@ -0,0 +1,13 @@
 452+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
 453+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
 454+// SPDX-License-Identifier: GPL-2.0-or-later
 455+
 456+#pragma once
 457+
 458+#define BOARD_OTG_VBUSIG
 459+
 460+#define USB_ENDPOINTS_ARE_REORDERABLE
 461+
 462+#ifndef EARLY_INIT_PERFORM_BOOTLOADER_JUMP
 463+#    define EARLY_INIT_PERFORM_BOOTLOADER_JUMP TRUE
 464+#endif
 465diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/mcuconf.h b/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/mcuconf.h
 466new file mode 100644
 467index 0000000000000000000000000000000000000000..d148379fe1dbdc538607acf79a40ffa6efc0050b
 468--- /dev/null
 469+++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/mcuconf.h
 470@@ -0,0 +1,236 @@
 471+/*
 472+    ChibiOS - Copyright (C) 2006..2020 Giovanni Di Sirio
 473+    ChibiOS - Copyright (C) 2023..2024 HorrorTroll
 474+    ChibiOS - Copyright (C) 2023..2024 Zhaqian
 475+
 476+    Licensed under the Apache License, Version 2.0 (the "License");
 477+    you may not use this file except in compliance with the License.
 478+    You may obtain a copy of the License at
 479+
 480+        http://www.apache.org/licenses/LICENSE-2.0
 481+
 482+    Unless required by applicable law or agreed to in writing, software
 483+    distributed under the License is distributed on an "AS IS" BASIS,
 484+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 485+    See the License for the specific language governing permissions and
 486+    limitations under the License.
 487+*/
 488+
 489+#ifndef MCUCONF_H
 490+#define MCUCONF_H
 491+
 492+/*
 493+ * AT32F415 drivers configuration.
 494+ * The following settings override the default settings present in
 495+ * the various device driver implementation headers.
 496+ * Note that the settings for each driver only have effect if the whole
 497+ * driver is enabled in halconf.h.
 498+ *
 499+ * IRQ priorities:
 500+ * 15...0       Lowest...Highest.
 501+ *
 502+ * DMA priorities:
 503+ * 0...3        Lowest...Highest.
 504+ */
 505+
 506+#define AT32F415_MCUCONF
 507+
 508+/*
 509+ * General settings.
 510+ */
 511+#define AT32_NO_INIT                        FALSE
 512+
 513+/*
 514+ * HAL driver system settings.
 515+ */
 516+#define AT32_HICK_ENABLED                   TRUE
 517+#define AT32_LICK_ENABLED                   FALSE
 518+#define AT32_HEXT_ENABLED                   TRUE
 519+#define AT32_LEXT_ENABLED                   FALSE
 520+#define AT32_SCLKSEL                        AT32_SCLKSEL_PLL
 521+#define AT32_PLLRCS                         AT32_PLLRCS_HEXT
 522+#define AT32_PLLHEXTDIV                     AT32_PLLHEXTDIV_DIV1
 523+#define AT32_PLLCFGEN                       AT32_PLLCFGEN_SOLID
 524+#define AT32_PLLMULT_VALUE                  18
 525+#define AT32_PLL_FR_VALUE                   4
 526+#define AT32_PLL_MS_VALUE                   1
 527+#define AT32_PLL_NS_VALUE                   72
 528+#define AT32_AHBDIV                         AT32_AHBDIV_DIV1
 529+#define AT32_APB1DIV                        AT32_APB1DIV_DIV2
 530+#define AT32_APB2DIV                        AT32_APB2DIV_DIV2
 531+#define AT32_ADCDIV                         AT32_ADCDIV_DIV4
 532+#define AT32_USB_CLOCK_REQUIRED             TRUE
 533+#define AT32_USBDIV                         AT32_USBDIV_DIV3
 534+#define AT32_CLKOUT_SEL                     AT32_CLKOUT_SEL_NOCLOCK
 535+#define AT32_CLKOUTDIV                      AT32_CLKOUTDIV_DIV1
 536+#define AT32_ERTCSEL                        AT32_ERTCSEL_HEXTDIV
 537+#define AT32_PVM_ENABLE                     FALSE
 538+#define AT32_PVMSEL                         AT32_PVMSEL_LEV1
 539+
 540+/*
 541+ * IRQ system settings.
 542+ */
 543+#define AT32_IRQ_EXINT0_PRIORITY            6
 544+#define AT32_IRQ_EXINT1_PRIORITY            6
 545+#define AT32_IRQ_EXINT2_PRIORITY            6
 546+#define AT32_IRQ_EXINT3_PRIORITY            6
 547+#define AT32_IRQ_EXINT4_PRIORITY            6
 548+#define AT32_IRQ_EXINT5_9_PRIORITY          6
 549+#define AT32_IRQ_EXINT10_15_PRIORITY        6
 550+#define AT32_IRQ_EXINT16_PRIORITY           6
 551+#define AT32_IRQ_EXINT17_PRIORITY           15
 552+#define AT32_IRQ_EXINT18_PRIORITY           6
 553+#define AT32_IRQ_EXINT19_PRIORITY           6
 554+#define AT32_IRQ_EXINT20_PRIORITY           6
 555+#define AT32_IRQ_EXINT21_PRIORITY           15
 556+#define AT32_IRQ_EXINT22_PRIORITY           15
 557+
 558+#define AT32_IRQ_TMR1_BRK_TMR9_PRIORITY     7
 559+#define AT32_IRQ_TMR1_OVF_TMR10_PRIORITY    7
 560+#define AT32_IRQ_TMR1_HALL_TMR11_PRIORITY   7
 561+#define AT32_IRQ_TMR1_CH_PRIORITY           7
 562+#define AT32_IRQ_TMR2_PRIORITY              7
 563+#define AT32_IRQ_TMR3_PRIORITY              7
 564+#define AT32_IRQ_TMR4_PRIORITY              7
 565+#define AT32_IRQ_TMR5_PRIORITY              7
 566+
 567+#define AT32_IRQ_USART1_PRIORITY            12
 568+#define AT32_IRQ_USART2_PRIORITY            12
 569+#define AT32_IRQ_USART3_PRIORITY            12
 570diff --git a/platforms/chibios/bootloader.mk b/platforms/chibios/bootloader.mk
 571index 5b6edd73ad343edc81522485b9e2aaf0ecffce82..e292e1e0b08b50ae66422992291990bf6b30fec2 100644
 572--- a/platforms/chibios/bootloader.mk
 573+++ b/platforms/chibios/bootloader.mk
 574@@ -26,6 +26,7 @@
 575 #     stm32-dfu    STM32 USB DFU in ROM
 576 #     apm32-dfu    APM32 USB DFU in ROM
 577 #     wb32-dfu     WB32 USB DFU in ROM
 578+#     at32-dfu     AT32 USB DFU in ROM
 579 #     tinyuf2      TinyUF2
 580 #     rp2040       Raspberry Pi RP2040
 581 # Current options for RISC-V:
 582@@ -119,6 +120,14 @@ ifeq ($(strip $(BOOTLOADER)), wb32-dfu)
 583     OPT_DEFS += -DBOOTLOADER_WB32_DFU
 584     BOOTLOADER_TYPE = wb32_dfu
 585 endif
 586+ifeq ($(strip $(BOOTLOADER)), at32-dfu)
 587+    OPT_DEFS += -DBOOTLOADER_AT32_DFU
 588+    BOOTLOADER_TYPE = at32_dfu
 589+
 590+    # Options to pass to dfu-util when flashing
 591+    DFU_ARGS ?= -d 2E3C:DF11 -a 0 -s 0x08000000:leave
 592+    DFU_SUFFIX_ARGS ?= -v 2E3C -p DF11
 593+endif
 594 
 595 ifeq ($(strip $(BOOTLOADER_TYPE)),)
 596     ifneq ($(strip $(BOOTLOADER)),)
 597diff --git a/platforms/chibios/bootloaders/at32_dfu.c b/platforms/chibios/bootloaders/at32_dfu.c
 598new file mode 100644
 599index 0000000000000000000000000000000000000000..f3a453d0fce89d489a762121d46df3a37d70ddd5
 600--- /dev/null
 601+++ b/platforms/chibios/bootloaders/at32_dfu.c
 602@@ -0,0 +1,83 @@
 603+// Copyright 2021-2023 QMK
 604+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
 605+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
 606+// SPDX-License-Identifier: GPL-2.0-or-later
 607+
 608+#include "bootloader.h"
 609+#include "util.h"
 610+
 611+#include <ch.h>
 612+#include <hal.h>
 613+#include "wait.h"
 614+
 615+#ifndef AT32_BOOTLOADER_RAM_SYMBOL
 616+#    define AT32_BOOTLOADER_RAM_SYMBOL __ram0_end__
 617+#endif
 618+
 619+extern uint32_t AT32_BOOTLOADER_RAM_SYMBOL;
 620+
 621+/* This code should be checked whether it runs correctly on platforms */
 622+#define SYMVAL(sym) (uint32_t)(((uint8_t *)&(sym)) - ((uint8_t *)0))
 623+#define BOOTLOADER_MAGIC 0xDEADBEEF
 624+#define MAGIC_ADDR (unsigned long *)(SYMVAL(AT32_BOOTLOADER_RAM_SYMBOL) - 4)
 625+
 626+__attribute__((weak)) void bootloader_marker_enable(void) {
 627+    uint32_t *marker = (uint32_t *)MAGIC_ADDR;
 628+    *marker          = BOOTLOADER_MAGIC; // set magic flag => reset handler will jump into boot loader
 629+}
 630+
 631+__attribute__((weak)) bool bootloader_marker_active(void) {
 632+    const uint32_t *marker = (const uint32_t *)MAGIC_ADDR;
 633+    return (*marker == BOOTLOADER_MAGIC) ? true : false;
 634+}
 635+
 636+__attribute__((weak)) void bootloader_marker_disable(void) {
 637+    uint32_t *marker = (uint32_t *)MAGIC_ADDR;
 638+    *marker          = 0;
 639+}
 640+
 641+__attribute__((weak)) void bootloader_jump(void) {
 642+    bootloader_marker_enable();
 643+    NVIC_SystemReset();
 644+}
 645+
 646+__attribute__((weak)) void mcu_reset(void) {
 647+    NVIC_SystemReset();
 648+}
 649+
 650+void enter_bootloader_mode_if_requested(void) {
 651+    if (bootloader_marker_active()) {
 652+        bootloader_marker_disable();
 653+
 654+        struct system_memory_vector_t {
 655+            uint32_t stack_top;
 656+            void (*entrypoint)(void);
 657+        };
 658+        const struct system_memory_vector_t *bootloader = (const struct system_memory_vector_t *)(AT32_BOOTLOADER_ADDRESS);
 659+
 660+        __disable_irq();
 661+
 662+#if defined(__MPU_PRESENT) && (__MPU_PRESENT == 1U)
 663+        ARM_MPU_Disable();
 664+#endif
 665+
 666+        SysTick->CTRL = 0;
 667+        SysTick->VAL  = 0;
 668+        SysTick->LOAD = 0;
 669+
 670+        // Clear interrupt enable and interrupt pending registers
 671+        for (int i = 0; i < ARRAY_SIZE(NVIC->ICER); i++) {
 672+            NVIC->ICER[i] = 0xFFFFFFFF;
 673+            NVIC->ICPR[i] = 0xFFFFFFFF;
 674+        }
 675+
 676+        __set_CONTROL(0);
 677+        __set_MSP(bootloader->stack_top);
 678+        __enable_irq();
 679+
 680+        // Jump to bootloader
 681+        bootloader->entrypoint();
 682+        while (true) {
 683+        }
 684+    }
 685+}
 686diff --git a/platforms/chibios/chibios_config.h b/platforms/chibios/chibios_config.h
 687index 8f46fe0736080798e18e5186f06a759b8db56f71..95136baf30324dd2e9c697d1f39446434a1335e5 100644
 688--- a/platforms/chibios/chibios_config.h
 689+++ b/platforms/chibios/chibios_config.h
 690@@ -142,6 +142,19 @@
 691 #    endif
 692 #endif
 693 
 694+// AT32 compatibility
 695+#if defined(MCU_AT32)
 696+#    define CPU_CLOCK AT32_SYSCLK
 697+
 698+#    if defined(AT32F415)
 699+#        define USE_GPIOV1
 700+#        define USE_I2CV1
 701+#        define PAL_MODE_ALTERNATE_OPENDRAIN PAL_MODE_AT32_ALTERNATE_OPENDRAIN
 702+#        define PAL_MODE_ALTERNATE_PUSHPULL PAL_MODE_AT32_ALTERNATE_PUSHPULL
 703+#        define AUDIO_PWM_PAL_MODE PAL_MODE_ALTERNATE_PUSHPULL
 704+#    endif
 705+#endif
 706+
 707 #if defined(GD32VF103)
 708 /* This chip has the same API as STM32F103, but uses different names for literally the same thing.
 709  * As of 4.7.2021 QMK is tailored to use STM32 defines/names, for compatibility sake
 710diff --git a/platforms/chibios/drivers/analog.c b/platforms/chibios/drivers/analog.c
 711index fb146df9367a9e609d5e7c0c733d8ccbbbf4c0a3..7e1f87e6c95e3937bb6a5b66e7bd750a9bebb217 100644
 712--- a/platforms/chibios/drivers/analog.c
 713+++ b/platforms/chibios/drivers/analog.c
 714@@ -22,7 +22,7 @@
 715 #    error "You need to set HAL_USE_ADC to TRUE in your halconf.h to use the ADC."
 716 #endif
 717 
 718-#if !RP_ADC_USE_ADC1 && !STM32_ADC_USE_ADC1 && !STM32_ADC_USE_ADC2 && !STM32_ADC_USE_ADC3 && !STM32_ADC_USE_ADC4 && !WB32_ADC_USE_ADC1
 719+#if !RP_ADC_USE_ADC1 && !STM32_ADC_USE_ADC1 && !STM32_ADC_USE_ADC2 && !STM32_ADC_USE_ADC3 && !STM32_ADC_USE_ADC4 && !WB32_ADC_USE_ADC1 && !AT32_ADC_USE_ADC1
 720 #    error "You need to set one of the 'xxx_ADC_USE_ADCx' settings to TRUE in your mcuconf.h to use the ADC."
 721 #endif
 722 
 723@@ -45,7 +45,7 @@
 724 // Otherwise assume V3
 725 #if defined(STM32F0XX) || defined(STM32L0XX)
 726 #    define USE_ADCV1
 727-#elif defined(STM32F1XX) || defined(STM32F2XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx)
 728+#elif defined(STM32F1XX) || defined(STM32F2XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415)
 729 #    define USE_ADCV2
 730 #endif
 731 
 732@@ -82,7 +82,7 @@
 733 
 734 /* User configurable ADC options */
 735 #ifndef ADC_COUNT
 736-#    if defined(RP2040) || defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx)
 737+#    if defined(RP2040) || defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415)
 738 #        define ADC_COUNT 1
 739 #    elif defined(STM32F3XX) || defined(STM32G4XX)
 740 #        define ADC_COUNT 4
 741@@ -142,11 +142,16 @@ static ADCConversionGroup adcConversionGroup = {
 742     .cfgr1 = ADC_CFGR1_CONT | ADC_RESOLUTION,
 743     .smpr  = ADC_SAMPLING_RATE,
 744 #elif defined(USE_ADCV2)
 745-#    if !defined(STM32F1XX) && !defined(GD32VF103) && !defined(WB32F3G71xx) && !defined(WB32FQ95xx)
 746-    .cr2   = ADC_CR2_SWSTART, // F103 seem very unhappy with, F401 seems very unhappy without...
 747+#    if !defined(STM32F1XX) && !defined(GD32VF103) && !defined(WB32F3G71xx) && !defined(WB32FQ95xx) && !defined(AT32F415)
 748+    .cr2  = ADC_CR2_SWSTART, // F103 seem very unhappy with, F401 seems very unhappy without...
 749 #    endif
 750+#    if defined(AT32F415)
 751diff --git a/platforms/chibios/drivers/serial_usart.c b/platforms/chibios/drivers/serial_usart.c
 752index 767ef8726fc3668a9a5eb9f95454f2a5a51d17ea..becf3afbce1ea7412dc413e8aca02622e2aaceef 100644
 753--- a/platforms/chibios/drivers/serial_usart.c
 754+++ b/platforms/chibios/drivers/serial_usart.c
 755@@ -9,6 +9,17 @@
 756 
 757 #if defined(SERIAL_USART_CONFIG)
 758 static QMKSerialConfig serial_config = SERIAL_USART_CONFIG;
 759+#elif defined(MCU_AT32) /* AT32 MCUs */
 760+static QMKSerialConfig serial_config = {
 761+    .speed = (SERIAL_USART_SPEED),
 762+    .ctrl1 = (SERIAL_USART_CTRL1),
 763+    .ctrl2 = (SERIAL_USART_CTRL2),
 764+#    if !defined(SERIAL_USART_FULL_DUPLEX)
 765+    .ctrl3 = ((SERIAL_USART_CTRL3) | USART_CTRL3_SLBEN) /* activate half-duplex mode */
 766+#    else
 767+    .ctrl3 = (SERIAL_USART_CTRL3)
 768+#    endif
 769+};
 770 #elif defined(MCU_STM32) /* STM32 MCUs */
 771 static QMKSerialConfig serial_config = {
 772 #    if HAL_USE_SERIAL
 773@@ -160,7 +171,7 @@ inline bool serial_transport_receive_blocking(uint8_t* destination, const size_t
 774  * @brief Initiate pins for USART peripheral. Half-duplex configuration.
 775  */
 776 __attribute__((weak)) void usart_init(void) {
 777-#    if defined(MCU_STM32) /* STM32 MCUs */
 778+#    if defined(MCU_STM32) || defined(MCU_AT32) /* STM32 and AT32 MCUs */
 779 #        if defined(USE_GPIOV1)
 780     palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE_OPENDRAIN);
 781 #        else
 782@@ -183,7 +194,7 @@ __attribute__((weak)) void usart_init(void) {
 783  * @brief Initiate pins for USART peripheral. Full-duplex configuration.
 784  */
 785 __attribute__((weak)) void usart_init(void) {
 786-#    if defined(MCU_STM32) /* STM32 MCUs */
 787+#    if defined(MCU_STM32) || defined(MCU_AT32) /* STM32 and AT32 MCUs */
 788 #        if defined(USE_GPIOV1)
 789     palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE_PUSHPULL);
 790     palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_INPUT);
 791diff --git a/platforms/chibios/drivers/serial_usart.h b/platforms/chibios/drivers/serial_usart.h
 792index dec8a292e98b707e8938fb8dcd77381a4df7c5c1..dbd7673273f0cff7902d1bbaae3dc4c7a586bedc 100644
 793--- a/platforms/chibios/drivers/serial_usart.h
 794+++ b/platforms/chibios/drivers/serial_usart.h
 795@@ -74,40 +74,75 @@ typedef SIOConfig QMKSerialConfig;
 796 #    endif
 797 #endif
 798 
 799-#if !defined(USART_CR1_M0)
 800-#    define USART_CR1_M0 USART_CR1_M // some platforms (f1xx) dont have this so
 801-#endif
 802+#if defined(MCU_STM32) /* STM32 MCUs */
 803+#    if !defined(USART_CR1_M0)
 804+#        define USART_CR1_M0 USART_CR1_M // some platforms (f1xx) dont have this so
 805+#    endif
 806 
 807-#if !defined(SERIAL_USART_CR1)
 808-#    define SERIAL_USART_CR1 (USART_CR1_PCE | USART_CR1_PS | USART_CR1_M0) // parity enable, odd parity, 9 bit length
 809-#endif
 810+#    if !defined(SERIAL_USART_CR1)
 811+#        define SERIAL_USART_CR1 (USART_CR1_PCE | USART_CR1_PS | USART_CR1_M0) // parity enable, odd parity, 9 bit length
 812+#    endif
 813 
 814-#if !defined(SERIAL_USART_CR2)
 815-#    define SERIAL_USART_CR2 (USART_CR2_STOP_1) // 2 stop bits
 816-#endif
 817+#    if !defined(SERIAL_USART_CR2)
 818+#        define SERIAL_USART_CR2 (USART_CR2_STOP_1) // 2 stop bits
 819+#    endif
 820 
 821-#if !defined(SERIAL_USART_CR3)
 822-#    define SERIAL_USART_CR3 0
 823-#endif
 824+#    if !defined(SERIAL_USART_CR3)
 825+#        define SERIAL_USART_CR3 0
 826+#    endif
 827+
 828+#    if defined(USART1_REMAP)
 829+#        define USART_REMAP                             \
 830+            do {                                        \
 831+                (AFIO->MAPR |= AFIO_MAPR_USART1_REMAP); \
 832+            } while (0)
 833+#    elif defined(USART2_REMAP)
 834+#        define USART_REMAP                             \
 835+            do {                                        \
 836+                (AFIO->MAPR |= AFIO_MAPR_USART2_REMAP); \
 837+            } while (0)
 838+#    elif defined(USART3_PARTIALREMAP)
 839+#        define USART_REMAP                                          \
 840+            do {                                                     \
 841+                (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_PARTIALREMAP); \
 842+            } while (0)
 843+#    elif defined(USART3_FULLREMAP)
 844+#        define USART_REMAP                                       \
 845+            do {                                                  \
 846+                (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_FULLREMAP); \
 847+            } while (0)
 848+#    endif
 849+#elif defined(MCU_AT32) /* AT32 MCUs */
 850+#    if !defined(USART_CTRL1_DBN0)
 851+#        define USART_CTRL1_DBN0 USART_CTRL1_DBN
 852+#    endif
 853 
 854-#if defined(USART1_REMAP)
 855-#    define USART_REMAP                             \
 856-        do {                                        \
 857-            (AFIO->MAPR |= AFIO_MAPR_USART1_REMAP); \
 858-        } while (0)
 859-#elif defined(USART2_REMAP)
 860-#    define USART_REMAP                             \
 861-        do {                                        \
 862-            (AFIO->MAPR |= AFIO_MAPR_USART2_REMAP); \
 863-        } while (0)
 864-#elif defined(USART3_PARTIALREMAP)
 865-#    define USART_REMAP                                          \
 866-        do {                                                     \
 867-            (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_PARTIALREMAP); \
 868-        } while (0)
 869-#elif defined(USART3_FULLREMAP)
 870-#    define USART_REMAP                                       \
 871-        do {                                                  \
 872-            (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_FULLREMAP); \
 873-        } while (0)
 874+#    if !defined(SERIAL_USART_CTRL1)
 875+#        define SERIAL_USART_CTRL1 (USART_CTRL1_PEN | USART_CTRL1_PSEL | USART_CTRL1_DBN0) // parity enable, odd parity, 9 bit length
 876+#    endif
 877+
 878+#    if !defined(SERIAL_USART_CTRL2)
 879+#        define SERIAL_USART_CTRL2 (USART_CTRL2_STOPBN_1) // 2 stop bits
 880+#    endif
 881+
 882+#    if !defined(SERIAL_USART_CTRL3)
 883+#        define SERIAL_USART_CTRL3 0
 884+#    endif
 885+
 886+#    if defined(USART1_REMAP)
 887+#        define USART_REMAP                               \
 888+            do {                                          \
 889+                (IOMUX->REMAP |= IOMUX_REMAP_USART1_MUX); \
 890+            } while (0)
 891+#    elif defined(USART3_PARTIALREMAP)
 892+#        define USART_REMAP                                    \
 893+            do {                                               \
 894+                (IOMUX->REMAP |= IOMUX_REMAP_USART3_MUX_MUX1); \
 895diff --git a/platforms/chibios/drivers/spi_master.c b/platforms/chibios/drivers/spi_master.c
 896index cbe765e233d767decd22239bd0fb5d176e108dac..6417b7077f540a246363ad36b4148ea137eed794 100644
 897--- a/platforms/chibios/drivers/spi_master.c
 898+++ b/platforms/chibios/drivers/spi_master.c
 899@@ -103,9 +103,15 @@ bool spi_start_extended(spi_start_config_t *start_config) {
 900         roundedDivisor <<= 1;
 901     }
 902 
 903+#    if defined(AT32F415)
 904+    if (roundedDivisor < 2 || roundedDivisor > 1024) {
 905+        return false;
 906+    }
 907+#    else
 908     if (roundedDivisor < 2 || roundedDivisor > 256) {
 909         return false;
 910     }
 911+#    endif
 912 #endif
 913 
 914 #if defined(K20x) || defined(KL2x)
 915@@ -240,6 +246,59 @@ bool spi_start_extended(spi_start_config_t *start_config) {
 916             spiConfig.SSPCR0 |= SPI_SSPCR0_SPH; // Clock phase: sample on second edge transition
 917             break;
 918     }
 919+#elif defined(AT32F415)
 920+    spiConfig.ctrl1 = 0;
 921+
 922+    if (lsbFirst) {
 923+        spiConfig.ctrl1 |= SPI_CTRL1_LTF;
 924+    }
 925+
 926+    switch (mode) {
 927+        case 0:
 928+            break;
 929+        case 1:
 930+            spiConfig.ctrl1 |= SPI_CTRL1_CLKPHA;
 931+            break;
 932+        case 2:
 933+            spiConfig.ctrl1 |= SPI_CTRL1_CLKPOL;
 934+            break;
 935+        case 3:
 936+            spiConfig.ctrl1 |= SPI_CTRL1_CLKPHA | SPI_CTRL1_CLKPOL;
 937+            break;
 938+    }
 939+
 940+    switch (roundedDivisor) {
 941+        case 2:
 942+            break;
 943+        case 4:
 944+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_0;
 945+            break;
 946+        case 8:
 947+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_1;
 948+            break;
 949+        case 16:
 950+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0;
 951+            break;
 952+        case 32:
 953+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2;
 954+            break;
 955+        case 64:
 956+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_0;
 957+            break;
 958+        case 128:
 959+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1;
 960+            break;
 961+        case 256:
 962+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0;
 963+            break;
 964+        case 512:
 965+            spiConfig.ctrl2 |= SPI_CTRL1_MDIV_3;
 966+            break;
 967+        case 1024:
 968+            spiConfig.ctrl2 |= SPI_CTRL1_MDIV_3;
 969+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_0;
 970+            break;
 971+    }
 972 #else
 973     spiConfig.cr1 = 0;
 974 
 975diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
 976index f49c4a45b0bf9a45e09fd8644d2964f939563eca..fed16d20b1aa9e58f27bf0a578e3fcc0fef5f519 100644
 977--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
 978+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
 979@@ -33,7 +33,7 @@ static inline uint32_t detect_flash_size(void) {
 980 #elif defined(FLASH_SIZE)
 981     return FLASH_SIZE;
 982 #elif defined(FLASHSIZE_BASE)
 983diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
 984index 0f0fa694e9c5078f46d48ffe8ff6c50457626dbb..f09f824bd8bd796edcccfd9583cc25f3d59b7c10 100644
 985--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
 986+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
 987@@ -16,6 +16,8 @@
 988 #        define BACKING_STORE_WRITE_SIZE 4 // from hal_efl_lld.c
 989 #    elif defined(QMK_MCU_FAMILY_WB32)
 990 #        define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
 991+#    elif defined(QMK_MCU_FAMILY_AT32)
 992+#        define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
 993 #    elif defined(QMK_MCU_FAMILY_STM32)
 994 #        if defined(STM32_FLASH_LINE_SIZE) // from some family's stm32_registry.h file
 995 #            define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE)
 996diff --git a/platforms/chibios/drivers/ws2812_bitbang.c b/platforms/chibios/drivers/ws2812_bitbang.c
 997index fce1963d0ae1670849537945a1d684172aaa36e5..a88c5ff619d5a8ca57f8717c9d74d2cd5b9ecbd7 100644
 998--- a/platforms/chibios/drivers/ws2812_bitbang.c
 999+++ b/platforms/chibios/drivers/ws2812_bitbang.c
1000@@ -11,7 +11,7 @@
1001 /* Adapted from https://github.com/bigjosh/SimpleNeoPixelDemo/ */
1002 
1003 #ifndef WS2812_BITBANG_NOP_FUDGE
1004-#    if defined(STM32F0XX) || defined(STM32F1XX) || defined(GD32VF103) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(WB32F3G71xx) || defined(WB32FQ95xx)
1005+#    if defined(STM32F0XX) || defined(STM32F1XX) || defined(GD32VF103) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415)
1006 #        define WS2812_BITBANG_NOP_FUDGE 0.4
1007 #    else
1008 #        if defined(RP2040)
1009diff --git a/platforms/chibios/drivers/ws2812_pwm.c b/platforms/chibios/drivers/ws2812_pwm.c
1010index c46e9171ab87cce67494f2e0910fd71e0bbd0347..50927b849a41b8af47297473644ffcd8f3037933 100644
1011--- a/platforms/chibios/drivers/ws2812_pwm.c
1012+++ b/platforms/chibios/drivers/ws2812_pwm.c
1013@@ -40,6 +40,9 @@
1014 #if (STM32_DMA_SUPPORTS_DMAMUX == TRUE) && !defined(WS2812_PWM_DMAMUX_ID)
1015 #    error "please consult your MCU's datasheet and specify in your config.h: #define WS2812_PWM_DMAMUX_ID STM32_DMAMUX1_TIM?_UP"
1016 #endif
1017+#if (AT32_DMA_SUPPORTS_DMAMUX == TRUE) && !defined(WS2812_PWM_DMAMUX_CHANNEL) && !defined(WS2812_PWM_DMAMUX_ID)
1018+#    error "please consult your MCU's datasheet and specify in your config.h: #define WS2812_PWM_DMAMUX_CHANNEL 1, #define WS2812_PWM_DMAMUX_ID AT32_DMAMUX_TMR?_OVERFLOW"
1019+#endif
1020 
1021 /* Summarize https://www.st.com/resource/en/application_note/an4013-stm32-crossseries-timer-overview-stmicroelectronics.pdf to
1022  * figure out if we are using a 32bit timer. This is needed to setup the DMA controller correctly.
1023@@ -269,6 +272,14 @@ typedef uint32_t ws2812_buffer_t;
1024 #        define WS2812_PWM_DMA_PERIPHERAL_WIDTH STM32_DMA_CR_PSIZE_HWORD
1025 typedef uint16_t ws2812_buffer_t;
1026 #    endif
1027+#elif defined(AT32F415)
1028+#    define WS2812_PWM_DMA_MEMORY_WIDTH AT32_DMA_CCTRL_MWIDTH_BYTE
1029+#    if defined(WS2812_PWM_TIMER_32BIT)
1030+#        define WS2812_PWM_DMA_PERIPHERAL_WIDTH AT32_DMA_CCTRL_PWIDTH_WORD
1031+#    else
1032+#        define WS2812_PWM_DMA_PERIPHERAL_WIDTH AT32_DMA_CCTRL_PWIDTH_HWORD
1033+#    endif
1034+typedef uint8_t ws2812_buffer_t;
1035 #else
1036 #    define WS2812_PWM_DMA_MEMORY_WIDTH STM32_DMA_CR_MSIZE_BYTE
1037 #    if defined(WS2812_PWM_TIMER_32BIT)
1038@@ -309,8 +320,13 @@ void ws2812_init(void) {
1039                 [0 ... 3]                = {.mode = PWM_OUTPUT_DISABLED, .callback = NULL},    // Channels default to disabled
1040                 [WS2812_PWM_CHANNEL - 1] = {.mode = WS2812_PWM_OUTPUT_MODE, .callback = NULL}, // Turn on the channel we care about
1041             },
1042+#if defined(AT32F415)
1043+        .ctrl2 = 0,
1044+        .iden  = AT32_TMR_IDEN_OVFDEN, // DMA on update event for next period
1045+#else
1046         .cr2  = 0,
1047         .dier = TIM_DIER_UDE, // DMA on update event for next period
1048+#endif
1049     };
1050     //#pragma GCC diagnostic pop  // Restore command-line warning options
1051 
1052@@ -321,6 +337,11 @@ void ws2812_init(void) {
1053     dmaStreamSetSource(WS2812_PWM_DMA_STREAM, ws2812_frame_buffer);
1054     dmaStreamSetDestination(WS2812_PWM_DMA_STREAM, &(WS2812_PWM_DRIVER.tim->CCR[WS2812_PWM_CHANNEL - 1])); // Ziel ist der An-Zeit im Cap-Comp-Register
1055diff --git a/platforms/chibios/drivers/ws2812_spi.c b/platforms/chibios/drivers/ws2812_spi.c
1056index a1357edec57b3fae180bb77abd9510b950e51eb6..d1792b871bf0166330e1e6b41bb0d6863af98886 100644
1057--- a/platforms/chibios/drivers/ws2812_spi.c
1058+++ b/platforms/chibios/drivers/ws2812_spi.c
1059@@ -40,26 +40,53 @@
1060 
1061 // Define SPI config speed
1062 // baudrate should target 3.2MHz
1063+#if defined(AT32F415)
1064+#    if WS2812_SPI_DIVISOR == 2
1065+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (0)
1066+#    elif WS2812_SPI_DIVISOR == 4
1067+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0)
1068+#    elif WS2812_SPI_DIVISOR == 8
1069+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1)
1070+#    elif WS2812_SPI_DIVISOR == 16 // default
1071+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0)
1072+#    elif WS2812_SPI_DIVISOR == 32
1073+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2)
1074+#    elif WS2812_SPI_DIVISOR == 64
1075+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_0)
1076+#    elif WS2812_SPI_DIVISOR == 128
1077+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1)
1078+#    elif WS2812_SPI_DIVISOR == 256
1079+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0)
1080+#    elif WS2812_SPI_DIVISOR == 512
1081+#        define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3)
1082+#    elif WS2812_SPI_DIVISOR == 1024
1083+#        define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3)
1084+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0)
1085+#    else
1086+#        error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
1087+#    endif
1088+#else
1089 // F072 fpclk = 48MHz
1090 // 48/16 = 3Mhz
1091-#if WS2812_SPI_DIVISOR == 2
1092-#    define WS2812_SPI_DIVISOR_CR1_BR_X (0)
1093-#elif WS2812_SPI_DIVISOR == 4
1094-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_0)
1095-#elif WS2812_SPI_DIVISOR == 8
1096-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1)
1097-#elif WS2812_SPI_DIVISOR == 16 // default
1098-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1 | SPI_CR1_BR_0)
1099-#elif WS2812_SPI_DIVISOR == 32
1100-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2)
1101-#elif WS2812_SPI_DIVISOR == 64
1102-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_0)
1103-#elif WS2812_SPI_DIVISOR == 128
1104-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1)
1105-#elif WS2812_SPI_DIVISOR == 256
1106-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
1107-#else
1108-#    error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
1109+#    if WS2812_SPI_DIVISOR == 2
1110+#        define WS2812_SPI_DIVISOR_CR1_BR_X (0)
1111+#    elif WS2812_SPI_DIVISOR == 4
1112+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_0)
1113+#    elif WS2812_SPI_DIVISOR == 8
1114+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1)
1115+#    elif WS2812_SPI_DIVISOR == 16 // default
1116+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1 | SPI_CR1_BR_0)
1117+#    elif WS2812_SPI_DIVISOR == 32
1118+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2)
1119+#    elif WS2812_SPI_DIVISOR == 64
1120+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_0)
1121+#    elif WS2812_SPI_DIVISOR == 128
1122+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1)
1123+#    elif WS2812_SPI_DIVISOR == 256
1124+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
1125+#    else
1126+#        error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
1127+#    endif
1128 #endif
1129 
1130 // Use SPI circular buffer
1131@@ -176,8 +203,16 @@ void ws2812_init(void) {
1132         NULL, // error_cb
1133         PAL_PORT(WS2812_DI_PIN),
1134         PAL_PAD(WS2812_DI_PIN),
1135+#    if defined(AT32F415)
1136+        WS2812_SPI_DIVISOR_CTRL1_MDIV_X,
1137+#        if (WS2812_SPI_DIVISOR == 512 || WS2812_SPI_DIVISOR == 1024)
1138+        WS2812_SPI_DIVISOR_CTRL2_MDIV_X,
1139+#        endif
1140+        0
1141+#    else
1142         WS2812_SPI_DIVISOR_CR1_BR_X,
1143         0
1144+#    endif
1145 #endif
1146     };
1147 
1148diff --git a/platforms/chibios/flash.mk b/platforms/chibios/flash.mk
1149index 525f177f9ebb943729641456374719c83131d1c2..f4db17a58bf9d7a23ac9f12875b61d9a456840dc 100644
1150--- a/platforms/chibios/flash.mk
1151+++ b/platforms/chibios/flash.mk
1152@@ -113,6 +113,8 @@ else ifeq ($(strip $(MCU_FAMILY)),STM32)
1153 	$(UNSYNC_OUTPUT_CMD) && $(call EXEC_DFU_UTIL)
1154 else ifeq ($(strip $(MCU_FAMILY)),WB32)
1155 	$(UNSYNC_OUTPUT_CMD) && $(call EXEC_WB32_DFU_UPDATER)
1156+else ifeq ($(strip $(MCU_FAMILY)),AT32)
1157+	$(UNSYNC_OUTPUT_CMD) && $(call EXEC_DFU_UTIL)
1158 else ifeq ($(strip $(MCU_FAMILY)),GD32V)
1159 	$(UNSYNC_OUTPUT_CMD) && $(call EXEC_DFU_UTIL)
1160 else
1161diff --git a/platforms/chibios/mcu_selection.mk b/platforms/chibios/mcu_selection.mk
1162index a1597fa1e91236b05b1892437427d6c09944d073..086a2b31c60b66ad17c22cf0ccfb6ff6ef8ee4ae 100644
1163--- a/platforms/chibios/mcu_selection.mk
1164+++ b/platforms/chibios/mcu_selection.mk
1165@@ -809,6 +809,40 @@ ifneq ($(findstring WB32FQ95, $(MCU)),)
1166   WB32_BOOTLOADER_ADDRESS ?= 0x1FFFE000
1167 endif
1168 
1169+ifneq ($(findstring AT32F415, $(MCU)),)
1170+  # Cortex version
1171+  MCU = cortex-m4
1172+
1173+  # ARM version, CORTEX-M0/M1 are 6, CORTEX-M3/M4/M7 are 7
1174+  ARMV = 7
1175+
1176+  ## chip/board settings
1177+  # - the next two should match the directories in
1178+  #   <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
1179+  #   OR
1180+  #   <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
1181+  MCU_FAMILY = AT32
1182+  MCU_SERIES = AT32F415
1183+
1184+  # Linker script to use
1185+  # - it should exist either in <chibios>/os/common/startup/ARMCMx/compilers/GCC/ld/
1186+  #   or <keyboard_dir>/ld/
1187+  MCU_LDSCRIPT ?= AT32F415xB
1188+
1189+  # Startup code to use
1190+  #  - it should exist in <chibios>/os/common/startup/ARMCMx/compilers/GCC/mk/
1191+  MCU_STARTUP ?= at32f415
1192+
1193+  # Board: it should exist either in <chibios>/os/hal/boards/,
1194+  # <keyboard_dir>/boards/, or drivers/boards/
1195+  BOARD ?= GENERIC_AT32_F415XX
1196+
1197+  USE_FPU ?= no
1198+
1199+  # Bootloader address for AT32 DFU
1200+  AT32_BOOTLOADER_ADDRESS ?= 0x1FFFAC00
1201+endif
1202+
1203 ifneq ($(findstring GD32VF103, $(MCU)),)
1204   # RISC-V
1205   MCU = risc-v
1206diff --git a/platforms/chibios/platform.mk b/platforms/chibios/platform.mk
1207index 169707966ff84f305a8dffb65f34dccb5a08d4ed..cf9fac251e459377ed7bbef15c6b35dfffbf67e9 100644
1208--- a/platforms/chibios/platform.mk
1209+++ b/platforms/chibios/platform.mk
1210@@ -155,6 +155,10 @@ ifdef WB32_BOOTLOADER_ADDRESS
1211     OPT_DEFS += -DWB32_BOOTLOADER_ADDRESS=$(WB32_BOOTLOADER_ADDRESS)
1212 endif
1213 
1214+ifdef AT32_BOOTLOADER_ADDRESS
1215+    OPT_DEFS += -DAT32_BOOTLOADER_ADDRESS=$(AT32_BOOTLOADER_ADDRESS)
1216+endif
1217+
1218 # Work out if we need to set up the include for the bootloader definitions
1219 ifneq ("$(wildcard $(KEYBOARD_PATH_5)/bootloader_defs.h)","")
1220     OPT_DEFS += -include $(KEYBOARD_PATH_5)/bootloader_defs.h