Browse Source

Added MCU support for ArteryTek AT32F415 (#23445)

HorrorTroll 1 year ago
parent
commit
39161b9ee7
32 changed files with 1065 additions and 63 deletions
  1. 1 1
      builddefs/common_features.mk
  2. 2 0
      data/schemas/keyboard.jsonschema
  3. 4 0
      docs/compatible_microcontrollers.md
  4. 1 0
      docs/driver_installation_zadig.md
  5. 33 0
      docs/flashing.md
  6. 10 0
      keyboards/handwired/onekey/at_start_f415/board.h
  7. 10 0
      keyboards/handwired/onekey/at_start_f415/config.h
  8. 13 0
      keyboards/handwired/onekey/at_start_f415/halconf.h
  9. 20 0
      keyboards/handwired/onekey/at_start_f415/keyboard.json
  10. 16 0
      keyboards/handwired/onekey/at_start_f415/mcuconf.h
  11. 3 0
      keyboards/handwired/onekey/at_start_f415/readme.md
  12. 3 1
      lib/python/qmk/constants.py
  13. 101 0
      platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.c
  14. 207 0
      platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.h
  15. 9 0
      platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.mk
  16. 13 0
      platforms/chibios/boards/GENERIC_AT32_F415XX/configs/config.h
  17. 236 0
      platforms/chibios/boards/GENERIC_AT32_F415XX/configs/mcuconf.h
  18. 9 0
      platforms/chibios/bootloader.mk
  19. 83 0
      platforms/chibios/bootloaders/at32_dfu.c
  20. 13 0
      platforms/chibios/chibios_config.h
  21. 16 7
      platforms/chibios/drivers/analog.c
  22. 13 2
      platforms/chibios/drivers/serial_usart.c
  23. 67 32
      platforms/chibios/drivers/serial_usart.h
  24. 59 0
      platforms/chibios/drivers/spi_master.c
  25. 1 1
      platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
  26. 2 0
      platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
  27. 1 1
      platforms/chibios/drivers/ws2812_bitbang.c
  28. 26 0
      platforms/chibios/drivers/ws2812_pwm.c
  29. 53 18
      platforms/chibios/drivers/ws2812_spi.c
  30. 2 0
      platforms/chibios/flash.mk
  31. 34 0
      platforms/chibios/mcu_selection.mk
  32. 4 0
      platforms/chibios/platform.mk

+ 1 - 1
builddefs/common_features.mk

@@ -215,7 +215,7 @@ else
         COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
         COMMON_VPATH += $(DRIVER_PATH)/flash
         SRC += eeprom_driver.c eeprom_legacy_emulated_flash.c legacy_flash_ops.c
-      else ifneq ($(filter $(MCU_SERIES),STM32F1xx STM32F3xx STM32F4xx STM32L4xx STM32G4xx WB32F3G71xx WB32FQ95xx GD32VF103),)
+      else ifneq ($(filter $(MCU_SERIES),STM32F1xx STM32F3xx STM32F4xx STM32L4xx STM32G4xx WB32F3G71xx WB32FQ95xx AT32F415 GD32VF103),)
         # Wear-leveling EEPROM implementation, backed by MCU flash
         OPT_DEFS += -DEEPROM_DRIVER -DEEPROM_WEAR_LEVELING
         SRC += eeprom_driver.c eeprom_wear_leveling.c

+ 2 - 0
data/schemas/keyboard.jsonschema

@@ -92,6 +92,7 @@
                 "GD32VF103",
                 "WB32F3G71",
                 "WB32FQ95",
+                "AT32F415",
                 "atmega16u2",
                 "atmega32u2",
                 "atmega16u4",
@@ -216,6 +217,7 @@
             "type": "string",
             "enum": [
                 "apm32-dfu",
+                "at32-dfu",
                 "atmel-dfu",
                 "bootloadhid",
                 "caterina",

+ 4 - 0
docs/compatible_microcontrollers.md

@@ -57,6 +57,10 @@ You can also use any ARM chip with USB that [ChibiOS](https://www.chibios.org) s
  * [WB32F3G71xx](http://www.westberrytech.com)
  * [WB32FQ95xx](http://www.westberrytech.com)
 
+### Artery (AT32)
+
+ * [AT32F415](https://www.arterychip.com/en/product/AT32F415.jsp)
+
 ### NXP (Kinetis)
 
  * [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)

+ 1 - 0
docs/driver_installation_zadig.md

@@ -98,6 +98,7 @@ The device name here is the name that appears in Zadig, and may not be what the
 |`bootloadhid` |HIDBoot                       |`16C0:05DF`   |HidUsb |
 |`usbasploader`|USBasp                        |`16C0:05DC`   |libusbK|
 |`apm32-dfu`   |APM32 DFU ISP Mode            |`314B:0106`   |WinUSB |
+|`at32-dfu`    |AT32 Bootloader DFU           |`2E3C:DF11`   |WinUSB |
 |`stm32-dfu`   |STM32 BOOTLOADER              |`0483:DF11`   |WinUSB |
 |`gd32v-dfu`   |GD32V BOOTLOADER              |`28E9:0189`   |WinUSB |
 |`kiibohd`     |Kiibohd DFU Bootloader        |`1C11:B007`   |WinUSB |

+ 33 - 0
docs/flashing.md

@@ -345,6 +345,39 @@ Flashing sequence:
 3. Flash a .bin file
 4. Reset the device into application mode (may be done automatically)
 
+## AT32 DFU
+
+All AT32 MCUs come preloaded with a factory bootloader that cannot be modified nor deleted.
+
+To ensure compatibility with the AT32-DFU bootloader, make sure this block is present in your `rules.mk`:
+
+```make
+# Bootloader selection
+BOOTLOADER = at32-dfu
+```
+
+Compatible flashers:
+
+* [dfu-util](https://dfu-util.sourceforge.net/) / `:dfu-util` target in QMK (recommended command line)
+  ```
+  dfu-util -a 0 -d 2E3C:DF11 -s 0x8000000:leave -D <filename>
+  ```
+
+Flashing sequence:
+
+1. Enter the bootloader using any of the following methods:
+    * Tap the `QK_BOOT` keycode
+    * 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
+    * 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
+2. Wait for the OS to detect the device
+3. Flash a .bin file
+4. Reset the device into application mode (may be done automatically)
+
+### `make` Targets
+
+* `:dfu-util`: Waits until an AT32 bootloader device is available, and then flashes the firmware.
+* `:dfu-util-split-left` and `:dfu-util-split-right`: Flashes the firmware as with `:dfu-util`, but also sets the handedness setting in EEPROM.
+
 ## tinyuf2
 
 Keyboards may opt into supporting the tinyuf2 bootloader. This is currently only supported on F303/F401/F411.

+ 10 - 0
keyboards/handwired/onekey/at_start_f415/board.h

@@ -0,0 +1,10 @@
+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include_next <board.h>
+
+#undef AT32F415KB
+#define AT32F415RC

+ 10 - 0
keyboards/handwired/onekey/at_start_f415/config.h

@@ -0,0 +1,10 @@
+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#define ADC_PIN A0
+
+#define BACKLIGHT_PWM_DRIVER PWMD5
+#define BACKLIGHT_PWM_CHANNEL 1

+ 13 - 0
keyboards/handwired/onekey/at_start_f415/halconf.h

@@ -0,0 +1,13 @@
+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#define HAL_USE_ADC TRUE
+
+#define HAL_USE_I2C TRUE
+
+#define HAL_USE_PWM TRUE
+
+#include_next <halconf.h>

+ 20 - 0
keyboards/handwired/onekey/at_start_f415/keyboard.json

@@ -0,0 +1,20 @@
+{
+    "keyboard_name": "Onekey AT-START-F415",
+    "processor": "AT32F415",
+    "bootloader": "at32-dfu",
+    "usb": {
+        "shared_endpoint": {
+            "keyboard": true
+        }
+    },
+    "matrix_pins": {
+        "cols": ["B3"],
+        "rows": ["B4"]
+    },
+    "backlight": {
+        "pin": "A0"
+    },
+    "ws2812": {
+        "pin": "B0"
+    }
+}

+ 16 - 0
keyboards/handwired/onekey/at_start_f415/mcuconf.h

@@ -0,0 +1,16 @@
+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include_next <mcuconf.h>
+
+#undef AT32_ADC_USE_ADC1
+#define AT32_ADC_USE_ADC1 TRUE
+
+#undef AT32_I2C_USE_I2C1
+#define AT32_I2C_USE_I2C1 TRUE
+
+#undef AT32_PWM_USE_TMR5
+#define AT32_PWM_USE_TMR5 TRUE

+ 3 - 0
keyboards/handwired/onekey/at_start_f415/readme.md

@@ -0,0 +1,3 @@
+# Artery AT-START-F415 Board Onekey
+
+To trigger keypress, short together pins *B3* and *B4*.

+ 3 - 1
lib/python/qmk/constants.py

@@ -22,7 +22,7 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware'
 MAX_KEYBOARD_SUBFOLDERS = 5
 
 # Supported processor types
-CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK64FX512', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32H723', 'STM32H733', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95'
+CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK64FX512', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32H723', 'STM32H733', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95', 'AT32F415'
 LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None
 VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85'
 
@@ -55,6 +55,7 @@ MCU2BOOTLOADER = {
     "GD32VF103": "gd32v-dfu",
     "WB32F3G71": "wb32-dfu",
     "WB32FQ95": "wb32-dfu",
+    "AT32F415": "at32-dfu",
     "atmega16u2": "atmel-dfu",
     "atmega32u2": "atmel-dfu",
     "atmega16u4": "atmel-dfu",
@@ -93,6 +94,7 @@ BOOTLOADER_VIDS_PIDS = {
     'apm32-dfu': {("314b", "0106")},
     'gd32v-dfu': {("28e9", "0189")},
     'wb32-dfu': {("342d", "dfa0")},
+    'at32-dfu': {("2e3c", "df11")},
     'bootloadhid': {("16c0", "05df")},
     'usbasploader': {("16c0", "05dc")},
     'usbtinyisp': {("1782", "0c9f")},

+ 101 - 0
platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.c

@@ -0,0 +1,101 @@
+/*
+    ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
+    ChibiOS - Copyright (C) 2023..2024 HorrorTroll
+    ChibiOS - Copyright (C) 2023..2024 Zhaqian
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+#include "hal.h"
+
+/*===========================================================================*/
+/* Driver local definitions.                                                 */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* Driver exported variables.                                                */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* Driver local variables and types.                                         */
+/*===========================================================================*/
+
+/**
+ * @brief   PAL setup.
+ * @details Digital I/O ports static configuration as defined in @p board.h.
+ *          This variable is used by the HAL when initializing the PAL driver.
+ */
+#if HAL_USE_PAL || defined(__DOXYGEN__)
+const PALConfig pal_default_config =
+{
+  {VAL_GPIOAODT, VAL_GPIOACFGLR, VAL_GPIOACFGHR},
+  {VAL_GPIOBODT, VAL_GPIOBCFGLR, VAL_GPIOBCFGHR},
+#if AT32_HAS_GPIOC
+  {VAL_GPIOCODT, VAL_GPIOCCFGLR, VAL_GPIOCCFGHR},
+#endif
+  {VAL_GPIODODT, VAL_GPIODCFGLR, VAL_GPIODCFGHR},
+#if AT32_HAS_GPIOF
+  {VAL_GPIOFODT, VAL_GPIOFCFGLR, VAL_GPIOFCFGHR},
+#endif
+};
+#endif
+
+/*===========================================================================*/
+/* Driver local functions.                                                   */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* Driver interrupt handlers.                                                */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* Driver exported functions.                                                */
+/*===========================================================================*/
+
+/**
+ * @brief   Early initialization code.
+ * @details System clocks are initialized before everything else.
+ */
+void __early_init(void) {
+  at32_clock_init();
+}
+
+#if HAL_USE_SDC || defined(__DOXYGEN__)
+/**
+ * @brief   SDC card detection.
+ */
+bool sdc_lld_is_card_inserted(SDCDriver *sdcp) {
+  static bool last_status = false;
+
+  if (blkIsTransferring(sdcp))
+    return last_status;
+  return last_status = (bool)palReadPad(GPIOC, GPIOC_PIN11);
+}
+
+/**
+ * @brief   SDC card write protection detection.
+ */
+bool sdc_lld_is_write_protected(SDCDriver *sdcp) {
+
+  (void)sdcp;
+  return false;
+}
+#endif /* HAL_USE_SDC */
+
+/**
+ * @brief   Board-specific initialization code.
+ * @note    You can add your board-specific code here.
+ */
+void boardInit(void) {
+  IOMUX->REMAP |= IOMUX_REMAP_SWJTAG_MUX_JTAGDIS;
+}

+ 207 - 0
platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.h

@@ -0,0 +1,207 @@
+/*
+    ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
+    ChibiOS - Copyright (C) 2023..2024 HorrorTroll
+    ChibiOS - Copyright (C) 2023..2024 Zhaqian
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+#ifndef _BOARD_H_
+#define _BOARD_H_
+
+/*===========================================================================*/
+/* Driver constants.                                                         */
+/*===========================================================================*/
+
+/*
+ * Setup for a Generic AT32F415 board.
+ */
+
+/*
+ * Board identifier.
+ */
+#define BOARD_GENERIC_AT32_F415XX
+#define BOARD_NAME                  "GENERIC AT32F415 board"
+
+/*
+ * Board oscillators-related settings.
+ */
+#if !defined(AT32_LEXTCLK)
+#define AT32_LEXTCLK                32768
+#endif
+
+#if !defined(AT32_HEXTCLK)
+#define AT32_HEXTCLK                8000000
+#endif
+
+/*
+ * MCU type, supported types are defined in ./os/hal/platforms/hal_lld.h.
+ */
+#define AT32F415KB
+
+/*
+ * IO pins assignments.
+ */
+#define GPIOA_PIN0                  0U
+#define GPIOA_PIN1                  1U
+#define GPIOA_PIN2                  2U
+#define GPIOA_PIN3                  3U
+#define GPIOA_PIN4                  4U
+#define GPIOA_PIN5                  5U
+#define GPIOA_PIN6                  6U
+#define GPIOA_PIN7                  7U
+#define GPIOA_PIN8                  8U
+#define GPIOA_PIN9                  9U
+#define GPIOA_PIN10                 10U
+#define GPIOA_PIN11                 11U
+#define GPIOA_PIN12                 12U
+#define GPIOA_SWDIO                 13U
+#define GPIOA_SWCLK                 14U
+#define GPIOA_PIN15                 15U
+
+#define GPIOB_PIN0                  0U
+#define GPIOB_PIN1                  1U
+#define GPIOB_PIN2                  2U
+#define GPIOB_PIN3                  3U
+#define GPIOB_PIN4                  4U
+#define GPIOB_PIN5                  5U
+#define GPIOB_PIN6                  6U
+#define GPIOB_PIN7                  7U
+#define GPIOB_PIN8                  8U
+#define GPIOB_PIN9                  9U
+#define GPIOB_PIN10                 10U
+#define GPIOB_PIN11                 11U
+#define GPIOB_PIN12                 12U
+#define GPIOB_PIN13                 13U
+#define GPIOB_PIN14                 14U
+#define GPIOB_PIN15                 15U
+
+#define GPIOC_PIN0                  0U
+#define GPIOC_PIN1                  1U
+#define GPIOC_PIN2                  2U
+#define GPIOC_PIN3                  3U
+#define GPIOC_PIN4                  4U
+#define GPIOC_PIN5                  5U
+#define GPIOC_PIN6                  6U
+#define GPIOC_PIN7                  7U
+#define GPIOC_PIN8                  8U
+#define GPIOC_PIN9                  9U
+#define GPIOC_PIN10                 10U
+#define GPIOC_PIN11                 11U
+#define GPIOC_PIN12                 12U
+#define GPIOC_PIN13                 13U
+#define GPIOC_PIN14                 14U
+#define GPIOC_PIN15                 15U
+
+#define GPIOD_HEXT_IN               0U
+#define GPIOD_HEXT_OUT              1U
+#define GPIOD_PIN2                  2U
+
+#define GPIOF_PIN4                  4U
+#define GPIOF_PIN5                  5U
+#define GPIOF_PIN6                  6U
+#define GPIOF_PIN7                  7U
+
+/*===========================================================================*/
+/* Driver pre-compile time settings.                                         */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* Derived constants and error checks.                                       */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* Driver data structures and types.                                         */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* Driver macros.                                                            */
+/*===========================================================================*/
+
+/*
+ * I/O ports initial setup, this configuration is established soon after reset
+ * in the initialization code.
+ *
+ * The digits have the following meaning:
+ *   0 - Analog input.
+ *   1 - Push Pull output 10MHz.
+ *   2 - Push Pull output 2MHz.
+ *   3 - Push Pull output 50MHz.
+ *   4 - Digital input.
+ *   5 - Open Drain output 10MHz.
+ *   6 - Open Drain output 2MHz.
+ *   7 - Open Drain output 50MHz.
+ *   8 - Digital input with Pull-Up or Pull-Down resistor depending on ODT.
+ *   9 - Alternate Push Pull output 10MHz.
+ *   A - Alternate Push Pull output 2MHz.
+ *   B - Alternate Push Pull output 50MHz.
+ *   C - Reserved.
+ *   D - Alternate Open Drain output 10MHz.
+ *   E - Alternate Open Drain output 2MHz.
+ *   F - Alternate Open Drain output 50MHz.
+ * Please refer to the AT32 Reference Manual for details.
+ */
+
+/*
+ * Port A setup.
+ */
+#define VAL_GPIOACFGLR          0x88888B88      /*  PA7...PA0 */
+#define VAL_GPIOACFGHR          0x888888B8      /* PA15...PA8 */
+#define VAL_GPIOAODT            0xFFFFFFFF
+
+/*
+ * Port B setup.
+ */
+#define VAL_GPIOBCFGLR          0x88888888      /*  PB7...PB0 */
+#define VAL_GPIOBCFGHR          0x88888888      /* PB15...PB8 */
+#define VAL_GPIOBODT            0xFFFFFFFF
+
+/*
+ * Port C setup.
+ */
+#define VAL_GPIOCCFGLR          0x88888888      /*  PC7...PC0 */
+#define VAL_GPIOCCFGHR          0x88888888      /* PC15...PC8 */
+#define VAL_GPIOCODT            0xFFFFFFFF
+
+/*
+ * Port D setup.
+ * Everything input with pull-up except:
+ * PD0  - Normal input              (GPIOD_HEXT_IN).
+ * PD1  - Normal input              (GPIOD_HEXT_OUT).
+ */
+#define VAL_GPIODCFGLR          0x88888844      /*  PD7...PD0 */
+#define VAL_GPIODCFGHR          0x88888888      /* PD15...PD8 */
+#define VAL_GPIODODT            0xFFFFFFFF
+
+/*
+ * Port F setup.
+ */
+#define VAL_GPIOFCFGLR          0x88888888      /*  PF7...PF0 */
+#define VAL_GPIOFCFGHR          0x88888888      /* PF15...PF8 */
+#define VAL_GPIOFODT            0xFFFFFFFF
+
+/*===========================================================================*/
+/* External declarations.                                                    */
+/*===========================================================================*/
+
+#if !defined(_FROM_ASM_)
+#ifdef __cplusplus
+extern "C" {
+#endif
+  void boardInit(void);
+#ifdef __cplusplus
+}
+#endif
+#endif /* _FROM_ASM_ */
+
+#endif /* _BOARD_H_ */

+ 9 - 0
platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.mk

@@ -0,0 +1,9 @@
+# List of all the board related files.
+BOARDSRC = $(BOARD_PATH)/board/board.c
+
+# Required include directories
+BOARDINC = $(BOARD_PATH)/board
+
+# Shared variables
+ALLCSRC += $(BOARDSRC)
+ALLINC  += $(BOARDINC)

+ 13 - 0
platforms/chibios/boards/GENERIC_AT32_F415XX/configs/config.h

@@ -0,0 +1,13 @@
+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#define BOARD_OTG_VBUSIG
+
+#define USB_ENDPOINTS_ARE_REORDERABLE
+
+#ifndef EARLY_INIT_PERFORM_BOOTLOADER_JUMP
+#    define EARLY_INIT_PERFORM_BOOTLOADER_JUMP TRUE
+#endif

+ 236 - 0
platforms/chibios/boards/GENERIC_AT32_F415XX/configs/mcuconf.h

@@ -0,0 +1,236 @@
+/*
+    ChibiOS - Copyright (C) 2006..2020 Giovanni Di Sirio
+    ChibiOS - Copyright (C) 2023..2024 HorrorTroll
+    ChibiOS - Copyright (C) 2023..2024 Zhaqian
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+#ifndef MCUCONF_H
+#define MCUCONF_H
+
+/*
+ * AT32F415 drivers configuration.
+ * The following settings override the default settings present in
+ * the various device driver implementation headers.
+ * Note that the settings for each driver only have effect if the whole
+ * driver is enabled in halconf.h.
+ *
+ * IRQ priorities:
+ * 15...0       Lowest...Highest.
+ *
+ * DMA priorities:
+ * 0...3        Lowest...Highest.
+ */
+
+#define AT32F415_MCUCONF
+
+/*
+ * General settings.
+ */
+#define AT32_NO_INIT                        FALSE
+
+/*
+ * HAL driver system settings.
+ */
+#define AT32_HICK_ENABLED                   TRUE
+#define AT32_LICK_ENABLED                   FALSE
+#define AT32_HEXT_ENABLED                   TRUE
+#define AT32_LEXT_ENABLED                   FALSE
+#define AT32_SCLKSEL                        AT32_SCLKSEL_PLL
+#define AT32_PLLRCS                         AT32_PLLRCS_HEXT
+#define AT32_PLLHEXTDIV                     AT32_PLLHEXTDIV_DIV1
+#define AT32_PLLCFGEN                       AT32_PLLCFGEN_SOLID
+#define AT32_PLLMULT_VALUE                  18
+#define AT32_PLL_FR_VALUE                   4
+#define AT32_PLL_MS_VALUE                   1
+#define AT32_PLL_NS_VALUE                   72
+#define AT32_AHBDIV                         AT32_AHBDIV_DIV1
+#define AT32_APB1DIV                        AT32_APB1DIV_DIV2
+#define AT32_APB2DIV                        AT32_APB2DIV_DIV2
+#define AT32_ADCDIV                         AT32_ADCDIV_DIV4
+#define AT32_USB_CLOCK_REQUIRED             TRUE
+#define AT32_USBDIV                         AT32_USBDIV_DIV3
+#define AT32_CLKOUT_SEL                     AT32_CLKOUT_SEL_NOCLOCK
+#define AT32_CLKOUTDIV                      AT32_CLKOUTDIV_DIV1
+#define AT32_ERTCSEL                        AT32_ERTCSEL_HEXTDIV
+#define AT32_PVM_ENABLE                     FALSE
+#define AT32_PVMSEL                         AT32_PVMSEL_LEV1
+
+/*
+ * IRQ system settings.
+ */
+#define AT32_IRQ_EXINT0_PRIORITY            6
+#define AT32_IRQ_EXINT1_PRIORITY            6
+#define AT32_IRQ_EXINT2_PRIORITY            6
+#define AT32_IRQ_EXINT3_PRIORITY            6
+#define AT32_IRQ_EXINT4_PRIORITY            6
+#define AT32_IRQ_EXINT5_9_PRIORITY          6
+#define AT32_IRQ_EXINT10_15_PRIORITY        6
+#define AT32_IRQ_EXINT16_PRIORITY           6
+#define AT32_IRQ_EXINT17_PRIORITY           15
+#define AT32_IRQ_EXINT18_PRIORITY           6
+#define AT32_IRQ_EXINT19_PRIORITY           6
+#define AT32_IRQ_EXINT20_PRIORITY           6
+#define AT32_IRQ_EXINT21_PRIORITY           15
+#define AT32_IRQ_EXINT22_PRIORITY           15
+
+#define AT32_IRQ_TMR1_BRK_TMR9_PRIORITY     7
+#define AT32_IRQ_TMR1_OVF_TMR10_PRIORITY    7
+#define AT32_IRQ_TMR1_HALL_TMR11_PRIORITY   7
+#define AT32_IRQ_TMR1_CH_PRIORITY           7
+#define AT32_IRQ_TMR2_PRIORITY              7
+#define AT32_IRQ_TMR3_PRIORITY              7
+#define AT32_IRQ_TMR4_PRIORITY              7
+#define AT32_IRQ_TMR5_PRIORITY              7
+
+#define AT32_IRQ_USART1_PRIORITY            12
+#define AT32_IRQ_USART2_PRIORITY            12
+#define AT32_IRQ_USART3_PRIORITY            12
+#define AT32_IRQ_UART4_PRIORITY             12
+#define AT32_IRQ_UART5_PRIORITY             12
+
+/*
+ * ADC driver system settings.
+ */
+#define AT32_ADC_USE_ADC1                   FALSE
+#define AT32_ADC_ADC1_DMA_PRIORITY          2
+#define AT32_ADC_ADC1_IRQ_PRIORITY          6
+
+/*
+ * CAN driver system settings.
+ */
+#define AT32_CAN_USE_CAN1                   FALSE
+#define AT32_CAN_CAN1_IRQ_PRIORITY          11
+
+/*
+ * DMA driver system settings.
+ */
+#define AT32_DMA_USE_DMAMUX                 TRUE
+
+/*
+ * GPT driver system settings.
+ */
+#define AT32_GPT_USE_TMR1                   FALSE
+#define AT32_GPT_USE_TMR2                   FALSE
+#define AT32_GPT_USE_TMR3                   FALSE
+#define AT32_GPT_USE_TMR4                   FALSE
+#define AT32_GPT_USE_TMR5                   FALSE
+#define AT32_GPT_USE_TMR9                   FALSE
+#define AT32_GPT_USE_TMR10                  FALSE
+#define AT32_GPT_USE_TMR11                  FALSE
+
+/*
+ * I2C driver system settings.
+ */
+#define AT32_I2C_USE_I2C1                   FALSE
+#define AT32_I2C_USE_I2C2                   FALSE
+#define AT32_I2C_BUSY_TIMEOUT               50
+#define AT32_I2C_I2C1_IRQ_PRIORITY          5
+#define AT32_I2C_I2C2_IRQ_PRIORITY          5
+#define AT32_I2C_I2C1_DMA_PRIORITY          3
+#define AT32_I2C_I2C2_DMA_PRIORITY          3
+#define AT32_I2C_DMA_ERROR_HOOK(i2cp)       osalSysHalt("DMA failure")
+
+/*
+ * ICU driver system settings.
+ */
+#define AT32_ICU_USE_TMR1                   FALSE
+#define AT32_ICU_USE_TMR2                   FALSE
+#define AT32_ICU_USE_TMR3                   FALSE
+#define AT32_ICU_USE_TMR4                   FALSE
+#define AT32_ICU_USE_TMR5                   FALSE
+#define AT32_ICU_USE_TMR9                   FALSE
+#define AT32_ICU_USE_TMR10                  FALSE
+#define AT32_ICU_USE_TMR11                  FALSE
+
+/*
+ * PWM driver system settings.
+ */
+#define AT32_PWM_USE_TMR1                   FALSE
+#define AT32_PWM_USE_TMR2                   FALSE
+#define AT32_PWM_USE_TMR3                   FALSE
+#define AT32_PWM_USE_TMR4                   FALSE
+#define AT32_PWM_USE_TMR5                   FALSE
+#define AT32_PWM_USE_TMR9                   FALSE
+#define AT32_PWM_USE_TMR10                  FALSE
+#define AT32_PWM_USE_TMR11                  FALSE
+
+/*
+ * RTC driver system settings.
+ */
+#define AT32_ERTC_DIVA_VALUE                32
+#define AT32_ERTC_DIVB_VALUE                1024
+#define AT32_ERTC_CTRL_INIT                 0
+#define AT32_ERTC_TAMP_INIT                 0
+
+/*
+ * SDC driver system settings.
+ */
+#define AT32_SDC_SDIO_DMA_PRIORITY          3
+#define AT32_SDC_SDIO_IRQ_PRIORITY          9
+#define AT32_SDC_WRITE_TIMEOUT_MS           1000
+#define AT32_SDC_READ_TIMEOUT_MS            1000
+#define AT32_SDC_CLOCK_ACTIVATION_DELAY     10
+#define AT32_SDC_SDIO_UNALIGNED_SUPPORT     TRUE
+
+/*
+ * SERIAL driver system settings.
+ */
+#define AT32_SERIAL_USE_USART1              FALSE
+#define AT32_SERIAL_USE_USART2              FALSE
+#define AT32_SERIAL_USE_USART3              FALSE
+#define AT32_SERIAL_USE_UART4               FALSE
+#define AT32_SERIAL_USE_UART5               FALSE
+
+/*
+ * SPI driver system settings.
+ */
+#define AT32_SPI_USE_SPI1                   FALSE
+#define AT32_SPI_USE_SPI2                   FALSE
+#define AT32_SPI_SPI1_DMA_PRIORITY          1
+#define AT32_SPI_SPI2_DMA_PRIORITY          1
+#define AT32_SPI_SPI1_IRQ_PRIORITY          10
+#define AT32_SPI_SPI2_IRQ_PRIORITY          10
+#define AT32_SPI_DMA_ERROR_HOOK(spip)       osalSysHalt("DMA failure")
+
+/*
+ * ST driver system settings.
+ */
+#define AT32_ST_IRQ_PRIORITY                8
+#define AT32_ST_USE_TIMER                   2
+
+/*
+ * UART driver system settings.
+ */
+#define AT32_UART_USE_USART1                FALSE
+#define AT32_UART_USE_USART2                FALSE
+#define AT32_UART_USE_USART3                FALSE
+#define AT32_UART_USART1_DMA_PRIORITY       0
+#define AT32_UART_USART2_DMA_PRIORITY       0
+#define AT32_UART_USART3_DMA_PRIORITY       0
+#define AT32_UART_DMA_ERROR_HOOK(uartp)     osalSysHalt("DMA failure")
+
+/*
+ * USB driver system settings.
+ */
+#define AT32_USB_USE_OTG1                   TRUE
+#define AT32_USB_OTG1_IRQ_PRIORITY          14
+#define AT32_USB_OTG1_RX_FIFO_SIZE          512
+
+/*
+ * WDG driver system settings.
+ */
+#define AT32_WDG_USE_WDT                    FALSE
+
+#endif /* MCUCONF_H */

+ 9 - 0
platforms/chibios/bootloader.mk

@@ -26,6 +26,7 @@
 #     stm32-dfu    STM32 USB DFU in ROM
 #     apm32-dfu    APM32 USB DFU in ROM
 #     wb32-dfu     WB32 USB DFU in ROM
+#     at32-dfu     AT32 USB DFU in ROM
 #     tinyuf2      TinyUF2
 #     rp2040       Raspberry Pi RP2040
 # Current options for RISC-V:
@@ -119,6 +120,14 @@ ifeq ($(strip $(BOOTLOADER)), wb32-dfu)
     OPT_DEFS += -DBOOTLOADER_WB32_DFU
     BOOTLOADER_TYPE = wb32_dfu
 endif
+ifeq ($(strip $(BOOTLOADER)), at32-dfu)
+    OPT_DEFS += -DBOOTLOADER_AT32_DFU
+    BOOTLOADER_TYPE = at32_dfu
+
+    # Options to pass to dfu-util when flashing
+    DFU_ARGS ?= -d 2E3C:DF11 -a 0 -s 0x08000000:leave
+    DFU_SUFFIX_ARGS ?= -v 2E3C -p DF11
+endif
 
 ifeq ($(strip $(BOOTLOADER_TYPE)),)
     ifneq ($(strip $(BOOTLOADER)),)

+ 83 - 0
platforms/chibios/bootloaders/at32_dfu.c

@@ -0,0 +1,83 @@
+// Copyright 2021-2023 QMK
+// Copyright 2023-2024 HorrorTroll <https://github.com/HorrorTroll>
+// Copyright 2023-2024 Zhaqian <https://github.com/zhaqian12>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "bootloader.h"
+#include "util.h"
+
+#include <ch.h>
+#include <hal.h>
+#include "wait.h"
+
+#ifndef AT32_BOOTLOADER_RAM_SYMBOL
+#    define AT32_BOOTLOADER_RAM_SYMBOL __ram0_end__
+#endif
+
+extern uint32_t AT32_BOOTLOADER_RAM_SYMBOL;
+
+/* This code should be checked whether it runs correctly on platforms */
+#define SYMVAL(sym) (uint32_t)(((uint8_t *)&(sym)) - ((uint8_t *)0))
+#define BOOTLOADER_MAGIC 0xDEADBEEF
+#define MAGIC_ADDR (unsigned long *)(SYMVAL(AT32_BOOTLOADER_RAM_SYMBOL) - 4)
+
+__attribute__((weak)) void bootloader_marker_enable(void) {
+    uint32_t *marker = (uint32_t *)MAGIC_ADDR;
+    *marker          = BOOTLOADER_MAGIC; // set magic flag => reset handler will jump into boot loader
+}
+
+__attribute__((weak)) bool bootloader_marker_active(void) {
+    const uint32_t *marker = (const uint32_t *)MAGIC_ADDR;
+    return (*marker == BOOTLOADER_MAGIC) ? true : false;
+}
+
+__attribute__((weak)) void bootloader_marker_disable(void) {
+    uint32_t *marker = (uint32_t *)MAGIC_ADDR;
+    *marker          = 0;
+}
+
+__attribute__((weak)) void bootloader_jump(void) {
+    bootloader_marker_enable();
+    NVIC_SystemReset();
+}
+
+__attribute__((weak)) void mcu_reset(void) {
+    NVIC_SystemReset();
+}
+
+void enter_bootloader_mode_if_requested(void) {
+    if (bootloader_marker_active()) {
+        bootloader_marker_disable();
+
+        struct system_memory_vector_t {
+            uint32_t stack_top;
+            void (*entrypoint)(void);
+        };
+        const struct system_memory_vector_t *bootloader = (const struct system_memory_vector_t *)(AT32_BOOTLOADER_ADDRESS);
+
+        __disable_irq();
+
+#if defined(__MPU_PRESENT) && (__MPU_PRESENT == 1U)
+        ARM_MPU_Disable();
+#endif
+
+        SysTick->CTRL = 0;
+        SysTick->VAL  = 0;
+        SysTick->LOAD = 0;
+
+        // Clear interrupt enable and interrupt pending registers
+        for (int i = 0; i < ARRAY_SIZE(NVIC->ICER); i++) {
+            NVIC->ICER[i] = 0xFFFFFFFF;
+            NVIC->ICPR[i] = 0xFFFFFFFF;
+        }
+
+        __set_CONTROL(0);
+        __set_MSP(bootloader->stack_top);
+        __enable_irq();
+
+        // Jump to bootloader
+        bootloader->entrypoint();
+        while (true) {
+        }
+    }
+}

+ 13 - 0
platforms/chibios/chibios_config.h

@@ -142,6 +142,19 @@
 #    endif
 #endif
 
+// AT32 compatibility
+#if defined(MCU_AT32)
+#    define CPU_CLOCK AT32_SYSCLK
+
+#    if defined(AT32F415)
+#        define USE_GPIOV1
+#        define USE_I2CV1
+#        define PAL_MODE_ALTERNATE_OPENDRAIN PAL_MODE_AT32_ALTERNATE_OPENDRAIN
+#        define PAL_MODE_ALTERNATE_PUSHPULL PAL_MODE_AT32_ALTERNATE_PUSHPULL
+#        define AUDIO_PWM_PAL_MODE PAL_MODE_ALTERNATE_PUSHPULL
+#    endif
+#endif
+
 #if defined(GD32VF103)
 /* This chip has the same API as STM32F103, but uses different names for literally the same thing.
  * As of 4.7.2021 QMK is tailored to use STM32 defines/names, for compatibility sake

+ 16 - 7
platforms/chibios/drivers/analog.c

@@ -22,7 +22,7 @@
 #    error "You need to set HAL_USE_ADC to TRUE in your halconf.h to use the ADC."
 #endif
 
-#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
+#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
 #    error "You need to set one of the 'xxx_ADC_USE_ADCx' settings to TRUE in your mcuconf.h to use the ADC."
 #endif
 
@@ -45,7 +45,7 @@
 // Otherwise assume V3
 #if defined(STM32F0XX) || defined(STM32L0XX)
 #    define USE_ADCV1
-#elif defined(STM32F1XX) || defined(STM32F2XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx)
+#elif defined(STM32F1XX) || defined(STM32F2XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415)
 #    define USE_ADCV2
 #endif
 
@@ -82,7 +82,7 @@
 
 /* User configurable ADC options */
 #ifndef ADC_COUNT
-#    if defined(RP2040) || defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx)
+#    if defined(RP2040) || defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415)
 #        define ADC_COUNT 1
 #    elif defined(STM32F3XX) || defined(STM32G4XX)
 #        define ADC_COUNT 4
@@ -142,11 +142,16 @@ static ADCConversionGroup adcConversionGroup = {
     .cfgr1 = ADC_CFGR1_CONT | ADC_RESOLUTION,
     .smpr  = ADC_SAMPLING_RATE,
 #elif defined(USE_ADCV2)
-#    if !defined(STM32F1XX) && !defined(GD32VF103) && !defined(WB32F3G71xx) && !defined(WB32FQ95xx)
-    .cr2   = ADC_CR2_SWSTART, // F103 seem very unhappy with, F401 seems very unhappy without...
+#    if !defined(STM32F1XX) && !defined(GD32VF103) && !defined(WB32F3G71xx) && !defined(WB32FQ95xx) && !defined(AT32F415)
+    .cr2  = ADC_CR2_SWSTART, // F103 seem very unhappy with, F401 seems very unhappy without...
 #    endif
+#    if defined(AT32F415)
+    .spt2 = ADC_SPT2_CSPT_AN0(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN1(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN2(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN3(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN4(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN5(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN6(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN7(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN8(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN9(ADC_SAMPLING_RATE),
+    .spt1 = ADC_SPT1_CSPT_AN10(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN11(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN12(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN13(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN14(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN15(ADC_SAMPLING_RATE),
+#    else
     .smpr2 = ADC_SMPR2_SMP_AN0(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN1(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN2(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN3(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN4(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN5(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN6(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN7(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN8(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN9(ADC_SAMPLING_RATE),
     .smpr1 = ADC_SMPR1_SMP_AN10(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN11(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN12(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN13(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN14(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN15(ADC_SAMPLING_RATE),
+#    endif
 #elif defined(RP2040)
 // RP2040 does not have any extra config here
 #else
@@ -242,7 +247,7 @@ __attribute__((weak)) adc_mux pinToMux(pin_t pin) {
         case F9:  return TO_MUX( ADC_CHANNEL_IN7,  2 );
         case F10: return TO_MUX( ADC_CHANNEL_IN8,  2 );
 #    endif
-#elif defined(STM32F1XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx)
+#elif defined(STM32F1XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415)
         case A0:  return TO_MUX( ADC_CHANNEL_IN0,  0 );
         case A1:  return TO_MUX( ADC_CHANNEL_IN1,  0 );
         case A2:  return TO_MUX( ADC_CHANNEL_IN2,  0 );
@@ -344,7 +349,7 @@ __attribute__((weak)) adc_mux pinToMux(pin_t pin) {
 
 static inline ADCDriver* intToADCDriver(uint8_t adcInt) {
     switch (adcInt) {
-#if RP_ADC_USE_ADC1 || STM32_ADC_USE_ADC1 || WB32_ADC_USE_ADC1
+#if RP_ADC_USE_ADC1 || STM32_ADC_USE_ADC1 || WB32_ADC_USE_ADC1 || AT32_ADC_USE_ADC1
         case 0:
             return &ADCD1;
 #endif
@@ -391,7 +396,11 @@ int16_t adc_read(adc_mux mux) {
     // TODO: fix previous assumption of only 1 input...
     adcConversionGroup.chselr = 1 << mux.input; /*no macro to convert N to ADC_CHSELR_CHSEL1*/
 #elif defined(USE_ADCV2)
+#    if defined(AT32F415)
+    adcConversionGroup.osq3 = ADC_OSQ3_OSN1_N(mux.input);
+#    else
     adcConversionGroup.sqr3 = ADC_SQR3_SQ1_N(mux.input);
+#    endif
 #elif defined(RP2040)
     adcConversionGroup.channel_mask = 1 << mux.input;
 #else

+ 13 - 2
platforms/chibios/drivers/serial_usart.c

@@ -9,6 +9,17 @@
 
 #if defined(SERIAL_USART_CONFIG)
 static QMKSerialConfig serial_config = SERIAL_USART_CONFIG;
+#elif defined(MCU_AT32) /* AT32 MCUs */
+static QMKSerialConfig serial_config = {
+    .speed = (SERIAL_USART_SPEED),
+    .ctrl1 = (SERIAL_USART_CTRL1),
+    .ctrl2 = (SERIAL_USART_CTRL2),
+#    if !defined(SERIAL_USART_FULL_DUPLEX)
+    .ctrl3 = ((SERIAL_USART_CTRL3) | USART_CTRL3_SLBEN) /* activate half-duplex mode */
+#    else
+    .ctrl3 = (SERIAL_USART_CTRL3)
+#    endif
+};
 #elif defined(MCU_STM32) /* STM32 MCUs */
 static QMKSerialConfig serial_config = {
 #    if HAL_USE_SERIAL
@@ -160,7 +171,7 @@ inline bool serial_transport_receive_blocking(uint8_t* destination, const size_t
  * @brief Initiate pins for USART peripheral. Half-duplex configuration.
  */
 __attribute__((weak)) void usart_init(void) {
-#    if defined(MCU_STM32) /* STM32 MCUs */
+#    if defined(MCU_STM32) || defined(MCU_AT32) /* STM32 and AT32 MCUs */
 #        if defined(USE_GPIOV1)
     palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE_OPENDRAIN);
 #        else
@@ -183,7 +194,7 @@ __attribute__((weak)) void usart_init(void) {
  * @brief Initiate pins for USART peripheral. Full-duplex configuration.
  */
 __attribute__((weak)) void usart_init(void) {
-#    if defined(MCU_STM32) /* STM32 MCUs */
+#    if defined(MCU_STM32) || defined(MCU_AT32) /* STM32 and AT32 MCUs */
 #        if defined(USE_GPIOV1)
     palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE_PUSHPULL);
     palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_INPUT);

+ 67 - 32
platforms/chibios/drivers/serial_usart.h

@@ -74,40 +74,75 @@ typedef SIOConfig QMKSerialConfig;
 #    endif
 #endif
 
-#if !defined(USART_CR1_M0)
-#    define USART_CR1_M0 USART_CR1_M // some platforms (f1xx) dont have this so
-#endif
+#if defined(MCU_STM32) /* STM32 MCUs */
+#    if !defined(USART_CR1_M0)
+#        define USART_CR1_M0 USART_CR1_M // some platforms (f1xx) dont have this so
+#    endif
 
-#if !defined(SERIAL_USART_CR1)
-#    define SERIAL_USART_CR1 (USART_CR1_PCE | USART_CR1_PS | USART_CR1_M0) // parity enable, odd parity, 9 bit length
-#endif
+#    if !defined(SERIAL_USART_CR1)
+#        define SERIAL_USART_CR1 (USART_CR1_PCE | USART_CR1_PS | USART_CR1_M0) // parity enable, odd parity, 9 bit length
+#    endif
 
-#if !defined(SERIAL_USART_CR2)
-#    define SERIAL_USART_CR2 (USART_CR2_STOP_1) // 2 stop bits
-#endif
+#    if !defined(SERIAL_USART_CR2)
+#        define SERIAL_USART_CR2 (USART_CR2_STOP_1) // 2 stop bits
+#    endif
 
-#if !defined(SERIAL_USART_CR3)
-#    define SERIAL_USART_CR3 0
-#endif
+#    if !defined(SERIAL_USART_CR3)
+#        define SERIAL_USART_CR3 0
+#    endif
+
+#    if defined(USART1_REMAP)
+#        define USART_REMAP                             \
+            do {                                        \
+                (AFIO->MAPR |= AFIO_MAPR_USART1_REMAP); \
+            } while (0)
+#    elif defined(USART2_REMAP)
+#        define USART_REMAP                             \
+            do {                                        \
+                (AFIO->MAPR |= AFIO_MAPR_USART2_REMAP); \
+            } while (0)
+#    elif defined(USART3_PARTIALREMAP)
+#        define USART_REMAP                                          \
+            do {                                                     \
+                (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_PARTIALREMAP); \
+            } while (0)
+#    elif defined(USART3_FULLREMAP)
+#        define USART_REMAP                                       \
+            do {                                                  \
+                (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_FULLREMAP); \
+            } while (0)
+#    endif
+#elif defined(MCU_AT32) /* AT32 MCUs */
+#    if !defined(USART_CTRL1_DBN0)
+#        define USART_CTRL1_DBN0 USART_CTRL1_DBN
+#    endif
 
-#if defined(USART1_REMAP)
-#    define USART_REMAP                             \
-        do {                                        \
-            (AFIO->MAPR |= AFIO_MAPR_USART1_REMAP); \
-        } while (0)
-#elif defined(USART2_REMAP)
-#    define USART_REMAP                             \
-        do {                                        \
-            (AFIO->MAPR |= AFIO_MAPR_USART2_REMAP); \
-        } while (0)
-#elif defined(USART3_PARTIALREMAP)
-#    define USART_REMAP                                          \
-        do {                                                     \
-            (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_PARTIALREMAP); \
-        } while (0)
-#elif defined(USART3_FULLREMAP)
-#    define USART_REMAP                                       \
-        do {                                                  \
-            (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_FULLREMAP); \
-        } while (0)
+#    if !defined(SERIAL_USART_CTRL1)
+#        define SERIAL_USART_CTRL1 (USART_CTRL1_PEN | USART_CTRL1_PSEL | USART_CTRL1_DBN0) // parity enable, odd parity, 9 bit length
+#    endif
+
+#    if !defined(SERIAL_USART_CTRL2)
+#        define SERIAL_USART_CTRL2 (USART_CTRL2_STOPBN_1) // 2 stop bits
+#    endif
+
+#    if !defined(SERIAL_USART_CTRL3)
+#        define SERIAL_USART_CTRL3 0
+#    endif
+
+#    if defined(USART1_REMAP)
+#        define USART_REMAP                               \
+            do {                                          \
+                (IOMUX->REMAP |= IOMUX_REMAP_USART1_MUX); \
+            } while (0)
+#    elif defined(USART3_PARTIALREMAP)
+#        define USART_REMAP                                    \
+            do {                                               \
+                (IOMUX->REMAP |= IOMUX_REMAP_USART3_MUX_MUX1); \
+            } while (0)
+#    elif defined(USART3_FULLREMAP)
+#        define USART_REMAP                                    \
+            do {                                               \
+                (IOMUX->REMAP |= IOMUX_REMAP_USART3_MUX_MUX2); \
+            } while (0)
+#    endif
 #endif

+ 59 - 0
platforms/chibios/drivers/spi_master.c

@@ -103,9 +103,15 @@ bool spi_start_extended(spi_start_config_t *start_config) {
         roundedDivisor <<= 1;
     }
 
+#    if defined(AT32F415)
+    if (roundedDivisor < 2 || roundedDivisor > 1024) {
+        return false;
+    }
+#    else
     if (roundedDivisor < 2 || roundedDivisor > 256) {
         return false;
     }
+#    endif
 #endif
 
 #if defined(K20x) || defined(KL2x)
@@ -240,6 +246,59 @@ bool spi_start_extended(spi_start_config_t *start_config) {
             spiConfig.SSPCR0 |= SPI_SSPCR0_SPH; // Clock phase: sample on second edge transition
             break;
     }
+#elif defined(AT32F415)
+    spiConfig.ctrl1 = 0;
+
+    if (lsbFirst) {
+        spiConfig.ctrl1 |= SPI_CTRL1_LTF;
+    }
+
+    switch (mode) {
+        case 0:
+            break;
+        case 1:
+            spiConfig.ctrl1 |= SPI_CTRL1_CLKPHA;
+            break;
+        case 2:
+            spiConfig.ctrl1 |= SPI_CTRL1_CLKPOL;
+            break;
+        case 3:
+            spiConfig.ctrl1 |= SPI_CTRL1_CLKPHA | SPI_CTRL1_CLKPOL;
+            break;
+    }
+
+    switch (roundedDivisor) {
+        case 2:
+            break;
+        case 4:
+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_0;
+            break;
+        case 8:
+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_1;
+            break;
+        case 16:
+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0;
+            break;
+        case 32:
+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2;
+            break;
+        case 64:
+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_0;
+            break;
+        case 128:
+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1;
+            break;
+        case 256:
+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0;
+            break;
+        case 512:
+            spiConfig.ctrl2 |= SPI_CTRL1_MDIV_3;
+            break;
+        case 1024:
+            spiConfig.ctrl2 |= SPI_CTRL1_MDIV_3;
+            spiConfig.ctrl1 |= SPI_CTRL1_MDIV_0;
+            break;
+    }
 #else
     spiConfig.cr1 = 0;
 

+ 1 - 1
platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c

@@ -33,7 +33,7 @@ static inline uint32_t detect_flash_size(void) {
 #elif defined(FLASH_SIZE)
     return FLASH_SIZE;
 #elif defined(FLASHSIZE_BASE)
-#    if defined(QMK_MCU_SERIES_STM32F0XX) || defined(QMK_MCU_SERIES_STM32F1XX) || defined(QMK_MCU_SERIES_STM32F3XX) || defined(QMK_MCU_SERIES_STM32F4XX) || defined(QMK_MCU_SERIES_STM32G4XX) || defined(QMK_MCU_SERIES_STM32L0XX) || defined(QMK_MCU_SERIES_STM32L4XX) || defined(QMK_MCU_SERIES_GD32VF103)
+#    if defined(QMK_MCU_SERIES_STM32F0XX) || defined(QMK_MCU_SERIES_STM32F1XX) || defined(QMK_MCU_SERIES_STM32F3XX) || defined(QMK_MCU_SERIES_STM32F4XX) || defined(QMK_MCU_SERIES_STM32G4XX) || defined(QMK_MCU_SERIES_STM32L0XX) || defined(QMK_MCU_SERIES_STM32L4XX) || defined(QMK_MCU_SERIES_AT32F415) || defined(QMK_MCU_SERIES_GD32VF103)
     return ((*(uint32_t *)FLASHSIZE_BASE) & 0xFFFFU) << 10U; // this register has the flash size in kB, so we convert it to bytes
 #    elif defined(QMK_MCU_SERIES_STM32L1XX)
 #        error This MCU family has an uncommon flash size register definition and has not been implemented. Perhaps try using the true EEPROM on the MCU instead?

+ 2 - 0
platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h

@@ -16,6 +16,8 @@
 #        define BACKING_STORE_WRITE_SIZE 4 // from hal_efl_lld.c
 #    elif defined(QMK_MCU_FAMILY_WB32)
 #        define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
+#    elif defined(QMK_MCU_FAMILY_AT32)
+#        define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
 #    elif defined(QMK_MCU_FAMILY_STM32)
 #        if defined(STM32_FLASH_LINE_SIZE) // from some family's stm32_registry.h file
 #            define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE)

+ 1 - 1
platforms/chibios/drivers/ws2812_bitbang.c

@@ -11,7 +11,7 @@
 /* Adapted from https://github.com/bigjosh/SimpleNeoPixelDemo/ */
 
 #ifndef WS2812_BITBANG_NOP_FUDGE
-#    if defined(STM32F0XX) || defined(STM32F1XX) || defined(GD32VF103) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(WB32F3G71xx) || defined(WB32FQ95xx)
+#    if defined(STM32F0XX) || defined(STM32F1XX) || defined(GD32VF103) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415)
 #        define WS2812_BITBANG_NOP_FUDGE 0.4
 #    else
 #        if defined(RP2040)

+ 26 - 0
platforms/chibios/drivers/ws2812_pwm.c

@@ -40,6 +40,9 @@
 #if (STM32_DMA_SUPPORTS_DMAMUX == TRUE) && !defined(WS2812_PWM_DMAMUX_ID)
 #    error "please consult your MCU's datasheet and specify in your config.h: #define WS2812_PWM_DMAMUX_ID STM32_DMAMUX1_TIM?_UP"
 #endif
+#if (AT32_DMA_SUPPORTS_DMAMUX == TRUE) && !defined(WS2812_PWM_DMAMUX_CHANNEL) && !defined(WS2812_PWM_DMAMUX_ID)
+#    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"
+#endif
 
 /* Summarize https://www.st.com/resource/en/application_note/an4013-stm32-crossseries-timer-overview-stmicroelectronics.pdf to
  * figure out if we are using a 32bit timer. This is needed to setup the DMA controller correctly.
@@ -269,6 +272,14 @@ typedef uint32_t ws2812_buffer_t;
 #        define WS2812_PWM_DMA_PERIPHERAL_WIDTH STM32_DMA_CR_PSIZE_HWORD
 typedef uint16_t ws2812_buffer_t;
 #    endif
+#elif defined(AT32F415)
+#    define WS2812_PWM_DMA_MEMORY_WIDTH AT32_DMA_CCTRL_MWIDTH_BYTE
+#    if defined(WS2812_PWM_TIMER_32BIT)
+#        define WS2812_PWM_DMA_PERIPHERAL_WIDTH AT32_DMA_CCTRL_PWIDTH_WORD
+#    else
+#        define WS2812_PWM_DMA_PERIPHERAL_WIDTH AT32_DMA_CCTRL_PWIDTH_HWORD
+#    endif
+typedef uint8_t ws2812_buffer_t;
 #else
 #    define WS2812_PWM_DMA_MEMORY_WIDTH STM32_DMA_CR_MSIZE_BYTE
 #    if defined(WS2812_PWM_TIMER_32BIT)
@@ -309,8 +320,13 @@ void ws2812_init(void) {
                 [0 ... 3]                = {.mode = PWM_OUTPUT_DISABLED, .callback = NULL},    // Channels default to disabled
                 [WS2812_PWM_CHANNEL - 1] = {.mode = WS2812_PWM_OUTPUT_MODE, .callback = NULL}, // Turn on the channel we care about
             },
+#if defined(AT32F415)
+        .ctrl2 = 0,
+        .iden  = AT32_TMR_IDEN_OVFDEN, // DMA on update event for next period
+#else
         .cr2  = 0,
         .dier = TIM_DIER_UDE, // DMA on update event for next period
+#endif
     };
     //#pragma GCC diagnostic pop  // Restore command-line warning options
 
@@ -321,6 +337,11 @@ void ws2812_init(void) {
     dmaStreamSetSource(WS2812_PWM_DMA_STREAM, ws2812_frame_buffer);
     dmaStreamSetDestination(WS2812_PWM_DMA_STREAM, &(WS2812_PWM_DRIVER.tim->CCR[WS2812_PWM_CHANNEL - 1])); // Ziel ist der An-Zeit im Cap-Comp-Register
     dmaStreamSetMode(WS2812_PWM_DMA_STREAM, WB32_DMA_CHCFG_HWHIF(WS2812_PWM_DMA_CHANNEL) | WB32_DMA_CHCFG_DIR_M2P | WB32_DMA_CHCFG_PSIZE_WORD | WB32_DMA_CHCFG_MSIZE_WORD | WB32_DMA_CHCFG_MINC | WB32_DMA_CHCFG_CIRC | WB32_DMA_CHCFG_TCIE | WB32_DMA_CHCFG_PL(3));
+#elif defined(AT32F415)
+    dmaStreamAlloc(WS2812_PWM_DMA_STREAM - AT32_DMA_STREAM(0), 10, NULL, NULL);
+    dmaStreamSetPeripheral(WS2812_PWM_DMA_STREAM, &(WS2812_PWM_DRIVER.tmr->CDT[WS2812_PWM_CHANNEL - 1])); // Ziel ist der An-Zeit im Cap-Comp-Register
+    dmaStreamSetMemory0(WS2812_PWM_DMA_STREAM, ws2812_frame_buffer);
+    dmaStreamSetMode(WS2812_PWM_DMA_STREAM, AT32_DMA_CCTRL_DTD_M2P | WS2812_PWM_DMA_PERIPHERAL_WIDTH | WS2812_PWM_DMA_MEMORY_WIDTH | AT32_DMA_CCTRL_MINCM | AT32_DMA_CCTRL_LM | AT32_DMA_CCTRL_CHPL(3));
 #else
     dmaStreamAlloc(WS2812_PWM_DMA_STREAM - STM32_DMA_STREAM(0), 10, NULL, NULL);
     dmaStreamSetPeripheral(WS2812_PWM_DMA_STREAM, &(WS2812_PWM_DRIVER.tim->CCR[WS2812_PWM_CHANNEL - 1])); // Ziel ist der An-Zeit im Cap-Comp-Register
@@ -335,6 +356,11 @@ void ws2812_init(void) {
     dmaSetRequestSource(WS2812_PWM_DMA_STREAM, WS2812_PWM_DMAMUX_ID);
 #endif
 
+#if (AT32_DMA_SUPPORTS_DMAMUX == TRUE)
+    // If the MCU has a DMAMUX we need to assign the correct resource
+    dmaSetRequestSource(WS2812_PWM_DMA_STREAM, WS2812_PWM_DMAMUX_CHANNEL, WS2812_PWM_DMAMUX_ID);
+#endif
+
     // Start DMA
     dmaStreamEnable(WS2812_PWM_DMA_STREAM);
 

+ 53 - 18
platforms/chibios/drivers/ws2812_spi.c

@@ -40,26 +40,53 @@
 
 // Define SPI config speed
 // baudrate should target 3.2MHz
+#if defined(AT32F415)
+#    if WS2812_SPI_DIVISOR == 2
+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (0)
+#    elif WS2812_SPI_DIVISOR == 4
+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0)
+#    elif WS2812_SPI_DIVISOR == 8
+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1)
+#    elif WS2812_SPI_DIVISOR == 16 // default
+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0)
+#    elif WS2812_SPI_DIVISOR == 32
+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2)
+#    elif WS2812_SPI_DIVISOR == 64
+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_0)
+#    elif WS2812_SPI_DIVISOR == 128
+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1)
+#    elif WS2812_SPI_DIVISOR == 256
+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0)
+#    elif WS2812_SPI_DIVISOR == 512
+#        define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3)
+#    elif WS2812_SPI_DIVISOR == 1024
+#        define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3)
+#        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0)
+#    else
+#        error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
+#    endif
+#else
 // F072 fpclk = 48MHz
 // 48/16 = 3Mhz
-#if WS2812_SPI_DIVISOR == 2
-#    define WS2812_SPI_DIVISOR_CR1_BR_X (0)
-#elif WS2812_SPI_DIVISOR == 4
-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_0)
-#elif WS2812_SPI_DIVISOR == 8
-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1)
-#elif WS2812_SPI_DIVISOR == 16 // default
-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1 | SPI_CR1_BR_0)
-#elif WS2812_SPI_DIVISOR == 32
-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2)
-#elif WS2812_SPI_DIVISOR == 64
-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_0)
-#elif WS2812_SPI_DIVISOR == 128
-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1)
-#elif WS2812_SPI_DIVISOR == 256
-#    define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
-#else
-#    error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
+#    if WS2812_SPI_DIVISOR == 2
+#        define WS2812_SPI_DIVISOR_CR1_BR_X (0)
+#    elif WS2812_SPI_DIVISOR == 4
+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_0)
+#    elif WS2812_SPI_DIVISOR == 8
+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1)
+#    elif WS2812_SPI_DIVISOR == 16 // default
+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1 | SPI_CR1_BR_0)
+#    elif WS2812_SPI_DIVISOR == 32
+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2)
+#    elif WS2812_SPI_DIVISOR == 64
+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_0)
+#    elif WS2812_SPI_DIVISOR == 128
+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1)
+#    elif WS2812_SPI_DIVISOR == 256
+#        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
+#    else
+#        error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
+#    endif
 #endif
 
 // Use SPI circular buffer
@@ -176,8 +203,16 @@ void ws2812_init(void) {
         NULL, // error_cb
         PAL_PORT(WS2812_DI_PIN),
         PAL_PAD(WS2812_DI_PIN),
+#    if defined(AT32F415)
+        WS2812_SPI_DIVISOR_CTRL1_MDIV_X,
+#        if (WS2812_SPI_DIVISOR == 512 || WS2812_SPI_DIVISOR == 1024)
+        WS2812_SPI_DIVISOR_CTRL2_MDIV_X,
+#        endif
+        0
+#    else
         WS2812_SPI_DIVISOR_CR1_BR_X,
         0
+#    endif
 #endif
     };
 

+ 2 - 0
platforms/chibios/flash.mk

@@ -113,6 +113,8 @@ else ifeq ($(strip $(MCU_FAMILY)),STM32)
 	$(UNSYNC_OUTPUT_CMD) && $(call EXEC_DFU_UTIL)
 else ifeq ($(strip $(MCU_FAMILY)),WB32)
 	$(UNSYNC_OUTPUT_CMD) && $(call EXEC_WB32_DFU_UPDATER)
+else ifeq ($(strip $(MCU_FAMILY)),AT32)
+	$(UNSYNC_OUTPUT_CMD) && $(call EXEC_DFU_UTIL)
 else ifeq ($(strip $(MCU_FAMILY)),GD32V)
 	$(UNSYNC_OUTPUT_CMD) && $(call EXEC_DFU_UTIL)
 else

+ 34 - 0
platforms/chibios/mcu_selection.mk

@@ -809,6 +809,40 @@ ifneq ($(findstring WB32FQ95, $(MCU)),)
   WB32_BOOTLOADER_ADDRESS ?= 0x1FFFE000
 endif
 
+ifneq ($(findstring AT32F415, $(MCU)),)
+  # Cortex version
+  MCU = cortex-m4
+
+  # ARM version, CORTEX-M0/M1 are 6, CORTEX-M3/M4/M7 are 7
+  ARMV = 7
+
+  ## chip/board settings
+  # - the next two should match the directories in
+  #   <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
+  #   OR
+  #   <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
+  MCU_FAMILY = AT32
+  MCU_SERIES = AT32F415
+
+  # Linker script to use
+  # - it should exist either in <chibios>/os/common/startup/ARMCMx/compilers/GCC/ld/
+  #   or <keyboard_dir>/ld/
+  MCU_LDSCRIPT ?= AT32F415xB
+
+  # Startup code to use
+  #  - it should exist in <chibios>/os/common/startup/ARMCMx/compilers/GCC/mk/
+  MCU_STARTUP ?= at32f415
+
+  # Board: it should exist either in <chibios>/os/hal/boards/,
+  # <keyboard_dir>/boards/, or drivers/boards/
+  BOARD ?= GENERIC_AT32_F415XX
+
+  USE_FPU ?= no
+
+  # Bootloader address for AT32 DFU
+  AT32_BOOTLOADER_ADDRESS ?= 0x1FFFAC00
+endif
+
 ifneq ($(findstring GD32VF103, $(MCU)),)
   # RISC-V
   MCU = risc-v

+ 4 - 0
platforms/chibios/platform.mk

@@ -155,6 +155,10 @@ ifdef WB32_BOOTLOADER_ADDRESS
     OPT_DEFS += -DWB32_BOOTLOADER_ADDRESS=$(WB32_BOOTLOADER_ADDRESS)
 endif
 
+ifdef AT32_BOOTLOADER_ADDRESS
+    OPT_DEFS += -DAT32_BOOTLOADER_ADDRESS=$(AT32_BOOTLOADER_ADDRESS)
+endif
+
 # Work out if we need to set up the include for the bootloader definitions
 ifneq ("$(wildcard $(KEYBOARD_PATH_5)/bootloader_defs.h)","")
     OPT_DEFS += -include $(KEYBOARD_PATH_5)/bootloader_defs.h