From cfc9f7bf547795b08969ab9a29f6294721b9be5c Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Sun, 12 Jun 2022 14:55:03 +1000
Subject: [PATCH 01/19] STM32F4 wear-leveling implementation.

---
 builddefs/common_features.mk                  | 33 +++++++++--
 drivers/eeprom/eeprom_wear_leveling.c         | 23 ++++++++
 .../wear_leveling/wear_leveling_stm32f4x1.c   | 59 +++++++++++++++++++
 .../wear_leveling_stm32f4x1_config.h          | 33 +++++++++++
 platforms/eeprom.h                            |  2 +
 .../wear_leveling/wear_leveling_internal.h    | 11 ++--
 6 files changed, 151 insertions(+), 10 deletions(-)
 create mode 100644 drivers/eeprom/eeprom_wear_leveling.c
 create mode 100644 platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
 create mode 100644 platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1_config.h

diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index 552171fe689e..5085dea88129 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -173,7 +173,7 @@ ifeq ($(strip $(QUANTUM_PAINTER_ENABLE)), yes)
     include $(QUANTUM_DIR)/painter/rules.mk
 endif
 
-VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi
+VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi wear_leveling
 EEPROM_DRIVER ?= vendor
 ifeq ($(filter $(EEPROM_DRIVER),$(VALID_EEPROM_DRIVER_TYPES)),)
   $(call CATASTROPHIC_ERROR,Invalid EEPROM_DRIVER,EEPROM_DRIVER="$(EEPROM_DRIVER)" is not a valid EEPROM driver)
@@ -186,6 +186,10 @@ else
     # Custom EEPROM implementation -- only needs to implement init/erase/read_block/write_block
     OPT_DEFS += -DEEPROM_DRIVER -DEEPROM_CUSTOM
     SRC += eeprom_driver.c
+  else ifeq ($(strip $(EEPROM_DRIVER)), wear_leveling)
+    # Wear-leveling EEPROM implementation
+    OPT_DEFS += -DEEPROM_DRIVER -DEEPROM_WEAR_LEVELING
+    SRC += eeprom_driver.c eeprom_wear_leveling.c
   else ifeq ($(strip $(EEPROM_DRIVER)), i2c)
     # External I2C EEPROM implementation
     OPT_DEFS += -DEEPROM_DRIVER -DEEPROM_I2C
@@ -238,13 +242,13 @@ else
 endif
 
 VALID_FLASH_DRIVER_TYPES := spi
-FLASH_DRIVER ?= no
-ifneq ($(strip $(FLASH_DRIVER)), no)
+FLASH_DRIVER ?= none
+ifneq ($(strip $(FLASH_DRIVER)), none)
     ifeq ($(filter $(FLASH_DRIVER),$(VALID_FLASH_DRIVER_TYPES)),)
-        $(error FLASH_DRIVER="$(FLASH_DRIVER)" is not a valid FLASH driver)
+        $(call CATASTROPHIC_ERROR,Invalid FLASH_DRIVER,FLASH_DRIVER="$(FLASH_DRIVER)" is not a valid flash driver)
     else
         OPT_DEFS += -DFLASH_ENABLE
-        ifeq ($(strip $(FLASH_DRIVER)), spi)
+        ifeq ($(strip $(FLASH_DRIVER)),spi)
             OPT_DEFS += -DFLASH_DRIVER -DFLASH_SPI
             COMMON_VPATH += $(DRIVER_PATH)/flash
             SRC += flash_spi.c
@@ -252,6 +256,25 @@ ifneq ($(strip $(FLASH_DRIVER)), no)
     endif
 endif
 
+VALID_WEAR_LEVELING_DRIVER_TYPES := custom stm32f4x1
+WEAR_LEVELING_DRIVER ?= none
+ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
+  ifeq ($(filter $(WEAR_LEVELING_DRIVER),$(VALID_WEAR_LEVELING_DRIVER_TYPES)),)
+    $(call CATASTROPHIC_ERROR,Invalid WEAR_LEVELING_DRIVER,WEAR_LEVELING_DRIVER="$(WEAR_LEVELING_DRIVER)" is not a valid wear leveling driver)
+  else
+    OPT_DEFS += -DWEAR_LEVELING_ENABLE
+    COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling
+    COMMON_VPATH += $(DRIVER_PATH)/wear_leveling
+    COMMON_VPATH += $(QUANTUM_DIR)/wear_leveling
+    ifeq ($(strip $(WEAR_LEVELING_DRIVER)), stm32f4x1)
+      OPT_DEFS += -DWEAR_LEVELING_DRIVER_STM32F4X1
+      COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
+      SRC += wear_leveling.c flash_stm32.c wear_leveling_stm32f4x1.c
+      POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_stm32f4x1_config.h
+    endif
+  endif
+endif
+
 RGBLIGHT_ENABLE ?= no
 VALID_RGBLIGHT_TYPES := WS2812 APA102 custom
 
diff --git a/drivers/eeprom/eeprom_wear_leveling.c b/drivers/eeprom/eeprom_wear_leveling.c
new file mode 100644
index 000000000000..bd77eef35cca
--- /dev/null
+++ b/drivers/eeprom/eeprom_wear_leveling.c
@@ -0,0 +1,23 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdint.h>
+#include <string.h>
+
+#include "eeprom_driver.h"
+#include "wear_leveling.h"
+
+void eeprom_driver_init(void) {
+    wear_leveling_init();
+}
+
+void eeprom_driver_erase(void) {
+    wear_leveling_erase();
+}
+
+void eeprom_read_block(void *buf, const void *addr, size_t len) {
+    wear_leveling_read((uint32_t)addr, buf, len);
+}
+
+void eeprom_write_block(const void *buf, void *addr, size_t len) {
+    wear_leveling_write((uint32_t)addr, buf, len);
+}
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
new file mode 100644
index 000000000000..d41e11d3e626
--- /dev/null
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
@@ -0,0 +1,59 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdbool.h>
+#include <hal.h>
+#include "timer.h"
+#include "wear_leveling.h"
+#include "wear_leveling_internal.h"
+#include "flash_stm32.h"
+
+bool backing_store_init(void) {
+    bs_dprintf("Init\n");
+    return true;
+}
+
+bool backing_store_unlock(void) {
+    bs_dprintf("Unlock\n");
+    FLASH_Unlock();
+    return true;
+}
+
+bool backing_store_erase(void) {
+#ifdef WEAR_LEVELING_DEBUG_OUTPUT
+    uint32_t start = timer_read32();
+#endif
+
+    bool         ret = true;
+    FLASH_Status status;
+    for (int i = 0; i < (EMULATED_EEPROM_PAGE_COUNT); ++i) {
+        status = FLASH_ErasePage(EMULATED_EEPROM_BASE_PAGE_ADDRESS + (i * (EMULATED_EEPROM_PAGE_SIZE)));
+        if (status != FLASH_COMPLETE) {
+            ret = false;
+        }
+    }
+
+    bs_dprintf("Backing store erase took %ldms to complete\n", ((long)(timer_read32() - start)));
+    return ret;
+}
+
+bool backing_store_write(uint32_t address, backing_store_int_t value) {
+    uint32_t offset = ((EMULATED_EEPROM_BASE_PAGE_ADDRESS) + address);
+    bs_dprintf("Write ");
+    wl_dump(address, &value, 2);
+    return FLASH_ProgramHalfWord(offset, ~value) == FLASH_COMPLETE;
+}
+
+bool backing_store_lock(void) {
+    bs_dprintf("Lock  \n");
+    FLASH_Lock();
+    return true;
+}
+
+bool backing_store_read(uint32_t address, backing_store_int_t* value) {
+    uint32_t offset = ((EMULATED_EEPROM_BASE_PAGE_ADDRESS) + address);
+    backing_store_int_t *loc = (backing_store_int_t*)offset;
+    *value = ~(*loc);
+    bs_dprintf("Read  ");
+    wl_dump(address, loc, 2);
+    return true;
+}
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1_config.h
new file mode 100644
index 000000000000..a3dc9c35d081
--- /dev/null
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1_config.h
@@ -0,0 +1,33 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+// bodge to force 2nd 16k page
+#ifndef EMULATED_EEPROM_BASE_PAGE_ADDRESS
+#    define EMULATED_EEPROM_BASE_PAGE_ADDRESS 0x08004000
+#endif // EMULATED_EEPROM_BASE_PAGE_ADDRESS
+
+// 16k pages
+#ifndef EMULATED_EEPROM_PAGE_SIZE
+#    define EMULATED_EEPROM_PAGE_SIZE 0x4000
+#endif // EMULATED_EEPROM_PAGE_SIZE
+
+// Only use 1 page
+#ifndef EMULATED_EEPROM_PAGE_COUNT
+#    define EMULATED_EEPROM_PAGE_COUNT 1
+#endif // EMULATED_EEPROM_PAGE_COUNT
+
+// 2-byte writes
+#ifndef BACKING_STORE_WRITE_SIZE
+#    define BACKING_STORE_WRITE_SIZE 2
+#endif
+
+// 16kB space allocated
+#ifndef WEAR_LEVELING_BACKING_SIZE
+#    define WEAR_LEVELING_BACKING_SIZE (EMULATED_EEPROM_PAGE_SIZE)
+#endif // WEAR_LEVELING_BACKING_SIZE
+
+// 4kB logical EEPROM
+#ifndef WEAR_LEVELING_LOGICAL_SIZE
+#    define WEAR_LEVELING_LOGICAL_SIZE 4096
+#endif // WEAR_LEVELING_LOGICAL_SIZE
diff --git a/platforms/eeprom.h b/platforms/eeprom.h
index 091e6e440091..8cb7e342dc71 100644
--- a/platforms/eeprom.h
+++ b/platforms/eeprom.h
@@ -27,6 +27,8 @@ void     eeprom_update_block(const void *__src, void *__dst, size_t __n);
 #        error EEPROM_SIZE has not been defined for custom driver.
 #    endif
 #    define TOTAL_EEPROM_BYTE_COUNT (EEPROM_SIZE)
+#elif defined(EEPROM_WEAR_LEVELING)
+#    define TOTAL_EEPROM_BYTE_COUNT (WEAR_LEVELING_LOGICAL_SIZE)
 #elif defined(EEPROM_TRANSIENT)
 #    include "eeprom_transient.h"
 #    define TOTAL_EEPROM_BYTE_COUNT (TRANSIENT_EEPROM_SIZE)
diff --git a/quantum/wear_leveling/wear_leveling_internal.h b/quantum/wear_leveling/wear_leveling_internal.h
index 74b43932dfde..8a1a87727fa9 100644
--- a/quantum/wear_leveling/wear_leveling_internal.h
+++ b/quantum/wear_leveling/wear_leveling_internal.h
@@ -28,16 +28,17 @@ typedef uint64_t backing_store_int_t;
 #endif
 
 #ifdef WEAR_LEVELING_DEBUG_OUTPUT
-#    include <stdio.h>
-#    define wl_dprintf(...) printf("Wear leveling: " __VA_ARGS__)
+#    include <debug.h>
+#    define bs_dprintf(...) dprintf("Backing store: " __VA_ARGS__)
+#    define wl_dprintf(...) dprintf("Wear leveling: " __VA_ARGS__)
 #    define wl_dump(address, value, length)             \
         do {                                            \
-            printf("[0x%04X]: ", (int)(address));       \
+            dprintf("[0x%04X]: ", (int)(address));       \
             const uint8_t* p = (const uint8_t*)(value); \
             for (int i = 0; i < (length); ++i) {        \
-                printf(" %02X", (int)p[i]);             \
+                dprintf(" %02X", (int)p[i]);             \
             }                                           \
-            printf("\n");                               \
+            dprintf("\n");                               \
         } while (0)
 #else
 #    define wl_dprintf(...) \

From 10a916bd52c6bb619ad22f5fab024ab299d099fd Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 13 Jun 2022 11:55:14 +1000
Subject: [PATCH 02/19] Add support to tinyuf2 F411 blackpill.

---
 .../onekey/blackpill_f411_tinyuf2/config.h    |  3 ++
 .../onekey/blackpill_f411_tinyuf2/rules.mk    |  4 ++
 keyboards/handwired/onekey/config.h           |  2 +
 keyboards/handwired/onekey/onekey.c           | 44 +++++++++++++++++++
 4 files changed, 53 insertions(+)

diff --git a/keyboards/handwired/onekey/blackpill_f411_tinyuf2/config.h b/keyboards/handwired/onekey/blackpill_f411_tinyuf2/config.h
index 44ec0bfc6e87..fd7b9e2125a2 100755
--- a/keyboards/handwired/onekey/blackpill_f411_tinyuf2/config.h
+++ b/keyboards/handwired/onekey/blackpill_f411_tinyuf2/config.h
@@ -31,3 +31,6 @@
 #define RGB_DI_PIN A1
 
 #define ADC_PIN A0
+
+// Force 3rd 16k page => 48k offset
+#define EMULATED_EEPROM_BASE_PAGE_ADDRESS 0x0800C000
diff --git a/keyboards/handwired/onekey/blackpill_f411_tinyuf2/rules.mk b/keyboards/handwired/onekey/blackpill_f411_tinyuf2/rules.mk
index 70180ee790b5..59398eaa54f5 100755
--- a/keyboards/handwired/onekey/blackpill_f411_tinyuf2/rules.mk
+++ b/keyboards/handwired/onekey/blackpill_f411_tinyuf2/rules.mk
@@ -5,3 +5,7 @@ MCU = STM32F411
 BOOTLOADER = tinyuf2
 
 KEYBOARD_SHARED_EP = yes
+
+# Use the wear-leveling driver
+EEPROM_DRIVER = wear_leveling
+WEAR_LEVELING_DRIVER = stm32f4x1
\ No newline at end of file
diff --git a/keyboards/handwired/onekey/config.h b/keyboards/handwired/onekey/config.h
index 88c15806cefd..d06ffc32a640 100644
--- a/keyboards/handwired/onekey/config.h
+++ b/keyboards/handwired/onekey/config.h
@@ -63,3 +63,5 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 /* Bootmagic Lite key configuration */
 //#define BOOTMAGIC_LITE_ROW 0
 //#define BOOTMAGIC_LITE_COLUMN 0
+
+#define WEAR_LEVELING_DEBUG_OUTPUT
\ No newline at end of file
diff --git a/keyboards/handwired/onekey/onekey.c b/keyboards/handwired/onekey/onekey.c
index cbd67012ce6d..8efb890cbef9 100644
--- a/keyboards/handwired/onekey/onekey.c
+++ b/keyboards/handwired/onekey/onekey.c
@@ -1 +1,45 @@
 #include "onekey.h"
+
+uint8_t prng(void) {
+    static uint8_t s = 0xAA, a = 0;
+    s ^= s << 3;
+    s ^= s >> 5;
+    s ^= a++ >> 2;
+    return s;
+}
+
+void keyboard_post_init_user(void) {
+    // Customise these values to desired behaviour
+    debug_enable = true;
+    // debug_matrix = true;
+    // debug_keyboard=true;
+    // debug_mouse=true;
+
+    wait_ms(10000);
+}
+
+void matrix_scan_user(void) {
+    static uint32_t last_eeprom_access = 0;
+    uint32_t        now                = timer_read32();
+    if (now - last_eeprom_access > 500) {
+        dprint("reading eeprom\n");
+        last_eeprom_access = now;
+
+        union {
+            uint8_t  bytes[4];
+            uint32_t raw;
+        } tmp;
+        tmp.bytes[0] = prng();
+        tmp.bytes[1] = prng();
+        tmp.bytes[2] = prng();
+        tmp.bytes[3] = prng();
+
+        eeconfig_update_user(tmp.raw);
+        uint32_t value = eeconfig_read_user();
+        if (value != tmp.raw) {
+            dprint("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
+            dprint("!! EEPROM readback mismatch!\n");
+            dprint("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
+        }
+    }
+}

From 5151180e22a44a184c2ae8452c76252170741e81 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 13 Jun 2022 14:05:39 +1000
Subject: [PATCH 03/19] Add support for EFL-backed.

---
 builddefs/common_features.mk                  |  8 +-
 keyboards/handwired/onekey/bluepill/rules.mk  |  3 +
 .../drivers/wear_leveling/wear_leveling_efl.c | 85 +++++++++++++++++++
 .../wear_leveling/wear_leveling_efl_config.h  | 18 ++++
 .../wear_leveling/wear_leveling_stm32f4x1.c   | 10 +--
 .../wear_leveling/wear_leveling_internal.h    |  6 +-
 6 files changed, 120 insertions(+), 10 deletions(-)
 create mode 100644 platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
 create mode 100644 platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h

diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index 5085dea88129..4b1bc6470ec9 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -256,7 +256,7 @@ ifneq ($(strip $(FLASH_DRIVER)), none)
     endif
 endif
 
-VALID_WEAR_LEVELING_DRIVER_TYPES := custom stm32f4x1
+VALID_WEAR_LEVELING_DRIVER_TYPES := custom efl stm32f4x1
 WEAR_LEVELING_DRIVER ?= none
 ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
   ifeq ($(filter $(WEAR_LEVELING_DRIVER),$(VALID_WEAR_LEVELING_DRIVER_TYPES)),)
@@ -266,7 +266,11 @@ ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
     COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling
     COMMON_VPATH += $(DRIVER_PATH)/wear_leveling
     COMMON_VPATH += $(QUANTUM_DIR)/wear_leveling
-    ifeq ($(strip $(WEAR_LEVELING_DRIVER)), stm32f4x1)
+    ifeq ($(strip $(WEAR_LEVELING_DRIVER)), efl)
+      OPT_DEFS += -DWEAR_LEVELING_DRIVER_EFL -DHAL_USE_EFL
+      SRC += wear_leveling.c wear_leveling_efl.c
+      POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_efl_config.h
+    else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), stm32f4x1)
       OPT_DEFS += -DWEAR_LEVELING_DRIVER_STM32F4X1
       COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
       SRC += wear_leveling.c flash_stm32.c wear_leveling_stm32f4x1.c
diff --git a/keyboards/handwired/onekey/bluepill/rules.mk b/keyboards/handwired/onekey/bluepill/rules.mk
index 019b1130d995..cefb8d72b3aa 100644
--- a/keyboards/handwired/onekey/bluepill/rules.mk
+++ b/keyboards/handwired/onekey/bluepill/rules.mk
@@ -6,3 +6,6 @@ BOOTLOADER = stm32duino
 
 # Enter lower-power sleep mode when on the ChibiOS idle thread
 OPT_DEFS += -DCORTEX_ENABLE_WFI_IDLE=TRUE
+
+EEPROM_DRIVER = wear_leveling
+WEAR_LEVELING_DRIVER = efl
\ No newline at end of file
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
new file mode 100644
index 000000000000..d75da53ca804
--- /dev/null
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
@@ -0,0 +1,85 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdbool.h>
+#include <hal.h>
+#include "timer.h"
+#include "wear_leveling.h"
+#include "wear_leveling_internal.h"
+
+static flash_offset_t base_offset  = UINT32_MAX;
+static uint8_t        first_sector = UINT8_MAX;
+static uint8_t        sector_count = UINT8_MAX;
+static BaseFlash *    flash;
+
+bool backing_store_init(void) {
+    bs_dprintf("Init\n");
+    flash = (BaseFlash *)&EFLD1;
+
+    // Work out how many sectors we want to use, working backwards from the end of the flash
+    const flash_descriptor_t *desc    = flashGetDescriptor(flash);
+    uint32_t                  counter = 0;
+    for (flash_sector_t i = 0; i < desc->sectors_count; ++i) {
+        first_sector = desc->sectors_count - i - 1;
+        counter += flashGetSectorSize(flash, first_sector);
+        if (counter >= (WEAR_LEVELING_BACKING_SIZE)) {
+            sector_count = i + 1;
+            base_offset  = flashGetSectorOffset(flash, first_sector);
+            break;
+        }
+    }
+
+    return true;
+}
+
+bool backing_store_unlock(void) {
+    bs_dprintf("Unlock\n");
+    return eflStart(&EFLD1, NULL) == HAL_RET_SUCCESS;
+}
+
+bool backing_store_erase(void) {
+#ifdef WEAR_LEVELING_DEBUG_OUTPUT
+    uint32_t start = timer_read32();
+#endif
+
+    bool          ret = true;
+    flash_error_t status;
+    for (int i = 0; i < sector_count; ++i) {
+        // Kick off the sector erase
+        status = flashStartEraseSector(flash, first_sector + i);
+        if (status != FLASH_NO_ERROR && status != FLASH_BUSY_ERASING) {
+            ret = false;
+        }
+
+        // Wait for the erase to complete
+        status = flashWaitErase(flash);
+        if (status != FLASH_NO_ERROR && status != FLASH_BUSY_ERASING) {
+            ret = false;
+        }
+    }
+
+    bs_dprintf("Backing store erase took %ldms to complete\n", ((long)(timer_read32() - start)));
+    return ret;
+}
+
+bool backing_store_write(uint32_t address, backing_store_int_t value) {
+    uint32_t offset = (base_offset + address);
+    bs_dprintf("Write ");
+    wl_dump(offset, &value, 2);
+    value = ~value;
+    return flashProgram(flash, offset, 2, (const uint8_t *)&value) == FLASH_NO_ERROR;
+}
+
+bool backing_store_lock(void) {
+    bs_dprintf("Lock  \n");
+    eflStop(&EFLD1);
+    return true;
+}
+
+bool backing_store_read(uint32_t address, backing_store_int_t *value) {
+    uint32_t             offset = (base_offset + address);
+    backing_store_int_t *loc    = (backing_store_int_t *)offset;
+    *value                      = ~(*loc);
+    bs_dprintf("Read  ");
+    wl_dump(offset, loc, 2);
+    return true;
+}
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
new file mode 100644
index 000000000000..a9710f5a39aa
--- /dev/null
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
@@ -0,0 +1,18 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+// 2-byte writes
+#ifndef BACKING_STORE_WRITE_SIZE
+#    define BACKING_STORE_WRITE_SIZE 2
+#endif
+
+// 16kB space allocated
+#ifndef WEAR_LEVELING_BACKING_SIZE
+#    define WEAR_LEVELING_BACKING_SIZE 16384
+#endif // WEAR_LEVELING_BACKING_SIZE
+
+// 4kB logical EEPROM
+#ifndef WEAR_LEVELING_LOGICAL_SIZE
+#    define WEAR_LEVELING_LOGICAL_SIZE 4096
+#endif // WEAR_LEVELING_LOGICAL_SIZE
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
index d41e11d3e626..1621277a219e 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
@@ -39,7 +39,7 @@ bool backing_store_erase(void) {
 bool backing_store_write(uint32_t address, backing_store_int_t value) {
     uint32_t offset = ((EMULATED_EEPROM_BASE_PAGE_ADDRESS) + address);
     bs_dprintf("Write ");
-    wl_dump(address, &value, 2);
+    wl_dump(offset, &value, 2);
     return FLASH_ProgramHalfWord(offset, ~value) == FLASH_COMPLETE;
 }
 
@@ -50,10 +50,10 @@ bool backing_store_lock(void) {
 }
 
 bool backing_store_read(uint32_t address, backing_store_int_t* value) {
-    uint32_t offset = ((EMULATED_EEPROM_BASE_PAGE_ADDRESS) + address);
-    backing_store_int_t *loc = (backing_store_int_t*)offset;
-    *value = ~(*loc);
+    uint32_t             offset = ((EMULATED_EEPROM_BASE_PAGE_ADDRESS) + address);
+    backing_store_int_t* loc    = (backing_store_int_t*)offset;
+    *value                      = ~(*loc);
     bs_dprintf("Read  ");
-    wl_dump(address, loc, 2);
+    wl_dump(offset, loc, 2);
     return true;
 }
diff --git a/quantum/wear_leveling/wear_leveling_internal.h b/quantum/wear_leveling/wear_leveling_internal.h
index 8a1a87727fa9..bda86ae8557e 100644
--- a/quantum/wear_leveling/wear_leveling_internal.h
+++ b/quantum/wear_leveling/wear_leveling_internal.h
@@ -33,12 +33,12 @@ typedef uint64_t backing_store_int_t;
 #    define wl_dprintf(...) dprintf("Wear leveling: " __VA_ARGS__)
 #    define wl_dump(address, value, length)             \
         do {                                            \
-            dprintf("[0x%04X]: ", (int)(address));       \
+            dprintf("[0x%04X]: ", (int)(address));      \
             const uint8_t* p = (const uint8_t*)(value); \
             for (int i = 0; i < (length); ++i) {        \
-                dprintf(" %02X", (int)p[i]);             \
+                dprintf(" %02X", (int)p[i]);            \
             }                                           \
-            dprintf("\n");                               \
+            dprintf("\n");                              \
         } while (0)
 #else
 #    define wl_dprintf(...) \

From c9321cdffc825c05b7aa5e57b52a5c629f2c0394 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 13 Jun 2022 15:05:42 +1000
Subject: [PATCH 04/19] 8-byte EFL compatibility.

---
 .../drivers/wear_leveling/wear_leveling_efl.c |  6 ++--
 .../wear_leveling/wear_leveling_efl_config.h  | 32 ++++++++++++++++++-
 .../wear_leveling/wear_leveling_internal.h    |  3 ++
 3 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
index d75da53ca804..cc67250b6ad9 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
@@ -64,9 +64,9 @@ bool backing_store_erase(void) {
 bool backing_store_write(uint32_t address, backing_store_int_t value) {
     uint32_t offset = (base_offset + address);
     bs_dprintf("Write ");
-    wl_dump(offset, &value, 2);
+    wl_dump(offset, &value, sizeof(value));
     value = ~value;
-    return flashProgram(flash, offset, 2, (const uint8_t *)&value) == FLASH_NO_ERROR;
+    return flashProgram(flash, offset, sizeof(value), (const uint8_t *)&value) == FLASH_NO_ERROR;
 }
 
 bool backing_store_lock(void) {
@@ -80,6 +80,6 @@ bool backing_store_read(uint32_t address, backing_store_int_t *value) {
     backing_store_int_t *loc    = (backing_store_int_t *)offset;
     *value                      = ~(*loc);
     bs_dprintf("Read  ");
-    wl_dump(offset, loc, 2);
+    wl_dump(offset, loc, sizeof(value));
     return true;
 }
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
index a9710f5a39aa..6b7f20370158 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
@@ -2,9 +2,39 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 #pragma once
 
+#ifndef __ASSEMBLER__
+#include <hal.h>
+#endif
+
 // 2-byte writes
 #ifndef BACKING_STORE_WRITE_SIZE
-#    define BACKING_STORE_WRITE_SIZE 2
+// These need to match EFL's XXXXXX_FLASH_LINE_SIZE, see associated code in `lib/chibios/os/hal/ports/**/hal_efl_lld.c`,
+// or associated `stm32_registry.h` for the MCU in question (or equivalent for the family).
+#    if defined(MCU_GD32V)
+#        define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
+#    elif defined(MCU_STM32)               //  defined(MCU_?????)
+#        if defined(STM32_FLASH_LINE_SIZE)
+#            define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE)
+#        else // defined(STM32_FLASH_LINE_SIZE)
+#            if defined(STM32F0XX)
+#                error "ChibiOS hasn't defined a specific STM32_FLASH_LINE_SIZE, but should've." // normally defined in stm32_registry.h, should be set by STM32_FLASH_LINE_SIZE
+#            elif defined(STM32F1XX)
+#                define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
+#            elif defined(STM32F3XX)
+#                define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
+#            elif defined(STM32F4XX)
+#                define BACKING_STORE_WRITE_SIZE (1 << STM32_FLASH_PSIZE) // from hal_efl_lld.c
+#            elif defined(STM32L4XX)
+#                define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
+#            elif defined(STM32G0XX)
+#                define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
+#            elif defined(STM32G4XX)
+#                error "ChibiOS hasn't defined a specific STM32_FLASH_LINE_SIZE, but should've." // normally defined in stm32_registry.h, should be set by STM32_FLASH_LINE_SIZE
+#            endif                         // defined(STM32??XX)
+#        endif                             // defined(STM32_FLASH_LINE_SIZE)
+#    elif defined(MCU_GD32V)               //  defined(MCU_?????)
+#        define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
+#    endif                                 // defined(MCU_?????)
 #endif
 
 // 16kB space allocated
diff --git a/quantum/wear_leveling/wear_leveling_internal.h b/quantum/wear_leveling/wear_leveling_internal.h
index bda86ae8557e..12cf94aeaa6a 100644
--- a/quantum/wear_leveling/wear_leveling_internal.h
+++ b/quantum/wear_leveling/wear_leveling_internal.h
@@ -44,6 +44,9 @@ typedef uint64_t backing_store_int_t;
 #    define wl_dprintf(...) \
         do {                \
         } while (0)
+#    define bs_dprintf(...) \
+        do {                \
+        } while (0)
 #    define wl_dump(...) \
         do {             \
         } while (0)

From f96507c50d6e1ce88f4f6abcbbb9453d81a18da7 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 13 Jun 2022 15:29:46 +1000
Subject: [PATCH 05/19] Rollback `keyboards`

---
 .../onekey/blackpill_f411_tinyuf2/config.h    |  3 --
 .../onekey/blackpill_f411_tinyuf2/rules.mk    |  4 --
 keyboards/handwired/onekey/bluepill/rules.mk  |  3 --
 keyboards/handwired/onekey/config.h           |  2 -
 keyboards/handwired/onekey/onekey.c           | 44 -------------------
 5 files changed, 56 deletions(-)

diff --git a/keyboards/handwired/onekey/blackpill_f411_tinyuf2/config.h b/keyboards/handwired/onekey/blackpill_f411_tinyuf2/config.h
index fd7b9e2125a2..44ec0bfc6e87 100755
--- a/keyboards/handwired/onekey/blackpill_f411_tinyuf2/config.h
+++ b/keyboards/handwired/onekey/blackpill_f411_tinyuf2/config.h
@@ -31,6 +31,3 @@
 #define RGB_DI_PIN A1
 
 #define ADC_PIN A0
-
-// Force 3rd 16k page => 48k offset
-#define EMULATED_EEPROM_BASE_PAGE_ADDRESS 0x0800C000
diff --git a/keyboards/handwired/onekey/blackpill_f411_tinyuf2/rules.mk b/keyboards/handwired/onekey/blackpill_f411_tinyuf2/rules.mk
index 59398eaa54f5..70180ee790b5 100755
--- a/keyboards/handwired/onekey/blackpill_f411_tinyuf2/rules.mk
+++ b/keyboards/handwired/onekey/blackpill_f411_tinyuf2/rules.mk
@@ -5,7 +5,3 @@ MCU = STM32F411
 BOOTLOADER = tinyuf2
 
 KEYBOARD_SHARED_EP = yes
-
-# Use the wear-leveling driver
-EEPROM_DRIVER = wear_leveling
-WEAR_LEVELING_DRIVER = stm32f4x1
\ No newline at end of file
diff --git a/keyboards/handwired/onekey/bluepill/rules.mk b/keyboards/handwired/onekey/bluepill/rules.mk
index cefb8d72b3aa..019b1130d995 100644
--- a/keyboards/handwired/onekey/bluepill/rules.mk
+++ b/keyboards/handwired/onekey/bluepill/rules.mk
@@ -6,6 +6,3 @@ BOOTLOADER = stm32duino
 
 # Enter lower-power sleep mode when on the ChibiOS idle thread
 OPT_DEFS += -DCORTEX_ENABLE_WFI_IDLE=TRUE
-
-EEPROM_DRIVER = wear_leveling
-WEAR_LEVELING_DRIVER = efl
\ No newline at end of file
diff --git a/keyboards/handwired/onekey/config.h b/keyboards/handwired/onekey/config.h
index d06ffc32a640..88c15806cefd 100644
--- a/keyboards/handwired/onekey/config.h
+++ b/keyboards/handwired/onekey/config.h
@@ -63,5 +63,3 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 /* Bootmagic Lite key configuration */
 //#define BOOTMAGIC_LITE_ROW 0
 //#define BOOTMAGIC_LITE_COLUMN 0
-
-#define WEAR_LEVELING_DEBUG_OUTPUT
\ No newline at end of file
diff --git a/keyboards/handwired/onekey/onekey.c b/keyboards/handwired/onekey/onekey.c
index 8efb890cbef9..cbd67012ce6d 100644
--- a/keyboards/handwired/onekey/onekey.c
+++ b/keyboards/handwired/onekey/onekey.c
@@ -1,45 +1 @@
 #include "onekey.h"
-
-uint8_t prng(void) {
-    static uint8_t s = 0xAA, a = 0;
-    s ^= s << 3;
-    s ^= s >> 5;
-    s ^= a++ >> 2;
-    return s;
-}
-
-void keyboard_post_init_user(void) {
-    // Customise these values to desired behaviour
-    debug_enable = true;
-    // debug_matrix = true;
-    // debug_keyboard=true;
-    // debug_mouse=true;
-
-    wait_ms(10000);
-}
-
-void matrix_scan_user(void) {
-    static uint32_t last_eeprom_access = 0;
-    uint32_t        now                = timer_read32();
-    if (now - last_eeprom_access > 500) {
-        dprint("reading eeprom\n");
-        last_eeprom_access = now;
-
-        union {
-            uint8_t  bytes[4];
-            uint32_t raw;
-        } tmp;
-        tmp.bytes[0] = prng();
-        tmp.bytes[1] = prng();
-        tmp.bytes[2] = prng();
-        tmp.bytes[3] = prng();
-
-        eeconfig_update_user(tmp.raw);
-        uint32_t value = eeconfig_read_user();
-        if (value != tmp.raw) {
-            dprint("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
-            dprint("!! EEPROM readback mismatch!\n");
-            dprint("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
-        }
-    }
-}

From ba5e1cab020547157ea4994105a641d81b1527da Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 13 Jun 2022 15:59:43 +1000
Subject: [PATCH 06/19] `qmk format-c`

---
 .../chibios/drivers/wear_leveling/wear_leveling_efl_config.h    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
index 6b7f20370158..18b6e67b02d9 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
@@ -3,7 +3,7 @@
 #pragma once
 
 #ifndef __ASSEMBLER__
-#include <hal.h>
+#    include <hal.h>
 #endif
 
 // 2-byte writes

From 7946b172a2f992ad1f7ad558209278d349cc0144 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 13 Jun 2022 16:45:33 +1000
Subject: [PATCH 07/19] Add initial configs for NUC123. Doesn't boot.

---
 keyboards/ducky/one2mini/1861st/config.h           |  4 ++++
 keyboards/ducky/one2mini/1861st/mcuconf.h          |  3 +++
 keyboards/ducky/one2mini/1861st/rules.mk           |  3 +++
 .../wear_leveling/wear_leveling_efl_config.h       | 14 +++++++-------
 4 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/keyboards/ducky/one2mini/1861st/config.h b/keyboards/ducky/one2mini/1861st/config.h
index 3f5676280c18..4c46b7e51e6d 100644
--- a/keyboards/ducky/one2mini/1861st/config.h
+++ b/keyboards/ducky/one2mini/1861st/config.h
@@ -54,3 +54,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #define FORCE_NKRO
 
 #define GPIO_INPUT_PIN_DELAY (NUC123_HCLK / 6 / 1000000L)
+
+#define STM32_USB_USE_OTG1 TRUE // Make the endpoints reorderable. We should give this a different name.
+#define WEAR_LEVELING_BACKING_SIZE 4096
+#define WEAR_LEVELING_LOGICAL_SIZE 1024
diff --git a/keyboards/ducky/one2mini/1861st/mcuconf.h b/keyboards/ducky/one2mini/1861st/mcuconf.h
index 12e9d2a82dc3..ad5dab351811 100644
--- a/keyboards/ducky/one2mini/1861st/mcuconf.h
+++ b/keyboards/ducky/one2mini/1861st/mcuconf.h
@@ -46,6 +46,9 @@
 #define NUC123_SERIAL_USE_UART0 TRUE
 #define NUC123_SERIAL_CLKSRC NUC123_SERIAL_CLKSRC_HSI
 
+#define NUC123_EFL_USE_EFL1 TRUE
+#define NUC123_EFL_ACCESS_APROM TRUE
+
 #define NUC123_MCUCONF
 
 #endif /* _MCUCONF_H_ */
diff --git a/keyboards/ducky/one2mini/1861st/rules.mk b/keyboards/ducky/one2mini/1861st/rules.mk
index a5b4708b26f4..0ca50f18d2e5 100644
--- a/keyboards/ducky/one2mini/1861st/rules.mk
+++ b/keyboards/ducky/one2mini/1861st/rules.mk
@@ -32,3 +32,6 @@ DIP_SWITCH_ENABLE = yes
 
 # Enter lower-power sleep mode when on the ChibiOS idle thread
 OPT_DEFS += -DCORTEX_ENABLE_WFI_IDLE=TRUE
+
+EEPROM_DRIVER = wear_leveling
+WEAR_LEVELING_DRIVER = efl
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
index 18b6e67b02d9..15c773bcf2d0 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
@@ -10,9 +10,11 @@
 #ifndef BACKING_STORE_WRITE_SIZE
 // These need to match EFL's XXXXXX_FLASH_LINE_SIZE, see associated code in `lib/chibios/os/hal/ports/**/hal_efl_lld.c`,
 // or associated `stm32_registry.h` for the MCU in question (or equivalent for the family).
-#    if defined(MCU_GD32V)
+#    if defined(GD32VF103)
 #        define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
-#    elif defined(MCU_STM32)               //  defined(MCU_?????)
+#    elif defined(NUC123SD4AN0)            //  defined(MCU_?????)
+#        define BACKING_STORE_WRITE_SIZE 4 // from hal_efl_lld.c
+#    elif defined(MCU_STM32) //  defined(MCU_?????)
 #        if defined(STM32_FLASH_LINE_SIZE)
 #            define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE)
 #        else // defined(STM32_FLASH_LINE_SIZE)
@@ -30,11 +32,9 @@
 #                define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
 #            elif defined(STM32G4XX)
 #                error "ChibiOS hasn't defined a specific STM32_FLASH_LINE_SIZE, but should've." // normally defined in stm32_registry.h, should be set by STM32_FLASH_LINE_SIZE
-#            endif                         // defined(STM32??XX)
-#        endif                             // defined(STM32_FLASH_LINE_SIZE)
-#    elif defined(MCU_GD32V)               //  defined(MCU_?????)
-#        define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
-#    endif                                 // defined(MCU_?????)
+#            endif // defined(STM32??XX)
+#        endif     // defined(STM32_FLASH_LINE_SIZE)
+#    endif         // defined(MCU_?????)
 #endif
 
 // 16kB space allocated

From ee0ff769879b9b03dccd6378154ee5bfc8d4b5a8 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 13 Jun 2022 17:22:06 +1000
Subject: [PATCH 08/19] Allow specifying first sector directly.

---
 keyboards/ducky/one2mini/1861st/config.h      |  4 ---
 keyboards/ducky/one2mini/1861st/mcuconf.h     |  3 --
 keyboards/ducky/one2mini/1861st/rules.mk      |  3 --
 .../drivers/wear_leveling/wear_leveling_efl.c | 35 ++++++++++++++++---
 .../wear_leveling/wear_leveling_efl_config.h  |  5 ++-
 5 files changed, 35 insertions(+), 15 deletions(-)

diff --git a/keyboards/ducky/one2mini/1861st/config.h b/keyboards/ducky/one2mini/1861st/config.h
index 4c46b7e51e6d..3f5676280c18 100644
--- a/keyboards/ducky/one2mini/1861st/config.h
+++ b/keyboards/ducky/one2mini/1861st/config.h
@@ -54,7 +54,3 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #define FORCE_NKRO
 
 #define GPIO_INPUT_PIN_DELAY (NUC123_HCLK / 6 / 1000000L)
-
-#define STM32_USB_USE_OTG1 TRUE // Make the endpoints reorderable. We should give this a different name.
-#define WEAR_LEVELING_BACKING_SIZE 4096
-#define WEAR_LEVELING_LOGICAL_SIZE 1024
diff --git a/keyboards/ducky/one2mini/1861st/mcuconf.h b/keyboards/ducky/one2mini/1861st/mcuconf.h
index ad5dab351811..12e9d2a82dc3 100644
--- a/keyboards/ducky/one2mini/1861st/mcuconf.h
+++ b/keyboards/ducky/one2mini/1861st/mcuconf.h
@@ -46,9 +46,6 @@
 #define NUC123_SERIAL_USE_UART0 TRUE
 #define NUC123_SERIAL_CLKSRC NUC123_SERIAL_CLKSRC_HSI
 
-#define NUC123_EFL_USE_EFL1 TRUE
-#define NUC123_EFL_ACCESS_APROM TRUE
-
 #define NUC123_MCUCONF
 
 #endif /* _MCUCONF_H_ */
diff --git a/keyboards/ducky/one2mini/1861st/rules.mk b/keyboards/ducky/one2mini/1861st/rules.mk
index 0ca50f18d2e5..a5b4708b26f4 100644
--- a/keyboards/ducky/one2mini/1861st/rules.mk
+++ b/keyboards/ducky/one2mini/1861st/rules.mk
@@ -32,6 +32,3 @@ DIP_SWITCH_ENABLE = yes
 
 # Enter lower-power sleep mode when on the ChibiOS idle thread
 OPT_DEFS += -DCORTEX_ENABLE_WFI_IDLE=TRUE
-
-EEPROM_DRIVER = wear_leveling
-WEAR_LEVELING_DRIVER = efl
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
index cc67250b6ad9..d85fdc14c88a 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
@@ -6,18 +6,43 @@
 #include "wear_leveling.h"
 #include "wear_leveling_internal.h"
 
-static flash_offset_t base_offset  = UINT32_MAX;
-static uint8_t        first_sector = UINT8_MAX;
-static uint8_t        sector_count = UINT8_MAX;
+static flash_offset_t base_offset = UINT32_MAX;
+
+#if defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
+static flash_sector_t first_sector = WEAR_LEVELING_EFL_FIRST_SECTOR;
+#else  // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
+static flash_sector_t first_sector = UINT16_MAX;
+#endif // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
+
+static flash_sector_t sector_count = UINT16_MAX;
 static BaseFlash *    flash;
 
 bool backing_store_init(void) {
     bs_dprintf("Init\n");
     flash = (BaseFlash *)&EFLD1;
 
-    // Work out how many sectors we want to use, working backwards from the end of the flash
     const flash_descriptor_t *desc    = flashGetDescriptor(flash);
     uint32_t                  counter = 0;
+
+#if defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
+
+    // Work out how many sectors we want to use, working forwards from the first sector specified
+    for (flash_sector_t i = 0; i < desc->sectors_count - first_sector; ++i) {
+        counter += flashGetSectorSize(flash, first_sector + i);
+        if (counter >= (WEAR_LEVELING_BACKING_SIZE)) {
+            sector_count = i + 1;
+            base_offset  = flashGetSectorOffset(flash, first_sector);
+            break;
+        }
+    }
+    if (sector_count == UINT16_MAX) {
+        // We didn't get the required number of sectors. Can't do anything here. Fault.
+        chSysHalt("Invalid sector count intended to be used with wear_leveling");
+    }
+
+#else // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
+
+    // Work out how many sectors we want to use, working backwards from the end of the flash
     for (flash_sector_t i = 0; i < desc->sectors_count; ++i) {
         first_sector = desc->sectors_count - i - 1;
         counter += flashGetSectorSize(flash, first_sector);
@@ -28,6 +53,8 @@ bool backing_store_init(void) {
         }
     }
 
+#endif // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
+
     return true;
 }
 
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
index 15c773bcf2d0..58eb09cfa493 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
@@ -13,8 +13,11 @@
 #    if defined(GD32VF103)
 #        define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
 #    elif defined(NUC123SD4AN0)            //  defined(MCU_?????)
+#        ifndef NUC123_EFL_IMPLEMENTATION_TESTING
+#            error "This configuration currently results in a broken build."
+#        endif                             // NUC123_EFL_IMPLEMENTATION_TESTING
 #        define BACKING_STORE_WRITE_SIZE 4 // from hal_efl_lld.c
-#    elif defined(MCU_STM32) //  defined(MCU_?????)
+#    elif defined(MCU_STM32)               //  defined(MCU_?????)
 #        if defined(STM32_FLASH_LINE_SIZE)
 #            define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE)
 #        else // defined(STM32_FLASH_LINE_SIZE)

From 0fbd305f527017fdb236c6849e68c9bfeb830860 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Tue, 14 Jun 2022 07:58:55 +1000
Subject: [PATCH 09/19] Error checks.

---
 .../drivers/wear_leveling/wear_leveling_efl_config.h        | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
index 58eb09cfa493..7ef99125ad3a 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
@@ -21,9 +21,7 @@
 #        if defined(STM32_FLASH_LINE_SIZE)
 #            define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE)
 #        else // defined(STM32_FLASH_LINE_SIZE)
-#            if defined(STM32F0XX)
-#                error "ChibiOS hasn't defined a specific STM32_FLASH_LINE_SIZE, but should've." // normally defined in stm32_registry.h, should be set by STM32_FLASH_LINE_SIZE
-#            elif defined(STM32F1XX)
+#            if defined(STM32F1XX)
 #                define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
 #            elif defined(STM32F3XX)
 #                define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
@@ -33,7 +31,7 @@
 #                define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
 #            elif defined(STM32G0XX)
 #                define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
-#            elif defined(STM32G4XX)
+#            else
 #                error "ChibiOS hasn't defined a specific STM32_FLASH_LINE_SIZE, but should've." // normally defined in stm32_registry.h, should be set by STM32_FLASH_LINE_SIZE
 #            endif // defined(STM32??XX)
 #        endif     // defined(STM32_FLASH_LINE_SIZE)

From f7f0420446e35ca69ca3eee2cf55a26da0aa4e48 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Wed, 22 Jun 2022 12:48:23 +1000
Subject: [PATCH 10/19] Reworked preprocessor conditionals.

---
 builddefs/build_keyboard.mk                   | 16 +++++++
 .../wear_leveling/wear_leveling_efl_config.h  | 45 ++++++++++---------
 platforms/chibios/platform.mk                 |  5 +++
 3 files changed, 44 insertions(+), 22 deletions(-)

diff --git a/builddefs/build_keyboard.mk b/builddefs/build_keyboard.mk
index 2b9ec48e7045..b5616a438f03 100644
--- a/builddefs/build_keyboard.mk
+++ b/builddefs/build_keyboard.mk
@@ -13,6 +13,14 @@ endif
 include paths.mk
 include $(BUILDDEFS_PATH)/message.mk
 
+# Helper to add defines with a 'QMK_' prefix
+define add_qmk_prefix_defs
+    ifdef $1
+        # Need to cater for 'STM32L4xx+'
+        OPT_DEFS += -DQMK_$(2)="$($1)" -DQMK_$(2)_$(shell echo $($1) | sed -e 's@+@Plus@g' -e 's@[^a-zA-Z0-9]@_@g' | tr '[:lower:]' '[:upper:]')
+    endif
+endef
+
 # Set the qmk cli to use
 QMK_BIN ?= qmk
 
@@ -438,6 +446,14 @@ else
     include $(TMK_PATH)/protocol/$(PLATFORM_KEY).mk
 endif
 
+# Setup definitions based on the selected MCU
+$(eval $(call add_qmk_prefix_defs,MCU_ORIG,MCU))
+$(eval $(call add_qmk_prefix_defs,MCU_ARCH,MCU_ARCH))
+$(eval $(call add_qmk_prefix_defs,MCU_PORT_NAME,MCU_PORT_NAME))
+$(eval $(call add_qmk_prefix_defs,MCU_FAMILY,MCU_FAMILY))
+$(eval $(call add_qmk_prefix_defs,MCU_SERIES,MCU_SERIES))
+$(eval $(call add_qmk_prefix_defs,BOARD,BOARD))
+
 # TODO: remove this bodge?
 PROJECT_DEFS := $(OPT_DEFS)
 PROJECT_INC := $(VPATH) $(EXTRAINCDIRS) $(KEYBOARD_PATHS)
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
index 7ef99125ad3a..2d2716aac2d5 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
@@ -6,44 +6,45 @@
 #    include <hal.h>
 #endif
 
-// 2-byte writes
+// Work out how many bytes per write
 #ifndef BACKING_STORE_WRITE_SIZE
 // These need to match EFL's XXXXXX_FLASH_LINE_SIZE, see associated code in `lib/chibios/os/hal/ports/**/hal_efl_lld.c`,
 // or associated `stm32_registry.h` for the MCU in question (or equivalent for the family).
-#    if defined(GD32VF103)
+#    if defined(QMK_MCU_SERIES_GD32VF103)
 #        define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
-#    elif defined(NUC123SD4AN0)            //  defined(MCU_?????)
-#        ifndef NUC123_EFL_IMPLEMENTATION_TESTING
-#            error "This configuration currently results in a broken build."
-#        endif                             // NUC123_EFL_IMPLEMENTATION_TESTING
+#    elif defined(QMK_MCU_FAMILY_NUC123)
 #        define BACKING_STORE_WRITE_SIZE 4 // from hal_efl_lld.c
-#    elif defined(MCU_STM32)               //  defined(MCU_?????)
-#        if defined(STM32_FLASH_LINE_SIZE)
+#    elif defined(QMK_MCU_FAMILY_STM32)
+#        if defined(STM32_FLASH_LINE_SIZE) // from some family's stm32_registry.h file
 #            define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE)
-#        else // defined(STM32_FLASH_LINE_SIZE)
-#            if defined(STM32F1XX)
+#        else
+#            if defined(QMK_MCU_SERIES_STM32F1XX)
 #                define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
-#            elif defined(STM32F3XX)
+#            elif defined(QMK_MCU_SERIES_STM32F3XX)
 #                define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c
-#            elif defined(STM32F4XX)
+#            elif defined(QMK_MCU_SERIES_STM32F4XX)
 #                define BACKING_STORE_WRITE_SIZE (1 << STM32_FLASH_PSIZE) // from hal_efl_lld.c
-#            elif defined(STM32L4XX)
+#            elif defined(QMK_MCU_SERIES_STM32L4XX)
 #                define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
-#            elif defined(STM32G0XX)
+#            elif defined(QMK_MCU_SERIES_STM32G0XX)
+#                define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
+#            elif defined(QMK_MCU_SERIES_STM32G4XX)
 #                define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c
 #            else
-#                error "ChibiOS hasn't defined a specific STM32_FLASH_LINE_SIZE, but should've." // normally defined in stm32_registry.h, should be set by STM32_FLASH_LINE_SIZE
-#            endif // defined(STM32??XX)
-#        endif     // defined(STM32_FLASH_LINE_SIZE)
-#    endif         // defined(MCU_?????)
+#                error "ChibiOS hasn't defined STM32_FLASH_LINE_SIZE, and could not automatically determine BACKING_STORE_WRITE_SIZE" // normally defined in stm32_registry.h, should be set by STM32_FLASH_LINE_SIZE
+#            endif
+#        endif
+#    else
+#        error "Could not automatically determine BACKING_STORE_WRITE_SIZE"
+#    endif
 #endif
 
-// 16kB space allocated
+// 4kB space allocated
 #ifndef WEAR_LEVELING_BACKING_SIZE
-#    define WEAR_LEVELING_BACKING_SIZE 16384
+#    define WEAR_LEVELING_BACKING_SIZE 4096
 #endif // WEAR_LEVELING_BACKING_SIZE
 
-// 4kB logical EEPROM
+// 2kB logical EEPROM
 #ifndef WEAR_LEVELING_LOGICAL_SIZE
-#    define WEAR_LEVELING_LOGICAL_SIZE 4096
+#    define WEAR_LEVELING_LOGICAL_SIZE 2048
 #endif // WEAR_LEVELING_LOGICAL_SIZE
diff --git a/platforms/chibios/platform.mk b/platforms/chibios/platform.mk
index a30ca62bf536..72428a762fc5 100644
--- a/platforms/chibios/platform.mk
+++ b/platforms/chibios/platform.mk
@@ -94,6 +94,11 @@ ifeq ("$(wildcard $(PLATFORM_MK))","")
     endif
 endif
 
+# If no MCU architecture specified, use the MCU instead (allows for mcu_selection.mk to swap to cortex-m0 etc.)
+ifeq ("$(MCU_ARCH)","")
+    MCU_ARCH = $(MCU)
+endif
+
 include $(STARTUP_MK)
 include $(PORT_V)
 include $(PLATFORM_MK)

From 997534bdae1d31db5b595a8e754475ac35df425d Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Sun, 26 Jun 2022 10:11:56 +1000
Subject: [PATCH 11/19] Cleanup makefile, force FNV inclusion.

---
 builddefs/common_features.mk | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index 4b1bc6470ec9..be772e04c04c 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -262,18 +262,20 @@ ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
   ifeq ($(filter $(WEAR_LEVELING_DRIVER),$(VALID_WEAR_LEVELING_DRIVER_TYPES)),)
     $(call CATASTROPHIC_ERROR,Invalid WEAR_LEVELING_DRIVER,WEAR_LEVELING_DRIVER="$(WEAR_LEVELING_DRIVER)" is not a valid wear leveling driver)
   else
+    FNV_ENABLE := yes
     OPT_DEFS += -DWEAR_LEVELING_ENABLE
+    OPT_DEFS += -DWEAR_LEVELING_$(strip $(shell echo $(WEAR_LEVELING_DRIVER) | tr '[:lower:]' '[:upper:]'))
     COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling
     COMMON_VPATH += $(DRIVER_PATH)/wear_leveling
     COMMON_VPATH += $(QUANTUM_DIR)/wear_leveling
+    SRC += wear_leveling.c
     ifeq ($(strip $(WEAR_LEVELING_DRIVER)), efl)
-      OPT_DEFS += -DWEAR_LEVELING_DRIVER_EFL -DHAL_USE_EFL
-      SRC += wear_leveling.c wear_leveling_efl.c
+      OPT_DEFS += -DHAL_USE_EFL
+      SRC += wear_leveling_efl.c
       POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_efl_config.h
     else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), stm32f4x1)
-      OPT_DEFS += -DWEAR_LEVELING_DRIVER_STM32F4X1
       COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
-      SRC += wear_leveling.c flash_stm32.c wear_leveling_stm32f4x1.c
+      SRC += flash_stm32.c wear_leveling_stm32f4x1.c
       POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_stm32f4x1_config.h
     endif
   endif

From f45effd9532771c5bccdc793d8595aeb052b0bd5 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Sun, 26 Jun 2022 18:08:29 +1000
Subject: [PATCH 12/19] Add support for external SPI flash.

---
 builddefs/common_features.mk                  |  6 +-
 .../wear_leveling/wear_leveling_flash_spi.c   | 60 +++++++++++++++++++
 .../wear_leveling_flash_spi_config.h          | 34 +++++++++++
 .../drivers/wear_leveling/wear_leveling_efl.c |  2 +-
 .../wear_leveling/wear_leveling_stm32f4x1.c   |  2 +-
 5 files changed, 101 insertions(+), 3 deletions(-)
 create mode 100644 drivers/wear_leveling/wear_leveling_flash_spi.c
 create mode 100644 drivers/wear_leveling/wear_leveling_flash_spi_config.h

diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index be772e04c04c..7ad5d417b2c4 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -256,7 +256,7 @@ ifneq ($(strip $(FLASH_DRIVER)), none)
     endif
 endif
 
-VALID_WEAR_LEVELING_DRIVER_TYPES := custom efl stm32f4x1
+VALID_WEAR_LEVELING_DRIVER_TYPES := custom efl stm32f4x1 flash_spi
 WEAR_LEVELING_DRIVER ?= none
 ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
   ifeq ($(filter $(WEAR_LEVELING_DRIVER),$(VALID_WEAR_LEVELING_DRIVER_TYPES)),)
@@ -277,6 +277,10 @@ ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
       COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
       SRC += flash_stm32.c wear_leveling_stm32f4x1.c
       POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_stm32f4x1_config.h
+    else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), flash_spi)
+      COMMON_VPATH += $(DRIVER_PATH)/flash
+      SRC += flash_spi.c wear_leveling_flash_spi.c
+      POST_CONFIG_H += $(DRIVER_PATH)/wear_leveling/wear_leveling_flash_spi_config.h
     endif
   endif
 endif
diff --git a/drivers/wear_leveling/wear_leveling_flash_spi.c b/drivers/wear_leveling/wear_leveling_flash_spi.c
new file mode 100644
index 000000000000..3038b3ae1e3a
--- /dev/null
+++ b/drivers/wear_leveling/wear_leveling_flash_spi.c
@@ -0,0 +1,60 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdbool.h>
+#include <hal.h>
+#include "timer.h"
+#include "wear_leveling.h"
+#include "wear_leveling_internal.h"
+
+bool backing_store_init(void) {
+    bs_dprintf("Init\n");
+    flash_init();
+    return true;
+}
+
+bool backing_store_unlock(void) {
+    bs_dprintf("Unlock\n");
+    // No-op -- handled by the flash driver as it is.
+    return true;
+}
+
+bool backing_store_erase(void) {
+#ifdef WEAR_LEVELING_DEBUG_OUTPUT
+    uint32_t start = timer_read32();
+#endif
+
+    bool ret = true;
+    for (int i = 0; i < (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT); ++i) {
+        flash_status_t status = flash_erase_block(((WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) + i) * (EXTERNAL_FLASH_BLOCK_SIZE));
+        if (status != FLASH_STATUS_SUCCESS) {
+            ret = false;
+            break;
+        }
+    }
+
+    bs_dprintf("Backing store erase took %ldms to complete\n", ((long)(timer_read32() - start)));
+    return ret;
+}
+
+bool backing_store_write(uint32_t address, backing_store_int_t value) {
+    bs_dprintf("Write ");
+    uint32_t offset = (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) * (EXTERNAL_FLASH_BLOCK_SIZE) + address;
+    wl_dump(offset, &value, sizeof(value));
+    value = ~value;
+    return flash_write_block(offset, &value, sizeof(value)) == FLASH_STATUS_SUCCESS;
+}
+
+bool backing_store_lock(void) {
+    bs_dprintf("Lock  \n");
+    // No-op -- handled by the flash driver as it is.
+    return true;
+}
+
+bool backing_store_read(uint32_t address, backing_store_int_t *value) {
+    bs_dprintf("Read  ");
+    uint32_t       offset = (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) * (EXTERNAL_FLASH_BLOCK_SIZE) + address;
+    flash_status_t status = flash_read_block(offset, value, sizeof(backing_store_int_t));
+    *value                = ~(*value);
+    wl_dump(offset, value, sizeof(backing_store_int_t));
+    return status == FLASH_STATUS_SUCCESS;
+}
diff --git a/drivers/wear_leveling/wear_leveling_flash_spi_config.h b/drivers/wear_leveling/wear_leveling_flash_spi_config.h
new file mode 100644
index 000000000000..394370daa3d3
--- /dev/null
+++ b/drivers/wear_leveling/wear_leveling_flash_spi_config.h
@@ -0,0 +1,34 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+#ifndef __ASSEMBLER__
+#    include <stdlib.h>
+#    include <stdint.h>
+#    include "flash_spi.h"
+#endif
+
+// Use 1 block -- check the config for the SPI flash to determine how big it is
+#ifndef WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT
+#    define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT 1
+#endif // WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT
+
+// Start at the first block of the external flash
+#ifndef WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET
+#    define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET 0
+#endif // WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET
+
+// 8-byte writes by default
+#ifndef BACKING_STORE_WRITE_SIZE
+#    define BACKING_STORE_WRITE_SIZE 8
+#endif
+
+// The space allocated by the block
+#ifndef WEAR_LEVELING_BACKING_SIZE
+#    define WEAR_LEVELING_BACKING_SIZE ((EXTERNAL_FLASH_BLOCK_SIZE) * (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT))
+#endif // WEAR_LEVELING_BACKING_SIZE
+
+// Use half of the backing size for logical EEPROM
+#ifndef WEAR_LEVELING_LOGICAL_SIZE
+#    define WEAR_LEVELING_LOGICAL_SIZE ((WEAR_LEVELING_BACKING_SIZE) / 2)
+#endif // WEAR_LEVELING_LOGICAL_SIZE
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
index d85fdc14c88a..7e85d6aeb875 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
@@ -107,6 +107,6 @@ bool backing_store_read(uint32_t address, backing_store_int_t *value) {
     backing_store_int_t *loc    = (backing_store_int_t *)offset;
     *value                      = ~(*loc);
     bs_dprintf("Read  ");
-    wl_dump(offset, loc, sizeof(value));
+    wl_dump(offset, loc, sizeof(backing_store_int_t));
     return true;
 }
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
index 1621277a219e..aec1d0d03695 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
@@ -54,6 +54,6 @@ bool backing_store_read(uint32_t address, backing_store_int_t* value) {
     backing_store_int_t* loc    = (backing_store_int_t*)offset;
     *value                      = ~(*loc);
     bs_dprintf("Read  ");
-    wl_dump(offset, loc, 2);
+    wl_dump(offset, loc, sizeof(backing_store_int_t));
     return true;
 }

From bc0ce68f6fc982f2836c436cb857497233cd12cd Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 27 Jun 2022 08:25:26 +1000
Subject: [PATCH 13/19] Renaming.

---
 builddefs/common_features.mk                           |  8 ++++----
 ...ear_leveling_stm32f4x1.c => wear_leveling_legacy.c} |  0
 ...tm32f4x1_config.h => wear_leveling_legacy_config.h} | 10 +++++-----
 3 files changed, 9 insertions(+), 9 deletions(-)
 rename platforms/chibios/drivers/wear_leveling/{wear_leveling_stm32f4x1.c => wear_leveling_legacy.c} (100%)
 rename platforms/chibios/drivers/wear_leveling/{wear_leveling_stm32f4x1_config.h => wear_leveling_legacy_config.h} (90%)

diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index 7ad5d417b2c4..c75b09f4bf3a 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -256,7 +256,7 @@ ifneq ($(strip $(FLASH_DRIVER)), none)
     endif
 endif
 
-VALID_WEAR_LEVELING_DRIVER_TYPES := custom efl stm32f4x1 flash_spi
+VALID_WEAR_LEVELING_DRIVER_TYPES := custom efl legacy_emulation flash_spi
 WEAR_LEVELING_DRIVER ?= none
 ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
   ifeq ($(filter $(WEAR_LEVELING_DRIVER),$(VALID_WEAR_LEVELING_DRIVER_TYPES)),)
@@ -273,10 +273,10 @@ ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
       OPT_DEFS += -DHAL_USE_EFL
       SRC += wear_leveling_efl.c
       POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_efl_config.h
-    else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), stm32f4x1)
+    else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), legacy_emulation)
       COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
-      SRC += flash_stm32.c wear_leveling_stm32f4x1.c
-      POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_stm32f4x1_config.h
+      SRC += flash_stm32.c wear_leveling_legacy.c
+      POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_legacy_config.h
     else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), flash_spi)
       COMMON_VPATH += $(DRIVER_PATH)/flash
       SRC += flash_spi.c wear_leveling_flash_spi.c
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy.c
similarity index 100%
rename from platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1.c
rename to platforms/chibios/drivers/wear_leveling/wear_leveling_legacy.c
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy_config.h
similarity index 90%
rename from platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1_config.h
rename to platforms/chibios/drivers/wear_leveling/wear_leveling_legacy_config.h
index a3dc9c35d081..9d8297b64380 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_stm32f4x1_config.h
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy_config.h
@@ -2,16 +2,16 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 #pragma once
 
-// bodge to force 2nd 16k page
-#ifndef EMULATED_EEPROM_BASE_PAGE_ADDRESS
-#    define EMULATED_EEPROM_BASE_PAGE_ADDRESS 0x08004000
-#endif // EMULATED_EEPROM_BASE_PAGE_ADDRESS
-
 // 16k pages
 #ifndef EMULATED_EEPROM_PAGE_SIZE
 #    define EMULATED_EEPROM_PAGE_SIZE 0x4000
 #endif // EMULATED_EEPROM_PAGE_SIZE
 
+// bodge to force 2nd 16k page
+#ifndef EMULATED_EEPROM_BASE_PAGE_ADDRESS
+#    define EMULATED_EEPROM_BASE_PAGE_ADDRESS (0x08000000 + (1 * (EMULATED_EEPROM_PAGE_SIZE))
+#endif // EMULATED_EEPROM_BASE_PAGE_ADDRESS
+
 // Only use 1 page
 #ifndef EMULATED_EEPROM_PAGE_COUNT
 #    define EMULATED_EEPROM_PAGE_COUNT 1

From 60cb806ded7b9a4de8d4d4c9587ea07e2711bcdd Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 27 Jun 2022 11:43:43 +1000
Subject: [PATCH 14/19] Add bulk operations for drivers to optimise.

---
 .../wear_leveling/wear_leveling_flash_spi.c   | 57 +++++++++--
 .../drivers/wear_leveling/wear_leveling_efl.c |  2 +-
 quantum/wear_leveling/wear_leveling.c         | 97 ++++++++-----------
 .../wear_leveling/wear_leveling_internal.h    |  2 +
 4 files changed, 95 insertions(+), 63 deletions(-)

diff --git a/drivers/wear_leveling/wear_leveling_flash_spi.c b/drivers/wear_leveling/wear_leveling_flash_spi.c
index 3038b3ae1e3a..cb1fde6b58d3 100644
--- a/drivers/wear_leveling/wear_leveling_flash_spi.c
+++ b/drivers/wear_leveling/wear_leveling_flash_spi.c
@@ -2,10 +2,15 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 #include <stdbool.h>
 #include <hal.h>
+#include "util.h"
 #include "timer.h"
 #include "wear_leveling.h"
 #include "wear_leveling_internal.h"
 
+#ifndef FLASH_SPI_BULK_ITEM_COUNT
+#    define FLASH_SPI_BULK_ITEM_COUNT 32
+#endif // FLASH_SPI_BULK_ITEM_COUNT
+
 bool backing_store_init(void) {
     bs_dprintf("Init\n");
     flash_init();
@@ -37,11 +42,7 @@ bool backing_store_erase(void) {
 }
 
 bool backing_store_write(uint32_t address, backing_store_int_t value) {
-    bs_dprintf("Write ");
-    uint32_t offset = (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) * (EXTERNAL_FLASH_BLOCK_SIZE) + address;
-    wl_dump(offset, &value, sizeof(value));
-    value = ~value;
-    return flash_write_block(offset, &value, sizeof(value)) == FLASH_STATUS_SUCCESS;
+    return backing_store_write_bulk(address, &value, 1);
 }
 
 bool backing_store_lock(void) {
@@ -51,10 +52,50 @@ bool backing_store_lock(void) {
 }
 
 bool backing_store_read(uint32_t address, backing_store_int_t *value) {
+    return backing_store_read_bulk(address, value, 1);
+}
+
+bool backing_store_read_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
     bs_dprintf("Read  ");
     uint32_t       offset = (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) * (EXTERNAL_FLASH_BLOCK_SIZE) + address;
-    flash_status_t status = flash_read_block(offset, value, sizeof(backing_store_int_t));
-    *value                = ~(*value);
-    wl_dump(offset, value, sizeof(backing_store_int_t));
+    flash_status_t status = flash_read_block(offset, values, sizeof(backing_store_int_t) * item_count);
+    if (status == FLASH_STATUS_SUCCESS) {
+        for (size_t i = 0; i < item_count; ++i) {
+            values[i] = ~values[i];
+        }
+        wl_dump(offset, values, sizeof(backing_store_int_t) * item_count);
+    }
     return status == FLASH_STATUS_SUCCESS;
 }
+
+bool backing_store_write_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
+    uint32_t            offset = (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) * (EXTERNAL_FLASH_BLOCK_SIZE) + address;
+    size_t              index  = 0;
+    backing_store_int_t temp[FLASH_SPI_BULK_ITEM_COUNT];
+    do {
+        // Copy out the block of data we want to transmit first
+        size_t this_loop = MIN(item_count, FLASH_SPI_BULK_ITEM_COUNT);
+        for (size_t i = 0; i < this_loop; ++i) {
+            temp[i] = values[index + i];
+        }
+
+        bs_dprintf("Write ");
+        wl_dump(offset, temp, sizeof(backing_store_int_t) * this_loop);
+
+        // Take the complement instead
+        for (size_t i = 0; i < this_loop; ++i) {
+            temp[i] = ~temp[i];
+        }
+
+        // Write out the block
+        if (flash_write_block(offset, temp, sizeof(backing_store_int_t) * this_loop) != FLASH_STATUS_SUCCESS) {
+            return false;
+        }
+
+        offset += this_loop * sizeof(backing_store_int_t);
+        index += this_loop;
+        item_count -= this_loop;
+    } while (item_count > 0);
+
+    return true;
+}
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
index 7e85d6aeb875..06376c12a8b7 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
@@ -107,6 +107,6 @@ bool backing_store_read(uint32_t address, backing_store_int_t *value) {
     backing_store_int_t *loc    = (backing_store_int_t *)offset;
     *value                      = ~(*loc);
     bs_dprintf("Read  ");
-    wl_dump(offset, loc, sizeof(backing_store_int_t));
+    wl_dump(offset, value, sizeof(backing_store_int_t));
     return true;
 }
diff --git a/quantum/wear_leveling/wear_leveling.c b/quantum/wear_leveling/wear_leveling.c
index 8418ae77bf51..0a519639eaed 100644
--- a/quantum/wear_leveling/wear_leveling.c
+++ b/quantum/wear_leveling/wear_leveling.c
@@ -213,36 +213,29 @@ static wear_leveling_status_t wear_leveling_read_consolidated(void) {
     wl_dprintf("Reading consolidated data\n");
 
     wear_leveling_status_t status = WEAR_LEVELING_SUCCESS;
-    for (int address = 0; address < (WEAR_LEVELING_LOGICAL_SIZE); address += (BACKING_STORE_WRITE_SIZE)) {
-        backing_store_int_t *const loc = (backing_store_int_t *)&wear_leveling.cache[address];
-        backing_store_int_t        temp;
-        bool                       ok = backing_store_read(address, &temp);
-        if (!ok) {
-            wl_dprintf("Failed to read from backing store\n");
-            status = WEAR_LEVELING_FAILED;
-            break;
-        }
-        *loc = temp;
+    if (!backing_store_read_bulk(0, (backing_store_int_t *)wear_leveling.cache, sizeof(wear_leveling.cache) / sizeof(backing_store_int_t))) {
+        wl_dprintf("Failed to read from backing store\n");
+        status = WEAR_LEVELING_FAILED;
     }
 
     // Verify the FNV1a_64 result
     if (status != WEAR_LEVELING_FAILED) {
         uint64_t          expected = fnv_64a_buf(wear_leveling.cache, (WEAR_LEVELING_LOGICAL_SIZE), FNV1A_64_INIT);
         write_log_entry_t entry;
+        wl_dprintf("Reading checksum\n");
 #if BACKING_STORE_WRITE_SIZE == 2
-        backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 0, &entry.raw16[0]);
-        backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 2, &entry.raw16[1]);
-        backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 4, &entry.raw16[2]);
-        backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 6, &entry.raw16[3]);
+        backing_store_read_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw16, 4);
 #elif BACKING_STORE_WRITE_SIZE == 4
-        backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 0, &entry.raw32[0]);
-        backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 4, &entry.raw32[1]);
+        backing_store_read_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw32, 2);
 #elif BACKING_STORE_WRITE_SIZE == 8
         backing_store_read((WEAR_LEVELING_LOGICAL_SIZE) + 0, &entry.raw64);
 #endif
         // If we have a mismatch, clear the cache but do not flag a failure,
         // which will cater for the completely clean MCU case.
-        if (entry.raw64 != expected) {
+        if (entry.raw64 == expected) {
+            wl_dprintf("Checksum matches, consolidated data is correct\n");
+        } else {
+            wl_dprintf("Checksum mismatch, clearing cache\n");
             wear_leveling_clear_cache();
         }
     }
@@ -258,64 +251,36 @@ static wear_leveling_status_t wear_leveling_read_consolidated(void) {
 /**
  * Writes the current cache to consolidated data at the beginning of the backing store.
  * Does not clear the write log.
+ * Pre-condition: this is just after an erase, so we can write directly without reading.
  */
 static wear_leveling_status_t wear_leveling_write_consolidated(void) {
     wl_dprintf("Writing consolidated data\n");
 
-    wear_leveling_status_t      status      = WEAR_LEVELING_CONSOLIDATED;
     backing_store_lock_status_t lock_status = wear_leveling_unlock();
-    for (int address = 0; address < (WEAR_LEVELING_LOGICAL_SIZE); address += (BACKING_STORE_WRITE_SIZE)) {
-        const backing_store_int_t value = *(backing_store_int_t *)&wear_leveling.cache[address];
-        backing_store_int_t       temp;
-        bool                      ok = backing_store_read(address, &temp);
-        if (!ok) {
-            wl_dprintf("Failed to read from backing store\n");
-            status = WEAR_LEVELING_FAILED;
-            break;
-        }
-        if (temp != value) {
-            ok = backing_store_write(address, value);
-            if (!ok) {
-                wl_dprintf("Failed to write to backing store\n");
-                status = WEAR_LEVELING_FAILED;
-                break;
-            }
-        }
+    wear_leveling_status_t      status      = WEAR_LEVELING_CONSOLIDATED;
+    if (!backing_store_write_bulk(0, (backing_store_int_t *)wear_leveling.cache, sizeof(wear_leveling.cache) / sizeof(backing_store_int_t))) {
+        wl_dprintf("Failed to write to backing store\n");
+        status = WEAR_LEVELING_FAILED;
     }
 
     if (status != WEAR_LEVELING_FAILED) {
         // Write out the FNV1a_64 result of the consolidated data
         write_log_entry_t entry;
         entry.raw64 = fnv_64a_buf(wear_leveling.cache, (WEAR_LEVELING_LOGICAL_SIZE), FNV1A_64_INIT);
+        wl_dprintf("Writing checksum\n");
         do {
 #if BACKING_STORE_WRITE_SIZE == 2
-            if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 0, entry.raw16[0])) {
-                status = WEAR_LEVELING_FAILED;
-                break;
-            }
-            if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 2, entry.raw16[1])) {
-                status = WEAR_LEVELING_FAILED;
-                break;
-            }
-            if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 4, entry.raw16[2])) {
-                status = WEAR_LEVELING_FAILED;
-                break;
-            }
-            if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 6, entry.raw16[3])) {
+            if (!backing_store_write_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw16, 4)) {
                 status = WEAR_LEVELING_FAILED;
                 break;
             }
 #elif BACKING_STORE_WRITE_SIZE == 4
-            if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 0, entry.raw32[0])) {
-                status = WEAR_LEVELING_FAILED;
-                break;
-            }
-            if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 4, entry.raw32[1])) {
+            if (!backing_store_write_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw32, 2)) {
                 status = WEAR_LEVELING_FAILED;
                 break;
             }
 #elif BACKING_STORE_WRITE_SIZE == 8
-            if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE) + 0, entry.raw64)) {
+            if (!backing_store_write((WEAR_LEVELING_LOGICAL_SIZE), entry.raw64)) {
                 status = WEAR_LEVELING_FAILED;
                 break;
             }
@@ -777,3 +742,27 @@ wear_leveling_status_t wear_leveling_read(const uint32_t address, void *value, s
     wl_dump(address, value, length);
     return WEAR_LEVELING_SUCCESS;
 }
+
+/**
+ * Weak implementation of bulk read, drivers can implement more optimised implementations.
+ */
+__attribute__((weak)) bool backing_store_read_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
+    for (size_t i = 0; i < item_count; ++i) {
+        if (!backing_store_read(address + (i * BACKING_STORE_WRITE_SIZE), &values[i])) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ * Weak implementation of bulk write, drivers can implement more optimised implementations.
+ */
+__attribute__((weak)) bool backing_store_write_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
+    for (size_t i = 0; i < item_count; ++i) {
+        if (!backing_store_write(address + (i * BACKING_STORE_WRITE_SIZE), values[i])) {
+            return false;
+        }
+    }
+    return true;
+}
\ No newline at end of file
diff --git a/quantum/wear_leveling/wear_leveling_internal.h b/quantum/wear_leveling/wear_leveling_internal.h
index 12cf94aeaa6a..e83f9b22eaf3 100644
--- a/quantum/wear_leveling/wear_leveling_internal.h
+++ b/quantum/wear_leveling/wear_leveling_internal.h
@@ -71,8 +71,10 @@ bool backing_store_init(void);
 bool backing_store_unlock(void);
 bool backing_store_erase(void);
 bool backing_store_write(uint32_t address, backing_store_int_t value);
+bool backing_store_write_bulk(uint32_t address, backing_store_int_t* values, size_t item_count); // weak implementation already provided, optimized implementation can be implemented by driver
 bool backing_store_lock(void);
 bool backing_store_read(uint32_t address, backing_store_int_t* value);
+bool backing_store_read_bulk(uint32_t address, backing_store_int_t* values, size_t item_count); // weak implementation already provided, optimized implementation can be implemented by driver
 
 /**
  * Helper type used to contain a write log entry.

From a9299f3ab5f7d03bc60bdf045c6b4d21e6a08bfb Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Mon, 27 Jun 2022 13:12:22 +1000
Subject: [PATCH 15/19] Makefile simplification.

---
 builddefs/common_features.mk | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index c75b09f4bf3a..c668fc936415 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -241,21 +241,6 @@ else
   endif
 endif
 
-VALID_FLASH_DRIVER_TYPES := spi
-FLASH_DRIVER ?= none
-ifneq ($(strip $(FLASH_DRIVER)), none)
-    ifeq ($(filter $(FLASH_DRIVER),$(VALID_FLASH_DRIVER_TYPES)),)
-        $(call CATASTROPHIC_ERROR,Invalid FLASH_DRIVER,FLASH_DRIVER="$(FLASH_DRIVER)" is not a valid flash driver)
-    else
-        OPT_DEFS += -DFLASH_ENABLE
-        ifeq ($(strip $(FLASH_DRIVER)),spi)
-            OPT_DEFS += -DFLASH_DRIVER -DFLASH_SPI
-            COMMON_VPATH += $(DRIVER_PATH)/flash
-            SRC += flash_spi.c
-        endif
-    endif
-endif
-
 VALID_WEAR_LEVELING_DRIVER_TYPES := custom efl legacy_emulation flash_spi
 WEAR_LEVELING_DRIVER ?= none
 ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
@@ -278,13 +263,28 @@ ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
       SRC += flash_stm32.c wear_leveling_legacy.c
       POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_legacy_config.h
     else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), flash_spi)
-      COMMON_VPATH += $(DRIVER_PATH)/flash
-      SRC += flash_spi.c wear_leveling_flash_spi.c
+      FLASH_DRIVER := spi
+      SRC += wear_leveling_flash_spi.c
       POST_CONFIG_H += $(DRIVER_PATH)/wear_leveling/wear_leveling_flash_spi_config.h
     endif
   endif
 endif
 
+VALID_FLASH_DRIVER_TYPES := spi
+FLASH_DRIVER ?= none
+ifneq ($(strip $(FLASH_DRIVER)), none)
+    ifeq ($(filter $(FLASH_DRIVER),$(VALID_FLASH_DRIVER_TYPES)),)
+        $(call CATASTROPHIC_ERROR,Invalid FLASH_DRIVER,FLASH_DRIVER="$(FLASH_DRIVER)" is not a valid flash driver)
+    else
+        OPT_DEFS += -DFLASH_ENABLE
+        ifeq ($(strip $(FLASH_DRIVER)),spi)
+            OPT_DEFS += -DFLASH_DRIVER -DFLASH_SPI
+            COMMON_VPATH += $(DRIVER_PATH)/flash
+            SRC += flash_spi.c
+        endif
+    endif
+endif
+
 RGBLIGHT_ENABLE ?= no
 VALID_RGBLIGHT_TYPES := WS2812 APA102 custom
 

From 48707a8b014f9f26b63286872c77aa59ce54d777 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Wed, 29 Jun 2022 22:43:33 +1000
Subject: [PATCH 16/19] Fixup legaxy driver, add documentation.

---
 docs/eeprom_driver.md                         | 81 +++++++++++++++++--
 .../wear_leveling/wear_leveling_flash_spi.c   | 10 +--
 .../drivers/wear_leveling/wear_leveling_efl.c | 32 +++++++-
 .../wear_leveling/wear_leveling_efl_config.h  | 10 +--
 .../wear_leveling/wear_leveling_legacy.c      | 10 +--
 .../wear_leveling_legacy_config.h             | 70 +++++++++++-----
 6 files changed, 171 insertions(+), 42 deletions(-)

diff --git a/docs/eeprom_driver.md b/docs/eeprom_driver.md
index 306ebacb3f41..9bb085dc67db 100644
--- a/docs/eeprom_driver.md
+++ b/docs/eeprom_driver.md
@@ -2,12 +2,15 @@
 
 The EEPROM driver can be swapped out depending on the needs of the keyboard, or whether extra hardware is present.
 
+Selecting the EEPROM driver is done in your keyboard's `rules.mk`:
+
 Driver                             | Description
 -----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 `EEPROM_DRIVER = vendor` (default) | Uses the on-chip driver provided by the chip manufacturer. For AVR, this is provided by avr-libc. This is supported on ARM for a subset of chips -- STM32F3xx, STM32F1xx, and STM32F072xB will be emulated by writing to flash. STM32L0xx and STM32L1xx will use the onboard dedicated true EEPROM. Other chips will generally act as "transient" below.
 `EEPROM_DRIVER = i2c`              | Supports writing to I2C-based 24xx EEPROM chips. See the driver section below.
 `EEPROM_DRIVER = spi`              | Supports writing to SPI-based 25xx EEPROM chips. See the driver section below.
 `EEPROM_DRIVER = transient`        | Fake EEPROM driver -- supports reading/writing to RAM, and will be discarded when power is lost.
+`EEPROM_DRIVER = wear_leveling`    | Frontend driver for the wear_leveling system, allowing for EEPROM emulation on top of flash -- both in-MCU and external SPI NOR flash.
 
 ## Vendor Driver Configuration :id=vendor-eeprom-driver-configuration
 
@@ -55,13 +58,13 @@ MB85RC256V FRAM  | `#define EEPROM_I2C_MB85RC256V` | <https://www.adafruit.com/p
 
 Currently QMK supports 25xx-series chips over SPI. As such, requires a working spi_master driver configuration. You can override the driver configuration via your config.h:
 
-`config.h` override                            | Description                                                                          | Default Value
------------------------------------------------|--------------------------------------------------------------------------------------|--------------
-`#define EXTERNAL_EEPROM_SPI_SLAVE_SELECT_PIN` | SPI Slave select pin in order to inform that the EEPROM is currently being addressed | _none_
-`#define EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR`    | Clock divisor used to divide the peripheral clock to derive the SPI frequency        | `64`
-`#define EXTERNAL_EEPROM_BYTE_COUNT`           | Total size of the EEPROM in bytes                                                    | 8192
-`#define EXTERNAL_EEPROM_PAGE_SIZE`            | Page size of the EEPROM in bytes, as specified in the datasheet                      | 32
-`#define EXTERNAL_EEPROM_ADDRESS_SIZE`         | The number of bytes to transmit for the memory location within the EEPROM            | 2
+`config.h` override                            | Default Value | Description
+-----------------------------------------------|---------------|-------------------------------------------------------------------------------------
+`#define EXTERNAL_EEPROM_SPI_SLAVE_SELECT_PIN` | _none_        | SPI Slave select pin in order to inform that the EEPROM is currently being addressed
+`#define EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR`    | `64`          | Clock divisor used to divide the peripheral clock to derive the SPI frequency
+`#define EXTERNAL_EEPROM_BYTE_COUNT`           | `8192`        | Total size of the EEPROM in bytes
+`#define EXTERNAL_EEPROM_PAGE_SIZE`            | `32`          | Page size of the EEPROM in bytes, as specified in the datasheet
+`#define EXTERNAL_EEPROM_ADDRESS_SIZE`         | `2`           | The number of bytes to transmit for the memory location within the EEPROM
 
 !> There's no way to determine if there is an SPI EEPROM actually responding. Generally, this will result in reads of nothing but zero.
 
@@ -74,3 +77,67 @@ The only configurable item for the transient EEPROM driver is its size:
 `#define TRANSIENT_EEPROM_SIZE` | Total size of the EEPROM storage in bytes | 64
 
 Default values and extended descriptions can be found in `drivers/eeprom/eeprom_transient.h`.
+
+## Wear-leveling Driver Configuration :id=wear_leveling-eeprom-driver-configuration
+
+The wear-leveling driver uses an algorithm to minimise the number of erase cycles on the underlying MCU flash memory.
+
+There is no specific configuration for this driver, but the wear-leveling system used by this driver may need configuration. See the [wear-leveling configuration](#wear_leveling-configuration) section for more information.
+
+# Wear-leveling Driver Configuration :id=wear_leveling-configuration
+
+The wear-leveling driver has a few possible _backing stores_ that may be used by adding to your keyboard's `rules.mk` file:
+
+Driver                                    | Description
+------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+`WEAR_LEVELING_DRIVER = efl`              | This driver is used for emulating EEPROM by writing to internal flash on the MCU.
+`WEAR_LEVELING_DRIVER = flash_spi`        | This driver is used to address external SPI NOR Flash peripherals.
+`WEAR_LEVELING_DRIVER = legacy_emulation` | This driver is the "legacy" emulated EEPROM provided in historical revisions of QMK. Used for STM32F0xx and STM32F4x1. Slated for deprecation and removal once EFL support for those MCU families is complete.
+
+## Wear-leveling Embedded Flash Driver Configuration :id=wear_leveling-efl-driver-configuration
+
+This driver performs writes to the embedded flash storage embedded in the MCU. In most circumstances, the last few of sectors of flash are used in order to minimise the likelihood of collision with program code.
+
+Configurable options in your keyboard's `config.h`:
+
+`config.h` override                      | Default     | Description
+-----------------------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+`#define WEAR_LEVELING_EFL_FIRST_SECTOR` | _unset_     | The first sector on the MCU to use. By default this is not defined and calculated at runtime based on the MCU. However, different flash sizes on MCUs may require custom configuration.
+`#define WEAR_LEVELING_EFL_FLASH_SIZE`   | _unset_     | Allows overriding the flash size available for use for wear-leveling. Under normal circumstances this is automatically calculated and should not need to be overridden. Specifying a size larger than the amount actually available in flash will usually prevent the MCU from booting.
+`#define WEAR_LEVELING_LOGICAL_SIZE`     | `1024`      | Number of bytes "exposed" to the rest of QMK and denotes the size of the usable EEPROM.
+`#define WEAR_LEVELING_BACKING_SIZE`     | `2048`      | Number of bytes used by the wear-leveling algorithm for its underlying storage, and needs to be a multiple of the logical size.
+`#define BACKING_STORE_WRITE_SIZE`       | _automatic_ | The byte width of the underlying write used on the MCU, and is usually automatically determined from the selected MCU family. If an error occurs in the auto-detection, you'll need to consult the MCU's datasheet and determine this value, specifying it directly.
+
+!> If your MCU does not boot after swapping to the EFL wear-leveling driver, it's likely that the flash size is incorrectly detected, usually as an MCU with larger flash and may require overriding.
+
+## Wear-leveling SPI NOR Flash Driver Configuration :id=wear_leveling-flash_spi-driver-configuration
+
+This driver performs writes to an external SPI NOR Flash peripheral. It also requires a working configuration for the SPI NOR Flash peripheral -- see the [flash driver](flash_driver.md) documentation for more information.
+
+Configurable options in your keyboard's `config.h`:
+
+`config.h` override                                 | Default                        | Description
+----------------------------------------------------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------
+`#define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT`  | `1`                            | Number of blocks in the external flash used by the wear-leveling algorithm.
+`#define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET` | `0`                            | The index first block in the external flash used by the wear-leveling algorithm.
+`#define WEAR_LEVELING_LOGICAL_SIZE`                | `((block_count*block_size)/2)` | Number of bytes "exposed" to the rest of QMK and denotes the size of the usable EEPROM. Result must be <= 64kB.
+`#define WEAR_LEVELING_BACKING_SIZE`                | `(block_count*block_size)`     | Number of bytes used by the wear-leveling algorithm for its underlying storage, and needs to be a multiple of the logical size.
+`#define BACKING_STORE_WRITE_SIZE`                  | `8`                            | The write width used whenever a write is performed on the external flash peripheral.
+
+!> There is currently a limit of 64kB for the EEPROM subsystem within QMK, so using a larger flash is not going to be beneficial as the logical size cannot be increased beyond 65536. The backing size may be increased to a larger value, but erase timing may suffer as a result.
+
+## Wear-leveling Legacy EEPROM Emulation Driver Configuration :id=wear_leveling-legacy-driver-configuration
+
+This driver performs writes to the embedded flash storage embedded in the MCU much like the normal Embedded Flash Driver, and is only for use with STM32F0xx and STM32F4x1 devices. This flash implementation is still currently provided as the EFL driver is currently non-functional for the previously mentioned families.
+
+By default, `1024` bytes of emulated EEPROM is provided:
+
+MCU       | EEPROM Provided | Flash Used
+----------|-----------------|--------------
+STM32F042 | `1024` bytes    | `2048` bytes
+STM32F070 | `1024` bytes    | `2048` bytes
+STM32F072 | `1024` bytes    | `2048` bytes
+STM32F401 | `1024` bytes    | `16384` bytes
+STM32F411 | `1024` bytes    | `16384` bytes
+
+Under normal circumstances configuration of this driver requires intimate knowledge of the MCU's flash structure -- reconfiguration is at your own risk and will require referring to the code.
\ No newline at end of file
diff --git a/drivers/wear_leveling/wear_leveling_flash_spi.c b/drivers/wear_leveling/wear_leveling_flash_spi.c
index cb1fde6b58d3..6191f8bf0958 100644
--- a/drivers/wear_leveling/wear_leveling_flash_spi.c
+++ b/drivers/wear_leveling/wear_leveling_flash_spi.c
@@ -7,9 +7,9 @@
 #include "wear_leveling.h"
 #include "wear_leveling_internal.h"
 
-#ifndef FLASH_SPI_BULK_ITEM_COUNT
-#    define FLASH_SPI_BULK_ITEM_COUNT 32
-#endif // FLASH_SPI_BULK_ITEM_COUNT
+#ifndef WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT
+#    define WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT 32
+#endif // WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT
 
 bool backing_store_init(void) {
     bs_dprintf("Init\n");
@@ -71,10 +71,10 @@ bool backing_store_read_bulk(uint32_t address, backing_store_int_t *values, size
 bool backing_store_write_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) {
     uint32_t            offset = (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) * (EXTERNAL_FLASH_BLOCK_SIZE) + address;
     size_t              index  = 0;
-    backing_store_int_t temp[FLASH_SPI_BULK_ITEM_COUNT];
+    backing_store_int_t temp[WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT];
     do {
         // Copy out the block of data we want to transmit first
-        size_t this_loop = MIN(item_count, FLASH_SPI_BULK_ITEM_COUNT);
+        size_t this_loop = MIN(item_count, WEAR_LEVELING_EXTERNAL_FLASH_BULK_COUNT);
         for (size_t i = 0; i < this_loop; ++i) {
             temp[i] = values[index + i];
         }
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
index 06376c12a8b7..4b5639ee4ad0 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
@@ -17,6 +17,28 @@ static flash_sector_t first_sector = UINT16_MAX;
 static flash_sector_t sector_count = UINT16_MAX;
 static BaseFlash *    flash;
 
+#ifndef WEAR_LEVELING_EFL_FIRST_SECTOR
+// "Automatic" detection of the flash size -- ideally ChibiOS would have this already, but alas, it doesn't.
+static inline uint32_t detect_flash_size(void) {
+#    if defined(WEAR_LEVELING_EFL_FLASH_SIZE)
+    return WEAR_LEVELING_EFL_FLASH_SIZE;
+#    elif defined(FLASH_BANK_SIZE)
+    return FLASH_BANK_SIZE;
+#    elif defined(FLASH_SIZE)
+    return FLASH_SIZE;
+#    elif defined(FLASHSIZE_BASE)
+#        if defined(QMK_MCU_SERIES_STM32F0XX) || defined(QMK_MCU_SERIES_STM32F1XX) || defined(QMK_MCU_SERIES_STM32F3XX) || defined(QMK_MCU_SERIES_STM32F4XX) || defined(QMK_MCU_SERIES_STM32G4XX) || defined(QMK_MCU_SERIES_STM32L0XX) || defined(QMK_MCU_SERIES_STM32L4XX) || defined(QMK_MCU_SERIES_GD32VF103)
+    return ((*(uint32_t *)FLASHSIZE_BASE) & 0xFFFFU) << 10U; // this register has the flash size in kB, so we convert it to bytes
+#        elif defined(QMK_MCU_SERIES_STM32L1XX)
+#            error This MCU family has an uncommon flash size register definition and has not been implemented. Perhaps try using the true EEPROM on the MCU instead?
+#        endif
+#    else
+#        error Unknown flash size definition.
+    return 0;
+#    endif
+}
+#endif // WEAR_LEVELING_EFL_FIRST_SECTOR
+
 bool backing_store_init(void) {
     bs_dprintf("Init\n");
     flash = (BaseFlash *)&EFLD1;
@@ -35,7 +57,7 @@ bool backing_store_init(void) {
             break;
         }
     }
-    if (sector_count == UINT16_MAX) {
+    if (sector_count == UINT16_MAX || base_offset >= flash_size) {
         // We didn't get the required number of sectors. Can't do anything here. Fault.
         chSysHalt("Invalid sector count intended to be used with wear_leveling");
     }
@@ -43,11 +65,17 @@ bool backing_store_init(void) {
 #else // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
 
     // Work out how many sectors we want to use, working backwards from the end of the flash
+    uint32_t       flash_size  = detect_flash_size();
+    flash_sector_t last_sector = desc->sectors_count;
     for (flash_sector_t i = 0; i < desc->sectors_count; ++i) {
         first_sector = desc->sectors_count - i - 1;
+        if (flashGetSectorOffset(flash, first_sector) >= flash_size) {
+            last_sector = first_sector;
+            continue;
+        }
         counter += flashGetSectorSize(flash, first_sector);
         if (counter >= (WEAR_LEVELING_BACKING_SIZE)) {
-            sector_count = i + 1;
+            sector_count = last_sector - first_sector;
             base_offset  = flashGetSectorOffset(flash, first_sector);
             break;
         }
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
index 2d2716aac2d5..9e38c2965d0e 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h
@@ -6,7 +6,7 @@
 #    include <hal.h>
 #endif
 
-// Work out how many bytes per write
+// Work out how many bytes per write to internal flash
 #ifndef BACKING_STORE_WRITE_SIZE
 // These need to match EFL's XXXXXX_FLASH_LINE_SIZE, see associated code in `lib/chibios/os/hal/ports/**/hal_efl_lld.c`,
 // or associated `stm32_registry.h` for the MCU in question (or equivalent for the family).
@@ -39,12 +39,12 @@
 #    endif
 #endif
 
-// 4kB space allocated
+// 2kB backing space allocated
 #ifndef WEAR_LEVELING_BACKING_SIZE
-#    define WEAR_LEVELING_BACKING_SIZE 4096
+#    define WEAR_LEVELING_BACKING_SIZE 2048
 #endif // WEAR_LEVELING_BACKING_SIZE
 
-// 2kB logical EEPROM
+// 1kB logical EEPROM
 #ifndef WEAR_LEVELING_LOGICAL_SIZE
-#    define WEAR_LEVELING_LOGICAL_SIZE 2048
+#    define WEAR_LEVELING_LOGICAL_SIZE 1024
 #endif // WEAR_LEVELING_LOGICAL_SIZE
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy.c
index aec1d0d03695..87126c44672b 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy.c
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy.c
@@ -25,8 +25,8 @@ bool backing_store_erase(void) {
 
     bool         ret = true;
     FLASH_Status status;
-    for (int i = 0; i < (EMULATED_EEPROM_PAGE_COUNT); ++i) {
-        status = FLASH_ErasePage(EMULATED_EEPROM_BASE_PAGE_ADDRESS + (i * (EMULATED_EEPROM_PAGE_SIZE)));
+    for (int i = 0; i < (WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT); ++i) {
+        status = FLASH_ErasePage(WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS + (i * (WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE)));
         if (status != FLASH_COMPLETE) {
             ret = false;
         }
@@ -37,9 +37,9 @@ bool backing_store_erase(void) {
 }
 
 bool backing_store_write(uint32_t address, backing_store_int_t value) {
-    uint32_t offset = ((EMULATED_EEPROM_BASE_PAGE_ADDRESS) + address);
+    uint32_t offset = ((WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS) + address);
     bs_dprintf("Write ");
-    wl_dump(offset, &value, 2);
+    wl_dump(offset, &value, sizeof(backing_store_int_t));
     return FLASH_ProgramHalfWord(offset, ~value) == FLASH_COMPLETE;
 }
 
@@ -50,7 +50,7 @@ bool backing_store_lock(void) {
 }
 
 bool backing_store_read(uint32_t address, backing_store_int_t* value) {
-    uint32_t             offset = ((EMULATED_EEPROM_BASE_PAGE_ADDRESS) + address);
+    uint32_t             offset = ((WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS) + address);
     backing_store_int_t* loc    = (backing_store_int_t*)offset;
     *value                      = ~(*loc);
     bs_dprintf("Read  ");
diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy_config.h
index 9d8297b64380..1e4691a6c0c3 100644
--- a/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy_config.h
+++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_legacy_config.h
@@ -2,32 +2,66 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 #pragma once
 
-// 16k pages
-#ifndef EMULATED_EEPROM_PAGE_SIZE
-#    define EMULATED_EEPROM_PAGE_SIZE 0x4000
-#endif // EMULATED_EEPROM_PAGE_SIZE
+// Work out the page size to use
+#ifndef WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE
+#    if defined(QMK_MCU_STM32F042)
+#        define WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE 1024
+#    elif defined(QMK_MCU_STM32F070) || defined(QMK_MCU_STM32F072)
+#        define WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE 2048
+#    elif defined(QMK_MCU_STM32F401) || defined(QMK_MCU_STM32F411)
+#        define WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE 16384
+#    endif
+#endif
 
-// bodge to force 2nd 16k page
-#ifndef EMULATED_EEPROM_BASE_PAGE_ADDRESS
-#    define EMULATED_EEPROM_BASE_PAGE_ADDRESS (0x08000000 + (1 * (EMULATED_EEPROM_PAGE_SIZE))
-#endif // EMULATED_EEPROM_BASE_PAGE_ADDRESS
+// Work out how much flash space we have
+#ifndef WEAR_LEVELING_LEGACY_EMULATION_FLASH_SIZE
+#    define WEAR_LEVELING_LEGACY_EMULATION_FLASH_SIZE ((*(uint32_t *)FLASHSIZE_BASE) & 0xFFFFU) // in kB
+#endif
 
-// Only use 1 page
-#ifndef EMULATED_EEPROM_PAGE_COUNT
-#    define EMULATED_EEPROM_PAGE_COUNT 1
-#endif // EMULATED_EEPROM_PAGE_COUNT
+// The base location of program memory
+#ifndef WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE
+#    define WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE 0x08000000
+#endif
+
+// The number of pages to use
+#ifndef WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT
+#    if defined(QMK_MCU_STM32F042)
+#        define WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT 2
+#    elif defined(QMK_MCU_STM32F070) || defined(QMK_MCU_STM32F072)
+#        define WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT 1
+#    elif defined(QMK_MCU_STM32F401) || defined(QMK_MCU_STM32F411)
+#        define WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT 1
+#    endif
+#endif
+
+// The origin of the emulated eeprom
+#ifndef WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS
+#    if defined(QMK_MCU_STM32F042) || defined(QMK_MCU_STM32F070) || defined(QMK_MCU_STM32F072)
+#        define WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS ((uintptr_t)(WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE) + WEAR_LEVELING_LEGACY_EMULATION_FLASH_SIZE * 1024 - (WEAR_LEVELING_LEGACY_EMULATION_PAGE_COUNT * WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE))
+#    elif defined(QMK_MCU_STM32F401) || defined(QMK_MCU_STM32F411)
+#        if defined(BOOTLOADER_STM32)
+#            define WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS (WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE + (1 * (WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE))) // +16k
+#        elif defined(BOOTLOADER_TINYUF2)
+#            define WEAR_LEVELING_LEGACY_EMULATION_BASE_PAGE_ADDRESS (WEAR_LEVELING_LEGACY_EMULATION_FLASH_BASE + (3 * (WEAR_LEVELING_LEGACY_EMULATION_PAGE_SIZE))) // +48k
+#        endif
+#    endif
+#endif
 
 // 2-byte writes
 #ifndef BACKING_STORE_WRITE_SIZE
 #    define BACKING_STORE_WRITE_SIZE 2
 #endif
 
-// 16kB space allocated
+// The amount of space to use for the entire set of emulation
 #ifndef WEAR_LEVELING_BACKING_SIZE
-#    define WEAR_LEVELING_BACKING_SIZE (EMULATED_EEPROM_PAGE_SIZE)
-#endif // WEAR_LEVELING_BACKING_SIZE
+#    if defined(QMK_MCU_STM32F042) || defined(QMK_MCU_STM32F070) || defined(QMK_MCU_STM32F072)
+#        define WEAR_LEVELING_BACKING_SIZE 2048
+#    elif defined(QMK_MCU_STM32F401) || defined(QMK_MCU_STM32F411)
+#        define WEAR_LEVELING_BACKING_SIZE 16384
+#    endif
+#endif
 
-// 4kB logical EEPROM
+// The logical amount of eeprom available
 #ifndef WEAR_LEVELING_LOGICAL_SIZE
-#    define WEAR_LEVELING_LOGICAL_SIZE 4096
-#endif // WEAR_LEVELING_LOGICAL_SIZE
+#    define WEAR_LEVELING_LOGICAL_SIZE 1024
+#endif

From 64f7b3854e538d935fd95f80007be41071681ed3 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Thu, 30 Jun 2022 07:26:29 +1000
Subject: [PATCH 17/19] Review comments.

---
 builddefs/common_features.mk | 15 ++++++++-------
 docs/eeprom_driver.md        | 10 ++++++----
 2 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index c668fc936415..b447c6743da5 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -241,7 +241,7 @@ else
   endif
 endif
 
-VALID_WEAR_LEVELING_DRIVER_TYPES := custom efl legacy_emulation flash_spi
+VALID_WEAR_LEVELING_DRIVER_TYPES := custom embedded_flash spi_flash legacy
 WEAR_LEVELING_DRIVER ?= none
 ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
   ifeq ($(filter $(WEAR_LEVELING_DRIVER),$(VALID_WEAR_LEVELING_DRIVER_TYPES)),)
@@ -254,18 +254,18 @@ ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none)
     COMMON_VPATH += $(DRIVER_PATH)/wear_leveling
     COMMON_VPATH += $(QUANTUM_DIR)/wear_leveling
     SRC += wear_leveling.c
-    ifeq ($(strip $(WEAR_LEVELING_DRIVER)), efl)
+    ifeq ($(strip $(WEAR_LEVELING_DRIVER)), embedded_flash)
       OPT_DEFS += -DHAL_USE_EFL
       SRC += wear_leveling_efl.c
       POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_efl_config.h
-    else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), legacy_emulation)
-      COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
-      SRC += flash_stm32.c wear_leveling_legacy.c
-      POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_legacy_config.h
-    else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), flash_spi)
+    else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), spi_flash)
       FLASH_DRIVER := spi
       SRC += wear_leveling_flash_spi.c
       POST_CONFIG_H += $(DRIVER_PATH)/wear_leveling/wear_leveling_flash_spi_config.h
+    else ifeq ($(strip $(WEAR_LEVELING_DRIVER)), legacy)
+      COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash
+      SRC += flash_stm32.c wear_leveling_legacy.c
+      POST_CONFIG_H += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/wear_leveling/wear_leveling_legacy_config.h
     endif
   endif
 endif
@@ -281,6 +281,7 @@ ifneq ($(strip $(FLASH_DRIVER)), none)
             OPT_DEFS += -DFLASH_DRIVER -DFLASH_SPI
             COMMON_VPATH += $(DRIVER_PATH)/flash
             SRC += flash_spi.c
+            QUANTUM_LIB_SRC += spi_master.c
         endif
     endif
 endif
diff --git a/docs/eeprom_driver.md b/docs/eeprom_driver.md
index 9bb085dc67db..06f6e8392fe5 100644
--- a/docs/eeprom_driver.md
+++ b/docs/eeprom_driver.md
@@ -84,16 +84,18 @@ The wear-leveling driver uses an algorithm to minimise the number of erase cycle
 
 There is no specific configuration for this driver, but the wear-leveling system used by this driver may need configuration. See the [wear-leveling configuration](#wear_leveling-configuration) section for more information.
 
-# Wear-leveling Driver Configuration :id=wear_leveling-configuration
+# Wear-leveling Configuration :id=wear_leveling-configuration
 
 The wear-leveling driver has a few possible _backing stores_ that may be used by adding to your keyboard's `rules.mk` file:
 
 Driver                                    | Description
 ------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-`WEAR_LEVELING_DRIVER = efl`              | This driver is used for emulating EEPROM by writing to internal flash on the MCU.
-`WEAR_LEVELING_DRIVER = flash_spi`        | This driver is used to address external SPI NOR Flash peripherals.
+`WEAR_LEVELING_DRIVER = embedded_flash`   | This driver is used for emulating EEPROM by writing to embedded flash on the MCU.
+`WEAR_LEVELING_DRIVER = spi_flash`        | This driver is used to address external SPI NOR Flash peripherals.
 `WEAR_LEVELING_DRIVER = legacy_emulation` | This driver is the "legacy" emulated EEPROM provided in historical revisions of QMK. Used for STM32F0xx and STM32F4x1. Slated for deprecation and removal once EFL support for those MCU families is complete.
 
+!> All wear-leveling drivers require an amount of RAM equivalent to the selected logical EEPROM size. Increasing the size to 32kB of EEPROM requires 32kB of RAM, which a significant number of MCUs simply do not have.
+
 ## Wear-leveling Embedded Flash Driver Configuration :id=wear_leveling-efl-driver-configuration
 
 This driver performs writes to the embedded flash storage embedded in the MCU. In most circumstances, the last few of sectors of flash are used in order to minimise the likelihood of collision with program code.
@@ -110,7 +112,7 @@ Configurable options in your keyboard's `config.h`:
 
 !> If your MCU does not boot after swapping to the EFL wear-leveling driver, it's likely that the flash size is incorrectly detected, usually as an MCU with larger flash and may require overriding.
 
-## Wear-leveling SPI NOR Flash Driver Configuration :id=wear_leveling-flash_spi-driver-configuration
+## Wear-leveling SPI Flash Driver Configuration :id=wear_leveling-flash_spi-driver-configuration
 
 This driver performs writes to an external SPI NOR Flash peripheral. It also requires a working configuration for the SPI NOR Flash peripheral -- see the [flash driver](flash_driver.md) documentation for more information.
 

From 596cd2aca7473209521877254df35ace8b7d4571 Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Thu, 30 Jun 2022 07:29:31 +1000
Subject: [PATCH 18/19] Review comments, part deux.

---
 docs/eeprom_driver.md | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/docs/eeprom_driver.md b/docs/eeprom_driver.md
index 06f6e8392fe5..de2eaf2e5b8d 100644
--- a/docs/eeprom_driver.md
+++ b/docs/eeprom_driver.md
@@ -88,11 +88,11 @@ There is no specific configuration for this driver, but the wear-leveling system
 
 The wear-leveling driver has a few possible _backing stores_ that may be used by adding to your keyboard's `rules.mk` file:
 
-Driver                                    | Description
-------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-`WEAR_LEVELING_DRIVER = embedded_flash`   | This driver is used for emulating EEPROM by writing to embedded flash on the MCU.
-`WEAR_LEVELING_DRIVER = spi_flash`        | This driver is used to address external SPI NOR Flash peripherals.
-`WEAR_LEVELING_DRIVER = legacy_emulation` | This driver is the "legacy" emulated EEPROM provided in historical revisions of QMK. Used for STM32F0xx and STM32F4x1. Slated for deprecation and removal once EFL support for those MCU families is complete.
+Driver                                  | Description
+----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+`WEAR_LEVELING_DRIVER = embedded_flash` | This driver is used for emulating EEPROM by writing to embedded flash on the MCU.
+`WEAR_LEVELING_DRIVER = spi_flash`      | This driver is used to address external SPI NOR Flash peripherals.
+`WEAR_LEVELING_DRIVER = legacy`         | This driver is the "legacy" emulated EEPROM provided in historical revisions of QMK. Currently used for STM32F0xx and STM32F4x1, but slated for deprecation and removal once `embedded_flash` support for those MCU families is complete.
 
 !> All wear-leveling drivers require an amount of RAM equivalent to the selected logical EEPROM size. Increasing the size to 32kB of EEPROM requires 32kB of RAM, which a significant number of MCUs simply do not have.
 

From 44f23f0e6287e8c8880546add8207231774212cb Mon Sep 17 00:00:00 2001
From: Nick Brassel <nick@tzarc.org>
Date: Thu, 30 Jun 2022 07:35:44 +1000
Subject: [PATCH 19/19] Add flash driver to sidebar.

---
 docs/_summary.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/_summary.md b/docs/_summary.md
index 78d7f30ea436..e6d636402b5f 100644
--- a/docs/_summary.md
+++ b/docs/_summary.md
@@ -146,6 +146,7 @@
       * [SPI Driver](spi_driver.md)
       * [WS2812 Driver](ws2812_driver.md)
       * [EEPROM Driver](eeprom_driver.md)
+      * [Flash Driver](flash_driver.md)
       * ['serial' Driver](serial_driver.md)
       * [UART Driver](uart_driver.md)
     * [GPIO Controls](gpio_control.md)