summary refs log tree commit diff
path: root/platforms
diff options
context:
space:
mode:
authorJoel Challis <git@zvecr.com>2021-11-19 18:41:02 +0000
committerGitHub <noreply@github.com>2021-11-19 10:41:02 -0800
commit2728603fe6d73e805a539d337fd01051c46ca806 (patch)
tree5c83ffc7efa112da870bd5d8502a9d91d4792f35 /platforms
parent43b9e23bae12916d5161f03700c9bfe46737324b (diff)
Move tmk_core/common/<plat> (#13918)
Diffstat (limited to 'platforms')
-rw-r--r--platforms/arm_atsam/_timer.h19
-rw-r--r--platforms/arm_atsam/_wait.h22
-rw-r--r--platforms/arm_atsam/atomic_util.h37
-rw-r--r--platforms/arm_atsam/bootloader.c57
-rw-r--r--platforms/arm_atsam/eeprom.c184
-rw-r--r--platforms/arm_atsam/gpio.h77
-rw-r--r--platforms/arm_atsam/pin_defs.h84
-rw-r--r--platforms/arm_atsam/platform.c21
-rw-r--r--platforms/arm_atsam/platform.mk67
-rw-r--r--platforms/arm_atsam/platform_deps.h18
-rw-r--r--platforms/arm_atsam/suspend.c77
-rw-r--r--platforms/arm_atsam/timer.c19
-rw-r--r--platforms/atomic_util.h32
-rw-r--r--platforms/avr/_print.h33
-rw-r--r--platforms/avr/_timer.h19
-rw-r--r--platforms/avr/_wait.h49
-rw-r--r--platforms/avr/atomic_util.h22
-rw-r--r--platforms/avr/bootloader.c293
-rw-r--r--platforms/avr/bootloader_size.c21
-rw-r--r--platforms/avr/gpio.h49
-rw-r--r--platforms/avr/pin_defs.h128
-rw-r--r--platforms/avr/platform.c21
-rw-r--r--platforms/avr/platform.mk179
-rw-r--r--platforms/avr/platform_deps.h20
-rw-r--r--platforms/avr/printf.c20
-rw-r--r--platforms/avr/printf.mk2
-rw-r--r--platforms/avr/sleep_led.c124
-rw-r--r--platforms/avr/suspend.c152
-rw-r--r--platforms/avr/timer.c133
-rw-r--r--platforms/avr/timer_avr.h39
-rw-r--r--platforms/avr/xprintf.S498
-rw-r--r--platforms/avr/xprintf.h103
-rw-r--r--platforms/bootloader.h21
-rw-r--r--platforms/chibios/_timer.h19
-rw-r--r--platforms/chibios/_wait.c89
-rw-r--r--platforms/chibios/_wait.h60
-rw-r--r--platforms/chibios/atomic_util.h37
-rw-r--r--platforms/chibios/bootloader.c145
-rw-r--r--platforms/chibios/chibios_config.h78
-rw-r--r--platforms/chibios/eeprom_stm32.c687
-rw-r--r--platforms/chibios/eeprom_stm32.h33
-rw-r--r--platforms/chibios/eeprom_stm32_defs.h74
-rw-r--r--platforms/chibios/eeprom_teensy.c795
-rw-r--r--platforms/chibios/flash_stm32.c208
-rw-r--r--platforms/chibios/flash_stm32.h44
-rw-r--r--platforms/chibios/gd32v_compatibility.h120
-rw-r--r--platforms/chibios/gpio.h50
-rw-r--r--platforms/chibios/pin_defs.h323
-rw-r--r--platforms/chibios/platform.c22
-rw-r--r--platforms/chibios/platform.mk436
-rw-r--r--platforms/chibios/platform_deps.h19
-rw-r--r--platforms/chibios/sleep_led.c192
-rw-r--r--platforms/chibios/suspend.c92
-rw-r--r--platforms/chibios/syscall-fallbacks.c110
-rw-r--r--platforms/chibios/timer.c47
-rw-r--r--platforms/chibios/wait.c41
-rw-r--r--platforms/common.mk12
-rw-r--r--platforms/eeprom.h21
-rw-r--r--platforms/gpio.h22
-rw-r--r--platforms/pin_defs.h23
-rw-r--r--platforms/progmem.h19
-rw-r--r--platforms/sleep_led.h17
-rw-r--r--platforms/suspend.h20
-rw-r--r--platforms/test/_wait.h22
-rw-r--r--platforms/test/bootloader.c19
-rw-r--r--platforms/test/eeprom.c95
-rw-r--r--platforms/test/eeprom_stm32_tests.cpp438
-rw-r--r--platforms/test/flash_stm32_mock.c49
-rw-r--r--platforms/test/hal.h18
-rw-r--r--platforms/test/platform.c21
-rw-r--r--platforms/test/platform.h18
-rw-r--r--platforms/test/platform.mk34
-rw-r--r--platforms/test/platform_deps.h18
-rw-r--r--platforms/test/rules.mk24
-rw-r--r--platforms/test/suspend.c15
-rw-r--r--platforms/test/testlist.mk1
-rw-r--r--platforms/test/timer.c33
-rw-r--r--platforms/timer.h67
-rw-r--r--platforms/wait.h30
79 files changed, 7397 insertions, 0 deletions
diff --git a/platforms/arm_atsam/_timer.h b/platforms/arm_atsam/_timer.h
new file mode 100644
index 0000000000..77402b612a
--- /dev/null
+++ b/platforms/arm_atsam/_timer.h
@@ -0,0 +1,19 @@
+/* Copyright 2021 Simon Arlott
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// The platform is 32-bit, so prefer 32-bit timers to avoid overflow
+#define FAST_TIMER_T_SIZE 32
diff --git a/platforms/arm_atsam/_wait.h b/platforms/arm_atsam/_wait.h
new file mode 100644
index 0000000000..41b686b56c
--- /dev/null
+++ b/platforms/arm_atsam/_wait.h
@@ -0,0 +1,22 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "clks.h"
+
+#define wait_ms(ms) CLK_delay_ms(ms)
+#define wait_us(us) CLK_delay_us(us)
+#define waitInputPinDelay()
diff --git a/platforms/arm_atsam/atomic_util.h b/platforms/arm_atsam/atomic_util.h
new file mode 100644
index 0000000000..848542d23a
--- /dev/null
+++ b/platforms/arm_atsam/atomic_util.h
@@ -0,0 +1,37 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "samd51j18a.h"
+
+static __inline__ uint8_t __interrupt_disable__(void) {
+    __disable_irq();
+
+    return 1;
+}
+
+static __inline__ void __interrupt_enable__(const uint8_t *__s) {
+    __enable_irq();
+
+    __asm__ volatile("" ::: "memory");
+    (void)__s;
+}
+
+#define ATOMIC_BLOCK(type) for (type, __ToDo = __interrupt_disable__(); __ToDo; __ToDo = 0)
+#define ATOMIC_FORCEON uint8_t sreg_save __attribute__((__cleanup__(__interrupt_enable__))) = 0
+
+#define ATOMIC_BLOCK_RESTORESTATE _Static_assert(0, "ATOMIC_BLOCK_RESTORESTATE not implemented")
+#define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK(ATOMIC_FORCEON)
diff --git a/platforms/arm_atsam/bootloader.c b/platforms/arm_atsam/bootloader.c
new file mode 100644
index 0000000000..9015b00aab
--- /dev/null
+++ b/platforms/arm_atsam/bootloader.c
@@ -0,0 +1,57 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "bootloader.h"
+#include "samd51j18a.h"
+#include "md_bootloader.h"
+
+// Set watchdog timer to reset. Directs the bootloader to stay in programming mode.
+void bootloader_jump(void) {
+#ifdef KEYBOARD_massdrop_ctrl
+    // CTRL keyboards released with bootloader version below must use RAM method. Otherwise use WDT method.
+    uint8_t  ver_ram_method[] = "v2.18Jun 22 2018 17:28:08";  // The version to match (NULL terminated by compiler)
+    uint8_t *ver_check        = ver_ram_method;               // Pointer to version match string for traversal
+    uint8_t *ver_rom          = (uint8_t *)0x21A0;            // Pointer to address in ROM where this specific bootloader version would exist
+
+    while (*ver_check && *ver_rom == *ver_check) {  // While there are check version characters to match and bootloader's version matches check's version
+        ver_check++;                                // Move check version pointer to next character
+        ver_rom++;                                  // Move ROM version pointer to next character
+    }
+
+    if (!*ver_check) {                   // If check version pointer is NULL, all characters have matched
+        *MAGIC_ADDR = BOOTLOADER_MAGIC;  // Set magic number into RAM
+        NVIC_SystemReset();              // Perform system reset
+        while (1) {
+        }  // Won't get here
+    }
+#endif
+
+    WDT->CTRLA.bit.ENABLE = 0;
+    while (WDT->SYNCBUSY.bit.ENABLE) {
+    }
+    while (WDT->CTRLA.bit.ENABLE) {
+    }
+    WDT->CONFIG.bit.WINDOW   = 0;
+    WDT->CONFIG.bit.PER      = 0;
+    WDT->EWCTRL.bit.EWOFFSET = 0;
+    WDT->CTRLA.bit.ENABLE    = 1;
+    while (WDT->SYNCBUSY.bit.ENABLE) {
+    }
+    while (!WDT->CTRLA.bit.ENABLE) {
+    }
+    while (1) {
+    }  // Wait on timeout
+}
diff --git a/platforms/arm_atsam/eeprom.c b/platforms/arm_atsam/eeprom.c
new file mode 100644
index 0000000000..ff1a692623
--- /dev/null
+++ b/platforms/arm_atsam/eeprom.c
@@ -0,0 +1,184 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "eeprom.h"
+#include "debug.h"
+#include "samd51j18a.h"
+#include "core_cm4.h"
+#include "component/nvmctrl.h"
+
+#ifndef EEPROM_SIZE
+#    include "eeconfig.h"
+#    define EEPROM_SIZE (((EECONFIG_SIZE + 3) / 4) * 4)  // based off eeconfig's current usage, aligned to 4-byte sizes, to deal with LTO
+#endif
+
+#ifndef MAX
+#    define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
+#endif
+
+#ifndef BUSY_RETRIES
+#    define BUSY_RETRIES 10000
+#endif
+
+// #define DEBUG_EEPROM_OUTPUT
+
+/*
+ * Debug print utils
+ */
+#if defined(DEBUG_EEPROM_OUTPUT)
+#    define eeprom_printf(fmt, ...) xprintf(fmt, ##__VA_ARGS__);
+#else /* NO_DEBUG */
+#    define eeprom_printf(fmt, ...)
+#endif /* NO_DEBUG */
+
+__attribute__((aligned(4))) static uint8_t buffer[EEPROM_SIZE] = {0};
+volatile uint8_t *                         SmartEEPROM8        = (uint8_t *)SEEPROM_ADDR;
+
+static inline bool eeprom_is_busy(void) {
+    int timeout = BUSY_RETRIES;
+    while (NVMCTRL->SEESTAT.bit.BUSY && timeout-- > 0)
+        ;
+
+    return NVMCTRL->SEESTAT.bit.BUSY;
+}
+
+static uint32_t get_virtual_eeprom_size(void) {
+    // clang-format off
+    static const uint32_t VIRTUAL_EEPROM_MAP[11][8] = {
+    /*          4    8    16    32    64    128    256    512 */
+    /* 0*/ {   0,    0,    0,    0,    0,     0,     0,     0 },
+    /* 1*/ { 512, 1024, 2048, 4096, 4096,  4096,  4096,  4096 },
+    /* 2*/ { 512, 1024, 2048, 4096, 8192,  8192,  8192,  8192 },
+    /* 3*/ { 512, 1024, 2048, 4096, 8192, 16384, 16384, 16384 },
+    /* 4*/ { 512, 1024, 2048, 4096, 8192, 16384, 16384, 16384 },
+    /* 5*/ { 512, 1024, 2048, 4096, 8192, 16384, 32768, 32768 },
+    /* 6*/ { 512, 1024, 2048, 4096, 8192, 16384, 32768, 32768 },
+    /* 7*/ { 512, 1024, 2048, 4096, 8192, 16384, 32768, 32768 },
+    /* 8*/ { 512, 1024, 2048, 4096, 8192, 16384, 32768, 32768 },
+    /* 9*/ { 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536 },
+    /*10*/ { 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536 },
+    };
+    // clang-format on
+
+    static uint32_t virtual_eeprom_size = UINT32_MAX;
+    if (virtual_eeprom_size == UINT32_MAX) {
+        virtual_eeprom_size = VIRTUAL_EEPROM_MAP[NVMCTRL->SEESTAT.bit.PSZ][NVMCTRL->SEESTAT.bit.SBLK];
+    }
+    // eeprom_printf("get_virtual_eeprom_size:: %d:%d:%d\n", NVMCTRL->SEESTAT.bit.PSZ, NVMCTRL->SEESTAT.bit.SBLK, virtual_eeprom_size);
+    return virtual_eeprom_size;
+}
+
+uint8_t eeprom_read_byte(const uint8_t *addr) {
+    uintptr_t offset = (uintptr_t)addr;
+    if (offset >= MAX(EEPROM_SIZE, get_virtual_eeprom_size())) {
+        eeprom_printf("eeprom_read_byte:: out of bounds\n");
+        return 0x0;
+    }
+
+    if (get_virtual_eeprom_size() == 0) {
+        return buffer[offset];
+    }
+
+    if (eeprom_is_busy()) {
+        eeprom_printf("eeprom_write_byte:: timeout\n");
+        return 0x0;
+    }
+
+    return SmartEEPROM8[offset];
+}
+
+void eeprom_write_byte(uint8_t *addr, uint8_t value) {
+    uintptr_t offset = (uintptr_t)addr;
+    if (offset >= MAX(EEPROM_SIZE, get_virtual_eeprom_size())) {
+        eeprom_printf("eeprom_write_byte:: out of bounds\n");
+        return;
+    }
+
+    if (get_virtual_eeprom_size() == 0) {
+        buffer[offset] = value;
+        return;
+    }
+
+    if (eeprom_is_busy()) {
+        eeprom_printf("eeprom_write_byte:: timeout\n");
+        return;
+    }
+
+    SmartEEPROM8[offset] = value;
+}
+
+uint16_t eeprom_read_word(const uint16_t *addr) {
+    const uint8_t *p = (const uint8_t *)addr;
+    return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8);
+}
+
+uint32_t eeprom_read_dword(const uint32_t *addr) {
+    const uint8_t *p = (const uint8_t *)addr;
+    return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8) | (eeprom_read_byte(p + 2) << 16) | (eeprom_read_byte(p + 3) << 24);
+}
+
+void eeprom_read_block(void *buf, const void *addr, size_t len) {
+    const uint8_t *p    = (const uint8_t *)addr;
+    uint8_t *      dest = (uint8_t *)buf;
+    while (len--) {
+        *dest++ = eeprom_read_byte(p++);
+    }
+}
+
+void eeprom_write_word(uint16_t *addr, uint16_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p, value >> 8);
+}
+
+void eeprom_write_dword(uint32_t *addr, uint32_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p++, value >> 8);
+    eeprom_write_byte(p++, value >> 16);
+    eeprom_write_byte(p, value >> 24);
+}
+
+void eeprom_write_block(const void *buf, void *addr, size_t len) {
+    uint8_t *      p   = (uint8_t *)addr;
+    const uint8_t *src = (const uint8_t *)buf;
+    while (len--) {
+        eeprom_write_byte(p++, *src++);
+    }
+}
+
+void eeprom_update_byte(uint8_t *addr, uint8_t value) { eeprom_write_byte(addr, value); }
+
+void eeprom_update_word(uint16_t *addr, uint16_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p, value >> 8);
+}
+
+void eeprom_update_dword(uint32_t *addr, uint32_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p++, value >> 8);
+    eeprom_write_byte(p++, value >> 16);
+    eeprom_write_byte(p, value >> 24);
+}
+
+void eeprom_update_block(const void *buf, void *addr, size_t len) {
+    uint8_t *      p   = (uint8_t *)addr;
+    const uint8_t *src = (const uint8_t *)buf;
+    while (len--) {
+        eeprom_write_byte(p++, *src++);
+    }
+}
diff --git a/platforms/arm_atsam/gpio.h b/platforms/arm_atsam/gpio.h
new file mode 100644
index 0000000000..915ed0ef4f
--- /dev/null
+++ b/platforms/arm_atsam/gpio.h
@@ -0,0 +1,77 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "stdint.h"
+#include "samd51j18a.h"
+
+#include "pin_defs.h"
+
+typedef uint8_t pin_t;
+
+#define SAMD_PORT(pin) ((pin & 0x20) >> 5)
+#define SAMD_PIN(pin) (pin & 0x1f)
+#define SAMD_PIN_MASK(pin) (1 << (pin & 0x1f))
+
+#define setPinInput(pin)                                                                 \
+    do {                                                                                 \
+        PORT->Group[SAMD_PORT(pin)].PINCFG[SAMD_PIN(pin)].bit.INEN = 1;                  \
+        PORT->Group[SAMD_PORT(pin)].DIRCLR.reg                     = SAMD_PIN_MASK(pin); \
+    } while (0)
+
+#define setPinInputHigh(pin)                                                               \
+    do {                                                                                   \
+        PORT->Group[SAMD_PORT(pin)].DIRCLR.reg                       = SAMD_PIN_MASK(pin); \
+        PORT->Group[SAMD_PORT(pin)].OUTSET.reg                       = SAMD_PIN_MASK(pin); \
+        PORT->Group[SAMD_PORT(pin)].PINCFG[SAMD_PIN(pin)].bit.INEN   = 1;                  \
+        PORT->Group[SAMD_PORT(pin)].PINCFG[SAMD_PIN(pin)].bit.PULLEN = 1;                  \
+    } while (0)
+
+#define setPinInputLow(pin)                                                                \
+    do {                                                                                   \
+        PORT->Group[SAMD_PORT(pin)].DIRCLR.reg                       = SAMD_PIN_MASK(pin); \
+        PORT->Group[SAMD_PORT(pin)].OUTCLR.reg                       = SAMD_PIN_MASK(pin); \
+        PORT->Group[SAMD_PORT(pin)].PINCFG[SAMD_PIN(pin)].bit.INEN   = 1;                  \
+        PORT->Group[SAMD_PORT(pin)].PINCFG[SAMD_PIN(pin)].bit.PULLEN = 1;                  \
+    } while (0)
+
+#define setPinOutput(pin)                                            \
+    do {                                                             \
+        PORT->Group[SAMD_PORT(pin)].DIRSET.reg = SAMD_PIN_MASK(pin); \
+        PORT->Group[SAMD_PORT(pin)].OUTCLR.reg = SAMD_PIN_MASK(pin); \
+    } while (0)
+
+#define writePinHigh(pin)                                            \
+    do {                                                             \
+        PORT->Group[SAMD_PORT(pin)].OUTSET.reg = SAMD_PIN_MASK(pin); \
+    } while (0)
+
+#define writePinLow(pin)                                             \
+    do {                                                             \
+        PORT->Group[SAMD_PORT(pin)].OUTCLR.reg = SAMD_PIN_MASK(pin); \
+    } while (0)
+
+#define writePin(pin, level)                                             \
+    do {                                                                 \
+        if (level)                                                       \
+            PORT->Group[SAMD_PORT(pin)].OUTSET.reg = SAMD_PIN_MASK(pin); \
+        else                                                             \
+            PORT->Group[SAMD_PORT(pin)].OUTCLR.reg = SAMD_PIN_MASK(pin); \
+    } while (0)
+
+#define readPin(pin) ((PORT->Group[SAMD_PORT(pin)].IN.reg & SAMD_PIN_MASK(pin)) != 0)
+
+#define togglePin(pin) (PORT->Group[SAMD_PORT(pin)].OUTTGL.reg = SAMD_PIN_MASK(pin))
diff --git a/platforms/arm_atsam/pin_defs.h b/platforms/arm_atsam/pin_defs.h
new file mode 100644
index 0000000000..5b50b23910
--- /dev/null
+++ b/platforms/arm_atsam/pin_defs.h
@@ -0,0 +1,84 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "samd51j18a.h"
+
+#define A00 PIN_PA00
+#define A01 PIN_PA01
+#define A02 PIN_PA02
+#define A03 PIN_PA03
+#define A04 PIN_PA04
+#define A05 PIN_PA05
+#define A06 PIN_PA06
+#define A07 PIN_PA07
+#define A08 PIN_PA08
+#define A09 PIN_PA09
+#define A10 PIN_PA10
+#define A11 PIN_PA11
+#define A12 PIN_PA12
+#define A13 PIN_PA13
+#define A14 PIN_PA14
+#define A15 PIN_PA15
+#define A16 PIN_PA16
+#define A17 PIN_PA17
+#define A18 PIN_PA18
+#define A19 PIN_PA19
+#define A20 PIN_PA20
+#define A21 PIN_PA21
+#define A22 PIN_PA22
+#define A23 PIN_PA23
+#define A24 PIN_PA24
+#define A25 PIN_PA25
+#define A26 PIN_PA26
+#define A27 PIN_PA27
+#define A28 PIN_PA28
+#define A29 PIN_PA29
+#define A30 PIN_PA30
+#define A31 PIN_PA31
+
+#define B00 PIN_PB00
+#define B01 PIN_PB01
+#define B02 PIN_PB02
+#define B03 PIN_PB03
+#define B04 PIN_PB04
+#define B05 PIN_PB05
+#define B06 PIN_PB06
+#define B07 PIN_PB07
+#define B08 PIN_PB08
+#define B09 PIN_PB09
+#define B10 PIN_PB10
+#define B11 PIN_PB11
+#define B12 PIN_PB12
+#define B13 PIN_PB13
+#define B14 PIN_PB14
+#define B15 PIN_PB15
+#define B16 PIN_PB16
+#define B17 PIN_PB17
+#define B18 PIN_PB18
+#define B19 PIN_PB19
+#define B20 PIN_PB20
+#define B21 PIN_PB21
+#define B22 PIN_PB22
+#define B23 PIN_PB23
+#define B24 PIN_PB24
+#define B25 PIN_PB25
+#define B26 PIN_PB26
+#define B27 PIN_PB27
+#define B28 PIN_PB28
+#define B29 PIN_PB29
+#define B30 PIN_PB30
+#define B31 PIN_PB31
diff --git a/platforms/arm_atsam/platform.c b/platforms/arm_atsam/platform.c
new file mode 100644
index 0000000000..3e35b4fe4c
--- /dev/null
+++ b/platforms/arm_atsam/platform.c
@@ -0,0 +1,21 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform_deps.h"
+
+void platform_setup(void) {
+    // do nothing
+}
diff --git a/platforms/arm_atsam/platform.mk b/platforms/arm_atsam/platform.mk
new file mode 100644
index 0000000000..b49bf764d7
--- /dev/null
+++ b/platforms/arm_atsam/platform.mk
@@ -0,0 +1,67 @@
+# Hey Emacs, this is a -*- makefile -*-
+##############################################################################
+# Compiler settings
+#
+CC = $(CC_PREFIX) arm-none-eabi-gcc
+OBJCOPY = arm-none-eabi-objcopy
+OBJDUMP = arm-none-eabi-objdump
+SIZE = arm-none-eabi-size
+AR = arm-none-eabi-ar
+NM = arm-none-eabi-nm
+HEX = $(OBJCOPY) -O $(FORMAT) -R .eeprom -R .fuse -R .lock -R .signature
+EEP = $(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O $(FORMAT)
+BIN =
+
+COMMON_VPATH += $(LIB_PATH)/arm_atsam/packs/atmel/SAMD51_DFP/1.0.70/include
+COMMON_VPATH += $(LIB_PATH)/arm_atsam/packs/arm/cmsis/5.0.1/CMSIS/Include
+
+COMPILEFLAGS += -funsigned-char
+COMPILEFLAGS += -funsigned-bitfields
+COMPILEFLAGS += -ffunction-sections
+COMPILEFLAGS += -fshort-enums
+COMPILEFLAGS += -fno-inline-small-functions
+COMPILEFLAGS += -fno-strict-aliasing
+COMPILEFLAGS += -mfloat-abi=hard
+COMPILEFLAGS += -mfpu=fpv4-sp-d16
+COMPILEFLAGS += -mthumb
+
+#ALLOW_WARNINGS = yes
+
+CFLAGS += $(COMPILEFLAGS)
+
+CXXFLAGS += $(COMPILEFLAGS)
+CXXFLAGS += -fno-exceptions -std=c++11
+
+LDFLAGS +=-Wl,--gc-sections
+LDFLAGS += -Wl,-Map="%OUT%%PROJ_NAME%.map"
+LDFLAGS += -Wl,--start-group
+LDFLAGS += -Wl,--end-group
+LDFLAGS += --specs=rdimon.specs
+LDFLAGS += -T$(LIB_PATH)/arm_atsam/packs/atmel/SAMD51_DFP/1.0.70/gcc/gcc/samd51j18a_flash.ld
+
+OPT_DEFS += -DPROTOCOL_ARM_ATSAM
+
+MCUFLAGS = -mcpu=$(MCU)
+MCUFLAGS += -D__$(ARM_ATSAM)__
+
+# List any extra directories to look for libraries here.
+#     Each directory must be seperated by a space.
+#     Use forward slashes for directory separators.
+#     For a directory that has spaces, enclose it in quotes.
+EXTRALIBDIRS =
+
+cpfirmware: warn-arm_atsam
+.INTERMEDIATE: warn-arm_atsam
+warn-arm_atsam: $(FIRMWARE_FORMAT)
+	$(info @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@)
+	$(info This MCU support package has a lack of support from the upstream provider (Massdrop).)
+	$(info There are currently questions about valid licensing, and at this stage it's likely)
+	$(info their boards and supporting code will be removed from QMK in the near future. Please)
+	$(info contact Massdrop for support, and encourage them to align their future board design)
+	$(info choices to gain proper license compatibility with QMK.)
+	$(info @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@)
+
+# Convert hex to bin.
+bin: $(BUILD_DIR)/$(TARGET).hex
+	$(OBJCOPY) -Iihex -Obinary $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
+	$(COPY) $(BUILD_DIR)/$(TARGET).bin $(TARGET).bin;
diff --git a/platforms/arm_atsam/platform_deps.h b/platforms/arm_atsam/platform_deps.h
new file mode 100644
index 0000000000..f296d1d535
--- /dev/null
+++ b/platforms/arm_atsam/platform_deps.h
@@ -0,0 +1,18 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// here just to please the build
diff --git a/platforms/arm_atsam/suspend.c b/platforms/arm_atsam/suspend.c
new file mode 100644
index 0000000000..e51426128d
--- /dev/null
+++ b/platforms/arm_atsam/suspend.c
@@ -0,0 +1,77 @@
+#include "matrix.h"
+#include "i2c_master.h"
+#include "md_rgb_matrix.h"
+#include "suspend.h"
+
+/** \brief Suspend idle
+ *
+ * FIXME: needs doc
+ */
+void suspend_idle(uint8_t time) { /* Note: Not used anywhere currently */
+}
+
+/** \brief Run user level Power down
+ *
+ * FIXME: needs doc
+ */
+__attribute__((weak)) void suspend_power_down_user(void) {}
+
+/** \brief Run keyboard level Power down
+ *
+ * FIXME: needs doc
+ */
+__attribute__((weak)) void suspend_power_down_kb(void) { suspend_power_down_user(); }
+
+/** \brief Suspend power down
+ *
+ * FIXME: needs doc
+ */
+void suspend_power_down(void) {
+#ifdef RGB_MATRIX_ENABLE
+    I2C3733_Control_Set(0);  // Disable LED driver
+#endif
+
+    suspend_power_down_kb();
+}
+
+__attribute__((weak)) void matrix_power_up(void) {}
+__attribute__((weak)) void matrix_power_down(void) {}
+bool                       suspend_wakeup_condition(void) {
+    matrix_power_up();
+    matrix_scan();
+    matrix_power_down();
+    for (uint8_t r = 0; r < MATRIX_ROWS; r++) {
+        if (matrix_get_row(r)) return true;
+    }
+    return false;
+}
+
+/** \brief run user level code immediately after wakeup
+ *
+ * FIXME: needs doc
+ */
+__attribute__((weak)) void suspend_wakeup_init_user(void) {}
+
+/** \brief run keyboard level code immediately after wakeup
+ *
+ * FIXME: needs doc
+ */
+__attribute__((weak)) void suspend_wakeup_init_kb(void) { suspend_wakeup_init_user(); }
+
+/** \brief run immediately after wakeup
+ *
+ * FIXME: needs doc
+ */
+void suspend_wakeup_init(void) {
+#ifdef RGB_MATRIX_ENABLE
+#    ifdef USE_MASSDROP_CONFIGURATOR
+    if (led_enabled) {
+        I2C3733_Control_Set(1);
+    }
+#    else
+    I2C3733_Control_Set(1);
+#    endif
+#endif
+
+    suspend_wakeup_init_kb();
+}
diff --git a/platforms/arm_atsam/timer.c b/platforms/arm_atsam/timer.c
new file mode 100644
index 0000000000..b835dd5e75
--- /dev/null
+++ b/platforms/arm_atsam/timer.c
@@ -0,0 +1,19 @@
+#include "samd51j18a.h"
+#include "timer.h"
+#include "tmk_core/protocol/arm_atsam/clks.h"
+
+void set_time(uint64_t tset) { ms_clk = tset; }
+
+void timer_init(void) { timer_clear(); }
+
+uint16_t timer_read(void) { return (uint16_t)ms_clk; }
+
+uint32_t timer_read32(void) { return (uint32_t)ms_clk; }
+
+uint64_t timer_read64(void) { return ms_clk; }
+
+uint16_t timer_elapsed(uint16_t tlast) { return TIMER_DIFF_16(timer_read(), tlast); }
+
+uint32_t timer_elapsed32(uint32_t tlast) { return TIMER_DIFF_32(timer_read32(), tlast); }
+
+void timer_clear(void) { set_time(0); }
diff --git a/platforms/atomic_util.h b/platforms/atomic_util.h
new file mode 100644
index 0000000000..2c95302a13
--- /dev/null
+++ b/platforms/atomic_util.h
@@ -0,0 +1,32 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// Macro to help make GPIO and other controls atomic.
+
+#ifndef IGNORE_ATOMIC_BLOCK
+#    if __has_include_next("atomic_util.h")
+#        include_next "atomic_util.h" /* Include the platforms atomic.h */
+#    else
+#        define ATOMIC_BLOCK _Static_assert(0, "ATOMIC_BLOCK not implemented")
+#        define ATOMIC_BLOCK_RESTORESTATE _Static_assert(0, "ATOMIC_BLOCK_RESTORESTATE not implemented")
+#        define ATOMIC_BLOCK_FORCEON _Static_assert(0, "ATOMIC_BLOCK_FORCEON not implemented")
+#    endif
+#else /* do nothing atomic macro */
+#    define ATOMIC_BLOCK for (uint8_t __ToDo = 1; __ToDo; __ToDo = 0)
+#    define ATOMIC_BLOCK_RESTORESTATE ATOMIC_BLOCK
+#    define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK
+#endif
diff --git a/platforms/avr/_print.h b/platforms/avr/_print.h
new file mode 100644
index 0000000000..5c1fdd26d8
--- /dev/null
+++ b/platforms/avr/_print.h
@@ -0,0 +1,33 @@
+/* Copyright 2012 Jun Wako <wakojun@gmail.com> */
+/* Very basic print functions, intended to be used with usb_debug_only.c
+ * http://www.pjrc.com/teensy/
+ * Copyright (c) 2008 PJRC.COM, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#pragma once
+
+#include "avr/xprintf.h"
+
+// Create user & normal print defines
+#define print(s) xputs(PSTR(s))
+#define println(s) xputs(PSTR(s "\r\n"))
+#define uprint(s) xputs(PSTR(s))
+#define uprintln(s) xputs(PSTR(s "\r\n"))
+#define uprintf(fmt, ...) __xprintf(PSTR(fmt), ##__VA_ARGS__)
\ No newline at end of file
diff --git a/platforms/avr/_timer.h b/platforms/avr/_timer.h
new file mode 100644
index 0000000000..b81e0f68b7
--- /dev/null
+++ b/platforms/avr/_timer.h
@@ -0,0 +1,19 @@
+/* Copyright 2021 Simon Arlott
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// The platform is 8-bit, so prefer 16-bit timers to reduce code size
+#define FAST_TIMER_T_SIZE 16
diff --git a/platforms/avr/_wait.h b/platforms/avr/_wait.h
new file mode 100644
index 0000000000..683db6ae57
--- /dev/null
+++ b/platforms/avr/_wait.h
@@ -0,0 +1,49 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <util/delay.h>
+
+#define wait_ms(ms)                             \
+    do {                                        \
+        if (__builtin_constant_p(ms)) {         \
+            _delay_ms(ms);                      \
+        } else {                                \
+            for (uint16_t i = ms; i > 0; i--) { \
+                _delay_ms(1);                   \
+            }                                   \
+        }                                       \
+    } while (0)
+#define wait_us(us)                             \
+    do {                                        \
+        if (__builtin_constant_p(us)) {         \
+            _delay_us(us);                      \
+        } else {                                \
+            for (uint16_t i = us; i > 0; i--) { \
+                _delay_us(1);                   \
+            }                                   \
+        }                                       \
+    } while (0)
+#define wait_cpuclock(n) __builtin_avr_delay_cycles(n)
+#define CPU_CLOCK F_CPU
+
+/* The AVR series GPIOs have a one clock read delay for changes in the digital input signal.
+ * But here's more margin to make it two clocks. */
+#ifndef GPIO_INPUT_PIN_DELAY
+#    define GPIO_INPUT_PIN_DELAY 2
+#endif
+
+#define waitInputPinDelay() wait_cpuclock(GPIO_INPUT_PIN_DELAY)
diff --git a/platforms/avr/atomic_util.h b/platforms/avr/atomic_util.h
new file mode 100644
index 0000000000..7c5d2e7dcc
--- /dev/null
+++ b/platforms/avr/atomic_util.h
@@ -0,0 +1,22 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+/* atomic macro for AVR */
+#include <util/atomic.h>
+
+#define ATOMIC_BLOCK_RESTORESTATE ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
+#define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK(ATOMIC_FORCEON)
diff --git a/platforms/avr/bootloader.c b/platforms/avr/bootloader.c
new file mode 100644
index 0000000000..c0272903b8
--- /dev/null
+++ b/platforms/avr/bootloader.c
@@ -0,0 +1,293 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <avr/io.h>
+#include <avr/eeprom.h>
+#include <avr/interrupt.h>
+#include <avr/wdt.h>
+#include <util/delay.h>
+#include "bootloader.h"
+#include <avr/boot.h>
+
+#ifdef PROTOCOL_LUFA
+#    include <LUFA/Drivers/USB/USB.h>
+#endif
+
+/** \brief Bootloader Size in *bytes*
+ *
+ * AVR Boot section size are defined by setting BOOTSZ fuse in fact. Consult with your MCU datasheet.
+ * Note that 'Word'(2 bytes) size and address are used in datasheet while TMK uses 'Byte'.
+ *
+ * Size of Bootloaders in bytes:
+ *   Atmel DFU loader(ATmega32U4)   4096
+ *   Atmel DFU loader(AT90USB128)   8192
+ *   LUFA bootloader(ATmega32U4)    4096
+ *   Arduino Caterina(ATmega32U4)   4096
+ *   USBaspLoader(ATmega***)        2048
+ *   Teensy   halfKay(ATmega32U4)   512
+ *   Teensy++ halfKay(AT90USB128)   1024
+ *
+ * AVR Boot section is located at the end of Flash memory like the followings.
+ *
+ * byte     Atmel/LUFA(ATMega32u4)          byte     Atmel(AT90SUB128)
+ * 0x0000   +---------------+               0x00000  +---------------+
+ *          |               |                        |               |
+ *          |               |                        |               |
+ *          |  Application  |                        |  Application  |
+ *          |               |                        |               |
+ *          =               =                        =               =
+ *          |               | 32KB-4KB               |               | 128KB-8KB
+ * 0x7000   +---------------+               0x1E000  +---------------+
+ *          |  Bootloader   | 4KB                    |  Bootloader   | 8KB
+ * 0x7FFF   +---------------+               0x1FFFF  +---------------+
+ *
+ *
+ * byte     Teensy(ATMega32u4)              byte     Teensy++(AT90SUB128)
+ * 0x0000   +---------------+               0x00000  +---------------+
+ *          |               |                        |               |
+ *          |               |                        |               |
+ *          |  Application  |                        |  Application  |
+ *          |               |                        |               |
+ *          =               =                        =               =
+ *          |               | 32KB-512B              |               | 128KB-1KB
+ * 0x7E00   +---------------+               0x1FC00  +---------------+
+ *          |  Bootloader   | 512B                   |  Bootloader   | 1KB
+ * 0x7FFF   +---------------+               0x1FFFF  +---------------+
+ */
+#define FLASH_SIZE (FLASHEND + 1L)
+
+#if !defined(BOOTLOADER_SIZE)
+uint16_t bootloader_start;
+#endif
+
+// compatibility between ATMega8 and ATMega88
+#if !defined(MCUCSR)
+#    if defined(MCUSR)
+#        define MCUCSR MCUSR
+#    endif
+#endif
+
+/** \brief Entering the Bootloader via Software
+ *
+ * http://www.fourwalledcubicle.com/files/LUFA/Doc/120730/html/_page__software_bootloader_start.html
+ */
+#define BOOTLOADER_RESET_KEY 0xB007B007
+uint32_t reset_key __attribute__((section(".noinit,\"aw\",@nobits;")));
+
+/** \brief initialize MCU status by watchdog reset
+ *
+ * FIXME: needs doc
+ */
+__attribute__((weak)) void bootloader_jump(void) {
+#if !defined(BOOTLOADER_SIZE)
+    uint8_t high_fuse = boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS);
+
+    if (high_fuse & ~(FUSE_BOOTSZ0 & FUSE_BOOTSZ1)) {
+        bootloader_start = (FLASH_SIZE - 512) >> 1;
+    } else if (high_fuse & ~(FUSE_BOOTSZ1)) {
+        bootloader_start = (FLASH_SIZE - 1024) >> 1;
+    } else if (high_fuse & ~(FUSE_BOOTSZ0)) {
+        bootloader_start = (FLASH_SIZE - 2048) >> 1;
+    } else {
+        bootloader_start = (FLASH_SIZE - 4096) >> 1;
+    }
+#endif
+
+    // Something like this might work, but it compiled larger than the block above
+    // bootloader_start = FLASH_SIZE - (256 << (~high_fuse & 0b110 >> 1));
+
+#if defined(BOOTLOADER_HALFKAY)
+    //  http://www.pjrc.com/teensy/jump_to_bootloader.html
+    cli();
+    // disable watchdog, if enabled (it's not)
+    // disable all peripherals
+    // a shutdown call might make sense here
+    UDCON  = 1;
+    USBCON = (1 << FRZCLK);  // disable USB
+    UCSR1B = 0;
+    _delay_ms(5);
+#    if defined(__AVR_AT90USB162__)  // Teensy 1.0
+    EIMSK  = 0;
+    PCICR  = 0;
+    SPCR   = 0;
+    ACSR   = 0;
+    EECR   = 0;
+    TIMSK0 = 0;
+    TIMSK1 = 0;
+    UCSR1B = 0;
+    DDRB   = 0;
+    DDRC   = 0;
+    DDRD   = 0;
+    PORTB  = 0;
+    PORTC  = 0;
+    PORTD  = 0;
+    asm volatile("jmp 0x3E00");
+#    elif defined(__AVR_ATmega32U4__)   // Teensy 2.0
+    EIMSK  = 0;
+    PCICR  = 0;
+    SPCR   = 0;
+    ACSR   = 0;
+    EECR   = 0;
+    ADCSRA = 0;
+    TIMSK0 = 0;
+    TIMSK1 = 0;
+    TIMSK3 = 0;
+    TIMSK4 = 0;
+    UCSR1B = 0;
+    TWCR   = 0;
+    DDRB   = 0;
+    DDRC   = 0;
+    DDRD   = 0;
+    DDRE   = 0;
+    DDRF   = 0;
+    TWCR   = 0;
+    PORTB  = 0;
+    PORTC  = 0;
+    PORTD  = 0;
+    PORTE  = 0;
+    PORTF  = 0;
+    asm volatile("jmp 0x7E00");
+#    elif defined(__AVR_AT90USB646__)   // Teensy++ 1.0
+    EIMSK  = 0;
+    PCICR  = 0;
+    SPCR   = 0;
+    ACSR   = 0;
+    EECR   = 0;
+    ADCSRA = 0;
+    TIMSK0 = 0;
+    TIMSK1 = 0;
+    TIMSK2 = 0;
+    TIMSK3 = 0;
+    UCSR1B = 0;
+    TWCR   = 0;
+    DDRA   = 0;
+    DDRB   = 0;
+    DDRC   = 0;
+    DDRD   = 0;
+    DDRE   = 0;
+    DDRF   = 0;
+    PORTA  = 0;
+    PORTB  = 0;
+    PORTC  = 0;
+    PORTD  = 0;
+    PORTE  = 0;
+    PORTF  = 0;
+    asm volatile("jmp 0xFC00");
+#    elif defined(__AVR_AT90USB1286__)  // Teensy++ 2.0
+    EIMSK  = 0;
+    PCICR  = 0;
+    SPCR   = 0;
+    ACSR   = 0;
+    EECR   = 0;
+    ADCSRA = 0;
+    TIMSK0 = 0;
+    TIMSK1 = 0;
+    TIMSK2 = 0;
+    TIMSK3 = 0;
+    UCSR1B = 0;
+    TWCR   = 0;
+    DDRA   = 0;
+    DDRB   = 0;
+    DDRC   = 0;
+    DDRD   = 0;
+    DDRE   = 0;
+    DDRF   = 0;
+    PORTA  = 0;
+    PORTB  = 0;
+    PORTC  = 0;
+    PORTD  = 0;
+    PORTE  = 0;
+    PORTF  = 0;
+    asm volatile("jmp 0x1FC00");
+#    endif
+
+#elif defined(BOOTLOADER_CATERINA)
+    // this block may be optional
+    // TODO: figure it out
+
+    uint16_t *const bootKeyPtr = (uint16_t *)0x0800;
+
+    // Value used by Caterina bootloader use to determine whether to run the
+    // sketch or the bootloader programmer.
+    uint16_t bootKey = 0x7777;
+
+    *bootKeyPtr = bootKey;
+
+    // setup watchdog timeout
+    wdt_enable(WDTO_60MS);
+
+    while (1) {
+    }  // wait for watchdog timer to trigger
+
+#elif defined(BOOTLOADER_USBASP)
+    // Taken with permission of Stephan Baerwolf from https://github.com/tinyusbboard/API/blob/master/apipage.c
+    wdt_enable(WDTO_15MS);
+    wdt_reset();
+    asm volatile("cli                    \n\t"
+                 "ldi    r29 ,       %[ramendhi] \n\t"
+                 "ldi    r28 ,       %[ramendlo] \n\t"
+#    if (FLASHEND > 131071)
+                 "ldi    r18 ,       %[bootaddrhi]   \n\t"
+                 "st     Y+,         r18     \n\t"
+#    endif
+                 "ldi    r18 ,       %[bootaddrme]   \n\t"
+                 "st     Y+,     r18     \n\t"
+                 "ldi    r18 ,       %[bootaddrlo]   \n\t"
+                 "st     Y+,     r18     \n\t"
+                 "out    %[mcucsrio],    __zero_reg__    \n\t"
+                 "bootloader_startup_loop%=:         \n\t"
+                 "rjmp bootloader_startup_loop%=     \n\t"
+                 :
+                 : [mcucsrio] "I"(_SFR_IO_ADDR(MCUCSR)),
+#    if (FLASHEND > 131071)
+                   [ramendhi] "M"(((RAMEND - 2) >> 8) & 0xff), [ramendlo] "M"(((RAMEND - 2) >> 0) & 0xff), [bootaddrhi] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 16) & 0xff),
+#    else
+                   [ramendhi] "M"(((RAMEND - 1) >> 8) & 0xff), [ramendlo] "M"(((RAMEND - 1) >> 0) & 0xff),
+#    endif
+                   [bootaddrme] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 8) & 0xff), [bootaddrlo] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 0) & 0xff));
+
+#else  // Assume remaining boards are DFU, even if the flag isn't set
+
+#    if !(defined(__AVR_ATmega32A__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) || defined(__AVR_ATtiny85__))  // no USB - maybe BOOTLOADER_BOOTLOADHID instead though?
+    UDCON  = 1;
+    USBCON = (1 << FRZCLK);  // disable USB
+    UCSR1B = 0;
+    _delay_ms(5);  // 5 seems to work fine
+#    endif
+
+#    ifdef BOOTLOADER_BOOTLOADHID
+    // force bootloadHID to stay in bootloader mode, so that it waits
+    // for a new firmware to be flashed
+    eeprom_write_byte((uint8_t *)1, 0x00);
+#    endif
+
+    // watchdog reset
+    reset_key = BOOTLOADER_RESET_KEY;
+    wdt_enable(WDTO_250MS);
+    for (;;)
+        ;
+#endif
+}
+
+/* this runs before main() */
+void bootloader_jump_after_watchdog_reset(void) __attribute__((used, naked, section(".init3")));
+void bootloader_jump_after_watchdog_reset(void) {
+#ifndef BOOTLOADER_HALFKAY
+    if ((MCUCSR & (1 << WDRF)) && reset_key == BOOTLOADER_RESET_KEY) {
+        reset_key = 0;
+
+        // My custom USBasploader requires this to come up.
+        MCUCSR = 0;
+
+        // Seems like Teensy halfkay loader requires clearing WDRF and disabling watchdog.
+        MCUCSR &= ~(1 << WDRF);
+        wdt_disable();
+
+// This is compled into 'icall', address should be in word unit, not byte.
+#    ifdef BOOTLOADER_SIZE
+        ((void (*)(void))((FLASH_SIZE - BOOTLOADER_SIZE) >> 1))();
+#    else
+        asm("ijmp" ::"z"(bootloader_start));
+#    endif
+    }
+#endif
+}
diff --git a/platforms/avr/bootloader_size.c b/platforms/avr/bootloader_size.c
new file mode 100644
index 0000000000..a029f9321f
--- /dev/null
+++ b/platforms/avr/bootloader_size.c
@@ -0,0 +1,21 @@
+// Copyright 2017 Jack Humbert
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <avr/io.h>
+#include <avr/boot.h>
+
+// clang-format off
+// this is not valid C - it's for computing the size available on the chip
+AVR_SIZE: FLASHEND + 1 - BOOTLOADER_SIZE
diff --git a/platforms/avr/gpio.h b/platforms/avr/gpio.h
new file mode 100644
index 0000000000..e9be68491d
--- /dev/null
+++ b/platforms/avr/gpio.h
@@ -0,0 +1,49 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <avr/io.h>
+#include "pin_defs.h"
+
+typedef uint8_t pin_t;
+
+/* Operation of GPIO by pin. */
+
+#define setPinInput(pin) (DDRx_ADDRESS(pin) &= ~_BV((pin)&0xF), PORTx_ADDRESS(pin) &= ~_BV((pin)&0xF))
+#define setPinInputHigh(pin) (DDRx_ADDRESS(pin) &= ~_BV((pin)&0xF), PORTx_ADDRESS(pin) |= _BV((pin)&0xF))
+#define setPinInputLow(pin) _Static_assert(0, "AVR processors cannot implement an input as pull low")
+#define setPinOutput(pin) (DDRx_ADDRESS(pin) |= _BV((pin)&0xF))
+
+#define writePinHigh(pin) (PORTx_ADDRESS(pin) |= _BV((pin)&0xF))
+#define writePinLow(pin) (PORTx_ADDRESS(pin) &= ~_BV((pin)&0xF))
+#define writePin(pin, level) ((level) ? writePinHigh(pin) : writePinLow(pin))
+
+#define readPin(pin) ((bool)(PINx_ADDRESS(pin) & _BV((pin)&0xF)))
+
+#define togglePin(pin) (PORTx_ADDRESS(pin) ^= _BV((pin)&0xF))
+
+/* Operation of GPIO by port. */
+
+typedef uint8_t port_data_t;
+
+#define readPort(port) PINx_ADDRESS(port)
+
+#define setPortBitInput(port, bit) (DDRx_ADDRESS(port) &= ~_BV((bit)&0xF), PORTx_ADDRESS(port) &= ~_BV((bit)&0xF))
+#define setPortBitInputHigh(port, bit) (DDRx_ADDRESS(port) &= ~_BV((bit)&0xF), PORTx_ADDRESS(port) |= _BV((bit)&0xF))
+#define setPortBitOutput(port, bit) (DDRx_ADDRESS(port) |= _BV((bit)&0xF))
+
+#define writePortBitLow(port, bit) (PORTx_ADDRESS(port) &= ~_BV((bit)&0xF))
+#define writePortBitHigh(port, bit) (PORTx_ADDRESS(port) |= _BV((bit)&0xF))
diff --git a/platforms/avr/pin_defs.h b/platforms/avr/pin_defs.h
new file mode 100644
index 0000000000..23d948041d
--- /dev/null
+++ b/platforms/avr/pin_defs.h
@@ -0,0 +1,128 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <avr/io.h>
+
+#define PORT_SHIFTER 4  // this may be 4 for all AVR chips
+
+// If you want to add more to this list, reference the PINx definitions in these header
+// files: https://github.com/vancegroup-mirrors/avr-libc/tree/master/avr-libc/include/avr
+
+#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)
+#    define ADDRESS_BASE 0x00
+#    define PINB_ADDRESS 0x3
+#    define PINC_ADDRESS 0x6
+#    define PIND_ADDRESS 0x9
+#    define PINE_ADDRESS 0xC
+#    define PINF_ADDRESS 0xF
+#elif defined(__AVR_AT90USB162__) || defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)
+#    define ADDRESS_BASE 0x00
+#    define PINB_ADDRESS 0x3
+#    define PINC_ADDRESS 0x6
+#    define PIND_ADDRESS 0x9
+#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__)
+#    define ADDRESS_BASE 0x00
+#    define PINA_ADDRESS 0x0
+#    define PINB_ADDRESS 0x3
+#    define PINC_ADDRESS 0x6
+#    define PIND_ADDRESS 0x9
+#    define PINE_ADDRESS 0xC
+#    define PINF_ADDRESS 0xF
+#elif defined(__AVR_ATmega32A__)
+#    define ADDRESS_BASE 0x10
+#    define PIND_ADDRESS 0x0
+#    define PINC_ADDRESS 0x3
+#    define PINB_ADDRESS 0x6
+#    define PINA_ADDRESS 0x9
+#elif defined(__AVR_ATtiny85__)
+#    define ADDRESS_BASE 0x10
+#    define PINB_ADDRESS 0x6
+#else
+#    error "Pins are not defined"
+#endif
+
+#define PINDEF(port, pin) ((PIN##port##_ADDRESS << PORT_SHIFTER) | pin)
+
+#define _PIN_ADDRESS(p, offset) _SFR_IO8(ADDRESS_BASE + ((p) >> PORT_SHIFTER) + (offset))
+// Port X Input Pins Address
+#define PINx_ADDRESS(p) _PIN_ADDRESS(p, 0)
+// Port X Data Direction Register,  0:input 1:output
+#define DDRx_ADDRESS(p) _PIN_ADDRESS(p, 1)
+// Port X Data Register
+#define PORTx_ADDRESS(p) _PIN_ADDRESS(p, 2)
+
+/* I/O pins */
+#ifdef PORTA
+#    define A0 PINDEF(A, 0)
+#    define A1 PINDEF(A, 1)
+#    define A2 PINDEF(A, 2)
+#    define A3 PINDEF(A, 3)
+#    define A4 PINDEF(A, 4)
+#    define A5 PINDEF(A, 5)
+#    define A6 PINDEF(A, 6)
+#    define A7 PINDEF(A, 7)
+#endif
+#ifdef PORTB
+#    define B0 PINDEF(B, 0)
+#    define B1 PINDEF(B, 1)
+#    define B2 PINDEF(B, 2)
+#    define B3 PINDEF(B, 3)
+#    define B4 PINDEF(B, 4)
+#    define B5 PINDEF(B, 5)
+#    define B6 PINDEF(B, 6)
+#    define B7 PINDEF(B, 7)
+#endif
+#ifdef PORTC
+#    define C0 PINDEF(C, 0)
+#    define C1 PINDEF(C, 1)
+#    define C2 PINDEF(C, 2)
+#    define C3 PINDEF(C, 3)
+#    define C4 PINDEF(C, 4)
+#    define C5 PINDEF(C, 5)
+#    define C6 PINDEF(C, 6)
+#    define C7 PINDEF(C, 7)
+#endif
+#ifdef PORTD
+#    define D0 PINDEF(D, 0)
+#    define D1 PINDEF(D, 1)
+#    define D2 PINDEF(D, 2)
+#    define D3 PINDEF(D, 3)
+#    define D4 PINDEF(D, 4)
+#    define D5 PINDEF(D, 5)
+#    define D6 PINDEF(D, 6)
+#    define D7 PINDEF(D, 7)
+#endif
+#ifdef PORTE
+#    define E0 PINDEF(E, 0)
+#    define E1 PINDEF(E, 1)
+#    define E2 PINDEF(E, 2)
+#    define E3 PINDEF(E, 3)
+#    define E4 PINDEF(E, 4)
+#    define E5 PINDEF(E, 5)
+#    define E6 PINDEF(E, 6)
+#    define E7 PINDEF(E, 7)
+#endif
+#ifdef PORTF
+#    define F0 PINDEF(F, 0)
+#    define F1 PINDEF(F, 1)
+#    define F2 PINDEF(F, 2)
+#    define F3 PINDEF(F, 3)
+#    define F4 PINDEF(F, 4)
+#    define F5 PINDEF(F, 5)
+#    define F6 PINDEF(F, 6)
+#    define F7 PINDEF(F, 7)
+#endif
diff --git a/platforms/avr/platform.c b/platforms/avr/platform.c
new file mode 100644
index 0000000000..3e35b4fe4c
--- /dev/null
+++ b/platforms/avr/platform.c
@@ -0,0 +1,21 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform_deps.h"
+
+void platform_setup(void) {
+    // do nothing
+}
diff --git a/platforms/avr/platform.mk b/platforms/avr/platform.mk
new file mode 100644
index 0000000000..b45108736f
--- /dev/null
+++ b/platforms/avr/platform.mk
@@ -0,0 +1,179 @@
+# Hey Emacs, this is a -*- makefile -*-
+##############################################################################
+# Compiler settings
+#
+CC = $(CC_PREFIX) avr-gcc
+OBJCOPY = avr-objcopy
+OBJDUMP = avr-objdump
+SIZE = avr-size
+AR = avr-ar
+NM = avr-nm
+HEX = $(OBJCOPY) -O $(FORMAT) -R .eeprom -R .fuse -R .lock -R .signature
+EEP = $(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O $(FORMAT)
+BIN =
+
+COMPILEFLAGS += -funsigned-char
+COMPILEFLAGS += -funsigned-bitfields
+COMPILEFLAGS += -ffunction-sections
+COMPILEFLAGS += -fdata-sections
+COMPILEFLAGS += -fpack-struct
+COMPILEFLAGS += -fshort-enums
+
+ASFLAGS += $(AVR_ASFLAGS)
+
+CFLAGS += $(COMPILEFLAGS) $(AVR_CFLAGS)
+CFLAGS += -fno-inline-small-functions
+CFLAGS += -fno-strict-aliasing
+
+CXXFLAGS += $(COMPILEFLAGS)
+CXXFLAGS += -fno-exceptions -std=c++11
+
+LDFLAGS +=-Wl,--gc-sections
+
+OPT_DEFS += -DF_CPU=$(F_CPU)UL
+
+MCUFLAGS = -mmcu=$(MCU)
+
+# List any extra directories to look for libraries here.
+#     Each directory must be seperated by a space.
+#     Use forward slashes for directory separators.
+#     For a directory that has spaces, enclose it in quotes.
+EXTRALIBDIRS =
+
+
+#---------------- External Memory Options ----------------
+
+# 64 KB of external RAM, starting after internal RAM (ATmega128!),
+# used for variables (.data/.bss) and heap (malloc()).
+#EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff
+
+# 64 KB of external RAM, starting after internal RAM (ATmega128!),
+# only used for heap (malloc()).
+#EXTMEMOPTS = -Wl,--section-start,.data=0x801100,--defsym=__heap_end=0x80ffff
+
+EXTMEMOPTS =
+
+#---------------- Debugging Options ----------------
+
+# Debugging format.
+#     Native formats for AVR-GCC's -g are dwarf-2 [default] or stabs.
+#     AVR Studio 4.10 requires dwarf-2.
+#     AVR [Extended] COFF format requires stabs, plus an avr-objcopy run.
+DEBUG = dwarf-2
+
+# For simulavr only - target MCU frequency.
+DEBUG_MFREQ = $(F_CPU)
+
+# Set the DEBUG_UI to either gdb or insight.
+# DEBUG_UI = gdb
+DEBUG_UI = insight
+
+# Set the debugging back-end to either avarice, simulavr.
+DEBUG_BACKEND = avarice
+#DEBUG_BACKEND = simulavr
+
+# GDB Init Filename.
+GDBINIT_FILE = __avr_gdbinit
+
+# When using avarice settings for the JTAG
+JTAG_DEV = /dev/com1
+
+# Debugging port used to communicate between GDB / avarice / simulavr.
+DEBUG_PORT = 4242
+
+# Debugging host used to communicate between GDB / avarice / simulavr, normally
+#     just set to localhost unless doing some sort of crazy debugging when
+#     avarice is running on a different computer.
+DEBUG_HOST = localhost
+
+#============================================================================
+
+# Convert hex to bin.
+bin: $(BUILD_DIR)/$(TARGET).hex
+ifeq ($(BOOTLOADER),lufa-ms)
+	$(eval BIN_PADDING=$(shell n=`expr 32768 - $(BOOTLOADER_SIZE)` && echo $$(($$n)) || echo 0))
+	$(OBJCOPY) -Iihex -Obinary $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin --pad-to $(BIN_PADDING)
+else
+	$(OBJCOPY) -Iihex -Obinary $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
+endif
+	$(COPY) $(BUILD_DIR)/$(TARGET).bin $(TARGET).bin;
+
+# copy bin to FLASH.bin
+flashbin: bin
+	$(COPY) $(BUILD_DIR)/$(TARGET).bin FLASH.bin;
+
+# Generate avr-gdb config/init file which does the following:
+#     define the reset signal, load the target file, connect to target, and set
+#     a breakpoint at main().
+gdb-config:
+	@$(REMOVE) $(GDBINIT_FILE)
+	@echo define reset >> $(GDBINIT_FILE)
+	@echo SIGNAL SIGHUP >> $(GDBINIT_FILE)
+	@echo end >> $(GDBINIT_FILE)
+	@echo file $(BUILD_DIR)/$(TARGET).elf >> $(GDBINIT_FILE)
+	@echo target remote $(DEBUG_HOST):$(DEBUG_PORT)  >> $(GDBINIT_FILE)
+ifeq ($(DEBUG_BACKEND),simulavr)
+	@echo load  >> $(GDBINIT_FILE)
+endif
+	@echo break main >> $(GDBINIT_FILE)
+
+debug: gdb-config $(BUILD_DIR)/$(TARGET).elf
+ifeq ($(DEBUG_BACKEND), avarice)
+	@echo Starting AVaRICE - Press enter when "waiting to connect" message displays.
+	@$(WINSHELL) /c start avarice --jtag $(JTAG_DEV) --erase --program --file \
+	$(BUILD_DIR)/$(TARGET).elf $(DEBUG_HOST):$(DEBUG_PORT)
+	@$(WINSHELL) /c pause
+
+else
+	@$(WINSHELL) /c start simulavr --gdbserver --device $(MCU) --clock-freq \
+	$(DEBUG_MFREQ) --port $(DEBUG_PORT)
+endif
+	@$(WINSHELL) /c start avr-$(DEBUG_UI) --command=$(GDBINIT_FILE)
+
+
+
+
+# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
+COFFCONVERT = $(OBJCOPY) --debugging
+COFFCONVERT += --change-section-address .data-0x800000
+COFFCONVERT += --change-section-address .bss-0x800000
+COFFCONVERT += --change-section-address .noinit-0x800000
+COFFCONVERT += --change-section-address .eeprom-0x810000
+
+
+
+coff: $(BUILD_DIR)/$(TARGET).elf
+	@$(SECHO) $(MSG_COFF) $(BUILD_DIR)/$(TARGET).cof
+	$(COFFCONVERT) -O coff-avr $< $(BUILD_DIR)/$(TARGET).cof
+
+
+extcoff: $(BUILD_DIR)/$(TARGET).elf
+	@$(SECHO) $(MSG_EXTENDED_COFF) $(BUILD_DIR)/$(TARGET).cof
+	$(COFFCONVERT) -O coff-ext-avr $< $(BUILD_DIR)/$(TARGET).cof
+
+ifeq ($(strip $(BOOTLOADER)), qmk-dfu)
+QMK_BOOTLOADER_TYPE = DFU
+else ifeq ($(strip $(BOOTLOADER)), qmk-hid)
+QMK_BOOTLOADER_TYPE = HID
+endif
+
+bootloader:
+ifeq ($(strip $(QMK_BOOTLOADER_TYPE)),)
+	$(error Please set BOOTLOADER to "qmk-dfu" or "qmk-hid" first!)
+else
+	make -C lib/lufa/Bootloaders/$(QMK_BOOTLOADER_TYPE)/ clean
+	$(QMK_BIN) generate-dfu-header --quiet --keyboard $(KEYBOARD) --output lib/lufa/Bootloaders/$(QMK_BOOTLOADER_TYPE)/Keyboard.h
+	$(eval MAX_SIZE=$(shell n=`$(CC) -E -mmcu=$(MCU) -D__ASSEMBLER__ $(CFLAGS) $(OPT_DEFS) platforms/avr/bootloader_size.c 2> /dev/null | sed -ne 's/\r//;/^#/n;/^AVR_SIZE:/,$${s/^AVR_SIZE: //;p;}'` && echo $$(($$n)) || echo 0))
+	$(eval PROGRAM_SIZE_KB=$(shell n=`expr $(MAX_SIZE) / 1024` && echo $$(($$n)) || echo 0))
+	$(eval BOOT_SECTION_SIZE_KB=$(shell n=`expr  $(BOOTLOADER_SIZE) / 1024` && echo $$(($$n)) || echo 0))
+	$(eval FLASH_SIZE_KB=$(shell n=`expr $(PROGRAM_SIZE_KB) + $(BOOT_SECTION_SIZE_KB)` && echo $$(($$n)) || echo 0))
+	make -C lib/lufa/Bootloaders/$(QMK_BOOTLOADER_TYPE)/ MCU=$(MCU) ARCH=$(ARCH) F_CPU=$(F_CPU) FLASH_SIZE_KB=$(FLASH_SIZE_KB) BOOT_SECTION_SIZE_KB=$(BOOT_SECTION_SIZE_KB)
+	printf "Bootloader$(QMK_BOOTLOADER_TYPE).hex copied to $(TARGET)_bootloader.hex\n"
+	cp lib/lufa/Bootloaders/$(QMK_BOOTLOADER_TYPE)/Bootloader$(QMK_BOOTLOADER_TYPE).hex $(TARGET)_bootloader.hex
+endif
+
+production: $(BUILD_DIR)/$(TARGET).hex bootloader cpfirmware
+	@cat $(BUILD_DIR)/$(TARGET).hex | awk '/^:00000001FF/ == 0' > $(TARGET)_production.hex
+	@cat $(TARGET)_bootloader.hex >> $(TARGET)_production.hex
+	echo "File sizes:"
+	$(SIZE) $(TARGET).hex $(TARGET)_bootloader.hex $(TARGET)_production.hex
diff --git a/platforms/avr/platform_deps.h b/platforms/avr/platform_deps.h
new file mode 100644
index 0000000000..45d9dcebfa
--- /dev/null
+++ b/platforms/avr/platform_deps.h
@@ -0,0 +1,20 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <avr/pgmspace.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
diff --git a/platforms/avr/printf.c b/platforms/avr/printf.c
new file mode 100644
index 0000000000..9ad7a38693
--- /dev/null
+++ b/platforms/avr/printf.c
@@ -0,0 +1,20 @@
+/*
+Copyright 2011 Jun Wako <wakojun@gmail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "xprintf.h"
+#include "sendchar.h"
+
+void print_set_sendchar(sendchar_func_t func) { xdev_out(func); }
diff --git a/platforms/avr/printf.mk b/platforms/avr/printf.mk
new file mode 100644
index 0000000000..060ad88c57
--- /dev/null
+++ b/platforms/avr/printf.mk
@@ -0,0 +1,2 @@
+TMK_COMMON_SRC += $(PLATFORM_COMMON_DIR)/xprintf.S
+TMK_COMMON_SRC += $(PLATFORM_COMMON_DIR)/printf.c
diff --git a/platforms/avr/sleep_led.c b/platforms/avr/sleep_led.c
new file mode 100644
index 0000000000..9a3b52abe5
--- /dev/null
+++ b/platforms/avr/sleep_led.c
@@ -0,0 +1,124 @@
+#include <stdint.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+#include "led.h"
+#include "sleep_led.h"
+
+#ifndef SLEEP_LED_TIMER
+#    define SLEEP_LED_TIMER 1
+#endif
+
+#if SLEEP_LED_TIMER == 1
+#    define TCCRxB TCCR1B
+#    define TIMERx_COMPA_vect TIMER1_COMPA_vect
+#    if defined(__AVR_ATmega32A__)  // This MCU has only one TIMSK register
+#        define TIMSKx TIMSK
+#    else
+#        define TIMSKx TIMSK1
+#    endif
+#    define OCIExA OCIE1A
+#    define OCRxx OCR1A
+#elif SLEEP_LED_TIMER == 3
+#    define TCCRxB TCCR3B
+#    define TIMERx_COMPA_vect TIMER3_COMPA_vect
+#    define TIMSKx TIMSK3
+#    define OCIExA OCIE3A
+#    define OCRxx OCR3A
+#else
+error("Invalid SLEEP_LED_TIMER config")
+#endif
+
+/* Software PWM
+ *  ______           ______           __
+ * |  ON  |___OFF___|  ON  |___OFF___|   ....
+ * |<-------------->|<-------------->|<- ....
+ *     PWM period       PWM period
+ *
+ * 256              interrupts/period[resolution]
+ * 64               periods/second[frequency]
+ * 256*64           interrupts/second
+ * F_CPU/(256*64)   clocks/interrupt
+ */
+#define SLEEP_LED_TIMER_TOP F_CPU / (256 * 64)
+
+/** \brief Sleep LED initialization
+ *
+ * FIXME: needs doc
+ */
+void sleep_led_init(void) {
+    /* Timer1 setup */
+    /* CTC mode */
+    TCCRxB |= _BV(WGM12);
+    /* Clock selelct: clk/1 */
+    TCCRxB |= _BV(CS10);
+    /* Set TOP value */
+    uint8_t sreg = SREG;
+    cli();
+    OCRxx = SLEEP_LED_TIMER_TOP;
+    SREG  = sreg;
+}
+
+/** \brief Sleep LED enable
+ *
+ * FIXME: needs doc
+ */
+void sleep_led_enable(void) {
+    /* Enable Compare Match Interrupt */
+    TIMSKx |= _BV(OCIExA);
+}
+
+/** \brief Sleep LED disable
+ *
+ * FIXME: needs doc
+ */
+void sleep_led_disable(void) {
+    /* Disable Compare Match Interrupt */
+    TIMSKx &= ~_BV(OCIExA);
+}
+
+/** \brief Sleep LED toggle
+ *
+ * FIXME: needs doc
+ */
+void sleep_led_toggle(void) {
+    /* Disable Compare Match Interrupt */
+    TIMSKx ^= _BV(OCIExA);
+}
+
+/** \brief Breathing Sleep LED brighness(PWM On period) table
+ *
+ * (64[steps] * 4[duration]) / 64[PWM periods/s] = 4 second breath cycle
+ *
+ * https://www.wolframalpha.com/input/?i=sin%28x%2F64*pi%29**8+*+255%2C+x%3D0+to+63
+ * (0..63).each {|x| p ((sin(x/64.0*PI)**8)*255).to_i }
+ */
+static const uint8_t breathing_table[64] PROGMEM = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 6, 10, 15, 23, 32, 44, 58, 74, 93, 113, 135, 157, 179, 199, 218, 233, 245, 252, 255, 252, 245, 233, 218, 199, 179, 157, 135, 113, 93, 74, 58, 44, 32, 23, 15, 10, 6, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ISR(TIMERx_COMPA_vect) {
+    /* Software PWM
+     * timer:1111 1111 1111 1111
+     *       \_____/\/ \_______/____  count(0-255)
+     *          \    \______________  duration of step(4)
+     *           \__________________  index of step table(0-63)
+     */
+    static union {
+        uint16_t row;
+        struct {
+            uint8_t count : 8;
+            uint8_t duration : 2;
+            uint8_t index : 6;
+        } pwm;
+    } timer = {.row = 0};
+
+    timer.row++;
+
+    // LED on
+    if (timer.pwm.count == 0) {
+        led_set(1 << USB_LED_CAPS_LOCK);
+    }
+    // LED off
+    if (timer.pwm.count == pgm_read_byte(&breathing_table[timer.pwm.index])) {
+        led_set(0);
+    }
+}
diff --git a/platforms/avr/suspend.c b/platforms/avr/suspend.c
new file mode 100644
index 0000000000..b614746e6c
--- /dev/null
+++ b/platforms/avr/suspend.c
@@ -0,0 +1,152 @@
+#include <stdbool.h>
+#include <avr/sleep.h>
+#include <avr/wdt.h>
+#include <avr/interrupt.h>
+#include "matrix.h"
+#include "action.h"
+#include "suspend.h"
+#include "timer.h"
+#include "led.h"
+#include "host.h"
+
+#ifdef PROTOCOL_LUFA
+#    include "lufa.h"
+#endif
+#ifdef PROTOCOL_VUSB
+#    include "vusb.h"
+#endif
+
+/** \brief Suspend idle
+ *
+ * FIXME: needs doc
+ */
+void suspend_idle(uint8_t time) {
+    cli();
+    set_sleep_mode(SLEEP_MODE_IDLE);
+    sleep_enable();
+    sei();
+    sleep_cpu();
+    sleep_disable();
+}
+
+// TODO: This needs some cleanup
+
+#if !defined(NO_SUSPEND_POWER_DOWN) && defined(WDT_vect)
+
+// clang-format off
+#define wdt_intr_enable(value) \
+__asm__ __volatile__ ( \
+    "in __tmp_reg__,__SREG__" "\n\t" \
+    "cli" "\n\t" \
+    "wdr" "\n\t" \
+    "sts %0,%1" "\n\t" \
+    "out __SREG__,__tmp_reg__" "\n\t" \
+    "sts %0,%2" "\n\t" \
+    : /* no outputs */ \
+    : "M" (_SFR_MEM_ADDR(_WD_CONTROL_REG)), \
+    "r" (_BV(_WD_CHANGE_BIT) | _BV(WDE)), \
+    "r" ((uint8_t) ((value & 0x08 ? _WD_PS3_MASK : 0x00) | _BV(WDIE) | (value & 0x07))) \
+    : "r0" \
+)
+// clang-format on
+
+/** \brief Power down MCU with watchdog timer
+ *
+ * wdto: watchdog timer timeout defined in <avr/wdt.h>
+ *          WDTO_15MS
+ *          WDTO_30MS
+ *          WDTO_60MS
+ *          WDTO_120MS
+ *          WDTO_250MS
+ *          WDTO_500MS
+ *          WDTO_1S
+ *          WDTO_2S
+ *          WDTO_4S
+ *          WDTO_8S
+ */
+static uint8_t wdt_timeout = 0;
+
+/** \brief Power down
+ *
+ * FIXME: needs doc
+ */
+static void power_down(uint8_t wdto) {
+    wdt_timeout = wdto;
+
+    // Watchdog Interrupt Mode
+    wdt_intr_enable(wdto);
+
+    // TODO: more power saving
+    // See PicoPower application note
+    // - I/O port input with pullup
+    // - prescale clock
+    // - BOD disable
+    // - Power Reduction Register PRR
+    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
+    sleep_enable();
+    sei();
+    sleep_cpu();
+    sleep_disable();
+
+    // Disable watchdog after sleep
+    wdt_disable();
+}
+#endif
+
+/** \brief Suspend power down
+ *
+ * FIXME: needs doc
+ */
+void suspend_power_down(void) {
+#ifdef PROTOCOL_LUFA
+    if (USB_DeviceState == DEVICE_STATE_Configured) return;
+#endif
+#ifdef PROTOCOL_VUSB
+    if (!vusb_suspended) return;
+#endif
+
+    suspend_power_down_quantum();
+
+#ifndef NO_SUSPEND_POWER_DOWN
+    // Enter sleep state if possible (ie, the MCU has a watchdog timeout interrupt)
+#    if defined(WDT_vect)
+    power_down(WDTO_15MS);
+#    endif
+#endif
+}
+
+__attribute__((weak)) void matrix_power_up(void) {}
+__attribute__((weak)) void matrix_power_down(void) {}
+bool                       suspend_wakeup_condition(void) {
+    matrix_power_up();
+    matrix_scan();
+    matrix_power_down();
+    for (uint8_t r = 0; r < MATRIX_ROWS; r++) {
+        if (matrix_get_row(r)) return true;
+    }
+    return false;
+}
+
+/** \brief run immediately after wakeup
+ *
+ * FIXME: needs doc
+ */
+void suspend_wakeup_init(void) {
+    // clear keyboard state
+    clear_keyboard();
+
+    suspend_wakeup_init_quantum();
+}
+
+#if !defined(NO_SUSPEND_POWER_DOWN) && defined(WDT_vect)
+/* watchdog timeout */
+ISR(WDT_vect) {
+    // compensate timer for sleep
+    switch (wdt_timeout) {
+        case WDTO_15MS:
+            timer_count += 15 + 2;  // WDTO_15MS + 2(from observation)
+            break;
+        default:;
+    }
+}
+#endif
diff --git a/platforms/avr/timer.c b/platforms/avr/timer.c
new file mode 100644
index 0000000000..c2e6c6e081
--- /dev/null
+++ b/platforms/avr/timer.c
@@ -0,0 +1,133 @@
+/*
+Copyright 2011 Jun Wako <wakojun@gmail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <util/atomic.h>
+#include <stdint.h>
+#include "timer_avr.h"
+#include "timer.h"
+
+// counter resolution 1ms
+// NOTE: union { uint32_t timer32; struct { uint16_t dummy; uint16_t timer16; }}
+volatile uint32_t timer_count;
+
+/** \brief timer initialization
+ *
+ * FIXME: needs doc
+ */
+void timer_init(void) {
+#if TIMER_PRESCALER == 1
+    uint8_t prescaler = _BV(CS00);
+#elif TIMER_PRESCALER == 8
+    uint8_t prescaler = _BV(CS01);
+#elif TIMER_PRESCALER == 64
+    uint8_t prescaler = _BV(CS00) | _BV(CS01);
+#elif TIMER_PRESCALER == 256
+    uint8_t prescaler = _BV(CS02);
+#elif TIMER_PRESCALER == 1024
+    uint8_t prescaler = _BV(CS00) | _BV(CS02);
+#else
+#    error "Timer prescaler value is not valid"
+#endif
+
+#if defined(__AVR_ATmega32A__)
+    // Timer0 CTC mode
+    TCCR0 = _BV(WGM01) | prescaler;
+
+    OCR0  = TIMER_RAW_TOP;
+    TIMSK = _BV(OCIE0);
+#elif defined(__AVR_ATtiny85__)
+    // Timer0 CTC mode
+    TCCR0A = _BV(WGM01);
+    TCCR0B = prescaler;
+
+    OCR0A = TIMER_RAW_TOP;
+    TIMSK = _BV(OCIE0A);
+#else
+    // Timer0 CTC mode
+    TCCR0A = _BV(WGM01);
+    TCCR0B = prescaler;
+
+    OCR0A  = TIMER_RAW_TOP;
+    TIMSK0 = _BV(OCIE0A);
+#endif
+}
+
+/** \brief timer clear
+ *
+ * FIXME: needs doc
+ */
+inline void timer_clear(void) {
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { timer_count = 0; }
+}
+
+/** \brief timer read
+ *
+ * FIXME: needs doc
+ */
+inline uint16_t timer_read(void) {
+    uint32_t t;
+
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { t = timer_count; }
+
+    return (t & 0xFFFF);
+}
+
+/** \brief timer read32
+ *
+ * FIXME: needs doc
+ */
+inline uint32_t timer_read32(void) {
+    uint32_t t;
+
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { t = timer_count; }
+
+    return t;
+}
+
+/** \brief timer elapsed
+ *
+ * FIXME: needs doc
+ */
+inline uint16_t timer_elapsed(uint16_t last) {
+    uint32_t t;
+
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { t = timer_count; }
+
+    return TIMER_DIFF_16((t & 0xFFFF), last);
+}
+
+/** \brief timer elapsed32
+ *
+ * FIXME: needs doc
+ */
+inline uint32_t timer_elapsed32(uint32_t last) {
+    uint32_t t;
+
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { t = timer_count; }
+
+    return TIMER_DIFF_32(t, last);
+}
+
+// excecuted once per 1ms.(excess for just timer count?)
+#ifndef __AVR_ATmega32A__
+#    define TIMER_INTERRUPT_VECTOR TIMER0_COMPA_vect
+#else
+#    define TIMER_INTERRUPT_VECTOR TIMER0_COMP_vect
+#endif
+ISR(TIMER_INTERRUPT_VECTOR, ISR_NOBLOCK) { timer_count++; }
diff --git a/platforms/avr/timer_avr.h b/platforms/avr/timer_avr.h
new file mode 100644
index 0000000000..c1b726bd01
--- /dev/null
+++ b/platforms/avr/timer_avr.h
@@ -0,0 +1,39 @@
+/*
+Copyright 2011 Jun Wako <wakojun@gmail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <stdint.h>
+
+#ifndef TIMER_PRESCALER
+#    if F_CPU > 16000000
+#        define TIMER_PRESCALER 256
+#    elif F_CPU > 2000000
+#        define TIMER_PRESCALER 64
+#    elif F_CPU > 250000
+#        define TIMER_PRESCALER 8
+#    else
+#        define TIMER_PRESCALER 1
+#    endif
+#endif
+#define TIMER_RAW_FREQ (F_CPU / TIMER_PRESCALER)
+#define TIMER_RAW TCNT0
+#define TIMER_RAW_TOP (TIMER_RAW_FREQ / 1000)
+
+#if (TIMER_RAW_TOP > 255)
+#    error "Timer0 can't count 1ms at this clock freq. Use larger prescaler."
+#endif
diff --git a/platforms/avr/xprintf.S b/platforms/avr/xprintf.S
new file mode 100644
index 0000000000..c5a414c35c
--- /dev/null
+++ b/platforms/avr/xprintf.S
@@ -0,0 +1,498 @@
+;---------------------------------------------------------------------------;
+; Extended itoa, puts, printf and atoi                     (C)ChaN, 2011
+;---------------------------------------------------------------------------;
+
+				// Base size is 152 bytes
+#define	CR_CRLF		0	// Convert \n to \r\n (+10 bytes)
+#define USE_XPRINTF	1	// Enable xprintf function (+194 bytes)
+#define USE_XSPRINTF	0	// Add xsprintf function (+78 bytes)
+#define USE_XFPRINTF	0	// Add xfprintf function (+54 bytes)
+#define USE_XATOI	0	// Enable xatoi function (+182 bytes)
+
+
+#if FLASHEND > 0x1FFFF
+#error xitoa module does not support 256K devices
+#endif
+
+.nolist
+#include <avr/io.h>	// Include device specific definitions.
+.list
+
+#ifdef SPM_PAGESIZE	// Recent devices have "lpm Rd,Z+" and "movw".
+.macro	_LPMI	reg
+	lpm	\reg, Z+
+.endm
+.macro	_MOVW	dh,dl, sh,sl
+	movw	\dl, \sl
+.endm
+#else			// Earlier devices do not have "lpm Rd,Z+" nor "movw".
+.macro	_LPMI	reg
+	lpm
+	mov	\reg, r0
+	adiw	ZL, 1
+.endm
+.macro	_MOVW	dh,dl, sh,sl
+	mov	\dl, \sl
+	mov	\dh, \sh
+.endm
+#endif
+
+
+
+;---------------------------------------------------------------------------
+; Stub function to forward to user output function
+;
+;Prototype: void xputc (char chr	// a character to be output
+;			);
+;Size: 12/12 words
+
+.section .bss
+.global xfunc_out	; xfunc_out must be initialized before using this module.
+xfunc_out:	.ds.w	1
+.section .text
+
+
+.func xputc
+.global xputc
+xputc:
+#if CR_CRLF
+	cpi	r24, 10		;LF --> CRLF
+	brne	1f		;
+	ldi	r24, 13		;
+	rcall	1f		;
+	ldi	r24, 10		;/
+1:
+#endif
+	push	ZH
+	push	ZL
+	lds	ZL, xfunc_out+0	;Pointer to the registered output function.
+	lds	ZH, xfunc_out+1	;/
+	sbiw	ZL, 0		;Skip if null
+	breq	2f		;/
+	icall
+2:	pop	ZL
+	pop	ZH
+	ret
+.endfunc
+
+
+
+;---------------------------------------------------------------------------
+; Direct ROM string output
+;
+;Prototype: void xputs (const char *str_p // rom string to be output
+;			);
+
+.func xputs
+.global xputs
+xputs:
+	_MOVW	ZH,ZL, r25,r24	; Z = pointer to rom string
+1:	_LPMI	r24
+	cpi	r24, 0
+	breq	2f
+	rcall	xputc
+	rjmp	1b
+2:	ret
+.endfunc
+
+
+;---------------------------------------------------------------------------
+; Extended direct numeral string output (32bit version)
+;
+;Prototype: void xitoa (long value,	// value to be output
+;                       char radix,	// radix
+;                       char width);	// minimum width
+;
+
+.func xitoa
+.global xitoa
+xitoa:
+				;r25:r22 = value, r20 = base, r18 = digits
+	clr	r31		;r31 = stack level
+	ldi	r30, ' '	;r30 = sign
+	ldi	r19, ' '	;r19 = filler
+	sbrs	r20, 7		;When base indicates signd format and the value
+	rjmp	0f		;is minus, add a '-'.
+	neg	r20		;
+	sbrs	r25, 7		;
+	rjmp	0f		;
+	ldi	r30, '-'	;
+	com	r22		;
+	com	r23		;
+	com	r24		;
+	com	r25		;
+	adc	r22, r1		;
+	adc	r23, r1		;
+	adc	r24, r1		;
+	adc	r25, r1		;/
+0:	sbrs	r18, 7		;When digits indicates zero filled,
+	rjmp	1f		;filler is '0'.
+	neg	r18		;
+	ldi	r19, '0'	;/
+				;----- string conversion loop
+1:	ldi	r21, 32		;r26 = r25:r22 % r20
+	clr	r26		;r25:r22 /= r20
+2:	lsl	r22		;
+	rol	r23		;
+	rol	r24		;
+	rol	r25		;
+	rol	r26		;
+	cp	r26, r20	;
+	brcs	3f		;
+	sub	r26, r20	;
+	inc	r22		;
+3:	dec	r21		;
+	brne	2b		;/
+	cpi	r26, 10		;r26 is a numeral digit '0'-'F'
+	brcs	4f		;
+	subi	r26, -7		;
+4:	subi	r26, -'0'	;/
+	push	r26		;Stack it
+	inc	r31		;/
+	cp	r22, r1		;Repeat until r25:r22 gets zero
+	cpc	r23, r1		;
+	cpc	r24, r1		;
+	cpc	r25, r1		;
+	brne	1b		;/
+
+	cpi	r30, '-'	;Minus sign if needed
+	brne	5f		;
+	push	r30		;
+	inc	r31		;/
+5:	cp	r31, r18	;Filler
+	brcc	6f		;
+	push	r19		;
+	inc	r31		;
+	rjmp	5b		;/
+
+6:	pop	r24		;Flush stacked digits and exit
+	rcall	xputc		;
+	dec	r31		;
+	brne	6b		;/
+
+	ret
+.endfunc
+
+
+
+;---------------------------------------------------------------------------;
+; Formatted string output (16/32bit version)
+;
+;Prototype:
+; void __xprintf (const char *format_p, ...);
+; void __xsprintf(char*, const char *format_p, ...);
+; void __xfprintf(void(*func)(char), const char *format_p, ...);
+;
+
+#if USE_XPRINTF
+
+.func xvprintf
+xvprintf:
+	ld	ZL, Y+		;Z = pointer to format string
+	ld	ZH, Y+		;/
+
+0:	_LPMI	r24		;Get a format char
+	cpi	r24, 0		;End of format string?
+	breq	90f		;/
+	cpi	r24, '%'	;Is format?
+	breq	20f		;/
+1:	rcall	xputc		;Put a normal character
+	rjmp	0b		;/
+90:	ret
+
+20:	ldi	r18, 0		;r18: digits
+	clt			;T: filler
+	_LPMI	r21		;Get flags
+	cpi	r21, '%'	;Is a %?
+	breq	1b		;/
+	cpi	r21, '0'	;Zero filled?
+	brne	23f		;
+	set			;/
+22:	_LPMI	r21		;Get width
+23:	cpi	r21, '9'+1	;
+	brcc	24f		;
+	subi	r21, '0'	;
+	brcs	90b		;
+	lsl	r18		;
+	mov	r0, r18		;
+	lsl	r18		;
+	lsl	r18		;
+	add	r18, r0		;
+	add	r18, r21	;
+	rjmp	22b		;/
+
+24:	brtc	25f		;get value (low word)
+	neg	r18		;
+25:	ld	r24, Y+		;
+	ld	r25, Y+		;/
+	cpi	r21, 'c'	;Is type character?
+	breq	1b		;/
+	cpi	r21, 's'	;Is type RAM string?
+	breq	50f		;/
+	cpi	r21, 'S'	;Is type ROM string?
+	breq	60f		;/
+	_MOVW	r23,r22,r25,r24	;r25:r22 = value
+	clr	r24		;
+	clr	r25		;
+	clt			;/
+	cpi	r21, 'l'	;Is long int?
+	brne	26f		;
+	ld	r24, Y+		;get value (high word)
+	ld	r25, Y+		;
+	set			;
+	_LPMI	r21		;/
+26:	cpi	r21, 'd'	;Is type signed decimal?
+	brne	27f		;/
+	ldi	r20, -10	;
+	brts	40f		;
+	sbrs	r23, 7		;
+	rjmp	40f		;
+	ldi	r24, -1		;
+	ldi	r25, -1		;
+	rjmp	40f		;/
+27:	cpi	r21, 'u'	;Is type unsigned decimal?
+	ldi	r20, 10		;
+	breq	40f		;/
+	cpi	r21, 'X'	;Is type hexdecimal?
+	ldi	r20, 16		;
+	breq	40f		;/
+	cpi	r21, 'b'	;Is type binary?
+	ldi	r20, 2		;
+	breq	40f		;/
+	ret			;abort
+40:	push	ZH		;Output the value
+	push	ZL		;
+	rcall	xitoa		;
+42:	pop	ZL		;
+	pop	ZH		;
+	rjmp	0b		;/
+
+50:	push	ZH		;Put a string on the RAM
+	push	ZL
+	_MOVW	ZH,ZL, r25,r24
+51:	ld	r24, Z+
+	cpi	r24, 0
+	breq	42b
+	rcall	xputc
+	rjmp	51b
+
+60:	push	ZH		;Put a string on the ROM
+	push	ZL
+	rcall	xputs
+	rjmp	42b
+.endfunc
+
+
+.func __xprintf
+.global __xprintf
+__xprintf:
+	push	YH
+	push	YL
+	in	YL, _SFR_IO_ADDR(SPL)
+#ifdef SPH
+	in	YH, _SFR_IO_ADDR(SPH)
+#else
+	clr	YH
+#endif
+	adiw	YL, 5		;Y = pointer to arguments
+	rcall	xvprintf
+	pop	YL
+	pop	YH
+	ret
+.endfunc
+
+
+#if USE_XSPRINTF
+
+.func __xsprintf
+putram:
+	_MOVW	ZH,ZL, r15,r14
+	st	Z+, r24
+	_MOVW	r15,r14, ZH,ZL
+	ret
+.global __xsprintf
+__xsprintf:
+	push	YH
+	push	YL
+	in	YL, _SFR_IO_ADDR(SPL)
+#ifdef SPH
+	in	YH, _SFR_IO_ADDR(SPH)
+#else
+	clr	YH
+#endif
+	adiw	YL, 5		;Y = pointer to arguments
+	lds	ZL, xfunc_out+0	;Save registered output function
+	lds	ZH, xfunc_out+1	;
+	push	ZL		;
+	push	ZH		;/
+	ldi	ZL, lo8(pm(putram));Set local output function
+	ldi	ZH, hi8(pm(putram));
+	sts	xfunc_out+0, ZL	;
+	sts	xfunc_out+1, ZH	;/
+	push	r15		;Initialize pointer to string buffer
+	push	r14		;
+	ld	r14, Y+		;
+	ld	r15, Y+		;/
+	rcall	xvprintf
+	_MOVW	ZH,ZL, r15,r14	;Terminate string
+	st	Z, r1		;
+	pop	r14		;
+	pop	r15		;/
+	pop	ZH		;Restore registered output function
+	pop	ZL		;
+	sts	xfunc_out+0, ZL	;
+	sts	xfunc_out+1, ZH	;/
+	pop	YL
+	pop	YH
+	ret
+.endfunc
+#endif
+
+
+#if USE_XFPRINTF
+.func __xfprintf
+.global __xfprintf
+__xfprintf:
+	push	YH
+	push	YL
+	in	YL, _SFR_IO_ADDR(SPL)
+#ifdef SPH
+	in	YH, _SFR_IO_ADDR(SPH)
+#else
+	clr	YH
+#endif
+	adiw	YL, 5		;Y = pointer to arguments
+	lds	ZL, xfunc_out+0	;Save registered output function
+	lds	ZH, xfunc_out+1	;
+	push	ZL		;
+	push	ZH		;/
+	ld	ZL, Y+		;Set output function
+	ld	ZH, Y+		;
+	sts	xfunc_out+0, ZL	;
+	sts	xfunc_out+1, ZH	;/
+	rcall	xvprintf
+	pop	ZH		;Restore registered output function
+	pop	ZL		;
+	sts	xfunc_out+0, ZL	;
+	sts	xfunc_out+1, ZH	;/
+	pop	YL
+	pop	YH
+	ret
+.endfunc
+#endif
+
+#endif
+
+
+
+;---------------------------------------------------------------------------
+; Extended numeral string input
+;
+;Prototype:
+; char xatoi (           /* 1: Successful, 0: Failed */
+;      const char **str, /* pointer to pointer to source string */
+;      long *res         /* result */
+; );
+;
+
+
+#if USE_XATOI
+.func xatoi
+.global xatoi
+xatoi:
+	_MOVW	r1, r0, r23, r22
+	_MOVW	XH, XL, r25, r24
+	ld	ZL, X+
+	ld	ZH, X+
+	clr	r18		;r21:r18 = 0;
+	clr	r19		;
+	clr	r20		;
+	clr	r21		;/
+	clt			;T = 0;
+
+	ldi	r25, 10		;r25 = 10;
+	rjmp	41f		;/
+40:	adiw	ZL, 1		;Z++;
+41:	ld	r22, Z		;r22 = *Z;
+	cpi	r22, ' '	;if(r22 == ' ') continue
+	breq	40b		;/
+	brcs	70f		;if(r22 < ' ') error;
+	cpi	r22, '-'	;if(r22 == '-') {
+	brne	42f		; T = 1;
+	set			; continue;
+	rjmp	40b		;}
+42:	cpi	r22, '9'+1	;if(r22 > '9') error;
+	brcc	70f		;/
+	cpi	r22, '0'	;if(r22 < '0') error;
+	brcs	70f		;/
+	brne	51f		;if(r22 > '0') cv_start;
+	ldi	r25, 8		;r25 = 8;
+	adiw	ZL, 1		;r22 = *(++Z);
+	ld	r22, Z		;/
+	cpi	r22, ' '+1	;if(r22 <= ' ') exit;
+	brcs	80f		;/
+	cpi	r22, 'b'	;if(r22 == 'b') {
+	brne	43f		; r25 = 2;
+	ldi	r25, 2		; cv_start;
+	rjmp	50f		;}
+43:	cpi	r22, 'x'	;if(r22 != 'x') error;
+	brne	51f		;/
+	ldi	r25, 16		;r25 = 16;
+
+50:	adiw	ZL, 1		;Z++;
+	ld	r22, Z		;r22 = *Z;
+51:	cpi	r22, ' '+1	;if(r22 <= ' ') break;
+	brcs	80f		;/
+	cpi	r22, 'a'	;if(r22 >= 'a') r22 =- 0x20;
+	brcs	52f		;
+	subi	r22, 0x20	;/
+52:	subi	r22, '0'	;if((r22 -= '0') < 0) error;
+	brcs	70f		;/
+	cpi	r22, 10		;if(r22 >= 10) {
+	brcs	53f		; r22 -= 7;
+	subi	r22, 7		; if(r22 < 10)
+	cpi	r22, 10		;
+	brcs	70f		;}
+53:	cp	r22, r25	;if(r22 >= r25) error;
+	brcc	70f		;/
+60:	ldi	r24, 33		;r21:r18 *= r25;
+	sub	r23, r23	;
+61:	brcc	62f		;
+	add	r23, r25	;
+62:	lsr	r23		;
+	ror	r21		;
+	ror	r20		;
+	ror	r19		;
+	ror	r18		;
+	dec	r24		;
+	brne	61b		;/
+	add	r18, r22	;r21:r18 += r22;
+	adc	r19, r24	;
+	adc	r20, r24	;
+	adc	r21, r24	;/
+	rjmp	50b		;repeat
+
+70:	ldi	r24, 0
+	rjmp	81f
+80:	ldi	r24, 1
+81:	brtc	82f
+	clr	r22
+	com	r18
+	com	r19
+	com	r20
+	com	r21
+	adc	r18, r22
+	adc	r19, r22
+	adc	r20, r22
+	adc	r21, r22
+82:	st	-X, ZH
+	st	-X, ZL
+	_MOVW	XH, XL, r1, r0
+	st	X+, r18
+	st	X+, r19
+	st	X+, r20
+	st	X+, r21
+	clr	r1
+	ret
+.endfunc
+#endif
diff --git a/platforms/avr/xprintf.h b/platforms/avr/xprintf.h
new file mode 100644
index 0000000000..80834f1714
--- /dev/null
+++ b/platforms/avr/xprintf.h
@@ -0,0 +1,103 @@
+/*---------------------------------------------------------------------------
+   Extended itoa, puts and printf                    (C)ChaN, 2011
+-----------------------------------------------------------------------------*/
+
+#pragma once
+
+#include <inttypes.h>
+#include <avr/pgmspace.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void (*xfunc_out)(uint8_t);
+#define xdev_out(func) xfunc_out = (void (*)(uint8_t))(func)
+
+/* This is a pointer to user defined output function. It must be initialized
+   before using this modle.
+*/
+
+void xputc(char chr);
+
+/* This is a stub function to forward outputs to user defined output function.
+   All outputs from this module are output via this function.
+*/
+
+/*-----------------------------------------------------------------------------*/
+void xputs(const char *string_p);
+
+/*  The string placed in the ROM is forwarded to xputc() directly.
+ */
+
+/*-----------------------------------------------------------------------------*/
+void xitoa(long value, char radix, char width);
+
+/* Extended itoa().
+
+      value  radix  width   output
+        100     10      6   "   100"
+        100     10     -6   "000100"
+        100     10      0   "100"
+ 4294967295     10      0   "4294967295"
+ 4294967295    -10      0   "-1"
+     655360     16     -8   "000A0000"
+       1024     16      0   "400"
+       0x55      2     -8   "01010101"
+*/
+
+/*-----------------------------------------------------------------------------*/
+#define xprintf(format, ...) __xprintf(PSTR(format), ##__VA_ARGS__)
+#define xsprintf(str, format, ...) __xsprintf(str, PSTR(format), ##__VA_ARGS__)
+#define xfprintf(func, format, ...) __xfprintf(func, PSTR(format), ##__VA_ARGS__)
+
+void __xprintf(const char *format_p, ...); /* Send formatted string to the registered device */
+// void __xsprintf(char*, const char *format_p, ...);	/* Put formatted string to the memory */
+// void __xfprintf(void(*func)(uint8_t), const char *format_p, ...); /* Send formatted string to the specified device */
+
+/* Format string is placed in the ROM. The format flags is similar to printf().
+
+   %[flag][width][size]type
+
+   flag
+     A '0' means filled with '0' when output is shorter than width.
+     ' ' is used in default. This is effective only numeral type.
+   width
+     Minimum width in decimal number. This is effective only numeral type.
+     Default width is zero.
+   size
+     A 'l' means the argument is long(32bit). Default is short(16bit).
+     This is effective only numeral type.
+   type
+     'c' : Character, argument is the value
+     's' : String placed on the RAM, argument is the pointer
+     'S' : String placed on the ROM, argument is the pointer
+     'd' : Signed decimal, argument is the value
+     'u' : Unsigned decimal, argument is the value
+     'X' : Hexdecimal, argument is the value
+     'b' : Binary, argument is the value
+     '%' : '%'
+
+*/
+
+/*-----------------------------------------------------------------------------*/
+char xatoi(char **str, long *ret);
+
+/* Get value of the numeral string.
+
+  str
+    Pointer to pointer to source string
+
+    "0b11001010" binary
+    "0377" octal
+    "0xff800" hexdecimal
+    "1250000" decimal
+    "-25000" decimal
+
+  ret
+    Pointer to return value
+*/
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/platforms/bootloader.h b/platforms/bootloader.h
new file mode 100644
index 0000000000..25ebd95288
--- /dev/null
+++ b/platforms/bootloader.h
@@ -0,0 +1,21 @@
+/*
+Copyright 2011 Jun Wako <wakojun@gmail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+/* give code for your bootloader to come up if needed */
+void bootloader_jump(void);
diff --git a/platforms/chibios/_timer.h b/platforms/chibios/_timer.h
new file mode 100644
index 0000000000..77402b612a
--- /dev/null
+++ b/platforms/chibios/_timer.h
@@ -0,0 +1,19 @@
+/* Copyright 2021 Simon Arlott
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// The platform is 32-bit, so prefer 32-bit timers to avoid overflow
+#define FAST_TIMER_T_SIZE 32
diff --git a/platforms/chibios/_wait.c b/platforms/chibios/_wait.c
new file mode 100644
index 0000000000..1fbea2dd5e
--- /dev/null
+++ b/platforms/chibios/_wait.c
@@ -0,0 +1,89 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __OPTIMIZE__
+#    pragma message "Compiler optimizations disabled; wait_cpuclock() won't work as designed"
+#endif
+
+#define CLOCK_DELAY_NOP8 "nop\n\t nop\n\t nop\n\t nop\n\t   nop\n\t nop\n\t nop\n\t nop\n\t"
+
+__attribute__((always_inline)) static inline void wait_cpuclock(unsigned int n) { /* n: 1..135 */
+    /* The argument n must be a constant expression.
+     * That way, compiler optimization will remove unnecessary code. */
+    if (n < 1) {
+        return;
+    }
+    if (n > 8) {
+        unsigned int n8 = n / 8;
+        n               = n - n8 * 8;
+        switch (n8) {
+            case 16:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 15:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 14:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 13:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 12:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 11:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 10:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 9:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 8:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 7:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 6:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 5:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 4:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 3:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 2:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 1:
+                asm volatile(CLOCK_DELAY_NOP8::: "memory");
+            case 0:
+                break;
+        }
+    }
+    switch (n) {
+        case 8:
+            asm volatile("nop" ::: "memory");
+        case 7:
+            asm volatile("nop" ::: "memory");
+        case 6:
+            asm volatile("nop" ::: "memory");
+        case 5:
+            asm volatile("nop" ::: "memory");
+        case 4:
+            asm volatile("nop" ::: "memory");
+        case 3:
+            asm volatile("nop" ::: "memory");
+        case 2:
+            asm volatile("nop" ::: "memory");
+        case 1:
+            asm volatile("nop" ::: "memory");
+        case 0:
+            break;
+    }
+}
diff --git a/platforms/chibios/_wait.h b/platforms/chibios/_wait.h
new file mode 100644
index 0000000000..2f36c64a2e
--- /dev/null
+++ b/platforms/chibios/_wait.h
@@ -0,0 +1,60 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <ch.h>
+#include <hal.h>
+
+/* chThdSleepX of zero maps to infinite - so we map to a tiny delay to still yield */
+#define wait_ms(ms)                     \
+    do {                                \
+        if (ms != 0) {                  \
+            chThdSleepMilliseconds(ms); \
+        } else {                        \
+            chThdSleepMicroseconds(1);  \
+        }                               \
+    } while (0)
+
+#ifdef WAIT_US_TIMER
+void wait_us(uint16_t duration);
+#else
+#    define wait_us(us)                     \
+        do {                                \
+            if (us != 0) {                  \
+                chThdSleepMicroseconds(us); \
+            } else {                        \
+                chThdSleepMicroseconds(1);  \
+            }                               \
+        } while (0)
+#endif
+
+#include "_wait.c"
+
+/* For GPIOs on ARM-based MCUs, the input pins are sampled by the clock of the bus
+ * to which the GPIO is connected.
+ * The connected buses differ depending on the various series of MCUs.
+ * And since the instruction execution clock of the CPU and the bus clock of GPIO are different,
+ * there is a delay of several clocks to read the change of the input signal.
+ *
+ * Define this delay with the GPIO_INPUT_PIN_DELAY macro.
+ * If the GPIO_INPUT_PIN_DELAY macro is not defined, the following default values will be used.
+ * (A fairly large value of 0.25 microseconds is set.)
+ */
+#ifndef GPIO_INPUT_PIN_DELAY
+#    define GPIO_INPUT_PIN_DELAY (CPU_CLOCK / 1000000L / 4)
+#endif
+
+#define waitInputPinDelay() wait_cpuclock(GPIO_INPUT_PIN_DELAY)
diff --git a/platforms/chibios/atomic_util.h b/platforms/chibios/atomic_util.h
new file mode 100644
index 0000000000..8975045153
--- /dev/null
+++ b/platforms/chibios/atomic_util.h
@@ -0,0 +1,37 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <ch.h>
+
+static __inline__ uint8_t __interrupt_disable__(void) {
+    chSysLock();
+
+    return 1;
+}
+
+static __inline__ void __interrupt_enable__(const uint8_t *__s) {
+    chSysUnlock();
+
+    __asm__ volatile("" ::: "memory");
+    (void)__s;
+}
+
+#define ATOMIC_BLOCK(type) for (type, __ToDo = __interrupt_disable__(); __ToDo; __ToDo = 0)
+#define ATOMIC_FORCEON uint8_t sreg_save __attribute__((__cleanup__(__interrupt_enable__))) = 0
+
+#define ATOMIC_BLOCK_RESTORESTATE _Static_assert(0, "ATOMIC_BLOCK_RESTORESTATE not implemented")
+#define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK(ATOMIC_FORCEON)
diff --git a/platforms/chibios/bootloader.c b/platforms/chibios/bootloader.c
new file mode 100644
index 0000000000..5cadadeeeb
--- /dev/null
+++ b/platforms/chibios/bootloader.c
@@ -0,0 +1,145 @@
+#include "bootloader.h"
+
+#include <ch.h>
+#include <hal.h>
+#include "wait.h"
+
+/* 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(__ram0_end__) - 4)
+
+#ifndef STM32_BOOTLOADER_DUAL_BANK
+#    define STM32_BOOTLOADER_DUAL_BANK FALSE
+#endif
+
+#ifdef BOOTLOADER_TINYUF2
+
+#    define DBL_TAP_MAGIC 0xf01669ef  // From tinyuf2's board_api.h
+
+// defined by linker script
+extern uint32_t _board_dfu_dbl_tap[];
+#    define DBL_TAP_REG _board_dfu_dbl_tap[0]
+
+void bootloader_jump(void) {
+    DBL_TAP_REG = DBL_TAP_MAGIC;
+    NVIC_SystemReset();
+}
+
+void enter_bootloader_mode_if_requested(void) { /* not needed, no two-stage reset */
+}
+
+#elif STM32_BOOTLOADER_DUAL_BANK
+
+// Need pin definitions
+#    include "config_common.h"
+
+#    ifndef STM32_BOOTLOADER_DUAL_BANK_GPIO
+#        error "No STM32_BOOTLOADER_DUAL_BANK_GPIO defined, don't know which pin to toggle"
+#    endif
+
+#    ifndef STM32_BOOTLOADER_DUAL_BANK_POLARITY
+#        define STM32_BOOTLOADER_DUAL_BANK_POLARITY 0
+#    endif
+
+#    ifndef STM32_BOOTLOADER_DUAL_BANK_DELAY
+#        define STM32_BOOTLOADER_DUAL_BANK_DELAY 100000
+#    endif
+
+extern uint32_t __ram0_end__;
+
+__attribute__((weak)) void bootloader_jump(void) {
+    // For STM32 MCUs with dual-bank flash, and we're incapable of jumping to the bootloader. The first valid flash
+    // bank is executed unconditionally after a reset, so it doesn't enter DFU unless BOOT0 is high. Instead, we do
+    // it with hardware...in this case, we pull a GPIO high/low depending on the configuration, connects 3.3V to
+    // BOOT0's RC charging circuit, lets it charge the capacitor, and issue a system reset. See the QMK discord
+    // #hardware channel pins for an example circuit.
+    palSetPadMode(PAL_PORT(STM32_BOOTLOADER_DUAL_BANK_GPIO), PAL_PAD(STM32_BOOTLOADER_DUAL_BANK_GPIO), PAL_MODE_OUTPUT_PUSHPULL);
+#    if STM32_BOOTLOADER_DUAL_BANK_POLARITY
+    palSetPad(PAL_PORT(STM32_BOOTLOADER_DUAL_BANK_GPIO), PAL_PAD(STM32_BOOTLOADER_DUAL_BANK_GPIO));
+#    else
+    palClearPad(PAL_PORT(STM32_BOOTLOADER_DUAL_BANK_GPIO), PAL_PAD(STM32_BOOTLOADER_DUAL_BANK_GPIO));
+#    endif
+
+    // Wait for a while for the capacitor to charge
+    wait_ms(100);
+
+    // Issue a system reset to get the ROM bootloader to execute, with BOOT0 high
+    NVIC_SystemReset();
+}
+
+void enter_bootloader_mode_if_requested(void) {}  // not needed at all, but if anybody attempts to invoke it....
+
+#elif defined(STM32_BOOTLOADER_ADDRESS)  // STM32_BOOTLOADER_DUAL_BANK
+
+extern uint32_t __ram0_end__;
+
+__attribute__((weak)) void bootloader_jump(void) {
+    *MAGIC_ADDR = BOOTLOADER_MAGIC;  // set magic flag => reset handler will jump into boot loader
+    NVIC_SystemReset();
+}
+
+void enter_bootloader_mode_if_requested(void) {
+    unsigned long *check = MAGIC_ADDR;
+    if (*check == BOOTLOADER_MAGIC) {
+        *check = 0;
+        __set_CONTROL(0);
+        __set_MSP(*(__IO uint32_t *)STM32_BOOTLOADER_ADDRESS);
+        __enable_irq();
+
+        typedef void (*BootJump_t)(void);
+        BootJump_t boot_jump = *(BootJump_t *)(STM32_BOOTLOADER_ADDRESS + 4);
+        boot_jump();
+        while (1)
+            ;
+    }
+}
+
+#elif defined(GD32VF103)
+
+#    define DBGMCU_KEY_UNLOCK 0x4B5A6978
+#    define DBGMCU_CMD_RESET 0x1
+
+__IO uint32_t *DBGMCU_KEY = (uint32_t *)DBGMCU_BASE + 0x0CU;
+__IO uint32_t *DBGMCU_CMD = (uint32_t *)DBGMCU_BASE + 0x08U;
+
+__attribute__((weak)) void bootloader_jump(void) {
+    /* The MTIMER unit of the GD32VF103 doesn't have the MSFRST
+     * register to generate a software reset request.
+     * BUT instead two undocumented registers in the debug peripheral
+     * that allow issueing a software reset. WHO would need the MSFRST
+     * register anyway? Source:
+     * https://github.com/esmil/gd32vf103inator/blob/master/include/gd32vf103/dbg.h */
+    *DBGMCU_KEY = DBGMCU_KEY_UNLOCK;
+    *DBGMCU_CMD = DBGMCU_CMD_RESET;
+}
+
+void enter_bootloader_mode_if_requested(void) { /* Jumping to bootloader is not possible from user code. */
+}
+
+#elif defined(KL2x) || defined(K20x) || defined(MK66F18) || defined(MIMXRT1062)  // STM32_BOOTLOADER_DUAL_BANK // STM32_BOOTLOADER_ADDRESS
+/* Kinetis */
+
+#    if defined(BOOTLOADER_KIIBOHD)
+/* Kiibohd Bootloader (MCHCK and Infinity KB) */
+#        define SCB_AIRCR_VECTKEY_WRITEMAGIC 0x05FA0000
+const uint8_t              sys_reset_to_loader_magic[] = "\xff\x00\x7fRESET TO LOADER\x7f\x00\xff";
+__attribute__((weak)) void bootloader_jump(void) {
+    void *volatile vbat = (void *)VBAT;
+    __builtin_memcpy(vbat, (const void *)sys_reset_to_loader_magic, sizeof(sys_reset_to_loader_magic));
+    // request reset
+    SCB->AIRCR = SCB_AIRCR_VECTKEY_WRITEMAGIC | SCB_AIRCR_SYSRESETREQ_Msk;
+}
+
+#    else /* defined(BOOTLOADER_KIIBOHD) */
+/* Default for Kinetis - expecting an ARM Teensy */
+#        include "wait.h"
+__attribute__((weak)) void bootloader_jump(void) {
+    wait_ms(100);
+    __BKPT(0);
+}
+#    endif /* defined(BOOTLOADER_KIIBOHD) */
+
+#else /* neither STM32 nor KINETIS */
+__attribute__((weak)) void bootloader_jump(void) {}
+#endif
diff --git a/platforms/chibios/chibios_config.h b/platforms/chibios/chibios_config.h
new file mode 100644
index 0000000000..ad2f808a95
--- /dev/null
+++ b/platforms/chibios/chibios_config.h
@@ -0,0 +1,78 @@
+/* Copyright 2019
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#ifndef USB_VBUS_PIN
+#    define SPLIT_USB_DETECT  // Force this on when dedicated pin is not used
+#endif
+
+// STM32 compatibility
+#if defined(MCU_STM32)
+#    define CPU_CLOCK STM32_SYSCLK
+
+#    if defined(STM32F1XX)
+#        define USE_GPIOV1
+#        define PAL_MODE_ALTERNATE_OPENDRAIN PAL_MODE_STM32_ALTERNATE_OPENDRAIN
+#        define PAL_MODE_ALTERNATE_PUSHPULL PAL_MODE_STM32_ALTERNATE_PUSHPULL
+#    else
+#        define PAL_OUTPUT_TYPE_OPENDRAIN PAL_STM32_OTYPE_OPENDRAIN
+#        define PAL_OUTPUT_TYPE_PUSHPULL PAL_STM32_OTYPE_PUSHPULL
+#        define PAL_OUTPUT_SPEED_HIGHEST PAL_STM32_OSPEED_HIGHEST
+#        define PAL_PUPDR_FLOATING PAL_STM32_PUPDR_FLOATING
+#    endif
+
+#    if defined(STM32F1XX) || defined(STM32F2XX) || defined(STM32F4XX) || defined(STM32L1XX)
+#        define USE_I2CV1
+#    endif
+#endif
+
+// GD32 compatibility
+#if defined(MCU_GD32V)
+#    define CPU_CLOCK GD32_SYSCLK
+
+#    if defined(GD32VF103)
+#        define USE_GPIOV1
+#        define USE_I2CV1
+#        define PAL_MODE_ALTERNATE_OPENDRAIN PAL_MODE_GD32_ALTERNATE_OPENDRAIN
+#        define PAL_MODE_ALTERNATE_PUSHPULL PAL_MODE_GD32_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
+ * we just redefine the GD32 names. */
+#    include "gd32v_compatibility.h"
+#endif
+
+// teensy compatibility
+#if defined(MCU_KINETIS)
+#    define CPU_CLOCK KINETIS_SYSCLK_FREQUENCY
+
+#    if defined(K20x) || defined(KL2x)
+#        define USE_I2CV1
+#        define USE_I2CV1_CONTRIB  // for some reason a bunch of ChibiOS-Contrib boards only have clock_speed
+#        define USE_GPIOV1
+#    endif
+#endif
+
+#if defined(HT32)
+#    define CPU_CLOCK HT32_CK_SYS_FREQUENCY
+#    define PAL_MODE_ALTERNATE PAL_HT32_MODE_AF
+#    define PAL_OUTPUT_TYPE_OPENDRAIN (PAL_HT32_MODE_OD | PAL_HT32_MODE_DIR)
+#    define PAL_OUTPUT_TYPE_PUSHPULL PAL_HT32_MODE_DIR
+#    define PAL_OUTPUT_SPEED_HIGHEST 0
+#endif
diff --git a/platforms/chibios/eeprom_stm32.c b/platforms/chibios/eeprom_stm32.c
new file mode 100644
index 0000000000..acc6a48516
--- /dev/null
+++ b/platforms/chibios/eeprom_stm32.c
@@ -0,0 +1,687 @@
+/*
+ * This software is experimental and a work in progress.
+ * Under no circumstances should these files be used in relation to any critical system(s).
+ * Use of these files is at your own risk.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * This files are free to use from http://engsta.com/stm32-flash-memory-eeprom-emulator/ by
+ * Artur F.
+ *
+ * Modifications for QMK and STM32F303 by Yiancar
+ * Modifications to add flash wear leveling by Ilya Zhuravlev
+ * Modifications to increase flash density by Don Kjer
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include "util.h"
+#include "debug.h"
+#include "eeprom_stm32.h"
+#include "flash_stm32.h"
+
+/*
+ * We emulate eeprom by writing a snapshot compacted view of eeprom contents,
+ * followed by a write log of any change since that snapshot:
+ *
+ * === SIMULATED EEPROM CONTENTS ===
+ *
+ * ┌─ Compacted ┬ Write Log ─┐
+ * │............│[BYTE][BYTE]│
+ * │FFFF....FFFF│[WRD0][WRD1]│
+ * │FFFFFFFFFFFF│[WORD][NEXT]│
+ * │....FFFFFFFF│[BYTE][WRD0]│
+ * ├────────────┼────────────┤
+ * └──PAGE_BASE │            │
+ *    PAGE_LAST─┴─WRITE_BASE │
+ *                WRITE_LAST ┘
+ *
+ * Compacted contents are the 1's complement of the actual EEPROM contents.
+ * e.g. An 'FFFF' represents a '0000' value.
+ *
+ * The size of the 'compacted' area is equal to the size of the 'emulated' eeprom.
+ * The size of the compacted-area and write log are configurable, and the combined
+ * size of Compacted + WriteLog is a multiple FEE_PAGE_SIZE, which is MCU dependent.
+ * Simulated Eeprom contents are located at the end of available flash space.
+ *
+ * The following configuration defines can be set:
+ *
+ * FEE_PAGE_COUNT   # Total number of pages to use for eeprom simulation (Compact + Write log)
+ * FEE_DENSITY_BYTES   # Size of simulated eeprom. (Defaults to half the space allocated by FEE_PAGE_COUNT)
+ * NOTE: The current implementation does not include page swapping,
+ * and FEE_DENSITY_BYTES will consume that amount of RAM as a cached view of actual EEPROM contents.
+ *
+ * The maximum size of FEE_DENSITY_BYTES is currently 16384. The write log size equals
+ * FEE_PAGE_COUNT * FEE_PAGE_SIZE - FEE_DENSITY_BYTES.
+ * The larger the write log, the less frequently the compacted area needs to be rewritten.
+ *
+ *
+ * *** General Algorithm ***
+ *
+ * During initialization:
+ * The contents of the Compacted-flash area are loaded and the 1's complement value
+ * is cached into memory (e.g. 0xFFFF in Flash represents 0x0000 in cache).
+ * Write log entries are processed until a 0xFFFF is reached.
+ * Each log entry updates a byte or word in the cache.
+ *
+ * During reads:
+ * EEPROM contents are given back directly from the cache in memory.
+ *
+ * During writes:
+ * The contents of the cache is updated first.
+ * If the Compacted-flash area corresponding to the write address is unprogrammed, the 1's complement of the value is written directly into Compacted-flash
+ * Otherwise:
+ * If the write log is full, erase both the Compacted-flash area and the Write log, then write cached contents to the Compacted-flash area.
+ * Otherwise a Write log entry is constructed and appended to the next free position in the Write log.
+ *
+ *
+ * *** Write Log Structure ***
+ *
+ * Write log entries allow for optimized byte writes to addresses below 128. Writing 0 or 1 words are also optimized when word-aligned.
+ *
+ * === WRITE LOG ENTRY FORMATS ===
+ *
+ * ╔═══ Byte-Entry ══╗
+ * ║0XXXXXXX║YYYYYYYY║
+ * ║ └──┬──┘║└──┬───┘║
+ * ║ Address║ Value  ║
+ * ╚════════╩════════╝
+ * 0 <= Address < 0x80 (128)
+ *
+ * ╔ Word-Encoded 0 ╗
+ * ║100XXXXXXXXXXXXX║
+ * ║  │└─────┬─────┘║
+ * ║  │Address >> 1 ║
+ * ║  └── Value: 0  ║
+ * ╚════════════════╝
+ * 0 <= Address <= 0x3FFE (16382)
+ *
+ * ╔ Word-Encoded 1 ╗
+ * ║101XXXXXXXXXXXXX║
+ * ║  │└─────┬─────┘║
+ * ║  │Address >> 1 ║
+ * ║  └── Value: 1  ║
+ * ╚════════════════╝
+ * 0 <= Address <= 0x3FFE (16382)
+ *
+ * ╔═══ Reserved ═══╗
+ * ║110XXXXXXXXXXXXX║
+ * ╚════════════════╝
+ *
+ * ╔═══════════ Word-Next ═══════════╗
+ * ║111XXXXXXXXXXXXX║YYYYYYYYYYYYYYYY║
+ * ║   └─────┬─────┘║└───────┬──────┘║
+ * ║(Address-128)>>1║     ~Value     ║
+ * ╚════════════════╩════════════════╝
+ * (  0 <= Address <  0x0080 (128): Reserved)
+ * 0x80 <= Address <= 0x3FFE (16382)
+ *
+ * Write Log entry ranges:
+ * 0x0000 ... 0x7FFF - Byte-Entry;     address is (Entry & 0x7F00) >> 4; value is (Entry & 0xFF)
+ * 0x8000 ... 0x9FFF - Word-Encoded 0; address is (Entry & 0x1FFF) << 1; value is 0
+ * 0xA000 ... 0xBFFF - Word-Encoded 1; address is (Entry & 0x1FFF) << 1; value is 1
+ * 0xC000 ... 0xDFFF - Reserved
+ * 0xE000 ... 0xFFBF - Word-Next;      address is (Entry & 0x1FFF) << 1 + 0x80; value is ~(Next_Entry)
+ * 0xFFC0 ... 0xFFFE - Reserved
+ * 0xFFFF            - Unprogrammed
+ *
+ */
+
+#include "eeprom_stm32_defs.h"
+#if !defined(FEE_PAGE_SIZE) || !defined(FEE_PAGE_COUNT) || !defined(FEE_MCU_FLASH_SIZE) || !defined(FEE_PAGE_BASE_ADDRESS)
+#    error "not implemented."
+#endif
+
+/* These bits are used for optimizing encoding of bytes, 0 and 1 */
+#define FEE_WORD_ENCODING 0x8000
+#define FEE_VALUE_NEXT 0x6000
+#define FEE_VALUE_RESERVED 0x4000
+#define FEE_VALUE_ENCODED 0x2000
+#define FEE_BYTE_RANGE 0x80
+
+/* Addressable range 16KByte: 0 <-> (0x1FFF << 1) */
+#define FEE_ADDRESS_MAX_SIZE 0x4000
+
+/* Flash word value after erase */
+#define FEE_EMPTY_WORD ((uint16_t)0xFFFF)
+
+/* Size of combined compacted eeprom and write log pages */
+#define FEE_DENSITY_MAX_SIZE (FEE_PAGE_COUNT * FEE_PAGE_SIZE)
+
+#ifndef FEE_MCU_FLASH_SIZE_IGNORE_CHECK /* *TODO: Get rid of this check */
+#    if FEE_DENSITY_MAX_SIZE > (FEE_MCU_FLASH_SIZE * 1024)
+#        pragma message STR(FEE_DENSITY_MAX_SIZE) " > " STR(FEE_MCU_FLASH_SIZE * 1024)
+#        error emulated eeprom: FEE_DENSITY_MAX_SIZE is greater than available flash size
+#    endif
+#endif
+
+/* Size of emulated eeprom */
+#ifdef FEE_DENSITY_BYTES
+#    if (FEE_DENSITY_BYTES > FEE_DENSITY_MAX_SIZE)
+#        pragma message STR(FEE_DENSITY_BYTES) " > " STR(FEE_DENSITY_MAX_SIZE)
+#        error emulated eeprom: FEE_DENSITY_BYTES exceeds FEE_DENSITY_MAX_SIZE
+#    endif
+#    if (FEE_DENSITY_BYTES == FEE_DENSITY_MAX_SIZE)
+#        pragma message STR(FEE_DENSITY_BYTES) " == " STR(FEE_DENSITY_MAX_SIZE)
+#        warning emulated eeprom: FEE_DENSITY_BYTES leaves no room for a write log.  This will greatly increase the flash wear rate!
+#    endif
+#    if FEE_DENSITY_BYTES > FEE_ADDRESS_MAX_SIZE
+#        pragma message STR(FEE_DENSITY_BYTES) " > " STR(FEE_ADDRESS_MAX_SIZE)
+#        error emulated eeprom: FEE_DENSITY_BYTES is greater than FEE_ADDRESS_MAX_SIZE allows
+#    endif
+#    if ((FEE_DENSITY_BYTES) % 2) == 1
+#        error emulated eeprom: FEE_DENSITY_BYTES must be even
+#    endif
+#else
+/* Default to half of allocated space used for emulated eeprom, half for write log */
+#    define FEE_DENSITY_BYTES (FEE_PAGE_COUNT * FEE_PAGE_SIZE / 2)
+#endif
+
+/* Size of write log */
+#ifdef FEE_WRITE_LOG_BYTES
+#    if ((FEE_DENSITY_BYTES + FEE_WRITE_LOG_BYTES) > FEE_DENSITY_MAX_SIZE)
+#        pragma message STR(FEE_DENSITY_BYTES) " + " STR(FEE_WRITE_LOG_BYTES) " > " STR(FEE_DENSITY_MAX_SIZE)
+#        error emulated eeprom: FEE_WRITE_LOG_BYTES exceeds remaining FEE_DENSITY_MAX_SIZE
+#    endif
+#    if ((FEE_WRITE_LOG_BYTES) % 2) == 1
+#        error emulated eeprom: FEE_WRITE_LOG_BYTES must be even
+#    endif
+#else
+/* Default to use all remaining space */
+#    define FEE_WRITE_LOG_BYTES (FEE_PAGE_COUNT * FEE_PAGE_SIZE - FEE_DENSITY_BYTES)
+#endif
+
+/* Start of the emulated eeprom compacted flash area */
+#define FEE_COMPACTED_BASE_ADDRESS FEE_PAGE_BASE_ADDRESS
+/* End of the emulated eeprom compacted flash area */
+#define FEE_COMPACTED_LAST_ADDRESS (FEE_COMPACTED_BASE_ADDRESS + FEE_DENSITY_BYTES)
+/* Start of the emulated eeprom write log */
+#define FEE_WRITE_LOG_BASE_ADDRESS FEE_COMPACTED_LAST_ADDRESS
+/* End of the emulated eeprom write log */
+#define FEE_WRITE_LOG_LAST_ADDRESS (FEE_WRITE_LOG_BASE_ADDRESS + FEE_WRITE_LOG_BYTES)
+
+#if defined(DYNAMIC_KEYMAP_EEPROM_MAX_ADDR) && (DYNAMIC_KEYMAP_EEPROM_MAX_ADDR >= FEE_DENSITY_BYTES)
+#    error emulated eeprom: DYNAMIC_KEYMAP_EEPROM_MAX_ADDR is greater than the FEE_DENSITY_BYTES available
+#endif
+
+/* In-memory contents of emulated eeprom for faster access */
+/* *TODO: Implement page swapping */
+static uint16_t WordBuf[FEE_DENSITY_BYTES / 2];
+static uint8_t *DataBuf = (uint8_t *)WordBuf;
+
+/* Pointer to the first available slot within the write log */
+static uint16_t *empty_slot;
+
+// #define DEBUG_EEPROM_OUTPUT
+
+/*
+ * Debug print utils
+ */
+
+#if defined(DEBUG_EEPROM_OUTPUT)
+
+#    define debug_eeprom debug_enable
+#    define eeprom_println(s) println(s)
+#    define eeprom_printf(fmt, ...) xprintf(fmt, ##__VA_ARGS__);
+
+#else /* NO_DEBUG */
+
+#    define debug_eeprom false
+#    define eeprom_println(s)
+#    define eeprom_printf(fmt, ...)
+
+#endif /* NO_DEBUG */
+
+void print_eeprom(void) {
+#ifndef NO_DEBUG
+    int empty_rows = 0;
+    for (uint16_t i = 0; i < FEE_DENSITY_BYTES; i++) {
+        if (i % 16 == 0) {
+            if (i >= FEE_DENSITY_BYTES - 16) {
+                /* Make sure we display the last row */
+                empty_rows = 0;
+            }
+            /* Check if this row is uninitialized */
+            ++empty_rows;
+            for (uint16_t j = 0; j < 16; j++) {
+                if (DataBuf[i + j]) {
+                    empty_rows = 0;
+                    break;
+                }
+            }
+            if (empty_rows > 1) {
+                /* Repeat empty row */
+                if (empty_rows == 2) {
+                    /* Only display the first repeat empty row */
+                    println("*");
+                }
+                i += 15;
+                continue;
+            }
+            xprintf("%04x", i);
+        }
+        if (i % 8 == 0) print(" ");
+
+        xprintf(" %02x", DataBuf[i]);
+        if ((i + 1) % 16 == 0) {
+            println("");
+        }
+    }
+#endif
+}
+
+uint16_t EEPROM_Init(void) {
+    /* Load emulated eeprom contents from compacted flash into memory */
+    uint16_t *src  = (uint16_t *)FEE_COMPACTED_BASE_ADDRESS;
+    uint16_t *dest = (uint16_t *)DataBuf;
+    for (; src < (uint16_t *)FEE_COMPACTED_LAST_ADDRESS; ++src, ++dest) {
+        *dest = ~*src;
+    }
+
+    if (debug_eeprom) {
+        println("EEPROM_Init Compacted Pages:");
+        print_eeprom();
+        println("EEPROM_Init Write Log:");
+    }
+
+    /* Replay write log */
+    uint16_t *log_addr;
+    for (log_addr = (uint16_t *)FEE_WRITE_LOG_BASE_ADDRESS; log_addr < (uint16_t *)FEE_WRITE_LOG_LAST_ADDRESS; ++log_addr) {
+        uint16_t address = *log_addr;
+        if (address == FEE_EMPTY_WORD) {
+            break;
+        }
+        /* Check for lowest 128-bytes optimization */
+        if (!(address & FEE_WORD_ENCODING)) {
+            uint8_t bvalue = (uint8_t)address;
+            address >>= 8;
+            DataBuf[address] = bvalue;
+            eeprom_printf("DataBuf[0x%02x] = 0x%02x;\n", address, bvalue);
+        } else {
+            uint16_t wvalue;
+            /* Check if value is in next word */
+            if ((address & FEE_VALUE_NEXT) == FEE_VALUE_NEXT) {
+                /* Read value from next word */
+                if (++log_addr >= (uint16_t *)FEE_WRITE_LOG_LAST_ADDRESS) {
+                    break;
+                }
+                wvalue = ~*log_addr;
+                if (!wvalue) {
+                    eeprom_printf("Incomplete write at log_addr: 0x%04x;\n", (uint32_t)log_addr);
+                    /* Possibly incomplete write.  Ignore and continue */
+                    continue;
+                }
+                address &= 0x1FFF;
+                address <<= 1;
+                /* Writes to addresses less than 128 are byte log entries */
+                address += FEE_BYTE_RANGE;
+            } else {
+                /* Reserved for future use */
+                if (address & FEE_VALUE_RESERVED) {
+                    eeprom_printf("Reserved encoded value at log_addr: 0x%04x;\n", (uint32_t)log_addr);
+                    continue;
+                }
+                /* Optimization for 0 or 1 values. */
+                wvalue = (address & FEE_VALUE_ENCODED) >> 13;
+                address &= 0x1FFF;
+                address <<= 1;
+            }
+            if (address < FEE_DENSITY_BYTES) {
+                eeprom_printf("DataBuf[0x%04x] = 0x%04x;\n", address, wvalue);
+                *(uint16_t *)(&DataBuf[address]) = wvalue;
+            } else {
+                eeprom_printf("DataBuf[0x%04x] cannot be set to 0x%04x [BAD ADDRESS]\n", address, wvalue);
+            }
+        }
+    }
+
+    empty_slot = log_addr;
+
+    if (debug_eeprom) {
+        println("EEPROM_Init Final DataBuf:");
+        print_eeprom();
+    }
+
+    return FEE_DENSITY_BYTES;
+}
+
+/* Clear flash contents (doesn't touch in-memory DataBuf) */
+static void eeprom_clear(void) {
+    FLASH_Unlock();
+
+    for (uint16_t page_num = 0; page_num < FEE_PAGE_COUNT; ++page_num) {
+        eeprom_printf("FLASH_ErasePage(0x%04x)\n", (uint32_t)(FEE_PAGE_BASE_ADDRESS + (page_num * FEE_PAGE_SIZE)));
+        FLASH_ErasePage(FEE_PAGE_BASE_ADDRESS + (page_num * FEE_PAGE_SIZE));
+    }
+
+    FLASH_Lock();
+
+    empty_slot = (uint16_t *)FEE_WRITE_LOG_BASE_ADDRESS;
+    eeprom_printf("eeprom_clear empty_slot: 0x%08x\n", (uint32_t)empty_slot);
+}
+
+/* Erase emulated eeprom */
+void EEPROM_Erase(void) {
+    eeprom_println("EEPROM_Erase");
+    /* Erase compacted pages and write log */
+    eeprom_clear();
+    /* re-initialize to reset DataBuf */
+    EEPROM_Init();
+}
+
+/* Compact write log */
+static uint8_t eeprom_compact(void) {
+    /* Erase compacted pages and write log */
+    eeprom_clear();
+
+    FLASH_Unlock();
+
+    FLASH_Status final_status = FLASH_COMPLETE;
+
+    /* Write emulated eeprom contents from memory to compacted flash */
+    uint16_t *src  = (uint16_t *)DataBuf;
+    uintptr_t dest = FEE_COMPACTED_BASE_ADDRESS;
+    uint16_t  value;
+    for (; dest < FEE_COMPACTED_LAST_ADDRESS; ++src, dest += 2) {
+        value = *src;
+        if (value) {
+            eeprom_printf("FLASH_ProgramHalfWord(0x%04x, 0x%04x)\n", (uint32_t)dest, ~value);
+            FLASH_Status status = FLASH_ProgramHalfWord(dest, ~value);
+            if (status != FLASH_COMPLETE) final_status = status;
+        }
+    }
+
+    FLASH_Lock();
+
+    if (debug_eeprom) {
+        println("eeprom_compacted:");
+        print_eeprom();
+    }
+
+    return final_status;
+}
+
+static uint8_t eeprom_write_direct_entry(uint16_t Address) {
+    /* Check if we can just write this directly to the compacted flash area */
+    uintptr_t directAddress = FEE_COMPACTED_BASE_ADDRESS + (Address & 0xFFFE);
+    if (*(uint16_t *)directAddress == FEE_EMPTY_WORD) {
+        /* Write the value directly to the compacted area without a log entry */
+        uint16_t value = ~*(uint16_t *)(&DataBuf[Address & 0xFFFE]);
+        /* Early exit if a write isn't needed */
+        if (value == FEE_EMPTY_WORD) return FLASH_COMPLETE;
+
+        FLASH_Unlock();
+
+        eeprom_printf("FLASH_ProgramHalfWord(0x%08x, 0x%04x) [DIRECT]\n", (uint32_t)directAddress, value);
+        FLASH_Status status = FLASH_ProgramHalfWord(directAddress, value);
+
+        FLASH_Lock();
+        return status;
+    }
+    return 0;
+}
+
+static uint8_t eeprom_write_log_word_entry(uint16_t Address) {
+    FLASH_Status final_status = FLASH_COMPLETE;
+
+    uint16_t value = *(uint16_t *)(&DataBuf[Address]);
+    eeprom_printf("eeprom_write_log_word_entry(0x%04x): 0x%04x\n", Address, value);
+
+    /* MSB signifies the lowest 128-byte optimization is not in effect */
+    uint16_t encoding = FEE_WORD_ENCODING;
+    uint8_t  entry_size;
+    if (value <= 1) {
+        encoding |= value << 13;
+        entry_size = 2;
+    } else {
+        encoding |= FEE_VALUE_NEXT;
+        entry_size = 4;
+        /* Writes to addresses less than 128 are byte log entries */
+        Address -= FEE_BYTE_RANGE;
+    }
+
+    /* if we can't find an empty spot, we must compact emulated eeprom */
+    if (empty_slot > (uint16_t *)(FEE_WRITE_LOG_LAST_ADDRESS - entry_size)) {
+        /* compact the write log into the compacted flash area */
+        return eeprom_compact();
+    }
+
+    /* Word log writes should be word-aligned.  Take back a bit */
+    Address >>= 1;
+    Address |= encoding;
+
+    /* ok we found a place let's write our data */
+    FLASH_Unlock();
+
+    /* address */
+    eeprom_printf("FLASH_ProgramHalfWord(0x%08x, 0x%04x)\n", (uint32_t)empty_slot, Address);
+    final_status = FLASH_ProgramHalfWord((uintptr_t)empty_slot++, Address);
+
+    /* value */
+    if (encoding == (FEE_WORD_ENCODING | FEE_VALUE_NEXT)) {
+        eeprom_printf("FLASH_ProgramHalfWord(0x%08x, 0x%04x)\n", (uint32_t)empty_slot, ~value);
+        FLASH_Status status = FLASH_ProgramHalfWord((uintptr_t)empty_slot++, ~value);
+        if (status != FLASH_COMPLETE) final_status = status;
+    }
+
+    FLASH_Lock();
+
+    return final_status;
+}
+
+static uint8_t eeprom_write_log_byte_entry(uint16_t Address) {
+    eeprom_printf("eeprom_write_log_byte_entry(0x%04x): 0x%02x\n", Address, DataBuf[Address]);
+
+    /* if couldn't find an empty spot, we must compact emulated eeprom */
+    if (empty_slot >= (uint16_t *)FEE_WRITE_LOG_LAST_ADDRESS) {
+        /* compact the write log into the compacted flash area */
+        return eeprom_compact();
+    }
+
+    /* ok we found a place let's write our data */
+    FLASH_Unlock();
+
+    /* Pack address and value into the same word */
+    uint16_t value = (Address << 8) | DataBuf[Address];
+
+    /* write to flash */
+    eeprom_printf("FLASH_ProgramHalfWord(0x%08x, 0x%04x)\n", (uint32_t)empty_slot, value);
+    FLASH_Status status = FLASH_ProgramHalfWord((uintptr_t)empty_slot++, value);
+
+    FLASH_Lock();
+
+    return status;
+}
+
+uint8_t EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte) {
+    /* if the address is out-of-bounds, do nothing */
+    if (Address >= FEE_DENSITY_BYTES) {
+        eeprom_printf("EEPROM_WriteDataByte(0x%04x, 0x%02x) [BAD ADDRESS]\n", Address, DataByte);
+        return FLASH_BAD_ADDRESS;
+    }
+
+    /* if the value is the same, don't bother writing it */
+    if (DataBuf[Address] == DataByte) {
+        eeprom_printf("EEPROM_WriteDataByte(0x%04x, 0x%02x) [SKIP SAME]\n", Address, DataByte);
+        return 0;
+    }
+
+    /* keep DataBuf cache in sync */
+    DataBuf[Address] = DataByte;
+    eeprom_printf("EEPROM_WriteDataByte DataBuf[0x%04x] = 0x%02x\n", Address, DataBuf[Address]);
+
+    /* perform the write into flash memory */
+    /* First, attempt to write directly into the compacted flash area */
+    FLASH_Status status = eeprom_write_direct_entry(Address);
+    if (!status) {
+        /* Otherwise append to the write log */
+        if (Address < FEE_BYTE_RANGE) {
+            status = eeprom_write_log_byte_entry(Address);
+        } else {
+            status = eeprom_write_log_word_entry(Address & 0xFFFE);
+        }
+    }
+    if (status != 0 && status != FLASH_COMPLETE) {
+        eeprom_printf("EEPROM_WriteDataByte [STATUS == %d]\n", status);
+    }
+    return status;
+}
+
+uint8_t EEPROM_WriteDataWord(uint16_t Address, uint16_t DataWord) {
+    /* if the address is out-of-bounds, do nothing */
+    if (Address >= FEE_DENSITY_BYTES) {
+        eeprom_printf("EEPROM_WriteDataWord(0x%04x, 0x%04x) [BAD ADDRESS]\n", Address, DataWord);
+        return FLASH_BAD_ADDRESS;
+    }
+
+    /* Check for word alignment */
+    FLASH_Status final_status = FLASH_COMPLETE;
+    if (Address % 2) {
+        final_status        = EEPROM_WriteDataByte(Address, DataWord);
+        FLASH_Status status = EEPROM_WriteDataByte(Address + 1, DataWord >> 8);
+        if (status != FLASH_COMPLETE) final_status = status;
+        if (final_status != 0 && final_status != FLASH_COMPLETE) {
+            eeprom_printf("EEPROM_WriteDataWord [STATUS == %d]\n", final_status);
+        }
+        return final_status;
+    }
+
+    /* if the value is the same, don't bother writing it */
+    uint16_t oldValue = *(uint16_t *)(&DataBuf[Address]);
+    if (oldValue == DataWord) {
+        eeprom_printf("EEPROM_WriteDataWord(0x%04x, 0x%04x) [SKIP SAME]\n", Address, DataWord);
+        return 0;
+    }
+
+    /* keep DataBuf cache in sync */
+    *(uint16_t *)(&DataBuf[Address]) = DataWord;
+    eeprom_printf("EEPROM_WriteDataWord DataBuf[0x%04x] = 0x%04x\n", Address, *(uint16_t *)(&DataBuf[Address]));
+
+    /* perform the write into flash memory */
+    /* First, attempt to write directly into the compacted flash area */
+    final_status = eeprom_write_direct_entry(Address);
+    if (!final_status) {
+        /* Otherwise append to the write log */
+        /* Check if we need to fall back to byte write */
+        if (Address < FEE_BYTE_RANGE) {
+            final_status = FLASH_COMPLETE;
+            /* Only write a byte if it has changed */
+            if ((uint8_t)oldValue != (uint8_t)DataWord) {
+                final_status = eeprom_write_log_byte_entry(Address);
+            }
+            FLASH_Status status = FLASH_COMPLETE;
+            /* Only write a byte if it has changed */
+            if ((oldValue >> 8) != (DataWord >> 8)) {
+                status = eeprom_write_log_byte_entry(Address + 1);
+            }
+            if (status != FLASH_COMPLETE) final_status = status;
+        } else {
+            final_status = eeprom_write_log_word_entry(Address);
+        }
+    }
+    if (final_status != 0 && final_status != FLASH_COMPLETE) {
+        eeprom_printf("EEPROM_WriteDataWord [STATUS == %d]\n", final_status);
+    }
+    return final_status;
+}
+
+uint8_t EEPROM_ReadDataByte(uint16_t Address) {
+    uint8_t DataByte = 0xFF;
+
+    if (Address < FEE_DENSITY_BYTES) {
+        DataByte = DataBuf[Address];
+    }
+
+    eeprom_printf("EEPROM_ReadDataByte(0x%04x): 0x%02x\n", Address, DataByte);
+
+    return DataByte;
+}
+
+uint16_t EEPROM_ReadDataWord(uint16_t Address) {
+    uint16_t DataWord = 0xFFFF;
+
+    if (Address < FEE_DENSITY_BYTES - 1) {
+        /* Check word alignment */
+        if (Address % 2) {
+            DataWord = DataBuf[Address] | (DataBuf[Address + 1] << 8);
+        } else {
+            DataWord = *(uint16_t *)(&DataBuf[Address]);
+        }
+    }
+
+    eeprom_printf("EEPROM_ReadDataWord(0x%04x): 0x%04x\n", Address, DataWord);
+
+    return DataWord;
+}
+
+/*****************************************************************************
+ *  Bind to eeprom_driver.c
+ *******************************************************************************/
+void eeprom_driver_init(void) { EEPROM_Init(); }
+
+void eeprom_driver_erase(void) { EEPROM_Erase(); }
+
+void eeprom_read_block(void *buf, const void *addr, size_t len) {
+    const uint8_t *src  = (const uint8_t *)addr;
+    uint8_t *      dest = (uint8_t *)buf;
+
+    /* Check word alignment */
+    if (len && (uintptr_t)src % 2) {
+        /* Read the unaligned first byte */
+        *dest++ = EEPROM_ReadDataByte((const uintptr_t)src++);
+        --len;
+    }
+
+    uint16_t value;
+    bool     aligned = ((uintptr_t)dest % 2 == 0);
+    while (len > 1) {
+        value = EEPROM_ReadDataWord((const uintptr_t)((uint16_t *)src));
+        if (aligned) {
+            *(uint16_t *)dest = value;
+            dest += 2;
+        } else {
+            *dest++ = value;
+            *dest++ = value >> 8;
+        }
+        src += 2;
+        len -= 2;
+    }
+    if (len) {
+        *dest = EEPROM_ReadDataByte((const uintptr_t)src);
+    }
+}
+
+void eeprom_write_block(const void *buf, void *addr, size_t len) {
+    uint8_t *      dest = (uint8_t *)addr;
+    const uint8_t *src  = (const uint8_t *)buf;
+
+    /* Check word alignment */
+    if (len && (uintptr_t)dest % 2) {
+        /* Write the unaligned first byte */
+        EEPROM_WriteDataByte((uintptr_t)dest++, *src++);
+        --len;
+    }
+
+    uint16_t value;
+    bool     aligned = ((uintptr_t)src % 2 == 0);
+    while (len > 1) {
+        if (aligned) {
+            value = *(uint16_t *)src;
+        } else {
+            value = *(uint8_t *)src | (*(uint8_t *)(src + 1) << 8);
+        }
+        EEPROM_WriteDataWord((uintptr_t)((uint16_t *)dest), value);
+        dest += 2;
+        src += 2;
+        len -= 2;
+    }
+
+    if (len) {
+        EEPROM_WriteDataByte((uintptr_t)dest, *src);
+    }
+}
diff --git a/platforms/chibios/eeprom_stm32.h b/platforms/chibios/eeprom_stm32.h
new file mode 100644
index 0000000000..8fcfb556b8
--- /dev/null
+++ b/platforms/chibios/eeprom_stm32.h
@@ -0,0 +1,33 @@
+/*
+ * This software is experimental and a work in progress.
+ * Under no circumstances should these files be used in relation to any critical system(s).
+ * Use of these files is at your own risk.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * This files are free to use from http://engsta.com/stm32-flash-memory-eeprom-emulator/ by
+ * Artur F.
+ *
+ * Modifications for QMK and STM32F303 by Yiancar
+ *
+ * This library assumes 8-bit data locations. To add a new MCU, please provide the flash
+ * page size and the total flash size in Kb. The number of available pages must be a multiple
+ * of 2. Only half of the pages account for the total EEPROM size.
+ * This library also assumes that the pages are not used by the firmware.
+ */
+
+#pragma once
+
+uint16_t EEPROM_Init(void);
+void     EEPROM_Erase(void);
+uint8_t  EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte);
+uint8_t  EEPROM_WriteDataWord(uint16_t Address, uint16_t DataWord);
+uint8_t  EEPROM_ReadDataByte(uint16_t Address);
+uint16_t EEPROM_ReadDataWord(uint16_t Address);
+
+void print_eeprom(void);
diff --git a/platforms/chibios/eeprom_stm32_defs.h b/platforms/chibios/eeprom_stm32_defs.h
new file mode 100644
index 0000000000..66904f247f
--- /dev/null
+++ b/platforms/chibios/eeprom_stm32_defs.h
@@ -0,0 +1,74 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <hal.h>
+
+#if !defined(FEE_PAGE_SIZE) || !defined(FEE_PAGE_COUNT)
+#    if defined(STM32F103xB) || defined(STM32F042x6) || defined(GD32VF103C8) || defined(GD32VF103CB)
+#        ifndef FEE_PAGE_SIZE
+#            define FEE_PAGE_SIZE 0x400  // Page size = 1KByte
+#        endif
+#        ifndef FEE_PAGE_COUNT
+#            define FEE_PAGE_COUNT 2  // How many pages are used
+#        endif
+#    elif defined(STM32F103xE) || defined(STM32F303xC) || defined(STM32F072xB) || defined(STM32F070xB)
+#        ifndef FEE_PAGE_SIZE
+#            define FEE_PAGE_SIZE 0x800  // Page size = 2KByte
+#        endif
+#        ifndef FEE_PAGE_COUNT
+#            define FEE_PAGE_COUNT 4  // How many pages are used
+#        endif
+#    elif defined(STM32F401xC) || defined(STM32F401xE) || defined(STM32F405xG) || defined(STM32F411xE)
+#        ifndef FEE_PAGE_SIZE
+#            define FEE_PAGE_SIZE 0x4000  // Page size = 16KByte
+#        endif
+#        ifndef FEE_PAGE_COUNT
+#            define FEE_PAGE_COUNT 1  // How many pages are used
+#        endif
+#    endif
+#endif
+
+#if !defined(FEE_MCU_FLASH_SIZE)
+#    if defined(STM32F042x6)
+#        define FEE_MCU_FLASH_SIZE 32  // Size in Kb
+#    elif defined(GD32VF103C8)
+#        define FEE_MCU_FLASH_SIZE 64  // Size in Kb
+#    elif defined(STM32F103xB) || defined(STM32F072xB) || defined(STM32F070xB) || defined(GD32VF103CB)
+#        define FEE_MCU_FLASH_SIZE 128  // Size in Kb
+#    elif defined(STM32F303xC) || defined(STM32F401xC)
+#        define FEE_MCU_FLASH_SIZE 256  // Size in Kb
+#    elif defined(STM32F103xE) || defined(STM32F401xE) || defined(STM32F411xE)
+#        define FEE_MCU_FLASH_SIZE 512  // Size in Kb
+#    elif defined(STM32F405xG)
+#        define FEE_MCU_FLASH_SIZE 1024  // Size in Kb
+#    endif
+#endif
+
+/* Start of the emulated eeprom */
+#if !defined(FEE_PAGE_BASE_ADDRESS)
+#    if defined(STM32F401xC) || defined(STM32F401xE) || defined(STM32F405xG) || defined(STM32F411xE)
+#        ifndef FEE_PAGE_BASE_ADDRESS
+#            define FEE_PAGE_BASE_ADDRESS 0x08004000  // bodge to force 2nd 16k page
+#        endif
+#    else
+#        ifndef FEE_FLASH_BASE
+#            define FEE_FLASH_BASE 0x8000000
+#        endif
+/* Default to end of flash */
+#        define FEE_PAGE_BASE_ADDRESS ((uintptr_t)(FEE_FLASH_BASE) + FEE_MCU_FLASH_SIZE * 1024 - (FEE_PAGE_COUNT * FEE_PAGE_SIZE))
+#    endif
+#endif
diff --git a/platforms/chibios/eeprom_teensy.c b/platforms/chibios/eeprom_teensy.c
new file mode 100644
index 0000000000..97da6f9e14
--- /dev/null
+++ b/platforms/chibios/eeprom_teensy.c
@@ -0,0 +1,795 @@
+#include <ch.h>
+#include <hal.h>
+
+#include "eeconfig.h"
+
+/*************************************/
+/*          Hardware backend         */
+/*                                   */
+/*    Code from PJRC/Teensyduino     */
+/*************************************/
+
+/* Teensyduino Core Library
+ * http://www.pjrc.com/teensy/
+ * Copyright (c) 2013 PJRC.COM, LLC.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * 1. The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * 2. If the Software is incorporated into a build system that allows
+ * selection among a list of target devices, then similar target
+ * devices manufactured by PJRC.COM must be included in the list of
+ * target devices and selectable in the same manner.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define SMC_PMSTAT_RUN ((uint8_t)0x01)
+#define SMC_PMSTAT_HSRUN ((uint8_t)0x80)
+
+#define F_CPU KINETIS_SYSCLK_FREQUENCY
+
+static inline int kinetis_hsrun_disable(void) {
+#if defined(MK66F18)
+    if (SMC->PMSTAT == SMC_PMSTAT_HSRUN) {
+// First, reduce the CPU clock speed, but do not change
+// the peripheral speed (F_BUS).  Serial1 & Serial2 baud
+// rates will be impacted, but most other peripherals
+// will continue functioning at the same speed.
+#    if F_CPU == 256000000 && F_BUS == 64000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 3, 1, 7);  // TODO: TEST
+#    elif F_CPU == 256000000 && F_BUS == 128000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7);  // TODO: TEST
+#    elif F_CPU == 240000000 && F_BUS == 60000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 3, 1, 7);  // ok
+#    elif F_CPU == 240000000 && F_BUS == 80000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 8);  // ok
+#    elif F_CPU == 240000000 && F_BUS == 120000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7);  // ok
+#    elif F_CPU == 216000000 && F_BUS == 54000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 3, 1, 7);  // ok
+#    elif F_CPU == 216000000 && F_BUS == 72000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 8);  // ok
+#    elif F_CPU == 216000000 && F_BUS == 108000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7);  // ok
+#    elif F_CPU == 192000000 && F_BUS == 48000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 3, 1, 7);  // ok
+#    elif F_CPU == 192000000 && F_BUS == 64000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 8);  // ok
+#    elif F_CPU == 192000000 && F_BUS == 96000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7);  // ok
+#    elif F_CPU == 180000000 && F_BUS == 60000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 8);  // ok
+#    elif F_CPU == 180000000 && F_BUS == 90000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7);  // ok
+#    elif F_CPU == 168000000 && F_BUS == 56000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 5);  // ok
+#    elif F_CPU == 144000000 && F_BUS == 48000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 5);  // ok
+#    elif F_CPU == 144000000 && F_BUS == 72000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 5);  // ok
+#    elif F_CPU == 120000000 && F_BUS == 60000000
+        SIM->CLKDIV1 = SIM_CLKDIV1_OUTDIV1(KINETIS_CLKDIV1_OUTDIV1 - 1) | SIM_CLKDIV1_OUTDIV2(KINETIS_CLKDIV1_OUTDIV2 - 1) |
+#        if defined(MK66F18)
+                       SIM_CLKDIV1_OUTDIV3(KINETIS_CLKDIV1_OUTDIV3 - 1) |
+#        endif
+                       SIM_CLKDIV1_OUTDIV4(KINETIS_CLKDIV1_OUTDIV4 - 1);
+#    else
+        return 0;
+#    endif
+        // Then turn off HSRUN mode
+        SMC->PMCTRL = SMC_PMCTRL_RUNM_SET(0);
+        while (SMC->PMSTAT == SMC_PMSTAT_HSRUN)
+            ;  // wait
+        return 1;
+    }
+#endif
+    return 0;
+}
+
+static inline int kinetis_hsrun_enable(void) {
+#if defined(MK66F18)
+    if (SMC->PMSTAT == SMC_PMSTAT_RUN) {
+        // Turn HSRUN mode on
+        SMC->PMCTRL = SMC_PMCTRL_RUNM_SET(3);
+        while (SMC->PMSTAT != SMC_PMSTAT_HSRUN) {
+            ;
+        }  // wait
+// Then configure clock for full speed
+#    if F_CPU == 256000000 && F_BUS == 64000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 3, 0, 7);
+#    elif F_CPU == 256000000 && F_BUS == 128000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 7);
+#    elif F_CPU == 240000000 && F_BUS == 60000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 3, 0, 7);
+#    elif F_CPU == 240000000 && F_BUS == 80000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 7);
+#    elif F_CPU == 240000000 && F_BUS == 120000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 7);
+#    elif F_CPU == 216000000 && F_BUS == 54000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 3, 0, 7);
+#    elif F_CPU == 216000000 && F_BUS == 72000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 7);
+#    elif F_CPU == 216000000 && F_BUS == 108000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 7);
+#    elif F_CPU == 192000000 && F_BUS == 48000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 3, 0, 6);
+#    elif F_CPU == 192000000 && F_BUS == 64000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 6);
+#    elif F_CPU == 192000000 && F_BUS == 96000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 6);
+#    elif F_CPU == 180000000 && F_BUS == 60000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 6);
+#    elif F_CPU == 180000000 && F_BUS == 90000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 6);
+#    elif F_CPU == 168000000 && F_BUS == 56000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 5);
+#    elif F_CPU == 144000000 && F_BUS == 48000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 4);
+#    elif F_CPU == 144000000 && F_BUS == 72000000
+        SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 4);
+#    elif F_CPU == 120000000 && F_BUS == 60000000
+        SIM->CLKDIV1 = SIM_CLKDIV1_OUTDIV1(KINETIS_CLKDIV1_OUTDIV1 - 1) | SIM_CLKDIV1_OUTDIV2(KINETIS_CLKDIV1_OUTDIV2 - 1) |
+#        if defined(MK66F18)
+                       SIM_CLKDIV1_OUTDIV3(KINETIS_CLKDIV1_OUTDIV3 - 1) |
+#        endif
+                       SIM_CLKDIV1_OUTDIV4(KINETIS_CLKDIV1_OUTDIV4 - 1);
+#    else
+        return 0;
+#    endif
+        return 1;
+    }
+#endif
+    return 0;
+}
+
+#if defined(K20x) || defined(MK66F18) /* chip selection */
+/* Teensy 3.0, 3.1, 3.2; mchck; infinity keyboard */
+
+// The EEPROM is really RAM with a hardware-based backup system to
+// flash memory.  Selecting a smaller size EEPROM allows more wear
+// leveling, for higher write endurance.  If you edit this file,
+// set this to the smallest size your application can use.  Also,
+// due to Freescale's implementation, writing 16 or 32 bit words
+// (aligned to 2 or 4 byte boundaries) has twice the endurance
+// compared to writing 8 bit bytes.
+//
+#    ifndef EEPROM_SIZE
+#        define EEPROM_SIZE 32
+#    endif
+
+/*
+    ^^^ Here be dragons:
+        NXP AppNote AN4282 section 3.1 states that partitioning must only be done once.
+        Once EEPROM partitioning is done, the size is locked to this initial configuration.
+        Attempts to modify the EEPROM_SIZE setting may brick your board.
+*/
+
+// Writing unaligned 16 or 32 bit data is handled automatically when
+// this is defined, but at a cost of extra code size.  Without this,
+// any unaligned write will cause a hard fault exception!  If you're
+// absolutely sure all 16 and 32 bit writes will be aligned, you can
+// remove the extra unnecessary code.
+//
+#    define HANDLE_UNALIGNED_WRITES
+
+#    if defined(K20x)
+#        define EEPROM_MAX 2048
+#        define EEPARTITION 0x03  // all 32K dataflash for EEPROM, none for Data
+#        define EEESPLIT 0x30     // must be 0x30 on these chips
+#    elif defined(MK66F18)
+#        define EEPROM_MAX 4096
+#        define EEPARTITION 0x05  // 128K dataflash for EEPROM, 128K for Data
+#        define EEESPLIT 0x10     // best endurance: 0x00 = first 12%, 0x10 = first 25%, 0x30 = all equal
+#    endif
+
+// Minimum EEPROM Endurance
+// ------------------------
+#    if (EEPROM_SIZE == 4096)
+#        define EEESIZE 0x02
+#    elif (EEPROM_SIZE == 2048)  // 35000 writes/byte or 70000 writes/word
+#        define EEESIZE 0x03
+#    elif (EEPROM_SIZE == 1024)  // 75000 writes/byte or 150000 writes/word
+#        define EEESIZE 0x04
+#    elif (EEPROM_SIZE == 512)  // 155000 writes/byte or 310000 writes/word
+#        define EEESIZE 0x05
+#    elif (EEPROM_SIZE == 256)  // 315000 writes/byte or 630000 writes/word
+#        define EEESIZE 0x06
+#    elif (EEPROM_SIZE == 128)  // 635000 writes/byte or 1270000 writes/word
+#        define EEESIZE 0x07
+#    elif (EEPROM_SIZE == 64)  // 1275000 writes/byte or 2550000 writes/word
+#        define EEESIZE 0x08
+#    elif (EEPROM_SIZE == 32)  // 2555000 writes/byte or 5110000 writes/word
+#        define EEESIZE 0x09
+#    endif
+
+/** \brief eeprom initialization
+ *
+ * FIXME: needs doc
+ */
+void eeprom_initialize(void) {
+    uint32_t count          = 0;
+    uint16_t do_flash_cmd[] = {0xf06f, 0x037f, 0x7003, 0x7803, 0xf013, 0x0f80, 0xd0fb, 0x4770};
+    uint8_t  status;
+
+    if (FTFL->FCNFG & FTFL_FCNFG_RAMRDY) {
+        uint8_t stat = FTFL->FSTAT & 0x70;
+        if (stat) FTFL->FSTAT = stat;
+
+        // FlexRAM is configured as traditional RAM
+        // We need to reconfigure for EEPROM usage
+        kinetis_hsrun_disable();
+        FTFL->FCCOB0 = 0x80;  // PGMPART = Program Partition Command
+        FTFL->FCCOB3 = 0;
+        FTFL->FCCOB4 = EEESPLIT | EEESIZE;
+        FTFL->FCCOB5 = EEPARTITION;
+        __disable_irq();
+        // do_flash_cmd() must execute from RAM.  Luckily the C syntax is simple...
+        (*((void (*)(volatile uint8_t *))((uint32_t)do_flash_cmd | 1)))(&(FTFL->FSTAT));
+        __enable_irq();
+        kinetis_hsrun_enable();
+        status = FTFL->FSTAT;
+        if (status & (FTFL_FSTAT_RDCOLERR | FTFL_FSTAT_ACCERR | FTFL_FSTAT_FPVIOL)) {
+            FTFL->FSTAT = (status & (FTFL_FSTAT_RDCOLERR | FTFL_FSTAT_ACCERR | FTFL_FSTAT_FPVIOL));
+            return;  // error
+        }
+    }
+    // wait for eeprom to become ready (is this really necessary?)
+    while (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) {
+        if (++count > 200000) break;
+    }
+}
+
+#    define FlexRAM ((volatile uint8_t *)0x14000000)
+
+/** \brief eeprom read byte
+ *
+ * FIXME: needs doc
+ */
+uint8_t eeprom_read_byte(const uint8_t *addr) {
+    uint32_t offset = (uint32_t)addr;
+    if (offset >= EEPROM_SIZE) return 0;
+    if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
+    return FlexRAM[offset];
+}
+
+/** \brief eeprom read word
+ *
+ * FIXME: needs doc
+ */
+uint16_t eeprom_read_word(const uint16_t *addr) {
+    uint32_t offset = (uint32_t)addr;
+    if (offset >= EEPROM_SIZE - 1) return 0;
+    if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
+    return *(uint16_t *)(&FlexRAM[offset]);
+}
+
+/** \brief eeprom read dword
+ *
+ * FIXME: needs doc
+ */
+uint32_t eeprom_read_dword(const uint32_t *addr) {
+    uint32_t offset = (uint32_t)addr;
+    if (offset >= EEPROM_SIZE - 3) return 0;
+    if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
+    return *(uint32_t *)(&FlexRAM[offset]);
+}
+
+/** \brief eeprom read block
+ *
+ * FIXME: needs doc
+ */
+void eeprom_read_block(void *buf, const void *addr, uint32_t len) {
+    uint32_t offset = (uint32_t)addr;
+    uint8_t *dest   = (uint8_t *)buf;
+    uint32_t end    = offset + len;
+
+    if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
+    if (end > EEPROM_SIZE) end = EEPROM_SIZE;
+    while (offset < end) {
+        *dest++ = FlexRAM[offset++];
+    }
+}
+
+/** \brief eeprom is ready
+ *
+ * FIXME: needs doc
+ */
+int eeprom_is_ready(void) { return (FTFL->FCNFG & FTFL_FCNFG_EEERDY) ? 1 : 0; }
+
+/** \brief flexram wait
+ *
+ * FIXME: needs doc
+ */
+static void flexram_wait(void) {
+    while (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) {
+        // TODO: timeout
+    }
+}
+
+/** \brief eeprom_write_byte
+ *
+ * FIXME: needs doc
+ */
+void eeprom_write_byte(uint8_t *addr, uint8_t value) {
+    uint32_t offset = (uint32_t)addr;
+
+    if (offset >= EEPROM_SIZE) return;
+    if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
+    if (FlexRAM[offset] != value) {
+        kinetis_hsrun_disable();
+        uint8_t stat = FTFL->FSTAT & 0x70;
+        if (stat) FTFL->FSTAT = stat;
+        FlexRAM[offset] = value;
+        flexram_wait();
+        kinetis_hsrun_enable();
+    }
+}
+
+/** \brief eeprom write word
+ *
+ * FIXME: needs doc
+ */
+void eeprom_write_word(uint16_t *addr, uint16_t value) {
+    uint32_t offset = (uint32_t)addr;
+
+    if (offset >= EEPROM_SIZE - 1) return;
+    if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
+#    ifdef HANDLE_UNALIGNED_WRITES
+    if ((offset & 1) == 0) {
+#    endif
+        if (*(uint16_t *)(&FlexRAM[offset]) != value) {
+            kinetis_hsrun_disable();
+            uint8_t stat = FTFL->FSTAT & 0x70;
+            if (stat) FTFL->FSTAT = stat;
+            *(uint16_t *)(&FlexRAM[offset]) = value;
+            flexram_wait();
+            kinetis_hsrun_enable();
+        }
+#    ifdef HANDLE_UNALIGNED_WRITES
+    } else {
+        if (FlexRAM[offset] != value) {
+            kinetis_hsrun_disable();
+            uint8_t stat = FTFL->FSTAT & 0x70;
+            if (stat) FTFL->FSTAT = stat;
+            FlexRAM[offset] = value;
+            flexram_wait();
+            kinetis_hsrun_enable();
+        }
+        if (FlexRAM[offset + 1] != (value >> 8)) {
+            kinetis_hsrun_disable();
+            uint8_t stat = FTFL->FSTAT & 0x70;
+            if (stat) FTFL->FSTAT = stat;
+            FlexRAM[offset + 1] = value >> 8;
+            flexram_wait();
+            kinetis_hsrun_enable();
+        }
+    }
+#    endif
+}
+
+/** \brief eeprom write dword
+ *
+ * FIXME: needs doc
+ */
+void eeprom_write_dword(uint32_t *addr, uint32_t value) {
+    uint32_t offset = (uint32_t)addr;
+
+    if (offset >= EEPROM_SIZE - 3) return;
+    if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
+#    ifdef HANDLE_UNALIGNED_WRITES
+    switch (offset & 3) {
+        case 0:
+#    endif
+            if (*(uint32_t *)(&FlexRAM[offset]) != value) {
+                kinetis_hsrun_disable();
+                uint8_t stat = FTFL->FSTAT & 0x70;
+                if (stat) FTFL->FSTAT = stat;
+                *(uint32_t *)(&FlexRAM[offset]) = value;
+                flexram_wait();
+                kinetis_hsrun_enable();
+            }
+            return;
+#    ifdef HANDLE_UNALIGNED_WRITES
+        case 2:
+            if (*(uint16_t *)(&FlexRAM[offset]) != value) {
+                kinetis_hsrun_disable();
+                uint8_t stat = FTFL->FSTAT & 0x70;
+                if (stat) FTFL->FSTAT = stat;
+                *(uint16_t *)(&FlexRAM[offset]) = value;
+                flexram_wait();
+                kinetis_hsrun_enable();
+            }
+            if (*(uint16_t *)(&FlexRAM[offset + 2]) != (value >> 16)) {
+                kinetis_hsrun_disable();
+                uint8_t stat = FTFL->FSTAT & 0x70;
+                if (stat) FTFL->FSTAT = stat;
+                *(uint16_t *)(&FlexRAM[offset + 2]) = value >> 16;
+                flexram_wait();
+                kinetis_hsrun_enable();
+            }
+            return;
+        default:
+            if (FlexRAM[offset] != value) {
+                kinetis_hsrun_disable();
+                uint8_t stat = FTFL->FSTAT & 0x70;
+                if (stat) FTFL->FSTAT = stat;
+                FlexRAM[offset] = value;
+                flexram_wait();
+                kinetis_hsrun_enable();
+            }
+            if (*(uint16_t *)(&FlexRAM[offset + 1]) != (value >> 8)) {
+                kinetis_hsrun_disable();
+                uint8_t stat = FTFL->FSTAT & 0x70;
+                if (stat) FTFL->FSTAT = stat;
+                *(uint16_t *)(&FlexRAM[offset + 1]) = value >> 8;
+                flexram_wait();
+                kinetis_hsrun_enable();
+            }
+            if (FlexRAM[offset + 3] != (value >> 24)) {
+                kinetis_hsrun_disable();
+                uint8_t stat = FTFL->FSTAT & 0x70;
+                if (stat) FTFL->FSTAT = stat;
+                FlexRAM[offset + 3] = value >> 24;
+                flexram_wait();
+                kinetis_hsrun_enable();
+            }
+    }
+#    endif
+}
+
+/** \brief eeprom write block
+ *
+ * FIXME: needs doc
+ */
+void eeprom_write_block(const void *buf, void *addr, uint32_t len) {
+    uint32_t       offset = (uint32_t)addr;
+    const uint8_t *src    = (const uint8_t *)buf;
+
+    if (offset >= EEPROM_SIZE) return;
+    if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
+    if (len >= EEPROM_SIZE) len = EEPROM_SIZE;
+    if (offset + len >= EEPROM_SIZE) len = EEPROM_SIZE - offset;
+    kinetis_hsrun_disable();
+    while (len > 0) {
+        uint32_t lsb = offset & 3;
+        if (lsb == 0 && len >= 4) {
+            // write aligned 32 bits
+            uint32_t val32;
+            val32 = *src++;
+            val32 |= (*src++ << 8);
+            val32 |= (*src++ << 16);
+            val32 |= (*src++ << 24);
+            if (*(uint32_t *)(&FlexRAM[offset]) != val32) {
+                uint8_t stat = FTFL->FSTAT & 0x70;
+                if (stat) FTFL->FSTAT = stat;
+                *(uint32_t *)(&FlexRAM[offset]) = val32;
+                flexram_wait();
+            }
+            offset += 4;
+            len -= 4;
+        } else if ((lsb == 0 || lsb == 2) && len >= 2) {
+            // write aligned 16 bits
+            uint16_t val16;
+            val16 = *src++;
+            val16 |= (*src++ << 8);
+            if (*(uint16_t *)(&FlexRAM[offset]) != val16) {
+                uint8_t stat = FTFL->FSTAT & 0x70;
+                if (stat) FTFL->FSTAT = stat;
+                *(uint16_t *)(&FlexRAM[offset]) = val16;
+                flexram_wait();
+            }
+            offset += 2;
+            len -= 2;
+        } else {
+            // write 8 bits
+            uint8_t val8 = *src++;
+            if (FlexRAM[offset] != val8) {
+                uint8_t stat = FTFL->FSTAT & 0x70;
+                if (stat) FTFL->FSTAT = stat;
+                FlexRAM[offset] = val8;
+                flexram_wait();
+            }
+            offset++;
+            len--;
+        }
+    }
+    kinetis_hsrun_enable();
+}
+
+/*
+void do_flash_cmd(volatile uint8_t *fstat)
+{
+    *fstat = 0x80;
+    while ((*fstat & 0x80) == 0) ; // wait
+}
+00000000 <do_flash_cmd>:
+   0:	f06f 037f 	mvn.w	r3, #127	; 0x7f
+   4:	7003      	strb	r3, [r0, #0]
+   6:	7803      	ldrb	r3, [r0, #0]
+   8:	f013 0f80 	tst.w	r3, #128	; 0x80
+   c:	d0fb      	beq.n	6 <do_flash_cmd+0x6>
+   e:	4770      	bx	lr
+*/
+
+#elif defined(KL2x) /* chip selection */
+/* Teensy LC (emulated) */
+
+#    define SYMVAL(sym) (uint32_t)(((uint8_t *)&(sym)) - ((uint8_t *)0))
+
+extern uint32_t __eeprom_workarea_start__;
+extern uint32_t __eeprom_workarea_end__;
+
+#    define EEPROM_SIZE 128
+
+static uint32_t flashend = 0;
+
+void eeprom_initialize(void) {
+    const uint16_t *p = (uint16_t *)SYMVAL(__eeprom_workarea_start__);
+
+    do {
+        if (*p++ == 0xFFFF) {
+            flashend = (uint32_t)(p - 2);
+            return;
+        }
+    } while (p < (uint16_t *)SYMVAL(__eeprom_workarea_end__));
+    flashend = (uint32_t)(p - 1);
+}
+
+uint8_t eeprom_read_byte(const uint8_t *addr) {
+    uint32_t        offset = (uint32_t)addr;
+    const uint16_t *p      = (uint16_t *)SYMVAL(__eeprom_workarea_start__);
+    const uint16_t *end    = (const uint16_t *)((uint32_t)flashend);
+    uint16_t        val;
+    uint8_t         data = 0xFF;
+
+    if (!end) {
+        eeprom_initialize();
+        end = (const uint16_t *)((uint32_t)flashend);
+    }
+    if (offset < EEPROM_SIZE) {
+        while (p <= end) {
+            val = *p++;
+            if ((val & 255) == offset) data = val >> 8;
+        }
+    }
+    return data;
+}
+
+static void flash_write(const uint16_t *code, uint32_t addr, uint32_t data) {
+    // with great power comes great responsibility....
+    uint32_t stat;
+    *(uint32_t *)&(FTFA->FCCOB3) = 0x06000000 | (addr & 0x00FFFFFC);
+    *(uint32_t *)&(FTFA->FCCOB7) = data;
+    __disable_irq();
+    (*((void (*)(volatile uint8_t *))((uint32_t)code | 1)))(&(FTFA->FSTAT));
+    __enable_irq();
+    stat = FTFA->FSTAT & (FTFA_FSTAT_RDCOLERR | FTFA_FSTAT_ACCERR | FTFA_FSTAT_FPVIOL);
+    if (stat) {
+        FTFA->FSTAT = stat;
+    }
+    MCM->PLACR |= MCM_PLACR_CFCC;
+}
+
+void eeprom_write_byte(uint8_t *addr, uint8_t data) {
+    uint32_t        offset = (uint32_t)addr;
+    const uint16_t *p, *end = (const uint16_t *)((uint32_t)flashend);
+    uint32_t        i, val, flashaddr;
+    uint16_t        do_flash_cmd[] = {0x2380, 0x7003, 0x7803, 0xb25b, 0x2b00, 0xdafb, 0x4770};
+    uint8_t         buf[EEPROM_SIZE];
+
+    if (offset >= EEPROM_SIZE) return;
+    if (!end) {
+        eeprom_initialize();
+        end = (const uint16_t *)((uint32_t)flashend);
+    }
+    if (++end < (uint16_t *)SYMVAL(__eeprom_workarea_end__)) {
+        val       = (data << 8) | offset;
+        flashaddr = (uint32_t)end;
+        flashend  = flashaddr;
+        if ((flashaddr & 2) == 0) {
+            val |= 0xFFFF0000;
+        } else {
+            val <<= 16;
+            val |= 0x0000FFFF;
+        }
+        flash_write(do_flash_cmd, flashaddr, val);
+    } else {
+        for (i = 0; i < EEPROM_SIZE; i++) {
+            buf[i] = 0xFF;
+        }
+        val = 0;
+        for (p = (uint16_t *)SYMVAL(__eeprom_workarea_start__); p < (uint16_t *)SYMVAL(__eeprom_workarea_end__); p++) {
+            val = *p;
+            if ((val & 255) < EEPROM_SIZE) {
+                buf[val & 255] = val >> 8;
+            }
+        }
+        buf[offset] = data;
+        for (flashaddr = (uint32_t)(uint16_t *)SYMVAL(__eeprom_workarea_start__); flashaddr < (uint32_t)(uint16_t *)SYMVAL(__eeprom_workarea_end__); flashaddr += 1024) {
+            *(uint32_t *)&(FTFA->FCCOB3) = 0x09000000 | flashaddr;
+            __disable_irq();
+            (*((void (*)(volatile uint8_t *))((uint32_t)do_flash_cmd | 1)))(&(FTFA->FSTAT));
+            __enable_irq();
+            val = FTFA->FSTAT & (FTFA_FSTAT_RDCOLERR | FTFA_FSTAT_ACCERR | FTFA_FSTAT_FPVIOL);
+            ;
+            if (val) FTFA->FSTAT = val;
+            MCM->PLACR |= MCM_PLACR_CFCC;
+        }
+        flashaddr = (uint32_t)(uint16_t *)SYMVAL(__eeprom_workarea_start__);
+        for (i = 0; i < EEPROM_SIZE; i++) {
+            if (buf[i] == 0xFF) continue;
+            if ((flashaddr & 2) == 0) {
+                val = (buf[i] << 8) | i;
+            } else {
+                val = val | (buf[i] << 24) | (i << 16);
+                flash_write(do_flash_cmd, flashaddr, val);
+            }
+            flashaddr += 2;
+        }
+        flashend = flashaddr;
+        if ((flashaddr & 2)) {
+            val |= 0xFFFF0000;
+            flash_write(do_flash_cmd, flashaddr, val);
+        }
+    }
+}
+
+/*
+void do_flash_cmd(volatile uint8_t *fstat)
+{
+        *fstat = 0x80;
+        while ((*fstat & 0x80) == 0) ; // wait
+}
+00000000 <do_flash_cmd>:
+   0:	2380      	movs	r3, #128	; 0x80
+   2:	7003      	strb	r3, [r0, #0]
+   4:	7803      	ldrb	r3, [r0, #0]
+   6:	b25b      	sxtb	r3, r3
+   8:	2b00      	cmp	r3, #0
+   a:	dafb      	bge.n	4 <do_flash_cmd+0x4>
+   c:	4770      	bx	lr
+*/
+
+uint16_t eeprom_read_word(const uint16_t *addr) {
+    const uint8_t *p = (const uint8_t *)addr;
+    return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8);
+}
+
+uint32_t eeprom_read_dword(const uint32_t *addr) {
+    const uint8_t *p = (const uint8_t *)addr;
+    return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8) | (eeprom_read_byte(p + 2) << 16) | (eeprom_read_byte(p + 3) << 24);
+}
+
+void eeprom_read_block(void *buf, const void *addr, uint32_t len) {
+    const uint8_t *p    = (const uint8_t *)addr;
+    uint8_t *      dest = (uint8_t *)buf;
+    while (len--) {
+        *dest++ = eeprom_read_byte(p++);
+    }
+}
+
+int eeprom_is_ready(void) { return 1; }
+
+void eeprom_write_word(uint16_t *addr, uint16_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p, value >> 8);
+}
+
+void eeprom_write_dword(uint32_t *addr, uint32_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p++, value >> 8);
+    eeprom_write_byte(p++, value >> 16);
+    eeprom_write_byte(p, value >> 24);
+}
+
+void eeprom_write_block(const void *buf, void *addr, uint32_t len) {
+    uint8_t *      p   = (uint8_t *)addr;
+    const uint8_t *src = (const uint8_t *)buf;
+    while (len--) {
+        eeprom_write_byte(p++, *src++);
+    }
+}
+
+#else
+// No EEPROM supported, so emulate it
+
+#    ifndef EEPROM_SIZE
+#        include "eeconfig.h"
+#        define EEPROM_SIZE (((EECONFIG_SIZE + 3) / 4) * 4)  // based off eeconfig's current usage, aligned to 4-byte sizes, to deal with LTO
+#    endif
+__attribute__((aligned(4))) static uint8_t buffer[EEPROM_SIZE];
+
+uint8_t eeprom_read_byte(const uint8_t *addr) {
+    uint32_t offset = (uint32_t)addr;
+    return buffer[offset];
+}
+
+void eeprom_write_byte(uint8_t *addr, uint8_t value) {
+    uint32_t offset = (uint32_t)addr;
+    buffer[offset]  = value;
+}
+
+uint16_t eeprom_read_word(const uint16_t *addr) {
+    const uint8_t *p = (const uint8_t *)addr;
+    return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8);
+}
+
+uint32_t eeprom_read_dword(const uint32_t *addr) {
+    const uint8_t *p = (const uint8_t *)addr;
+    return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8) | (eeprom_read_byte(p + 2) << 16) | (eeprom_read_byte(p + 3) << 24);
+}
+
+void eeprom_read_block(void *buf, const void *addr, size_t len) {
+    const uint8_t *p    = (const uint8_t *)addr;
+    uint8_t *      dest = (uint8_t *)buf;
+    while (len--) {
+        *dest++ = eeprom_read_byte(p++);
+    }
+}
+
+void eeprom_write_word(uint16_t *addr, uint16_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p, value >> 8);
+}
+
+void eeprom_write_dword(uint32_t *addr, uint32_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p++, value >> 8);
+    eeprom_write_byte(p++, value >> 16);
+    eeprom_write_byte(p, value >> 24);
+}
+
+void eeprom_write_block(const void *buf, void *addr, size_t len) {
+    uint8_t *      p   = (uint8_t *)addr;
+    const uint8_t *src = (const uint8_t *)buf;
+    while (len--) {
+        eeprom_write_byte(p++, *src++);
+    }
+}
+
+#endif /* chip selection */
+// The update functions just calls write for now, but could probably be optimized
+
+void eeprom_update_byte(uint8_t *addr, uint8_t value) { eeprom_write_byte(addr, value); }
+
+void eeprom_update_word(uint16_t *addr, uint16_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p, value >> 8);
+}
+
+void eeprom_update_dword(uint32_t *addr, uint32_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p++, value >> 8);
+    eeprom_write_byte(p++, value >> 16);
+    eeprom_write_byte(p, value >> 24);
+}
+
+void eeprom_update_block(const void *buf, void *addr, size_t len) {
+    uint8_t *      p   = (uint8_t *)addr;
+    const uint8_t *src = (const uint8_t *)buf;
+    while (len--) {
+        eeprom_write_byte(p++, *src++);
+    }
+}
diff --git a/platforms/chibios/flash_stm32.c b/platforms/chibios/flash_stm32.c
new file mode 100644
index 0000000000..72c41b8b78
--- /dev/null
+++ b/platforms/chibios/flash_stm32.c
@@ -0,0 +1,208 @@
+/*
+ * This software is experimental and a work in progress.
+ * Under no circumstances should these files be used in relation to any critical system(s).
+ * Use of these files is at your own risk.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * This files are free to use from https://github.com/rogerclarkmelbourne/Arduino_STM32 and
+ * https://github.com/leaflabs/libmaple
+ *
+ * Modifications for QMK and STM32F303 by Yiancar
+ */
+
+#include <hal.h>
+#include "flash_stm32.h"
+
+#if defined(STM32F1XX)
+#    define FLASH_SR_WRPERR FLASH_SR_WRPRTERR
+#endif
+
+#if defined(MCU_GD32V)
+/* GigaDevice GD32VF103 is a STM32F103 clone at heart. */
+#    include "gd32v_compatibility.h"
+#endif
+
+#if defined(STM32F4XX)
+#    define FLASH_SR_PGERR (FLASH_SR_PGSERR | FLASH_SR_PGPERR | FLASH_SR_PGAERR)
+
+#    define FLASH_KEY1 0x45670123U
+#    define FLASH_KEY2 0xCDEF89ABU
+
+static uint8_t ADDR2PAGE(uint32_t Page_Address) {
+    switch (Page_Address) {
+        case 0x08000000 ... 0x08003FFF:
+            return 0;
+        case 0x08004000 ... 0x08007FFF:
+            return 1;
+        case 0x08008000 ... 0x0800BFFF:
+            return 2;
+        case 0x0800C000 ... 0x0800FFFF:
+            return 3;
+    }
+
+    // TODO: bad times...
+    return 7;
+}
+#endif
+
+/* Delay definition */
+#define EraseTimeout ((uint32_t)0x00000FFF)
+#define ProgramTimeout ((uint32_t)0x0000001F)
+
+#define ASSERT(exp) (void)((0))
+
+/**
+ * @brief  Inserts a time delay.
+ * @param  None
+ * @retval None
+ */
+static void delay(void) {
+    __IO uint32_t i = 0;
+    for (i = 0xFF; i != 0; i--) {
+    }
+}
+
+/**
+ * @brief  Returns the FLASH Status.
+ * @param  None
+ * @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
+ *   FLASH_ERROR_WRP or FLASH_COMPLETE
+ */
+FLASH_Status FLASH_GetStatus(void) {
+    if ((FLASH->SR & FLASH_SR_BSY) == FLASH_SR_BSY) return FLASH_BUSY;
+
+    if ((FLASH->SR & FLASH_SR_PGERR) != 0) return FLASH_ERROR_PG;
+
+    if ((FLASH->SR & FLASH_SR_WRPERR) != 0) return FLASH_ERROR_WRP;
+
+#if defined(FLASH_OBR_OPTERR)
+    if ((FLASH->SR & FLASH_OBR_OPTERR) != 0) return FLASH_ERROR_OPT;
+#endif
+
+    return FLASH_COMPLETE;
+}
+
+/**
+ * @brief  Waits for a Flash operation to complete or a TIMEOUT to occur.
+ * @param  Timeout: FLASH progamming Timeout
+ * @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,
+ *   FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
+ */
+FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout) {
+    FLASH_Status status;
+
+    /* Check for the Flash Status */
+    status = FLASH_GetStatus();
+    /* Wait for a Flash operation to complete or a TIMEOUT to occur */
+    while ((status == FLASH_BUSY) && (Timeout != 0x00)) {
+        delay();
+        status = FLASH_GetStatus();
+        Timeout--;
+    }
+    if (Timeout == 0) status = FLASH_TIMEOUT;
+    /* Return the operation status */
+    return status;
+}
+
+/**
+ * @brief  Erases a specified FLASH page.
+ * @param  Page_Address: The page address to be erased.
+ * @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
+ *   FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
+ */
+FLASH_Status FLASH_ErasePage(uint32_t Page_Address) {
+    FLASH_Status status = FLASH_COMPLETE;
+    /* Check the parameters */
+    ASSERT(IS_FLASH_ADDRESS(Page_Address));
+    /* Wait for last operation to be completed */
+    status = FLASH_WaitForLastOperation(EraseTimeout);
+
+    if (status == FLASH_COMPLETE) {
+        /* if the previous operation is completed, proceed to erase the page */
+#if defined(FLASH_CR_SNB)
+        FLASH->CR &= ~FLASH_CR_SNB;
+        FLASH->CR |= FLASH_CR_SER | (ADDR2PAGE(Page_Address) << FLASH_CR_SNB_Pos);
+#else
+        FLASH->CR |= FLASH_CR_PER;
+        FLASH->AR = Page_Address;
+#endif
+        FLASH->CR |= FLASH_CR_STRT;
+
+        /* Wait for last operation to be completed */
+        status = FLASH_WaitForLastOperation(EraseTimeout);
+        if (status != FLASH_TIMEOUT) {
+            /* if the erase operation is completed, disable the configured Bits */
+#if defined(FLASH_CR_SNB)
+            FLASH->CR &= ~(FLASH_CR_SER | FLASH_CR_SNB);
+#else
+            FLASH->CR &= ~FLASH_CR_PER;
+#endif
+        }
+        FLASH->SR = (FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPERR);
+    }
+    /* Return the Erase Status */
+    return status;
+}
+
+/**
+ * @brief  Programs a half word at a specified address.
+ * @param  Address: specifies the address to be programmed.
+ * @param  Data: specifies the data to be programmed.
+ * @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,
+ *   FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
+ */
+FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data) {
+    FLASH_Status status = FLASH_BAD_ADDRESS;
+
+    if (IS_FLASH_ADDRESS(Address)) {
+        /* Wait for last operation to be completed */
+        status = FLASH_WaitForLastOperation(ProgramTimeout);
+        if (status == FLASH_COMPLETE) {
+            /* if the previous operation is completed, proceed to program the new data */
+
+#if defined(FLASH_CR_PSIZE)
+            FLASH->CR &= ~FLASH_CR_PSIZE;
+            FLASH->CR |= FLASH_CR_PSIZE_0;
+#endif
+            FLASH->CR |= FLASH_CR_PG;
+            *(__IO uint16_t*)Address = Data;
+            /* Wait for last operation to be completed */
+            status = FLASH_WaitForLastOperation(ProgramTimeout);
+            if (status != FLASH_TIMEOUT) {
+                /* if the program operation is completed, disable the PG Bit */
+                FLASH->CR &= ~FLASH_CR_PG;
+            }
+            FLASH->SR = (FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPERR);
+        }
+    }
+    return status;
+}
+
+/**
+ * @brief  Unlocks the FLASH Program Erase Controller.
+ * @param  None
+ * @retval None
+ */
+void FLASH_Unlock(void) {
+    if (FLASH->CR & FLASH_CR_LOCK) {
+        /* Authorize the FPEC Access */
+        FLASH->KEYR = FLASH_KEY1;
+        FLASH->KEYR = FLASH_KEY2;
+    }
+}
+
+/**
+ * @brief  Locks the FLASH Program Erase Controller.
+ * @param  None
+ * @retval None
+ */
+void FLASH_Lock(void) {
+    /* Set the Lock Bit to lock the FPEC and the FCR */
+    FLASH->CR |= FLASH_CR_LOCK;
+}
diff --git a/platforms/chibios/flash_stm32.h b/platforms/chibios/flash_stm32.h
new file mode 100644
index 0000000000..6c66642ec5
--- /dev/null
+++ b/platforms/chibios/flash_stm32.h
@@ -0,0 +1,44 @@
+/*
+ * This software is experimental and a work in progress.
+ * Under no circumstances should these files be used in relation to any critical system(s).
+ * Use of these files is at your own risk.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * This files are free to use from https://github.com/rogerclarkmelbourne/Arduino_STM32 and
+ * https://github.com/leaflabs/libmaple
+ *
+ * Modifications for QMK and STM32F303 by Yiancar
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+#ifdef FLASH_STM32_MOCKED
+extern uint8_t FlashBuf[MOCK_FLASH_SIZE];
+#endif
+
+typedef enum { FLASH_BUSY = 1, FLASH_ERROR_PG, FLASH_ERROR_WRP, FLASH_ERROR_OPT, FLASH_COMPLETE, FLASH_TIMEOUT, FLASH_BAD_ADDRESS } FLASH_Status;
+
+#define IS_FLASH_ADDRESS(ADDRESS) (((ADDRESS) >= 0x08000000) && ((ADDRESS) < 0x0807FFFF))
+
+FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);
+FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
+FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
+
+void FLASH_Unlock(void);
+void FLASH_Lock(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/platforms/chibios/gd32v_compatibility.h b/platforms/chibios/gd32v_compatibility.h
new file mode 100644
index 0000000000..f4dcfd8c55
--- /dev/null
+++ b/platforms/chibios/gd32v_compatibility.h
@@ -0,0 +1,120 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+/* GD32VF103 has the same API as STM32F103, but uses different names for literally the same thing.
+ * As of 23.7.2021 QMK is tailored to use STM32 defines/names, for compatibility sake
+ * we just redefine the GD32 names. */
+
+/* Close your eyes kids. */
+#define MCU_STM32
+
+/* AFIO redefines */
+#define MAPR PCF0
+#define AFIO_MAPR_USART1_REMAP AFIO_PCF0_USART0_REMAP
+#define AFIO_MAPR_USART2_REMAP AFIO_PCF0_USART1_REMAP
+#define AFIO_MAPR_USART3_REMAP_PARTIALREMAP AFIO_PCF0_USART2_REMAP_PARTIALREMAP
+#define AFIO_MAPR_USART3_REMAP_FULLREMAP AFIO_PCF0_USART2_REMAP_FULLREMAP
+
+/* DMA redefines. */
+#define STM32_DMA_STREAM(stream) GD32_DMA_STREAM(stream)
+#define STM32_DMA_STREAM_ID(peripheral, channel) GD32_DMA_STREAM_ID(peripheral - 1, channel - 1)
+#define STM32_DMA_CR_DIR_M2P GD32_DMA_CTL_DIR_M2P
+#define STM32_DMA_CR_PSIZE_WORD GD32_DMA_CTL_PWIDTH_WORD
+#define STM32_DMA_CR_MSIZE_WORD GD32_DMA_CTL_MWIDTH_WORD
+#define STM32_DMA_CR_MINC GD32_DMA_CTL_MNAGA
+#define STM32_DMA_CR_CIRC GD32_DMA_CTL_CMEN
+#define STM32_DMA_CR_PL GD32_DMA_CTL_PRIO
+#define STM32_DMA_CR_CHSEL GD32_DMA_CTL_CHSEL
+#define cr1 ctl0
+#define cr2 ctl1
+#define cr3 ctl2
+#define dier dmainten
+
+/* ADC redefines */
+#if HAL_USE_ADC
+#    define STM32_ADC_USE_ADC1 GD32_ADC_USE_ADC0
+
+#    define smpr1 sampt0
+#    define smpr2 sampt1
+#    define sqr1 rsq0
+#    define sqr2 rsq1
+#    define sqr3 rsq2
+
+#    define ADC_SMPR2_SMP_AN0 ADC_SAMPT1_SMP_SPT0
+#    define ADC_SMPR2_SMP_AN1 ADC_SAMPT1_SMP_SPT1
+#    define ADC_SMPR2_SMP_AN2 ADC_SAMPT1_SMP_SPT2
+#    define ADC_SMPR2_SMP_AN3 ADC_SAMPT1_SMP_SPT3
+#    define ADC_SMPR2_SMP_AN4 ADC_SAMPT1_SMP_SPT4
+#    define ADC_SMPR2_SMP_AN5 ADC_SAMPT1_SMP_SPT5
+#    define ADC_SMPR2_SMP_AN6 ADC_SAMPT1_SMP_SPT6
+#    define ADC_SMPR2_SMP_AN7 ADC_SAMPT1_SMP_SPT7
+#    define ADC_SMPR2_SMP_AN8 ADC_SAMPT1_SMP_SPT8
+#    define ADC_SMPR2_SMP_AN9 ADC_SAMPT1_SMP_SPT9
+
+#    define ADC_SMPR1_SMP_AN10 ADC_SAMPT0_SMP_SPT10
+#    define ADC_SMPR1_SMP_AN11 ADC_SAMPT0_SMP_SPT11
+#    define ADC_SMPR1_SMP_AN12 ADC_SAMPT0_SMP_SPT12
+#    define ADC_SMPR1_SMP_AN13 ADC_SAMPT0_SMP_SPT13
+#    define ADC_SMPR1_SMP_AN14 ADC_SAMPT0_SMP_SPT14
+#    define ADC_SMPR1_SMP_AN15 ADC_SAMPT0_SMP_SPT15
+
+#    define ADC_SQR3_SQ1_N ADC_RSQ2_RSQ1_N
+#endif
+
+/* FLASH redefines */
+#if defined(EEPROM_ENABLE)
+#    define SR STAT
+#    define FLASH_SR_BSY FLASH_STAT_BUSY
+#    define FLASH_SR_PGERR FLASH_STAT_PGERR
+#    define FLASH_SR_EOP FLASH_STAT_ENDF
+#    define FLASH_SR_WRPRTERR FLASH_STAT_WPERR
+#    define FLASH_SR_WRPERR FLASH_SR_WRPRTERR
+#    define FLASH_OBR_OPTERR FLASH_OBSTAT_OBERR
+#    define AR ADDR
+#    define CR CTL
+#    define FLASH_CR_PER FLASH_CTL_PER
+#    define FLASH_CR_STRT FLASH_CTL_START
+#    define FLASH_CR_LOCK FLASH_CTL_LK
+#    define FLASH_CR_PG FLASH_CTL_PG
+#    define KEYR KEY
+#endif
+
+/* Serial USART redefines. */
+#if HAL_USE_SERIAL
+#    if !defined(SERIAL_USART_CR1)
+#        define SERIAL_USART_CR1 (USART_CTL0_PCEN | USART_CTL0_PM | USART_CTL0_WL)  // parity enable, odd parity, 9 bit length
+#    endif
+#    if !defined(SERIAL_USART_CR2)
+#        define SERIAL_USART_CR2 (USART_CTL1_STB_1)  // 2 stop bits
+#    endif
+#    if !defined(SERIAL_USART_CR3)
+#        define SERIAL_USART_CR3 0x0
+#    endif
+#    define USART_CR3_HDSEL USART_CTL2_HDEN
+#    define CCR CHCV
+#endif
+
+/* SPI redefines. */
+#if HAL_USE_SPI
+#    define SPI_CR1_LSBFIRST SPI_CTL0_LF
+#    define SPI_CR1_CPHA SPI_CTL0_CKPH
+#    define SPI_CR1_CPOL SPI_CTL0_CKPL
+#    define SPI_CR1_BR_0 SPI_CTL0_PSC_0
+#    define SPI_CR1_BR_1 SPI_CTL0_PSC_1
+#    define SPI_CR1_BR_2 SPI_CTL0_PSC_2
+#endif
diff --git a/platforms/chibios/gpio.h b/platforms/chibios/gpio.h
new file mode 100644
index 0000000000..4d057f1cab
--- /dev/null
+++ b/platforms/chibios/gpio.h
@@ -0,0 +1,50 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <hal.h>
+#include "pin_defs.h"
+
+typedef ioline_t pin_t;
+
+/* Operation of GPIO by pin. */
+
+#define setPinInput(pin) palSetLineMode(pin, PAL_MODE_INPUT)
+#define setPinInputHigh(pin) palSetLineMode(pin, PAL_MODE_INPUT_PULLUP)
+#define setPinInputLow(pin) palSetLineMode(pin, PAL_MODE_INPUT_PULLDOWN)
+#define setPinOutput(pin) palSetLineMode(pin, PAL_MODE_OUTPUT_PUSHPULL)
+
+#define writePinHigh(pin) palSetLine(pin)
+#define writePinLow(pin) palClearLine(pin)
+#define writePin(pin, level) ((level) ? (writePinHigh(pin)) : (writePinLow(pin)))
+
+#define readPin(pin) palReadLine(pin)
+
+#define togglePin(pin) palToggleLine(pin)
+
+/* Operation of GPIO by port. */
+
+typedef uint16_t port_data_t;
+
+#define readPort(pin) palReadPort(PAL_PORT(pin))
+
+#define setPortBitInput(pin, bit) palSetPadMode(PAL_PORT(pin), bit, PAL_MODE_INPUT)
+#define setPortBitInputHigh(pin, bit) palSetPadMode(PAL_PORT(pin), bit, PAL_MODE_INPUT_PULLUP)
+#define setPortBitInputLow(pin, bit) palSetPadMode(PAL_PORT(pin), bit, PAL_MODE_INPUT_PULLDOWN)
+#define setPortBitOutput(pin, bit) palSetPadMode(PAL_PORT(pin), bit, PAL_MODE_OUTPUT_PUSHPULL)
+
+#define writePortBitLow(pin, bit) palClearLine(PAL_LINE(PAL_PORT(pin), bit))
+#define writePortBitHigh(pin, bit) palSetLine(PAL_LINE(PAL_PORT(pin), bit))
diff --git a/platforms/chibios/pin_defs.h b/platforms/chibios/pin_defs.h
new file mode 100644
index 0000000000..c03f8de0c2
--- /dev/null
+++ b/platforms/chibios/pin_defs.h
@@ -0,0 +1,323 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// Defines mapping for Proton C replacement
+#ifdef CONVERT_TO_PROTON_C
+// Left side (front)
+#    define D3 PAL_LINE(GPIOA, 9)
+#    define D2 PAL_LINE(GPIOA, 10)
+//      GND
+//      GND
+#    define D1 PAL_LINE(GPIOB, 7)
+#    define D0 PAL_LINE(GPIOB, 6)
+#    define D4 PAL_LINE(GPIOB, 5)
+#    define C6 PAL_LINE(GPIOB, 4)
+#    define D7 PAL_LINE(GPIOB, 3)
+#    define E6 PAL_LINE(GPIOB, 2)
+#    define B4 PAL_LINE(GPIOB, 1)
+#    define B5 PAL_LINE(GPIOB, 0)
+
+// Right side (front)
+//      RAW
+//      GND
+//      RESET
+//      VCC
+#    define F4 PAL_LINE(GPIOA, 2)
+#    define F5 PAL_LINE(GPIOA, 1)
+#    define F6 PAL_LINE(GPIOA, 0)
+#    define F7 PAL_LINE(GPIOB, 8)
+#    define B1 PAL_LINE(GPIOB, 13)
+#    define B3 PAL_LINE(GPIOB, 14)
+#    define B2 PAL_LINE(GPIOB, 15)
+#    define B6 PAL_LINE(GPIOB, 9)
+
+// LEDs (only D5/C13 uses an actual LED)
+#    ifdef CONVERT_TO_PROTON_C_RXLED
+#        define D5 PAL_LINE(GPIOC, 14)
+#        define B0 PAL_LINE(GPIOC, 13)
+#    else
+#        define D5 PAL_LINE(GPIOC, 13)
+#        define B0 PAL_LINE(GPIOC, 14)
+#    endif
+#else
+#    define A0 PAL_LINE(GPIOA, 0)
+#    define A1 PAL_LINE(GPIOA, 1)
+#    define A2 PAL_LINE(GPIOA, 2)
+#    define A3 PAL_LINE(GPIOA, 3)
+#    define A4 PAL_LINE(GPIOA, 4)
+#    define A5 PAL_LINE(GPIOA, 5)
+#    define A6 PAL_LINE(GPIOA, 6)
+#    define A7 PAL_LINE(GPIOA, 7)
+#    define A8 PAL_LINE(GPIOA, 8)
+#    define A9 PAL_LINE(GPIOA, 9)
+#    define A10 PAL_LINE(GPIOA, 10)
+#    define A11 PAL_LINE(GPIOA, 11)
+#    define A12 PAL_LINE(GPIOA, 12)
+#    define A13 PAL_LINE(GPIOA, 13)
+#    define A14 PAL_LINE(GPIOA, 14)
+#    define A15 PAL_LINE(GPIOA, 15)
+#    define A16 PAL_LINE(GPIOA, 16)
+#    define A17 PAL_LINE(GPIOA, 17)
+#    define A18 PAL_LINE(GPIOA, 18)
+#    define A19 PAL_LINE(GPIOA, 19)
+#    define A20 PAL_LINE(GPIOA, 20)
+#    define A21 PAL_LINE(GPIOA, 21)
+#    define A22 PAL_LINE(GPIOA, 22)
+#    define A23 PAL_LINE(GPIOA, 23)
+#    define A24 PAL_LINE(GPIOA, 24)
+#    define A25 PAL_LINE(GPIOA, 25)
+#    define A26 PAL_LINE(GPIOA, 26)
+#    define A27 PAL_LINE(GPIOA, 27)
+#    define A28 PAL_LINE(GPIOA, 28)
+#    define A29 PAL_LINE(GPIOA, 29)
+#    define A30 PAL_LINE(GPIOA, 30)
+#    define A31 PAL_LINE(GPIOA, 31)
+#    define A32 PAL_LINE(GPIOA, 32)
+#    define B0 PAL_LINE(GPIOB, 0)
+#    define B1 PAL_LINE(GPIOB, 1)
+#    define B2 PAL_LINE(GPIOB, 2)
+#    define B3 PAL_LINE(GPIOB, 3)
+#    define B4 PAL_LINE(GPIOB, 4)
+#    define B5 PAL_LINE(GPIOB, 5)
+#    define B6 PAL_LINE(GPIOB, 6)
+#    define B7 PAL_LINE(GPIOB, 7)
+#    define B8 PAL_LINE(GPIOB, 8)
+#    define B9 PAL_LINE(GPIOB, 9)
+#    define B10 PAL_LINE(GPIOB, 10)
+#    define B11 PAL_LINE(GPIOB, 11)
+#    define B12 PAL_LINE(GPIOB, 12)
+#    define B13 PAL_LINE(GPIOB, 13)
+#    define B14 PAL_LINE(GPIOB, 14)
+#    define B15 PAL_LINE(GPIOB, 15)
+#    define B16 PAL_LINE(GPIOB, 16)
+#    define B17 PAL_LINE(GPIOB, 17)
+#    define B18 PAL_LINE(GPIOB, 18)
+#    define B19 PAL_LINE(GPIOB, 19)
+#    define B20 PAL_LINE(GPIOB, 20)
+#    define B21 PAL_LINE(GPIOB, 21)
+#    define B22 PAL_LINE(GPIOB, 22)
+#    define B23 PAL_LINE(GPIOB, 23)
+#    define B24 PAL_LINE(GPIOB, 24)
+#    define B25 PAL_LINE(GPIOB, 25)
+#    define B26 PAL_LINE(GPIOB, 26)
+#    define B27 PAL_LINE(GPIOB, 27)
+#    define B28 PAL_LINE(GPIOB, 28)
+#    define B29 PAL_LINE(GPIOB, 29)
+#    define B30 PAL_LINE(GPIOB, 30)
+#    define B31 PAL_LINE(GPIOB, 31)
+#    define B32 PAL_LINE(GPIOB, 32)
+#    define C0 PAL_LINE(GPIOC, 0)
+#    define C1 PAL_LINE(GPIOC, 1)
+#    define C2 PAL_LINE(GPIOC, 2)
+#    define C3 PAL_LINE(GPIOC, 3)
+#    define C4 PAL_LINE(GPIOC, 4)
+#    define C5 PAL_LINE(GPIOC, 5)
+#    define C6 PAL_LINE(GPIOC, 6)
+#    define C7 PAL_LINE(GPIOC, 7)
+#    define C8 PAL_LINE(GPIOC, 8)
+#    define C9 PAL_LINE(GPIOC, 9)
+#    define C10 PAL_LINE(GPIOC, 10)
+#    define C11 PAL_LINE(GPIOC, 11)
+#    define C12 PAL_LINE(GPIOC, 12)
+#    define C13 PAL_LINE(GPIOC, 13)
+#    define C14 PAL_LINE(GPIOC, 14)
+#    define C15 PAL_LINE(GPIOC, 15)
+#    define C16 PAL_LINE(GPIOC, 16)
+#    define C17 PAL_LINE(GPIOC, 17)
+#    define C18 PAL_LINE(GPIOC, 18)
+#    define C19 PAL_LINE(GPIOC, 19)
+#    define C20 PAL_LINE(GPIOC, 20)
+#    define C21 PAL_LINE(GPIOC, 21)
+#    define C22 PAL_LINE(GPIOC, 22)
+#    define C23 PAL_LINE(GPIOC, 23)
+#    define C24 PAL_LINE(GPIOC, 24)
+#    define C25 PAL_LINE(GPIOC, 25)
+#    define C26 PAL_LINE(GPIOC, 26)
+#    define C27 PAL_LINE(GPIOC, 27)
+#    define C28 PAL_LINE(GPIOC, 28)
+#    define C29 PAL_LINE(GPIOC, 29)
+#    define C30 PAL_LINE(GPIOC, 30)
+#    define C31 PAL_LINE(GPIOC, 31)
+#    define C32 PAL_LINE(GPIOC, 32)
+#    define D0 PAL_LINE(GPIOD, 0)
+#    define D1 PAL_LINE(GPIOD, 1)
+#    define D2 PAL_LINE(GPIOD, 2)
+#    define D3 PAL_LINE(GPIOD, 3)
+#    define D4 PAL_LINE(GPIOD, 4)
+#    define D5 PAL_LINE(GPIOD, 5)
+#    define D6 PAL_LINE(GPIOD, 6)
+#    define D7 PAL_LINE(GPIOD, 7)
+#    define D8 PAL_LINE(GPIOD, 8)
+#    define D9 PAL_LINE(GPIOD, 9)
+#    define D10 PAL_LINE(GPIOD, 10)
+#    define D11 PAL_LINE(GPIOD, 11)
+#    define D12 PAL_LINE(GPIOD, 12)
+#    define D13 PAL_LINE(GPIOD, 13)
+#    define D14 PAL_LINE(GPIOD, 14)
+#    define D15 PAL_LINE(GPIOD, 15)
+#    define D16 PAL_LINE(GPIOD, 16)
+#    define D17 PAL_LINE(GPIOD, 17)
+#    define D18 PAL_LINE(GPIOD, 18)
+#    define D19 PAL_LINE(GPIOD, 19)
+#    define D20 PAL_LINE(GPIOD, 20)
+#    define D21 PAL_LINE(GPIOD, 21)
+#    define D22 PAL_LINE(GPIOD, 22)
+#    define D23 PAL_LINE(GPIOD, 23)
+#    define D24 PAL_LINE(GPIOD, 24)
+#    define D25 PAL_LINE(GPIOD, 25)
+#    define D26 PAL_LINE(GPIOD, 26)
+#    define D27 PAL_LINE(GPIOD, 27)
+#    define D28 PAL_LINE(GPIOD, 28)
+#    define D29 PAL_LINE(GPIOD, 29)
+#    define D30 PAL_LINE(GPIOD, 30)
+#    define D31 PAL_LINE(GPIOD, 31)
+#    define D32 PAL_LINE(GPIOD, 32)
+#    define E0 PAL_LINE(GPIOE, 0)
+#    define E1 PAL_LINE(GPIOE, 1)
+#    define E2 PAL_LINE(GPIOE, 2)
+#    define E3 PAL_LINE(GPIOE, 3)
+#    define E4 PAL_LINE(GPIOE, 4)
+#    define E5 PAL_LINE(GPIOE, 5)
+#    define E6 PAL_LINE(GPIOE, 6)
+#    define E7 PAL_LINE(GPIOE, 7)
+#    define E8 PAL_LINE(GPIOE, 8)
+#    define E9 PAL_LINE(GPIOE, 9)
+#    define E10 PAL_LINE(GPIOE, 10)
+#    define E11 PAL_LINE(GPIOE, 11)
+#    define E12 PAL_LINE(GPIOE, 12)
+#    define E13 PAL_LINE(GPIOE, 13)
+#    define E14 PAL_LINE(GPIOE, 14)
+#    define E15 PAL_LINE(GPIOE, 15)
+#    define E16 PAL_LINE(GPIOE, 16)
+#    define E17 PAL_LINE(GPIOE, 17)
+#    define E18 PAL_LINE(GPIOE, 18)
+#    define E19 PAL_LINE(GPIOE, 19)
+#    define E20 PAL_LINE(GPIOE, 20)
+#    define E21 PAL_LINE(GPIOE, 21)
+#    define E22 PAL_LINE(GPIOE, 22)
+#    define E23 PAL_LINE(GPIOE, 23)
+#    define E24 PAL_LINE(GPIOE, 24)
+#    define E25 PAL_LINE(GPIOE, 25)
+#    define E26 PAL_LINE(GPIOE, 26)
+#    define E27 PAL_LINE(GPIOE, 27)
+#    define E28 PAL_LINE(GPIOE, 28)
+#    define E29 PAL_LINE(GPIOE, 29)
+#    define E30 PAL_LINE(GPIOE, 30)
+#    define E31 PAL_LINE(GPIOE, 31)
+#    define E32 PAL_LINE(GPIOE, 32)
+#    define F0 PAL_LINE(GPIOF, 0)
+#    define F1 PAL_LINE(GPIOF, 1)
+#    define F2 PAL_LINE(GPIOF, 2)
+#    define F3 PAL_LINE(GPIOF, 3)
+#    define F4 PAL_LINE(GPIOF, 4)
+#    define F5 PAL_LINE(GPIOF, 5)
+#    define F6 PAL_LINE(GPIOF, 6)
+#    define F7 PAL_LINE(GPIOF, 7)
+#    define F8 PAL_LINE(GPIOF, 8)
+#    define F9 PAL_LINE(GPIOF, 9)
+#    define F10 PAL_LINE(GPIOF, 10)
+#    define F11 PAL_LINE(GPIOF, 11)
+#    define F12 PAL_LINE(GPIOF, 12)
+#    define F13 PAL_LINE(GPIOF, 13)
+#    define F14 PAL_LINE(GPIOF, 14)
+#    define F15 PAL_LINE(GPIOF, 15)
+#    define G0 PAL_LINE(GPIOG, 0)
+#    define G1 PAL_LINE(GPIOG, 1)
+#    define G2 PAL_LINE(GPIOG, 2)
+#    define G3 PAL_LINE(GPIOG, 3)
+#    define G4 PAL_LINE(GPIOG, 4)
+#    define G5 PAL_LINE(GPIOG, 5)
+#    define G6 PAL_LINE(GPIOG, 6)
+#    define G7 PAL_LINE(GPIOG, 7)
+#    define G8 PAL_LINE(GPIOG, 8)
+#    define G9 PAL_LINE(GPIOG, 9)
+#    define G10 PAL_LINE(GPIOG, 10)
+#    define G11 PAL_LINE(GPIOG, 11)
+#    define G12 PAL_LINE(GPIOG, 12)
+#    define G13 PAL_LINE(GPIOG, 13)
+#    define G14 PAL_LINE(GPIOG, 14)
+#    define G15 PAL_LINE(GPIOG, 15)
+#    define H0 PAL_LINE(GPIOH, 0)
+#    define H1 PAL_LINE(GPIOH, 1)
+#    define H2 PAL_LINE(GPIOH, 2)
+#    define H3 PAL_LINE(GPIOH, 3)
+#    define H4 PAL_LINE(GPIOH, 4)
+#    define H5 PAL_LINE(GPIOH, 5)
+#    define H6 PAL_LINE(GPIOH, 6)
+#    define H7 PAL_LINE(GPIOH, 7)
+#    define H8 PAL_LINE(GPIOH, 8)
+#    define H9 PAL_LINE(GPIOH, 9)
+#    define H10 PAL_LINE(GPIOH, 10)
+#    define H11 PAL_LINE(GPIOH, 11)
+#    define H12 PAL_LINE(GPIOH, 12)
+#    define H13 PAL_LINE(GPIOH, 13)
+#    define H14 PAL_LINE(GPIOH, 14)
+#    define H15 PAL_LINE(GPIOH, 15)
+#    define I0 PAL_LINE(GPIOI, 0)
+#    define I1 PAL_LINE(GPIOI, 1)
+#    define I2 PAL_LINE(GPIOI, 2)
+#    define I3 PAL_LINE(GPIOI, 3)
+#    define I4 PAL_LINE(GPIOI, 4)
+#    define I5 PAL_LINE(GPIOI, 5)
+#    define I6 PAL_LINE(GPIOI, 6)
+#    define I7 PAL_LINE(GPIOI, 7)
+#    define I8 PAL_LINE(GPIOI, 8)
+#    define I9 PAL_LINE(GPIOI, 9)
+#    define I10 PAL_LINE(GPIOI, 10)
+#    define I11 PAL_LINE(GPIOI, 11)
+#    define I12 PAL_LINE(GPIOI, 12)
+#    define I13 PAL_LINE(GPIOI, 13)
+#    define I14 PAL_LINE(GPIOI, 14)
+#    define I15 PAL_LINE(GPIOI, 15)
+#    define J0 PAL_LINE(GPIOJ, 0)
+#    define J1 PAL_LINE(GPIOJ, 1)
+#    define J2 PAL_LINE(GPIOJ, 2)
+#    define J3 PAL_LINE(GPIOJ, 3)
+#    define J4 PAL_LINE(GPIOJ, 4)
+#    define J5 PAL_LINE(GPIOJ, 5)
+#    define J6 PAL_LINE(GPIOJ, 6)
+#    define J7 PAL_LINE(GPIOJ, 7)
+#    define J8 PAL_LINE(GPIOJ, 8)
+#    define J9 PAL_LINE(GPIOJ, 9)
+#    define J10 PAL_LINE(GPIOJ, 10)
+#    define J11 PAL_LINE(GPIOJ, 11)
+#    define J12 PAL_LINE(GPIOJ, 12)
+#    define J13 PAL_LINE(GPIOJ, 13)
+#    define J14 PAL_LINE(GPIOJ, 14)
+#    define J15 PAL_LINE(GPIOJ, 15)
+// Keyboards can `#define KEYBOARD_REQUIRES_GPIOK` if they need to access GPIO-K pins. These conflict with a whole
+// bunch of layout definitions, so it's intentionally left out unless absolutely required -- in that case, the
+// keyboard designer should use a different symbol when defining their layout macros.
+#    ifdef KEYBOARD_REQUIRES_GPIOK
+#        define K0 PAL_LINE(GPIOK, 0)
+#        define K1 PAL_LINE(GPIOK, 1)
+#        define K2 PAL_LINE(GPIOK, 2)
+#        define K3 PAL_LINE(GPIOK, 3)
+#        define K4 PAL_LINE(GPIOK, 4)
+#        define K5 PAL_LINE(GPIOK, 5)
+#        define K6 PAL_LINE(GPIOK, 6)
+#        define K7 PAL_LINE(GPIOK, 7)
+#        define K8 PAL_LINE(GPIOK, 8)
+#        define K9 PAL_LINE(GPIOK, 9)
+#        define K10 PAL_LINE(GPIOK, 10)
+#        define K11 PAL_LINE(GPIOK, 11)
+#        define K12 PAL_LINE(GPIOK, 12)
+#        define K13 PAL_LINE(GPIOK, 13)
+#        define K14 PAL_LINE(GPIOK, 14)
+#        define K15 PAL_LINE(GPIOK, 15)
+#    endif
+#endif
diff --git a/platforms/chibios/platform.c b/platforms/chibios/platform.c
new file mode 100644
index 0000000000..d4a229f278
--- /dev/null
+++ b/platforms/chibios/platform.c
@@ -0,0 +1,22 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform_deps.h"
+
+void platform_setup(void) {
+    halInit();
+    chSysInit();
+}
\ No newline at end of file
diff --git a/platforms/chibios/platform.mk b/platforms/chibios/platform.mk
new file mode 100644
index 0000000000..6b298732c2
--- /dev/null
+++ b/platforms/chibios/platform.mk
@@ -0,0 +1,436 @@
+# Hey Emacs, this is a -*- makefile -*-
+##############################################################################
+# Architecture or project specific options
+#
+
+# Stack size to be allocated to the Cortex-M process stack. This stack is
+# the stack used by the main() thread.
+ifeq ($(USE_PROCESS_STACKSIZE),)
+  USE_PROCESS_STACKSIZE = 0x800
+endif
+
+# Stack size to the allocated to the Cortex-M main/exceptions stack. This
+# stack is used for processing interrupts and exceptions.
+ifeq ($(USE_EXCEPTIONS_STACKSIZE),)
+  USE_EXCEPTIONS_STACKSIZE = 0x400
+endif
+
+#
+# Architecture or project specific options
+##############################################################################
+
+##############################################################################
+# Project, sources and paths
+#
+
+# Imported source files and paths
+OPT_OS = chibios
+CHIBIOS = $(TOP_DIR)/lib/chibios
+CHIBIOS_CONTRIB = $(TOP_DIR)/lib/chibios-contrib
+
+#
+# Startup, Port and Platform support selection
+##############################################################################
+
+ifeq ($(strip $(MCU)), risc-v)
+    # RISC-V Support
+    # As of 7.4.2021 there is only one supported RISC-V platform in Chibios-Contrib,
+    # therefore all required settings are hard-coded
+    STARTUP_MK = $(CHIBIOS_CONTRIB)/os/common/startup/RISCV-ECLIC/compilers/GCC/mk/startup_$(MCU_STARTUP).mk
+    PORT_V = $(CHIBIOS_CONTRIB)/os/common/ports/RISCV-ECLIC/compilers/GCC/mk/port.mk
+    RULESPATH = $(CHIBIOS_CONTRIB)/os/common/startup/RISCV-ECLIC/compilers/GCC
+    PLATFORM_MK = $(CHIBIOS_CONTRIB)/os/hal/ports/GD/GD32VF103/platform.mk
+else
+    # ARM Support
+    CHIBIOS_PORT ?=
+    ifeq ("$(CHIBIOS_PORT)","")
+        CHIBIOS_PORT = ARMv$(ARMV)-M
+    endif
+
+    # Startup files. Try a few different locations, for compability with old versions and
+    # for things hardware in the contrib repository
+    STARTUP_MK = $(CHIBIOS)/os/common/ports/ARMCMx/compilers/GCC/mk/startup_$(MCU_STARTUP).mk
+    ifeq ("$(wildcard $(STARTUP_MK))","")
+        STARTUP_MK = $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC/mk/startup_$(MCU_STARTUP).mk
+        ifeq ("$(wildcard $(STARTUP_MK))","")
+            STARTUP_MK = $(CHIBIOS_CONTRIB)/os/common/startup/ARMCMx/compilers/GCC/mk/startup_$(MCU_STARTUP).mk
+        endif
+    endif
+
+    # Port files. Try a few different locations, for compability with old versions and
+    # for things hardware in the contrib repository
+    PORT_V = $(CHIBIOS)/os/common/ports/$(CHIBIOS_PORT)/compilers/GCC/mk/port.mk
+    ifeq ("$(wildcard $(PORT_V))","")
+        PORT_V = $(CHIBIOS)/os/rt/ports/ARMCMx/compilers/GCC/mk/port_v$(ARMV)m.mk
+        ifeq ("$(wildcard $(PORT_V))","")
+            PORT_V = $(CHIBIOS)/os/common/ports/ARMCMx/compilers/GCC/mk/port_v$(ARMV)m.mk
+        endif
+    endif
+
+    # Rules location. Try a few different locations, for compability with old versions and
+    # for things hardware in the contrib repository
+    RULESPATH = $(CHIBIOS)/os/common/ports/$(CHIBIOS_PORT)/compilers/GCC
+    ifeq ("$(wildcard $(RULESPATH)/rules.mk)","")
+        RULESPATH = $(CHIBIOS)/os/common/ports/ARMCMx/compilers/GCC
+        ifeq ("$(wildcard $(RULESPATH)/rules.mk)","")
+            RULESPATH = $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC
+        endif
+    endif
+endif
+
+ifeq ("$(PLATFORM_NAME)","")
+    PLATFORM_NAME = platform
+endif
+
+ifeq ("$(wildcard $(PLATFORM_MK))","")
+    PLATFORM_MK = $(CHIBIOS)/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)/$(PLATFORM_NAME).mk
+    ifeq ("$(wildcard $(PLATFORM_MK))","")
+        PLATFORM_MK = $(CHIBIOS_CONTRIB)/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)/$(PLATFORM_NAME).mk
+    endif
+endif
+
+include $(STARTUP_MK)
+include $(PORT_V)
+include $(PLATFORM_MK)
+
+#
+# Board support selection.
+##############################################################################
+
+BOARD_MK :=
+
+ifneq ("$(wildcard $(KEYBOARD_PATH_5)/boards/$(BOARD)/board.mk)","")
+    BOARD_PATH = $(KEYBOARD_PATH_5)
+    BOARD_MK += $(KEYBOARD_PATH_5)/boards/$(BOARD)/board.mk
+else ifneq ("$(wildcard $(KEYBOARD_PATH_4)/boards/$(BOARD)/board.mk)","")
+    BOARD_PATH = $(KEYBOARD_PATH_4)
+    BOARD_MK += $(KEYBOARD_PATH_4)/boards/$(BOARD)/board.mk
+else ifneq ("$(wildcard $(KEYBOARD_PATH_3)/boards/$(BOARD)/board.mk)","")
+    BOARD_PATH = $(KEYBOARD_PATH_3)
+    BOARD_MK += $(KEYBOARD_PATH_3)/boards/$(BOARD)/board.mk
+else ifneq ("$(wildcard $(KEYBOARD_PATH_2)/boards/$(BOARD)/board.mk)","")
+    BOARD_PATH = $(KEYBOARD_PATH_2)
+    BOARD_MK += $(KEYBOARD_PATH_2)/boards/$(BOARD)/board.mk
+else ifneq ("$(wildcard $(KEYBOARD_PATH_1)/boards/$(BOARD)/board.mk)","")
+    BOARD_PATH = $(KEYBOARD_PATH_1)
+    BOARD_MK += $(KEYBOARD_PATH_1)/boards/$(BOARD)/board.mk
+else ifneq ("$(wildcard $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/board/board.mk)","")
+    BOARD_PATH = $(TOP_DIR)/platforms/chibios/boards/$(BOARD)
+    BOARD_MK += $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/board/board.mk
+    KEYBOARD_PATHS += $(BOARD_PATH)/configs
+    ifneq ("$(wildcard $(BOARD_PATH)/rules.mk)","")
+        include $(BOARD_PATH)/rules.mk
+    endif
+endif
+
+ifeq ("$(wildcard $(BOARD_MK))","")
+    BOARD_MK = $(CHIBIOS)/os/hal/boards/$(BOARD)/board.mk
+    ifeq ("$(wildcard $(BOARD_MK))","")
+        BOARD_MK = $(CHIBIOS_CONTRIB)/os/hal/boards/$(BOARD)/board.mk
+    endif
+endif
+
+include $(BOARD_MK)
+
+#
+# Bootloader selection.
+##############################################################################
+
+# Set bootloader address if supplied.
+ifdef STM32_BOOTLOADER_ADDRESS
+    OPT_DEFS += -DSTM32_BOOTLOADER_ADDRESS=$(STM32_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
+else ifneq ("$(wildcard $(KEYBOARD_PATH_5)/boards/$(BOARD)/bootloader_defs.h)","")
+    OPT_DEFS += -include $(KEYBOARD_PATH_5)/boards/$(BOARD)/bootloader_defs.h
+else ifneq ("$(wildcard $(KEYBOARD_PATH_4)/bootloader_defs.h)","")
+    OPT_DEFS += -include $(KEYBOARD_PATH_4)/bootloader_defs.h
+else ifneq ("$(wildcard $(KEYBOARD_PATH_4)/boards/$(BOARD)/bootloader_defs.h)","")
+    OPT_DEFS += -include $(KEYBOARD_PATH_4)/boards/$(BOARD)/bootloader_defs.h
+else ifneq ("$(wildcard $(KEYBOARD_PATH_3)/bootloader_defs.h)","")
+    OPT_DEFS += -include $(KEYBOARD_PATH_3)/bootloader_defs.h
+else ifneq ("$(wildcard $(KEYBOARD_PATH_3)/boards/$(BOARD)/bootloader_defs.h)","")
+    OPT_DEFS += -include $(KEYBOARD_PATH_3)/boards/$(BOARD)/bootloader_defs.h
+else ifneq ("$(wildcard $(KEYBOARD_PATH_2)/bootloader_defs.h)","")
+    OPT_DEFS += -include $(KEYBOARD_PATH_2)/bootloader_defs.h
+else ifneq ("$(wildcard $(KEYBOARD_PATH_2)/boards/$(BOARD)/bootloader_defs.h)","")
+    OPT_DEFS += -include $(KEYBOARD_PATH_2)/boards/$(BOARD)/bootloader_defs.h
+else ifneq ("$(wildcard $(KEYBOARD_PATH_1)/bootloader_defs.h)","")
+    OPT_DEFS += -include $(KEYBOARD_PATH_1)/bootloader_defs.h
+else ifneq ("$(wildcard $(KEYBOARD_PATH_1)/boards/$(BOARD)/bootloader_defs.h)","")
+    OPT_DEFS += -include $(KEYBOARD_PATH_1)/boards/$(BOARD)/bootloader_defs.h
+else ifneq ("$(wildcard $(BOARD_PATH)/configs/bootloader_defs.h)","")
+    OPT_DEFS += -include $(BOARD_PATH)/configs/bootloader_defs.h
+endif
+
+#
+# ChibiOS config selection.
+##############################################################################
+
+# Work out the config file directories
+ifneq ("$(wildcard $(KEYBOARD_PATH_5)/chconf.h)","")
+    CHCONFDIR = $(KEYBOARD_PATH_5)
+else ifneq ("$(wildcard $(KEYBOARD_PATH_4)/chconf.h)","")
+    CHCONFDIR = $(KEYBOARD_PATH_4)
+else ifneq ("$(wildcard $(KEYBOARD_PATH_3)/chconf.h)","")
+    CHCONFDIR = $(KEYBOARD_PATH_3)
+else ifneq ("$(wildcard $(KEYBOARD_PATH_2)/chconf.h)","")
+    CHCONFDIR = $(KEYBOARD_PATH_2)
+else ifneq ("$(wildcard $(KEYBOARD_PATH_1)/chconf.h)","")
+    CHCONFDIR = $(KEYBOARD_PATH_1)
+else ifneq ("$(wildcard $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/configs/chconf.h)","")
+    CHCONFDIR = $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/configs
+else ifneq ("$(wildcard $(TOP_DIR)/platforms/boards/chibios/common/configs/chconf.h)","")
+    CHCONFDIR = $(TOP_DIR)/platforms/chibios/boards/common/configs
+endif
+
+#
+# HAL config selection.
+##############################################################################
+
+ifneq ("$(wildcard $(KEYBOARD_PATH_5)/halconf.h)","")
+    HALCONFDIR = $(KEYBOARD_PATH_5)
+else ifneq ("$(wildcard $(KEYBOARD_PATH_4)/halconf.h)","")
+    HALCONFDIR = $(KEYBOARD_PATH_4)
+else ifneq ("$(wildcard $(KEYBOARD_PATH_3)/halconf.h)","")
+    HALCONFDIR = $(KEYBOARD_PATH_3)
+else ifneq ("$(wildcard $(KEYBOARD_PATH_2)/halconf.h)","")
+    HALCONFDIR = $(KEYBOARD_PATH_2)
+else ifneq ("$(wildcard $(KEYBOARD_PATH_1)/halconf.h)","")
+    HALCONFDIR = $(KEYBOARD_PATH_1)
+else ifneq ("$(wildcard $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/configs/halconf.h)","")
+    HALCONFDIR = $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/configs
+else ifneq ("$(wildcard $(TOP_DIR)/platforms/chibios/boards/common/configs/halconf.h)","")
+    HALCONFDIR = $(TOP_DIR)/platforms/chibios/boards/common/configs
+endif
+
+#
+# Linker script selection.
+##############################################################################
+
+ifneq ("$(wildcard $(KEYBOARD_PATH_5)/ld/$(MCU_LDSCRIPT).ld)","")
+    LDSCRIPT = $(KEYBOARD_PATH_5)/ld/$(MCU_LDSCRIPT).ld
+else ifneq ("$(wildcard $(KEYBOARD_PATH_4)/ld/$(MCU_LDSCRIPT).ld)","")
+    LDSCRIPT = $(KEYBOARD_PATH_4)/ld/$(MCU_LDSCRIPT).ld
+else ifneq ("$(wildcard $(KEYBOARD_PATH_3)/ld/$(MCU_LDSCRIPT).ld)","")
+    LDSCRIPT = $(KEYBOARD_PATH_3)/ld/$(MCU_LDSCRIPT).ld
+else ifneq ("$(wildcard $(KEYBOARD_PATH_2)/ld/$(MCU_LDSCRIPT).ld)","")
+    LDSCRIPT = $(KEYBOARD_PATH_2)/ld/$(MCU_LDSCRIPT).ld
+else ifneq ("$(wildcard $(KEYBOARD_PATH_1)/ld/$(MCU_LDSCRIPT).ld)","")
+    LDSCRIPT = $(KEYBOARD_PATH_1)/ld/$(MCU_LDSCRIPT).ld
+else ifneq ("$(wildcard $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/ld/$(MCU_LDSCRIPT).ld)","")
+    LDFLAGS += -L$(TOP_DIR)/platforms/chibios/boards/$(BOARD)/ld
+    LDSCRIPT = $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/ld/$(MCU_LDSCRIPT).ld
+else ifneq ("$(wildcard $(TOP_DIR)/platforms/chibios/boards/common/ld/$(MCU_LDSCRIPT).ld)","")
+    LDSCRIPT = $(TOP_DIR)/platforms/chibios/boards/common/ld/$(MCU_LDSCRIPT).ld
+else ifneq ("$(wildcard $(STARTUPLD_CONTRIB)/$(MCU_LDSCRIPT).ld)","")
+    LDSCRIPT = $(STARTUPLD_CONTRIB)/$(MCU_LDSCRIPT).ld
+    USE_CHIBIOS_CONTRIB = yes
+else
+    LDSCRIPT = $(STARTUPLD)/$(MCU_LDSCRIPT).ld
+endif
+
+#
+# Include ChibiOS makefiles.
+##############################################################################
+
+# HAL-OSAL files (optional).
+include $(CHIBIOS)/os/hal/hal.mk
+-include $(CHIBIOS)/os/hal/osal/rt/osal.mk         # ChibiOS <= 19.x
+-include $(CHIBIOS)/os/hal/osal/rt-nil/osal.mk     # ChibiOS >= 20.x
+# RTOS files (optional).
+include $(CHIBIOS)/os/rt/rt.mk
+# Other files (optional).
+include $(CHIBIOS)/os/hal/lib/streams/streams.mk
+
+PLATFORM_SRC = \
+        $(STARTUPSRC) \
+        $(KERNSRC) \
+        $(PORTSRC) \
+        $(OSALSRC) \
+        $(HALSRC) \
+        $(PLATFORMSRC) \
+        $(BOARDSRC) \
+        $(STREAMSSRC) \
+        $(CHIBIOS)/os/various/syscalls.c \
+        $(PLATFORM_COMMON_DIR)/syscall-fallbacks.c \
+        $(PLATFORM_COMMON_DIR)/wait.c
+
+# Ensure the ASM files are not subjected to LTO -- it'll strip out interrupt handlers otherwise.
+QUANTUM_LIB_SRC += $(STARTUPASM) $(PORTASM) $(OSALASM) $(PLATFORMASM)
+
+PLATFORM_SRC := $(patsubst $(TOP_DIR)/%,%,$(PLATFORM_SRC))
+
+EXTRAINCDIRS += $(CHIBIOS)/os/license $(CHIBIOS)/os/oslib/include \
+         $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/configs \
+         $(TOP_DIR)/platforms/chibios/boards/common/configs \
+         $(HALCONFDIR) $(CHCONFDIR) \
+         $(STARTUPINC) $(KERNINC) $(PORTINC) $(OSALINC) \
+         $(HALINC) $(PLATFORMINC) $(BOARDINC) $(TESTINC) \
+         $(STREAMSINC) $(CHIBIOS)/os/various $(COMMON_VPATH)
+
+#
+# ChibiOS-Contrib
+##############################################################################
+
+# Work out if we're using ChibiOS-Contrib by checking if halconf_community.h exists
+ifneq ("$(wildcard $(KEYBOARD_PATH_5)/halconf_community.h)","")
+    USE_CHIBIOS_CONTRIB = yes
+else ifneq ("$(wildcard $(KEYBOARD_PATH_4)/halconf_community.h)","")
+    USE_CHIBIOS_CONTRIB = yes
+else ifneq ("$(wildcard $(KEYBOARD_PATH_3)/halconf_community.h)","")
+    USE_CHIBIOS_CONTRIB = yes
+else ifneq ("$(wildcard $(KEYBOARD_PATH_2)/halconf_community.h)","")
+    USE_CHIBIOS_CONTRIB = yes
+else ifneq ("$(wildcard $(KEYBOARD_PATH_1)/halconf_community.h)","")
+    USE_CHIBIOS_CONTRIB = yes
+else ifneq ("$(wildcard $(TOP_DIR)/platforms/chibios/boards/$(BOARD)/configs/halconf_community.h)","")
+    USE_CHIBIOS_CONTRIB = yes
+endif
+
+ifeq ($(strip $(USE_CHIBIOS_CONTRIB)),yes)
+    include $(CHIBIOS_CONTRIB)/os/hal/hal.mk
+    PLATFORM_SRC += $(PLATFORMSRC_CONTRIB) $(HALSRC_CONTRIB)
+    EXTRAINCDIRS += $(PLATFORMINC_CONTRIB) $(HALINC_CONTRIB) $(CHIBIOS_CONTRIB)/os/various
+endif
+
+#
+# Project, sources and paths
+##############################################################################
+
+##############################################################################
+# Injected configs
+#
+ifneq ("$(wildcard $(BOARD_PATH)/configs/config.h)","")
+    CONFIG_H += $(BOARD_PATH)/configs/config.h
+endif
+ifneq ("$(wildcard $(BOARD_PATH)/configs/post_config.h)","")
+    POST_CONFIG_H += $(BOARD_PATH)/configs/post_config.h
+endif
+
+##############################################################################
+# Compiler and Linker configuration
+#
+
+# Use defined stack sizes of the main thread in linker scripts
+LDSYMBOLS =--defsym=__process_stack_size__=$(USE_PROCESS_STACKSIZE),--defsym=__main_stack_size__=$(USE_EXCEPTIONS_STACKSIZE)
+
+# Shared Compiler flags for all toolchains
+SHARED_CFLAGS = -fomit-frame-pointer \
+                -ffunction-sections \
+                -fdata-sections \
+                -fno-common \
+                -fshort-wchar
+
+# Shared Linker flags for all toolchains
+SHARED_LDFLAGS = -T $(LDSCRIPT) \
+                 -Wl,$(LDSYMBOLS) \
+                 -Wl,--gc-sections \
+                 -nostartfiles
+
+ifeq ($(strip $(MCU)), risc-v)
+    # RISC-V toolchain specific configuration
+    # Find suitable GCC compiler
+    ifeq ($(strip $(TOOLCHAIN)),)
+        ifneq ($(shell which riscv32-unknown-elf-gcc 2>/dev/null),)
+            TOOLCHAIN = riscv32-unknown-elf-
+        else
+            ifneq ($(shell which riscv64-unknown-elf-gcc 2>/dev/null),)
+                TOOLCHAIN = riscv64-unknown-elf-
+            else
+                $(error "No RISC-V toolchain found. Can't find riscv32-unknown-elf-gcc or riscv64-unknown-elf-gcc found in your systems PATH variable. Please install a valid toolchain and make it accessible!")
+            endif
+        endif
+    endif
+
+    # Default to compiling with picolibc for RISC-V targets if available,
+    # which is available by default on current (bullseye) debian based systems.
+    ifeq ($(shell $(TOOLCHAIN)gcc --specs=picolibc.specs -E - 2>/dev/null >/dev/null </dev/null ; echo $$?),0)
+        # Toolchain specific Compiler flags
+        # Note that we still link with our own linker script
+        # by providing it via the -T flag above.
+        TOOLCHAIN_CFLAGS = --specs=picolibc.specs
+
+        # Tell QMK that we are compiling with picolibc.
+        OPT_DEFS += -DUSE_PICOLIBC
+    endif
+
+    # MCU architecture flags
+    MCUFLAGS = -march=$(MCU_ARCH) \
+               -mabi=$(MCU_ABI) \
+               -mcmodel=$(MCU_CMODEL) \
+               -mstrict-align
+else
+    # ARM toolchain specific configuration
+    TOOLCHAIN ?= arm-none-eabi-
+
+    # Toolchain specific Linker flags
+    TOOLCHAIN_LDFLAGS = -Wl,--no-wchar-size-warning \
+                        --specs=nano.specs
+
+    # MCU architecture flags
+    MCUFLAGS = -mcpu=$(MCU) \
+               -mthumb -DTHUMB_PRESENT \
+               -mno-thumb-interwork -DTHUMB_NO_INTERWORKING \
+               -mno-unaligned-access
+
+    # Some ARM cores like the M4 and M7 have floating point units which can be enabled
+    USE_FPU ?= no
+
+    ifneq ($(USE_FPU),no)
+        OPT_DEFS += -DCORTEX_USE_FPU=TRUE
+
+        # Default is single precision floats
+        USE_FPU_OPT ?= -mfloat-abi=hard \
+                       -mfpu=fpv4-sp-d16 \
+                       -fsingle-precision-constant
+
+        MCUFLAGS += $(USE_FPU_OPT)
+    else
+        OPT_DEFS += -DCORTEX_USE_FPU=FALSE
+    endif
+endif
+
+# Assembler flags
+ASFLAGS  += $(SHARED_ASFLAGS) $(TOOLCHAIN_ASFLAGS)
+
+# C Compiler flags
+CFLAGS   += $(SHARED_CFLAGS) $(TOOLCHAIN_CFLAGS)
+
+# C++ Compiler flags
+CXXFLAGS += $(CFLAGS) $(SHARED_CXXFLAGS) $(TOOLCHAIN_CXXFLAGS) -fno-rtti
+
+# Linker flags
+LDFLAGS  += $(SHARED_LDFLAGS) $(TOOLCHAIN_LDFLAGS) $(MCUFLAGS)
+
+# Tell QMK that we are hosting it on ChibiOS.
+OPT_DEFS += -DPROTOCOL_CHIBIOS
+
+# Workaround to stop ChibiOS from complaining about new GCC -- it's been fixed for 7/8/9 already
+OPT_DEFS += -DPORT_IGNORE_GCC_VERSION_CHECK=1
+
+# Construct GCC toolchain
+CC      = $(CC_PREFIX) $(TOOLCHAIN)gcc
+OBJCOPY = $(TOOLCHAIN)objcopy
+OBJDUMP = $(TOOLCHAIN)objdump
+SIZE    = $(TOOLCHAIN)size
+AR      = $(TOOLCHAIN)ar
+NM      = $(TOOLCHAIN)nm
+HEX     = $(OBJCOPY) -O $(FORMAT)
+EEP     =
+BIN     = $(OBJCOPY) -O binary
+
+##############################################################################
+# Make targets
+#
+
+DEBUG = gdb
+
+# List any extra directories to look for libraries here.
+EXTRALIBDIRS = $(RULESPATH)/ld
+
+bin: $(BUILD_DIR)/$(TARGET).bin sizeafter
+	$(COPY) $(BUILD_DIR)/$(TARGET).bin $(TARGET).bin;
diff --git a/platforms/chibios/platform_deps.h b/platforms/chibios/platform_deps.h
new file mode 100644
index 0000000000..8243dcec53
--- /dev/null
+++ b/platforms/chibios/platform_deps.h
@@ -0,0 +1,19 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <hal.h>
+#include "chibios_config.h"
diff --git a/platforms/chibios/sleep_led.c b/platforms/chibios/sleep_led.c
new file mode 100644
index 0000000000..477056a454
--- /dev/null
+++ b/platforms/chibios/sleep_led.c
@@ -0,0 +1,192 @@
+#include <ch.h>
+#include <hal.h>
+
+#include "led.h"
+#include "sleep_led.h"
+
+/* All right, we go the "software" way: timer, toggle LED in interrupt.
+ * Based on hasu's code for AVRs.
+ * Use LP timer on Kinetises, TIM14 on STM32F0.
+ */
+
+#ifndef SLEEP_LED_GPT_DRIVER
+#    if defined(STM32F0XX)
+#        define SLEEP_LED_GPT_DRIVER GPTD14
+#    endif
+#endif
+
+#if defined(KL2x) || defined(K20x) || defined(SLEEP_LED_GPT_DRIVER) /* common parts for timers/interrupts */
+
+/* Breathing Sleep LED brighness(PWM On period) table
+ * (64[steps] * 4[duration]) / 64[PWM periods/s] = 4 second breath cycle
+ *
+ * http://www.wolframalpha.com/input/?i=%28sin%28+x%2F64*pi%29**8+*+255%2C+x%3D0+to+63
+ * (0..63).each {|x| p ((sin(x/64.0*PI)**8)*255).to_i }
+ */
+static const uint8_t breathing_table[64] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 6, 10, 15, 23, 32, 44, 58, 74, 93, 113, 135, 157, 179, 199, 218, 233, 245, 252, 255, 252, 245, 233, 218, 199, 179, 157, 135, 113, 93, 74, 58, 44, 32, 23, 15, 10, 6, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+void sleep_led_timer_callback(void) {
+    /* Software PWM
+     * timer:1111 1111 1111 1111
+     *       \_____/\/ \_______/____  count(0-255)
+     *          \    \______________  duration of step(4)
+     *           \__________________  index of step table(0-63)
+     */
+
+    // this works for cca 65536 irqs/sec
+    static union {
+        uint16_t row;
+        struct {
+            uint8_t count : 8;
+            uint8_t duration : 2;
+            uint8_t index : 6;
+        } pwm;
+    } timer = {.row = 0};
+
+    timer.row++;
+
+    // LED on
+    if (timer.pwm.count == 0) {
+        led_set(1 << USB_LED_CAPS_LOCK);
+    }
+    // LED off
+    if (timer.pwm.count == breathing_table[timer.pwm.index]) {
+        led_set(0);
+    }
+}
+
+#endif /* common parts for known platforms */
+
+#if defined(KL2x) || defined(K20x) /* platform selection: familiar Kinetis chips */
+
+/* Use Low Power Timer (LPTMR) */
+#    define TIMER_INTERRUPT_VECTOR KINETIS_LPTMR0_IRQ_VECTOR
+#    define RESET_COUNTER LPTMR0->CSR |= LPTMRx_CSR_TCF
+
+/* LPTMR clock options */
+#    define LPTMR_CLOCK_MCGIRCLK 0 /* 4MHz clock */
+#    define LPTMR_CLOCK_LPO 1      /* 1kHz clock */
+#    define LPTMR_CLOCK_ERCLK32K 2 /* external 32kHz crystal */
+#    define LPTMR_CLOCK_OSCERCLK 3 /* output from OSC */
+
+/* Work around inconsistencies in Freescale naming */
+#    if !defined(SIM_SCGC5_LPTMR)
+#        define SIM_SCGC5_LPTMR SIM_SCGC5_LPTIMER
+#    endif
+
+/* interrupt handler */
+OSAL_IRQ_HANDLER(TIMER_INTERRUPT_VECTOR) {
+    OSAL_IRQ_PROLOGUE();
+
+    sleep_led_timer_callback();
+
+    /* Reset the counter */
+    RESET_COUNTER;
+
+    OSAL_IRQ_EPILOGUE();
+}
+
+/* Initialise the timer */
+void sleep_led_init(void) {
+    /* Make sure the clock to the LPTMR is enabled */
+    SIM->SCGC5 |= SIM_SCGC5_LPTMR;
+    /* Reset LPTMR settings */
+    LPTMR0->CSR = 0;
+    /* Set the compare value */
+    LPTMR0->CMR = 0;  // trigger on counter value (i.e. every time)
+
+/* Set up clock source and prescaler */
+/* Software PWM
+ *  ______           ______           __
+ * |  ON  |___OFF___|  ON  |___OFF___|   ....
+ * |<-------------->|<-------------->|<- ....
+ *     PWM period       PWM period
+ *
+ * R                interrupts/period[resolution]
+ * F                periods/second[frequency]
+ * R * F            interrupts/second
+ */
+
+/* === OPTION 1 === */
+#    if 0
+    //  1kHz LPO
+    //  No prescaler => 1024 irqs/sec
+    //  Note: this is too slow for a smooth breathe
+    LPTMR0->PSR = LPTMRx_PSR_PCS(LPTMR_CLOCK_LPO)|LPTMRx_PSR_PBYP;
+#    endif /* OPTION 1 */
+
+/* === OPTION 2 === */
+#    if 1
+    //  nMHz IRC (n=4 on KL25Z, KL26Z and K20x; n=2 or 8 on KL27Z)
+    MCG->C2 |= MCG_C2_IRCS;  // fast (4MHz) internal ref clock
+#        if defined(KL27)    // divide the 8MHz IRC by 2, to have the same MCGIRCLK speed as others
+    MCG->MC |= MCG_MC_LIRC_DIV2_DIV2;
+#        endif                  /* KL27 */
+    MCG->C1 |= MCG_C1_IRCLKEN;  // enable internal ref clock
+    //  to work in stop mode, also MCG_C1_IREFSTEN
+    //  Divide 4MHz by 2^N (N=6) => 62500 irqs/sec =>
+    //  => approx F=61, R=256, duration = 4
+    LPTMR0->PSR = LPTMRx_PSR_PCS(LPTMR_CLOCK_MCGIRCLK) | LPTMRx_PSR_PRESCALE(6);
+#    endif /* OPTION 2 */
+
+/* === OPTION 3 === */
+#    if 0
+    //  OSC output (external crystal), usually 8MHz or 16MHz
+    OSC0->CR |= OSC_CR_ERCLKEN; // enable ext ref clock
+    //  to work in stop mode, also OSC_CR_EREFSTEN
+    //  Divide by 2^N
+    LPTMR0->PSR = LPTMRx_PSR_PCS(LPTMR_CLOCK_OSCERCLK)|LPTMRx_PSR_PRESCALE(7);
+#    endif /* OPTION 3 */
+    /* === END OPTIONS === */
+
+    /* Interrupt on TCF set (compare flag) */
+    nvicEnableVector(LPTMR0_IRQn, 2);  // vector, priority
+    LPTMR0->CSR |= LPTMRx_CSR_TIE;
+}
+
+void sleep_led_enable(void) {
+    /* Enable the timer */
+    LPTMR0->CSR |= LPTMRx_CSR_TEN;
+}
+
+void sleep_led_disable(void) {
+    /* Disable the timer */
+    LPTMR0->CSR &= ~LPTMRx_CSR_TEN;
+}
+
+void sleep_led_toggle(void) {
+    /* Toggle the timer */
+    LPTMR0->CSR ^= LPTMRx_CSR_TEN;
+}
+
+#elif defined(SLEEP_LED_GPT_DRIVER)
+
+static void gptTimerCallback(GPTDriver *gptp) {
+    (void)gptp;
+    sleep_led_timer_callback();
+}
+
+static const GPTConfig gptcfg = {1000000, gptTimerCallback, 0, 0};
+
+/* Initialise the timer */
+void sleep_led_init(void) { gptStart(&SLEEP_LED_GPT_DRIVER, &gptcfg); }
+
+void sleep_led_enable(void) { gptStartContinuous(&SLEEP_LED_GPT_DRIVER, gptcfg.frequency / 0xFFFF); }
+
+void sleep_led_disable(void) { gptStopTimer(&SLEEP_LED_GPT_DRIVER); }
+
+void sleep_led_toggle(void) { (SLEEP_LED_GPT_DRIVER.state == GPT_READY) ? sleep_led_enable() : sleep_led_disable(); }
+
+#else /* platform selection: not on familiar chips */
+
+void sleep_led_init(void) {}
+
+void sleep_led_enable(void) { led_set(1 << USB_LED_CAPS_LOCK); }
+
+void sleep_led_disable(void) { led_set(0); }
+
+void sleep_led_toggle(void) {
+    // not implemented
+}
+
+#endif /* platform selection */
diff --git a/platforms/chibios/suspend.c b/platforms/chibios/suspend.c
new file mode 100644
index 0000000000..9310a99920
--- /dev/null
+++ b/platforms/chibios/suspend.c
@@ -0,0 +1,92 @@
+/* TODO */
+
+#include <ch.h>
+#include <hal.h>
+
+#include "matrix.h"
+#include "action.h"
+#include "action_util.h"
+#include "mousekey.h"
+#include "programmable_button.h"
+#include "host.h"
+#include "suspend.h"
+#include "led.h"
+#include "wait.h"
+
+/** \brief suspend idle
+ *
+ * FIXME: needs doc
+ */
+void suspend_idle(uint8_t time) {
+    // TODO: this is not used anywhere - what units is 'time' in?
+    wait_ms(time);
+}
+
+/** \brief suspend power down
+ *
+ * FIXME: needs doc
+ */
+void suspend_power_down(void) {
+    suspend_power_down_quantum();
+    // on AVR, this enables the watchdog for 15ms (max), and goes to
+    // SLEEP_MODE_PWR_DOWN
+
+    wait_ms(17);
+}
+
+/** \brief suspend wakeup condition
+ *
+ * FIXME: needs doc
+ */
+__attribute__((weak)) void matrix_power_up(void) {}
+__attribute__((weak)) void matrix_power_down(void) {}
+bool                       suspend_wakeup_condition(void) {
+    matrix_power_up();
+    matrix_scan();
+    matrix_power_down();
+    for (uint8_t r = 0; r < MATRIX_ROWS; r++) {
+        if (matrix_get_row(r)) return true;
+    }
+    return false;
+}
+
+/** \brief run user level code immediately after wakeup
+ *
+ * FIXME: needs doc
+ */
+__attribute__((weak)) void suspend_wakeup_init_user(void) {}
+
+/** \brief run keyboard level code immediately after wakeup
+ *
+ * FIXME: needs doc
+ */
+__attribute__((weak)) void suspend_wakeup_init_kb(void) { suspend_wakeup_init_user(); }
+
+/** \brief suspend wakeup condition
+ *
+ * run immediately after wakeup
+ * FIXME: needs doc
+ */
+void suspend_wakeup_init(void) {
+    // clear keyboard state
+    // need to do it manually, because we're running from ISR
+    //  and clear_keyboard() calls print
+    // so only clear the variables in memory
+    // the reports will be sent from main.c afterwards
+    // or if the PC asks for GET_REPORT
+    clear_mods();
+    clear_weak_mods();
+    clear_keys();
+#ifdef MOUSEKEY_ENABLE
+    mousekey_clear();
+#endif /* MOUSEKEY_ENABLE */
+#ifdef PROGRAMMABLE_BUTTON_ENABLE
+    programmable_button_clear();
+#endif /* PROGRAMMABLE_BUTTON_ENABLE */
+#ifdef EXTRAKEY_ENABLE
+    host_system_send(0);
+    host_consumer_send(0);
+#endif /* EXTRAKEY_ENABLE */
+
+    suspend_wakeup_init_quantum();
+}
diff --git a/platforms/chibios/syscall-fallbacks.c b/platforms/chibios/syscall-fallbacks.c
new file mode 100644
index 0000000000..4569879c7c
--- /dev/null
+++ b/platforms/chibios/syscall-fallbacks.c
@@ -0,0 +1,110 @@
+/* Copyright 2021 Nick Brassel, QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+/* To compile the ChibiOS syscall stubs with picolibc
+ * the _reent struct has to be defined. */
+#if defined(USE_PICOLIBC)
+struct _reent;
+#endif
+
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+
+__attribute__((weak, used)) int _open_r(struct _reent *r, const char *path, int flag, int m) {
+    __errno_r(r) = ENOENT;
+    return -1;
+}
+
+__attribute__((weak, used)) int _lseek_r(struct _reent *r, int file, int ptr, int dir) {
+    __errno_r(r) = EBADF;
+    return -1;
+}
+
+__attribute__((weak, used)) int _read_r(struct _reent *r, int file, char *ptr, int len) {
+    __errno_r(r) = EBADF;
+    return -1;
+}
+
+__attribute__((weak, used)) int _write_r(struct _reent *r, int file, char *ptr, int len) {
+    __errno_r(r) = EBADF;
+    return -1;
+}
+
+__attribute__((weak, used)) int _close_r(struct _reent *r, int file) {
+    __errno_r(r) = EBADF;
+    return -1;
+}
+
+__attribute__((weak, used)) int _link_r(struct _reent *r, const char *oldpath, const char *newpath) {
+    __errno_r(r) = EPERM;
+    return -1;
+}
+
+__attribute__((weak, used)) int _unlink_r(struct _reent *r, const char *path) {
+    __errno_r(r) = EPERM;
+    return -1;
+}
+
+__attribute__((weak, used)) clock_t _times_r(struct _reent *r, void *t) {
+    __errno_r(r) = EFAULT;
+    return -1;
+}
+
+__attribute__((weak, used)) int _fstat_r(struct _reent *r, int file, struct stat *st) {
+    __errno_r(r) = EBADF;
+    return -1;
+}
+
+__attribute__((weak, used)) int _isatty_r(struct _reent *r, int fd) {
+    __errno_r(r) = EBADF;
+    return 0;
+}
+
+__attribute__((weak, used)) caddr_t _sbrk_r(struct _reent *r, int incr) {
+    __errno_r(r) = ENOMEM;
+    return (caddr_t)-1;
+}
+
+__attribute__((weak, used)) int _kill(int pid, int sig) {
+    errno = EPERM;
+    return -1;
+}
+
+__attribute__((weak, used)) pid_t _getpid(void) { return 1; }
+
+__attribute__((weak, used)) void _fini(void) { return; }
+
+__attribute__((weak, used, noreturn)) void _exit(int i) {
+    while (1)
+        ;
+}
+
+__attribute__((weak, used)) int _gettimeofday_r(struct _reent *r, struct timeval *t, void *tzp) {
+    __errno_r(r) = EPERM;
+    return -1;
+}
+
+__attribute__((weak, used)) void *__dso_handle;
+
+__attribute__((weak, used)) void __cxa_pure_virtual(void) {
+    while (1)
+        ;
+}
+
+#pragma GCC diagnostic pop
diff --git a/platforms/chibios/timer.c b/platforms/chibios/timer.c
new file mode 100644
index 0000000000..9f664e1f79
--- /dev/null
+++ b/platforms/chibios/timer.c
@@ -0,0 +1,47 @@
+#include <ch.h>
+
+#include "timer.h"
+
+static uint32_t reset_point = 0;
+#if CH_CFG_ST_RESOLUTION < 32
+static uint32_t last_systime = 0;
+static uint32_t overflow     = 0;
+#endif
+
+void timer_init(void) { timer_clear(); }
+
+void timer_clear(void) {
+    reset_point = (uint32_t)chVTGetSystemTime();
+#if CH_CFG_ST_RESOLUTION < 32
+    last_systime = reset_point;
+    overflow     = 0;
+#endif
+}
+
+uint16_t timer_read(void) { return (uint16_t)timer_read32(); }
+
+uint32_t timer_read32(void) {
+    uint32_t systime = (uint32_t)chVTGetSystemTime();
+
+#if CH_CFG_ST_RESOLUTION < 32
+    // If/when we need to support 64-bit chips, this may need to be modified to match the native bit-ness of the MCU.
+    // At this point, the only SysTick resolution allowed other than 32 is 16 bit.
+    // In the 16-bit case, at:
+    //    - CH_CFG_ST_FREQUENCY = 100000, overflow will occur every ~0.65 seconds
+    //    - CH_CFG_ST_FREQUENCY = 10000, overflow will occur every ~6.5 seconds
+    //    - CH_CFG_ST_FREQUENCY = 1000, overflow will occur every ~65 seconds
+    // With this implementation, as long as we ensure a timer read happens at least once during the overflow period, timing should be accurate.
+    if (systime < last_systime) {
+        overflow += ((uint32_t)1) << CH_CFG_ST_RESOLUTION;
+    }
+
+    last_systime = systime;
+    return (uint32_t)TIME_I2MS(systime - reset_point + overflow);
+#else
+    return (uint32_t)TIME_I2MS(systime - reset_point);
+#endif
+}
+
+uint16_t timer_elapsed(uint16_t last) { return TIMER_DIFF_16(timer_read(), last); }
+
+uint32_t timer_elapsed32(uint32_t last) { return TIMER_DIFF_32(timer_read32(), last); }
diff --git a/platforms/chibios/wait.c b/platforms/chibios/wait.c
new file mode 100644
index 0000000000..56fd6ffcec
--- /dev/null
+++ b/platforms/chibios/wait.c
@@ -0,0 +1,41 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ch.h>
+#include <hal.h>
+
+#include "_wait.h"
+
+#ifdef WAIT_US_TIMER
+void wait_us(uint16_t duration) {
+    static const GPTConfig gpt_cfg = {1000000, NULL, 0, 0}; /* 1MHz timer, no callback */
+
+    if (duration == 0) {
+        duration = 1;
+    }
+
+    /*
+     * Only use this timer on the main thread;
+     * other threads need to use their own timer.
+     */
+    if (chThdGetSelfX() == &ch.mainthread && duration < (1ULL << (sizeof(gptcnt_t) * 8))) {
+        gptStart(&WAIT_US_TIMER, &gpt_cfg);
+        gptPolledDelay(&WAIT_US_TIMER, duration);
+    } else {
+        chThdSleepMicroseconds(duration);
+    }
+}
+#endif
diff --git a/platforms/common.mk b/platforms/common.mk
new file mode 100644
index 0000000000..f7a0fc7028
--- /dev/null
+++ b/platforms/common.mk
@@ -0,0 +1,12 @@
+PLATFORM_COMMON_DIR = $(PLATFORM_PATH)/$(PLATFORM_KEY)
+
+TMK_COMMON_SRC +=	\
+	$(PLATFORM_COMMON_DIR)/platform.c \
+	$(PLATFORM_COMMON_DIR)/suspend.c \
+	$(PLATFORM_COMMON_DIR)/timer.c \
+	$(PLATFORM_COMMON_DIR)/bootloader.c \
+
+# Search Path
+VPATH += $(PLATFORM_PATH)
+VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)
+VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)
diff --git a/platforms/eeprom.h b/platforms/eeprom.h
new file mode 100644
index 0000000000..f5b3f0ad53
--- /dev/null
+++ b/platforms/eeprom.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#if defined(__AVR__) && !defined(EEPROM_DRIVER)
+#    include <avr/eeprom.h>
+#else
+#    include <stdint.h>
+#    include <stdlib.h>
+
+uint8_t  eeprom_read_byte(const uint8_t *__p);
+uint16_t eeprom_read_word(const uint16_t *__p);
+uint32_t eeprom_read_dword(const uint32_t *__p);
+void     eeprom_read_block(void *__dst, const void *__src, size_t __n);
+void     eeprom_write_byte(uint8_t *__p, uint8_t __value);
+void     eeprom_write_word(uint16_t *__p, uint16_t __value);
+void     eeprom_write_dword(uint32_t *__p, uint32_t __value);
+void     eeprom_write_block(const void *__src, void *__dst, size_t __n);
+void     eeprom_update_byte(uint8_t *__p, uint8_t __value);
+void     eeprom_update_word(uint16_t *__p, uint16_t __value);
+void     eeprom_update_dword(uint32_t *__p, uint32_t __value);
+void     eeprom_update_block(const void *__src, void *__dst, size_t __n);
+#endif
diff --git a/platforms/gpio.h b/platforms/gpio.h
new file mode 100644
index 0000000000..b47f6f8e43
--- /dev/null
+++ b/platforms/gpio.h
@@ -0,0 +1,22 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "pin_defs.h"
+
+#if __has_include_next("gpio.h")
+#    include_next "gpio.h" /* Include the platforms gpio.h */
+#endif
\ No newline at end of file
diff --git a/platforms/pin_defs.h b/platforms/pin_defs.h
new file mode 100644
index 0000000000..ea730138f2
--- /dev/null
+++ b/platforms/pin_defs.h
@@ -0,0 +1,23 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// useful for direct pin mapping
+#define NO_PIN (pin_t)(~0)
+
+#if __has_include_next("pin_defs.h")
+#    include_next "pin_defs.h" /* Include the platforms pin_defs.h */
+#endif
diff --git a/platforms/progmem.h b/platforms/progmem.h
new file mode 100644
index 0000000000..a70d8e299f
--- /dev/null
+++ b/platforms/progmem.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#if defined(__AVR__)
+#    include <avr/pgmspace.h>
+#else
+#    include <string.h>
+#    define PROGMEM
+#    define __flash
+#    define PSTR(x) x
+#    define PGM_P const char*
+#    define memcpy_P(dest, src, n) memcpy(dest, src, n)
+#    define pgm_read_byte(address_short) *((uint8_t*)(address_short))
+#    define pgm_read_word(address_short) *((uint16_t*)(address_short))
+#    define pgm_read_dword(address_short) *((uint32_t*)(address_short))
+#    define pgm_read_ptr(address_short) *((void**)(address_short))
+#    define strcmp_P(s1, s2) strcmp(s1, s2)
+#    define strcpy_P(dest, src) strcpy(dest, src)
+#    define strlen_P(src) strlen(src)
+#endif
diff --git a/platforms/sleep_led.h b/platforms/sleep_led.h
new file mode 100644
index 0000000000..38f80a660d
--- /dev/null
+++ b/platforms/sleep_led.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#ifdef SLEEP_LED_ENABLE
+
+void sleep_led_init(void);
+void sleep_led_enable(void);
+void sleep_led_disable(void);
+void sleep_led_toggle(void);
+
+#else
+
+#    define sleep_led_init()
+#    define sleep_led_enable()
+#    define sleep_led_disable()
+#    define sleep_led_toggle()
+
+#endif
diff --git a/platforms/suspend.h b/platforms/suspend.h
new file mode 100644
index 0000000000..081735f90e
--- /dev/null
+++ b/platforms/suspend.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+void suspend_idle(uint8_t timeout);
+void suspend_power_down(void);
+bool suspend_wakeup_condition(void);
+void suspend_wakeup_init(void);
+
+void suspend_wakeup_init_user(void);
+void suspend_wakeup_init_kb(void);
+void suspend_wakeup_init_quantum(void);
+void suspend_power_down_user(void);
+void suspend_power_down_kb(void);
+void suspend_power_down_quantum(void);
+
+#ifndef USB_SUSPEND_WAKEUP_DELAY
+#    define USB_SUSPEND_WAKEUP_DELAY 0
+#endif
diff --git a/platforms/test/_wait.h b/platforms/test/_wait.h
new file mode 100644
index 0000000000..4e22f593b7
--- /dev/null
+++ b/platforms/test/_wait.h
@@ -0,0 +1,22 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <inttypes.h>
+
+void wait_ms(uint32_t ms);
+#define wait_us(us) wait_ms(us / 1000)
+#define waitInputPinDelay()
diff --git a/platforms/test/bootloader.c b/platforms/test/bootloader.c
new file mode 100644
index 0000000000..5155d9ff04
--- /dev/null
+++ b/platforms/test/bootloader.c
@@ -0,0 +1,19 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "bootloader.h"
+
+void bootloader_jump(void) {}
diff --git a/platforms/test/eeprom.c b/platforms/test/eeprom.c
new file mode 100644
index 0000000000..5c8e69dae3
--- /dev/null
+++ b/platforms/test/eeprom.c
@@ -0,0 +1,95 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "eeprom.h"
+
+#define EEPROM_SIZE 32
+
+static uint8_t buffer[EEPROM_SIZE];
+
+uint8_t eeprom_read_byte(const uint8_t *addr) {
+    uintptr_t offset = (uintptr_t)addr;
+    return buffer[offset];
+}
+
+void eeprom_write_byte(uint8_t *addr, uint8_t value) {
+    uintptr_t offset = (uintptr_t)addr;
+    buffer[offset]   = value;
+}
+
+uint16_t eeprom_read_word(const uint16_t *addr) {
+    const uint8_t *p = (const uint8_t *)addr;
+    return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8);
+}
+
+uint32_t eeprom_read_dword(const uint32_t *addr) {
+    const uint8_t *p = (const uint8_t *)addr;
+    return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8) | (eeprom_read_byte(p + 2) << 16) | (eeprom_read_byte(p + 3) << 24);
+}
+
+void eeprom_read_block(void *buf, const void *addr, size_t len) {
+    const uint8_t *p    = (const uint8_t *)addr;
+    uint8_t *      dest = (uint8_t *)buf;
+    while (len--) {
+        *dest++ = eeprom_read_byte(p++);
+    }
+}
+
+void eeprom_write_word(uint16_t *addr, uint16_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p, value >> 8);
+}
+
+void eeprom_write_dword(uint32_t *addr, uint32_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p++, value >> 8);
+    eeprom_write_byte(p++, value >> 16);
+    eeprom_write_byte(p, value >> 24);
+}
+
+void eeprom_write_block(const void *buf, void *addr, size_t len) {
+    uint8_t *      p   = (uint8_t *)addr;
+    const uint8_t *src = (const uint8_t *)buf;
+    while (len--) {
+        eeprom_write_byte(p++, *src++);
+    }
+}
+
+void eeprom_update_byte(uint8_t *addr, uint8_t value) { eeprom_write_byte(addr, value); }
+
+void eeprom_update_word(uint16_t *addr, uint16_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p, value >> 8);
+}
+
+void eeprom_update_dword(uint32_t *addr, uint32_t value) {
+    uint8_t *p = (uint8_t *)addr;
+    eeprom_write_byte(p++, value);
+    eeprom_write_byte(p++, value >> 8);
+    eeprom_write_byte(p++, value >> 16);
+    eeprom_write_byte(p, value >> 24);
+}
+
+void eeprom_update_block(const void *buf, void *addr, size_t len) {
+    uint8_t *      p   = (uint8_t *)addr;
+    const uint8_t *src = (const uint8_t *)buf;
+    while (len--) {
+        eeprom_write_byte(p++, *src++);
+    }
+}
diff --git a/platforms/test/eeprom_stm32_tests.cpp b/platforms/test/eeprom_stm32_tests.cpp
new file mode 100644
index 0000000000..5bc8d87900
--- /dev/null
+++ b/platforms/test/eeprom_stm32_tests.cpp
@@ -0,0 +1,438 @@
+/* Copyright 2021 by Don Kjer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gtest/gtest.h"
+
+extern "C" {
+#include "flash_stm32.h"
+#include "eeprom_stm32.h"
+#include "eeprom.h"
+}
+
+/* Mock Flash Parameters:
+ *
+ * === Large Layout ===
+ * flash size: 65536
+ * page size: 2048
+ * density pages: 16
+ * Simulated EEPROM size: 16384
+ *
+ * FlashBuf Layout:
+ * [Unused | Compact |  Write Log  ]
+ * [0......|32768......|49152......65535]
+ *
+ * === Tiny Layout ===
+ * flash size: 1024
+ * page size: 512
+ * density pages: 1
+ * Simulated EEPROM size: 256
+ *
+ * FlashBuf Layout:
+ * [Unused | Compact |  Write Log  ]
+ * [0......|512......|768......1023]
+ *
+ */
+
+#define EEPROM_SIZE (FEE_PAGE_SIZE * FEE_PAGE_COUNT / 2)
+#define LOG_SIZE EEPROM_SIZE
+#define LOG_BASE (MOCK_FLASH_SIZE - LOG_SIZE)
+#define EEPROM_BASE (LOG_BASE - EEPROM_SIZE)
+
+/* Log encoding helpers */
+#define BYTE_VALUE(addr, value) (((addr) << 8) | (value))
+#define WORD_ZERO(addr) (0x8000 | ((addr) >> 1))
+#define WORD_ONE(addr) (0xA000 | ((addr) >> 1))
+#define WORD_NEXT(addr) (0xE000 | (((addr)-0x80) >> 1))
+
+class EepromStm32Test : public testing::Test {
+   public:
+    EepromStm32Test() {}
+    ~EepromStm32Test() {}
+
+   protected:
+    void SetUp() override { EEPROM_Erase(); }
+
+    void TearDown() override {
+#ifdef EEPROM_DEBUG
+        dumpEepromDataBuf();
+#endif
+    }
+};
+
+TEST_F(EepromStm32Test, TestErase) {
+    EEPROM_WriteDataByte(0, 0x42);
+    EEPROM_Erase();
+    EXPECT_EQ(EEPROM_ReadDataByte(0), 0);
+    EXPECT_EQ(EEPROM_ReadDataByte(1), 0);
+}
+
+TEST_F(EepromStm32Test, TestReadGarbage) {
+    uint8_t garbage = 0x3c;
+    for (int i = 0; i < MOCK_FLASH_SIZE; ++i) {
+        garbage ^= 0xa3;
+        garbage += i;
+        FlashBuf[i] = garbage;
+    }
+    EEPROM_Init();  // Just verify we don't crash
+}
+
+TEST_F(EepromStm32Test, TestWriteBadAddress) {
+    EXPECT_EQ(EEPROM_WriteDataByte(EEPROM_SIZE, 0x42), FLASH_BAD_ADDRESS);
+    EXPECT_EQ(EEPROM_WriteDataWord(EEPROM_SIZE - 1, 0xbeef), FLASH_BAD_ADDRESS);
+    EXPECT_EQ(EEPROM_WriteDataWord(EEPROM_SIZE, 0xbeef), FLASH_BAD_ADDRESS);
+}
+
+TEST_F(EepromStm32Test, TestReadBadAddress) {
+    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE), 0xFF);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 1), 0xFFFF);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE), 0xFFFF);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 4)), 0);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 3)), 0xFF000000);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)EEPROM_SIZE), 0xFFFFFFFF);
+}
+
+TEST_F(EepromStm32Test, TestReadByte) {
+    /* Direct compacted-area baseline: Address < 0x80 */
+    FlashBuf[EEPROM_BASE + 2] = ~0xef;
+    FlashBuf[EEPROM_BASE + 3] = ~0xbe;
+    /* Direct compacted-area baseline: Address >= 0x80 */
+    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 2] = ~0x78;
+    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 1] = ~0x56;
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataByte(2), 0xef);
+    EXPECT_EQ(EEPROM_ReadDataByte(3), 0xbe);
+    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 2), 0x78);
+    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 1), 0x56);
+    /* Write Log byte value */
+    FlashBuf[LOG_BASE]     = 0x65;
+    FlashBuf[LOG_BASE + 1] = 3;
+    /* Write Log word value */
+    *(uint16_t*)&FlashBuf[LOG_BASE + 2] = WORD_NEXT(EEPROM_SIZE - 2);
+    *(uint16_t*)&FlashBuf[LOG_BASE + 4] = ~0x9abc;
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataByte(2), 0xef);
+    EXPECT_EQ(EEPROM_ReadDataByte(3), 0x65);
+    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 2), 0xbc);
+    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 1), 0x9a);
+}
+
+TEST_F(EepromStm32Test, TestWriteByte) {
+    /* Direct compacted-area baseline: Address < 0x80 */
+    EEPROM_WriteDataByte(2, 0xef);
+    EEPROM_WriteDataByte(3, 0xbe);
+    /* Direct compacted-area baseline: Address >= 0x80 */
+    EEPROM_WriteDataByte(EEPROM_SIZE - 2, 0x78);
+    EEPROM_WriteDataByte(EEPROM_SIZE - 1, 0x56);
+    /* Check values */
+    /* First write in each aligned word should have been direct */
+    EXPECT_EQ(FlashBuf[EEPROM_BASE + 2], (uint8_t)~0xef);
+    EXPECT_EQ(FlashBuf[EEPROM_BASE + EEPROM_SIZE - 2], (uint8_t)~0x78);
+
+    /* Second write per aligned word requires a log entry */
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE], BYTE_VALUE(3, 0xbe));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 2], WORD_NEXT(EEPROM_SIZE - 1));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 4], (uint16_t)~0x5678);
+}
+
+TEST_F(EepromStm32Test, TestByteRoundTrip) {
+    /* Direct compacted-area: Address < 0x80 */
+    EEPROM_WriteDataWord(0, 0xdead);
+    EEPROM_WriteDataByte(2, 0xef);
+    EEPROM_WriteDataByte(3, 0xbe);
+    /* Direct compacted-area: Address >= 0x80 */
+    EEPROM_WriteDataByte(EEPROM_SIZE - 2, 0x78);
+    EEPROM_WriteDataByte(EEPROM_SIZE - 1, 0x56);
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataByte(0), 0xad);
+    EXPECT_EQ(EEPROM_ReadDataByte(1), 0xde);
+    EXPECT_EQ(EEPROM_ReadDataByte(2), 0xef);
+    EXPECT_EQ(EEPROM_ReadDataByte(3), 0xbe);
+    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 2), 0x78);
+    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 1), 0x56);
+    /* Write log entries */
+    EEPROM_WriteDataByte(2, 0x80);
+    EEPROM_WriteDataByte(EEPROM_SIZE - 2, 0x3c);
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataByte(2), 0x80);
+    EXPECT_EQ(EEPROM_ReadDataByte(3), 0xbe);
+    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 2), 0x3c);
+    EXPECT_EQ(EEPROM_ReadDataByte(EEPROM_SIZE - 1), 0x56);
+}
+
+TEST_F(EepromStm32Test, TestReadWord) {
+    /* Direct compacted-area baseline: Address < 0x80 */
+    FlashBuf[EEPROM_BASE + 0] = ~0xad;
+    FlashBuf[EEPROM_BASE + 1] = ~0xde;
+    /* Direct compacted-area baseline: Address >= 0x80 */
+    FlashBuf[EEPROM_BASE + 200]             = ~0xcd;
+    FlashBuf[EEPROM_BASE + 201]             = ~0xab;
+    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 4] = ~0x34;
+    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 3] = ~0x12;
+    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 2] = ~0x78;
+    FlashBuf[EEPROM_BASE + EEPROM_SIZE - 1] = ~0x56;
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataWord(0), 0xdead);
+    EXPECT_EQ(EEPROM_ReadDataWord(200), 0xabcd);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 4), 0x1234);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 2), 0x5678);
+    /* Write Log word zero-encoded */
+    *(uint16_t*)&FlashBuf[LOG_BASE] = WORD_ZERO(200);
+    /* Write Log word one-encoded */
+    *(uint16_t*)&FlashBuf[LOG_BASE + 2] = WORD_ONE(EEPROM_SIZE - 4);
+    /* Write Log word value */
+    *(uint16_t*)&FlashBuf[LOG_BASE + 4] = WORD_NEXT(EEPROM_SIZE - 2);
+    *(uint16_t*)&FlashBuf[LOG_BASE + 6] = ~0x9abc;
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataWord(200), 0);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 4), 1);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 2), 0x9abc);
+}
+
+TEST_F(EepromStm32Test, TestWriteWord) {
+    /* Direct compacted-area: Address < 0x80 */
+    EEPROM_WriteDataWord(0, 0xdead);  // Aligned
+    EEPROM_WriteDataWord(3, 0xbeef);  // Unaligned
+    /* Direct compacted-area: Address >= 0x80 */
+    EEPROM_WriteDataWord(200, 0xabcd);  // Aligned
+    EEPROM_WriteDataWord(203, 0x9876);  // Unaligned
+    EEPROM_WriteDataWord(EEPROM_SIZE - 4, 0x1234);
+    EEPROM_WriteDataWord(EEPROM_SIZE - 2, 0x5678);
+    /* Write Log word zero-encoded */
+    EEPROM_WriteDataWord(EEPROM_SIZE - 4, 0);
+    /* Write Log word one-encoded */
+    EEPROM_WriteDataWord(EEPROM_SIZE - 2, 1);
+    /* Write Log word value aligned */
+    EEPROM_WriteDataWord(200, 0x4321);  // Aligned
+    /* Write Log word value unaligned */
+    EEPROM_WriteDataByte(202, 0x3c);    // Set neighboring byte
+    EEPROM_WriteDataWord(203, 0xcdef);  // Unaligned
+    /* Check values */
+    /* Direct compacted-area */
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE], (uint16_t)~0xdead);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE + 3], (uint16_t)~0xbeef);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE + 200], (uint16_t)~0xabcd);
+    EXPECT_EQ(FlashBuf[EEPROM_BASE + 203], (uint8_t)~0x76);
+    EXPECT_EQ(FlashBuf[EEPROM_BASE + 204], (uint8_t)~0x98);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE + EEPROM_SIZE - 4], (uint16_t)~0x1234);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[EEPROM_BASE + EEPROM_SIZE - 2], (uint16_t)~0x5678);
+    /* Write Log word zero-encoded */
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE], WORD_ZERO(EEPROM_SIZE - 4));
+    /* Write Log word one-encoded */
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 2], WORD_ONE(EEPROM_SIZE - 2));
+    /* Write Log word value aligned */
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 4], WORD_NEXT(200));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 6], (uint16_t)~0x4321);
+    /* Write Log word value unaligned */
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 8], WORD_NEXT(202));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 10], (uint16_t)~0x763c);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 12], WORD_NEXT(202));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 14], (uint16_t)~0xef3c);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 16], WORD_NEXT(204));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 18], (uint16_t)~0x00cd);
+}
+
+TEST_F(EepromStm32Test, TestWordRoundTrip) {
+    /* Direct compacted-area: Address < 0x80 */
+    EEPROM_WriteDataWord(0, 0xdead);  // Aligned
+    EEPROM_WriteDataWord(3, 0xbeef);  // Unaligned
+    /* Direct compacted-area: Address >= 0x80 */
+    EEPROM_WriteDataWord(200, 0xabcd);  // Aligned
+    EEPROM_WriteDataWord(203, 0x9876);  // Unaligned
+    EEPROM_WriteDataWord(EEPROM_SIZE - 4, 0x1234);
+    EEPROM_WriteDataWord(EEPROM_SIZE - 2, 0x5678);
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataWord(0), 0xdead);
+    EXPECT_EQ(EEPROM_ReadDataWord(3), 0xbeef);
+    EXPECT_EQ(EEPROM_ReadDataWord(200), 0xabcd);
+    EXPECT_EQ(EEPROM_ReadDataWord(203), 0x9876);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 4), 0x1234);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 2), 0x5678);
+
+    /* Write Log word zero-encoded */
+    EEPROM_WriteDataWord(EEPROM_SIZE - 4, 0);
+    /* Write Log word one-encoded */
+    EEPROM_WriteDataWord(EEPROM_SIZE - 2, 1);
+    /* Write Log word value aligned */
+    EEPROM_WriteDataWord(200, 0x4321);  // Aligned
+    /* Write Log word value unaligned */
+    EEPROM_WriteDataByte(202, 0x3c);    // Set neighboring byte
+    EEPROM_WriteDataWord(203, 0xcdef);  // Unaligned
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataWord(200), 0x4321);
+    EXPECT_EQ(EEPROM_ReadDataByte(202), 0x3c);
+    EXPECT_EQ(EEPROM_ReadDataWord(203), 0xcdef);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 4), 0);
+    EXPECT_EQ(EEPROM_ReadDataWord(EEPROM_SIZE - 2), 1);
+}
+
+TEST_F(EepromStm32Test, TestByteWordBoundary) {
+    /* Direct compacted-area write */
+    EEPROM_WriteDataWord(0x7e, 0xdead);
+    EEPROM_WriteDataWord(0x80, 0xbeef);
+    /* Byte log entry */
+    EEPROM_WriteDataByte(0x7f, 0x3c);
+    /* Word log entry */
+    EEPROM_WriteDataByte(0x80, 0x18);
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataWord(0x7e), 0x3cad);
+    EXPECT_EQ(EEPROM_ReadDataWord(0x80), 0xbe18);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE], BYTE_VALUE(0x7f, 0x3c));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 2], WORD_NEXT(0x80));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 4], (uint16_t)~0xbe18);
+    /* Byte log entries */
+    EEPROM_WriteDataWord(0x7e, 0xcafe);
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataWord(0x7e), 0xcafe);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 6], BYTE_VALUE(0x7e, 0xfe));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 8], BYTE_VALUE(0x7f, 0xca));
+    /* Byte and Word log entries */
+    EEPROM_WriteDataWord(0x7f, 0xba5e);
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataWord(0x7f), 0xba5e);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 10], BYTE_VALUE(0x7f, 0x5e));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 12], WORD_NEXT(0x80));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 14], (uint16_t)~0xbeba);
+    /* Word log entry */
+    EEPROM_WriteDataWord(0x80, 0xf00d);
+    /* Check values */
+    EEPROM_Init();
+    EXPECT_EQ(EEPROM_ReadDataWord(0x80), 0xf00d);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 16], WORD_NEXT(0x80));
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + 18], (uint16_t)~0xf00d);
+}
+
+TEST_F(EepromStm32Test, TestDWordRoundTrip) {
+    /* Direct compacted-area: Address < 0x80 */
+    eeprom_write_dword((uint32_t*)0, 0xdeadbeef);  // Aligned
+    eeprom_write_dword((uint32_t*)9, 0x12345678);  // Unaligned
+    /* Direct compacted-area: Address >= 0x80 */
+    eeprom_write_dword((uint32_t*)200, 0xfacef00d);
+    eeprom_write_dword((uint32_t*)(EEPROM_SIZE - 4), 0xba5eba11);  // Aligned
+    eeprom_write_dword((uint32_t*)(EEPROM_SIZE - 9), 0xcafed00d);  // Unaligned
+    /* Check direct values */
+    EEPROM_Init();
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)0), 0xdeadbeef);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)9), 0x12345678);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)200), 0xfacef00d);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 4)), 0xba5eba11);  // Aligned
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 9)), 0xcafed00d);  // Unaligned
+    /* Write Log byte encoded */
+    eeprom_write_dword((uint32_t*)0, 0xdecafbad);
+    eeprom_write_dword((uint32_t*)9, 0x87654321);
+    /* Write Log word encoded */
+    eeprom_write_dword((uint32_t*)200, 1);
+    /* Write Log word value aligned */
+    eeprom_write_dword((uint32_t*)(EEPROM_SIZE - 4), 0xdeadc0de);  // Aligned
+    eeprom_write_dword((uint32_t*)(EEPROM_SIZE - 9), 0x6789abcd);  // Unaligned
+    /* Check log values */
+    EEPROM_Init();
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)0), 0xdecafbad);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)9), 0x87654321);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)200), 1);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 4)), 0xdeadc0de);  // Aligned
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)(EEPROM_SIZE - 9)), 0x6789abcd);  // Unaligned
+}
+
+TEST_F(EepromStm32Test, TestBlockRoundTrip) {
+    char  src0[] = "0123456789abcdef";
+    void* src1   = (void*)&src0[1];
+    /* Various alignments of src & dst, Address < 0x80 */
+    eeprom_write_block(src0, (void*)0, sizeof(src0));
+    eeprom_write_block(src0, (void*)21, sizeof(src0));
+    eeprom_write_block(src1, (void*)40, sizeof(src0) - 1);
+    eeprom_write_block(src1, (void*)61, sizeof(src0) - 1);
+    /* Various alignments of src & dst, Address >= 0x80 */
+    eeprom_write_block(src0, (void*)140, sizeof(src0));
+    eeprom_write_block(src0, (void*)161, sizeof(src0));
+    eeprom_write_block(src1, (void*)180, sizeof(src0) - 1);
+    eeprom_write_block(src1, (void*)201, sizeof(src0) - 1);
+
+    /* Check values */
+    EEPROM_Init();
+
+    char  dstBuf[256] = {0};
+    char* dst0a       = (char*)dstBuf;
+    char* dst0b       = (char*)&dstBuf[20];
+    char* dst1a       = (char*)&dstBuf[41];
+    char* dst1b       = (char*)&dstBuf[61];
+    char* dst0c       = (char*)&dstBuf[80];
+    char* dst0d       = (char*)&dstBuf[100];
+    char* dst1c       = (char*)&dstBuf[121];
+    char* dst1d       = (char*)&dstBuf[141];
+    eeprom_read_block((void*)dst0a, (void*)0, sizeof(src0));
+    eeprom_read_block((void*)dst0b, (void*)21, sizeof(src0));
+    eeprom_read_block((void*)dst1a, (void*)40, sizeof(src0) - 1);
+    eeprom_read_block((void*)dst1b, (void*)61, sizeof(src0) - 1);
+    eeprom_read_block((void*)dst0c, (void*)140, sizeof(src0));
+    eeprom_read_block((void*)dst0d, (void*)161, sizeof(src0));
+    eeprom_read_block((void*)dst1c, (void*)180, sizeof(src0) - 1);
+    eeprom_read_block((void*)dst1d, (void*)201, sizeof(src0) - 1);
+    EXPECT_EQ(strcmp((char*)src0, dst0a), 0);
+    EXPECT_EQ(strcmp((char*)src0, dst0b), 0);
+    EXPECT_EQ(strcmp((char*)src0, dst0c), 0);
+    EXPECT_EQ(strcmp((char*)src0, dst0d), 0);
+    EXPECT_EQ(strcmp((char*)src1, dst1a), 0);
+    EXPECT_EQ(strcmp((char*)src1, dst1b), 0);
+    EXPECT_EQ(strcmp((char*)src1, dst1c), 0);
+    EXPECT_EQ(strcmp((char*)src1, dst1d), 0);
+}
+
+TEST_F(EepromStm32Test, TestCompaction) {
+    /* Direct writes */
+    eeprom_write_dword((uint32_t*)0, 0xdeadbeef);
+    eeprom_write_byte((uint8_t*)4, 0x3c);
+    eeprom_write_word((uint16_t*)6, 0xd00d);
+    eeprom_write_dword((uint32_t*)150, 0xcafef00d);
+    eeprom_write_dword((uint32_t*)200, 0x12345678);
+    /* Fill write log entries */
+    uint32_t i;
+    uint32_t val = 0xd8453c6b;
+    for (i = 0; i < (LOG_SIZE / (sizeof(uint32_t) * 2)); i++) {
+        val ^= 0x593ca5b3;
+        val += i;
+        eeprom_write_dword((uint32_t*)200, val);
+    }
+    /* Check values pre-compaction */
+    EEPROM_Init();
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)0), 0xdeadbeef);
+    EXPECT_EQ(eeprom_read_byte((uint8_t*)4), 0x3c);
+    EXPECT_EQ(eeprom_read_word((uint16_t*)6), 0xd00d);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)150), 0xcafef00d);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)200), val);
+    EXPECT_NE(*(uint16_t*)&FlashBuf[LOG_BASE], 0xFFFF);
+    EXPECT_NE(*(uint16_t*)&FlashBuf[LOG_BASE + LOG_SIZE - 2], 0xFFFF);
+    /* Run compaction */
+    eeprom_write_byte((uint8_t*)4, 0x1f);
+    EEPROM_Init();
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)0), 0xdeadbeef);
+    EXPECT_EQ(eeprom_read_byte((uint8_t*)4), 0x1f);
+    EXPECT_EQ(eeprom_read_word((uint16_t*)6), 0xd00d);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)150), 0xcafef00d);
+    EXPECT_EQ(eeprom_read_dword((uint32_t*)200), val);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE], 0xFFFF);
+    EXPECT_EQ(*(uint16_t*)&FlashBuf[LOG_BASE + LOG_SIZE - 2], 0xFFFF);
+}
diff --git a/platforms/test/flash_stm32_mock.c b/platforms/test/flash_stm32_mock.c
new file mode 100644
index 0000000000..222a004bc7
--- /dev/null
+++ b/platforms/test/flash_stm32_mock.c
@@ -0,0 +1,49 @@
+/* Copyright 2021 by Don Kjer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdbool.h>
+#include "flash_stm32.h"
+
+uint8_t FlashBuf[MOCK_FLASH_SIZE] = {0};
+
+static bool flash_locked = true;
+
+FLASH_Status FLASH_ErasePage(uint32_t Page_Address) {
+    if (flash_locked) return FLASH_ERROR_WRP;
+    Page_Address -= (uintptr_t)FlashBuf;
+    Page_Address -= (Page_Address % FEE_PAGE_SIZE);
+    if (Page_Address >= MOCK_FLASH_SIZE) return FLASH_BAD_ADDRESS;
+    memset(&FlashBuf[Page_Address], '\xff', FEE_PAGE_SIZE);
+    return FLASH_COMPLETE;
+}
+
+FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data) {
+    if (flash_locked) return FLASH_ERROR_WRP;
+    Address -= (uintptr_t)FlashBuf;
+    if (Address >= MOCK_FLASH_SIZE) return FLASH_BAD_ADDRESS;
+    uint16_t oldData = *(uint16_t*)&FlashBuf[Address];
+    if (oldData == 0xFFFF || Data == 0) {
+        *(uint16_t*)&FlashBuf[Address] = Data;
+        return FLASH_COMPLETE;
+    } else {
+        return FLASH_ERROR_PG;
+    }
+}
+
+FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout) { return FLASH_COMPLETE; }
+void         FLASH_Unlock(void) { flash_locked = false; }
+void         FLASH_Lock(void) { flash_locked = true; }
diff --git a/platforms/test/hal.h b/platforms/test/hal.h
new file mode 100644
index 0000000000..2d268ad54c
--- /dev/null
+++ b/platforms/test/hal.h
@@ -0,0 +1,18 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// Just here to please eeprom tests
diff --git a/platforms/test/platform.c b/platforms/test/platform.c
new file mode 100644
index 0000000000..8ddceeda8f
--- /dev/null
+++ b/platforms/test/platform.c
@@ -0,0 +1,21 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform_deps.h"
+
+void platform_setup(void) {
+    // do nothing
+}
\ No newline at end of file
diff --git a/platforms/test/platform.h b/platforms/test/platform.h
new file mode 100644
index 0000000000..f296d1d535
--- /dev/null
+++ b/platforms/test/platform.h
@@ -0,0 +1,18 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// here just to please the build
diff --git a/platforms/test/platform.mk b/platforms/test/platform.mk
new file mode 100644
index 0000000000..eb2424ec5c
--- /dev/null
+++ b/platforms/test/platform.mk
@@ -0,0 +1,34 @@
+SYSTEM_TYPE := $(shell gcc -dumpmachine)
+GCC_VERSION := $(shell gcc --version 2>/dev/null)
+
+CC = $(CC_PREFIX) gcc
+OBJCOPY =
+OBJDUMP =
+SIZE =
+AR =
+NM =
+HEX =
+EEP =
+BIN =
+
+
+COMPILEFLAGS += -funsigned-char
+ifeq ($(findstring clang, ${GCC_VERSION}),)
+COMPILEFLAGS += -funsigned-bitfields
+endif
+COMPILEFLAGS += -ffunction-sections
+COMPILEFLAGS += -fdata-sections
+COMPILEFLAGS += -fshort-enums
+ifneq ($(findstring mingw, ${SYSTEM_TYPE}),)
+COMPILEFLAGS += -mno-ms-bitfields
+endif
+
+CFLAGS += $(COMPILEFLAGS)
+ifeq ($(findstring clang, ${GCC_VERSION}),)
+CFLAGS += -fno-inline-small-functions
+endif
+CFLAGS += -fno-strict-aliasing
+
+CXXFLAGS += $(COMPILEFLAGS)
+CXXFLAGS += -fno-exceptions
+CXXFLAGS += -std=gnu++11
diff --git a/platforms/test/platform_deps.h b/platforms/test/platform_deps.h
new file mode 100644
index 0000000000..f296d1d535
--- /dev/null
+++ b/platforms/test/platform_deps.h
@@ -0,0 +1,18 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+// here just to please the build
diff --git a/platforms/test/rules.mk b/platforms/test/rules.mk
new file mode 100644
index 0000000000..66b853d8ee
--- /dev/null
+++ b/platforms/test/rules.mk
@@ -0,0 +1,24 @@
+eeprom_stm32_DEFS  := -DFLASH_STM32_MOCKED -DNO_PRINT -DFEE_FLASH_BASE=FlashBuf
+eeprom_stm32_tiny_DEFS := $(eeprom_stm32_DEFS) \
+	-DFEE_MCU_FLASH_SIZE=1 \
+	-DMOCK_FLASH_SIZE=1024 \
+	-DFEE_PAGE_SIZE=512 \
+	-DFEE_PAGE_COUNT=1
+eeprom_stm32_large_DEFS := $(eeprom_stm32_DEFS) \
+	-DFEE_MCU_FLASH_SIZE=64 \
+	-DMOCK_FLASH_SIZE=65536 \
+	-DFEE_PAGE_SIZE=2048 \
+	-DFEE_PAGE_COUNT=16
+
+eeprom_stm32_INC := \
+	$(PLATFORM_PATH)/chibios/
+eeprom_stm32_tiny_INC := $(eeprom_stm32_INC)
+eeprom_stm32_large_INC := $(eeprom_stm32_INC)
+
+eeprom_stm32_SRC := \
+	$(TOP_DIR)/drivers/eeprom/eeprom_driver.c \
+	$(PLATFORM_PATH)/$(PLATFORM_KEY)/eeprom_stm32_tests.cpp \
+	$(PLATFORM_PATH)/$(PLATFORM_KEY)/flash_stm32_mock.c \
+	$(PLATFORM_PATH)/chibios/eeprom_stm32.c
+eeprom_stm32_tiny_SRC := $(eeprom_stm32_SRC)
+eeprom_stm32_large_SRC := $(eeprom_stm32_SRC)
diff --git a/platforms/test/suspend.c b/platforms/test/suspend.c
new file mode 100644
index 0000000000..76b705967b
--- /dev/null
+++ b/platforms/test/suspend.c
@@ -0,0 +1,15 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
diff --git a/platforms/test/testlist.mk b/platforms/test/testlist.mk
new file mode 100644
index 0000000000..51a9638bb9
--- /dev/null
+++ b/platforms/test/testlist.mk
@@ -0,0 +1 @@
+TEST_LIST += eeprom_stm32_tiny eeprom_stm32_large
diff --git a/platforms/test/timer.c b/platforms/test/timer.c
new file mode 100644
index 0000000000..61c3a00201
--- /dev/null
+++ b/platforms/test/timer.c
@@ -0,0 +1,33 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "timer.h"
+
+static uint32_t current_time = 0;
+
+void timer_init(void) { current_time = 0; }
+
+void timer_clear(void) { current_time = 0; }
+
+uint16_t timer_read(void) { return current_time & 0xFFFF; }
+uint32_t timer_read32(void) { return current_time; }
+uint16_t timer_elapsed(uint16_t last) { return TIMER_DIFF_16(timer_read(), last); }
+uint32_t timer_elapsed32(uint32_t last) { return TIMER_DIFF_32(timer_read32(), last); }
+
+void set_time(uint32_t t) { current_time = t; }
+void advance_time(uint32_t ms) { current_time += ms; }
+
+void wait_ms(uint32_t ms) { advance_time(ms); }
diff --git a/platforms/timer.h b/platforms/timer.h
new file mode 100644
index 0000000000..02e39e79e7
--- /dev/null
+++ b/platforms/timer.h
@@ -0,0 +1,67 @@
+/*
+Copyright 2011 Jun Wako <wakojun@gmail.com>
+Copyright 2021 Simon Arlott
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#if __has_include_next("_timer.h")
+#    include_next "_timer.h" /* Include the platform's _timer.h */
+#endif
+
+#include <stdint.h>
+
+#define TIMER_DIFF(a, b, max) ((max == UINT8_MAX) ? ((uint8_t)((a) - (b))) : ((max == UINT16_MAX) ? ((uint16_t)((a) - (b))) : ((max == UINT32_MAX) ? ((uint32_t)((a) - (b))) : ((a) >= (b) ? (a) - (b) : (max) + 1 - (b) + (a)))))
+#define TIMER_DIFF_8(a, b) TIMER_DIFF(a, b, UINT8_MAX)
+#define TIMER_DIFF_16(a, b) TIMER_DIFF(a, b, UINT16_MAX)
+#define TIMER_DIFF_32(a, b) TIMER_DIFF(a, b, UINT32_MAX)
+#define TIMER_DIFF_RAW(a, b) TIMER_DIFF_8(a, b)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern volatile uint32_t timer_count;
+
+void     timer_init(void);
+void     timer_clear(void);
+uint16_t timer_read(void);
+uint32_t timer_read32(void);
+uint16_t timer_elapsed(uint16_t last);
+uint32_t timer_elapsed32(uint32_t last);
+
+// Utility functions to check if a future time has expired & autmatically handle time wrapping if checked / reset frequently (half of max value)
+#define timer_expired(current, future) ((uint16_t)(current - future) < UINT16_MAX / 2)
+#define timer_expired32(current, future) ((uint32_t)(current - future) < UINT32_MAX / 2)
+
+// Use an appropriate timer integer size based on architecture (16-bit will overflow sooner)
+#if FAST_TIMER_T_SIZE < 32
+#    define TIMER_DIFF_FAST(a, b) TIMER_DIFF_16(a, b)
+#    define timer_expired_fast(current, future) timer_expired(current, future)
+typedef uint16_t fast_timer_t;
+fast_timer_t inline timer_read_fast(void) { return timer_read(); }
+fast_timer_t inline timer_elapsed_fast(fast_timer_t last) { return timer_elapsed(last); }
+#else
+#    define TIMER_DIFF_FAST(a, b) TIMER_DIFF_32(a, b)
+#    define timer_expired_fast(current, future) timer_expired32(current, future)
+typedef uint32_t fast_timer_t;
+fast_timer_t inline timer_read_fast(void) { return timer_read32(); }
+fast_timer_t inline timer_elapsed_fast(fast_timer_t last) { return timer_elapsed32(last); }
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/platforms/wait.h b/platforms/wait.h
new file mode 100644
index 0000000000..cf7180fb07
--- /dev/null
+++ b/platforms/wait.h
@@ -0,0 +1,30 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if __has_include_next("_wait.h")
+#    include_next "_wait.h" /* Include the platforms _wait.h */
+#endif
+
+#ifdef __cplusplus
+}
+#endif