summaryrefslogtreecommitdiff
path: root/quantum
diff options
context:
space:
mode:
Diffstat (limited to 'quantum')
-rw-r--r--quantum/action.c2
-rw-r--r--quantum/action_layer.h2
-rw-r--r--quantum/action_tapping.c12
-rw-r--r--quantum/audio/song_list.h6
-rw-r--r--quantum/command.c4
-rw-r--r--quantum/debounce.h16
-rw-r--r--quantum/debounce/asym_eager_defer_pk.c11
-rw-r--r--quantum/debounce/none.c11
-rw-r--r--quantum/debounce/sym_defer_g.c12
-rw-r--r--quantum/debounce/sym_defer_pk.c12
-rw-r--r--quantum/debounce/sym_defer_pr.c14
-rw-r--r--quantum/debounce/sym_eager_pk.c7
-rw-r--r--quantum/debounce/sym_eager_pr.c11
-rw-r--r--quantum/debounce/tests/debounce_test_common.cpp6
-rw-r--r--quantum/dynamic_keymap.c38
-rw-r--r--quantum/encoder.c31
-rw-r--r--quantum/keyboard.c147
-rw-r--r--quantum/keyboard.h8
-rw-r--r--quantum/keycode_config.c4
-rw-r--r--quantum/keycode_config.h1
-rw-r--r--quantum/keymap.h7
-rw-r--r--quantum/keymap_extras/keymap_steno.h28
-rw-r--r--quantum/keymap_extras/keymap_turkish_f.h2
-rw-r--r--quantum/keymap_introspection.c30
-rw-r--r--quantum/keymap_introspection.h15
-rw-r--r--quantum/led.c12
-rw-r--r--quantum/led.h3
-rw-r--r--quantum/led_matrix/led_matrix.c12
-rw-r--r--quantum/led_matrix/led_matrix.h1
-rw-r--r--quantum/logging/print.c2
-rw-r--r--quantum/logging/print.h68
-rw-r--r--quantum/logging/print.mk17
-rw-r--r--quantum/matrix.c5
-rw-r--r--quantum/matrix_common.c5
-rw-r--r--quantum/mousekey.c27
-rw-r--r--quantum/mousekey.h4
-rw-r--r--quantum/painter/qp.h8
-rw-r--r--quantum/painter/rules.mk24
-rw-r--r--quantum/pointing_device/pointing_device.c (renamed from quantum/pointing_device.c)40
-rw-r--r--quantum/pointing_device/pointing_device.h (renamed from quantum/pointing_device.h)21
-rw-r--r--quantum/pointing_device/pointing_device_drivers.c368
-rw-r--r--quantum/pointing_device/pointing_device_gestures.c133
-rw-r--r--quantum/pointing_device/pointing_device_gestures.h58
-rw-r--r--quantum/pointing_device_drivers.c295
-rw-r--r--quantum/process_keycode/process_auto_shift.c2
-rw-r--r--quantum/process_keycode/process_dynamic_macro.c3
-rw-r--r--quantum/process_keycode/process_dynamic_tapping_term.c2
-rw-r--r--quantum/process_keycode/process_leader.c1
-rw-r--r--quantum/process_keycode/process_magic.c10
-rw-r--r--quantum/process_keycode/process_steno.c312
-rw-r--r--quantum/process_keycode/process_steno.h24
-rw-r--r--quantum/process_keycode/process_tap_dance.c137
-rw-r--r--quantum/process_keycode/process_tap_dance.h33
-rw-r--r--quantum/process_keycode/process_terminal.c330
-rw-r--r--quantum/process_keycode/process_terminal.h24
-rw-r--r--quantum/process_keycode/process_terminal_nop.h22
-rw-r--r--quantum/quantum.c27
-rw-r--r--quantum/quantum.h12
-rw-r--r--quantum/quantum_keycodes.h14
-rw-r--r--quantum/quantum_keycodes_legacy.h3
-rw-r--r--quantum/rgb_matrix/animations/pixel_rain_anim.h14
-rw-r--r--quantum/rgb_matrix/animations/raindrops_anim.h15
-rw-r--r--quantum/rgb_matrix/animations/solid_reactive_anim.h3
-rw-r--r--quantum/rgb_matrix/animations/solid_reactive_cross.h3
-rw-r--r--quantum/rgb_matrix/animations/solid_reactive_nexus.h3
-rw-r--r--quantum/rgb_matrix/animations/solid_reactive_simple_anim.h3
-rw-r--r--quantum/rgb_matrix/animations/solid_reactive_wide.h3
-rw-r--r--quantum/rgb_matrix/animations/typing_heatmap_anim.h88
-rw-r--r--quantum/rgb_matrix/rgb_matrix.c23
-rw-r--r--quantum/rgb_matrix/rgb_matrix.h1
-rw-r--r--quantum/send_string.h54
-rw-r--r--quantum/send_string/send_string.c (renamed from quantum/send_string.c)126
-rw-r--r--quantum/send_string/send_string.h152
-rw-r--r--quantum/send_string/send_string_keycodes.h434
-rw-r--r--quantum/send_string_keycodes.h505
-rw-r--r--quantum/split_common/split_util.c12
-rw-r--r--quantum/split_common/transactions.c18
-rw-r--r--quantum/split_common/transport.h9
-rw-r--r--quantum/via.c33
-rw-r--r--quantum/via.h6
-rw-r--r--quantum/wear_leveling/tests/backing_mocks.cpp154
-rw-r--r--quantum/wear_leveling/tests/backing_mocks.hpp210
-rw-r--r--quantum/wear_leveling/tests/rules.mk66
-rw-r--r--quantum/wear_leveling/tests/testlist.mk6
-rw-r--r--quantum/wear_leveling/tests/wear_leveling_2byte.cpp228
-rw-r--r--quantum/wear_leveling/tests/wear_leveling_2byte_optimized_writes.cpp295
-rw-r--r--quantum/wear_leveling/tests/wear_leveling_4byte.cpp193
-rw-r--r--quantum/wear_leveling/tests/wear_leveling_8byte.cpp178
-rw-r--r--quantum/wear_leveling/tests/wear_leveling_general.cpp204
-rw-r--r--quantum/wear_leveling/wear_leveling.c768
-rw-r--r--quantum/wear_leveling/wear_leveling.h54
-rw-r--r--quantum/wear_leveling/wear_leveling_internal.h151
92 files changed, 4674 insertions, 1834 deletions
diff --git a/quantum/action.c b/quantum/action.c
index 4e81a5466f..83f6e2a970 100644
--- a/quantum/action.c
+++ b/quantum/action.c
@@ -844,7 +844,7 @@ __attribute__((weak)) void register_code(uint8_t code) {
# endif
add_key(KC_CAPS_LOCK);
send_keyboard_report();
- wait_ms(100);
+ wait_ms(TAP_HOLD_CAPS_DELAY);
del_key(KC_CAPS_LOCK);
send_keyboard_report();
}
diff --git a/quantum/action_layer.h b/quantum/action_layer.h
index b87d096eed..bd1085a70f 100644
--- a/quantum/action_layer.h
+++ b/quantum/action_layer.h
@@ -41,7 +41,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#if !defined(LAYER_STATE_8BIT) && !defined(LAYER_STATE_16BIT) && !defined(LAYER_STATE_32BIT)
-# define LAYER_STATE_32BIT
+# define LAYER_STATE_16BIT
#endif
#if defined(LAYER_STATE_8BIT)
diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c
index 3c8b5678b7..df3317ac05 100644
--- a/quantum/action_tapping.c
+++ b/quantum/action_tapping.c
@@ -125,7 +125,7 @@ void action_tapping_process(keyrecord_t record) {
/* return true when key event is processed or consumed. */
bool process_tapping(keyrecord_t *keyp) {
keyevent_t event = keyp->event;
-# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(TAPPING_TERM_PER_KEY) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(TAPPING_FORCE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
+# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(TAPPING_FORCE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
uint16_t tapping_keycode = get_record_keycode(&tapping_key, false);
# endif
@@ -164,17 +164,15 @@ bool process_tapping(keyrecord_t *keyp) {
* useful for long TAPPING_TERM but may prevent fast typing.
*/
// clang-format off
-# if defined(TAPPING_TERM_PER_KEY) || (TAPPING_TERM >= 500) || defined(PERMISSIVE_HOLD) || defined(PERMISSIVE_HOLD_PER_KEY) || (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT))
+# if defined(PERMISSIVE_HOLD) || defined(PERMISSIVE_HOLD_PER_KEY) || (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT))
else if (
(
- (
- GET_TAPPING_TERM(tapping_keycode, &tapping_key) >= 500
+ IS_RELEASED(event) && waiting_buffer_typed(event)
# ifdef PERMISSIVE_HOLD_PER_KEY
- || get_permissive_hold(tapping_keycode, &tapping_key)
+ && get_permissive_hold(tapping_keycode, &tapping_key)
# elif defined(PERMISSIVE_HOLD)
- || true
+ && true
# endif
- ) && IS_RELEASED(event) && waiting_buffer_typed(event)
)
// Causes nested taps to not wait past TAPPING_TERM/RETRO_SHIFT
// unnecessarily and fixes them for Layer Taps.
diff --git a/quantum/audio/song_list.h b/quantum/audio/song_list.h
index 3e425abb47..ff22e6fe95 100644
--- a/quantum/audio/song_list.h
+++ b/quantum/audio/song_list.h
@@ -144,6 +144,12 @@
*/
#define USSR_ANTHEM B__NOTE(_G6), B__NOTE(_C7), W__NOTE(_G6), H__NOTE(_A6), B__NOTE(_B6), W__NOTE(_E6), W__NOTE(_E6), B__NOTE(_A6), W__NOTE(_G6), H__NOTE(_F6), B__NOTE(_G6), W__NOTE(_C6), W__NOTE(_C6), B__NOTE(_D6), W__NOTE(_D6), W__NOTE(_E6), B__NOTE(_D6), W__NOTE(_D6), W__NOTE(_G6), B__NOTE(_F6), W__NOTE(_G6), W__NOTE(_A6), B__NOTE(_B6),
+/* Title: Hymn Risen
+ * Author/Composer: Terrance Andrew Davis
+ * License: Public Domain
+ */
+#define TOS_HYMN_RISEN H__NOTE(_D5), H__NOTE(_E5), HD_NOTE(_F5), HD_NOTE(_F5), H__NOTE(_F5), HD_NOTE(_D5), E__NOTE(_E5), E__NOTE(_E5), H__NOTE(_C5), Q__NOTE(_D5), Q__NOTE(_D5), H__NOTE(_E5), H__NOTE(_C5), Q__NOTE(_G5), Q__NOTE(_F5), H__NOTE(_D5), H__NOTE(_E5), HD_NOTE(_F5), HD_NOTE(_F5), H__NOTE(_F5), HD_NOTE(_D5), E__NOTE(_E5), E__NOTE(_E5), H__NOTE(_C5), Q__NOTE(_D5), Q__NOTE(_D5), H__NOTE(_E5), H__NOTE(_C5), Q__NOTE(_G5), Q__NOTE(_F5), H__NOTE(_D5), H__NOTE(_C5), W__NOTE(_D5), W__NOTE(_E5), Q__NOTE(_A4), H__NOTE(_A4), Q__NOTE(_E5), Q__NOTE(_E5), Q__NOTE(_F5), Q__NOTE(_E5), Q__NOTE(_D5), Q__NOTE(_G5), Q__NOTE(_B4), Q__NOTE(_D5), Q__NOTE(_C5), M__NOTE(_F5, 80), H__NOTE(_D5), H__NOTE(_C5), W__NOTE(_D5), W__NOTE(_E5), Q__NOTE(_A4), H__NOTE(_A4), Q__NOTE(_E5), Q__NOTE(_E5), Q__NOTE(_F5), Q__NOTE(_E5), Q__NOTE(_D5), Q__NOTE(_G5), Q__NOTE(_B4), Q__NOTE(_D5), Q__NOTE(_C5), M__NOTE(_F5, 80)
+
/* Removed sounds
+ This list is here solely for compatibility, so that removed songs don't just break things
* If you think that any of these songs were wrongfully removed, let us know and provide
diff --git a/quantum/command.c b/quantum/command.c
index f90d73207c..84757b9b01 100644
--- a/quantum/command.c
+++ b/quantum/command.c
@@ -161,7 +161,7 @@ static void command_common_help(void) {
}
static void print_version(void) {
- print(/* clang-format off */
+ xprintf("%s", /* clang-format off */
"\n\t- Version -\n"
"VID: " STR(VENDOR_ID) "(" STR(MANUFACTURER) ") "
"PID: " STR(PRODUCT_ID) "(" STR(PRODUCT) ") "
@@ -282,6 +282,7 @@ static void print_eeconfig(void) {
".swap_grave_esc: %u\n"
".swap_backslash_backspace: %u\n"
".nkro: %u\n"
+ ".swap_escape_capslock: %u\n"
, kc.raw
, kc.swap_control_capslock
@@ -294,6 +295,7 @@ static void print_eeconfig(void) {
, kc.swap_grave_esc
, kc.swap_backslash_backspace
, kc.nkro
+ , kc.swap_escape_capslock
); /* clang-format on */
# ifdef BACKLIGHT_ENABLE
diff --git a/quantum/debounce.h b/quantum/debounce.h
index 3532d9cd7b..a8629654c2 100644
--- a/quantum/debounce.h
+++ b/quantum/debounce.h
@@ -1,10 +1,16 @@
#pragma once
-// raw is the current key state
-// on entry cooked is the previous debounced state
-// on exit cooked is the current debounced state
-// changed is true if raw has changed since the last call
-void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed);
+/**
+ * @brief Debounce raw matrix events according to the choosen debounce algorithm.
+ *
+ * @param raw The current key state
+ * @param cooked The debounced key state
+ * @param num_rows Number of rows to debounce
+ * @param changed True if raw has changed since the last call
+ * @return true Cooked has new keychanges after debouncing
+ * @return false Cooked is the same as before
+ */
+bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed);
void debounce_init(uint8_t num_rows);
diff --git a/quantum/debounce/asym_eager_defer_pk.c b/quantum/debounce/asym_eager_defer_pk.c
index b1eb4a2b7b..4745c6f465 100644
--- a/quantum/debounce/asym_eager_defer_pk.c
+++ b/quantum/debounce/asym_eager_defer_pk.c
@@ -55,6 +55,7 @@ static debounce_counter_t *debounce_counters;
static fast_timer_t last_time;
static bool counters_need_update;
static bool matrix_need_update;
+static bool cooked_changed;
# define DEBOUNCE_ELAPSED 0
@@ -77,8 +78,9 @@ void debounce_free(void) {
debounce_counters = NULL;
}
-void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
+bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
bool updated_last = false;
+ cooked_changed = false;
if (counters_need_update) {
fast_timer_t now = timer_read_fast();
@@ -102,6 +104,8 @@ void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool
transfer_matrix_values(raw, cooked, num_rows);
}
+
+ return cooked_changed;
}
static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time) {
@@ -123,7 +127,9 @@ static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[],
matrix_need_update = true;
} else {
// key-up: defer
- cooked[row] = (cooked[row] & ~col_mask) | (raw[row] & col_mask);
+ matrix_row_t cooked_next = (cooked[row] & ~col_mask) | (raw[row] & col_mask);
+ cooked_changed |= cooked_next ^ cooked[row];
+ cooked[row] = cooked_next;
}
} else {
debounce_pointer->time -= elapsed_time;
@@ -152,6 +158,7 @@ static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], ui
if (debounce_pointer->pressed) {
// key-down: eager
cooked[row] ^= col_mask;
+ cooked_changed = true;
}
}
} else if (debounce_pointer->time != DEBOUNCE_ELAPSED) {
diff --git a/quantum/debounce/none.c b/quantum/debounce/none.c
index 8a85cc04a8..4cff5e05e2 100644
--- a/quantum/debounce/none.c
+++ b/quantum/debounce/none.c
@@ -17,13 +17,16 @@
#include "matrix.h"
#include "quantum.h"
#include <stdlib.h>
+#include <string.h>
void debounce_init(uint8_t num_rows) {}
-void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
- for (int i = 0; i < num_rows; i++) {
- cooked[i] = raw[i];
- }
+bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
+ bool cooked_changed = memcmp(raw, cooked, sizeof(matrix_row_t) * num_rows) != 0;
+
+ memcpy(cooked, raw, sizeof(matrix_row_t) * num_rows);
+
+ return cooked_changed;
}
void debounce_free(void) {}
diff --git a/quantum/debounce/sym_defer_g.c b/quantum/debounce/sym_defer_g.c
index 47450992a4..d04310a761 100644
--- a/quantum/debounce/sym_defer_g.c
+++ b/quantum/debounce/sym_defer_g.c
@@ -20,6 +20,7 @@ When no state changes have occured for DEBOUNCE milliseconds, we push the state.
#include "matrix.h"
#include "timer.h"
#include "quantum.h"
+#include <string.h>
#ifndef DEBOUNCE
# define DEBOUNCE 5
#endif
@@ -30,18 +31,23 @@ static fast_timer_t debouncing_time;
void debounce_init(uint8_t num_rows) {}
-void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
+bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
+ bool cooked_changed = false;
+
if (changed) {
debouncing = true;
debouncing_time = timer_read_fast();
}
if (debouncing && timer_elapsed_fast(debouncing_time) >= DEBOUNCE) {
- for (int i = 0; i < num_rows; i++) {
- cooked[i] = raw[i];
+ if (memcmp(cooked, raw, sizeof(matrix_row_t) * num_rows) != 0) {
+ memcpy(cooked, raw, sizeof(matrix_row_t) * num_rows);
+ cooked_changed = true;
}
debouncing = false;
}
+
+ return cooked_changed;
}
void debounce_free(void) {}
diff --git a/quantum/debounce/sym_defer_pk.c b/quantum/debounce/sym_defer_pk.c
index 9dee29e28e..7b59b5e100 100644
--- a/quantum/debounce/sym_defer_pk.c
+++ b/quantum/debounce/sym_defer_pk.c
@@ -48,6 +48,7 @@ typedef uint8_t debounce_counter_t;
static debounce_counter_t *debounce_counters;
static fast_timer_t last_time;
static bool counters_need_update;
+static bool cooked_changed;
# define DEBOUNCE_ELAPSED 0
@@ -70,8 +71,9 @@ void debounce_free(void) {
debounce_counters = NULL;
}
-void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
+bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
bool updated_last = false;
+ cooked_changed = false;
if (counters_need_update) {
fast_timer_t now = timer_read_fast();
@@ -95,6 +97,8 @@ void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool
start_debounce_counters(raw, cooked, num_rows);
}
+
+ return cooked_changed;
}
static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time) {
@@ -104,8 +108,10 @@ static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[],
for (uint8_t col = 0; col < MATRIX_COLS; col++) {
if (*debounce_pointer != DEBOUNCE_ELAPSED) {
if (*debounce_pointer <= elapsed_time) {
- *debounce_pointer = DEBOUNCE_ELAPSED;
- cooked[row] = (cooked[row] & ~(ROW_SHIFTER << col)) | (raw[row] & (ROW_SHIFTER << col));
+ *debounce_pointer = DEBOUNCE_ELAPSED;
+ matrix_row_t cooked_next = (cooked[row] & ~(ROW_SHIFTER << col)) | (raw[row] & (ROW_SHIFTER << col));
+ cooked_changed |= cooked[row] ^ cooked_next;
+ cooked[row] = cooked_next;
} else {
*debounce_pointer -= elapsed_time;
counters_need_update = true;
diff --git a/quantum/debounce/sym_defer_pr.c b/quantum/debounce/sym_defer_pr.c
index ce24f0922f..452c4599d0 100644
--- a/quantum/debounce/sym_defer_pr.c
+++ b/quantum/debounce/sym_defer_pr.c
@@ -46,11 +46,12 @@ void debounce_free(void) {
last_raw = NULL;
}
-void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
- uint16_t now = timer_read();
- uint16_t elapsed16 = TIMER_DIFF_16(now, last_time);
- last_time = now;
- uint8_t elapsed = (elapsed16 > 255) ? 255 : elapsed16;
+bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
+ uint16_t now = timer_read();
+ uint16_t elapsed16 = TIMER_DIFF_16(now, last_time);
+ last_time = now;
+ uint8_t elapsed = (elapsed16 > 255) ? 255 : elapsed16;
+ bool cooked_changed = false;
uint8_t* countdown = countdowns;
@@ -63,10 +64,13 @@ void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool
} else if (*countdown > elapsed) {
*countdown -= elapsed;
} else if (*countdown) {
+ cooked_changed |= cooked[row] ^ raw_row;
cooked[row] = raw_row;
*countdown = 0;
}
}
+
+ return cooked_changed;
}
bool debounce_active(void) {
diff --git a/quantum/debounce/sym_eager_pk.c b/quantum/debounce/sym_eager_pk.c
index fe3e88bb06..f736d1645c 100644
--- a/quantum/debounce/sym_eager_pk.c
+++ b/quantum/debounce/sym_eager_pk.c
@@ -49,6 +49,7 @@ static debounce_counter_t *debounce_counters;
static fast_timer_t last_time;
static bool counters_need_update;
static bool matrix_need_update;
+static bool cooked_changed;
# define DEBOUNCE_ELAPSED 0
@@ -71,8 +72,9 @@ void debounce_free(void) {
debounce_counters = NULL;
}
-void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
+bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
bool updated_last = false;
+ cooked_changed = false;
if (counters_need_update) {
fast_timer_t now = timer_read_fast();
@@ -96,6 +98,8 @@ void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool
transfer_matrix_values(raw, cooked, num_rows);
}
+
+ return cooked_changed;
}
// If the current time is > debounce counter, set the counter to enable input.
@@ -132,6 +136,7 @@ static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], ui
*debounce_pointer = DEBOUNCE;
counters_need_update = true;
existing_row ^= col_mask; // flip the bit.
+ cooked_changed = true;
}
}
debounce_pointer++;
diff --git a/quantum/debounce/sym_eager_pr.c b/quantum/debounce/sym_eager_pr.c
index 29b0cabefb..aad5ca351b 100644
--- a/quantum/debounce/sym_eager_pr.c
+++ b/quantum/debounce/sym_eager_pr.c
@@ -48,6 +48,7 @@ static bool matrix_need_update;
static debounce_counter_t *debounce_counters;
static fast_timer_t last_time;
static bool counters_need_update;
+static bool cooked_changed;
# define DEBOUNCE_ELAPSED 0
@@ -67,8 +68,9 @@ void debounce_free(void) {
debounce_counters = NULL;
}
-void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
+bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
bool updated_last = false;
+ cooked_changed = false;
if (counters_need_update) {
fast_timer_t now = timer_read_fast();
@@ -92,6 +94,8 @@ void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool
transfer_matrix_values(raw, cooked, num_rows);
}
+
+ return cooked_changed;
}
// If the current time is > debounce counter, set the counter to enable input.
@@ -123,8 +127,9 @@ static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], ui
// determine new value basd on debounce pointer + raw value
if (existing_row != raw_row) {
if (*debounce_pointer == DEBOUNCE_ELAPSED) {
- *debounce_pointer = DEBOUNCE;
- cooked[row] = raw_row;
+ *debounce_pointer = DEBOUNCE;
+ cooked[row] = raw_row;
+ cooked_changed |= cooked[row] ^ raw[row];
counters_need_update = true;
}
}
diff --git a/quantum/debounce/tests/debounce_test_common.cpp b/quantum/debounce/tests/debounce_test_common.cpp
index 0d5a7bb766..bd98e32955 100644
--- a/quantum/debounce/tests/debounce_test_common.cpp
+++ b/quantum/debounce/tests/debounce_test_common.cpp
@@ -125,11 +125,15 @@ void DebounceTest::runDebounce(bool changed) {
std::copy(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_));
std::copy(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_));
- debounce(raw_matrix_, cooked_matrix_, MATRIX_ROWS, changed);
+ bool cooked_changed = debounce(raw_matrix_, cooked_matrix_, MATRIX_ROWS, changed);
if (!std::equal(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_))) {
FAIL() << "Fatal error: debounce() modified raw matrix at " << strTime() << "\ninput_matrix: changed=" << changed << "\n" << strMatrix(input_matrix_) << "\nraw_matrix:\n" << strMatrix(raw_matrix_);
}
+
+ if (std::equal(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_)) && cooked_changed) {
+ FAIL() << "Fatal error: debounce() did detect a wrong cooked matrix change at " << strTime() << "\noutput_matrix: cooked_changed=" << cooked_changed << "\n" << strMatrix(output_matrix_) << "\ncooked_matrix:\n" << strMatrix(cooked_matrix_);
+ }
}
void DebounceTest::checkCookedMatrix(bool changed, const std::string &error_message) {
diff --git a/quantum/dynamic_keymap.c b/quantum/dynamic_keymap.c
index fc1c55784d..01be9806e4 100644
--- a/quantum/dynamic_keymap.c
+++ b/quantum/dynamic_keymap.c
@@ -19,7 +19,13 @@
#include "progmem.h" // to read default from flash
#include "quantum.h" // for send_string()
#include "dynamic_keymap.h"
-#include "via.h" // for default VIA_EEPROM_ADDR_END
+
+#ifdef VIA_ENABLE
+# include "via.h" // for VIA_EEPROM_CONFIG_END
+# define DYNAMIC_KEYMAP_EEPROM_START (VIA_EEPROM_CONFIG_END)
+#else
+# define DYNAMIC_KEYMAP_EEPROM_START (EECONFIG_SIZE)
+#endif
#ifdef ENCODER_ENABLE
# include "encoder.h"
@@ -55,13 +61,8 @@
#endif
// If DYNAMIC_KEYMAP_EEPROM_ADDR not explicitly defined in config.h,
-// default it start after VIA_EEPROM_CUSTOM_ADDR+VIA_EEPROM_CUSTOM_SIZE
#ifndef DYNAMIC_KEYMAP_EEPROM_ADDR
-# ifdef VIA_EEPROM_CUSTOM_CONFIG_ADDR
-# define DYNAMIC_KEYMAP_EEPROM_ADDR (VIA_EEPROM_CUSTOM_CONFIG_ADDR + VIA_EEPROM_CUSTOM_CONFIG_SIZE)
-# else
-# error DYNAMIC_KEYMAP_EEPROM_ADDR not defined
-# endif
+# define DYNAMIC_KEYMAP_EEPROM_ADDR DYNAMIC_KEYMAP_EEPROM_START
#endif
// Dynamic encoders starts after dynamic keymaps
@@ -93,6 +94,10 @@ _Static_assert((DYNAMIC_KEYMAP_EEPROM_MAX_ADDR) - (DYNAMIC_KEYMAP_MACRO_EEPROM_A
# define DYNAMIC_KEYMAP_MACRO_EEPROM_SIZE (DYNAMIC_KEYMAP_EEPROM_MAX_ADDR - DYNAMIC_KEYMAP_MACRO_EEPROM_ADDR + 1)
#endif
+#ifndef DYNAMIC_KEYMAP_MACRO_DELAY
+# define DYNAMIC_KEYMAP_MACRO_DELAY TAP_CODE_DELAY
+#endif
+
uint8_t dynamic_keymap_get_layer_count(void) {
return DYNAMIC_KEYMAP_LAYER_COUNT;
}
@@ -144,18 +149,25 @@ void dynamic_keymap_set_encoder(uint8_t layer, uint8_t encoder_id, bool clockwis
void dynamic_keymap_reset(void) {
// Reset the keymaps in EEPROM to what is in flash.
- // All keyboards using dynamic keymaps should define a layout
- // for the same number of layers as DYNAMIC_KEYMAP_LAYER_COUNT.
for (int layer = 0; layer < DYNAMIC_KEYMAP_LAYER_COUNT; layer++) {
for (int row = 0; row < MATRIX_ROWS; row++) {
for (int column = 0; column < MATRIX_COLS; column++) {
- dynamic_keymap_set_keycode(layer, row, column, pgm_read_word(&keymaps[layer][row][column]));
+ if (layer < keymap_layer_count()) {
+ dynamic_keymap_set_keycode(layer, row, column, pgm_read_word(&keymaps[layer][row][column]));
+ } else {
+ dynamic_keymap_set_keycode(layer, row, column, KC_TRANSPARENT);
+ }
}
}
#ifdef ENCODER_MAP_ENABLE
for (int encoder = 0; encoder < NUM_ENCODERS; encoder++) {
- dynamic_keymap_set_encoder(layer, encoder, true, pgm_read_word(&encoder_map[layer][encoder][0]));
- dynamic_keymap_set_encoder(layer, encoder, false, pgm_read_word(&encoder_map[layer][encoder][1]));
+ if (layer < encodermap_layer_count()) {
+ dynamic_keymap_set_encoder(layer, encoder, true, pgm_read_word(&encoder_map[layer][encoder][0]));
+ dynamic_keymap_set_encoder(layer, encoder, false, pgm_read_word(&encoder_map[layer][encoder][1]));
+ } else {
+ dynamic_keymap_set_encoder(layer, encoder, true, KC_TRANSPARENT);
+ dynamic_keymap_set_encoder(layer, encoder, false, KC_TRANSPARENT);
+ }
}
#endif // ENCODER_MAP_ENABLE
}
@@ -300,6 +312,6 @@ void dynamic_keymap_macro_send(uint8_t id) {
break;
}
}
- send_string(data);
+ send_string_with_delay(data, DYNAMIC_KEYMAP_MACRO_DELAY);
}
}
diff --git a/quantum/encoder.c b/quantum/encoder.c
index 105bed0147..5f8a7ce080 100644
--- a/quantum/encoder.c
+++ b/quantum/encoder.c
@@ -163,27 +163,38 @@ static bool encoder_update(uint8_t index, uint8_t state) {
index += thisHand;
#endif
encoder_pulses[i] += encoder_LUT[state & 0xF];
+
+#ifdef ENCODER_DEFAULT_POS
+ if ((encoder_pulses[i] >= resolution) || (encoder_pulses[i] <= -resolution) || ((state & 0x3) == ENCODER_DEFAULT_POS)) {
+ if (encoder_pulses[i] >= 1) {
+#else
if (encoder_pulses[i] >= resolution) {
- encoder_value[index]++;
- changed = true;
+#endif
+
+ encoder_value[index]++;
+ changed = true;
#ifdef ENCODER_MAP_ENABLE
- encoder_exec_mapping(index, ENCODER_COUNTER_CLOCKWISE);
+ encoder_exec_mapping(index, ENCODER_COUNTER_CLOCKWISE);
#else // ENCODER_MAP_ENABLE
encoder_update_kb(index, ENCODER_COUNTER_CLOCKWISE);
#endif // ENCODER_MAP_ENABLE
- }
+ }
+
+#ifdef ENCODER_DEFAULT_POS
+ if (encoder_pulses[i] <= -1) {
+#else
if (encoder_pulses[i] <= -resolution) { // direction is arbitrary here, but this clockwise
- encoder_value[index]--;
- changed = true;
+#endif
+ encoder_value[index]--;
+ changed = true;
#ifdef ENCODER_MAP_ENABLE
- encoder_exec_mapping(index, ENCODER_CLOCKWISE);
+ encoder_exec_mapping(index, ENCODER_CLOCKWISE);
#else // ENCODER_MAP_ENABLE
encoder_update_kb(index, ENCODER_CLOCKWISE);
#endif // ENCODER_MAP_ENABLE
- }
- encoder_pulses[i] %= resolution;
+ }
+ encoder_pulses[i] %= resolution;
#ifdef ENCODER_DEFAULT_POS
- if ((state & 0x3) == ENCODER_DEFAULT_POS) {
encoder_pulses[i] = 0;
}
#endif
diff --git a/quantum/keyboard.c b/quantum/keyboard.c
index a65f9d6d18..1c62a43d9d 100644
--- a/quantum/keyboard.c
+++ b/quantum/keyboard.c
@@ -212,6 +212,12 @@ static inline bool has_ghost_in_row(uint8_t row, matrix_row_t rowdata) {
return false;
}
+#else
+
+static inline bool has_ghost_in_row(uint8_t row, matrix_row_t rowdata) {
+ return false;
+}
+
#endif
/** \brief matrix_setup
@@ -381,12 +387,9 @@ void keyboard_init(void) {
#ifdef ENCODER_ENABLE
encoder_init();
#endif
-#ifdef STENO_ENABLE
+#ifdef STENO_ENABLE_ALL
steno_init();
#endif
-#ifdef POINTING_DEVICE_ENABLE
- pointing_device_init();
-#endif
#if defined(NKRO_ENABLE) && defined(FORCE_NKRO)
keymap_config.nkro = 1;
eeconfig_update_keymap(keymap_config.raw);
@@ -403,6 +406,10 @@ void keyboard_init(void) {
#ifdef SPLIT_KEYBOARD
split_post_init();
#endif
+#ifdef POINTING_DEVICE_ENABLE
+ // init after split init
+ pointing_device_init();
+#endif
#if defined(DEBUG_MATRIX_SCAN_RATE) && defined(CONSOLE_ENABLE)
debug_enable = true;
@@ -425,64 +432,74 @@ void switch_events(uint8_t row, uint8_t col, bool pressed) {
#endif
}
-/** \brief Perform scan of keyboard matrix
+/**
+ * @brief Generates a tick event at a maximum rate of 1KHz that drives the
+ * internal QMK state machine.
+ */
+static inline void generate_tick_event(void) {
+ static uint16_t last_tick = 0;
+ const uint16_t now = timer_read();
+ if (TIMER_DIFF_16(now, last_tick) != 0) {
+ action_exec(TICK_EVENT);
+ last_tick = now;
+ }
+}
+
+/**
+ * @brief This task scans the keyboards matrix and processes any key presses
+ * that occur.
*
- * Any detected changes in state are sent out as part of the processing
+ * @return true Matrix did change
+ * @return false Matrix didn't change
*/
-bool matrix_scan_task(void) {
- static matrix_row_t matrix_prev[MATRIX_ROWS];
- matrix_row_t matrix_row = 0;
- matrix_row_t matrix_change = 0;
-#ifdef QMK_KEYS_PER_SCAN
- uint8_t keys_processed = 0;
-#endif
+static bool matrix_task(void) {
+ static matrix_row_t matrix_previous[MATRIX_ROWS];
- uint8_t matrix_changed = matrix_scan();
- if (matrix_changed) last_matrix_activity_trigger();
+ matrix_scan();
- for (uint8_t r = 0; r < MATRIX_ROWS; r++) {
- matrix_row = matrix_get_row(r);
- matrix_change = matrix_row ^ matrix_prev[r];
- if (matrix_change) {
-#ifdef MATRIX_HAS_GHOST
- if (has_ghost_in_row(r, matrix_row)) {
- continue;
- }
-#endif
- if (debug_matrix) matrix_print();
- matrix_row_t col_mask = 1;
- for (uint8_t c = 0; c < MATRIX_COLS; c++, col_mask <<= 1) {
- if (matrix_change & col_mask) {
- if (should_process_keypress()) {
- action_exec((keyevent_t){
- .key = (keypos_t){.row = r, .col = c}, .pressed = (matrix_row & col_mask), .time = (timer_read() | 1) /* time should not be 0 */
- });
- }
- // record a processed key
- matrix_prev[r] ^= col_mask;
-
- switch_events(r, c, (matrix_row & col_mask));
-
-#ifdef QMK_KEYS_PER_SCAN
- // only jump out if we have processed "enough" keys.
- if (++keys_processed >= QMK_KEYS_PER_SCAN)
-#endif
- // process a key per task call
- goto MATRIX_LOOP_END;
+ bool matrix_changed = false;
+ for (uint8_t row = 0; row < MATRIX_ROWS && !matrix_changed; row++) {
+ matrix_changed |= matrix_previous[row] ^ matrix_get_row(row);
+ }
+
+ matrix_scan_perf_task();
+
+ // Short-circuit the complete matrix processing if it is not necessary
+ if (!matrix_changed) {
+ generate_tick_event();
+ return matrix_changed;
+ }
+
+ if (debug_config.matrix) {
+ matrix_print();
+ }
+
+ const bool process_keypress = should_process_keypress();
+
+ for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
+ const matrix_row_t current_row = matrix_get_row(row);
+ const matrix_row_t row_changes = current_row ^ matrix_previous[row];
+
+ if (!row_changes || has_ghost_in_row(row, current_row)) {
+ continue;
+ }
+
+ matrix_row_t col_mask = 1;
+ for (uint8_t col = 0; col < MATRIX_COLS; col++, col_mask <<= 1) {
+ if (row_changes & col_mask) {
+ const bool key_pressed = current_row & col_mask;
+
+ if (process_keypress) {
+ action_exec(MAKE_KEYEVENT(row, col, key_pressed));
}
+
+ switch_events(row, col, key_pressed);
}
}
- }
- // call with pseudo tick event when no real key event.
-#ifdef QMK_KEYS_PER_SCAN
- // we can get here with some keys processed now.
- if (!keys_processed)
-#endif
- action_exec(TICK_EVENT);
-MATRIX_LOOP_END:
+ matrix_previous[row] = current_row;
+ }
- matrix_scan_perf_task();
return matrix_changed;
}
@@ -561,20 +578,12 @@ void quantum_task(void) {
#endif
}
-/** \brief Keyboard task: Do keyboard routine jobs
- *
- * Do routine keyboard jobs:
- *
- * * scan matrix
- * * handle mouse movements
- * * handle midi commands
- * * light LEDs
- *
- * This is repeatedly called as fast as possible.
- */
+/** \brief Main task that is repeatedly called as fast as possible. */
void keyboard_task(void) {
- bool matrix_changed = matrix_scan_task();
- (void)matrix_changed;
+ const bool matrix_changed = matrix_task();
+ if (matrix_changed) {
+ last_matrix_activity_trigger();
+ }
quantum_task();
@@ -596,8 +605,10 @@ void keyboard_task(void) {
#endif
#ifdef ENCODER_ENABLE
- bool encoders_changed = encoder_read();
- if (encoders_changed) last_encoder_activity_trigger();
+ const bool encoders_changed = encoder_read();
+ if (encoders_changed) {
+ last_encoder_activity_trigger();
+ }
#endif
#ifdef OLED_ENABLE
diff --git a/quantum/keyboard.h b/quantum/keyboard.h
index fe0736a515..86ce65aac1 100644
--- a/quantum/keyboard.h
+++ b/quantum/keyboard.h
@@ -71,9 +71,15 @@ static inline bool IS_RELEASED(keyevent_t event) {
/* Common keyevent object factory */
#define MAKE_KEYPOS(row_num, col_num) ((keypos_t){.row = (row_num), .col = (col_num)})
+
+/**
+ * @brief Constructs a key event for a pressed or released key.
+ */
#define MAKE_KEYEVENT(row_num, col_num, press) ((keyevent_t){.key = MAKE_KEYPOS((row_num), (col_num)), .pressed = (press), .time = (timer_read() | 1)})
-/* Tick event */
+/**
+ * @brief Constructs a internal tick event that is used to drive the internal QMK state machine.
+ */
#define TICK_EVENT MAKE_KEYEVENT(KEYLOC_TICK, KEYLOC_TICK, false)
#ifdef ENCODER_MAP_ENABLE
diff --git a/quantum/keycode_config.c b/quantum/keycode_config.c
index dd2a17e242..5b5cc5d28e 100644
--- a/quantum/keycode_config.c
+++ b/quantum/keycode_config.c
@@ -29,6 +29,8 @@ uint16_t keycode_config(uint16_t keycode) {
case KC_LOCKING_CAPS_LOCK:
if (keymap_config.swap_control_capslock || keymap_config.capslock_to_control) {
return KC_LEFT_CTRL;
+ } else if (keymap_config.swap_escape_capslock) {
+ return KC_ESCAPE;
}
return keycode;
case KC_LEFT_CTRL:
@@ -96,6 +98,8 @@ uint16_t keycode_config(uint16_t keycode) {
case KC_ESCAPE:
if (keymap_config.swap_grave_esc) {
return KC_GRAVE;
+ } else if (keymap_config.swap_escape_capslock) {
+ return KC_CAPS_LOCK;
}
return KC_ESCAPE;
case KC_BACKSLASH:
diff --git a/quantum/keycode_config.h b/quantum/keycode_config.h
index a2cb025ed2..81a8e61471 100644
--- a/quantum/keycode_config.h
+++ b/quantum/keycode_config.h
@@ -38,6 +38,7 @@ typedef union {
bool swap_lctl_lgui : 1;
bool swap_rctl_rgui : 1;
bool oneshot_enable : 1;
+ bool swap_escape_capslock : 1;
};
} keymap_config_t;
diff --git a/quantum/keymap.h b/quantum/keymap.h
index d64b271efb..edff484129 100644
--- a/quantum/keymap.h
+++ b/quantum/keymap.h
@@ -46,6 +46,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "quantum_keycodes.h"
+// Gross hack, remove me and change RESET keycode to QK_BOOT
+#if defined(MCU_RP)
+# undef RESET
+#endif
+
// translates key to keycode
uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t key);
@@ -55,3 +60,5 @@ extern const uint16_t keymaps[][MATRIX_ROWS][MATRIX_COLS];
// Ensure we have a forward declaration for the encoder map
# include "encoder.h"
#endif
+
+#include "keymap_introspection.h"
diff --git a/quantum/keymap_extras/keymap_steno.h b/quantum/keymap_extras/keymap_steno.h
index e888ccd643..07d96b7465 100644
--- a/quantum/keymap_extras/keymap_steno.h
+++ b/quantum/keymap_extras/keymap_steno.h
@@ -89,3 +89,31 @@ enum steno_combined_keycodes {
STN_COMB_MAX = STN_EU,
};
#endif
+
+#ifdef STENO_ENABLE_BOLT
+// TxBolt Codes
+# define TXB_NUL 0
+# define TXB_S_L 0b00000001
+# define TXB_T_L 0b00000010
+# define TXB_K_L 0b00000100
+# define TXB_P_L 0b00001000
+# define TXB_W_L 0b00010000
+# define TXB_H_L 0b00100000
+# define TXB_R_L 0b01000001
+# define TXB_A_L 0b01000010
+# define TXB_O_L 0b01000100
+# define TXB_STR 0b01001000
+# define TXB_E_R 0b01010000
+# define TXB_U_R 0b01100000
+# define TXB_F_R 0b10000001
+# define TXB_R_R 0b10000010
+# define TXB_P_R 0b10000100
+# define TXB_B_R 0b10001000
+# define TXB_L_R 0b10010000
+# define TXB_G_R 0b10100000
+# define TXB_T_R 0b11000001
+# define TXB_S_R 0b11000010
+# define TXB_D_R 0b11000100
+# define TXB_Z_R 0b11001000
+# define TXB_NUM 0b11010000
+#endif // STENO_ENABLE_BOLT
diff --git a/quantum/keymap_extras/keymap_turkish_f.h b/quantum/keymap_extras/keymap_turkish_f.h
index f86ef21546..aaee2aa480 100644
--- a/quantum/keymap_extras/keymap_turkish_f.h
+++ b/quantum/keymap_extras/keymap_turkish_f.h
@@ -187,7 +187,7 @@
#define TR_CURR S(ALGR(TR_4)) // ¤
#define TR_IQUE S(ALGR(TR_SLSH)) // ¿
// Row 2
-#define TR_REGD S(ALGR(TR_I) // ®
+#define TR_REGD S(ALGR(TR_I)) // ®
// Row 3
#define TR_SECT S(ALGR(TR_IDOT)) // §
#define TR_FORD S(ALGR(TR_A)) // ª
diff --git a/quantum/keymap_introspection.c b/quantum/keymap_introspection.c
new file mode 100644
index 0000000000..7a96f802ef
--- /dev/null
+++ b/quantum/keymap_introspection.c
@@ -0,0 +1,30 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Pull the actual keymap code so that we can inspect stuff from it
+#include KEYMAP_C
+
+// Allow for keymap or userspace rules.mk to specify an alternate location for the keymap array
+#ifdef INTROSPECTION_KEYMAP_C
+# include INTROSPECTION_KEYMAP_C
+#endif // INTROSPECTION_KEYMAP_C
+
+#include "keymap_introspection.h"
+
+#define NUM_KEYMAP_LAYERS ((uint8_t)(sizeof(keymaps) / ((MATRIX_ROWS) * (MATRIX_COLS) * sizeof(uint16_t))))
+
+uint8_t keymap_layer_count(void) {
+ return NUM_KEYMAP_LAYERS;
+}
+
+#if defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE)
+
+# define NUM_ENCODERMAP_LAYERS ((uint8_t)(sizeof(encoder_map) / ((NUM_ENCODERS) * (2) * sizeof(uint16_t))))
+
+uint8_t encodermap_layer_count(void) {
+ return NUM_ENCODERMAP_LAYERS;
+}
+
+_Static_assert(NUM_KEYMAP_LAYERS == NUM_ENCODERMAP_LAYERS, "Number of encoder_map layers doesn't match the number of keymap layers");
+
+#endif // defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE)
diff --git a/quantum/keymap_introspection.h b/quantum/keymap_introspection.h
new file mode 100644
index 0000000000..23f6f2016f
--- /dev/null
+++ b/quantum/keymap_introspection.h
@@ -0,0 +1,15 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+#include <stdint.h>
+
+// Get the number of layers defined in the keymap
+uint8_t keymap_layer_count(void);
+
+#if defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE)
+
+// Get the number of layers defined in the encoder map
+uint8_t encodermap_layer_count(void);
+
+#endif // defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE)
diff --git a/quantum/led.c b/quantum/led.c
index c5ddbc22c5..444d38f751 100644
--- a/quantum/led.c
+++ b/quantum/led.c
@@ -15,6 +15,7 @@
*/
#include "led.h"
#include "host.h"
+#include "timer.h"
#include "debug.h"
#include "gpio.h"
@@ -54,6 +55,14 @@ static void handle_backlight_caps_lock(led_t led_state) {
}
#endif
+static uint32_t last_led_modification_time = 0;
+uint32_t last_led_activity_time(void) {
+ return last_led_modification_time;
+}
+uint32_t last_led_activity_elapsed(void) {
+ return timer_elapsed32(last_led_modification_time);
+}
+
/** \brief Lock LED set callback - keymap/user level
*
* \deprecated Use led_update_user() instead.
@@ -174,7 +183,8 @@ void led_task(void) {
// update LED
uint8_t led_status = host_keyboard_leds();
if (last_led_status != led_status) {
- last_led_status = led_status;
+ last_led_status = led_status;
+ last_led_modification_time = timer_read32();
if (debug_keyboard) {
debug("led_task: ");
diff --git a/quantum/led.h b/quantum/led.h
index 934d25312c..b8262cbd8e 100644
--- a/quantum/led.h
+++ b/quantum/led.h
@@ -61,6 +61,9 @@ void led_set_kb(uint8_t usb_led);
bool led_update_user(led_t led_state);
bool led_update_kb(led_t led_state);
+uint32_t last_led_activity_time(void); // Timestamp of the LED activity
+uint32_t last_led_activity_elapsed(void); // Number of milliseconds since the last LED activity
+
#ifdef __cplusplus
}
#endif
diff --git a/quantum/led_matrix/led_matrix.c b/quantum/led_matrix/led_matrix.c
index 38ed79bed0..14dd0dd48a 100644
--- a/quantum/led_matrix/led_matrix.c
+++ b/quantum/led_matrix/led_matrix.c
@@ -618,10 +618,20 @@ void led_matrix_decrease_speed(void) {
led_matrix_decrease_speed_helper(true);
}
+void led_matrix_set_flags_eeprom_helper(led_flags_t flags, bool write_to_eeprom) {
+ led_matrix_eeconfig.flags = flags;
+ eeconfig_flag_led_matrix(write_to_eeprom);
+ dprintf("led matrix set speed [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.flags);
+}
+
led_flags_t led_matrix_get_flags(void) {
return led_matrix_eeconfig.flags;
}
void led_matrix_set_flags(led_flags_t flags) {
- led_matrix_eeconfig.flags = flags;
+ led_matrix_set_flags_eeprom_helper(flags, true);
+}
+
+void led_matrix_set_flags_noeeprom(led_flags_t flags) {
+ led_matrix_set_flags_eeprom_helper(flags, false);
}
diff --git a/quantum/led_matrix/led_matrix.h b/quantum/led_matrix/led_matrix.h
index d21f36e295..b2abec7eb1 100644
--- a/quantum/led_matrix/led_matrix.h
+++ b/quantum/led_matrix/led_matrix.h
@@ -158,6 +158,7 @@ void led_matrix_decrease_speed(void);
void led_matrix_decrease_speed_noeeprom(void);
led_flags_t led_matrix_get_flags(void);
void led_matrix_set_flags(led_flags_t flags);
+void led_matrix_set_flags_noeeprom(led_flags_t flags);
typedef struct {
/* Perform any initialisation required for the other driver functions to work. */
diff --git a/quantum/logging/print.c b/quantum/logging/print.c
index 50a6b826ee..17e6737ac4 100644
--- a/quantum/logging/print.c
+++ b/quantum/logging/print.c
@@ -28,6 +28,6 @@ void print_set_sendchar(sendchar_func_t send) {
func = send;
}
-void _putchar(char character) {
+void putchar_(char character) {
func(character);
}
diff --git a/quantum/logging/print.h b/quantum/logging/print.h
index aa72fc7074..4c4195de50 100644
--- a/quantum/logging/print.h
+++ b/quantum/logging/print.h
@@ -32,6 +32,22 @@
void print_set_sendchar(sendchar_func_t func);
+/**
+ * @brief This macro suppress format warnings for the function that is passed
+ * in. The main use-case is that `b` format specifier for printing binary
+ * numbers is not in the official C standard. Inclusion is planned for the
+ * upcoming C2X C standard, but until then GCC will always output a warning for
+ * a unknown format specifier.
+ */
+#define IGNORE_FORMAT_WARNING(func) \
+ do { \
+ _Pragma("GCC diagnostic push"); \
+ _Pragma("GCC diagnostic ignored \"-Wformat\""); \
+ _Pragma("GCC diagnostic ignored \"-Wformat-extra-args\""); \
+ func; \
+ _Pragma("GCC diagnostic pop"); \
+ } while (0)
+
#ifndef NO_PRINT
# if __has_include_next("_print.h")
# include_next "_print.h" /* Include the platforms print.h */
@@ -78,25 +94,25 @@ void print_set_sendchar(sendchar_func_t func);
#define print_hex16(i) xprintf("%04X", i)
#define print_hex32(i) xprintf("%08lX", i)
/* binary */
-#define print_bin4(i) xprintf("%04b", i)
-#define print_bin8(i) xprintf("%08b", i)
-#define print_bin16(i) xprintf("%016b", i)
-#define print_bin32(i) xprintf("%032lb", i)
-#define print_bin_reverse8(i) xprintf("%08b", bitrev(i))
-#define print_bin_reverse16(i) xprintf("%016b", bitrev16(i))
-#define print_bin_reverse32(i) xprintf("%032lb", bitrev32(i))
+#define print_bin4(i) IGNORE_FORMAT_WARNING(xprintf("%04b", i))
+#define print_bin8(i) IGNORE_FORMAT_WARNING(xprintf("%08b", i))
+#define print_bin16(i) IGNORE_FORMAT_WARNING(xprintf("%016b", i))
+#define print_bin32(i) IGNORE_FORMAT_WARNING(xprintf("%032lb", i))
+#define print_bin_reverse8(i) IGNORE_FORMAT_WARNING(xprintf("%08b", bitrev(i)))
+#define print_bin_reverse16(i) IGNORE_FORMAT_WARNING(xprintf("%016b", bitrev16(i)))
+#define print_bin_reverse32(i) IGNORE_FORMAT_WARNING(xprintf("%032lb", bitrev32(i)))
/* print value utility */
#define print_val_dec(v) xprintf(#v ": %u\n", v)
#define print_val_decs(v) xprintf(#v ": %d\n", v)
#define print_val_hex8(v) xprintf(#v ": %X\n", v)
#define print_val_hex16(v) xprintf(#v ": %02X\n", v)
#define print_val_hex32(v) xprintf(#v ": %04lX\n", v)
-#define print_val_bin8(v) xprintf(#v ": %08b\n", v)
-#define print_val_bin16(v) xprintf(#v ": %016b\n", v)
-#define print_val_bin32(v) xprintf(#v ": %032lb\n", v)
-#define print_val_bin_reverse8(v) xprintf(#v ": %08b\n", bitrev(v))
-#define print_val_bin_reverse16(v) xprintf(#v ": %016b\n", bitrev16(v))
-#define print_val_bin_reverse32(v) xprintf(#v ": %032lb\n", bitrev32(v))
+#define print_val_bin8(v) IGNORE_FORMAT_WARNING(xprintf(#v ": %08b\n", v))
+#define print_val_bin16(v) IGNORE_FORMAT_WARNING(xprintf(#v ": %016b\n", v))
+#define print_val_bin32(v) IGNORE_FORMAT_WARNING(xprintf(#v ": %032lb\n", v))
+#define print_val_bin_reverse8(v) IGNORE_FORMAT_WARNING(xprintf(#v ": %08b\n", bitrev(v)))
+#define print_val_bin_reverse16(v) IGNORE_FORMAT_WARNING(xprintf(#v ": %016b\n", bitrev16(v)))
+#define print_val_bin_reverse32(v) IGNORE_FORMAT_WARNING(xprintf(#v ": %032lb\n", bitrev32(v)))
// User print disables the normal print messages in the body of QMK/TMK code and
// is meant as a lightweight alternative to NOPRINT. Use it when you only want to do
@@ -114,22 +130,22 @@ void print_set_sendchar(sendchar_func_t func);
#define uprint_hex16(i) uprintf("%04X", i)
#define uprint_hex32(i) uprintf("%08lX", i)
/* binary */
-#define uprint_bin4(i) uprintf("%04b", i)
-#define uprint_bin8(i) uprintf("%08b", i)
-#define uprint_bin16(i) uprintf("%016b", i)
-#define uprint_bin32(i) uprintf("%032lb", i)
-#define uprint_bin_reverse8(i) uprintf("%08b", bitrev(i))
-#define uprint_bin_reverse16(i) uprintf("%016b", bitrev16(i))
-#define uprint_bin_reverse32(i) uprintf("%032lb", bitrev32(i))
+#define uprint_bin4(i) IGNORE_FORMAT_WARNING(uprintf("%04b", i))
+#define uprint_bin8(i) IGNORE_FORMAT_WARNING(uprintf("%08b", i))
+#define uprint_bin16(i) IGNORE_FORMAT_WARNING(uprintf("%016b", i))
+#define uprint_bin32(i) IGNORE_FORMAT_WARNING(uprintf("%032lb", i))
+#define uprint_bin_reverse8(i) IGNORE_FORMAT_WARNING(uprintf("%08b", bitrev(i)))
+#define uprint_bin_reverse16(i) IGNORE_FORMAT_WARNING(uprintf("%016b", bitrev16(i)))
+#define uprint_bin_reverse32(i) IGNORE_FORMAT_WARNING(uprintf("%032lb", bitrev32(i)))
/* print value utility */
#define uprint_val_dec(v) uprintf(#v ": %u\n", v)
#define uprint_val_decs(v) uprintf(#v ": %d\n", v)
#define uprint_val_hex8(v) uprintf(#v ": %X\n", v)
#define uprint_val_hex16(v) uprintf(#v ": %02X\n", v)
#define uprint_val_hex32(v) uprintf(#v ": %04lX\n", v)
-#define uprint_val_bin8(v) uprintf(#v ": %08b\n", v)
-#define uprint_val_bin16(v) uprintf(#v ": %016b\n", v)
-#define uprint_val_bin32(v) uprintf(#v ": %032lb\n", v)
-#define uprint_val_bin_reverse8(v) uprintf(#v ": %08b\n", bitrev(v))
-#define uprint_val_bin_reverse16(v) uprintf(#v ": %016b\n", bitrev16(v))
-#define uprint_val_bin_reverse32(v) uprintf(#v ": %032lb\n", bitrev32(v))
+#define uprint_val_bin8(v) IGNORE_FORMAT_WARNING(uprintf(#v ": %08b\n", v))
+#define uprint_val_bin16(v) IGNORE_FORMAT_WARNING(uprintf(#v ": %016b\n", v))
+#define uprint_val_bin32(v) IGNORE_FORMAT_WARNING(uprintf(#v ": %032lb\n", v))
+#define uprint_val_bin_reverse8(v) IGNORE_FORMAT_WARNING(uprintf(#v ": %08b\n", bitrev(v)))
+#define uprint_val_bin_reverse16(v) IGNORE_FORMAT_WARNING(uprintf(#v ": %016b\n", bitrev16(v)))
+#define uprint_val_bin_reverse32(v) IGNORE_FORMAT_WARNING(uprintf(#v ": %032lb\n", bitrev32(v)))
diff --git a/quantum/logging/print.mk b/quantum/logging/print.mk
index 67c004192d..658c533dad 100644
--- a/quantum/logging/print.mk
+++ b/quantum/logging/print.mk
@@ -1,9 +1,12 @@
-PRINTF_PATH = $(LIB_PATH)/printf
+PRINTF_PATH = $(LIB_PATH)/printf/src
-VPATH += $(PRINTF_PATH)
-SRC += $(PRINTF_PATH)/printf.c
+VPATH += $(PRINTF_PATH) $(PRINTF_PATH)/printf
+SRC += printf.c
QUANTUM_SRC +=$(QUANTUM_DIR)/logging/print.c
-OPT_DEFS += -DPRINTF_DISABLE_SUPPORT_FLOAT
-OPT_DEFS += -DPRINTF_DISABLE_SUPPORT_EXPONENTIAL
-OPT_DEFS += -DPRINTF_DISABLE_SUPPORT_LONG_LONG
-OPT_DEFS += -DPRINTF_DISABLE_SUPPORT_PTRDIFF_T
+
+OPT_DEFS += -DPRINTF_SUPPORT_DECIMAL_SPECIFIERS=0
+OPT_DEFS += -DPRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS=0
+OPT_DEFS += -DPRINTF_SUPPORT_LONG_LONG=0
+OPT_DEFS += -DPRINTF_SUPPORT_WRITEBACK_SPECIFIER=0
+OPT_DEFS += -DSUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS=0
+OPT_DEFS += -DPRINTF_ALIAS_STANDARD_FUNCTION_NAMES=1
diff --git a/quantum/matrix.c b/quantum/matrix.c
index db59b73754..db683104ed 100644
--- a/quantum/matrix.c
+++ b/quantum/matrix.c
@@ -337,10 +337,9 @@ uint8_t matrix_scan(void) {
if (changed) memcpy(raw_matrix, curr_matrix, sizeof(curr_matrix));
#ifdef SPLIT_KEYBOARD
- debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, changed);
- changed = (changed || matrix_post_scan());
+ changed = debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, changed) | matrix_post_scan();
#else
- debounce(raw_matrix, matrix, ROWS_PER_HAND, changed);
+ changed = debounce(raw_matrix, matrix, ROWS_PER_HAND, changed);
matrix_scan_quantum();
#endif
return (uint8_t)changed;
diff --git a/quantum/matrix_common.c b/quantum/matrix_common.c
index 2cf8484347..68f0e38297 100644
--- a/quantum/matrix_common.c
+++ b/quantum/matrix_common.c
@@ -169,10 +169,9 @@ __attribute__((weak)) uint8_t matrix_scan(void) {
bool changed = matrix_scan_custom(raw_matrix);
#ifdef SPLIT_KEYBOARD
- debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, changed);
- changed = (changed || matrix_post_scan());
+ changed = debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, changed) | matrix_post_scan();
#else
- debounce(raw_matrix, matrix, ROWS_PER_HAND, changed);
+ changed = debounce(raw_matrix, matrix, ROWS_PER_HAND, changed);
matrix_scan_quantum();
#endif
diff --git a/quantum/mousekey.c b/quantum/mousekey.c
index c5e3f8bcda..25a89bdba7 100644
--- a/quantum/mousekey.c
+++ b/quantum/mousekey.c
@@ -66,11 +66,18 @@ uint8_t mk_time_to_max = MOUSEKEY_TIME_TO_MAX;
/* milliseconds between the initial key press and first repeated motion event (0-2550) */
uint8_t mk_wheel_delay = MOUSEKEY_WHEEL_DELAY / 10;
/* milliseconds between repeated motion events (0-255) */
-uint8_t mk_wheel_interval = MOUSEKEY_WHEEL_INTERVAL;
+# ifdef MK_KINETIC_SPEED
+float mk_wheel_interval = 1000.0f / MOUSEKEY_WHEEL_INITIAL_MOVEMENTS;
+# else
+uint8_t mk_wheel_interval = MOUSEKEY_WHEEL_INTERVAL;
+# endif
uint8_t mk_wheel_max_speed = MOUSEKEY_WHEEL_MAX_SPEED;
uint8_t mk_wheel_time_to_max = MOUSEKEY_WHEEL_TIME_TO_MAX;
# ifndef MK_COMBINED
+# ifndef MK_KINETIC_SPEED
+
+/* Default accelerated mode */
static uint8_t move_unit(void) {
uint16_t unit;
@@ -108,8 +115,7 @@ static uint8_t wheel_unit(void) {
return (unit > MOUSEKEY_WHEEL_MAX ? MOUSEKEY_WHEEL_MAX : (unit == 0 ? 1 : unit));
}
-# else /* #ifndef MK_COMBINED */
-# ifdef MK_KINETIC_SPEED
+# else /* #ifndef MK_KINETIC_SPEED */
/*
* Kinetic movement acceleration algorithm
@@ -147,27 +153,27 @@ static uint8_t move_unit(void) {
return speed > MOUSEKEY_MOVE_MAX ? MOUSEKEY_MOVE_MAX : speed;
}
-float mk_wheel_interval = 1000.0f / MOUSEKEY_WHEEL_INITIAL_MOVEMENTS;
-
static uint8_t wheel_unit(void) {
float speed = MOUSEKEY_WHEEL_INITIAL_MOVEMENTS;
if (mousekey_accel & ((1 << 0) | (1 << 2))) {
speed = mousekey_accel & (1 << 2) ? MOUSEKEY_WHEEL_ACCELERATED_MOVEMENTS : MOUSEKEY_WHEEL_DECELERATED_MOVEMENTS;
- } else if (mousekey_repeat && mouse_timer) {
+ } else if (mousekey_wheel_repeat && mouse_timer) {
if (mk_wheel_interval != MOUSEKEY_WHEEL_BASE_MOVEMENTS) {
const float time_elapsed = timer_elapsed(mouse_timer) / 50;
speed = MOUSEKEY_WHEEL_INITIAL_MOVEMENTS + 1 * time_elapsed + 1 * 0.5 * time_elapsed * time_elapsed;
}
speed = speed > MOUSEKEY_WHEEL_BASE_MOVEMENTS ? MOUSEKEY_WHEEL_BASE_MOVEMENTS : speed;
}
-
mk_wheel_interval = 1000.0f / speed;
- return 1;
+ return (uint8_t)speed > MOUSEKEY_WHEEL_INITIAL_MOVEMENTS ? 2 : 1;
}
-# else /* #ifndef MK_KINETIC_SPEED */
+# endif /* #ifndef MK_KINETIC_SPEED */
+# else /* #ifndef MK_COMBINED */
+
+/* Combined mode */
static uint8_t move_unit(void) {
uint16_t unit;
@@ -205,8 +211,7 @@ static uint8_t wheel_unit(void) {
return (unit > MOUSEKEY_WHEEL_MAX ? MOUSEKEY_WHEEL_MAX : (unit == 0 ? 1 : unit));
}
-# endif /* #ifndef MK_KINETIC_SPEED */
-# endif /* #ifndef MK_COMBINED */
+# endif /* #ifndef MK_COMBINED */
void mousekey_task(void) {
// report cursor and scroll movement independently
diff --git a/quantum/mousekey.h b/quantum/mousekey.h
index 1714e52ff6..da2edb481a 100644
--- a/quantum/mousekey.h
+++ b/quantum/mousekey.h
@@ -39,7 +39,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# ifndef MK_KINETIC_SPEED
# define MOUSEKEY_MOVE_DELTA 8
# else
-# define MOUSEKEY_MOVE_DELTA 5
+# define MOUSEKEY_MOVE_DELTA 16
# endif
# endif
# ifndef MOUSEKEY_WHEEL_DELTA
@@ -82,7 +82,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# define MOUSEKEY_INITIAL_SPEED 100
# endif
# ifndef MOUSEKEY_BASE_SPEED
-# define MOUSEKEY_BASE_SPEED 1000
+# define MOUSEKEY_BASE_SPEED 5000
# endif
# ifndef MOUSEKEY_DECELERATED_SPEED
# define MOUSEKEY_DECELERATED_SPEED 400
diff --git a/quantum/painter/qp.h b/quantum/painter/qp.h
index e1c14d156c..fb6904de22 100644
--- a/quantum/painter/qp.h
+++ b/quantum/painter/qp.h
@@ -440,10 +440,18 @@ int16_t qp_drawtext_recolor(painter_device_t device, uint16_t x, uint16_t y, pai
# include "qp_ili9341.h"
#endif // QUANTUM_PAINTER_ILI9341_ENABLE
+#ifdef QUANTUM_PAINTER_ILI9488_ENABLE
+# include "qp_ili9488.h"
+#endif // QUANTUM_PAINTER_ILI9488_ENABLE
+
#ifdef QUANTUM_PAINTER_ST7789_ENABLE
# include "qp_st7789.h"
#endif // QUANTUM_PAINTER_ST7789_ENABLE
+#ifdef QUANTUM_PAINTER_ST7735_ENABLE
+# include "qp_st7735.h"
+#endif // QUANTUM_PAINTER_ST7735_ENABLE
+
#ifdef QUANTUM_PAINTER_GC9A01_ENABLE
# include "qp_gc9a01.h"
#endif // QUANTUM_PAINTER_GC9A01_ENABLE
diff --git a/quantum/painter/rules.mk b/quantum/painter/rules.mk
index 9115d3d406..91787dfe0e 100644
--- a/quantum/painter/rules.mk
+++ b/quantum/painter/rules.mk
@@ -3,7 +3,7 @@ QUANTUM_PAINTER_DRIVERS ?=
QUANTUM_PAINTER_ANIMATIONS_ENABLE ?= yes
# The list of permissible drivers that can be listed in QUANTUM_PAINTER_DRIVERS
-VALID_QUANTUM_PAINTER_DRIVERS := ili9163_spi ili9341_spi st7789_spi gc9a01_spi ssd1351_spi
+VALID_QUANTUM_PAINTER_DRIVERS := ili9163_spi ili9341_spi ili9488_spi st7789_spi st7735_spi gc9a01_spi ssd1351_spi
#-------------------------------------------------------------------------------
@@ -61,6 +61,17 @@ define handle_quantum_painter_driver
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
$(DRIVER_PATH)/painter/ili9xxx/qp_ili9341.c \
+ else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),ili9488_spi)
+ QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
+ QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
+ OPT_DEFS += -DQUANTUM_PAINTER_ILI9488_ENABLE -DQUANTUM_PAINTER_ILI9488_SPI_ENABLE
+ COMMON_VPATH += \
+ $(DRIVER_PATH)/painter/tft_panel \
+ $(DRIVER_PATH)/painter/ili9xxx
+ SRC += \
+ $(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
+ $(DRIVER_PATH)/painter/ili9xxx/qp_ili9488.c \
+
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),st7789_spi)
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
@@ -72,6 +83,17 @@ define handle_quantum_painter_driver
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
$(DRIVER_PATH)/painter/st77xx/qp_st7789.c
+ else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),st7735_spi)
+ QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
+ QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
+ OPT_DEFS += -DQUANTUM_PAINTER_ST7735_ENABLE -DQUANTUM_PAINTER_ST7735_SPI_ENABLE
+ COMMON_VPATH += \
+ $(DRIVER_PATH)/painter/tft_panel \
+ $(DRIVER_PATH)/painter/st77xx
+ SRC += \
+ $(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
+ $(DRIVER_PATH)/painter/st77xx/qp_st7735.c
+
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),gc9a01_spi)
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
diff --git a/quantum/pointing_device.c b/quantum/pointing_device/pointing_device.c
index a160647890..09f9e5efb7 100644
--- a/quantum/pointing_device.c
+++ b/quantum/pointing_device/pointing_device.c
@@ -25,6 +25,13 @@
#if (defined(POINTING_DEVICE_ROTATION_90) + defined(POINTING_DEVICE_ROTATION_180) + defined(POINTING_DEVICE_ROTATION_270)) > 1
# error More than one rotation selected. This is not supported.
#endif
+
+#if defined(POINTING_DEVICE_LEFT) || defined(POINTING_DEVICE_RIGHT) || defined(POINTING_DEVICE_COMBINED)
+# ifndef SPLIT_POINTING_ENABLE
+# error "Using POINTING_DEVICE_LEFT or POINTING_DEVICE_RIGHT or POINTING_DEVICE_COMBINED, then SPLIT_POINTING_ENABLE is required but has not been defined"
+# endif
+#endif
+
#if defined(SPLIT_POINTING_ENABLE)
# include "transactions.h"
# include "keyboard.h"
@@ -177,7 +184,8 @@ __attribute__((weak)) void pointing_device_send(void) {
report_mouse_t pointing_device_adjust_by_defines(report_mouse_t mouse_report) {
// Support rotation of the sensor data
#if defined(POINTING_DEVICE_ROTATION_90) || defined(POINTING_DEVICE_ROTATION_180) || defined(POINTING_DEVICE_ROTATION_270)
- int8_t x = mouse_report.x, y = mouse_report.y;
+ mouse_xy_report_t x = mouse_report.x;
+ mouse_xy_report_t y = mouse_report.y;
# if defined(POINTING_DEVICE_ROTATION_90)
mouse_report.x = y;
mouse_report.y = -x;
@@ -347,7 +355,7 @@ void pointing_device_set_cpi_on_side(bool left, uint16_t cpi) {
* @param[in] int16_t value
* @return int8_t clamped value
*/
-static inline int8_t pointing_device_movement_clamp(int16_t value) {
+static inline int8_t pointing_device_hv_clamp(int16_t value) {
if (value < INT8_MIN) {
return INT8_MIN;
} else if (value > INT8_MAX) {
@@ -358,6 +366,21 @@ static inline int8_t pointing_device_movement_clamp(int16_t value) {
}
/**
+ * @brief clamps int16_t to int8_t
+ *
+ * @param[in] clamp_range_t value
+ * @return mouse_xy_report_t clamped value
+ */
+static inline mouse_xy_report_t pointing_device_xy_clamp(clamp_range_t value) {
+ if (value < XY_REPORT_MIN) {
+ return XY_REPORT_MIN;
+ } else if (value > XY_REPORT_MAX) {
+ return XY_REPORT_MAX;
+ } else {
+ return value;
+ }
+}
+/**
* @brief combines 2 mouse reports and returns 2
*
* Combines 2 report_mouse_t structs, clamping movement values to int8_t and ignores report_id then returns the resulting report_mouse_t struct.
@@ -369,10 +392,10 @@ static inline int8_t pointing_device_movement_clamp(int16_t value) {
* @return combined report_mouse_t of left_report and right_report
*/
report_mouse_t pointing_device_combine_reports(report_mouse_t left_report, report_mouse_t right_report) {
- left_report.x = pointing_device_movement_clamp((int16_t)left_report.x + right_report.x);
- left_report.y = pointing_device_movement_clamp((int16_t)left_report.y + right_report.y);
- left_report.h = pointing_device_movement_clamp((int16_t)left_report.h + right_report.h);
- left_report.v = pointing_device_movement_clamp((int16_t)left_report.v + right_report.v);
+ left_report.x = pointing_device_xy_clamp((clamp_range_t)left_report.x + right_report.x);
+ left_report.y = pointing_device_xy_clamp((clamp_range_t)left_report.y + right_report.y);
+ left_report.h = pointing_device_hv_clamp((int16_t)left_report.h + right_report.h);
+ left_report.v = pointing_device_hv_clamp((int16_t)left_report.v + right_report.v);
left_report.buttons |= right_report.buttons;
return left_report;
}
@@ -389,8 +412,9 @@ report_mouse_t pointing_device_combine_reports(report_mouse_t left_report, repor
*/
report_mouse_t pointing_device_adjust_by_defines_right(report_mouse_t mouse_report) {
// Support rotation of the sensor data
-# if defined(POINTING_DEVICE_ROTATION_90_RIGHT) || defined(POINTING_DEVICE_ROTATION_RIGHT) || defined(POINTING_DEVICE_ROTATION_RIGHT)
- int8_t x = mouse_report.x, y = mouse_report.y;
+# if defined(POINTING_DEVICE_ROTATION_90_RIGHT) || defined(POINTING_DEVICE_ROTATION_180_RIGHT) || defined(POINTING_DEVICE_ROTATION_270_RIGHT)
+ mouse_xy_report_t x = mouse_report.x;
+ mouse_xy_report_t y = mouse_report.y;
# if defined(POINTING_DEVICE_ROTATION_90_RIGHT)
mouse_report.x = y;
mouse_report.y = -x;
diff --git a/quantum/pointing_device.h b/quantum/pointing_device/pointing_device.h
index 5c0eaeaf34..77db5471ea 100644
--- a/quantum/pointing_device.h
+++ b/quantum/pointing_device/pointing_device.h
@@ -31,6 +31,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# include "drivers/sensors/analog_joystick.h"
#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) || defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
# include "drivers/sensors/cirque_pinnacle.h"
+# include "drivers/sensors/cirque_pinnacle_gestures.h"
+# include "pointing_device_gestures.h"
+#elif defined(POINTING_DEVICE_DRIVER_paw3204)
+# include "drivers/sensors/paw3204.h"
#elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)
# include "i2c_master.h"
# include "drivers/sensors/pimoroni_trackball.h"
@@ -44,12 +48,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# ifdef PIMORONI_TRACKBALL_ROTATE
# define POINTING_DEVICE_ROTATION_90
# endif
-#elif defined(POINTING_DEVICE_DRIVER_pmw3360)
+#elif defined(POINTING_DEVICE_DRIVER_pmw3360) || defined(POINTING_DEVICE_DRIVER_pmw3389)
# include "spi_master.h"
-# include "drivers/sensors/pmw3360.h"
-#elif defined(POINTING_DEVICE_DRIVER_pmw3389)
-# include "spi_master.h"
-# include "drivers/sensors/pmw3389.h"
+# include "drivers/sensors/pmw33xx_common.h"
#else
void pointing_device_driver_init(void);
report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report);
@@ -75,6 +76,16 @@ typedef enum {
POINTING_DEVICE_BUTTON8,
} pointing_device_buttons_t;
+#ifdef MOUSE_EXTENDED_REPORT
+# define XY_REPORT_MIN INT16_MIN
+# define XY_REPORT_MAX INT16_MAX
+typedef int32_t clamp_range_t;
+#else
+# define XY_REPORT_MIN INT8_MIN
+# define XY_REPORT_MAX INT8_MAX
+typedef int16_t clamp_range_t;
+#endif
+
void pointing_device_init(void);
void pointing_device_task(void);
void pointing_device_send(void);
diff --git a/quantum/pointing_device/pointing_device_drivers.c b/quantum/pointing_device/pointing_device_drivers.c
new file mode 100644
index 0000000000..b96f8ff4b3
--- /dev/null
+++ b/quantum/pointing_device/pointing_device_drivers.c
@@ -0,0 +1,368 @@
+/* Copyright 2017 Joshua Broekhuijsen <snipeye+qmk@gmail.com>
+ * Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
+ * Copyright 2021 Dasky (@daskygit)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pointing_device.h"
+#include "debug.h"
+#include "wait.h"
+#include "timer.h"
+#include <stddef.h>
+
+#define CONSTRAIN_HID(amt) ((amt) < INT8_MIN ? INT8_MIN : ((amt) > INT8_MAX ? INT8_MAX : (amt)))
+#define CONSTRAIN_HID_XY(amt) ((amt) < XY_REPORT_MIN ? XY_REPORT_MIN : ((amt) > XY_REPORT_MAX ? XY_REPORT_MAX : (amt)))
+
+// get_report functions should probably be moved to their respective drivers.
+
+#if defined(POINTING_DEVICE_DRIVER_adns5050)
+report_mouse_t adns5050_get_report(report_mouse_t mouse_report) {
+ report_adns5050_t data = adns5050_read_burst();
+
+ if (data.dx != 0 || data.dy != 0) {
+# ifdef CONSOLE_ENABLE
+ if (debug_mouse) dprintf("Raw ] X: %d, Y: %d\n", data.dx, data.dy);
+# endif
+
+ mouse_report.x = (mouse_xy_report_t)data.dx;
+ mouse_report.y = (mouse_xy_report_t)data.dy;
+ }
+
+ return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+ .init = adns5050_init,
+ .get_report = adns5050_get_report,
+ .set_cpi = adns5050_set_cpi,
+ .get_cpi = adns5050_get_cpi,
+};
+// clang-format on
+
+#elif defined(POINTING_DEVICE_DRIVER_adns9800)
+
+report_mouse_t adns9800_get_report_driver(report_mouse_t mouse_report) {
+ report_adns9800_t sensor_report = adns9800_get_report();
+
+ mouse_report.x = CONSTRAIN_HID_XY(sensor_report.x);
+ mouse_report.y = CONSTRAIN_HID_XY(sensor_report.y);
+
+ return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+ .init = adns9800_init,
+ .get_report = adns9800_get_report_driver,
+ .set_cpi = adns9800_set_cpi,
+ .get_cpi = adns9800_get_cpi
+};
+// clang-format on
+
+#elif defined(POINTING_DEVICE_DRIVER_analog_joystick)
+report_mouse_t analog_joystick_get_report(report_mouse_t mouse_report) {
+ report_analog_joystick_t data = analog_joystick_read();
+
+# ifdef CONSOLE_ENABLE
+ if (debug_mouse) dprintf("Raw ] X: %d, Y: %d\n", data.x, data.y);
+# endif
+
+ mouse_report.x = data.x;
+ mouse_report.y = data.y;
+
+ mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, data.button, POINTING_DEVICE_BUTTON1);
+
+ return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+ .init = analog_joystick_init,
+ .get_report = analog_joystick_get_report,
+ .set_cpi = NULL,
+ .get_cpi = NULL
+};
+// clang-format on
+
+#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) || defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
+# ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+static bool cursor_glide_enable = true;
+
+static cursor_glide_context_t glide = {.config = {
+ .coef = 102, /* Good default friction coef */
+ .interval = 10, /* 100sps */
+ .trigger_px = 10, /* Default threshold in case of hover, set to 0 if you'd like */
+ }};
+
+void cirque_pinnacle_enable_cursor_glide(bool enable) {
+ cursor_glide_enable = enable;
+}
+
+void cirque_pinnacle_configure_cursor_glide(float trigger_px) {
+ glide.config.trigger_px = trigger_px;
+}
+# endif
+
+# if CIRQUE_PINNACLE_POSITION_MODE
+report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) {
+ pinnacle_data_t touchData = cirque_pinnacle_read_data();
+ mouse_xy_report_t report_x = 0, report_y = 0;
+ static uint16_t x = 0, y = 0;
+# ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+ cursor_glide_t glide_report = {0};
+
+ if (cursor_glide_enable) {
+ glide_report = cursor_glide_check(&glide);
+ }
+# endif
+
+ if (!touchData.valid) {
+# ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+ if (cursor_glide_enable && glide_report.valid) {
+ report_x = glide_report.dx;
+ report_y = glide_report.dy;
+ goto mouse_report_update;
+ }
+# endif
+ return mouse_report;
+ }
+
+# if CONSOLE_ENABLE
+ if (debug_mouse && touchData.touchDown) {
+ dprintf("cirque_pinnacle touchData x=%4d y=%4d z=%2d\n", touchData.xValue, touchData.yValue, touchData.zValue);
+ }
+# endif
+
+ // Scale coordinates to arbitrary X, Y resolution
+ cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale());
+
+ if (!cirque_pinnacle_gestures(&mouse_report, touchData)) {
+ if (x && y && touchData.xValue && touchData.yValue) {
+ report_x = CONSTRAIN_HID_XY((int16_t)(touchData.xValue - x));
+ report_y = CONSTRAIN_HID_XY((int16_t)(touchData.yValue - y));
+ }
+ x = touchData.xValue;
+ y = touchData.yValue;
+
+# ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+ if (cursor_glide_enable) {
+ if (touchData.touchDown) {
+ cursor_glide_update(&glide, report_x, report_y, touchData.zValue);
+ } else if (!glide_report.valid) {
+ glide_report = cursor_glide_start(&glide);
+ if (glide_report.valid) {
+ report_x = glide_report.dx;
+ report_y = glide_report.dy;
+ }
+ }
+ }
+# endif
+ }
+
+# ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+mouse_report_update:
+# endif
+ mouse_report.x = report_x;
+ mouse_report.y = report_y;
+
+ return mouse_report;
+}
+
+uint16_t cirque_pinnacle_get_cpi(void) {
+ return CIRQUE_PINNACLE_PX_TO_INCH(cirque_pinnacle_get_scale());
+}
+void cirque_pinnacle_set_cpi(uint16_t cpi) {
+ cirque_pinnacle_set_scale(CIRQUE_PINNACLE_INCH_TO_PX(cpi));
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+ .init = cirque_pinnacle_init,
+ .get_report = cirque_pinnacle_get_report,
+ .set_cpi = cirque_pinnacle_set_cpi,
+ .get_cpi = cirque_pinnacle_get_cpi
+};
+// clang-format on
+# else
+report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) {
+ pinnacle_data_t touchData = cirque_pinnacle_read_data();
+
+ // Scale coordinates to arbitrary X, Y resolution
+ cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale());
+
+ if (touchData.valid) {
+ mouse_report.buttons = touchData.buttons;
+ mouse_report.x = CONSTRAIN_HID_XY(touchData.xDelta);
+ mouse_report.y = CONSTRAIN_HID_XY(touchData.yDelta);
+ mouse_report.v = touchData.wheelCount;
+ }
+ return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+ .init = cirque_pinnacle_init,
+ .get_report = cirque_pinnacle_get_report,
+ .set_cpi = cirque_pinnacle_set_scale,
+ .get_cpi = cirque_pinnacle_get_scale
+};
+// clang-format on
+# endif
+
+#elif defined(POINTING_DEVICE_DRIVER_paw3204)
+
+report_mouse_t paw3204_get_report(report_mouse_t mouse_report) {
+ report_paw3204_t data = paw3204_read();
+ if (data.isMotion) {
+# ifdef CONSOLE_ENABLE
+ dprintf("Raw ] X: %d, Y: %d\n", data.x, data.y);
+# endif
+
+ mouse_report.x = data.x;
+ mouse_report.y = data.y;
+ }
+
+ return mouse_report;
+}
+const pointing_device_driver_t pointing_device_driver = {
+ .init = paw3204_init,
+ .get_report = paw3204_get_report,
+ .set_cpi = paw3204_set_cpi,
+ .get_cpi = paw3204_get_cpi,
+};
+#elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)
+
+mouse_xy_report_t pimoroni_trackball_adapt_values(clamp_range_t* offset) {
+ if (*offset > XY_REPORT_MAX) {
+ *offset -= XY_REPORT_MAX;
+ return (mouse_xy_report_t)XY_REPORT_MAX;
+ } else if (*offset < XY_REPORT_MIN) {
+ *offset += XY_REPORT_MAX;
+ return (mouse_xy_report_t)XY_REPORT_MIN;
+ } else {
+ mouse_xy_report_t temp_return = *offset;
+ *offset = 0;
+ return temp_return;
+ }
+}
+
+report_mouse_t pimoroni_trackball_get_report(report_mouse_t mouse_report) {
+ static uint16_t debounce = 0;
+ static uint8_t error_count = 0;
+ pimoroni_data_t pimoroni_data = {0};
+ static clamp_range_t x_offset = 0, y_offset = 0;
+
+ if (error_count < PIMORONI_TRACKBALL_ERROR_COUNT) {
+ i2c_status_t status = read_pimoroni_trackball(&pimoroni_data);
+
+ if (status == I2C_STATUS_SUCCESS) {
+ error_count = 0;
+
+ if (!(pimoroni_data.click & 128)) {
+ mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, false, POINTING_DEVICE_BUTTON1);
+ if (!debounce) {
+ x_offset += pimoroni_trackball_get_offsets(pimoroni_data.right, pimoroni_data.left, PIMORONI_TRACKBALL_SCALE);
+ y_offset += pimoroni_trackball_get_offsets(pimoroni_data.down, pimoroni_data.up, PIMORONI_TRACKBALL_SCALE);
+ mouse_report.x = pimoroni_trackball_adapt_values(&x_offset);
+ mouse_report.y = pimoroni_trackball_adapt_values(&y_offset);
+ } else {
+ debounce--;
+ }
+ } else {
+ mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1);
+ debounce = PIMORONI_TRACKBALL_DEBOUNCE_CYCLES;
+ }
+ } else {
+ error_count++;
+ }
+ }
+ return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+ .init = pimoroni_trackball_device_init,
+ .get_report = pimoroni_trackball_get_report,
+ .set_cpi = pimoroni_trackball_set_cpi,
+ .get_cpi = pimoroni_trackball_get_cpi
+};
+// clang-format on
+
+#elif defined(POINTING_DEVICE_DRIVER_pmw3360) || defined(POINTING_DEVICE_DRIVER_pmw3389)
+static void pmw33xx_init_wrapper(void) {
+ pmw33xx_init(0);
+}
+
+static void pmw33xx_set_cpi_wrapper(uint16_t cpi) {
+ pmw33xx_set_cpi(0, cpi);
+}
+
+static uint16_t pmw33xx_get_cpi_wrapper(void) {
+ return pmw33xx_get_cpi(0);
+}
+
+report_mouse_t pmw33xx_get_report(report_mouse_t mouse_report) {
+ pmw33xx_report_t report = pmw33xx_read_burst(0);
+ static bool in_motion = false;
+
+ if (report.motion.b.is_lifted) {
+ return mouse_report;
+ }
+
+ if (!report.motion.b.is_motion) {
+ in_motion = false;
+ return mouse_report;
+ }
+
+ if (!in_motion) {
+ in_motion = true;
+ dprintf("PWM3360 (0): starting motion\n");
+ }
+
+ mouse_report.x = CONSTRAIN_HID_XY(report.delta_x);
+ mouse_report.y = CONSTRAIN_HID_XY(report.delta_y);
+ return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+ .init = pmw33xx_init_wrapper,
+ .get_report = pmw33xx_get_report,
+ .set_cpi = pmw33xx_set_cpi_wrapper,
+ .get_cpi = pmw33xx_get_cpi_wrapper
+};
+// clang-format on
+
+#else
+__attribute__((weak)) void pointing_device_driver_init(void) {}
+__attribute__((weak)) report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) {
+ return mouse_report;
+}
+__attribute__((weak)) uint16_t pointing_device_driver_get_cpi(void) {
+ return 0;
+}
+__attribute__((weak)) void pointing_device_driver_set_cpi(uint16_t cpi) {}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+ .init = pointing_device_driver_init,
+ .get_report = pointing_device_driver_get_report,
+ .get_cpi = pointing_device_driver_get_cpi,
+ .set_cpi = pointing_device_driver_set_cpi
+};
+// clang-format on
+
+#endif
diff --git a/quantum/pointing_device/pointing_device_gestures.c b/quantum/pointing_device/pointing_device_gestures.c
new file mode 100644
index 0000000000..02b11ebe3f
--- /dev/null
+++ b/quantum/pointing_device/pointing_device_gestures.c
@@ -0,0 +1,133 @@
+/* Copyright 2022 Daniel Kao <daniel.m.kao@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <string.h>
+#include "pointing_device_gestures.h"
+#include "timer.h"
+
+#ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+# ifdef POINTING_DEVICE_MOTION_PIN
+# error POINTING_DEVICE_MOTION_PIN not supported when using inertial cursor. Need repeated calls to get_report() to generate glide events.
+# endif
+
+static void cursor_glide_stop(cursor_glide_context_t* glide) {
+ memset(&glide->status, 0, sizeof(glide->status));
+}
+
+static cursor_glide_t cursor_glide(cursor_glide_context_t* glide) {
+ cursor_glide_status_t* status = &glide->status;
+ cursor_glide_t report;
+ int32_t p;
+ int32_t x, y;
+
+ if (status->v0 == 0) {
+ report.dx = 0;
+ report.dy = 0;
+ report.valid = false;
+ cursor_glide_stop(glide);
+ goto exit;
+ }
+
+ status->counter++;
+ /* Calculate current 1D position */
+ p = status->v0 * status->counter - (int32_t)glide->config.coef * status->counter * status->counter / 2;
+ /*
+ * Translate to x & y axes
+ * Done this way instead of applying friction to each axis separately, so we don't end up with the shorter axis stuck at 0 towards the end of diagonal movements.
+ */
+ x = (int32_t)(p * status->dx0 / status->v0);
+ y = (int32_t)(p * status->dy0 / status->v0);
+ report.dx = (mouse_xy_report_t)(x - status->x);
+ report.dy = (mouse_xy_report_t)(y - status->y);
+ report.valid = true;
+ if (report.dx <= 1 && report.dx >= -1 && report.dy <= 1 && report.dy >= -1) {
+ /* Stop gliding once speed is low enough */
+ cursor_glide_stop(glide);
+ goto exit;
+ }
+ status->x = x;
+ status->y = y;
+ status->timer = timer_read();
+
+exit:
+ return report;
+}
+
+cursor_glide_t cursor_glide_check(cursor_glide_context_t* glide) {
+ cursor_glide_t invalid_report = {0, 0, false};
+ cursor_glide_status_t* status = &glide->status;
+
+ if (status->z || (status->dx0 == 0 && status->dy0 == 0) || timer_elapsed(status->timer) < glide->config.interval) {
+ return invalid_report;
+ } else {
+ return cursor_glide(glide);
+ }
+}
+
+static inline uint16_t sqrt32(uint32_t x) {
+ uint32_t l, m, h;
+
+ if (x == 0) {
+ return 0;
+ } else if (x > (UINT16_MAX >> 2)) {
+ /* Safe upper bound to avoid integer overflow with m * m */
+ h = UINT16_MAX;
+ } else {
+ /* Upper bound based on closest log2 */
+ h = (1 << (((__builtin_clzl(1) - __builtin_clzl(x) + 1) + 1) >> 1));
+ }
+ /* Lower bound based on closest log2 */
+ l = (1 << ((__builtin_clzl(1) - __builtin_clzl(x)) >> 1));
+
+ /* Binary search to find integer square root */
+ while (l != h - 1) {
+ m = (l + h) / 2;
+ if (m * m <= x) {
+ l = m;
+ } else {
+ h = m;
+ }
+ }
+ return l;
+}
+
+cursor_glide_t cursor_glide_start(cursor_glide_context_t* glide) {
+ cursor_glide_t invalid_report = {0, 0, false};
+ cursor_glide_status_t* status = &glide->status;
+
+ status->timer = timer_read();
+ status->counter = 0;
+ status->v0 = (status->dx0 == 0 && status->dy0 == 0) ? 0.0 : sqrt32(((int32_t)status->dx0 * 256 * status->dx0 * 256) + ((int32_t)status->dy0 * 256 * status->dy0 * 256)); // skip trigonometry if not needed, calculate distance in Q8
+ status->x = 0;
+ status->y = 0;
+ status->z = 0;
+
+ if (status->v0 < ((uint32_t)glide->config.trigger_px * 256)) { /* Q8 comparison */
+ /* Not enough velocity to be worth gliding, abort */
+ cursor_glide_stop(glide);
+ return invalid_report;
+ }
+
+ return cursor_glide(glide);
+}
+
+void cursor_glide_update(cursor_glide_context_t* glide, mouse_xy_report_t dx, mouse_xy_report_t dy, uint16_t z) {
+ cursor_glide_status_t* status = &glide->status;
+
+ status->dx0 = dx;
+ status->dy0 = dy;
+ status->z = z;
+}
+#endif
diff --git a/quantum/pointing_device/pointing_device_gestures.h b/quantum/pointing_device/pointing_device_gestures.h
new file mode 100644
index 0000000000..d2ea44971b
--- /dev/null
+++ b/quantum/pointing_device/pointing_device_gestures.h
@@ -0,0 +1,58 @@
+/* Copyright 2022 Daniel Kao <daniel.m.kao@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <stdint.h>
+#include "report.h"
+
+#ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+typedef struct {
+ mouse_xy_report_t dx;
+ mouse_xy_report_t dy;
+ bool valid;
+} cursor_glide_t;
+
+typedef struct {
+ uint16_t trigger_px; /* Pixels of movement needed to trigger cursor glide */
+ uint16_t coef; /* Coefficient of friction */
+ uint16_t interval; /* Glide report interval, in milliseconds */
+} cursor_glide_config_t;
+
+typedef struct {
+ int32_t v0;
+ int32_t x;
+ int32_t y;
+ uint16_t z;
+ uint16_t timer;
+ uint16_t counter;
+ mouse_xy_report_t dx0;
+ mouse_xy_report_t dy0;
+} cursor_glide_status_t;
+
+typedef struct {
+ cursor_glide_config_t config;
+ cursor_glide_status_t status;
+} cursor_glide_context_t;
+
+/* Check glide report conditions, calculates glide coordinates */
+cursor_glide_t cursor_glide_check(cursor_glide_context_t* glide);
+
+/* Start glide reporting, gives first set of glide coordinates */
+cursor_glide_t cursor_glide_start(cursor_glide_context_t* glide);
+
+/* Update glide engine on the latest cursor movement, cursor glide is based on the final movement */
+void cursor_glide_update(cursor_glide_context_t* glide, mouse_xy_report_t dx, mouse_xy_report_t dy, uint16_t z);
+#endif
diff --git a/quantum/pointing_device_drivers.c b/quantum/pointing_device_drivers.c
deleted file mode 100644
index 56363c7ac6..0000000000
--- a/quantum/pointing_device_drivers.c
+++ /dev/null
@@ -1,295 +0,0 @@
-/* Copyright 2017 Joshua Broekhuijsen <snipeye+qmk@gmail.com>
- * Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
- * Copyright 2021 Dasky (@daskygit)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "pointing_device.h"
-#include "debug.h"
-#include "wait.h"
-#include "timer.h"
-#include <stddef.h>
-
-// hid mouse reports cannot exceed -127 to 127, so constrain to that value
-#define constrain_hid(amt) ((amt) < -127 ? -127 : ((amt) > 127 ? 127 : (amt)))
-
-// get_report functions should probably be moved to their respective drivers.
-#if defined(POINTING_DEVICE_DRIVER_adns5050)
-report_mouse_t adns5050_get_report(report_mouse_t mouse_report) {
- report_adns5050_t data = adns5050_read_burst();
-
- if (data.dx != 0 || data.dy != 0) {
-# ifdef CONSOLE_ENABLE
- if (debug_mouse) dprintf("Raw ] X: %d, Y: %d\n", data.dx, data.dy);
-# endif
-
- mouse_report.x = data.dx;
- mouse_report.y = data.dy;
- }
-
- return mouse_report;
-}
-
-// clang-format off
-const pointing_device_driver_t pointing_device_driver = {
- .init = adns5050_init,
- .get_report = adns5050_get_report,
- .set_cpi = adns5050_set_cpi,
- .get_cpi = adns5050_get_cpi,
-};
-// clang-format on
-#elif defined(POINTING_DEVICE_DRIVER_adns9800)
-
-report_mouse_t adns9800_get_report_driver(report_mouse_t mouse_report) {
- report_adns9800_t sensor_report = adns9800_get_report();
-
- int8_t clamped_x = constrain_hid(sensor_report.x);
- int8_t clamped_y = constrain_hid(sensor_report.y);
-
- mouse_report.x = clamped_x;
- mouse_report.y = clamped_y;
-
- return mouse_report;
-}
-
-// clang-format off
-const pointing_device_driver_t pointing_device_driver = {
- .init = adns9800_init,
- .get_report = adns9800_get_report_driver,
- .set_cpi = adns9800_set_cpi,
- .get_cpi = adns9800_get_cpi
-};
-// clang-format on
-#elif defined(POINTING_DEVICE_DRIVER_analog_joystick)
-report_mouse_t analog_joystick_get_report(report_mouse_t mouse_report) {
- report_analog_joystick_t data = analog_joystick_read();
-
-# ifdef CONSOLE_ENABLE
- if (debug_mouse) dprintf("Raw ] X: %d, Y: %d\n", data.x, data.y);
-# endif
-
- mouse_report.x = data.x;
- mouse_report.y = data.y;
-
- mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, data.button, POINTING_DEVICE_BUTTON1);
-
- return mouse_report;
-}
-
-// clang-format off
-const pointing_device_driver_t pointing_device_driver = {
- .init = analog_joystick_init,
- .get_report = analog_joystick_get_report,
- .set_cpi = NULL,
- .get_cpi = NULL
-};
-// clang-format on
-#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) || defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
-# ifndef CIRQUE_PINNACLE_TAPPING_TERM
-# include "action.h"
-# include "action_tapping.h"
-# define CIRQUE_PINNACLE_TAPPING_TERM GET_TAPPING_TERM(KC_BTN1, &(keyrecord_t){})
-# endif
-# ifndef CIRQUE_PINNACLE_TOUCH_DEBOUNCE
-# define CIRQUE_PINNACLE_TOUCH_DEBOUNCE (CIRQUE_PINNACLE_TAPPING_TERM * 8)
-# endif
-
-report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) {
- pinnacle_data_t touchData = cirque_pinnacle_read_data();
- static uint16_t x = 0, y = 0, mouse_timer = 0;
- int8_t report_x = 0, report_y = 0;
- static bool is_z_down = false;
-
- cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale()); // Scale coordinates to arbitrary X, Y resolution
-
- if (x && y && touchData.xValue && touchData.yValue) {
- report_x = (int8_t)(touchData.xValue - x);
- report_y = (int8_t)(touchData.yValue - y);
- }
- x = touchData.xValue;
- y = touchData.yValue;
-
- if ((bool)touchData.zValue != is_z_down) {
- is_z_down = (bool)touchData.zValue;
- if (!touchData.zValue) {
- if (timer_elapsed(mouse_timer) < CIRQUE_PINNACLE_TAPPING_TERM && mouse_timer != 0) {
- mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1);
- pointing_device_set_report(mouse_report);
- pointing_device_send();
-# if TAP_CODE_DELAY > 0
- wait_ms(TAP_CODE_DELAY);
-# endif
- mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, false, POINTING_DEVICE_BUTTON1);
- pointing_device_set_report(mouse_report);
- pointing_device_send();
- }
- }
- mouse_timer = timer_read();
- }
- if (timer_elapsed(mouse_timer) > (CIRQUE_PINNACLE_TOUCH_DEBOUNCE)) {
- mouse_timer = 0;
- }
- mouse_report.x = report_x;
- mouse_report.y = report_y;
-
- return mouse_report;
-}
-
-// clang-format off
-const pointing_device_driver_t pointing_device_driver = {
- .init = cirque_pinnacle_init,
- .get_report = cirque_pinnacle_get_report,
- .set_cpi = cirque_pinnacle_set_scale,
- .get_cpi = cirque_pinnacle_get_scale
-};
-// clang-format on
-
-#elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)
-report_mouse_t pimoroni_trackball_get_report(report_mouse_t mouse_report) {
- static uint16_t debounce = 0;
- static uint8_t error_count = 0;
- pimoroni_data_t pimoroni_data = {0};
- static int16_t x_offset = 0, y_offset = 0;
-
- if (error_count < PIMORONI_TRACKBALL_ERROR_COUNT) {
- i2c_status_t status = read_pimoroni_trackball(&pimoroni_data);
-
- if (status == I2C_STATUS_SUCCESS) {
- error_count = 0;
-
- if (!(pimoroni_data.click & 128)) {
- mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, false, POINTING_DEVICE_BUTTON1);
- if (!debounce) {
- x_offset += pimoroni_trackball_get_offsets(pimoroni_data.right, pimoroni_data.left, PIMORONI_TRACKBALL_SCALE);
- y_offset += pimoroni_trackball_get_offsets(pimoroni_data.down, pimoroni_data.up, PIMORONI_TRACKBALL_SCALE);
- pimoroni_trackball_adapt_values(&mouse_report.x, &x_offset);
- pimoroni_trackball_adapt_values(&mouse_report.y, &y_offset);
- } else {
- debounce--;
- }
- } else {
- mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1);
- debounce = PIMORONI_TRACKBALL_DEBOUNCE_CYCLES;
- }
- } else {
- error_count++;
- }
- }
- return mouse_report;
-}
-
-// clang-format off
-const pointing_device_driver_t pointing_device_driver = {
- .init = pimoroni_trackball_device_init,
- .get_report = pimoroni_trackball_get_report,
- .set_cpi = pimoroni_trackball_set_cpi,
- .get_cpi = pimoroni_trackball_get_cpi
-};
-// clang-format on
-#elif defined(POINTING_DEVICE_DRIVER_pmw3360)
-static void pmw3360_device_init(void) {
- pmw3360_init(0);
-}
-
-report_mouse_t pmw3360_get_report(report_mouse_t mouse_report) {
- report_pmw3360_t data = pmw3360_read_burst(0);
- static uint16_t MotionStart = 0; // Timer for accel, 0 is resting state
-
- if (data.isOnSurface && data.isMotion) {
- // Reset timer if stopped moving
- if (!data.isMotion) {
- if (MotionStart != 0) MotionStart = 0;
- return mouse_report;
- }
-
- // Set timer if new motion
- if ((MotionStart == 0) && data.isMotion) {
-# ifdef CONSOLE_ENABLE
- if (debug_mouse) dprintf("Starting motion.\n");
-# endif
- MotionStart = timer_read();
- }
- mouse_report.x = constrain_hid(data.dx);
- mouse_report.y = constrain_hid(data.dy);
- }
-
- return mouse_report;
-}
-
-// clang-format off
-const pointing_device_driver_t pointing_device_driver = {
- .init = pmw3360_device_init,
- .get_report = pmw3360_get_report,
- .set_cpi = pmw3360_set_cpi,
- .get_cpi = pmw3360_get_cpi
-};
-// clang-format on
-#elif defined(POINTING_DEVICE_DRIVER_pmw3389)
-static void pmw3389_device_init(void) {
- pmw3389_init();
-}
-
-report_mouse_t pmw3389_get_report(report_mouse_t mouse_report) {
- report_pmw3389_t data = pmw3389_read_burst();
- static uint16_t MotionStart = 0; // Timer for accel, 0 is resting state
-
- if (data.isOnSurface && data.isMotion) {
- // Reset timer if stopped moving
- if (!data.isMotion) {
- if (MotionStart != 0) MotionStart = 0;
- return mouse_report;
- }
-
- // Set timer if new motion
- if ((MotionStart == 0) && data.isMotion) {
-# ifdef CONSOLE_ENABLE
- if (debug_mouse) dprintf("Starting motion.\n");
-# endif
- MotionStart = timer_read();
- }
- mouse_report.x = constrain_hid(data.dx);
- mouse_report.y = constrain_hid(data.dy);
- }
-
- return mouse_report;
-}
-
-// clang-format off
-const pointing_device_driver_t pointing_device_driver = {
- .init = pmw3389_device_init,
- .get_report = pmw3389_get_report,
- .set_cpi = pmw3389_set_cpi,
- .get_cpi = pmw3389_get_cpi
-};
-// clang-format on
-#else
-__attribute__((weak)) void pointing_device_driver_init(void) {}
-__attribute__((weak)) report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) {
- return mouse_report;
-}
-__attribute__((weak)) uint16_t pointing_device_driver_get_cpi(void) {
- return 0;
-}
-__attribute__((weak)) void pointing_device_driver_set_cpi(uint16_t cpi) {}
-
-// clang-format off
-const pointing_device_driver_t pointing_device_driver = {
- .init = pointing_device_driver_init,
- .get_report = pointing_device_driver_get_report,
- .get_cpi = pointing_device_driver_get_cpi,
- .set_cpi = pointing_device_driver_set_cpi
-};
-// clang-format on
-#endif
diff --git a/quantum/process_keycode/process_auto_shift.c b/quantum/process_keycode/process_auto_shift.c
index e6a7c01f2a..8cb45bc0ae 100644
--- a/quantum/process_keycode/process_auto_shift.c
+++ b/quantum/process_keycode/process_auto_shift.c
@@ -325,11 +325,13 @@ void autoshift_disable(void) {
# ifndef AUTO_SHIFT_NO_SETUP
void autoshift_timer_report(void) {
+# ifdef SEND_STRING_ENABLE
char display[8];
snprintf(display, 8, "\n%d\n", autoshift_timeout);
send_string((const char *)display);
+# endif
}
# endif
diff --git a/quantum/process_keycode/process_dynamic_macro.c b/quantum/process_keycode/process_dynamic_macro.c
index a1ada2d5a2..a7555fdd40 100644
--- a/quantum/process_keycode/process_dynamic_macro.c
+++ b/quantum/process_keycode/process_dynamic_macro.c
@@ -86,6 +86,9 @@ void dynamic_macro_play(keyrecord_t *macro_buffer, keyrecord_t *macro_end, int8_
while (macro_buffer != macro_end) {
process_record(macro_buffer);
macro_buffer += direction;
+#ifdef DYNAMIC_MACRO_DELAY
+ wait_ms(DYNAMIC_MACRO_DELAY);
+#endif
}
clear_keyboard();
diff --git a/quantum/process_keycode/process_dynamic_tapping_term.c b/quantum/process_keycode/process_dynamic_tapping_term.c
index bdc5529e33..b682f34da6 100644
--- a/quantum/process_keycode/process_dynamic_tapping_term.c
+++ b/quantum/process_keycode/process_dynamic_tapping_term.c
@@ -22,12 +22,14 @@
#endif
static void tapping_term_report(void) {
+#ifdef SEND_STRING_ENABLE
const char *tapping_term_str = get_u16_str(g_tapping_term, ' ');
// Skip padding spaces
while (*tapping_term_str == ' ') {
tapping_term_str++;
}
send_string(tapping_term_str);
+#endif
}
bool process_dynamic_tapping_term(uint16_t keycode, keyrecord_t *record) {
diff --git a/quantum/process_keycode/process_leader.c b/quantum/process_keycode/process_leader.c
index c2fd02e5c7..ae00b3227a 100644
--- a/quantum/process_keycode/process_leader.c
+++ b/quantum/process_keycode/process_leader.c
@@ -64,6 +64,7 @@ bool process_leader(uint16_t keycode, keyrecord_t *record) {
} else {
leading = false;
leader_end();
+ return true;
}
# ifdef LEADER_PER_KEY_TIMING
leader_time = timer_read();
diff --git a/quantum/process_keycode/process_magic.c b/quantum/process_keycode/process_magic.c
index 10161adda3..ae60f29bf5 100644
--- a/quantum/process_keycode/process_magic.c
+++ b/quantum/process_keycode/process_magic.c
@@ -45,12 +45,16 @@ bool process_magic(uint16_t keycode, keyrecord_t *record) {
case MAGIC_SWAP_LCTL_LGUI ... MAGIC_EE_HANDS_RIGHT:
case MAGIC_TOGGLE_GUI:
case MAGIC_TOGGLE_CONTROL_CAPSLOCK:
+ case MAGIC_SWAP_ESCAPE_CAPSLOCK ... MAGIC_TOGGLE_ESCAPE_CAPSLOCK:
/* keymap config */
keymap_config.raw = eeconfig_read_keymap();
switch (keycode) {
case MAGIC_SWAP_CONTROL_CAPSLOCK:
keymap_config.swap_control_capslock = true;
break;
+ case MAGIC_SWAP_ESCAPE_CAPSLOCK:
+ keymap_config.swap_escape_capslock = true;
+ break;
case MAGIC_CAPSLOCK_TO_CONTROL:
keymap_config.capslock_to_control = true;
break;
@@ -94,6 +98,9 @@ bool process_magic(uint16_t keycode, keyrecord_t *record) {
case MAGIC_UNSWAP_CONTROL_CAPSLOCK:
keymap_config.swap_control_capslock = false;
break;
+ case MAGIC_UNSWAP_ESCAPE_CAPSLOCK:
+ keymap_config.swap_escape_capslock = false;
+ break;
case MAGIC_UNCAPSLOCK_TO_CONTROL:
keymap_config.capslock_to_control = false;
break;
@@ -172,6 +179,9 @@ bool process_magic(uint16_t keycode, keyrecord_t *record) {
case MAGIC_TOGGLE_CONTROL_CAPSLOCK:
keymap_config.swap_control_capslock = !keymap_config.swap_control_capslock;
break;
+ case MAGIC_TOGGLE_ESCAPE_CAPSLOCK:
+ keymap_config.swap_escape_capslock = !keymap_config.swap_escape_capslock;
+ break;
}
eeconfig_update_keymap(keymap_config.raw);
diff --git a/quantum/process_keycode/process_steno.c b/quantum/process_keycode/process_steno.c
index 12ee898212..30a0d4056f 100644
--- a/quantum/process_keycode/process_steno.c
+++ b/quantum/process_keycode/process_steno.c
@@ -1,4 +1,4 @@
-/* Copyright 2017 Joseph Wasson
+/* Copyright 2017, 2022 Joseph Wasson, Vladislav Kucheriavykh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,77 +15,118 @@
*/
#include "process_steno.h"
#include "quantum_keycodes.h"
-#include "eeprom.h"
#include "keymap_steno.h"
-#include "virtser.h"
#include <string.h>
+#ifdef VIRTSER_ENABLE
+# include "virtser.h"
+#endif
+#ifdef STENO_ENABLE_ALL
+# include "eeprom.h"
+#endif
-// TxBolt Codes
-#define TXB_NUL 0
-#define TXB_S_L 0b00000001
-#define TXB_T_L 0b00000010
-#define TXB_K_L 0b00000100
-#define TXB_P_L 0b00001000
-#define TXB_W_L 0b00010000
-#define TXB_H_L 0b00100000
-#define TXB_R_L 0b01000001
-#define TXB_A_L 0b01000010
-#define TXB_O_L 0b01000100
-#define TXB_STR 0b01001000
-#define TXB_E_R 0b01010000
-#define TXB_U_R 0b01100000
-#define TXB_F_R 0b10000001
-#define TXB_R_R 0b10000010
-#define TXB_P_R 0b10000100
-#define TXB_B_R 0b10001000
-#define TXB_L_R 0b10010000
-#define TXB_G_R 0b10100000
-#define TXB_T_R 0b11000001
-#define TXB_S_R 0b11000010
-#define TXB_D_R 0b11000100
-#define TXB_Z_R 0b11001000
-#define TXB_NUM 0b11010000
-
-#define TXB_GRP0 0b00000000
-#define TXB_GRP1 0b01000000
-#define TXB_GRP2 0b10000000
-#define TXB_GRP3 0b11000000
-#define TXB_GRPMASK 0b11000000
-
-#define TXB_GET_GROUP(code) ((code & TXB_GRPMASK) >> 6)
-
-#define BOLT_STATE_SIZE 4
-#define GEMINI_STATE_SIZE 6
-#define MAX_STATE_SIZE GEMINI_STATE_SIZE
-
-static uint8_t state[MAX_STATE_SIZE] = {0};
-static uint8_t chord[MAX_STATE_SIZE] = {0};
-static int8_t pressed = 0;
+// All steno keys that have been pressed to form this chord,
+// stored in MAX_STROKE_SIZE groups of 8-bit arrays.
+static uint8_t chord[MAX_STROKE_SIZE] = {0};
+// The number of physical keys actually being held down.
+// This is not always equal to the number of 1 bits in `chord` because it is possible to
+// simultaneously press down four keys, then release three of those four keys and then press yet
+// another key while the fourth finger is still holding down its key.
+// At the end of this scenario given as an example, `chord` would have five bits set to 1 but
+// `n_pressed_keys` would be set to 2 because there are only two keys currently being pressed down.
+static int8_t n_pressed_keys = 0;
+
+#ifdef STENO_ENABLE_ALL
static steno_mode_t mode;
-
-static const uint8_t boltmap[64] PROGMEM = {TXB_NUL, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_S_L, TXB_S_L, TXB_T_L, TXB_K_L, TXB_P_L, TXB_W_L, TXB_H_L, TXB_R_L, TXB_A_L, TXB_O_L, TXB_STR, TXB_STR, TXB_NUL, TXB_NUL, TXB_NUL, TXB_STR, TXB_STR, TXB_E_R, TXB_U_R, TXB_F_R, TXB_R_R, TXB_P_R, TXB_B_R, TXB_L_R, TXB_G_R, TXB_T_R, TXB_S_R, TXB_D_R, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_Z_R};
-
-#ifdef STENO_COMBINEDMAP
-/* Used to look up when pressing the middle row key to combine two consonant or vowel keys */
-static const uint16_t combinedmap_first[] PROGMEM = {STN_S1, STN_TL, STN_PL, STN_HL, STN_FR, STN_PR, STN_LR, STN_TR, STN_DR, STN_A, STN_E};
-static const uint16_t combinedmap_second[] PROGMEM = {STN_S2, STN_KL, STN_WL, STN_RL, STN_RR, STN_BR, STN_GR, STN_SR, STN_ZR, STN_O, STN_U};
+#elif defined(STENO_ENABLE_GEMINI)
+static const steno_mode_t mode = STENO_MODE_GEMINI;
+#elif defined(STENO_ENABLE_BOLT)
+static const steno_mode_t mode = STENO_MODE_BOLT;
#endif
-static void steno_clear_state(void) {
- memset(state, 0, sizeof(state));
+static inline void steno_clear_chord(void) {
memset(chord, 0, sizeof(chord));
}
-static void send_steno_state(uint8_t size, bool send_empty) {
- for (uint8_t i = 0; i < size; ++i) {
- if (chord[i] || send_empty) {
-#ifdef VIRTSER_ENABLE
+#ifdef STENO_ENABLE_GEMINI
+
+# ifdef VIRTSER_ENABLE
+void send_steno_chord_gemini(void) {
+ // Set MSB to 1 to indicate the start of packet
+ chord[0] |= 0x80;
+ for (uint8_t i = 0; i < GEMINI_STROKE_SIZE; ++i) {
+ virtser_send(chord[i]);
+ }
+}
+# else
+# pragma message "VIRTSER_ENABLE = yes is required for Gemini PR to work properly out of the box!"
+# endif // VIRTSER_ENABLE
+
+/**
+ * @precondition: `key` is pressed
+ */
+bool add_gemini_key_to_chord(uint8_t key) {
+ // Although each group of the packet is 8 bits long, the MSB is reserved
+ // to indicate whether that byte is the first byte of the packet (MSB=1)
+ // or one of the remaining five bytes of the packet (MSB=0).
+ // As a consequence, only 7 out of the 8 bits are left to be used as a bit array
+ // for the steno keys of that group.
+ const int group_idx = key / 7;
+ const int intra_group_idx = key - group_idx * 7;
+ // The 0th steno key of the group has bit=0b01000000, the 1st has bit=0b00100000, etc.
+ const uint8_t bit = 1 << (6 - intra_group_idx);
+ chord[group_idx] |= bit;
+ return false;
+}
+#endif // STENO_ENABLE_GEMINI
+
+#ifdef STENO_ENABLE_BOLT
+
+# define TXB_GRP0 0b00000000
+# define TXB_GRP1 0b01000000
+# define TXB_GRP2 0b10000000
+# define TXB_GRP3 0b11000000
+# define TXB_GRPMASK 0b11000000
+
+# define TXB_GET_GROUP(code) ((code & TXB_GRPMASK) >> 6)
+
+static const uint8_t boltmap[64] PROGMEM = {TXB_NUL, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_S_L, TXB_S_L, TXB_T_L, TXB_K_L, TXB_P_L, TXB_W_L, TXB_H_L, TXB_R_L, TXB_A_L, TXB_O_L, TXB_STR, TXB_STR, TXB_NUL, TXB_NUL, TXB_NUL, TXB_STR, TXB_STR, TXB_E_R, TXB_U_R, TXB_F_R, TXB_R_R, TXB_P_R, TXB_B_R, TXB_L_R, TXB_G_R, TXB_T_R, TXB_S_R, TXB_D_R, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_Z_R};
+
+# ifdef VIRTSER_ENABLE
+static void send_steno_chord_bolt(void) {
+ for (uint8_t i = 0; i < BOLT_STROKE_SIZE; ++i) {
+ // TX Bolt uses variable length packets where each byte corresponds to a bit array of certain keys.
+ // If a user chorded the keys of the first group with keys of the last group, for example, there
+ // would be bytes of 0x00 in `chord` for the middle groups which we mustn't send.
+ if (chord[i]) {
virtser_send(chord[i]);
-#endif
}
}
+ // Sending a null packet is not always necessary, but it is simpler and more reliable
+ // to unconditionally send it every time instead of keeping track of more states and
+ // creating more branches in the execution of the program.
+ virtser_send(0);
}
+# else
+# pragma message "VIRTSER_ENABLE = yes is required for TX Bolt to work properly out of the box!"
+# endif // VIRTSER_ENABLE
+
+/**
+ * @precondition: `key` is pressed
+ */
+static bool add_bolt_key_to_chord(uint8_t key) {
+ uint8_t boltcode = pgm_read_byte(boltmap + key);
+ chord[TXB_GET_GROUP(boltcode)] |= boltcode;
+ return false;
+}
+#endif // STENO_ENABLE_BOLT
+
+#ifdef STENO_COMBINEDMAP
+/* Used to look up when pressing the middle row key to combine two consonant or vowel keys */
+static const uint16_t combinedmap_first[] PROGMEM = {STN_S1, STN_TL, STN_PL, STN_HL, STN_FR, STN_PR, STN_LR, STN_TR, STN_DR, STN_A, STN_E};
+static const uint16_t combinedmap_second[] PROGMEM = {STN_S2, STN_KL, STN_WL, STN_RL, STN_RR, STN_BR, STN_GR, STN_SR, STN_ZR, STN_O, STN_U};
+#endif
+#ifdef STENO_ENABLE_ALL
void steno_init() {
if (!eeconfig_is_enabled()) {
eeconfig_init();
@@ -94,19 +135,20 @@ void steno_init() {
}
void steno_set_mode(steno_mode_t new_mode) {
- steno_clear_state();
+ steno_clear_chord();
mode = new_mode;
eeprom_update_byte(EECONFIG_STENOMODE, mode);
}
+#endif // STENO_ENABLE_ALL
/* override to intercept chords right before they get sent.
* return zero to suppress normal sending behavior.
*/
-__attribute__((weak)) bool send_steno_chord_user(steno_mode_t mode, uint8_t chord[6]) {
+__attribute__((weak)) bool send_steno_chord_user(steno_mode_t mode, uint8_t chord[MAX_STROKE_SIZE]) {
return true;
}
-__attribute__((weak)) bool postprocess_steno_user(uint16_t keycode, keyrecord_t *record, steno_mode_t mode, uint8_t chord[6], int8_t pressed) {
+__attribute__((weak)) bool post_process_steno_user(uint16_t keycode, keyrecord_t *record, steno_mode_t mode, uint8_t chord[MAX_STROKE_SIZE], int8_t n_pressed_keys) {
return true;
}
@@ -114,108 +156,94 @@ __attribute__((weak)) bool process_steno_user(uint16_t keycode, keyrecord_t *rec
return true;
}
-static void send_steno_chord(void) {
- if (send_steno_chord_user(mode, chord)) {
- switch (mode) {
- case STENO_MODE_BOLT:
- send_steno_state(BOLT_STATE_SIZE, false);
-#ifdef VIRTSER_ENABLE
- virtser_send(0); // terminating byte
-#endif
- break;
- case STENO_MODE_GEMINI:
- chord[0] |= 0x80; // Indicate start of packet
- send_steno_state(GEMINI_STATE_SIZE, true);
- break;
- }
+bool process_steno(uint16_t keycode, keyrecord_t *record) {
+ if (keycode < QK_STENO || keycode > QK_STENO_MAX) {
+ return true; // Not a steno key, pass it further along the chain
+ /*
+ * Clearing or sending the chord state is not necessary as we intentionally ignore whatever
+ * normal keyboard keys the user may have tapped while chording steno keys.
+ */
}
- steno_clear_state();
-}
-
-uint8_t *steno_get_state(void) {
- return &state[0];
-}
-
-uint8_t *steno_get_chord(void) {
- return &chord[0];
-}
-
-static bool update_state_bolt(uint8_t key, bool press) {
- uint8_t boltcode = pgm_read_byte(boltmap + key);
- if (press) {
- state[TXB_GET_GROUP(boltcode)] |= boltcode;
- chord[TXB_GET_GROUP(boltcode)] |= boltcode;
- } else {
- state[TXB_GET_GROUP(boltcode)] &= ~boltcode;
+ if (IS_NOEVENT(record->event)) {
+ return true;
}
- return false;
-}
-
-static bool update_state_gemini(uint8_t key, bool press) {
- int idx = key / 7;
- uint8_t bit = 1 << (6 - (key % 7));
- if (press) {
- state[idx] |= bit;
- chord[idx] |= bit;
- } else {
- state[idx] &= ~bit;
+ if (!process_steno_user(keycode, record)) {
+ return false; // User fully processed the steno key themselves
}
- return false;
-}
-
-bool process_steno(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
+#ifdef STENO_ENABLE_ALL
case QK_STENO_BOLT:
- if (!process_steno_user(keycode, record)) {
- return false;
- }
if (IS_PRESSED(record->event)) {
steno_set_mode(STENO_MODE_BOLT);
}
return false;
case QK_STENO_GEMINI:
- if (!process_steno_user(keycode, record)) {
- return false;
- }
if (IS_PRESSED(record->event)) {
steno_set_mode(STENO_MODE_GEMINI);
}
return false;
+#endif // STENO_ENABLE_ALL
#ifdef STENO_COMBINEDMAP
case QK_STENO_COMB ... QK_STENO_COMB_MAX: {
- uint8_t result;
- result = process_steno(combinedmap_first[keycode - QK_STENO_COMB], record);
- result &= process_steno(combinedmap_second[keycode - QK_STENO_COMB], record);
- return result;
+ bool first_result = process_steno(combinedmap_first[keycode - QK_STENO_COMB], record);
+ bool second_result = process_steno(combinedmap_second[keycode - QK_STENO_COMB], record);
+ return first_result && second_result;
}
-#endif
+#endif // STENO_COMBINEDMAP
case STN__MIN ... STN__MAX:
- if (!process_steno_user(keycode, record)) {
- return false;
- }
- switch (mode) {
- case STENO_MODE_BOLT:
- update_state_bolt(keycode - QK_STENO, IS_PRESSED(record->event));
- break;
- case STENO_MODE_GEMINI:
- update_state_gemini(keycode - QK_STENO, IS_PRESSED(record->event));
- break;
- }
- // allow postprocessing hooks
- if (postprocess_steno_user(keycode, record, mode, chord, pressed)) {
- if (IS_PRESSED(record->event)) {
- ++pressed;
- } else {
- --pressed;
- if (pressed <= 0) {
- pressed = 0;
- send_steno_chord();
- }
+ if (IS_PRESSED(record->event)) {
+ n_pressed_keys++;
+ switch (mode) {
+#ifdef STENO_ENABLE_BOLT
+ case STENO_MODE_BOLT:
+ add_bolt_key_to_chord(keycode - QK_STENO);
+ break;
+#endif // STENO_ENABLE_BOLT
+#ifdef STENO_ENABLE_GEMINI
+ case STENO_MODE_GEMINI:
+ add_gemini_key_to_chord(keycode - QK_STENO);
+ break;
+#endif // STENO_ENABLE_GEMINI
+ default:
+ return false;
}
+ if (!post_process_steno_user(keycode, record, mode, chord, n_pressed_keys)) {
+ return false;
+ }
+ } else { // is released
+ n_pressed_keys--;
+ if (!post_process_steno_user(keycode, record, mode, chord, n_pressed_keys)) {
+ return false;
+ }
+ if (n_pressed_keys > 0) {
+ // User hasn't released all keys yet,
+ // so the chord cannot be sent
+ return false;
+ }
+ n_pressed_keys = 0;
+ if (!send_steno_chord_user(mode, chord)) {
+ steno_clear_chord();
+ return false;
+ }
+ switch (mode) {
+#if defined(STENO_ENABLE_BOLT) && defined(VIRTSER_ENABLE)
+ case STENO_MODE_BOLT:
+ send_steno_chord_bolt();
+ break;
+#endif // STENO_ENABLE_BOLT && VIRTSER_ENABLE
+#if defined(STENO_ENABLE_GEMINI) && defined(VIRTSER_ENABLE)
+ case STENO_MODE_GEMINI:
+ send_steno_chord_gemini();
+ break;
+#endif // STENO_ENABLE_GEMINI && VIRTSER_ENABLE
+ default:
+ break;
+ }
+ steno_clear_chord();
}
- return false;
+ break;
}
- return true;
+ return false;
}
diff --git a/quantum/process_keycode/process_steno.h b/quantum/process_keycode/process_steno.h
index d11fd40af0..68d6097b9b 100644
--- a/quantum/process_keycode/process_steno.h
+++ b/quantum/process_keycode/process_steno.h
@@ -18,10 +18,22 @@
#include "quantum.h"
-typedef enum { STENO_MODE_BOLT, STENO_MODE_GEMINI } steno_mode_t;
+#define BOLT_STROKE_SIZE 4
+#define GEMINI_STROKE_SIZE 6
-bool process_steno(uint16_t keycode, keyrecord_t *record);
-void steno_init(void);
-void steno_set_mode(steno_mode_t mode);
-uint8_t *steno_get_state(void);
-uint8_t *steno_get_chord(void);
+#ifdef STENO_ENABLE_GEMINI
+# define MAX_STROKE_SIZE GEMINI_STROKE_SIZE
+#else
+# define MAX_STROKE_SIZE BOLT_STROKE_SIZE
+#endif
+
+typedef enum {
+ STENO_MODE_GEMINI,
+ STENO_MODE_BOLT,
+} steno_mode_t;
+
+bool process_steno(uint16_t keycode, keyrecord_t *record);
+#ifdef STENO_ENABLE_ALL
+void steno_init(void);
+void steno_set_mode(steno_mode_t mode);
+#endif // STENO_ENABLE_ALL
diff --git a/quantum/process_keycode/process_tap_dance.c b/quantum/process_keycode/process_tap_dance.c
index db8df5f870..3270a1b000 100644
--- a/quantum/process_keycode/process_tap_dance.c
+++ b/quantum/process_keycode/process_tap_dance.c
@@ -15,12 +15,8 @@
*/
#include "quantum.h"
-#ifndef NO_ACTION_ONESHOT
-uint8_t get_oneshot_mods(void);
-#endif
-
-static uint16_t last_td;
-static int16_t highest_td = -1;
+static uint16_t active_td;
+static uint16_t last_tap_time;
void qk_tap_dance_pair_on_each_tap(qk_tap_dance_state_t *state, void *user_data) {
qk_tap_dance_pair_t *pair = (qk_tap_dance_pair_t *)user_data;
@@ -34,18 +30,14 @@ void qk_tap_dance_pair_on_each_tap(qk_tap_dance_state_t *state, void *user_data)
void qk_tap_dance_pair_finished(qk_tap_dance_state_t *state, void *user_data) {
qk_tap_dance_pair_t *pair = (qk_tap_dance_pair_t *)user_data;
- if (state->count == 1) {
- register_code16(pair->kc1);
- } else if (state->count == 2) {
- register_code16(pair->kc2);
- }
+ register_code16(pair->kc1);
}
void qk_tap_dance_pair_reset(qk_tap_dance_state_t *state, void *user_data) {
qk_tap_dance_pair_t *pair = (qk_tap_dance_pair_t *)user_data;
- wait_ms(TAP_CODE_DELAY);
if (state->count == 1) {
+ wait_ms(TAP_CODE_DELAY);
unregister_code16(pair->kc1);
} else if (state->count == 2) {
unregister_code16(pair->kc2);
@@ -87,23 +79,40 @@ static inline void _process_tap_dance_action_fn(qk_tap_dance_state_t *state, voi
}
static inline void process_tap_dance_action_on_each_tap(qk_tap_dance_action_t *action) {
+ action->state.count++;
+ action->state.weak_mods = get_mods();
+ action->state.weak_mods |= get_weak_mods();
+#ifndef NO_ACTION_ONESHOT
+ action->state.oneshot_mods = get_oneshot_mods();
+#endif
_process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_tap);
}
-static inline void process_tap_dance_action_on_dance_finished(qk_tap_dance_action_t *action) {
- if (action->state.finished) return;
- action->state.finished = true;
- add_mods(action->state.oneshot_mods);
- add_weak_mods(action->state.weak_mods);
- send_keyboard_report();
- _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_dance_finished);
-}
-
static inline void process_tap_dance_action_on_reset(qk_tap_dance_action_t *action) {
_process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_reset);
- del_mods(action->state.oneshot_mods);
del_weak_mods(action->state.weak_mods);
+#ifndef NO_ACTION_ONESHOT
+ del_mods(action->state.oneshot_mods);
+#endif
send_keyboard_report();
+ action->state = (const qk_tap_dance_state_t){0};
+}
+
+static inline void process_tap_dance_action_on_dance_finished(qk_tap_dance_action_t *action) {
+ if (!action->state.finished) {
+ action->state.finished = true;
+ add_weak_mods(action->state.weak_mods);
+#ifndef NO_ACTION_ONESHOT
+ add_mods(action->state.oneshot_mods);
+#endif
+ send_keyboard_report();
+ _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_dance_finished);
+ }
+ active_td = 0;
+ if (!action->state.pressed) {
+ // There will not be a key release event, so reset now.
+ process_tap_dance_action_on_reset(action);
+ }
}
void preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) {
@@ -111,51 +120,33 @@ void preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) {
if (!record->event.pressed) return;
- if (highest_td == -1) return;
-
- for (int i = 0; i <= highest_td; i++) {
- action = &tap_dance_actions[i];
- if (action->state.count) {
- if (keycode == action->state.keycode && keycode == last_td) continue;
- action->state.interrupted = true;
- action->state.interrupting_keycode = keycode;
- process_tap_dance_action_on_dance_finished(action);
- reset_tap_dance(&action->state);
-
- // Tap dance actions can leave some weak mods active (e.g., if the tap dance is mapped to a keycode with
- // modifiers), but these weak mods should not affect the keypress which interrupted the tap dance.
- clear_weak_mods();
- }
- }
+ if (!active_td || keycode == active_td) return;
+
+ action = &tap_dance_actions[TD_INDEX(active_td)];
+ action->state.interrupted = true;
+ action->state.interrupting_keycode = keycode;
+ process_tap_dance_action_on_dance_finished(action);
+
+ // Tap dance actions can leave some weak mods active (e.g., if the tap dance is mapped to a keycode with
+ // modifiers), but these weak mods should not affect the keypress which interrupted the tap dance.
+ clear_weak_mods();
}
bool process_tap_dance(uint16_t keycode, keyrecord_t *record) {
- uint16_t idx = keycode - QK_TAP_DANCE;
qk_tap_dance_action_t *action;
switch (keycode) {
case QK_TAP_DANCE ... QK_TAP_DANCE_MAX:
- if ((int16_t)idx > highest_td) highest_td = idx;
- action = &tap_dance_actions[idx];
+ action = &tap_dance_actions[TD_INDEX(keycode)];
action->state.pressed = record->event.pressed;
if (record->event.pressed) {
- action->state.keycode = keycode;
- action->state.count++;
- action->state.timer = timer_read();
-#ifndef NO_ACTION_ONESHOT
- action->state.oneshot_mods = get_oneshot_mods();
-#else
- action->state.oneshot_mods = 0;
-#endif
- action->state.weak_mods = get_mods();
- action->state.weak_mods |= get_weak_mods();
+ last_tap_time = timer_read();
process_tap_dance_action_on_each_tap(action);
-
- last_td = keycode;
+ active_td = action->state.finished ? 0 : keycode;
} else {
- if (action->state.count && action->state.finished) {
- reset_tap_dance(&action->state);
+ if (action->state.finished) {
+ process_tap_dance_action_on_reset(action);
}
}
@@ -166,35 +157,17 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) {
}
void tap_dance_task() {
- if (highest_td == -1) return;
- uint16_t tap_user_defined;
-
- for (uint8_t i = 0; i <= highest_td; i++) {
- qk_tap_dance_action_t *action = &tap_dance_actions[i];
- if (action->custom_tapping_term > 0) {
- tap_user_defined = action->custom_tapping_term;
- } else {
- tap_user_defined = GET_TAPPING_TERM(action->state.keycode, &(keyrecord_t){});
- }
- if (action->state.count && timer_elapsed(action->state.timer) > tap_user_defined) {
- process_tap_dance_action_on_dance_finished(action);
- reset_tap_dance(&action->state);
- }
- }
-}
-
-void reset_tap_dance(qk_tap_dance_state_t *state) {
qk_tap_dance_action_t *action;
- if (state->pressed) return;
+ if (!active_td || timer_elapsed(last_tap_time) <= GET_TAPPING_TERM(active_td, &(keyrecord_t){})) return;
- action = &tap_dance_actions[state->keycode - QK_TAP_DANCE];
-
- process_tap_dance_action_on_reset(action);
+ action = &tap_dance_actions[TD_INDEX(active_td)];
+ if (!action->state.interrupted) {
+ process_tap_dance_action_on_dance_finished(action);
+ }
+}
- state->count = 0;
- state->interrupted = false;
- state->finished = false;
- state->interrupting_keycode = 0;
- last_td = 0;
+void reset_tap_dance(qk_tap_dance_state_t *state) {
+ active_td = 0;
+ process_tap_dance_action_on_reset((qk_tap_dance_action_t *)state);
}
diff --git a/quantum/process_keycode/process_tap_dance.h b/quantum/process_keycode/process_tap_dance.h
index d9ffb1e73d..d97900d96b 100644
--- a/quantum/process_keycode/process_tap_dance.h
+++ b/quantum/process_keycode/process_tap_dance.h
@@ -22,30 +22,27 @@
# include <inttypes.h>
typedef struct {
+ uint16_t interrupting_keycode;
uint8_t count;
- uint8_t oneshot_mods;
uint8_t weak_mods;
- uint16_t keycode;
- uint16_t interrupting_keycode;
- uint16_t timer;
- bool interrupted;
- bool pressed;
- bool finished;
+# ifndef NO_ACTION_ONESHOT
+ uint8_t oneshot_mods;
+# endif
+ bool pressed : 1;
+ bool finished : 1;
+ bool interrupted : 1;
} qk_tap_dance_state_t;
-# define TD(n) (QK_TAP_DANCE | ((n)&0xFF))
-
typedef void (*qk_tap_dance_user_fn_t)(qk_tap_dance_state_t *state, void *user_data);
typedef struct {
+ qk_tap_dance_state_t state;
struct {
qk_tap_dance_user_fn_t on_each_tap;
qk_tap_dance_user_fn_t on_dance_finished;
qk_tap_dance_user_fn_t on_reset;
} fn;
- qk_tap_dance_state_t state;
- uint16_t custom_tapping_term;
- void * user_data;
+ void *user_data;
} qk_tap_dance_action_t;
typedef struct {
@@ -62,31 +59,31 @@ typedef struct {
# define ACTION_TAP_DANCE_DOUBLE(kc1, kc2) \
{ .fn = {qk_tap_dance_pair_on_each_tap, qk_tap_dance_pair_finished, qk_tap_dance_pair_reset}, .user_data = (void *)&((qk_tap_dance_pair_t){kc1, kc2}), }
-# define ACTION_TAP_DANCE_DUAL_ROLE(kc, layer) \
+# define ACTION_TAP_DANCE_LAYER_MOVE(kc, layer) \
{ .fn = {qk_tap_dance_dual_role_on_each_tap, qk_tap_dance_dual_role_finished, qk_tap_dance_dual_role_reset}, .user_data = (void *)&((qk_tap_dance_dual_role_t){kc, layer, layer_move}), }
# define ACTION_TAP_DANCE_LAYER_TOGGLE(kc, layer) \
{ .fn = {NULL, qk_tap_dance_dual_role_finished, qk_tap_dance_dual_role_reset}, .user_data = (void *)&((qk_tap_dance_dual_role_t){kc, layer, layer_invert}), }
-# define ACTION_TAP_DANCE_LAYER_MOVE(kc, layer) ACTION_TAP_DANCE_DUAL_ROLE(kc, layer)
-
# define ACTION_TAP_DANCE_FN(user_fn) \
{ .fn = {NULL, user_fn, NULL}, .user_data = NULL, }
# define ACTION_TAP_DANCE_FN_ADVANCED(user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_dance_reset) \
{ .fn = {user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_dance_reset}, .user_data = NULL, }
-# define ACTION_TAP_DANCE_FN_ADVANCED_TIME(user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_dance_reset, tap_specific_tapping_term) \
- { .fn = {user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_dance_reset}, .user_data = NULL, .custom_tapping_term = tap_specific_tapping_term, }
+# define TD(n) (QK_TAP_DANCE | TD_INDEX(n))
+# define TD_INDEX(code) ((code)&0xFF)
+# define TAP_DANCE_KEYCODE(state) TD(((qk_tap_dance_action_t *)state) - tap_dance_actions)
extern qk_tap_dance_action_t tap_dance_actions[];
+void reset_tap_dance(qk_tap_dance_state_t *state);
+
/* To be used internally */
void preprocess_tap_dance(uint16_t keycode, keyrecord_t *record);
bool process_tap_dance(uint16_t keycode, keyrecord_t *record);
void tap_dance_task(void);
-void reset_tap_dance(qk_tap_dance_state_t *state);
void qk_tap_dance_pair_on_each_tap(qk_tap_dance_state_t *state, void *user_data);
void qk_tap_dance_pair_finished(qk_tap_dance_state_t *state, void *user_data);
diff --git a/quantum/process_keycode/process_terminal.c b/quantum/process_keycode/process_terminal.c
deleted file mode 100644
index da1c4d506f..0000000000
--- a/quantum/process_keycode/process_terminal.c
+++ /dev/null
@@ -1,330 +0,0 @@
-/* Copyright 2017 Jack Humbert
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "process_terminal.h"
-#include <string.h>
-#include "version.h"
-#include <stdio.h>
-#include <math.h>
-
-#ifndef CMD_BUFF_SIZE
-# define CMD_BUFF_SIZE 5
-#endif
-
-bool terminal_enabled = false;
-char buffer[80] = "";
-char cmd_buffer[CMD_BUFF_SIZE][80];
-bool cmd_buffer_enabled = true; // replace with ifdef?
-char newline[2] = "\n";
-char arguments[6][20];
-bool firstTime = true;
-
-short int current_cmd_buffer_pos = 0; // used for up/down arrows - keeps track of where you are in the command buffer
-
-__attribute__((weak)) const char terminal_prompt[8] = "> ";
-
-#ifdef AUDIO_ENABLE
-# ifndef TERMINAL_SONG
-# define TERMINAL_SONG SONG(TERMINAL_SOUND)
-# endif
-float terminal_song[][2] = TERMINAL_SONG;
-# define TERMINAL_BELL() PLAY_SONG(terminal_song)
-#else
-# define TERMINAL_BELL()
-#endif
-
-__attribute__((weak)) const char keycode_to_ascii_lut[58] = {0, 0, 0, 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 0, 0, 0, '\t', ' ', '-', '=', '[', ']', '\\', 0, ';', '\'', '`', ',', '.', '/'};
-
-__attribute__((weak)) const char shifted_keycode_to_ascii_lut[58] = {0, 0, 0, 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', 0, 0, 0, '\t', ' ', '_', '+', '{', '}', '|', 0, ':', '\'', '~', '<', '>', '?'};
-
-struct stringcase {
- char *string;
- void (*func)(void);
-} typedef stringcase;
-
-void enable_terminal(void) {
- terminal_enabled = true;
- strcpy(buffer, "");
- memset(cmd_buffer, 0, CMD_BUFF_SIZE * 80);
- for (int i = 0; i < 6; i++)
- strcpy(arguments[i], "");
- // select all text to start over
- // SEND_STRING(SS_LCTL("a"));
- send_string(terminal_prompt);
-}
-
-void disable_terminal(void) {
- terminal_enabled = false;
- SEND_STRING("\n");
-}
-
-void push_to_cmd_buffer(void) {
- if (cmd_buffer_enabled) {
- if (cmd_buffer == NULL) {
- return;
- } else {
- if (firstTime) {
- firstTime = false;
- strcpy(cmd_buffer[0], buffer);
- return;
- }
-
- for (int i = CMD_BUFF_SIZE - 1; i > 0; --i) {
- strncpy(cmd_buffer[i], cmd_buffer[i - 1], 80);
- }
-
- strcpy(cmd_buffer[0], buffer);
-
- return;
- }
- }
-}
-
-void terminal_about(void) {
- SEND_STRING("QMK Firmware\n");
- SEND_STRING(" v");
- SEND_STRING(QMK_VERSION);
- SEND_STRING("\n" SS_TAP(X_HOME) " Built: ");
- SEND_STRING(QMK_BUILDDATE);
- send_string(newline);
-#ifdef TERMINAL_HELP
- if (strlen(arguments[1]) != 0) {
- SEND_STRING("You entered: ");
- send_string(arguments[1]);
- send_string(newline);
- }
-#endif
-}
-
-void terminal_help(void);
-
-extern const uint16_t keymaps[][MATRIX_ROWS][MATRIX_COLS];
-
-void terminal_keycode(void) {
- if (strlen(arguments[1]) != 0 && strlen(arguments[2]) != 0 && strlen(arguments[3]) != 0) {
- char keycode_dec[5];
- char keycode_hex[5];
- uint16_t layer = strtol(arguments[1], (char **)NULL, 10);
- uint16_t row = strtol(arguments[2], (char **)NULL, 10);
- uint16_t col = strtol(arguments[3], (char **)NULL, 10);
- uint16_t keycode = pgm_read_word(&keymaps[layer][row][col]);
- itoa(keycode, keycode_dec, 10);
- itoa(keycode, keycode_hex, 16);
- SEND_STRING("0x");
- send_string(keycode_hex);
- SEND_STRING(" (");
- send_string(keycode_dec);
- SEND_STRING(")\n");
- } else {
-#ifdef TERMINAL_HELP
- SEND_STRING("usage: keycode <layer> <row> <col>\n");
-#endif
- }
-}
-
-void terminal_keymap(void) {
- if (strlen(arguments[1]) != 0) {
- uint16_t layer = strtol(arguments[1], (char **)NULL, 10);
- for (int r = 0; r < MATRIX_ROWS; r++) {
- for (int c = 0; c < MATRIX_COLS; c++) {
- uint16_t keycode = pgm_read_word(&keymaps[layer][r][c]);
- char keycode_s[8];
- sprintf(keycode_s, "0x%04x,", keycode);
- send_string(keycode_s);
- }
- send_string(newline);
- }
- } else {
-#ifdef TERMINAL_HELP
- SEND_STRING("usage: keymap <layer>\n");
-#endif
- }
-}
-
-void print_cmd_buff(void) {
- /* without the below wait, a race condition can occur wherein the
- buffer can be printed before it has been fully moved */
- wait_ms(250);
- for (int i = 0; i < CMD_BUFF_SIZE; i++) {
- char tmpChar = ' ';
- itoa(i, &tmpChar, 10);
- const char *tmpCnstCharStr = &tmpChar; // because sned_string wont take a normal char *
- send_string(tmpCnstCharStr);
- SEND_STRING(". ");
- send_string(cmd_buffer[i]);
- SEND_STRING("\n");
- }
-}
-
-void flush_cmd_buffer(void) {
- memset(cmd_buffer, 0, CMD_BUFF_SIZE * 80);
- SEND_STRING("Buffer Cleared!\n");
-}
-
-stringcase terminal_cases[] = {{"about", terminal_about}, {"help", terminal_help}, {"keycode", terminal_keycode}, {"keymap", terminal_keymap}, {"flush-buffer", flush_cmd_buffer}, {"print-buffer", print_cmd_buff}, {"exit", disable_terminal}};
-
-void terminal_help(void) {
- SEND_STRING("commands available:\n ");
- for (stringcase *case_p = terminal_cases; case_p != terminal_cases + sizeof(terminal_cases) / sizeof(terminal_cases[0]); case_p++) {
- send_string(case_p->string);
- SEND_STRING(" ");
- }
- send_string(newline);
-}
-
-void command_not_found(void) {
- wait_ms(50); // sometimes buffer isnt grabbed quick enough
- SEND_STRING("command \"");
- send_string(buffer);
- SEND_STRING("\" not found\n");
-}
-
-void process_terminal_command(void) {
- // we capture return bc of the order of events, so we need to manually send a newline
- send_string(newline);
-
- char * pch;
- uint8_t i = 0;
- pch = strtok(buffer, " ");
- while (pch != NULL) {
- strcpy(arguments[i], pch);
- pch = strtok(NULL, " ");
- i++;
- }
-
- bool command_found = false;
- for (stringcase *case_p = terminal_cases; case_p != terminal_cases + sizeof(terminal_cases) / sizeof(terminal_cases[0]); case_p++) {
- if (0 == strcmp(case_p->string, buffer)) {
- command_found = true;
- (*case_p->func)();
- break;
- }
- }
-
- if (!command_found) command_not_found();
-
- if (terminal_enabled) {
- strcpy(buffer, "");
- for (int i = 0; i < 6; i++)
- strcpy(arguments[i], "");
- SEND_STRING(SS_TAP(X_HOME));
- send_string(terminal_prompt);
- }
-}
-void check_pos(void) {
- if (current_cmd_buffer_pos >= CMD_BUFF_SIZE) { // if over the top, move it back down to the top of the buffer so you can climb back down...
- current_cmd_buffer_pos = CMD_BUFF_SIZE - 1;
- } else if (current_cmd_buffer_pos < 0) { //...and if you fall under the bottom of the buffer, reset back to 0 so you can climb back up
- current_cmd_buffer_pos = 0;
- }
-}
-
-bool process_terminal(uint16_t keycode, keyrecord_t *record) {
- if (keycode == TERM_ON && record->event.pressed) {
- enable_terminal();
- return false;
- }
-
- if (terminal_enabled && record->event.pressed) {
- if (keycode == TERM_OFF && record->event.pressed) {
- disable_terminal();
- return false;
- }
-
- if ((keycode >= QK_MOD_TAP && keycode <= QK_MOD_TAP_MAX) || (keycode >= QK_LAYER_TAP && keycode <= QK_LAYER_TAP_MAX)) {
- keycode = keycode & 0xFF;
- }
-
- if (keycode < 256) {
- uint8_t str_len;
- char char_to_add;
- switch (keycode) {
- case KC_ENTER:
- case KC_KP_ENTER:
- push_to_cmd_buffer();
- current_cmd_buffer_pos = 0;
- process_terminal_command();
- return false;
- break;
- case KC_ESCAPE:
- SEND_STRING("\n");
- enable_terminal();
- return false;
- break;
- case KC_BACKSPACE:
- str_len = strlen(buffer);
- if (str_len > 0) {
- buffer[str_len - 1] = 0;
- return true;
- } else {
- TERMINAL_BELL();
- return false;
- }
- break;
- case KC_LEFT:
- return false;
- break;
- case KC_RIGHT:
- return false;
- break;
- case KC_UP: // 0 = recent
- check_pos(); // check our current buffer position is valid
- if (current_cmd_buffer_pos <= CMD_BUFF_SIZE - 1) { // once we get to the top, dont do anything
- str_len = strlen(buffer);
- for (int i = 0; i < str_len; ++i) {
- send_string(SS_TAP(X_BSPACE)); // clear w/e is on the line already
- // process_terminal(KC_BACKSPACE,record);
- }
- strncpy(buffer, cmd_buffer[current_cmd_buffer_pos], 80);
-
- send_string(buffer);
- ++current_cmd_buffer_pos; // get ready to access the above cmd if up/down is pressed again
- }
- return false;
- break;
- case KC_DOWN:
- check_pos();
- if (current_cmd_buffer_pos >= 0) { // once we get to the bottom, dont do anything
- str_len = strlen(buffer);
- for (int i = 0; i < str_len; ++i) {
- send_string(SS_TAP(X_BSPACE)); // clear w/e is on the line already
- // process_terminal(KC_BACKSPACE,record);
- }
- strncpy(buffer, cmd_buffer[current_cmd_buffer_pos], 79);
-
- send_string(buffer);
- --current_cmd_buffer_pos; // get ready to access the above cmd if down/up is pressed again
- }
- return false;
- break;
- default:
- if (keycode <= 58) {
- char_to_add = 0;
- if (get_mods() & (MOD_BIT(KC_LEFT_SHIFT) | MOD_BIT(KC_RIGHT_SHIFT))) {
- char_to_add = shifted_keycode_to_ascii_lut[keycode];
- } else if (get_mods() == 0) {
- char_to_add = keycode_to_ascii_lut[keycode];
- }
- if (char_to_add != 0) {
- strncat(buffer, &char_to_add, 1);
- }
- }
- break;
- }
- }
- }
- return true;
-}
diff --git a/quantum/process_keycode/process_terminal.h b/quantum/process_keycode/process_terminal.h
deleted file mode 100644
index 0159131e5b..0000000000
--- a/quantum/process_keycode/process_terminal.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/* Copyright 2017 Jack Humbert
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include "quantum.h"
-
-extern const char keycode_to_ascii_lut[58];
-extern const char shifted_keycode_to_ascii_lut[58];
-extern const char terminal_prompt[8];
-bool process_terminal(uint16_t keycode, keyrecord_t *record);
diff --git a/quantum/process_keycode/process_terminal_nop.h b/quantum/process_keycode/process_terminal_nop.h
deleted file mode 100644
index 36e25320c5..0000000000
--- a/quantum/process_keycode/process_terminal_nop.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/* Copyright 2017 Jack Humbert
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include "quantum.h"
-
-#define TERM_ON KC_NO
-#define TERM_OFF KC_NO
diff --git a/quantum/quantum.c b/quantum/quantum.c
index 33121f6b95..9a0016b150 100644
--- a/quantum/quantum.c
+++ b/quantum/quantum.c
@@ -93,16 +93,27 @@ __attribute__((weak)) void unregister_code16(uint16_t code) {
}
}
-__attribute__((weak)) void tap_code16(uint16_t code) {
+/** \brief Tap a keycode with a delay.
+ *
+ * \param code The modded keycode to tap.
+ * \param delay The amount of time in milliseconds to leave the keycode registered, before unregistering it.
+ */
+__attribute__((weak)) void tap_code16_delay(uint16_t code, uint16_t delay) {
register_code16(code);
- if (code == KC_CAPS_LOCK) {
- wait_ms(TAP_HOLD_CAPS_DELAY);
- } else if (TAP_CODE_DELAY > 0) {
- wait_ms(TAP_CODE_DELAY);
+ for (uint16_t i = delay; i > 0; i--) {
+ wait_ms(1);
}
unregister_code16(code);
}
+/** \brief Tap a keycode with the default delay.
+ *
+ * \param code The modded keycode to tap. If `code` is `KC_CAPS_LOCK`, the delay will be `TAP_HOLD_CAPS_DELAY`, otherwise `TAP_CODE_DELAY`, if defined.
+ */
+__attribute__((weak)) void tap_code16(uint16_t code) {
+ tap_code16_delay(code, code == KC_CAPS_LOCK ? TAP_HOLD_CAPS_DELAY : TAP_CODE_DELAY);
+}
+
__attribute__((weak)) bool process_action_kb(keyrecord_t *record) {
return true;
}
@@ -307,9 +318,6 @@ bool process_record_quantum(keyrecord_t *record) {
#ifdef DYNAMIC_TAPPING_TERM_ENABLE
process_dynamic_tapping_term(keycode, record) &&
#endif
-#ifdef TERMINAL_ENABLE
- process_terminal(keycode, record) &&
-#endif
#ifdef SPACE_CADET_ENABLE
process_space_cadet(keycode, record) &&
#endif
@@ -403,6 +411,9 @@ bool process_record_quantum(keyrecord_t *record) {
SEND_STRING_DELAY(" compile ", TAP_CODE_DELAY);
}
SEND_STRING_DELAY("-kb " QMK_KEYBOARD " -km " QMK_KEYMAP SS_TAP(X_ENTER), TAP_CODE_DELAY);
+ if (temp_mod & MOD_MASK_SHIFT && temp_mod & MOD_MASK_CTRL) {
+ reset_keyboard();
+ }
}
#endif
}
diff --git a/quantum/quantum.h b/quantum/quantum.h
index 92e1af1c40..8d74f2be38 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -49,7 +49,6 @@
#include "action_util.h"
#include "action_tapping.h"
#include "print.h"
-#include "send_string.h"
#include "suspend.h"
#include <stddef.h>
#include <stdlib.h>
@@ -141,12 +140,6 @@ extern layer_state_t layer_state;
# include "process_key_lock.h"
#endif
-#ifdef TERMINAL_ENABLE
-# include "process_terminal.h"
-#else
-# include "process_terminal_nop.h"
-#endif
-
#ifdef SPACE_CADET_ENABLE
# include "process_space_cadet.h"
#endif
@@ -175,6 +168,10 @@ extern layer_state_t layer_state;
# include "hd44780.h"
#endif
+#ifdef SEND_STRING_ENABLE
+# include "send_string.h"
+#endif
+
#ifdef HAPTIC_ENABLE
# include "haptic.h"
# include "process_haptic.h"
@@ -267,6 +264,7 @@ void shutdown_user(void);
void register_code16(uint16_t code);
void unregister_code16(uint16_t code);
void tap_code16(uint16_t code);
+void tap_code16_delay(uint16_t code, uint16_t delay);
const char *get_numeric_str(char *buf, size_t buf_len, uint32_t curr_num, char curr_pad);
const char *get_u8_str(uint8_t curr_num, char curr_pad);
diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h
index 40355d799a..456fad6f1b 100644
--- a/quantum/quantum_keycodes.h
+++ b/quantum/quantum_keycodes.h
@@ -473,9 +473,9 @@ enum quantum_keycodes {
// Lock Key
KC_LOCK, // 5D2B
- // Terminal
- TERM_ON, // 5D2C
- TERM_OFF, // 5D2D
+ // Unused slots
+ UNUSED_000, // 5D2C
+ UNUSED_001, // 5D2D
// Sequencer
SQ_ON, // 5D2E
@@ -605,6 +605,10 @@ enum quantum_keycodes {
CAPS_WORD,
+ MAGIC_SWAP_ESCAPE_CAPSLOCK,
+ MAGIC_UNSWAP_ESCAPE_CAPSLOCK,
+ MAGIC_TOGGLE_ESCAPE_CAPSLOCK,
+
// Start of custom keycode range for keyboards and keymaps - always leave at the end
SAFE_RANGE
};
@@ -756,6 +760,10 @@ enum quantum_keycodes {
#define CL_CAPS MAGIC_UNCAPSLOCK_TO_CONTROL
#define CL_TOGG MAGIC_TOGGLE_CONTROL_CAPSLOCK
+#define EC_SWAP MAGIC_SWAP_ESCAPE_CAPSLOCK
+#define EC_NORM MAGIC_UNSWAP_ESCAPE_CAPSLOCK
+#define EC_TOGG MAGIC_TOGGLE_ESCAPE_CAPSLOCK
+
#define LCG_SWP MAGIC_SWAP_LCTL_LGUI
#define LCG_NRM MAGIC_UNSWAP_LCTL_LGUI
#define RCG_SWP MAGIC_SWAP_RCTL_RGUI
diff --git a/quantum/quantum_keycodes_legacy.h b/quantum/quantum_keycodes_legacy.h
index ed9455ee74..51380d9c50 100644
--- a/quantum/quantum_keycodes_legacy.h
+++ b/quantum/quantum_keycodes_legacy.h
@@ -11,3 +11,6 @@
#define KC_GESC QK_GRAVE_ESCAPE
#define EEP_RST QK_CLEAR_EEPROM
+
+#define TERM_ON _Static_assert(false, "The Terminal feature has been removed from QMK. Please remove use of TERM_ON/TERM_OFF from your keymap.")
+#define TERM_OFF _Static_assert(false, "The Terminal feature has been removed from QMK.. Please remove use of TERM_ON/TERM_OFF from your keymap.") \ No newline at end of file
diff --git a/quantum/rgb_matrix/animations/pixel_rain_anim.h b/quantum/rgb_matrix/animations/pixel_rain_anim.h
index 03488b43df..ce2528a26d 100644
--- a/quantum/rgb_matrix/animations/pixel_rain_anim.h
+++ b/quantum/rgb_matrix/animations/pixel_rain_anim.h
@@ -20,17 +20,14 @@ RGB_MATRIX_EFFECT(PIXEL_RAIN)
static bool PIXEL_RAIN(effect_params_t* params) {
static uint32_t wait_timer = 0;
- if (wait_timer > g_rgb_timer) {
- return false;
- }
inline uint32_t interval(void) {
return 500 / scale16by8(qadd8(rgb_matrix_config.speed, 16), 16);
}
- bool rain_pixel(uint8_t i, effect_params_t * params, bool off) {
+ void rain_pixel(uint8_t i, effect_params_t * params, bool off) {
if (!HAS_ANY_FLAGS(g_led_config.flags[i], params->flags)) {
- return true;
+ return;
}
if (off) {
rgb_matrix_set_color(i, 0, 0, 0);
@@ -40,10 +37,13 @@ static bool PIXEL_RAIN(effect_params_t* params) {
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
wait_timer = g_rgb_timer + interval();
- return false;
}
- return rain_pixel(mod8(random8(), DRIVER_LED_TOTAL), params, random8() & 2);
+ RGB_MATRIX_USE_LIMITS(led_min, led_max);
+ if (g_rgb_timer > wait_timer) {
+ rain_pixel(mod8(random8(), DRIVER_LED_TOTAL), params, random8() & 2);
+ }
+ return rgb_matrix_check_finished_leds(led_max);
}
# endif // RGB_MATRIX_CUSTOM_EFFECT_IMPLS
diff --git a/quantum/rgb_matrix/animations/raindrops_anim.h b/quantum/rgb_matrix/animations/raindrops_anim.h
index 2d4b53b7b0..a508e51183 100644
--- a/quantum/rgb_matrix/animations/raindrops_anim.h
+++ b/quantum/rgb_matrix/animations/raindrops_anim.h
@@ -14,23 +14,22 @@ static void raindrops_set_color(int i, effect_params_t* params) {
deltaH += 256;
}
- hsv.h = rgb_matrix_config.hsv.h + (deltaH * (rand() & 0x03));
+ hsv.h = rgb_matrix_config.hsv.h + (deltaH * (random8() & 0x03));
RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
bool RAINDROPS(effect_params_t* params) {
+ RGB_MATRIX_USE_LIMITS(led_min, led_max);
if (!params->init) {
// Change one LED every tick, make sure speed is not 0
if (scale16by8(g_rgb_timer, qadd8(rgb_matrix_config.speed, 16)) % 10 == 0) {
- raindrops_set_color(rand() % DRIVER_LED_TOTAL, params);
+ raindrops_set_color(random8() % DRIVER_LED_TOTAL, params);
+ }
+ } else {
+ for (int i = led_min; i < led_max; i++) {
+ raindrops_set_color(i, params);
}
- return false;
- }
-
- RGB_MATRIX_USE_LIMITS(led_min, led_max);
- for (int i = led_min; i < led_max; i++) {
- raindrops_set_color(i, params);
}
return rgb_matrix_check_finished_leds(led_max);
}
diff --git a/quantum/rgb_matrix/animations/solid_reactive_anim.h b/quantum/rgb_matrix/animations/solid_reactive_anim.h
index d3a7ebbdf7..052bfb22fd 100644
--- a/quantum/rgb_matrix/animations/solid_reactive_anim.h
+++ b/quantum/rgb_matrix/animations/solid_reactive_anim.h
@@ -4,6 +4,9 @@ RGB_MATRIX_EFFECT(SOLID_REACTIVE)
# ifdef RGB_MATRIX_CUSTOM_EFFECT_IMPLS
static HSV SOLID_REACTIVE_math(HSV hsv, uint16_t offset) {
+# ifdef RGB_MATRIX_SOLID_REACTIVE_GRADIENT_MODE
+ hsv.h = scale16by8(g_rgb_timer, add8(rgb_matrix_config.speed, 1) >> 6);
+# endif
hsv.h += qsub8(130, offset);
return hsv;
}
diff --git a/quantum/rgb_matrix/animations/solid_reactive_cross.h b/quantum/rgb_matrix/animations/solid_reactive_cross.h
index 043a369b73..9e5703a0ea 100644
--- a/quantum/rgb_matrix/animations/solid_reactive_cross.h
+++ b/quantum/rgb_matrix/animations/solid_reactive_cross.h
@@ -19,6 +19,9 @@ static HSV SOLID_REACTIVE_CROSS_math(HSV hsv, int16_t dx, int16_t dy, uint8_t di
dy = dy * 16 > 255 ? 255 : dy * 16;
effect += dx > dy ? dy : dx;
if (effect > 255) effect = 255;
+# ifdef RGB_MATRIX_SOLID_REACTIVE_GRADIENT_MODE
+ hsv.h = scale16by8(g_rgb_timer, add8(rgb_matrix_config.speed, 1) >> 6);
+# endif
hsv.v = qadd8(hsv.v, 255 - effect);
return hsv;
}
diff --git a/quantum/rgb_matrix/animations/solid_reactive_nexus.h b/quantum/rgb_matrix/animations/solid_reactive_nexus.h
index 8d62a49fea..64f5064cf9 100644
--- a/quantum/rgb_matrix/animations/solid_reactive_nexus.h
+++ b/quantum/rgb_matrix/animations/solid_reactive_nexus.h
@@ -16,6 +16,9 @@ static HSV SOLID_REACTIVE_NEXUS_math(HSV hsv, int16_t dx, int16_t dy, uint8_t di
if (effect > 255) effect = 255;
if (dist > 72) effect = 255;
if ((dx > 8 || dx < -8) && (dy > 8 || dy < -8)) effect = 255;
+# ifdef RGB_MATRIX_SOLID_REACTIVE_GRADIENT_MODE
+ hsv.h = scale16by8(g_rgb_timer, add8(rgb_matrix_config.speed, 1) >> 6);
+# endif
hsv.v = qadd8(hsv.v, 255 - effect);
hsv.h = rgb_matrix_config.hsv.h + dy / 4;
return hsv;
diff --git a/quantum/rgb_matrix/animations/solid_reactive_simple_anim.h b/quantum/rgb_matrix/animations/solid_reactive_simple_anim.h
index 0d0a424cf3..bd3ce0817d 100644
--- a/quantum/rgb_matrix/animations/solid_reactive_simple_anim.h
+++ b/quantum/rgb_matrix/animations/solid_reactive_simple_anim.h
@@ -4,6 +4,9 @@ RGB_MATRIX_EFFECT(SOLID_REACTIVE_SIMPLE)
# ifdef RGB_MATRIX_CUSTOM_EFFECT_IMPLS
static HSV SOLID_REACTIVE_SIMPLE_math(HSV hsv, uint16_t offset) {
+# ifdef RGB_MATRIX_SOLID_REACTIVE_GRADIENT_MODE
+ hsv.h = scale16by8(g_rgb_timer, add8(rgb_matrix_config.speed, 1) >> 6);
+# endif
hsv.v = scale8(255 - offset, hsv.v);
return hsv;
}
diff --git a/quantum/rgb_matrix/animations/solid_reactive_wide.h b/quantum/rgb_matrix/animations/solid_reactive_wide.h
index 7598796316..24fedc2020 100644
--- a/quantum/rgb_matrix/animations/solid_reactive_wide.h
+++ b/quantum/rgb_matrix/animations/solid_reactive_wide.h
@@ -14,6 +14,9 @@ RGB_MATRIX_EFFECT(SOLID_REACTIVE_MULTIWIDE)
static HSV SOLID_REACTIVE_WIDE_math(HSV hsv, int16_t dx, int16_t dy, uint8_t dist, uint16_t tick) {
uint16_t effect = tick + dist * 5;
if (effect > 255) effect = 255;
+# ifdef RGB_MATRIX_SOLID_REACTIVE_GRADIENT_MODE
+ hsv.h = scale16by8(g_rgb_timer, add8(rgb_matrix_config.speed, 1) >> 6);
+# endif
hsv.v = qadd8(hsv.v, 255 - effect);
return hsv;
}
diff --git a/quantum/rgb_matrix/animations/typing_heatmap_anim.h b/quantum/rgb_matrix/animations/typing_heatmap_anim.h
index 4b17c4c3ed..a05c07760e 100644
--- a/quantum/rgb_matrix/animations/typing_heatmap_anim.h
+++ b/quantum/rgb_matrix/animations/typing_heatmap_anim.h
@@ -6,30 +6,41 @@ RGB_MATRIX_EFFECT(TYPING_HEATMAP)
# define RGB_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS 25
# endif
+# ifndef RGB_MATRIX_TYPING_HEATMAP_SPREAD
+# define RGB_MATRIX_TYPING_HEATMAP_SPREAD 40
+# endif
+
+# ifndef RGB_MATRIX_TYPING_HEATMAP_AREA_LIMIT
+# define RGB_MATRIX_TYPING_HEATMAP_AREA_LIMIT 16
+# endif
void process_rgb_matrix_typing_heatmap(uint8_t row, uint8_t col) {
# ifdef RGB_MATRIX_TYPING_HEATMAP_SLIM
// Limit effect to pressed keys
g_rgb_frame_buffer[row][col] = qadd8(g_rgb_frame_buffer[row][col], 32);
# else
- uint8_t m_row = row - 1;
- uint8_t p_row = row + 1;
- uint8_t m_col = col - 1;
- uint8_t p_col = col + 1;
-
- if (m_col < col) g_rgb_frame_buffer[row][m_col] = qadd8(g_rgb_frame_buffer[row][m_col], 16);
- g_rgb_frame_buffer[row][col] = qadd8(g_rgb_frame_buffer[row][col], 32);
- if (p_col < MATRIX_COLS) g_rgb_frame_buffer[row][p_col] = qadd8(g_rgb_frame_buffer[row][p_col], 16);
-
- if (p_row < MATRIX_ROWS) {
- if (m_col < col) g_rgb_frame_buffer[p_row][m_col] = qadd8(g_rgb_frame_buffer[p_row][m_col], 13);
- g_rgb_frame_buffer[p_row][col] = qadd8(g_rgb_frame_buffer[p_row][col], 16);
- if (p_col < MATRIX_COLS) g_rgb_frame_buffer[p_row][p_col] = qadd8(g_rgb_frame_buffer[p_row][p_col], 13);
+ if (g_led_config.matrix_co[row][col] == NO_LED) { // skip as pressed key doesn't have an led position
+ return;
}
-
- if (m_row < row) {
- if (m_col < col) g_rgb_frame_buffer[m_row][m_col] = qadd8(g_rgb_frame_buffer[m_row][m_col], 13);
- g_rgb_frame_buffer[m_row][col] = qadd8(g_rgb_frame_buffer[m_row][col], 16);
- if (p_col < MATRIX_COLS) g_rgb_frame_buffer[m_row][p_col] = qadd8(g_rgb_frame_buffer[m_row][p_col], 13);
+ for (uint8_t i_row = 0; i_row < MATRIX_ROWS; i_row++) {
+ for (uint8_t i_col = 0; i_col < MATRIX_COLS; i_col++) {
+ if (g_led_config.matrix_co[i_row][i_col] == NO_LED) { // skip as target key doesn't have an led position
+ continue;
+ }
+ if (i_row == row && i_col == col) {
+ g_rgb_frame_buffer[row][col] = qadd8(g_rgb_frame_buffer[row][col], 32);
+ } else {
+# define LED_DISTANCE(led_a, led_b) sqrt16(((int8_t)(led_a.x - led_b.x) * (int8_t)(led_a.x - led_b.x)) + ((int8_t)(led_a.y - led_b.y) * (int8_t)(led_a.y - led_b.y)))
+ uint8_t distance = LED_DISTANCE(g_led_config.point[g_led_config.matrix_co[row][col]], g_led_config.point[g_led_config.matrix_co[i_row][i_col]]);
+# undef LED_DISTANCE
+ if (distance <= RGB_MATRIX_TYPING_HEATMAP_SPREAD) {
+ uint8_t amount = qsub8(RGB_MATRIX_TYPING_HEATMAP_SPREAD, distance);
+ if (amount > RGB_MATRIX_TYPING_HEATMAP_AREA_LIMIT) {
+ amount = RGB_MATRIX_TYPING_HEATMAP_AREA_LIMIT;
+ }
+ g_rgb_frame_buffer[i_row][i_col] = qadd8(g_rgb_frame_buffer[i_row][i_col], amount);
+ }
+ }
+ }
}
# endif
}
@@ -40,10 +51,7 @@ static uint16_t heatmap_decrease_timer;
static bool decrease_heatmap_values;
bool TYPING_HEATMAP(effect_params_t* params) {
- // Modified version of RGB_MATRIX_USE_LIMITS to work off of matrix row / col size
- uint8_t led_min = RGB_MATRIX_LED_PROCESS_LIMIT * params->iter;
- uint8_t led_max = led_min + RGB_MATRIX_LED_PROCESS_LIMIT;
- if (led_max > sizeof(g_rgb_frame_buffer)) led_max = sizeof(g_rgb_frame_buffer);
+ RGB_MATRIX_USE_LIMITS(led_min, led_max);
if (params->init) {
rgb_matrix_set_color_all(0, 0, 0);
@@ -63,28 +71,26 @@ bool TYPING_HEATMAP(effect_params_t* params) {
}
// Render heatmap & decrease
- for (int i = led_min; i < led_max; i++) {
- uint8_t row = i % MATRIX_ROWS;
- uint8_t col = i / MATRIX_ROWS;
- uint8_t val = g_rgb_frame_buffer[row][col];
-
- // set the pixel colour
- uint8_t led[LED_HITS_TO_REMEMBER];
- uint8_t led_count = rgb_matrix_map_row_column_to_led(row, col, led);
- for (uint8_t j = 0; j < led_count; ++j) {
- if (!HAS_ANY_FLAGS(g_led_config.flags[led[j]], params->flags)) continue;
-
- HSV hsv = {170 - qsub8(val, 85), rgb_matrix_config.hsv.s, scale8((qadd8(170, val) - 170) * 3, rgb_matrix_config.hsv.v)};
- RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
- rgb_matrix_set_color(led[j], rgb.r, rgb.g, rgb.b);
- }
-
- if (decrease_heatmap_values) {
- g_rgb_frame_buffer[row][col] = qsub8(val, 1);
+ uint8_t count = 0;
+ for (uint8_t row = 0; row < MATRIX_ROWS && count < RGB_MATRIX_LED_PROCESS_LIMIT; row++) {
+ for (uint8_t col = 0; col < MATRIX_COLS && RGB_MATRIX_LED_PROCESS_LIMIT; col++) {
+ if (g_led_config.matrix_co[row][col] >= led_min && g_led_config.matrix_co[row][col] < led_max) {
+ count++;
+ uint8_t val = g_rgb_frame_buffer[row][col];
+ if (!HAS_ANY_FLAGS(g_led_config.flags[g_led_config.matrix_co[row][col]], params->flags)) continue;
+
+ HSV hsv = {170 - qsub8(val, 85), rgb_matrix_config.hsv.s, scale8((qadd8(170, val) - 170) * 3, rgb_matrix_config.hsv.v)};
+ RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
+ rgb_matrix_set_color(g_led_config.matrix_co[row][col], rgb.r, rgb.g, rgb.b);
+
+ if (decrease_heatmap_values) {
+ g_rgb_frame_buffer[row][col] = qsub8(val, 1);
+ }
+ }
}
}
- return led_max < sizeof(g_rgb_frame_buffer);
+ return rgb_matrix_check_finished_leds(led_max);
}
# endif // RGB_MATRIX_CUSTOM_EFFECT_IMPLS
diff --git a/quantum/rgb_matrix/rgb_matrix.c b/quantum/rgb_matrix/rgb_matrix.c
index f721dfc7f2..2730686839 100644
--- a/quantum/rgb_matrix/rgb_matrix.c
+++ b/quantum/rgb_matrix/rgb_matrix.c
@@ -249,8 +249,15 @@ void process_rgb_matrix(uint8_t row, uint8_t col, bool pressed) {
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED
#if defined(RGB_MATRIX_FRAMEBUFFER_EFFECTS) && defined(ENABLE_RGB_MATRIX_TYPING_HEATMAP)
- if (rgb_matrix_config.mode == RGB_MATRIX_TYPING_HEATMAP) {
- process_rgb_matrix_typing_heatmap(row, col);
+# if defined(RGB_MATRIX_KEYRELEASES)
+ if (!pressed)
+# else
+ if (pressed)
+# endif // defined(RGB_MATRIX_KEYRELEASES)
+ {
+ if (rgb_matrix_config.mode == RGB_MATRIX_TYPING_HEATMAP) {
+ process_rgb_matrix_typing_heatmap(row, col);
+ }
}
#endif // defined(RGB_MATRIX_FRAMEBUFFER_EFFECTS) && defined(ENABLE_RGB_MATRIX_TYPING_HEATMAP)
}
@@ -724,10 +731,20 @@ void rgb_matrix_decrease_speed(void) {
rgb_matrix_decrease_speed_helper(true);
}
+void rgb_matrix_set_flags_eeprom_helper(led_flags_t flags, bool write_to_eeprom) {
+ rgb_matrix_config.flags = flags;
+ eeconfig_flag_rgb_matrix(write_to_eeprom);
+ dprintf("rgb matrix set speed [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.flags);
+}
+
led_flags_t rgb_matrix_get_flags(void) {
return rgb_matrix_config.flags;
}
void rgb_matrix_set_flags(led_flags_t flags) {
- rgb_matrix_config.flags = flags;
+ rgb_matrix_set_flags_eeprom_helper(flags, true);
+}
+
+void rgb_matrix_set_flags_noeeprom(led_flags_t flags) {
+ rgb_matrix_set_flags_eeprom_helper(flags, false);
}
diff --git a/quantum/rgb_matrix/rgb_matrix.h b/quantum/rgb_matrix/rgb_matrix.h
index 359d507a4d..fc9fc3e020 100644
--- a/quantum/rgb_matrix/rgb_matrix.h
+++ b/quantum/rgb_matrix/rgb_matrix.h
@@ -182,6 +182,7 @@ void rgb_matrix_increase_speed_noeeprom(void);
void rgb_matrix_decrease_speed(void);
void rgb_matrix_decrease_speed_noeeprom(void);
led_flags_t rgb_matrix_get_flags(void);
+led_flags_t rgb_matrix_get_flags_noeeprom(void);
void rgb_matrix_set_flags(led_flags_t flags);
#ifndef RGBLIGHT_ENABLE
diff --git a/quantum/send_string.h b/quantum/send_string.h
deleted file mode 100644
index b90e6f6890..0000000000
--- a/quantum/send_string.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Copyright 2021
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdint.h>
-
-#include "progmem.h"
-#include "send_string_keycodes.h"
-
-#define SEND_STRING(string) send_string_P(PSTR(string))
-#define SEND_STRING_DELAY(string, interval) send_string_with_delay_P(PSTR(string), interval)
-
-// Look-Up Tables (LUTs) to convert ASCII character to keycode sequence.
-extern const uint8_t ascii_to_shift_lut[16];
-extern const uint8_t ascii_to_altgr_lut[16];
-extern const uint8_t ascii_to_dead_lut[16];
-extern const uint8_t ascii_to_keycode_lut[128];
-
-// clang-format off
-#define KCLUT_ENTRY(a, b, c, d, e, f, g, h) \
- ( ((a) ? 1 : 0) << 0 \
- | ((b) ? 1 : 0) << 1 \
- | ((c) ? 1 : 0) << 2 \
- | ((d) ? 1 : 0) << 3 \
- | ((e) ? 1 : 0) << 4 \
- | ((f) ? 1 : 0) << 5 \
- | ((g) ? 1 : 0) << 6 \
- | ((h) ? 1 : 0) << 7 )
-// clang-format on
-
-void send_string(const char *str);
-void send_string_with_delay(const char *str, uint8_t interval);
-void send_string_P(const char *str);
-void send_string_with_delay_P(const char *str, uint8_t interval);
-void send_char(char ascii_code);
-
-void send_dword(uint32_t number);
-void send_word(uint16_t number);
-void send_byte(uint8_t number);
-void send_nibble(uint8_t number);
-
-void tap_random_base64(void);
diff --git a/quantum/send_string.c b/quantum/send_string/send_string.c
index 0de12ba12d..818a52f6dc 100644
--- a/quantum/send_string.c
+++ b/quantum/send_string/send_string.c
@@ -142,40 +142,36 @@ __attribute__((weak)) const uint8_t ascii_to_keycode_lut[128] PROGMEM = {
// Note: we bit-pack in "reverse" order to optimize loading
#define PGM_LOADBIT(mem, pos) ((pgm_read_byte(&((mem)[(pos) / 8])) >> ((pos) % 8)) & 0x01)
-void send_string(const char *str) {
- send_string_with_delay(str, 0);
+void send_string(const char *string) {
+ send_string_with_delay(string, 0);
}
-void send_string_P(const char *str) {
- send_string_with_delay_P(str, 0);
-}
-
-void send_string_with_delay(const char *str, uint8_t interval) {
+void send_string_with_delay(const char *string, uint8_t interval) {
while (1) {
- char ascii_code = *str;
+ char ascii_code = *string;
if (!ascii_code) break;
if (ascii_code == SS_QMK_PREFIX) {
- ascii_code = *(++str);
+ ascii_code = *(++string);
if (ascii_code == SS_TAP_CODE) {
// tap
- uint8_t keycode = *(++str);
+ uint8_t keycode = *(++string);
tap_code(keycode);
} else if (ascii_code == SS_DOWN_CODE) {
// down
- uint8_t keycode = *(++str);
+ uint8_t keycode = *(++string);
register_code(keycode);
} else if (ascii_code == SS_UP_CODE) {
// up
- uint8_t keycode = *(++str);
+ uint8_t keycode = *(++string);
unregister_code(keycode);
} else if (ascii_code == SS_DELAY_CODE) {
// delay
int ms = 0;
- uint8_t keycode = *(++str);
+ uint8_t keycode = *(++string);
while (isdigit(keycode)) {
ms *= 10;
ms += keycode - '0';
- keycode = *(++str);
+ keycode = *(++string);
}
while (ms--)
wait_ms(1);
@@ -183,50 +179,7 @@ void send_string_with_delay(const char *str, uint8_t interval) {
} else {
send_char(ascii_code);
}
- ++str;
- // interval
- {
- uint8_t ms = interval;
- while (ms--)
- wait_ms(1);
- }
- }
-}
-
-void send_string_with_delay_P(const char *str, uint8_t interval) {
- while (1) {
- char ascii_code = pgm_read_byte(str);
- if (!ascii_code) break;
- if (ascii_code == SS_QMK_PREFIX) {
- ascii_code = pgm_read_byte(++str);
- if (ascii_code == SS_TAP_CODE) {
- // tap
- uint8_t keycode = pgm_read_byte(++str);
- tap_code(keycode);
- } else if (ascii_code == SS_DOWN_CODE) {
- // down
- uint8_t keycode = pgm_read_byte(++str);
- register_code(keycode);
- } else if (ascii_code == SS_UP_CODE) {
- // up
- uint8_t keycode = pgm_read_byte(++str);
- unregister_code(keycode);
- } else if (ascii_code == SS_DELAY_CODE) {
- // delay
- int ms = 0;
- uint8_t keycode = pgm_read_byte(++str);
- while (isdigit(keycode)) {
- ms *= 10;
- ms += keycode - '0';
- keycode = pgm_read_byte(++str);
- }
- while (ms--)
- wait_ms(1);
- }
- } else {
- send_char(ascii_code);
- }
- ++str;
+ ++string;
// interval
{
uint8_t ms = interval;
@@ -250,17 +203,17 @@ void send_char(char ascii_code) {
bool is_dead = PGM_LOADBIT(ascii_to_dead_lut, (uint8_t)ascii_code);
if (is_shifted) {
- register_code(KC_LSFT);
+ register_code(KC_LEFT_SHIFT);
}
if (is_altgred) {
- register_code(KC_RALT);
+ register_code(KC_RIGHT_ALT);
}
tap_code(keycode);
if (is_altgred) {
- unregister_code(KC_RALT);
+ unregister_code(KC_RIGHT_ALT);
}
if (is_shifted) {
- unregister_code(KC_LSFT);
+ unregister_code(KC_LEFT_SHIFT);
}
if (is_dead) {
tap_code(KC_SPACE);
@@ -320,3 +273,52 @@ void tap_random_base64(void) {
break;
}
}
+
+#if defined(__AVR__)
+void send_string_P(const char *string) {
+ send_string_with_delay_P(string, 0);
+}
+
+void send_string_with_delay_P(const char *string, uint8_t interval) {
+ while (1) {
+ char ascii_code = pgm_read_byte(string);
+ if (!ascii_code) break;
+ if (ascii_code == SS_QMK_PREFIX) {
+ ascii_code = pgm_read_byte(++string);
+ if (ascii_code == SS_TAP_CODE) {
+ // tap
+ uint8_t keycode = pgm_read_byte(++string);
+ tap_code(keycode);
+ } else if (ascii_code == SS_DOWN_CODE) {
+ // down
+ uint8_t keycode = pgm_read_byte(++string);
+ register_code(keycode);
+ } else if (ascii_code == SS_UP_CODE) {
+ // up
+ uint8_t keycode = pgm_read_byte(++string);
+ unregister_code(keycode);
+ } else if (ascii_code == SS_DELAY_CODE) {
+ // delay
+ int ms = 0;
+ uint8_t keycode = pgm_read_byte(++string);
+ while (isdigit(keycode)) {
+ ms *= 10;
+ ms += keycode - '0';
+ keycode = pgm_read_byte(++string);
+ }
+ while (ms--)
+ wait_ms(1);
+ }
+ } else {
+ send_char(ascii_code);
+ }
+ ++string;
+ // interval
+ {
+ uint8_t ms = interval;
+ while (ms--)
+ wait_ms(1);
+ }
+ }
+}
+#endif
diff --git a/quantum/send_string/send_string.h b/quantum/send_string/send_string.h
new file mode 100644
index 0000000000..4eb55b88dc
--- /dev/null
+++ b/quantum/send_string/send_string.h
@@ -0,0 +1,152 @@
+/* Copyright 2021
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \defgroup send_string
+ *
+ * Send String API. These functions allow you to create macros by typing out sequences of keystrokes.
+ * \{
+ */
+
+#include <stdint.h>
+
+#include "progmem.h"
+#include "send_string_keycodes.h"
+
+// Look-Up Tables (LUTs) to convert ASCII character to keycode sequence.
+extern const uint8_t ascii_to_shift_lut[16];
+extern const uint8_t ascii_to_altgr_lut[16];
+extern const uint8_t ascii_to_dead_lut[16];
+extern const uint8_t ascii_to_keycode_lut[128];
+
+// clang-format off
+#define KCLUT_ENTRY(a, b, c, d, e, f, g, h) \
+ ( ((a) ? 1 : 0) << 0 \
+ | ((b) ? 1 : 0) << 1 \
+ | ((c) ? 1 : 0) << 2 \
+ | ((d) ? 1 : 0) << 3 \
+ | ((e) ? 1 : 0) << 4 \
+ | ((f) ? 1 : 0) << 5 \
+ | ((g) ? 1 : 0) << 6 \
+ | ((h) ? 1 : 0) << 7 )
+// clang-format on
+
+/**
+ * \brief Type out a string of ASCII characters.
+ *
+ * This function simply calls `send_string_with_delay(string, 0)`.
+ *
+ * Most keycodes from the basic keycode range are also supported by way of a special sequence - see `send_string_keycodes.h`.
+ *
+ * \param string The string to type out.
+ */
+void send_string(const char *string);
+
+/**
+ * \brief Type out a string of ASCII characters, with a delay between each character.
+ *
+ * \param string The string to type out.
+ * \param interval The amount of time, in milliseconds, to wait before typing the next character.
+ */
+void send_string_with_delay(const char *string, uint8_t interval);
+
+/**
+ * \brief Type out an ASCII character.
+ *
+ * \param ascii_code The character to type.
+ */
+void send_char(char ascii_code);
+
+/**
+ * \brief Type out an eight digit (unsigned 32-bit) hexadecimal value.
+ *
+ * The format is `[0-9a-f]{8}`, eg. `00000000` through `ffffffff`.
+ *
+ * \param number The value to type, from 0 to 4,294,967,295.
+ */
+void send_dword(uint32_t number);
+
+/**
+ * \brief Type out a four digit (unsigned 16-bit) hexadecimal value.
+ *
+ * The format is `[0-9a-f]{4}`, eg. `0000` through `ffff`.
+ *
+ * \param number The value to type, from 0 to 65,535.
+ */
+void send_word(uint16_t number);
+
+/**
+ * \brief Type out a two digit (8-bit) hexadecimal value.
+ *
+ * The format is `[0-9a-f]{2}`, eg. `00` through `ff`.
+ *
+ * \param number The value to type, from 0 to 255.
+ */
+void send_byte(uint8_t number);
+
+/**
+ * \brief Type out a single hexadecimal digit.
+ *
+ * The format is `[0-9a-f]{1}`, eg. `0` through `f`.
+ *
+ * \param number The value to type, from 0 to 15.
+ */
+void send_nibble(uint8_t number);
+
+/**
+ * \brief Type a pseudorandom character from the set `A-Z`, `a-z`, `0-9`, `+` and `/`.
+ */
+void tap_random_base64(void);
+
+#if defined(__AVR__) || defined(__DOXYGEN__)
+/**
+ * \brief Type out a PROGMEM string of ASCII characters.
+ *
+ * On ARM devices, this function is simply an alias for send_string_with_delay(string, 0).
+ *
+ * \param string The string to type out.
+ */
+void send_string_P(const char *string);
+
+/**
+ * \brief Type out a PROGMEM string of ASCII characters, with a delay between each character.
+ *
+ * On ARM devices, this function is simply an alias for send_string_with_delay(string, interval).
+ *
+ * \param string The string to type out.
+ * \param interval The amount of time, in milliseconds, to wait before typing the next character.
+ */
+void send_string_with_delay_P(const char *string, uint8_t interval);
+#else
+# define send_string_P(string) send_string_with_delay(string, 0)
+# define send_string_with_delay_P(string, interval) send_string_with_delay(string, interval)
+#endif
+
+/**
+ * \brief Shortcut macro for send_string_with_delay_P(PSTR(string), 0).
+ *
+ * On ARM devices, this define evaluates to send_string_with_delay(string, 0).
+ */
+#define SEND_STRING(string) send_string_with_delay_P(PSTR(string), 0)
+
+/**
+ * \brief Shortcut macro for send_string_with_delay_P(PSTR(string), interval).
+ *
+ * On ARM devices, this define evaluates to send_string_with_delay(string, interval).
+ */
+#define SEND_STRING_DELAY(string, interval) send_string_with_delay_P(PSTR(string), interval)
+
+/** \} */
diff --git a/quantum/send_string/send_string_keycodes.h b/quantum/send_string/send_string_keycodes.h
new file mode 100644
index 0000000000..7017e03d5a
--- /dev/null
+++ b/quantum/send_string/send_string_keycodes.h
@@ -0,0 +1,434 @@
+/* Copyright 2019
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+// clang-format off
+
+/* Punctuation */
+#define X_ENT X_ENTER
+#define X_ESC X_ESCAPE
+#define X_BSPC X_BSPACE
+#define X_SPC X_SPACE
+#define X_MINS X_MINUS
+#define X_EQL X_EQUAL
+#define X_LBRC X_LBRACKET
+#define X_RBRC X_RBRACKET
+#define X_BSLS X_BSLASH
+#define X_NUHS X_NONUS_HASH
+#define X_SCLN X_SCOLON
+#define X_QUOT X_QUOTE
+#define X_GRV X_GRAVE
+#define X_COMM X_COMMA
+#define X_SLSH X_SLASH
+#define X_NUBS X_NONUS_BSLASH
+
+/* Lock Keys */
+#define X_CLCK X_CAPSLOCK
+#define X_CAPS X_CAPSLOCK
+#define X_SLCK X_SCROLLLOCK
+#define X_NLCK X_NUMLOCK
+#define X_LCAP X_LOCKING_CAPS
+#define X_LNUM X_LOCKING_NUM
+#define X_LSCR X_LOCKING_SCROLL
+
+/* Commands */
+#define X_PSCR X_PSCREEN
+#define X_PAUS X_PAUSE
+#define X_BRK X_PAUSE
+#define X_INS X_INSERT
+#define X_DEL X_DELETE
+#define X_PGDN X_PGDOWN
+#define X_RGHT X_RIGHT
+#define X_APP X_APPLICATION
+#define X_EXEC X_EXECUTE
+#define X_SLCT X_SELECT
+#define X_AGIN X_AGAIN
+#define X_PSTE X_PASTE
+#define X_ERAS X_ALT_ERASE
+#define X_CLR X_CLEAR
+
+/* Keypad */
+#define X_PSLS X_KP_SLASH
+#define X_PAST X_KP_ASTERISK
+#define X_PMNS X_KP_MINUS
+#define X_PPLS X_KP_PLUS
+#define X_PENT X_KP_ENTER
+#define X_P1 X_KP_1
+#define X_P2 X_KP_2
+#define X_P3 X_KP_3
+#define X_P4 X_KP_4
+#define X_P5 X_KP_5
+#define X_P6 X_KP_6
+#define X_P7 X_KP_7
+#define X_P8 X_KP_8
+#define X_P9 X_KP_9
+#define X_P0 X_KP_0
+#define X_PDOT X_KP_DOT
+#define X_PEQL X_KP_EQUAL
+#define X_PCMM X_KP_COMMA
+
+/* Japanese specific */
+#define X_ZKHK X_GRAVE
+#define X_RO X_INT1
+#define X_KANA X_INT2
+#define X_JYEN X_INT3
+#define X_HENK X_INT4
+#define X_MHEN X_INT5
+
+/* Korean specific */
+#define X_HAEN X_LANG1
+#define X_HANJ X_LANG2
+
+/* Modifiers */
+#define X_LCTL X_LCTRL
+#define X_LSFT X_LSHIFT
+#define X_LOPT X_LALT
+#define X_LCMD X_LGUI
+#define X_LWIN X_LGUI
+#define X_RCTL X_RCTRL
+#define X_RSFT X_RSHIFT
+#define X_ALGR X_RALT
+#define X_ROPT X_RALT
+#define X_RCMD X_RGUI
+#define X_RWIN X_RGUI
+
+/* Generic Desktop Page (0x01) */
+#define X_PWR X_SYSTEM_POWER
+#define X_SLEP X_SYSTEM_SLEEP
+#define X_WAKE X_SYSTEM_WAKE
+
+/* Consumer Page (0x0C) */
+#define X_MUTE X_AUDIO_MUTE
+#define X_VOLU X_AUDIO_VOL_UP
+#define X_VOLD X_AUDIO_VOL_DOWN
+#define X_MNXT X_MEDIA_NEXT_TRACK
+#define X_MPRV X_MEDIA_PREV_TRACK
+#define X_MSTP X_MEDIA_STOP
+#define X_MPLY X_MEDIA_PLAY_PAUSE
+#define X_MSEL X_MEDIA_SELECT
+#define X_EJCT X_MEDIA_EJECT
+#define X_CALC X_CALCULATOR
+#define X_MYCM X_MY_COMPUTER
+#define X_WSCH X_WWW_SEARCH
+#define X_WHOM X_WWW_HOME
+#define X_WBAK X_WWW_BACK
+#define X_WFWD X_WWW_FORWARD
+#define X_WSTP X_WWW_STOP
+#define X_WREF X_WWW_REFRESH
+#define X_WFAV X_WWW_FAVORITES
+#define X_MFFD X_MEDIA_FAST_FORWARD
+#define X_MRWD X_MEDIA_REWIND
+#define X_BRIU X_BRIGHTNESS_UP
+#define X_BRID X_BRIGHTNESS_DOWN
+
+/* System Specific */
+#define X_BRMU X_PAUSE
+#define X_BRMD X_SCROLLLOCK
+
+/* Mouse Keys */
+#define X_MS_U X_MS_UP
+#define X_MS_D X_MS_DOWN
+#define X_MS_L X_MS_LEFT
+#define X_MS_R X_MS_RIGHT
+#define X_BTN1 X_MS_BTN1
+#define X_BTN2 X_MS_BTN2
+#define X_BTN3 X_MS_BTN3
+#define X_BTN4 X_MS_BTN4
+#define X_BTN5 X_MS_BTN5
+#define X_WH_U X_MS_WH_UP
+#define X_WH_D X_MS_WH_DOWN
+#define X_WH_L X_MS_WH_LEFT
+#define X_WH_R X_MS_WH_RIGHT
+#define X_ACL0 X_MS_ACCEL0
+#define X_ACL1 X_MS_ACCEL1
+#define X_ACL2 X_MS_ACCEL2
+
+/* Keyboard/Keypad Page (0x07) */
+#define X_A 04
+#define X_B 05
+#define X_C 06
+#define X_D 07
+#define X_E 08
+#define X_F 09
+#define X_G 0a
+#define X_H 0b
+#define X_I 0c
+#define X_J 0d
+#define X_K 0e
+#define X_L 0f
+#define X_M 10
+#define X_N 11
+#define X_O 12
+#define X_P 13
+#define X_Q 14
+#define X_R 15
+#define X_S 16
+#define X_T 17
+#define X_U 18
+#define X_V 19
+#define X_W 1a
+#define X_X 1b
+#define X_Y 1c
+#define X_Z 1d
+#define X_1 1e
+#define X_2 1f
+#define X_3 20
+#define X_4 21
+#define X_5 22
+#define X_6 23
+#define X_7 24
+#define X_8 25
+#define X_9 26
+#define X_0 27
+#define X_ENTER 28
+#define X_ESCAPE 29
+#define X_BSPACE 2a
+#define X_TAB 2b
+#define X_SPACE 2c
+#define X_MINUS 2d
+#define X_EQUAL 2e
+#define X_LBRACKET 2f
+#define X_RBRACKET 30
+#define X_BSLASH 31
+#define X_NONUS_HASH 32
+#define X_SCOLON 33
+#define X_QUOTE 34
+#define X_GRAVE 35
+#define X_COMMA 36
+#define X_DOT 37
+#define X_SLASH 38
+#define X_CAPSLOCK 39
+#define X_F1 3a
+#define X_F2 3b
+#define X_F3 3c
+#define X_F4 3d
+#define X_F5 3e
+#define X_F6 3f
+#define X_F7 40
+#define X_F8 41
+#define X_F9 42
+#define X_F10 43
+#define X_F11 44
+#define X_F12 45
+#define X_PSCREEN 46
+#define X_SCROLLLOCK 47
+#define X_PAUSE 48
+#define X_INSERT 49
+#define X_HOME 4a
+#define X_PGUP 4b
+#define X_DELETE 4c
+#define X_END 4d
+#define X_PGDOWN 4e
+#define X_RIGHT 4f
+#define X_LEFT 50
+#define X_DOWN 51
+#define X_UP 52
+#define X_NUMLOCK 53
+#define X_KP_SLASH 54
+#define X_KP_ASTERISK 55
+#define X_KP_MINUS 56
+#define X_KP_PLUS 57
+#define X_KP_ENTER 58
+#define X_KP_1 59
+#define X_KP_2 5a
+#define X_KP_3 5b
+#define X_KP_4 5c
+#define X_KP_5 5d
+#define X_KP_6 5e
+#define X_KP_7 5f
+#define X_KP_8 60
+#define X_KP_9 61
+#define X_KP_0 62
+#define X_KP_DOT 63
+#define X_NONUS_BSLASH 64
+#define X_APPLICATION 65
+#define X_POWER 66
+#define X_KP_EQUAL 67
+#define X_F13 68
+#define X_F14 69
+#define X_F15 6a
+#define X_F16 6b
+#define X_F17 6c
+#define X_F18 6d
+#define X_F19 6e
+#define X_F20 6f
+#define X_F21 70
+#define X_F22 71
+#define X_F23 72
+#define X_F24 73
+#define X_EXECUTE 74
+#define X_HELP 75
+#define X_MENU 76
+#define X_SELECT 77
+#define X_STOP 78
+#define X_AGAIN 79
+#define X_UNDO 7a
+#define X_CUT 7b
+#define X_COPY 7c
+#define X_PASTE 7d
+#define X_FIND 7e
+#define X__MUTE 7f
+#define X__VOLUP 80
+#define X__VOLDOWN 81
+#define X_LOCKING_CAPS 82
+#define X_LOCKING_NUM 83
+#define X_LOCKING_SCROLL 84
+#define X_KP_COMMA 85
+#define X_KP_EQUAL_AS400 86
+#define X_INT1 87
+#define X_INT2 88
+#define X_INT3 89
+#define X_INT4 8a
+#define X_INT5 8b
+#define X_INT6 8c
+#define X_INT7 8d
+#define X_INT8 8e
+#define X_INT9 8f
+#define X_LANG1 90
+#define X_LANG2 91
+#define X_LANG3 92
+#define X_LANG4 93
+#define X_LANG5 94
+#define X_LANG6 95
+#define X_LANG7 96
+#define X_LANG8 97
+#define X_LANG9 98
+#define X_ALT_ERASE 99
+#define X_SYSREQ 9a
+#define X_CANCEL 9b
+#define X_CLEAR 9c
+#define X_PRIOR 9d
+#define X_RETURN 9e
+#define X_SEPARATOR 9f
+#define X_OUT a0
+#define X_OPER a1
+#define X_CLEAR_AGAIN a2
+#define X_CRSEL a3
+#define X_EXSEL a4
+
+/* Modifiers */
+#define X_LCTRL e0
+#define X_LSHIFT e1
+#define X_LALT e2
+#define X_LGUI e3
+#define X_RCTRL e4
+#define X_RSHIFT e5
+#define X_RALT e6
+#define X_RGUI e7
+
+/* Media and Function keys */
+/* Generic Desktop Page (0x01) */
+#define X_SYSTEM_POWER a5
+#define X_SYSTEM_SLEEP a6
+#define X_SYSTEM_WAKE a7
+
+/* Consumer Page (0x0C) */
+#define X_AUDIO_MUTE a8
+#define X_AUDIO_VOL_UP a9
+#define X_AUDIO_VOL_DOWN aa
+#define X_MEDIA_NEXT_TRACK ab
+#define X_MEDIA_PREV_TRACK ac
+#define X_MEDIA_STOP ad
+#define X_MEDIA_PLAY_PAUSE ae
+#define X_MEDIA_SELECT af
+#define X_MEDIA_EJECT b0
+#define X_MAIL b1
+#define X_CALCULATOR b2
+#define X_MY_COMPUTER b3
+#define X_WWW_SEARCH b4
+#define X_WWW_HOME b5
+#define X_WWW_BACK b6
+#define X_WWW_FORWARD b7
+#define X_WWW_STOP b8
+#define X_WWW_REFRESH b9
+#define X_WWW_FAVORITES ba
+#define X_MEDIA_FAST_FORWARD bb
+#define X_MEDIA_REWIND bc
+#define X_BRIGHTNESS_UP bd
+#define X_BRIGHTNESS_DOWN be
+
+/* Mouse Buttons (unallocated range in HID spec) */
+#ifdef VIA_ENABLE
+#define X_MS_UP f0
+#define X_MS_DOWN f1
+#define X_MS_LEFT f2
+#define X_MS_RIGHT f3
+#define X_MS_BTN1 f4
+#define X_MS_BTN2 f5
+#define X_MS_BTN3 f6
+#define X_MS_BTN4 f7
+#define X_MS_BTN5 f8
+#define X_MS_BTN6 f8
+#define X_MS_BTN7 f8
+#define X_MS_BTN8 f8
+#else
+#define X_MS_UP ed
+#define X_MS_DOWN ee
+#define X_MS_LEFT ef
+#define X_MS_RIGHT f0
+#define X_MS_BTN1 f1
+#define X_MS_BTN2 f2
+#define X_MS_BTN3 f3
+#define X_MS_BTN4 f4
+#define X_MS_BTN5 f5
+#define X_MS_BTN6 f6
+#define X_MS_BTN7 f7
+#define X_MS_BTN8 f8
+#endif
+#define X_MS_WH_UP f9
+#define X_MS_WH_DOWN fa
+#define X_MS_WH_LEFT fb
+#define X_MS_WH_RIGHT fc
+#define X_MS_ACCEL0 fd
+#define X_MS_ACCEL1 fe
+#define X_MS_ACCEL2 ff
+
+// Send string macros
+#define STRINGIZE(z) #z
+#define ADD_SLASH_X(y) STRINGIZE(\x##y)
+#define SYMBOL_STR(x) ADD_SLASH_X(x)
+
+#define SS_QMK_PREFIX 1
+
+#define SS_TAP_CODE 1
+#define SS_DOWN_CODE 2
+#define SS_UP_CODE 3
+#define SS_DELAY_CODE 4
+
+#define SS_TAP(keycode) "\1\1" SYMBOL_STR(keycode)
+#define SS_DOWN(keycode) "\1\2" SYMBOL_STR(keycode)
+#define SS_UP(keycode) "\1\3" SYMBOL_STR(keycode)
+#define SS_DELAY(msecs) "\1\4" STRINGIZE(msecs) "|"
+
+// `string` arguments must not be parenthesized
+#define SS_LCTL(string) SS_DOWN(X_LCTL) string SS_UP(X_LCTL)
+#define SS_LSFT(string) SS_DOWN(X_LSFT) string SS_UP(X_LSFT)
+#define SS_LALT(string) SS_DOWN(X_LALT) string SS_UP(X_LALT)
+#define SS_LGUI(string) SS_DOWN(X_LGUI) string SS_UP(X_LGUI)
+#define SS_LCMD(string) SS_LGUI(string)
+#define SS_LWIN(string) SS_LGUI(string)
+
+#define SS_RCTL(string) SS_DOWN(X_RCTL) string SS_UP(X_RCTL)
+#define SS_RSFT(string) SS_DOWN(X_RSFT) string SS_UP(X_RSFT)
+#define SS_RALT(string) SS_DOWN(X_RALT) string SS_UP(X_RALT)
+#define SS_RGUI(string) SS_DOWN(X_RGUI) string SS_UP(X_RGUI)
+#define SS_ALGR(string) SS_RALT(string)
+#define SS_RCMD(string) SS_RGUI(string)
+#define SS_RWIN(string) SS_RGUI(string)
+
+// DEPRECATED
+#define SS_LCTRL(string) SS_LCTL(string)
diff --git a/quantum/send_string_keycodes.h b/quantum/send_string_keycodes.h
deleted file mode 100644
index b35bf66b7b..0000000000
--- a/quantum/send_string_keycodes.h
+++ /dev/null
@@ -1,505 +0,0 @@
-/* Copyright 2019
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-// clang-format off
-
-/* Punctuation */
-#define X_ENT X_ENTER
-#define X_ESC X_ESCAPE
-#define X_BSPC X_BACKSPACE
-#define X_SPC X_SPACE
-#define X_MINS X_MINUS
-#define X_EQL X_EQUAL
-#define X_LBRC X_LEFT_BRACKET
-#define X_RBRC X_RIGHT_BRACKET
-#define X_BSLS X_BACKSLASH
-#define X_NUHS X_NONUS_HASH
-#define X_SCLN X_SEMICOLON
-#define X_QUOT X_QUOTE
-#define X_GRV X_GRAVE
-#define X_COMM X_COMMA
-#define X_SLSH X_SLASH
-#define X_NUBS X_NONUS_BACKSLASH
-
-/* Lock Keys */
-#define X_CAPS X_CAPS_LOCK
-#define X_SCRL X_SCROLL_LOCK
-#define X_NUM X_NUM_LOCK
-#define X_LCAP X_LOCKING_CAPS_LOCK
-#define X_LNUM X_LOCKING_NUM_LOCK
-#define X_LSCR X_LOCKING_SCROLL_LOCK
-
-/* Commands */
-#define X_PSCR X_PRINT_SCREEN
-#define X_PAUS X_PAUSE
-#define X_BRK X_PAUSE
-#define X_INS X_INSERT
-#define X_PGUP X_PAGE_UP
-#define X_DEL X_DELETE
-#define X_PGDN X_PAGE_DOWN
-#define X_RGHT X_RIGHT
-#define X_APP X_APPLICATION
-#define X_EXEC X_EXECUTE
-#define X_SLCT X_SELECT
-#define X_AGIN X_AGAIN
-#define X_PSTE X_PASTE
-#define X_ERAS X_ALTERNATE_ERASE
-#define X_SYRQ X_SYSTEM_REQUEST
-#define X_CNCL X_CANCEL
-#define X_CLR X_CLEAR
-#define X_PRIR X_PRIOR
-#define X_RETN X_RETURN
-#define X_SEPR X_SEPARATOR
-#define X_CLAG X_CLEAR_AGAIN
-#define X_CRSL X_CRSEL
-#define X_EXSL X_EXSEL
-
-/* Keypad */
-#define X_PSLS X_KP_SLASH
-#define X_PAST X_KP_ASTERISK
-#define X_PMNS X_KP_MINUS
-#define X_PPLS X_KP_PLUS
-#define X_PENT X_KP_ENTER
-#define X_P1 X_KP_1
-#define X_P2 X_KP_2
-#define X_P3 X_KP_3
-#define X_P4 X_KP_4
-#define X_P5 X_KP_5
-#define X_P6 X_KP_6
-#define X_P7 X_KP_7
-#define X_P8 X_KP_8
-#define X_P9 X_KP_9
-#define X_P0 X_KP_0
-#define X_PDOT X_KP_DOT
-#define X_PEQL X_KP_EQUAL
-#define X_PCMM X_KP_COMMA
-
-/* Language Specific */
-#define X_INT1 X_INTERNATIONAL_1
-#define X_INT2 X_INTERNATIONAL_2
-#define X_INT3 X_INTERNATIONAL_3
-#define X_INT4 X_INTERNATIONAL_4
-#define X_INT5 X_INTERNATIONAL_5
-#define X_INT6 X_INTERNATIONAL_6
-#define X_INT7 X_INTERNATIONAL_7
-#define X_INT8 X_INTERNATIONAL_8
-#define X_INT9 X_INTERNATIONAL_9
-#define X_LNG1 X_LANGUAGE_1
-#define X_LNG2 X_LANGUAGE_2
-#define X_LNG3 X_LANGUAGE_3
-#define X_LNG4 X_LANGUAGE_4
-#define X_LNG5 X_LANGUAGE_5
-#define X_LNG6 X_LANGUAGE_6
-#define X_LNG7 X_LANGUAGE_7
-#define X_LNG8 X_LANGUAGE_8
-#define X_LNG9 X_LANGUAGE_9
-
-/* Modifiers */
-#define X_LCTL X_LEFT_CTRL
-#define X_LSFT X_LEFT_SHIFT
-#define X_LALT X_LEFT_ALT
-#define X_LOPT X_LEFT_ALT
-#define X_LGUI X_LEFT_GUI
-#define X_LCMD X_LEFT_GUI
-#define X_LWIN X_LEFT_GUI
-#define X_RCTL X_RIGHT_CTRL
-#define X_RSFT X_RIGHT_SHIFT
-#define X_RALT X_RIGHT_ALT
-#define X_ALGR X_RIGHT_ALT
-#define X_ROPT X_RIGHT_ALT
-#define X_RGUI X_RIGHT_GUI
-#define X_RCMD X_RIGHT_GUI
-#define X_RWIN X_RIGHT_GUI
-
-/* Generic Desktop Page (0x01) */
-#define X_PWR X_SYSTEM_POWER
-#define X_SLEP X_SYSTEM_SLEEP
-#define X_WAKE X_SYSTEM_WAKE
-
-/* Consumer Page (0x0C) */
-#define X_MUTE X_AUDIO_MUTE
-#define X_VOLU X_AUDIO_VOL_UP
-#define X_VOLD X_AUDIO_VOL_DOWN
-#define X_MNXT X_MEDIA_NEXT_TRACK
-#define X_MPRV X_MEDIA_PREV_TRACK
-#define X_MSTP X_MEDIA_STOP
-#define X_MPLY X_MEDIA_PLAY_PAUSE
-#define X_MSEL X_MEDIA_SELECT
-#define X_EJCT X_MEDIA_EJECT
-#define X_CALC X_CALCULATOR
-#define X_MYCM X_MY_COMPUTER
-#define X_WSCH X_WWW_SEARCH
-#define X_WHOM X_WWW_HOME
-#define X_WBAK X_WWW_BACK
-#define X_WFWD X_WWW_FORWARD
-#define X_WSTP X_WWW_STOP
-#define X_WREF X_WWW_REFRESH
-#define X_WFAV X_WWW_FAVORITES
-#define X_MFFD X_MEDIA_FAST_FORWARD
-#define X_MRWD X_MEDIA_REWIND
-#define X_BRIU X_BRIGHTNESS_UP
-#define X_BRID X_BRIGHTNESS_DOWN
-
-/* System Specific */
-#define X_BRMU X_PAUSE
-#define X_BRMD X_SCROLL_LOCK
-
-/* Mouse Keys */
-#define X_MS_U X_MS_UP
-#define X_MS_D X_MS_DOWN
-#define X_MS_L X_MS_LEFT
-#define X_MS_R X_MS_RIGHT
-#define X_BTN1 X_MS_BTN1
-#define X_BTN2 X_MS_BTN2
-#define X_BTN3 X_MS_BTN3
-#define X_BTN4 X_MS_BTN4
-#define X_BTN5 X_MS_BTN5
-#define X_BTN6 X_MS_BTN6
-#define X_BTN7 X_MS_BTN7
-#define X_BTN8 X_MS_BTN8
-#define X_WH_U X_MS_WH_UP
-#define X_WH_D X_MS_WH_DOWN
-#define X_WH_L X_MS_WH_LEFT
-#define X_WH_R X_MS_WH_RIGHT
-#define X_ACL0 X_MS_ACCEL0
-#define X_ACL1 X_MS_ACCEL1
-#define X_ACL2 X_MS_ACCEL2
-
-/* Keyboard/Keypad Page (0x07) */
-#define X_A 04
-#define X_B 05
-#define X_C 06
-#define X_D 07
-#define X_E 08
-#define X_F 09
-#define X_G 0a
-#define X_H 0b
-#define X_I 0c
-#define X_J 0d
-#define X_K 0e
-#define X_L 0f
-#define X_M 10
-#define X_N 11
-#define X_O 12
-#define X_P 13
-#define X_Q 14
-#define X_R 15
-#define X_S 16
-#define X_T 17
-#define X_U 18
-#define X_V 19
-#define X_W 1a
-#define X_X 1b
-#define X_Y 1c
-#define X_Z 1d
-#define X_1 1e
-#define X_2 1f
-#define X_3 20
-#define X_4 21
-#define X_5 22
-#define X_6 23
-#define X_7 24
-#define X_8 25
-#define X_9 26
-#define X_0 27
-#define X_ENTER 28
-#define X_ESCAPE 29
-#define X_BACKSPACE 2a
-#define X_TAB 2b
-#define X_SPACE 2c
-#define X_MINUS 2d
-#define X_EQUAL 2e
-#define X_LEFT_BRACKET 2f
-#define X_RIGHT_BRACKET 30
-#define X_BACKSLASH 31
-#define X_NONUS_HASH 32
-#define X_SEMICOLON 33
-#define X_QUOTE 34
-#define X_GRAVE 35
-#define X_COMMA 36
-#define X_DOT 37
-#define X_SLASH 38
-#define X_CAPS_LOCK 39
-#define X_F1 3a
-#define X_F2 3b
-#define X_F3 3c
-#define X_F4 3d
-#define X_F5 3e
-#define X_F6 3f
-#define X_F7 40
-#define X_F8 41
-#define X_F9 42
-#define X_F10 43
-#define X_F11 44
-#define X_F12 45
-#define X_PRINT_SCREEN 46
-#define X_SCROLL_LOCK 47
-#define X_PAUSE 48
-#define X_INSERT 49
-#define X_HOME 4a
-#define X_PAGE_UP 4b
-#define X_DELETE 4c
-#define X_END 4d
-#define X_PAGE_DOWN 4e
-#define X_RIGHT 4f
-#define X_LEFT 50
-#define X_DOWN 51
-#define X_UP 52
-#define X_NUM_LOCK 53
-#define X_KP_SLASH 54
-#define X_KP_ASTERISK 55
-#define X_KP_MINUS 56
-#define X_KP_PLUS 57
-#define X_KP_ENTER 58
-#define X_KP_1 59
-#define X_KP_2 5a
-#define X_KP_3 5b
-#define X_KP_4 5c
-#define X_KP_5 5d
-#define X_KP_6 5e
-#define X_KP_7 5f
-#define X_KP_8 60
-#define X_KP_9 61
-#define X_KP_0 62
-#define X_KP_DOT 63
-#define X_NONUS_BACKSLASH 64
-#define X_APPLICATION 65
-#define X_KB_POWER 66
-#define X_KP_EQUAL 67
-#define X_F13 68
-#define X_F14 69
-#define X_F15 6a
-#define X_F16 6b
-#define X_F17 6c
-#define X_F18 6d
-#define X_F19 6e
-#define X_F20 6f
-#define X_F21 70
-#define X_F22 71
-#define X_F23 72
-#define X_F24 73
-#define X_EXECUTE 74
-#define X_HELP 75
-#define X_MENU 76
-#define X_SELECT 77
-#define X_STOP 78
-#define X_AGAIN 79
-#define X_UNDO 7a
-#define X_CUT 7b
-#define X_COPY 7c
-#define X_PASTE 7d
-#define X_FIND 7e
-#define X_KB_MUTE 7f
-#define X_KB_VOLUME_UP 80
-#define X_KB_VOLUME_DOWN 81
-#define X_LOCKING_CAPS_LOCK 82
-#define X_LOCKING_NUM_LOCK 83
-#define X_LOCKING_SCROLL_LOCK 84
-#define X_KP_COMMA 85
-#define X_KP_EQUAL_AS400 86
-#define X_INTERNATIONAL_1 87
-#define X_INTERNATIONAL_2 88
-#define X_INTERNATIONAL_3 89
-#define X_INTERNATIONAL_4 8a
-#define X_INTERNATIONAL_5 8b
-#define X_INTERNATIONAL_6 8c
-#define X_INTERNATIONAL_7 8d
-#define X_INTERNATIONAL_8 8e
-#define X_INTERNATIONAL_9 8f
-#define X_LANGUAGE_1 90
-#define X_LANGUAGE_2 91
-#define X_LANGUAGE_3 92
-#define X_LANGUAGE_4 93
-#define X_LANGUAGE_5 94
-#define X_LANGUAGE_6 95
-#define X_LANGUAGE_7 96
-#define X_LANGUAGE_8 97
-#define X_LANGUAGE_9 98
-#define X_ALTERNATE_ERASE 99
-#define X_SYSTEM_REQUEST 9a
-#define X_CANCEL 9b
-#define X_CLEAR 9c
-#define X_PRIOR 9d
-#define X_RETURN 9e
-#define X_SEPARATOR 9f
-#define X_OUT a0
-#define X_OPER a1
-#define X_CLEAR_AGAIN a2
-#define X_CRSEL a3
-#define X_EXSEL a4
-
-/* Modifiers */
-#define X_LEFT_CTRL e0
-#define X_LEFT_SHIFT e1
-#define X_LEFT_ALT e2
-#define X_LEFT_GUI e3
-#define X_RIGHT_CTRL e4
-#define X_RIGHT_SHIFT e5
-#define X_RIGHT_ALT e6
-#define X_RIGHT_GUI e7
-
-/* Media and Function keys */
-/* Generic Desktop Page (0x01) */
-#define X_SYSTEM_POWER a5
-#define X_SYSTEM_SLEEP a6
-#define X_SYSTEM_WAKE a7
-
-/* Consumer Page (0x0C) */
-#define X_AUDIO_MUTE a8
-#define X_AUDIO_VOL_UP a9
-#define X_AUDIO_VOL_DOWN aa
-#define X_MEDIA_NEXT_TRACK ab
-#define X_MEDIA_PREV_TRACK ac
-#define X_MEDIA_STOP ad
-#define X_MEDIA_PLAY_PAUSE ae
-#define X_MEDIA_SELECT af
-#define X_MEDIA_EJECT b0
-#define X_MAIL b1
-#define X_CALCULATOR b2
-#define X_MY_COMPUTER b3
-#define X_WWW_SEARCH b4
-#define X_WWW_HOME b5
-#define X_WWW_BACK b6
-#define X_WWW_FORWARD b7
-#define X_WWW_STOP b8
-#define X_WWW_REFRESH b9
-#define X_WWW_FAVORITES ba
-#define X_MEDIA_FAST_FORWARD bb
-#define X_MEDIA_REWIND bc
-#define X_BRIGHTNESS_UP bd
-#define X_BRIGHTNESS_DOWN be
-
-/* Mouse Buttons (unallocated range in HID spec) */
-#ifdef VIA_ENABLE
-#define X_MS_UP f0
-#define X_MS_DOWN f1
-#define X_MS_LEFT f2
-#define X_MS_RIGHT f3
-#define X_MS_BTN1 f4
-#define X_MS_BTN2 f5
-#define X_MS_BTN3 f6
-#define X_MS_BTN4 f7
-#define X_MS_BTN5 f8
-#define X_MS_BTN6 f8
-#define X_MS_BTN7 f8
-#define X_MS_BTN8 f8
-#else
-#define X_MS_UP ed
-#define X_MS_DOWN ee
-#define X_MS_LEFT ef
-#define X_MS_RIGHT f0
-#define X_MS_BTN1 f1
-#define X_MS_BTN2 f2
-#define X_MS_BTN3 f3
-#define X_MS_BTN4 f4
-#define X_MS_BTN5 f5
-#define X_MS_BTN6 f6
-#define X_MS_BTN7 f7
-#define X_MS_BTN8 f8
-#endif
-#define X_MS_WH_UP f9
-#define X_MS_WH_DOWN fa
-#define X_MS_WH_LEFT fb
-#define X_MS_WH_RIGHT fc
-#define X_MS_ACCEL0 fd
-#define X_MS_ACCEL1 fe
-#define X_MS_ACCEL2 ff
-
-// Send string macros
-#define STRINGIZE(z) #z
-#define ADD_SLASH_X(y) STRINGIZE(\x##y)
-#define SYMBOL_STR(x) ADD_SLASH_X(x)
-
-#define SS_QMK_PREFIX 1
-
-#define SS_TAP_CODE 1
-#define SS_DOWN_CODE 2
-#define SS_UP_CODE 3
-#define SS_DELAY_CODE 4
-
-#define SS_TAP(keycode) "\1\1" SYMBOL_STR(keycode)
-#define SS_DOWN(keycode) "\1\2" SYMBOL_STR(keycode)
-#define SS_UP(keycode) "\1\3" SYMBOL_STR(keycode)
-#define SS_DELAY(msecs) "\1\4" STRINGIZE(msecs) "|"
-
-// `string` arguments must not be parenthesized
-#define SS_LCTL(string) SS_DOWN(X_LCTL) string SS_UP(X_LCTL)
-#define SS_LSFT(string) SS_DOWN(X_LSFT) string SS_UP(X_LSFT)
-#define SS_LALT(string) SS_DOWN(X_LALT) string SS_UP(X_LALT)
-#define SS_LGUI(string) SS_DOWN(X_LGUI) string SS_UP(X_LGUI)
-#define SS_LCMD(string) SS_LGUI(string)
-#define SS_LWIN(string) SS_LGUI(string)
-
-#define SS_RCTL(string) SS_DOWN(X_RCTL) string SS_UP(X_RCTL)
-#define SS_RSFT(string) SS_DOWN(X_RSFT) string SS_UP(X_RSFT)
-#define SS_RALT(string) SS_DOWN(X_RALT) string SS_UP(X_RALT)
-#define SS_RGUI(string) SS_DOWN(X_RGUI) string SS_UP(X_RGUI)
-#define SS_ALGR(string) SS_RALT(string)
-#define SS_RCMD(string) SS_RGUI(string)
-#define SS_RWIN(string) SS_RGUI(string)
-
-// DEPRECATED
-#define X_BSPACE X_BACKSPACE
-#define X_LBRACKET X_LEFT_BRACKET
-#define X_RBRACKET X_RIGHT_BRACKET
-#define X_BSLASH X_BACKSLASH
-#define X_SCOLON X_SEMICOLON
-#define X_CAPSLOCK X_CAPS_LOCK
-#define X_PSCREEN X_PRINT_SCREEN
-#define X_SCROLLLOCK X_SCROLL_LOCK
-#define X_PGDOWN X_PAGE_DOWN
-#define X_NUMLOCK X_NUM_LOCK
-#define X_NONUS_BSLASH X_NONUS_BACKSLASH
-#define X_POWER X_KB_POWER
-#define X__MUTE X_KB_MUTE
-#define X__VOLUP X_KB_VOLUME_UP
-#define X__VOLDOWN X_KB_VOLUME_DOWN
-#define X_LOCKING_CAPS X_LOCKING_CAPS_LOCK
-#define X_LOCKING_NUM X_LOCKING_NUM_LOCK
-#define X_LOCKING_SCROLL X_LOCKING_SCROLL_LOCK
-#define X_LANG1 X_LANGUAGE_1
-#define X_LANG2 X_LANGUAGE_2
-#define X_LANG3 X_LANGUAGE_3
-#define X_LANG4 X_LANGUAGE_4
-#define X_LANG5 X_LANGUAGE_5
-#define X_LANG6 X_LANGUAGE_6
-#define X_LANG7 X_LANGUAGE_7
-#define X_LANG8 X_LANGUAGE_8
-#define X_LANG9 X_LANGUAGE_9
-#define X_ALT_ERASE X_ALTERNATE_ERASE
-#define X_SYSREQ X_SYSTEM_REQUEST
-
-#define X_LCTRL X_LEFT_CTRL
-#define X_LSHIFT X_LEFT_SHIFT
-#define X_RCTRL X_RIGHT_CTRL
-#define X_RSHIFT X_RIGHT_SHIFT
-
-#define X_ZKHK X_GRAVE
-#define X_RO X_INTERNATIONAL_1
-#define X_KANA X_INTERNATIONAL_2
-#define X_JYEN X_INTERNATIONAL_3
-#define X_HENK X_INTERNATIONAL_4
-#define X_MHEN X_INTERNATIONAL_5
-#define X_HAEN X_LANGUAGE_1
-#define X_HANJ X_LANGUAGE_2
-
-#define X_CLCK X_CAPS_LOCK
-#define X_SLCK X_SCROLL_LOCK
-#define X_NLCK X_NUM_LOCK
-
-#define SS_LCTRL(string) SS_LCTL(string)
diff --git a/quantum/split_common/split_util.c b/quantum/split_common/split_util.c
index 7d50adf758..4892b7f8d8 100644
--- a/quantum/split_common/split_util.c
+++ b/quantum/split_common/split_util.c
@@ -57,8 +57,9 @@ static uint8_t connection_errors = 0;
volatile bool isLeftHand = true;
#if defined(SPLIT_USB_DETECT)
+_Static_assert((SPLIT_USB_TIMEOUT / SPLIT_USB_TIMEOUT_POLL) <= UINT16_MAX, "Please lower SPLIT_USB_TIMEOUT and/or increase SPLIT_USB_TIMEOUT_POLL.");
static bool usbIsActive(void) {
- for (uint8_t i = 0; i < (SPLIT_USB_TIMEOUT / SPLIT_USB_TIMEOUT_POLL); i++) {
+ for (uint16_t i = 0; i < (SPLIT_USB_TIMEOUT / SPLIT_USB_TIMEOUT_POLL); i++) {
// This will return true if a USB connection has been established
if (usb_connected_state()) {
return true;
@@ -93,7 +94,6 @@ static uint8_t peek_matrix_intersection(pin_t out_pin, pin_t in_pin) {
__attribute__((weak)) bool is_keyboard_left(void) {
#if defined(SPLIT_HAND_PIN)
// Test pin SPLIT_HAND_PIN for High/Low, if low it's right hand
- setPinInput(SPLIT_HAND_PIN);
# ifdef SPLIT_HAND_PIN_LOW_IS_LEFT
return !readPin(SPLIT_HAND_PIN);
# else
@@ -132,6 +132,14 @@ __attribute__((weak)) bool is_keyboard_master(void) {
// this code runs before the keyboard is fully initialized
void split_pre_init(void) {
+#if defined(SPLIT_HAND_PIN)
+ setPinInput(SPLIT_HAND_PIN);
+ wait_us(100);
+#elif defined(EE_HANDS)
+ if (!eeconfig_is_enabled()) {
+ eeconfig_init();
+ }
+#endif
isLeftHand = is_keyboard_left();
#if defined(RGBLIGHT_ENABLE) && defined(RGBLED_SPLIT)
diff --git a/quantum/split_common/transactions.c b/quantum/split_common/transactions.c
index 9e3df534e3..719068908f 100644
--- a/quantum/split_common/transactions.c
+++ b/quantum/split_common/transactions.c
@@ -694,7 +694,7 @@ split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = {
#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
[PUT_RPC_INFO] = trans_initiator2target_initializer_cb(rpc_info, slave_rpc_info_callback),
[PUT_RPC_REQ_DATA] = trans_initiator2target_initializer(rpc_m2s_buffer),
- [EXECUTE_RPC] = trans_initiator2target_initializer_cb(rpc_info.transaction_id, slave_rpc_exec_callback),
+ [EXECUTE_RPC] = trans_initiator2target_initializer_cb(rpc_info.payload.transaction_id, slave_rpc_exec_callback),
[GET_RPC_RESP_DATA] = trans_target2initiator_initializer(rpc_s2m_buffer),
#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
};
@@ -760,7 +760,8 @@ bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer
if (target2initiator_buffer_size > RPC_S2M_BUFFER_SIZE) return false;
// Prepare the metadata block
- rpc_sync_info_t info = {.transaction_id = transaction_id, .m2s_length = initiator2target_buffer_size, .s2m_length = target2initiator_buffer_size};
+ rpc_sync_info_t info = {.payload = {.transaction_id = transaction_id, .m2s_length = initiator2target_buffer_size, .s2m_length = target2initiator_buffer_size}};
+ info.checksum = crc8(&info.payload, sizeof(info.payload));
// Make sure the local side knows that we're not sending the full block of data
split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = initiator2target_buffer_size;
@@ -791,18 +792,23 @@ void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *i
// Ignore the args -- the `split_shmem` already has the info, we just need to act upon it.
// We must keep the `split_transaction_table` non-const, so that it is able to be modified at runtime.
- split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = split_shmem->rpc_info.m2s_length;
- split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = split_shmem->rpc_info.s2m_length;
+ split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = split_shmem->rpc_info.payload.m2s_length;
+ split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = split_shmem->rpc_info.payload.s2m_length;
}
void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
// We can assume that the buffer lengths are correctly set, now, given that sequentially the rpc_info callback was already executed.
// Go through the rpc_info and execute _that_ transaction's callback, with the scratch buffers as inputs.
- int8_t transaction_id = split_shmem->rpc_info.transaction_id;
+ // As a safety precaution we check that the received payload matches its checksum first.
+ if (crc8(&split_shmem->rpc_info.payload, sizeof(split_shmem->rpc_info.payload)) != split_shmem->rpc_info.checksum) {
+ return;
+ }
+
+ int8_t transaction_id = split_shmem->rpc_info.payload.transaction_id;
if (transaction_id < NUM_TOTAL_TRANSACTIONS) {
split_transaction_desc_t *trans = &split_transaction_table[transaction_id];
if (trans->slave_callback) {
- trans->slave_callback(split_shmem->rpc_info.m2s_length, split_shmem->rpc_m2s_buffer, split_shmem->rpc_info.s2m_length, split_shmem->rpc_s2m_buffer);
+ trans->slave_callback(split_shmem->rpc_info.payload.m2s_length, split_shmem->rpc_m2s_buffer, split_shmem->rpc_info.payload.s2m_length, split_shmem->rpc_s2m_buffer);
}
}
}
diff --git a/quantum/split_common/transport.h b/quantum/split_common/transport.h
index e62679990a..06778ad14a 100644
--- a/quantum/split_common/transport.h
+++ b/quantum/split_common/transport.h
@@ -116,9 +116,12 @@ typedef struct _split_slave_pointing_sync_t {
#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
typedef struct _rpc_sync_info_t {
- int8_t transaction_id;
- uint8_t m2s_length;
- uint8_t s2m_length;
+ uint8_t checksum;
+ struct {
+ int8_t transaction_id;
+ uint8_t m2s_length;
+ uint8_t s2m_length;
+ } payload;
} rpc_sync_info_t;
#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
diff --git a/quantum/via.c b/quantum/via.c
index 320bd5546d..37e2046a10 100644
--- a/quantum/via.c
+++ b/quantum/via.c
@@ -64,6 +64,7 @@ void via_qmk_rgblight_get_value(uint8_t *data);
#endif
#if defined(VIA_QMK_RGB_MATRIX_ENABLE)
+# include <lib/lib8tion/lib8tion.h>
void via_qmk_rgb_matrix_set_value(uint8_t *data);
void via_qmk_rgb_matrix_get_value(uint8_t *data);
void eeconfig_update_rgb_matrix(void);
@@ -396,6 +397,18 @@ void raw_hid_receive(uint8_t *data, uint8_t length) {
dynamic_keymap_set_buffer(offset, size, &command_data[3]);
break;
}
+#ifdef ENCODER_MAP_ENABLE
+ case id_dynamic_keymap_get_encoder: {
+ uint16_t keycode = dynamic_keymap_get_encoder(command_data[0], command_data[1], command_data[2] != 0);
+ command_data[3] = keycode >> 8;
+ command_data[4] = keycode & 0xFF;
+ break;
+ }
+ case id_dynamic_keymap_set_encoder: {
+ dynamic_keymap_set_encoder(command_data[0], command_data[1], command_data[2] != 0, (command_data[3] << 8) | command_data[4]);
+ break;
+ }
+#endif
default: {
// The command ID is not known
// Return the unhandled state
@@ -421,7 +434,7 @@ void via_qmk_backlight_get_value(uint8_t *data) {
switch (*value_id) {
case id_qmk_backlight_brightness: {
// level / BACKLIGHT_LEVELS * 255
- value_data[0] = ((uint16_t)get_backlight_level()) * 255 / BACKLIGHT_LEVELS;
+ value_data[0] = ((uint16_t)get_backlight_level() * UINT8_MAX) / BACKLIGHT_LEVELS;
break;
}
case id_qmk_backlight_effect: {
@@ -441,7 +454,7 @@ void via_qmk_backlight_set_value(uint8_t *data) {
switch (*value_id) {
case id_qmk_backlight_brightness: {
// level / 255 * BACKLIGHT_LEVELS
- backlight_level_noeeprom(((uint16_t)value_data[0]) * BACKLIGHT_LEVELS / 255);
+ backlight_level_noeeprom(((uint16_t)value_data[0] * BACKLIGHT_LEVELS) / UINT8_MAX);
break;
}
case id_qmk_backlight_effect: {
@@ -460,13 +473,16 @@ void via_qmk_backlight_set_value(uint8_t *data) {
#endif // #if defined(VIA_QMK_BACKLIGHT_ENABLE)
#if defined(VIA_QMK_RGBLIGHT_ENABLE)
+# ifndef RGBLIGHT_LIMIT_VAL
+# define RGBLIGHT_LIMIT_VAL 255
+# endif
void via_qmk_rgblight_get_value(uint8_t *data) {
uint8_t *value_id = &(data[0]);
uint8_t *value_data = &(data[1]);
switch (*value_id) {
case id_qmk_rgblight_brightness: {
- value_data[0] = rgblight_get_val();
+ value_data[0] = ((uint16_t)rgblight_get_val() * UINT8_MAX) / RGBLIGHT_LIMIT_VAL;
break;
}
case id_qmk_rgblight_effect: {
@@ -490,7 +506,7 @@ void via_qmk_rgblight_set_value(uint8_t *data) {
uint8_t *value_data = &(data[1]);
switch (*value_id) {
case id_qmk_rgblight_brightness: {
- rgblight_sethsv_noeeprom(rgblight_get_hue(), rgblight_get_sat(), value_data[0]);
+ rgblight_sethsv_noeeprom(rgblight_get_hue(), rgblight_get_sat(), ((uint16_t)value_data[0] * RGBLIGHT_LIMIT_VAL) / UINT8_MAX);
break;
}
case id_qmk_rgblight_effect: {
@@ -517,6 +533,11 @@ void via_qmk_rgblight_set_value(uint8_t *data) {
#if defined(VIA_QMK_RGB_MATRIX_ENABLE)
+# if !defined(RGB_MATRIX_MAXIMUM_BRIGHTNESS) || RGB_MATRIX_MAXIMUM_BRIGHTNESS > UINT8_MAX
+# undef RGB_MATRIX_MAXIMUM_BRIGHTNESS
+# define RGB_MATRIX_MAXIMUM_BRIGHTNESS UINT8_MAX
+# endif
+
// VIA supports only 4 discrete values for effect speed; map these to some
// useful speed values for RGB Matrix.
enum speed_values {
@@ -557,7 +578,7 @@ void via_qmk_rgb_matrix_get_value(uint8_t *data) {
uint8_t *value_data = &(data[1]);
switch (*value_id) {
case id_qmk_rgblight_brightness:
- value_data[0] = rgb_matrix_get_val();
+ value_data[0] = ((uint16_t)rgb_matrix_get_val() * UINT8_MAX) / RGB_MATRIX_MAXIMUM_BRIGHTNESS;
break;
case id_qmk_rgblight_effect:
value_data[0] = rgb_matrix_get_mode();
@@ -577,7 +598,7 @@ void via_qmk_rgb_matrix_set_value(uint8_t *data) {
uint8_t *value_data = &(data[1]);
switch (*value_id) {
case id_qmk_rgblight_brightness:
- rgb_matrix_sethsv_noeeprom(rgb_matrix_get_hue(), rgb_matrix_get_sat(), value_data[0]);
+ rgb_matrix_sethsv_noeeprom(rgb_matrix_get_hue(), rgb_matrix_get_sat(), scale8(value_data[0], RGB_MATRIX_MAXIMUM_BRIGHTNESS));
break;
case id_qmk_rgblight_effect:
rgb_matrix_mode_noeeprom(value_data[0]);
diff --git a/quantum/via.h b/quantum/via.h
index ac29a58902..558ae95de4 100644
--- a/quantum/via.h
+++ b/quantum/via.h
@@ -54,9 +54,11 @@
# define VIA_EEPROM_CUSTOM_CONFIG_SIZE 0
#endif
+#define VIA_EEPROM_CONFIG_END (VIA_EEPROM_CUSTOM_CONFIG_ADDR + VIA_EEPROM_CUSTOM_CONFIG_SIZE)
+
// This is changed only when the command IDs change,
// so VIA Configurator can detect compatible firmware.
-#define VIA_PROTOCOL_VERSION 0x0009
+#define VIA_PROTOCOL_VERSION 0x000A
enum via_command_id {
id_get_protocol_version = 0x01, // always 0x01
@@ -78,6 +80,8 @@ enum via_command_id {
id_dynamic_keymap_get_layer_count = 0x11,
id_dynamic_keymap_get_buffer = 0x12,
id_dynamic_keymap_set_buffer = 0x13,
+ id_dynamic_keymap_get_encoder = 0x14,
+ id_dynamic_keymap_set_encoder = 0x15,
id_unhandled = 0xFF,
};
diff --git a/quantum/wear_leveling/tests/backing_mocks.cpp b/quantum/wear_leveling/tests/backing_mocks.cpp
new file mode 100644
index 0000000000..1dbb26f8e7
--- /dev/null
+++ b/quantum/wear_leveling/tests/backing_mocks.cpp
@@ -0,0 +1,154 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "backing_mocks.hpp"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Backing Store Mock implementation
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void MockBackingStore::reset_instance() {
+ for (auto&& e : backing_storage)
+ e.reset();
+
+ locked = true;
+
+ backing_erasure_count = 0;
+ backing_max_write_count = 0;
+ backing_total_write_count = 0;
+
+ backing_init_invoke_count = 0;
+ backing_unlock_invoke_count = 0;
+ backing_erase_invoke_count = 0;
+ backing_write_invoke_count = 0;
+ backing_lock_invoke_count = 0;
+
+ init_success_callback = [](std::uint64_t) { return true; };
+ erase_success_callback = [](std::uint64_t) { return true; };
+ unlock_success_callback = [](std::uint64_t) { return true; };
+ write_success_callback = [](std::uint64_t, std::uint32_t) { return true; };
+ lock_success_callback = [](std::uint64_t) { return true; };
+
+ write_log.clear();
+}
+
+bool MockBackingStore::init(void) {
+ ++backing_init_invoke_count;
+
+ if (init_success_callback) {
+ return init_success_callback(backing_init_invoke_count);
+ }
+ return true;
+}
+
+bool MockBackingStore::unlock(void) {
+ ++backing_unlock_invoke_count;
+
+ EXPECT_TRUE(is_locked()) << "Attempted to unlock but was not locked";
+ locked = false;
+
+ if (unlock_success_callback) {
+ return unlock_success_callback(backing_unlock_invoke_count);
+ }
+ return true;
+}
+
+bool MockBackingStore::erase(void) {
+ ++backing_erase_invoke_count;
+
+ // Erase each slot
+ for (std::size_t i = 0; i < backing_storage.size(); ++i) {
+ // Drop out of erase early with failure if we need to
+ if (erase_success_callback && !erase_success_callback(backing_erase_invoke_count)) {
+ append_log(true);
+ return false;
+ }
+
+ backing_storage[i].erase();
+ }
+
+ // Keep track of the erase in the write log so that we can verify during tests
+ append_log(true);
+
+ ++backing_erasure_count;
+ return true;
+}
+
+bool MockBackingStore::write(uint32_t address, backing_store_int_t value) {
+ ++backing_write_invoke_count;
+
+ // precondition: value's buffer size already matches BACKING_STORE_WRITE_SIZE
+ EXPECT_TRUE(address % BACKING_STORE_WRITE_SIZE == 0) << "Supplied address was not aligned with the backing store integral size";
+ EXPECT_TRUE(address + BACKING_STORE_WRITE_SIZE <= WEAR_LEVELING_BACKING_SIZE) << "Address would result of out-of-bounds access";
+ EXPECT_FALSE(is_locked()) << "Write was attempted without being unlocked first";
+
+ // Drop out of write early with failure if we need to
+ if (write_success_callback && !write_success_callback(backing_write_invoke_count, address)) {
+ return false;
+ }
+
+ // Write the complement as we're simulating flash memory -- 0xFF means 0x00
+ std::size_t index = address / BACKING_STORE_WRITE_SIZE;
+ backing_storage[index].set(~value);
+
+ // Keep track of the write log so that we can verify during tests
+ append_log(address, value);
+
+ // Keep track of the total number of writes into the backing store
+ ++backing_total_write_count;
+
+ return true;
+}
+
+bool MockBackingStore::lock(void) {
+ ++backing_lock_invoke_count;
+
+ EXPECT_FALSE(is_locked()) << "Attempted to lock but was not unlocked";
+ locked = true;
+
+ if (lock_success_callback) {
+ return lock_success_callback(backing_lock_invoke_count);
+ }
+ return true;
+}
+
+bool MockBackingStore::read(uint32_t address, backing_store_int_t& value) const {
+ // precondition: value's buffer size already matches BACKING_STORE_WRITE_SIZE
+ EXPECT_TRUE(address % BACKING_STORE_WRITE_SIZE == 0) << "Supplied address was not aligned with the backing store integral size";
+ EXPECT_TRUE(address + BACKING_STORE_WRITE_SIZE <= WEAR_LEVELING_BACKING_SIZE) << "Address would result of out-of-bounds access";
+
+ // Read and take the complement as we're simulating flash memory -- 0xFF means 0x00
+ std::size_t index = address / BACKING_STORE_WRITE_SIZE;
+ value = ~backing_storage[index].get();
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Backing Implementation
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+extern "C" bool backing_store_init(void) {
+ return MockBackingStore::Instance().init();
+}
+
+extern "C" bool backing_store_unlock(void) {
+ return MockBackingStore::Instance().unlock();
+}
+
+extern "C" bool backing_store_erase(void) {
+ return MockBackingStore::Instance().erase();
+}
+
+extern "C" bool backing_store_write(uint32_t address, backing_store_int_t value) {
+ return MockBackingStore::Instance().write(address, value);
+}
+
+extern "C" bool backing_store_lock(void) {
+ return MockBackingStore::Instance().lock();
+}
+
+extern "C" bool backing_store_read(uint32_t address, backing_store_int_t* value) {
+ return MockBackingStore::Instance().read(address, *value);
+}
diff --git a/quantum/wear_leveling/tests/backing_mocks.hpp b/quantum/wear_leveling/tests/backing_mocks.hpp
new file mode 100644
index 0000000000..e7af7895f3
--- /dev/null
+++ b/quantum/wear_leveling/tests/backing_mocks.hpp
@@ -0,0 +1,210 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <cstdlib>
+#include <functional>
+#include <type_traits>
+#include <vector>
+
+extern "C" {
+#include "fnv.h"
+#include "wear_leveling.h"
+#include "wear_leveling_internal.h"
+};
+
+// Maximum number of mock write log entries to keep
+using MOCK_WRITE_LOG_MAX_ENTRIES = std::integral_constant<std::size_t, 1024>;
+// Complement to the backing store integral, for emulating flash erases of all bytes=0xFF
+using BACKING_STORE_INTEGRAL_COMPLEMENT = std::integral_constant<backing_store_int_t, ((backing_store_int_t)(~(backing_store_int_t)0))>;
+// Total number of elements stored in the backing arrays
+using BACKING_STORE_ELEMENT_COUNT = std::integral_constant<std::size_t, (WEAR_LEVELING_BACKING_SIZE / sizeof(backing_store_int_t))>;
+
+class MockBackingStoreElement {
+ private:
+ backing_store_int_t value;
+ std::size_t writes;
+ std::size_t erases;
+
+ public:
+ MockBackingStoreElement() : value(BACKING_STORE_INTEGRAL_COMPLEMENT::value), writes(0), erases(0) {}
+ void reset() {
+ erase();
+ writes = 0;
+ erases = 0;
+ }
+ void erase() {
+ if (!is_erased()) {
+ ++erases;
+ }
+ value = BACKING_STORE_INTEGRAL_COMPLEMENT::value;
+ }
+ backing_store_int_t get() const {
+ return value;
+ }
+ void set(const backing_store_int_t& v) {
+ EXPECT_TRUE(is_erased()) << "Attempted write at index which isn't empty.";
+ value = v;
+ ++writes;
+ }
+ std::size_t num_writes() const {
+ return writes;
+ }
+ std::size_t num_erases() const {
+ return erases;
+ }
+ bool is_erased() const {
+ return value == BACKING_STORE_INTEGRAL_COMPLEMENT::value;
+ }
+};
+
+struct MockBackingStoreLogEntry {
+ MockBackingStoreLogEntry(uint32_t address, backing_store_int_t value) : address(address), value(value), erased(false) {}
+ MockBackingStoreLogEntry(bool erased) : address(0), value(0), erased(erased) {}
+ uint32_t address = 0; // The address of the operation
+ backing_store_int_t value = 0; // The value of the operation
+ bool erased = false; // Whether the entire backing store was erased
+};
+
+class MockBackingStore {
+ private:
+ MockBackingStore() {
+ reset_instance();
+ }
+
+ // Type containing each of the entries and the write counts
+ using storage_t = std::array<MockBackingStoreElement, BACKING_STORE_ELEMENT_COUNT::value>;
+
+ // Whether the backing store is locked
+ bool locked;
+ // The actual data stored in the emulated flash
+ storage_t backing_storage;
+ // The number of erase cycles that have occurred
+ std::uint64_t backing_erasure_count;
+ // The max number of writes to an element of the backing store
+ std::uint64_t backing_max_write_count;
+ // The total number of writes to all elements of the backing store
+ std::uint64_t backing_total_write_count;
+ // The write log for the backing store
+ std::vector<MockBackingStoreLogEntry> write_log;
+
+ // The number of times each API was invoked
+ std::uint64_t backing_init_invoke_count;
+ std::uint64_t backing_unlock_invoke_count;
+ std::uint64_t backing_erase_invoke_count;
+ std::uint64_t backing_write_invoke_count;
+ std::uint64_t backing_lock_invoke_count;
+
+ // Whether init should succeed
+ std::function<bool(std::uint64_t)> init_success_callback;
+ // Whether erase should succeed
+ std::function<bool(std::uint64_t)> erase_success_callback;
+ // Whether unlocks should succeed
+ std::function<bool(std::uint64_t)> unlock_success_callback;
+ // Whether writes should succeed
+ std::function<bool(std::uint64_t, std::uint32_t)> write_success_callback;
+ // Whether locks should succeed
+ std::function<bool(std::uint64_t)> lock_success_callback;
+
+ template <typename... Args>
+ void append_log(Args&&... args) {
+ if (write_log.size() < MOCK_WRITE_LOG_MAX_ENTRIES::value) {
+ write_log.emplace_back(std::forward<Args>(args)...);
+ }
+ }
+
+ public:
+ static MockBackingStore& Instance() {
+ static MockBackingStore instance;
+ return instance;
+ }
+
+ std::uint64_t erasure_count() const {
+ return backing_erasure_count;
+ }
+ std::uint64_t max_write_count() const {
+ return backing_max_write_count;
+ }
+ std::uint64_t total_write_count() const {
+ return backing_total_write_count;
+ }
+
+ // The number of times each API was invoked
+ std::uint64_t init_invoke_count() const {
+ return backing_init_invoke_count;
+ }
+ std::uint64_t unlock_invoke_count() const {
+ return backing_unlock_invoke_count;
+ }
+ std::uint64_t erase_invoke_count() const {
+ return backing_erase_invoke_count;
+ }
+ std::uint64_t write_invoke_count() const {
+ return backing_write_invoke_count;
+ }
+ std::uint64_t lock_invoke_count() const {
+ return backing_lock_invoke_count;
+ }
+
+ // Clear out the internal data for the next run
+ void reset_instance();
+
+ bool is_locked() const {
+ return locked;
+ }
+
+ // APIs for the backing store
+ bool init();
+ bool unlock();
+ bool erase();
+ bool write(std::uint32_t address, backing_store_int_t value);
+ bool lock();
+ bool read(std::uint32_t address, backing_store_int_t& value) const;
+
+ // Control over when init/writes/erases should succeed
+ void set_init_callback(std::function<bool(std::uint64_t)> callback) {
+ init_success_callback = callback;
+ }
+ void set_erase_callback(std::function<bool(std::uint64_t)> callback) {
+ erase_success_callback = callback;
+ }
+ void set_unlock_callback(std::function<bool(std::uint64_t)> callback) {
+ unlock_success_callback = callback;
+ }
+ void set_write_callback(std::function<bool(std::uint64_t, std::uint32_t)> callback) {
+ write_success_callback = callback;
+ }
+ void set_lock_callback(std::function<bool(std::uint64_t)> callback) {
+ lock_success_callback = callback;
+ }
+
+ auto storage_begin() const -> decltype(backing_storage.begin()) {
+ return backing_storage.begin();
+ }
+ auto storage_end() const -> decltype(backing_storage.end()) {
+ return backing_storage.end();
+ }
+
+ auto storage_begin() -> decltype(backing_storage.begin()) {
+ return backing_storage.begin();
+ }
+ auto storage_end() -> decltype(backing_storage.end()) {
+ return backing_storage.end();
+ }
+
+ auto log_begin() -> decltype(write_log.begin()) {
+ return write_log.begin();
+ }
+ auto log_end() -> decltype(write_log.end()) {
+ return write_log.end();
+ }
+
+ auto log_begin() const -> decltype(write_log.begin()) {
+ return write_log.begin();
+ }
+ auto log_end() const -> decltype(write_log.end()) {
+ return write_log.end();
+ }
+};
diff --git a/quantum/wear_leveling/tests/rules.mk b/quantum/wear_leveling/tests/rules.mk
new file mode 100644
index 0000000000..4d7a964049
--- /dev/null
+++ b/quantum/wear_leveling/tests/rules.mk
@@ -0,0 +1,66 @@
+wear_leveling_common_DEFS := \
+ -DWEAR_LEVELING_TESTS
+wear_leveling_common_SRC := \
+ $(LIB_PATH)/fnv/qmk_fnv_type_validation.c \
+ $(LIB_PATH)/fnv/hash_32a.c \
+ $(LIB_PATH)/fnv/hash_64a.c \
+ $(QUANTUM_PATH)/wear_leveling/wear_leveling.c \
+ $(QUANTUM_PATH)/wear_leveling/tests/backing_mocks.cpp
+wear_leveling_common_INC := \
+ $(LIB_PATH)/fnv \
+ $(QUANTUM_PATH)/wear_leveling
+
+wear_leveling_general_DEFS := \
+ $(wear_leveling_common_DEFS) \
+ -DBACKING_STORE_WRITE_SIZE=2 \
+ -DWEAR_LEVELING_BACKING_SIZE=48 \
+ -DWEAR_LEVELING_LOGICAL_SIZE=16
+wear_leveling_general_SRC := \
+ $(wear_leveling_common_SRC) \
+ $(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_general.cpp
+wear_leveling_general_INC := \
+ $(wear_leveling_common_INC)
+
+wear_leveling_2byte_optimized_writes_DEFS := \
+ $(wear_leveling_common_DEFS) \
+ -DBACKING_STORE_WRITE_SIZE=2 \
+ -DWEAR_LEVELING_BACKING_SIZE=65536 \
+ -DWEAR_LEVELING_LOGICAL_SIZE=32768
+wear_leveling_2byte_optimized_writes_SRC := \
+ $(wear_leveling_common_SRC) \
+ $(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_2byte_optimized_writes.cpp
+wear_leveling_2byte_optimized_writes_INC := \
+ $(wear_leveling_common_INC)
+
+wear_leveling_2byte_DEFS := \
+ $(wear_leveling_common_DEFS) \
+ -DBACKING_STORE_WRITE_SIZE=2 \
+ -DWEAR_LEVELING_BACKING_SIZE=48 \
+ -DWEAR_LEVELING_LOGICAL_SIZE=16
+wear_leveling_2byte_SRC := \
+ $(wear_leveling_common_SRC) \
+ $(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_2byte.cpp
+wear_leveling_2byte_INC := \
+ $(wear_leveling_common_INC)
+
+wear_leveling_4byte_DEFS := \
+ $(wear_leveling_common_DEFS) \
+ -DBACKING_STORE_WRITE_SIZE=4 \
+ -DWEAR_LEVELING_BACKING_SIZE=48 \
+ -DWEAR_LEVELING_LOGICAL_SIZE=16
+wear_leveling_4byte_SRC := \
+ $(wear_leveling_common_SRC) \
+ $(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_4byte.cpp
+wear_leveling_4byte_INC := \
+ $(wear_leveling_common_INC)
+
+wear_leveling_8byte_DEFS := \
+ $(wear_leveling_common_DEFS) \
+ -DBACKING_STORE_WRITE_SIZE=8 \
+ -DWEAR_LEVELING_BACKING_SIZE=48 \
+ -DWEAR_LEVELING_LOGICAL_SIZE=16
+wear_leveling_8byte_SRC := \
+ $(wear_leveling_common_SRC) \
+ $(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_8byte.cpp
+wear_leveling_8byte_INC := \
+ $(wear_leveling_common_INC) \ No newline at end of file
diff --git a/quantum/wear_leveling/tests/testlist.mk b/quantum/wear_leveling/tests/testlist.mk
new file mode 100644
index 0000000000..32cfc178b4
--- /dev/null
+++ b/quantum/wear_leveling/tests/testlist.mk
@@ -0,0 +1,6 @@
+TEST_LIST += \
+ wear_leveling_general \
+ wear_leveling_2byte_optimized_writes \
+ wear_leveling_2byte \
+ wear_leveling_4byte \
+ wear_leveling_8byte
diff --git a/quantum/wear_leveling/tests/wear_leveling_2byte.cpp b/quantum/wear_leveling/tests/wear_leveling_2byte.cpp
new file mode 100644
index 0000000000..b749c32b04
--- /dev/null
+++ b/quantum/wear_leveling/tests/wear_leveling_2byte.cpp
@@ -0,0 +1,228 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <numeric>
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "backing_mocks.hpp"
+
+class WearLeveling2Byte : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ MockBackingStore::Instance().reset_instance();
+ wear_leveling_init();
+ }
+};
+
+static std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> verify_data;
+
+static wear_leveling_status_t test_write(const uint32_t address, const void* value, size_t length) {
+ memcpy(&verify_data[address], value, length);
+ return wear_leveling_write(address, value, length);
+}
+
+/**
+ * This test verifies that the first write after initialisation occurs after the FNV1a_64 hash location.
+ */
+TEST_F(WearLeveling2Byte, FirstWriteOccursAfterHash) {
+ auto& inst = MockBackingStore::Instance();
+ uint8_t test_value = 0x15;
+ test_write(0x02, &test_value, sizeof(test_value));
+ EXPECT_EQ(inst.log_begin()->address, WEAR_LEVELING_LOGICAL_SIZE + 8) << "Invalid first write address.";
+}
+
+/**
+ * This test verifies that the first write after initialisation occurs after the FNV1a_64 hash location, after an erase has occurred.
+ */
+TEST_F(WearLeveling2Byte, FirstWriteOccursAfterHash_AfterErase) {
+ auto& inst = MockBackingStore::Instance();
+ uint8_t test_value = 0x15;
+ wear_leveling_erase();
+ test_write(0x02, &test_value, sizeof(test_value));
+ EXPECT_EQ((inst.log_begin() + 1)->address, WEAR_LEVELING_LOGICAL_SIZE + 8) << "Invalid first write address.";
+}
+
+/**
+ * This test forces consolidation by writing enough to the write log that it overflows, consolidating the data into the
+ * base logical area.
+ */
+TEST_F(WearLeveling2Byte, ConsolidationOverflow) {
+ auto& inst = MockBackingStore::Instance();
+
+ // Generate a test block of data which forces OPTIMIZED_64 writes
+ std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> testvalue;
+
+ // Write the data
+ std::iota(testvalue.begin(), testvalue.end(), 0x20);
+ EXPECT_EQ(test_write(0, testvalue.data(), testvalue.size()), WEAR_LEVELING_CONSOLIDATED) << "Write returned incorrect status";
+ uint8_t dummy = 0x40;
+ EXPECT_EQ(test_write(0x04, &dummy, sizeof(dummy)), WEAR_LEVELING_SUCCESS) << "Write returned incorrect status";
+
+ // All writes are at address<64, so each logical byte written will generate 1 write log entry, thus 1 backing store write.
+ // Expected log:
+ // [0..11]: optimised64, backing address 0x18, logical address 0x00
+ // [12]: erase
+ // [13..20]: consolidated data, backing address 0x00, logical address 0x00
+ // [21..24]: FNV1a_64 result, backing address 0x10
+ // [25]: optimised64, backing address 0x18, logical address 0x04
+ EXPECT_EQ(std::distance(inst.log_begin(), inst.log_end()), 26);
+
+ // Verify the backing store writes for the write log
+ std::size_t index;
+ write_log_entry_t e;
+ for (index = 0; index < 12; ++index) {
+ auto write_iter = inst.log_begin() + index;
+ EXPECT_EQ(write_iter->address, WEAR_LEVELING_LOGICAL_SIZE + 8 + (index * BACKING_STORE_WRITE_SIZE)) << "Invalid write log address";
+ e.raw16[0] = write_iter->value;
+ EXPECT_EQ(LOG_ENTRY_GET_TYPE(e), LOG_ENTRY_TYPE_OPTIMIZED_64) << "Invalid write log entry type";
+ }
+
+ // Verify the backing store erase
+ {
+ index = 12;
+ auto write_iter = inst.log_begin() + index;
+ e.raw16[0] = write_iter->value;
+ EXPECT_TRUE(write_iter->erased) << "Backing store erase did not occur as required";
+ }
+
+ // Verify the backing store writes for consolidation
+ for (index = 13; index < 21; ++index) {
+ auto write_iter = inst.log_begin() + index;
+ EXPECT_EQ(write_iter->address, (index - 13) * BACKING_STORE_WRITE_SIZE) << "Invalid write log entry address";
+ }
+
+ // Verify the FNV1a_64 write
+ {
+ EXPECT_EQ((inst.log_begin() + 21)->address, WEAR_LEVELING_LOGICAL_SIZE) << "Invalid write log address";
+ e.raw16[0] = (inst.log_begin() + 21)->value;
+ e.raw16[1] = (inst.log_begin() + 22)->value;
+ e.raw16[2] = (inst.log_begin() + 23)->value;
+ e.raw16[3] = (inst.log_begin() + 24)->value;
+ EXPECT_EQ(e.raw64, fnv_64a_buf(testvalue.data(), testvalue.size(), FNV1A_64_INIT)) << "Invalid checksum"; // Note that checksum is based on testvalue, as we overwrote one byte and need to consult the consolidated data, not the current
+ }
+
+ // Verify the final write
+ EXPECT_EQ((inst.log_begin() + 25)->address, WEAR_LEVELING_LOGICAL_SIZE + 8) << "Invalid write log address";
+
+ // Verify the data is what we expected
+ std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> readback;
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback did not match";
+
+ // Re-init and re-read, verifying the reload capability
+ EXPECT_NE(wear_leveling_init(), WEAR_LEVELING_FAILED) << "Re-initialisation failed";
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback did not match";
+}
+
+/**
+ * This test verifies multibyte readback gets canceled with an out-of-bounds address.
+ */
+TEST_F(WearLeveling2Byte, PlaybackReadbackMultibyte_OOB) {
+ auto& inst = MockBackingStore::Instance();
+ auto logstart = inst.storage_begin() + (WEAR_LEVELING_LOGICAL_SIZE / sizeof(backing_store_int_t));
+
+ // Invalid FNV1a_64 hash
+ (logstart + 0)->set(0);
+ (logstart + 1)->set(0);
+ (logstart + 2)->set(0);
+ (logstart + 3)->set(0);
+
+ // Set up a 2-byte logical write of [0x11,0x12] at logical offset 0x01
+ auto entry0 = LOG_ENTRY_MAKE_MULTIBYTE(0x01, 2);
+ entry0.raw8[3] = 0x11;
+ entry0.raw8[4] = 0x12;
+ (logstart + 4)->set(~entry0.raw16[0]);
+ (logstart + 5)->set(~entry0.raw16[1]);
+ (logstart + 6)->set(~entry0.raw16[2]);
+
+ // Set up a 2-byte logical write of [0x13,0x14] at logical offset 0x1000 (out of bounds)
+ auto entry1 = LOG_ENTRY_MAKE_MULTIBYTE(0x1000, 2);
+ entry1.raw8[3] = 0x13;
+ entry1.raw8[4] = 0x14;
+ (logstart + 7)->set(~entry1.raw16[0]);
+ (logstart + 8)->set(~entry1.raw16[1]);
+ (logstart + 9)->set(~entry1.raw16[2]);
+
+ // Set up a 2-byte logical write of [0x15,0x16] at logical offset 0x01
+ auto entry2 = LOG_ENTRY_MAKE_MULTIBYTE(0x01, 2);
+ entry2.raw8[3] = 0x15;
+ entry2.raw8[4] = 0x16;
+ (logstart + 10)->set(~entry2.raw16[0]);
+ (logstart + 11)->set(~entry2.raw16[1]);
+ (logstart + 12)->set(~entry2.raw16[2]);
+
+ EXPECT_EQ(inst.erasure_count(), 0) << "Invalid initial erase count";
+ EXPECT_EQ(wear_leveling_init(), WEAR_LEVELING_CONSOLIDATED) << "Readback should have failed and triggered consolidation";
+ EXPECT_EQ(inst.erasure_count(), 1) << "Invalid final erase count";
+
+ uint8_t buf[2];
+ wear_leveling_read(0x01, buf, sizeof(buf));
+ EXPECT_EQ(buf[0], 0x11) << "Readback should have maintained the previous pre-failure value from the write log";
+ EXPECT_EQ(buf[1], 0x12) << "Readback should have maintained the previous pre-failure value from the write log";
+}
+
+/**
+ * This test verifies optimized 64 readback gets canceled with an out-of-bounds address.
+ */
+TEST_F(WearLeveling2Byte, PlaybackReadbackOptimized64_OOB) {
+ auto& inst = MockBackingStore::Instance();
+ auto logstart = inst.storage_begin() + (WEAR_LEVELING_LOGICAL_SIZE / sizeof(backing_store_int_t));
+
+ // Invalid FNV1a_64 hash
+ (logstart + 0)->set(0);
+ (logstart + 1)->set(0);
+ (logstart + 2)->set(0);
+ (logstart + 3)->set(0);
+
+ // Set up a 1-byte logical write of 0x11 at logical offset 0x01
+ auto entry0 = LOG_ENTRY_MAKE_OPTIMIZED_64(0x01, 0x11);
+ (logstart + 4)->set(~entry0.raw16[0]);
+
+ // Set up a 1-byte logical write of 0x11 at logical offset 0x30 (out of bounds)
+ auto entry1 = LOG_ENTRY_MAKE_OPTIMIZED_64(0x30, 0x11);
+ (logstart + 5)->set(~entry1.raw16[0]);
+
+ // Set up a 1-byte logical write of 0x12 at logical offset 0x01
+ auto entry2 = LOG_ENTRY_MAKE_OPTIMIZED_64(0x01, 0x12);
+ (logstart + 6)->set(~entry2.raw16[0]);
+
+ EXPECT_EQ(inst.erasure_count(), 0) << "Invalid initial erase count";
+ EXPECT_EQ(wear_leveling_init(), WEAR_LEVELING_CONSOLIDATED) << "Readback should have failed and triggered consolidation";
+ EXPECT_EQ(inst.erasure_count(), 1) << "Invalid final erase count";
+ uint8_t tmp;
+ wear_leveling_read(0x01, &tmp, sizeof(tmp));
+ EXPECT_EQ(tmp, 0x11) << "Readback should have maintained the previous pre-failure value from the write log";
+}
+
+/**
+ * This test verifies word 0/1 readback gets canceled with an out-of-bounds address.
+ */
+TEST_F(WearLeveling2Byte, PlaybackReadbackWord01_OOB) {
+ auto& inst = MockBackingStore::Instance();
+ auto logstart = inst.storage_begin() + (WEAR_LEVELING_LOGICAL_SIZE / sizeof(backing_store_int_t));
+
+ // Invalid FNV1a_64 hash
+ (logstart + 0)->set(0);
+ (logstart + 1)->set(0);
+ (logstart + 2)->set(0);
+ (logstart + 3)->set(0);
+
+ // Set up a 1-byte logical write of 1 at logical offset 0x02
+ auto entry0 = LOG_ENTRY_MAKE_WORD_01(0x02, 1);
+ (logstart + 4)->set(~entry0.raw16[0]);
+
+ // Set up a 1-byte logical write of 1 at logical offset 0x1000 (out of bounds)
+ auto entry1 = LOG_ENTRY_MAKE_WORD_01(0x1000, 1);
+ (logstart + 5)->set(~entry1.raw16[0]);
+
+ // Set up a 1-byte logical write of 0 at logical offset 0x02
+ auto entry2 = LOG_ENTRY_MAKE_WORD_01(0x02, 0);
+ (logstart + 6)->set(~entry2.raw16[0]);
+
+ EXPECT_EQ(inst.erasure_count(), 0) << "Invalid initial erase count";
+ EXPECT_EQ(wear_leveling_init(), WEAR_LEVELING_CONSOLIDATED) << "Readback should have failed and triggered consolidation";
+ EXPECT_EQ(inst.erasure_count(), 1) << "Invalid final erase count";
+ uint8_t tmp;
+ wear_leveling_read(0x02, &tmp, sizeof(tmp));
+ EXPECT_EQ(tmp, 1) << "Readback should have maintained the previous pre-failure value from the write log";
+}
diff --git a/quantum/wear_leveling/tests/wear_leveling_2byte_optimized_writes.cpp b/quantum/wear_leveling/tests/wear_leveling_2byte_optimized_writes.cpp
new file mode 100644
index 0000000000..0b03113c89
--- /dev/null
+++ b/quantum/wear_leveling/tests/wear_leveling_2byte_optimized_writes.cpp
@@ -0,0 +1,295 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <numeric>
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "backing_mocks.hpp"
+
+class WearLeveling2ByteOptimizedWrites : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ MockBackingStore::Instance().reset_instance();
+ wear_leveling_init();
+ }
+};
+
+static std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> verify_data;
+
+static wear_leveling_status_t test_write(const uint32_t address, const void* value, size_t length) {
+ memcpy(&verify_data[address], value, length);
+ return wear_leveling_write(address, value, length);
+}
+
+/**
+ * This test ensures the correct number of backing store writes occurs with a multibyte write, given the input buffer size.
+ */
+TEST_F(WearLeveling2ByteOptimizedWrites, MultibyteBackingStoreWriteCounts) {
+ auto& inst = MockBackingStore::Instance();
+
+ for (std::size_t length = 1; length <= 5; ++length) {
+ // Clear things out
+ std::fill(verify_data.begin(), verify_data.end(), 0);
+ inst.reset_instance();
+ wear_leveling_init();
+
+ // Generate a test block of data
+ std::vector<std::uint8_t> testvalue(length);
+ std::iota(testvalue.begin(), testvalue.end(), 0x20);
+
+ // Write the data
+ EXPECT_EQ(test_write(2000, testvalue.data(), testvalue.size()), WEAR_LEVELING_SUCCESS) << "Write failed with incorrect status";
+
+ std::size_t expected;
+ if (length > 3) {
+ expected = 4;
+ } else if (length > 1) {
+ expected = 3;
+ } else {
+ expected = 2;
+ }
+
+ // Check that we got the expected number of write log entries
+ EXPECT_EQ(std::distance(inst.log_begin(), inst.log_end()), expected);
+ }
+}
+
+/**
+ * This test runs through writing U16 values of `0` or `1` over the entire logical address range, to even addresses only.
+ * - Addresses <16384 will result in a single optimised backing write
+ * - Higher addresses will result in a multibyte write of 3 backing writes
+ */
+TEST_F(WearLeveling2ByteOptimizedWrites, WriteOneThenZeroToEvenAddresses) {
+ auto& inst = MockBackingStore::Instance();
+
+ // Only attempt writes for each address up to a limit that would NOT force a consolidated data write.
+ std::size_t writes_per_loop = (MOCK_WRITE_LOG_MAX_ENTRIES::value / 6) - 1; // Worst case is 6 writes for each pair of writes of 0/1
+ std::size_t final_address;
+ for (uint32_t address = 0; address < WEAR_LEVELING_LOGICAL_SIZE; address += (writes_per_loop * 2)) {
+ // Clear things out
+ std::fill(verify_data.begin(), verify_data.end(), 0);
+ inst.reset_instance();
+ wear_leveling_init();
+
+ // Loop through all the addresses in this range
+ std::size_t expected = 0;
+ for (uint32_t offset = 0; offset < (writes_per_loop * 2); offset += 2) {
+ // If we're about to exceed the limit of the logical store, skip the writes
+ if (address + offset + 2 > WEAR_LEVELING_LOGICAL_SIZE) {
+ break;
+ }
+
+ // The default erased value of the wear-leveling cache is zero, so we write a one first, then a zero, to ensure a backing store write occurs.
+ uint16_t val = 1;
+ EXPECT_EQ(test_write(address + offset, &val, sizeof(val)), WEAR_LEVELING_SUCCESS) << "Write failed with incorrect status";
+ val = 0;
+ EXPECT_EQ(test_write(address + offset, &val, sizeof(val)), WEAR_LEVELING_SUCCESS) << "Write failed with incorrect status";
+
+ std::size_t backing_store_writes_expected = 0;
+ if (address + offset < 16384) {
+ // A U16 value of 0/1 at an even address <16384 will result in 1 backing write each, so we need 2 backing writes for 2 logical writes
+ backing_store_writes_expected = 2;
+ } else {
+ // All other addresses result in a multibyte write (3 backing store writes) to write two local bytes of data
+ backing_store_writes_expected = 6;
+ }
+
+ // Keep track of the total number of expected writes to the backing store
+ expected += backing_store_writes_expected;
+
+ // Verify we're at the correct number of writes
+ EXPECT_EQ(std::distance(inst.log_begin(), inst.log_end()), expected) << "Write log doesn't match required number of backing store writes for address " << (address + offset);
+
+ // Verify that the write log entries we expect are actually present
+ std::size_t write_index = expected - backing_store_writes_expected;
+ auto write_iter = inst.log_begin() + write_index;
+ write_log_entry_t e;
+ if (address + offset < 16384) {
+ // A U16 value of 0/1 at an even address <16384 will result in 1 backing write each, so we need 2 backing writes for 2 logical writes
+ for (std::size_t i = 0; i < 2; ++i) {
+ e.raw16[0] = write_iter->value;
+ EXPECT_EQ(LOG_ENTRY_GET_TYPE(e), LOG_ENTRY_TYPE_WORD_01) << "Invalid write log entry type at " << (address + offset);
+ ++write_iter;
+ }
+ } else {
+ // Multibyte write
+ e.raw16[0] = write_iter->value;
+ EXPECT_EQ(LOG_ENTRY_GET_TYPE(e), LOG_ENTRY_TYPE_MULTIBYTE) << "Invalid write log entry type at " << (address + offset);
+ EXPECT_EQ(LOG_ENTRY_MULTIBYTE_GET_LENGTH(e), 2) << "Invalid write log entry length at " << (address + offset);
+ ++write_iter;
+ }
+
+ // Keep track of the final address written, so we can verify the entire logical range was handled
+ final_address = address + offset;
+ }
+
+ // Verify the number of writes that occurred to the backing store
+ size_t backing_write_count = std::distance(inst.log_begin(), inst.log_end());
+ EXPECT_EQ(backing_write_count, expected) << "Invalid write count at address " << address;
+
+ // Verify the data is what we expected
+ std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> readback;
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback for address " << address << " did not match";
+
+ // Re-init and re-read, testing the reload capability
+ EXPECT_NE(wear_leveling_init(), WEAR_LEVELING_FAILED) << "Re-initialisation failed";
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback for address " << address << " did not match";
+ }
+
+ // Verify the full range of the logical area got written
+ EXPECT_EQ(final_address, WEAR_LEVELING_LOGICAL_SIZE - 2) << "Invalid final write address";
+}
+
+/**
+ * This test runs through writing U16 values of `0` or `1` over the entire logical address range, to odd addresses only.
+ * - Addresses <63 will result in 2 optimised backing writes
+ * - Address 63 results in a single optimised backing write for the first logical byte, and a multibyte write of 2 backing writes for the second logical byte
+ * - Higher addresses will result in a multibyte write of 3 backing writes
+ */
+TEST_F(WearLeveling2ByteOptimizedWrites, WriteOneThenZeroToOddAddresses) {
+ auto& inst = MockBackingStore::Instance();
+
+ // Only attempt writes for each address up to a limit that would NOT force a consolidated data write.
+ std::size_t writes_per_loop = (MOCK_WRITE_LOG_MAX_ENTRIES::value / 6) - 1; // Worst case is 6 writes for each pair of writes of 0/1
+ std::size_t final_address;
+ for (uint32_t address = 1; address < WEAR_LEVELING_LOGICAL_SIZE; address += (writes_per_loop * 2)) {
+ // Clear things out
+ std::fill(verify_data.begin(), verify_data.end(), 0);
+ inst.reset_instance();
+ wear_leveling_init();
+
+ // Loop through all the addresses in this range
+ std::size_t expected = 0;
+ for (uint32_t offset = 0; offset < (writes_per_loop * 2); offset += 2) {
+ // If we're about to exceed the limit of the logical store, skip the writes
+ if (address + offset + 2 > WEAR_LEVELING_LOGICAL_SIZE) {
+ break;
+ }
+
+ // The default erased value of the wear-leveling cache is zero, so we write a one first, then a zero, to ensure a backing store write occurs.
+ uint16_t val = 1;
+ EXPECT_EQ(test_write(address + offset, &val, sizeof(val)), WEAR_LEVELING_SUCCESS) << "Write failed with incorrect status";
+ val = 0;
+ EXPECT_EQ(test_write(address + offset, &val, sizeof(val)), WEAR_LEVELING_SUCCESS) << "Write failed with incorrect status";
+
+ std::size_t backing_store_writes_expected = 0;
+ if (address + offset < 63) {
+ // A U16 value of 0/1 at an odd address <64 will result in 2 backing writes each, so we need 4 backing writes for 2 logical writes
+ backing_store_writes_expected = 4;
+ } else if (address + offset == 63) {
+ // If we're straddling the boundary for optimised bytes (addr==64), then the first logical byte is written using the optimised write (1 backing
+ // store write), and the second logical byte uses a multibyte write (2 backing store writes)
+ backing_store_writes_expected = 2 // First logical bytes written using optimised log entries
+ + 4; // Second logical bytes written using multibyte log entries
+ } else {
+ // All other addresses result in a multibyte write (3 backing store writes) to write two local bytes of data
+ backing_store_writes_expected = 6;
+ }
+
+ // Keep track of the total number of expected writes to the backing store
+ expected += backing_store_writes_expected;
+
+ // Verify we're at the correct number of writes
+ EXPECT_EQ(std::distance(inst.log_begin(), inst.log_end()), expected) << "Write log doesn't match required number of backing store writes for address " << (address + offset);
+
+ // Verify that the write log entries we expect are actually present
+ std::size_t write_index = expected - backing_store_writes_expected;
+ auto write_iter = inst.log_begin() + write_index;
+ write_log_entry_t e;
+ if (address + offset < 63) {
+ // A U16 value of 0/1 at an odd address <64 will result in 2 backing writes each, so we need 4 backing writes for 2 logical writes
+ for (std::size_t i = 0; i < 4; ++i) {
+ e.raw16[0] = write_iter->value;
+ EXPECT_EQ(LOG_ENTRY_GET_TYPE(e), LOG_ENTRY_TYPE_OPTIMIZED_64) << "Invalid write log entry type";
+ ++write_iter;
+ }
+ } else if (address + offset == 63) {
+ // First log entry is the 64-addr optimised one
+ e.raw16[0] = write_iter->value;
+ EXPECT_EQ(LOG_ENTRY_GET_TYPE(e), LOG_ENTRY_TYPE_OPTIMIZED_64) << "Invalid write log entry type";
+ ++write_iter;
+
+ // Second log entry is the multibyte entry for the second logical byte
+ e.raw16[0] = write_iter->value;
+ EXPECT_EQ(LOG_ENTRY_GET_TYPE(e), LOG_ENTRY_TYPE_MULTIBYTE) << "Invalid write log entry type";
+ EXPECT_EQ(LOG_ENTRY_MULTIBYTE_GET_LENGTH(e), 1) << "Invalid write log entry length";
+ ++write_iter;
+ } else {
+ // Multibyte write
+ e.raw16[0] = write_iter->value;
+ EXPECT_EQ(LOG_ENTRY_GET_TYPE(e), LOG_ENTRY_TYPE_MULTIBYTE) << "Invalid write log entry type";
+ EXPECT_EQ(LOG_ENTRY_MULTIBYTE_GET_LENGTH(e), 2) << "Invalid write log entry length";
+ ++write_iter;
+ }
+
+ // Keep track of the final address written, so we can verify the entire logical range was handled
+ final_address = address + offset;
+ }
+
+ // Verify the number of writes that occurred to the backing store
+ size_t backing_write_count = std::distance(inst.log_begin(), inst.log_end());
+ EXPECT_EQ(backing_write_count, expected) << "Invalid write count at address " << address;
+
+ // Verify the data is what we expected
+ std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> readback;
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback for address " << address << " did not match";
+
+ // Re-init and re-read, testing the reload capability
+ EXPECT_NE(wear_leveling_init(), WEAR_LEVELING_FAILED) << "Re-initialisation failed";
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback for address " << address << " did not match";
+ }
+
+ // Verify the full range of the logical area got written
+ EXPECT_EQ(final_address, WEAR_LEVELING_LOGICAL_SIZE - 3) << "Invalid final write address";
+}
+
+/**
+ * This test verifies readback after playback of the write log, simulating power loss and reboot.
+ */
+TEST_F(WearLeveling2ByteOptimizedWrites, PlaybackReadbackOptimized64_Success) {
+ auto& inst = MockBackingStore::Instance();
+ auto logstart = inst.storage_begin() + (WEAR_LEVELING_LOGICAL_SIZE / sizeof(backing_store_int_t));
+
+ // Invalid FNV1a_64 hash
+ (logstart + 0)->set(0);
+ (logstart + 1)->set(0);
+ (logstart + 2)->set(0);
+ (logstart + 3)->set(0);
+
+ // Set up a 1-byte logical write of 0x11 at logical offset 0x01
+ auto entry0 = LOG_ENTRY_MAKE_OPTIMIZED_64(0x01, 0x11);
+ (logstart + 4)->set(~entry0.raw16[0]); // start at offset 4 to skip FNV1a_64 result
+
+ wear_leveling_init();
+ uint8_t tmp;
+
+ wear_leveling_read(0x01, &tmp, sizeof(tmp));
+ EXPECT_EQ(tmp, 0x11) << "Failed to read back the seeded data";
+}
+
+/**
+ * This test verifies readback after playback of the write log, simulating power loss and reboot.
+ */
+TEST_F(WearLeveling2ByteOptimizedWrites, PlaybackReadbackWord01_Success) {
+ auto& inst = MockBackingStore::Instance();
+ auto logstart = inst.storage_begin() + (WEAR_LEVELING_LOGICAL_SIZE / sizeof(backing_store_int_t));
+
+ // Invalid FNV1a_64 hash
+ (logstart + 0)->set(0);
+ (logstart + 1)->set(0);
+ (logstart + 2)->set(0);
+ (logstart + 3)->set(0);
+
+ // Set up a 1-byte logical write of 1 at logical offset 0x02
+ auto entry0 = LOG_ENTRY_MAKE_WORD_01(0x02, 1);
+ (logstart + 4)->set(~entry0.raw16[0]); // start at offset 4 to skip FNV1a_64 result
+
+ wear_leveling_init();
+ uint8_t tmp;
+
+ wear_leveling_read(0x02, &tmp, sizeof(tmp));
+ EXPECT_EQ(tmp, 1) << "Failed to read back the seeded data";
+}
diff --git a/quantum/wear_leveling/tests/wear_leveling_4byte.cpp b/quantum/wear_leveling/tests/wear_leveling_4byte.cpp
new file mode 100644
index 0000000000..54482c5fe7
--- /dev/null
+++ b/quantum/wear_leveling/tests/wear_leveling_4byte.cpp
@@ -0,0 +1,193 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <numeric>
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "backing_mocks.hpp"
+
+class WearLeveling4Byte : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ MockBackingStore::Instance().reset_instance();
+ wear_leveling_init();
+ }
+};
+
+static std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> verify_data;
+
+static wear_leveling_status_t test_write(const uint32_t address, const void* value, size_t length) {
+ memcpy(&verify_data[address], value, length);
+ return wear_leveling_write(address, value, length);
+}
+
+/**
+ * This test verifies that the first write after initialisation occurs after the FNV1a_64 hash location.
+ */
+TEST_F(WearLeveling4Byte, FirstWriteOccursAfterHash) {
+ auto& inst = MockBackingStore::Instance();
+ uint8_t test_value = 0x15;
+ test_write(0x02, &test_value, sizeof(test_value));
+ EXPECT_EQ(inst.log_begin()->address, WEAR_LEVELING_LOGICAL_SIZE + 8) << "Invalid first write address.";
+}
+
+/**
+ * This test verifies that the first write after initialisation occurs after the FNV1a_64 hash location, after an erase has occurred.
+ */
+TEST_F(WearLeveling4Byte, FirstWriteOccursAfterHash_AfterErase) {
+ auto& inst = MockBackingStore::Instance();
+ uint8_t test_value = 0x15;
+ wear_leveling_erase();
+ test_write(0x02, &test_value, sizeof(test_value));
+ EXPECT_EQ((inst.log_begin() + 1)->address, WEAR_LEVELING_LOGICAL_SIZE + 8) << "Invalid first write address.";
+}
+
+/**
+ * This test ensures the correct number of backing store writes occurs with a multibyte write, given the input buffer size.
+ */
+TEST_F(WearLeveling4Byte, MultibyteBackingStoreWriteCounts) {
+ auto& inst = MockBackingStore::Instance();
+
+ for (std::size_t length = 1; length <= 5; ++length) {
+ // Clear things out
+ std::fill(verify_data.begin(), verify_data.end(), 0);
+ inst.reset_instance();
+ wear_leveling_init();
+
+ // Generate a test block of data
+ std::vector<std::uint8_t> testvalue(length);
+ std::iota(testvalue.begin(), testvalue.end(), 0x20);
+
+ // Write the data
+ EXPECT_EQ(test_write(0, testvalue.data(), testvalue.size()), WEAR_LEVELING_SUCCESS) << "Write failed with incorrect status";
+
+ std::size_t expected;
+ if (length > 1) {
+ expected = 2;
+ } else {
+ expected = 1;
+ }
+
+ // Check that we got the expected number of write log entries
+ EXPECT_EQ(std::distance(inst.log_begin(), inst.log_end()), expected);
+ }
+}
+
+/**
+ * This test forces consolidation by writing enough to the write log that it overflows, consolidating the data into the
+ * base logical area.
+ */
+TEST_F(WearLeveling4Byte, ConsolidationOverflow) {
+ auto& inst = MockBackingStore::Instance();
+
+ // Generate a test block of data
+ std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> testvalue;
+
+ // Write the data
+ std::iota(testvalue.begin(), testvalue.end(), 0x20);
+ EXPECT_EQ(test_write(0, testvalue.data(), testvalue.size()), WEAR_LEVELING_CONSOLIDATED) << "Write returned incorrect status";
+ uint8_t dummy = 0x40;
+ EXPECT_EQ(test_write(0x04, &dummy, sizeof(dummy)), WEAR_LEVELING_SUCCESS) << "Write returned incorrect status";
+
+ // Expected log:
+ // [0,1]: multibyte, 5 bytes, backing address 0x18, logical address 0x00
+ // [2,3]: multibyte, 5 bytes, backing address 0x20, logical address 0x05
+ // [4,5]: multibyte, 5 bytes, backing address 0x28, logical address 0x0A, triggers consolidation
+ // [6]: erase
+ // [7,8]: consolidated data, backing address 0x00, logical address 0x00
+ // [9,10]: consolidated data, backing address 0x08, logical address 0x08
+ // [11,12]: FNV1a_64 result, backing address 0x10
+ // [13]: multibyte, 1 byte, backing address 0x18, logical address 0x04
+ EXPECT_EQ(std::distance(inst.log_begin(), inst.log_end()), 14);
+
+ // Verify the backing store writes for the write log
+ std::size_t index;
+ write_log_entry_t e;
+ for (index = 0; index < 6; ++index) {
+ auto write_iter = inst.log_begin() + index;
+ EXPECT_EQ(write_iter->address, WEAR_LEVELING_LOGICAL_SIZE + 8 + (index * BACKING_STORE_WRITE_SIZE)) << "Invalid write log address";
+
+ // If this is the backing store write that contains the metadata, verify it
+ if (index % 2 == 0) {
+ write_log_entry_t e;
+ e.raw64 = write_iter->value;
+ EXPECT_EQ(LOG_ENTRY_GET_TYPE(e), LOG_ENTRY_TYPE_MULTIBYTE) << "Invalid write log entry type";
+ }
+ }
+
+ // Verify the backing store erase
+ {
+ index = 6;
+ auto write_iter = inst.log_begin() + index;
+ e.raw64 = write_iter->value;
+ EXPECT_TRUE(write_iter->erased) << "Backing store erase did not occur as required";
+ }
+
+ // Verify the backing store writes for consolidation
+ for (index = 7; index < 11; ++index) {
+ auto write_iter = inst.log_begin() + index;
+ EXPECT_EQ(write_iter->address, (index - 7) * BACKING_STORE_WRITE_SIZE) << "Invalid write log entry address";
+ }
+
+ // Verify the FNV1a_64 write
+ {
+ EXPECT_EQ((inst.log_begin() + 11)->address, WEAR_LEVELING_LOGICAL_SIZE) << "Invalid write log address";
+ e.raw32[0] = (inst.log_begin() + 11)->value;
+ e.raw32[1] = (inst.log_begin() + 12)->value;
+ EXPECT_EQ(e.raw64, fnv_64a_buf(testvalue.data(), testvalue.size(), FNV1A_64_INIT)) << "Invalid checksum"; // Note that checksum is based on testvalue, as we overwrote one byte and need to consult the consolidated data, not the current
+ }
+
+ // Verify the final write
+ EXPECT_EQ((inst.log_begin() + 13)->address, WEAR_LEVELING_LOGICAL_SIZE + 8) << "Invalid write log address";
+
+ // Verify the data is what we expected
+ std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> readback;
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback did not match";
+
+ // Re-init and re-read, verifying the reload capability
+ EXPECT_NE(wear_leveling_init(), WEAR_LEVELING_FAILED) << "Re-initialisation failed";
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback did not match";
+}
+
+/**
+ * This test verifies multibyte readback gets canceled with an out-of-bounds address.
+ */
+TEST_F(WearLeveling4Byte, PlaybackReadbackMultibyte_OOB) {
+ auto& inst = MockBackingStore::Instance();
+ auto logstart = inst.storage_begin() + (WEAR_LEVELING_LOGICAL_SIZE / sizeof(backing_store_int_t));
+
+ // Invalid FNV1a_64 hash
+ (logstart + 0)->set(0);
+ (logstart + 1)->set(0);
+
+ // Set up a 2-byte logical write of [0x11,0x12] at logical offset 0x01
+ auto entry0 = LOG_ENTRY_MAKE_MULTIBYTE(0x01, 2);
+ entry0.raw8[3] = 0x11;
+ entry0.raw8[4] = 0x12;
+ (logstart + 2)->set(~entry0.raw32[0]);
+ (logstart + 3)->set(~entry0.raw32[1]);
+
+ // Set up a 2-byte logical write of [0x13,0x14] at logical offset 0x1000 (out of bounds)
+ auto entry1 = LOG_ENTRY_MAKE_MULTIBYTE(0x1000, 2);
+ entry1.raw8[3] = 0x13;
+ entry1.raw8[4] = 0x14;
+ (logstart + 4)->set(~entry1.raw32[0]);
+ (logstart + 5)->set(~entry1.raw32[1]);
+
+ // Set up a 2-byte logical write of [0x15,0x16] at logical offset 0x10
+ auto entry2 = LOG_ENTRY_MAKE_MULTIBYTE(0x01, 2);
+ entry2.raw8[3] = 0x15;
+ entry2.raw8[4] = 0x16;
+ (logstart + 6)->set(~entry2.raw32[0]);
+ (logstart + 7)->set(~entry2.raw32[1]);
+
+ EXPECT_EQ(inst.erasure_count(), 0) << "Invalid initial erase count";
+ EXPECT_EQ(wear_leveling_init(), WEAR_LEVELING_CONSOLIDATED) << "Readback should have failed and triggered consolidation";
+ EXPECT_EQ(inst.erasure_count(), 1) << "Invalid final erase count";
+
+ uint8_t buf[2];
+ wear_leveling_read(0x01, buf, sizeof(buf));
+ EXPECT_EQ(buf[0], 0x11) << "Readback should have maintained the previous pre-failure value from the write log";
+ EXPECT_EQ(buf[1], 0x12) << "Readback should have maintained the previous pre-failure value from the write log";
+}
diff --git a/quantum/wear_leveling/tests/wear_leveling_8byte.cpp b/quantum/wear_leveling/tests/wear_leveling_8byte.cpp
new file mode 100644
index 0000000000..c27c21d034
--- /dev/null
+++ b/quantum/wear_leveling/tests/wear_leveling_8byte.cpp
@@ -0,0 +1,178 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <numeric>
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "backing_mocks.hpp"
+
+class WearLeveling8Byte : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ MockBackingStore::Instance().reset_instance();
+ wear_leveling_init();
+ }
+};
+
+static std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> verify_data;
+
+static wear_leveling_status_t test_write(const uint32_t address, const void* value, size_t length) {
+ memcpy(&verify_data[address], value, length);
+ return wear_leveling_write(address, value, length);
+}
+
+/**
+ * This test verifies that the first write after initialisation occurs after the FNV1a_64 hash location.
+ */
+TEST_F(WearLeveling8Byte, FirstWriteOccursAfterHash) {
+ auto& inst = MockBackingStore::Instance();
+ uint8_t test_value = 0x15;
+ test_write(0x02, &test_value, sizeof(test_value));
+ EXPECT_EQ(inst.log_begin()->address, WEAR_LEVELING_LOGICAL_SIZE + 8) << "Invalid first write address.";
+}
+
+/**
+ * This test verifies that the first write after initialisation occurs after the FNV1a_64 hash location, after an erase has occurred.
+ */
+TEST_F(WearLeveling8Byte, FirstWriteOccursAfterHash_AfterErase) {
+ auto& inst = MockBackingStore::Instance();
+ uint8_t test_value = 0x15;
+ wear_leveling_erase();
+ test_write(0x02, &test_value, sizeof(test_value));
+ EXPECT_EQ((inst.log_begin() + 1)->address, WEAR_LEVELING_LOGICAL_SIZE + 8) << "Invalid first write address.";
+}
+
+/**
+ * This test ensures the correct number of backing store writes occurs with a multibyte write, given the input buffer size.
+ */
+TEST_F(WearLeveling8Byte, MultibyteBackingStoreWriteCounts) {
+ auto& inst = MockBackingStore::Instance();
+
+ for (std::size_t length = 1; length <= 5; ++length) {
+ // Clear things out
+ std::fill(verify_data.begin(), verify_data.end(), 0);
+ inst.reset_instance();
+ wear_leveling_init();
+
+ // Generate a test block of data
+ std::vector<std::uint8_t> testvalue(length);
+ std::iota(testvalue.begin(), testvalue.end(), 0x20);
+
+ // Write the data
+ EXPECT_EQ(test_write(0, testvalue.data(), testvalue.size()), WEAR_LEVELING_SUCCESS) << "Write failed with incorrect status";
+
+ // Check that we got the expected number of write log entries
+ EXPECT_EQ(std::distance(inst.log_begin(), inst.log_end()), 1);
+ }
+}
+
+/**
+ * This test forces consolidation by writing enough to the write log that it overflows, consolidating the data into the
+ * base logical area.
+ */
+TEST_F(WearLeveling8Byte, ConsolidationOverflow) {
+ auto& inst = MockBackingStore::Instance();
+
+ // Generate a test block of data
+ std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> testvalue;
+
+ // Write the data
+ std::iota(testvalue.begin(), testvalue.end(), 0x20);
+ EXPECT_EQ(test_write(0, testvalue.data(), testvalue.size()), WEAR_LEVELING_CONSOLIDATED) << "Write returned incorrect status";
+ uint8_t dummy = 0x40;
+ EXPECT_EQ(test_write(0x04, &dummy, sizeof(dummy)), WEAR_LEVELING_SUCCESS) << "Write returned incorrect status";
+
+ // Expected log:
+ // [0]: multibyte, 5 bytes, backing address 0x18, logical address 0x00
+ // [1]: multibyte, 5 bytes, backing address 0x20, logical address 0x05
+ // [2]: multibyte, 5 bytes, backing address 0x28, logical address 0x0A, triggers consolidation
+ // [3]: erase
+ // [4]: consolidated data, backing address 0x00, logical address 0x00
+ // [5]: consolidated data, backing address 0x08, logical address 0x08
+ // [6]: FNV1a_64 result, backing address 0x10
+ // [7]: multibyte, 1 byte, backing address 0x18, logical address 0x04
+ EXPECT_EQ(std::distance(inst.log_begin(), inst.log_end()), 8);
+
+ // Verify the backing store writes for the write log
+ std::size_t index;
+ write_log_entry_t e;
+ for (index = 0; index < 3; ++index) {
+ auto write_iter = inst.log_begin() + index;
+ EXPECT_EQ(write_iter->address, WEAR_LEVELING_LOGICAL_SIZE + 8 + (index * BACKING_STORE_WRITE_SIZE)) << "Invalid write log address";
+
+ write_log_entry_t e;
+ e.raw64 = write_iter->value;
+ EXPECT_EQ(LOG_ENTRY_GET_TYPE(e), LOG_ENTRY_TYPE_MULTIBYTE) << "Invalid write log entry type";
+ }
+
+ // Verify the backing store erase
+ {
+ index = 3;
+ auto write_iter = inst.log_begin() + index;
+ e.raw64 = write_iter->value;
+ EXPECT_TRUE(write_iter->erased) << "Backing store erase did not occur as required";
+ }
+
+ // Verify the backing store writes for consolidation
+ for (index = 4; index < 6; ++index) {
+ auto write_iter = inst.log_begin() + index;
+ EXPECT_EQ(write_iter->address, (index - 4) * BACKING_STORE_WRITE_SIZE) << "Invalid write log entry address";
+ }
+
+ // Verify the FNV1a_64 write
+ {
+ EXPECT_EQ((inst.log_begin() + 6)->address, WEAR_LEVELING_LOGICAL_SIZE) << "Invalid write log address";
+ e.raw64 = (inst.log_begin() + 6)->value;
+ EXPECT_EQ(e.raw64, fnv_64a_buf(testvalue.data(), testvalue.size(), FNV1A_64_INIT)) << "Invalid checksum"; // Note that checksum is based on testvalue, as we overwrote one byte and need to consult the consolidated data, not the current
+ }
+
+ // Verify the final write
+ EXPECT_EQ((inst.log_begin() + 7)->address, WEAR_LEVELING_LOGICAL_SIZE + 8) << "Invalid write log address";
+
+ // Verify the data is what we expected
+ std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> readback;
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback did not match";
+
+ // Re-init and re-read, verifying the reload capability
+ EXPECT_NE(wear_leveling_init(), WEAR_LEVELING_FAILED) << "Re-initialisation failed";
+ EXPECT_EQ(wear_leveling_read(0, readback.data(), WEAR_LEVELING_LOGICAL_SIZE), WEAR_LEVELING_SUCCESS) << "Failed to read back the saved data";
+ EXPECT_TRUE(memcmp(readback.data(), verify_data.data(), WEAR_LEVELING_LOGICAL_SIZE) == 0) << "Readback did not match";
+}
+
+/**
+ * This test verifies multibyte readback gets canceled with an out-of-bounds address.
+ */
+TEST_F(WearLeveling8Byte, PlaybackReadbackMultibyte_OOB) {
+ auto& inst = MockBackingStore::Instance();
+ auto logstart = inst.storage_begin() + (WEAR_LEVELING_LOGICAL_SIZE / sizeof(backing_store_int_t));
+
+ // Invalid FNV1a_64 hash
+ (logstart + 0)->set(0);
+
+ // Set up a 2-byte logical write of [0x11,0x12] at logical offset 0x01
+ auto entry0 = LOG_ENTRY_MAKE_MULTIBYTE(0x01, 2);
+ entry0.raw8[3] = 0x11;
+ entry0.raw8[4] = 0x12;
+ (logstart + 1)->set(~entry0.raw64);
+
+ // Set up a 2-byte logical write of [0x13,0x14] at logical offset 0x1000 (out of bounds)
+ auto entry1 = LOG_ENTRY_MAKE_MULTIBYTE(0x1000, 2);
+ entry1.raw8[3] = 0x13;
+ entry1.raw8[4] = 0x14;
+ (logstart + 2)->set(~entry1.raw64);
+
+ // Set up a 2-byte logical write of [0x15,0x16] at logical offset 0x10
+ auto entry2 = LOG_ENTRY_MAKE_MULTIBYTE(0x01, 2);
+ entry2.raw8[3] = 0x15;
+ entry2.raw8[4] = 0x16;
+ (logstart + 3)->set(~entry2.raw64);
+
+ EXPECT_EQ(inst.erasure_count(), 0) << "Invalid initial erase count";
+ EXPECT_EQ(wear_leveling_init(), WEAR_LEVELING_CONSOLIDATED) << "Readback should have failed and triggered consolidation";
+ EXPECT_EQ(inst.erasure_count(), 1) << "Invalid final erase count";
+
+ uint8_t buf[2];
+ wear_leveling_read(0x01, buf, sizeof(buf));
+ EXPECT_EQ(buf[0], 0x11) << "Readback should have maintained the previous pre-failure value from the write log";
+ EXPECT_EQ(buf[1], 0x12) << "Readback should have maintained the previous pre-failure value from the write log";
+}
diff --git a/quantum/wear_leveling/tests/wear_leveling_general.cpp b/quantum/wear_leveling/tests/wear_leveling_general.cpp
new file mode 100644
index 0000000000..76a4bf7bf3
--- /dev/null
+++ b/quantum/wear_leveling/tests/wear_leveling_general.cpp
@@ -0,0 +1,204 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <numeric>
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "backing_mocks.hpp"
+
+class WearLevelingGeneral : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ MockBackingStore::Instance().reset_instance();
+ wear_leveling_init();
+ }
+};
+
+/**
+ * This test verifies that even if there is consolidated data present, if the checksum doesn't match then the cache is zero'd after reading the consolidated area, but before write log is played back.
+ */
+TEST_F(WearLevelingGeneral, InvalidChecksum_ConsolidatedDataIgnored) {
+ auto& inst = MockBackingStore::Instance();
+ auto logstart = inst.storage_begin() + (WEAR_LEVELING_LOGICAL_SIZE / sizeof(backing_store_int_t));
+
+ // Generate a test block of data
+ std::array<std::uint8_t, WEAR_LEVELING_LOGICAL_SIZE> testvalue;
+ std::iota(testvalue.begin(), testvalue.end(), 0x20);
+
+ // Write the data
+ EXPECT_EQ(wear_leveling_write(0, testvalue.data(), testvalue.size()), WEAR_LEVELING_CONSOLIDATED) << "Write returned incorrect status";
+
+ // Invalidate the checksum
+ (logstart + 0)->erase();
+ (logstart + 1)->erase();
+ (logstart + 2)->erase();
+ (logstart + 3)->erase();
+
+ // Set up a 1-byte logical write of [0x11] at logical offset 0x01
+ auto entry0 = LOG_ENTRY_MAKE_OPTIMIZED_64(0x01, 0x11);
+ (logstart + 4)->set(~entry0.raw16[0]);
+
+ // Re-init
+ EXPECT_EQ(wear_leveling_init(), WEAR_LEVELING_SUCCESS) << "Init returned incorrect status";
+ EXPECT_EQ(wear_leveling_read(0, testvalue.data(), testvalue.size()), WEAR_LEVELING_SUCCESS) << "Failed to read";
+ for (int i = 0; i < WEAR_LEVELING_LOGICAL_SIZE; ++i) {
+ EXPECT_EQ(testvalue[i], i == 0x01 ? 0x11 : 0x00) << "Invalid readback";
+ }
+}
+
+/**
+ * This test verifies that writing the same data multiple times does not result in subsequent writes to the backing store.
+ */
+TEST_F(WearLevelingGeneral, SameValue_SingleBackingWrite) {
+ auto& inst = MockBackingStore::Instance();
+
+ uint8_t test_val = 0x14;
+ EXPECT_EQ(wear_leveling_write(0x02, &test_val, sizeof(test_val)), WEAR_LEVELING_SUCCESS) << "First overall write operation should have succeeded";
+
+ uint64_t invoke_count = inst.unlock_invoke_count();
+ uint64_t erase_count = inst.erase_invoke_count();
+ uint64_t write_count = inst.write_invoke_count();
+ uint64_t lock_count = inst.lock_invoke_count();
+
+ for (int i = 0; i < 10; ++i) {
+ EXPECT_EQ(wear_leveling_write(0x02, &test_val, sizeof(test_val)), WEAR_LEVELING_SUCCESS) << "Subsequent overall write operation should have succeeded";
+
+ EXPECT_EQ(inst.unlock_invoke_count(), invoke_count) << "Unlock count should match";
+ EXPECT_EQ(inst.erase_invoke_count(), erase_count) << "Erase count should match";
+ EXPECT_EQ(inst.write_invoke_count(), write_count) << "Write count should match";
+ EXPECT_EQ(inst.lock_invoke_count(), lock_count) << "Lock count should match";
+ }
+}
+
+/**
+ * This test verifies that no other invocations occur if `backing_store_init()` fails.
+ */
+TEST_F(WearLevelingGeneral, InitFailure) {
+ auto& inst = MockBackingStore::Instance();
+ inst.reset_instance(); // make sure the counters are all zero
+ inst.set_init_callback([](std::uint64_t count) { return false; });
+
+ EXPECT_EQ(inst.erasure_count(), 0) << "Invalid initial erase count";
+ EXPECT_EQ(wear_leveling_init(), WEAR_LEVELING_FAILED) << "Init should have failed";
+ EXPECT_EQ(inst.erasure_count(), 0) << "Invalid final erase count";
+
+ EXPECT_EQ(inst.init_invoke_count(), 1) << "Init should have been invoked once";
+ EXPECT_EQ(inst.unlock_invoke_count(), 0) << "Unlock should not have been invoked";
+ EXPECT_EQ(inst.erase_invoke_count(), 0) << "Erase should not have been invoked";
+ EXPECT_EQ(inst.write_invoke_count(), 0) << "Write should not have been invoked";
+ EXPECT_EQ(inst.lock_invoke_count(), 0) << "Lock should not have been invoked";
+}
+
+/**
+ * This test verifies that no invocations occur if the supplied address is out of range while writing.
+ */
+TEST_F(WearLevelingGeneral, WriteFailure_OOB) {
+ auto& inst = MockBackingStore::Instance();
+
+ uint8_t test_val = 0x14;
+ EXPECT_EQ(wear_leveling_write(0x21349830, &test_val, sizeof(test_val)), WEAR_LEVELING_FAILED) << "Overall write operation should have failed";
+
+ EXPECT_EQ(inst.unlock_invoke_count(), 0) << "Unlock should not have been invoked";
+ EXPECT_EQ(inst.erase_invoke_count(), 0) << "Erase should not have been invoked";
+ EXPECT_EQ(inst.write_invoke_count(), 0) << "Write should not have been invoked";
+ EXPECT_EQ(inst.lock_invoke_count(), 0) << "Lock should not have been invoked";
+}
+
+/**
+ * This test verifies that a single write occurs if the supplied address and data length hits the edge of the logical area.
+ */
+TEST_F(WearLevelingGeneral, WriteSuccess_BoundaryOK) {
+ auto& inst = MockBackingStore::Instance();
+
+ uint16_t test_val = 0x14;
+ EXPECT_EQ(wear_leveling_write(WEAR_LEVELING_LOGICAL_SIZE - sizeof(test_val), &test_val, sizeof(test_val)), WEAR_LEVELING_SUCCESS) << "Overall write operation should have succeeded";
+
+ EXPECT_EQ(inst.unlock_invoke_count(), 1) << "Unlock should have been invoked once";
+ EXPECT_EQ(inst.erase_invoke_count(), 0) << "Erase should not have been invoked";
+ EXPECT_EQ(inst.write_invoke_count(), 2) << "Write should have been invoked twice";
+ EXPECT_EQ(inst.lock_invoke_count(), 1) << "Lock should have been invoked once";
+}
+
+/**
+ * This test verifies that no invocations occur if the supplied address and length would generate writes outside the logical range.
+ */
+TEST_F(WearLevelingGeneral, WriteFailure_BoundaryOverflow) {
+ auto& inst = MockBackingStore::Instance();
+
+ uint16_t test_val = 0x14;
+ EXPECT_EQ(wear_leveling_write(WEAR_LEVELING_LOGICAL_SIZE - sizeof(test_val) + 1, &test_val, sizeof(test_val)), WEAR_LEVELING_FAILED) << "Overall write operation should have failed";
+
+ EXPECT_EQ(inst.unlock_invoke_count(), 0) << "Unlock should not have been invoked";
+ EXPECT_EQ(inst.erase_invoke_count(), 0) << "Erase should not have been invoked";
+ EXPECT_EQ(inst.write_invoke_count(), 0) << "Write should not have been invoked";
+ EXPECT_EQ(inst.lock_invoke_count(), 0) << "Lock should not have been invoked";
+}
+
+/**
+ * This test verifies that no invocations occur if the supplied address is out of range while reading.
+ */
+TEST_F(WearLevelingGeneral, ReadFailure_OOB) {
+ auto& inst = MockBackingStore::Instance();
+
+ uint8_t test_val = 0;
+ EXPECT_EQ(wear_leveling_read(0x21349830, &test_val, sizeof(test_val)), WEAR_LEVELING_FAILED) << "Overall read operation should have failed";
+
+ EXPECT_EQ(inst.unlock_invoke_count(), 0) << "Unlock should not have been invoked";
+ EXPECT_EQ(inst.erase_invoke_count(), 0) << "Erase should not have been invoked";
+ EXPECT_EQ(inst.write_invoke_count(), 0) << "Write should not have been invoked";
+ EXPECT_EQ(inst.lock_invoke_count(), 0) << "Lock should not have been invoked";
+}
+
+/**
+ * This test verifies that no write invocations occur if `backing_store_unlock()` fails.
+ */
+TEST_F(WearLevelingGeneral, UnlockFailure_NoWrite) {
+ auto& inst = MockBackingStore::Instance();
+ inst.set_unlock_callback([](std::uint64_t count) { return false; });
+
+ uint8_t test_val = 0x14;
+ EXPECT_EQ(wear_leveling_write(0x04, &test_val, sizeof(test_val)), WEAR_LEVELING_FAILED) << "Overall write operation should have failed";
+
+ EXPECT_EQ(inst.unlock_invoke_count(), 1) << "Unlock should have been invoked once";
+ EXPECT_EQ(inst.erase_invoke_count(), 0) << "Erase should not have been invoked";
+ EXPECT_EQ(inst.write_invoke_count(), 0) << "Write should not have been invoked";
+ EXPECT_EQ(inst.lock_invoke_count(), 0) << "Lock should not have been invoked";
+
+ test_val = 0;
+ wear_leveling_read(0x04, &test_val, sizeof(test_val));
+ EXPECT_EQ(test_val, 0x14) << "Readback should come from cache regardless of unlock failure";
+}
+
+/**
+ * This test verifies that no erase invocations occur if `backing_store_unlock()` fails.
+ */
+TEST_F(WearLevelingGeneral, UnlockFailure_NoErase) {
+ auto& inst = MockBackingStore::Instance();
+ inst.set_unlock_callback([](std::uint64_t count) { return false; });
+
+ EXPECT_EQ(wear_leveling_erase(), WEAR_LEVELING_FAILED) << "Overall erase operation should have failed";
+
+ EXPECT_EQ(inst.unlock_invoke_count(), 1) << "Unlock should have been invoked once";
+ EXPECT_EQ(inst.erase_invoke_count(), 0) << "Erase should not have been invoked";
+ EXPECT_EQ(inst.write_invoke_count(), 0) << "Write should not have been invoked";
+ EXPECT_EQ(inst.lock_invoke_count(), 0) << "Lock should not have been invoked";
+}
+
+/**
+ * This test verifies that only one write invocation occurs if `backing_store_write()` fails.
+ */
+TEST_F(WearLevelingGeneral, WriteFailure_NoSubsequentWrites) {
+ auto& inst = MockBackingStore::Instance();
+ inst.set_write_callback([](std::uint64_t count, std::uint32_t address) { return false; });
+
+ uint8_t test_val = 0x14;
+ EXPECT_EQ(wear_leveling_write(0x04, &test_val, sizeof(test_val)), WEAR_LEVELING_FAILED) << "Overall write operation should have failed";
+
+ EXPECT_EQ(inst.unlock_invoke_count(), 1) << "Unlock should have been invoked once";
+ EXPECT_EQ(inst.erase_invoke_count(), 0) << "Erase should not have been invoked";
+ EXPECT_EQ(inst.write_invoke_count(), 1) << "Write should have been invoked once";
+ EXPECT_EQ(inst.lock_invoke_count(), 1) << "Lock should have been invoked once";
+
+ test_val = 0;
+ wear_leveling_read(0x04, &test_val, sizeof(test_val));
+ EXPECT_EQ(test_val, 0x14) << "Readback should come from cache regardless of unlock failure";
+}
diff --git a/quantum/wear_leveling/wear_leveling.c b/quantum/wear_leveling/wear_leveling.c
new file mode 100644
index 0000000000..429df45df5
--- /dev/null
+++ b/quantum/wear_leveling/wear_leveling.c
@@ -0,0 +1,768 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdbool.h>
+#include "fnv.h"
+#include "wear_leveling.h"
+#include "wear_leveling_internal.h"
+
+/*
+ This wear leveling algorithm is adapted from algorithms from previous
+ implementations in QMK, namely:
+ - Artur F. (http://engsta.com/stm32-flash-memory-eeprom-emulator/)
+ - Yiancar -- QMK's base implementation for STM32F303
+ - Ilya Zhuravlev -- initial wear leveling algorithm
+ - Don Kjer -- increased flash density algorithm
+ - Nick Brassel (@tzarc) -- decoupled for use on other peripherals
+
+ At this layer, it is assumed that any reads/writes from the backing store
+ have a "reset state" after erasure of zero.
+ It is up to the backing store to perform translation of values, such as
+ taking the complement in order to deal with flash memory's reset value.
+
+ Terminology:
+
+ - Backing store: this is the storage area used by the wear leveling
+ algorithm.
+
+ - Backing size: this is the amount of storage provided by the backing
+ store for use by the wear leveling algorithm.
+
+ - Backing write size: this is the minimum number of bytes the backing
+ store can write in a single operation.
+
+ - Logical data: this is the externally-visible "emulated EEPROM" that
+ external subsystems "see" when performing reads/writes.
+
+ - Logical size: this is the amount of storage available for use
+ externally. Effectively, the "size of the EEPROM".
+
+ - Write log: this is a section of the backing store used to keep track
+ of modifications without overwriting existing data. This log is
+ "played back" on startup such that any subsequent reads are capable
+ of returning the latest data.
+
+ - Consolidated data: this is a section of the backing store reserved for
+ use for the latest copy of logical data. This is only ever written
+ when the write log is full -- the latest values for the logical data
+ are written here and the write log is cleared.
+
+ Configurables:
+
+ - BACKING_STORE_WRITE_SIZE: The number of bytes requires for a write
+ operation. This is defined by the capabilities of the backing store.
+
+ - WEAR_LEVELING_BACKING_SIZE: The number of bytes provided by the
+ backing store for use by the wear leveling algorithm. This is
+ defined by the capabilities of the backing store. This value must
+ also be at least twice the size of the logical size, as well as a
+ multiple of the logical size.
+
+ - WEAR_LEVELING_LOGICAL_SIZE: The number of bytes externally visible
+ to other subsystems performing reads/writes. This must be a multiple
+ of the write size.
+
+ General algorithm:
+
+ During initialization:
+ * The contents of the consolidated data section are read into cache.
+ * The contents of the write log are "played back" and update the
+ cache accordingly.
+
+ During reads:
+ * Logical data is served from the cache.
+
+ During writes:
+ * The cache is updated with the new data.
+ * A new write log entry is appended to the log.
+ * If the log's full, data is consolidated and the write log cleared.
+
+ Write log structure:
+
+ The first 8 bytes of the write log are a FNV1a_64 hash of the contents
+ of the consolidated data area, in an attempt to detect and guard against
+ any data corruption.
+
+ The write log follows the hash:
+
+ Given that the algorithm needs to cater for 2-, 4-, and 8-byte writes,
+ a variable-length write log entry is used such that the minimal amount
+ of storage is used based off the backing store write size.
+
+ Firstly, an empty log entry is expected to be all zeros. If the backing
+ store uses 0xFF for cleared bytes, it should return the complement, such
+ that this wear-leveling algorithm "receives" zeros.
+
+ For multi-byte writes, up to 8 bytes will be used for each log entry,
+ depending on the size of backing store writes:
+
+ ╔ Multi-byte Log Entry (2, 4-byte) ═╗
+ ║00XXXYYY║YYYYYYYY║YYYYYYYY║AAAAAAAA║
+ ║ └┬┘└┬┘║└──┬───┘║└──┬───┘║└──┬───┘║
+ ║ LenAdd║ Address║ Address║Value[0]║
+ ╚════════╩════════╩════════╩════════╝
+ ╔ Multi-byte Log Entry (2-byte) ══════════════════════╗
+ ║00XXXYYY║YYYYYYYY║YYYYYYYY║AAAAAAAA║BBBBBBBB║CCCCCCCC║
+ ║ └┬┘└┬┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║
+ ║ LenAdd║ Address║ Address║Value[0]║Value[1]║Value[2]║
+ ╚════════╩════════╩════════╩════════╩════════╩════════╝
+ ╔ Multi-byte Log Entry (2, 4, 8-byte) ══════════════════════════════════╗
+ ║00XXXYYY║YYYYYYYY║YYYYYYYY║AAAAAAAA║BBBBBBBB║CCCCCCCC║DDDDDDDD║EEEEEEEE║
+ ║ └┬┘└┬┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║└──┬───┘║
+ ║ LenAdd║ Address║ Address║Value[0]║Value[1]║Value[2]║Value[3]║Value[4]║
+ ╚════════╩════════╩════════╩════════╩════════╩════════╩════════╩════════╝
+
+ 19 bits are used for the address, which allows for a max logical size of
+ 512kB. Up to 5 bytes can be included in a single log entry.
+
+ For 2-byte backing store writes, the last two bytes are optional
+ depending on the length of data to be written. Accordingly, either 3
+ or 4 backing store write operations will occur.
+ For 4-byte backing store writes, either one or two write operations
+ occur, depending on the length.
+ For 8-byte backing store writes, one write operation occur.
+
+ 2-byte backing store optimizations:
+
+ For single byte writes, addresses between 0...63 are encoded in a single
+ backing store write operation. 4- and 8-byte backing stores do not have
+ this optimization as it does not minimize the number of bytes written.
+
+ ╔ Byte-Entry ════╗
+ ║01XXXXXXYYYYYYYY║
+ ║ └─┬──┘└──┬───┘║
+ ║ Address Value ║
+ ╚════════════════╝
+ 0 <= Address < 0x40 (64)
+
+ A second optimization takes into account uint16_t writes of 0 or 1,
+ specifically catering for KC_NO and KC_TRANSPARENT in the dynamic keymap
+ subsystem. This is valid only for the first 16kB of logical data --
+ addresses outside this range will use the multi-byte encoding above.
+
+ ╔ U16-Encoded 0 ═╗
+ ║100XXXXXXXXXXXXX║
+ ║ │└─────┬─────┘║
+ ║ │Address >> 1 ║
+ ║ └── Value: 0 ║
+ ╚════════════════╝
+ 0 <= Address <= 0x3FFE (16382)
+
+ ╔ U16-Encoded 1 ═╗
+ ║101XXXXXXXXXXXXX║
+ ║ │└─────┬─────┘║
+ ║ │Address >> 1 ║
+ ║ └── Value: 1 ║
+ ╚════════════════╝
+ 0 <= Address <= 0x3FFE (16382) */
+
+/**
+ * Storage area for the wear-leveling cache.
+ */
+static struct __attribute__((__aligned__(BACKING_STORE_WRITE_SIZE))) {
+ __attribute__((__aligned__(BACKING_STORE_WRITE_SIZE))) uint8_t cache[(WEAR_LEVELING_LOGICAL_SIZE)];
+ uint32_t write_address;
+ bool unlocked;
+} wear_leveling;
+
+/**
+ * Locking helper: status
+ */
+typedef enum backing_store_lock_status_t { STATUS_FAILURE = 0, STATUS_SUCCESS, STATUS_UNCHANGED } backing_store_lock_status_t;
+
+/**
+ * Locking helper: unlock
+ */
+static inline backing_store_lock_status_t wear_leveling_unlock(void) {
+ if (wear_leveling.unlocked) {
+ return STATUS_UNCHANGED;
+ }
+ if (!backing_store_unlock()) {
+ return STATUS_FAILURE;
+ }
+ wear_leveling.unlocked = true;
+ return STATUS_SUCCESS;
+}
+
+/**
+ * Locking helper: lock
+ */
+static inline backing_store_lock_status_t wear_leveling_lock(void) {
+ if (!wear_leveling.unlocked) {
+ return STATUS_UNCHANGED;
+ }
+ if (!backing_store_lock()) {
+ return STATUS_FAILURE;
+ }
+ wear_leveling.unlocked = false;
+ return STATUS_SUCCESS;
+}
+
+/**
+ * Resets the cache, ensuring the write address is correctly initialised.
+ */
+static void wear_leveling_clear_cache(void) {
+ memset(wear_leveling.cache, 0, (WEAR_LEVELING_LOGICAL_SIZE));
+ wear_leveling.write_address = (WEAR_LEVELING_LOGICAL_SIZE) + 8; // +8 is due to the FNV1a_64 of the consolidated buffer
+}
+
+/**
+ * Reads the consolidated data from the backing store into the cache.
+ * Does not consider the write log.
+ */
+static wear_leveling_status_t wear_leveling_read_consolidated(void) {
+ wl_dprintf("Reading consolidated data\n");
+
+ wear_leveling_status_t status = WEAR_LEVELING_SUCCESS;
+ 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_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw16, 4);
+#elif BACKING_STORE_WRITE_SIZE == 4
+ 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) {
+ wl_dprintf("Checksum matches, consolidated data is correct\n");
+ } else {
+ wl_dprintf("Checksum mismatch, clearing cache\n");
+ wear_leveling_clear_cache();
+ }
+ }
+
+ // If we failed for any reason, then clear the cache
+ if (status == WEAR_LEVELING_FAILED) {
+ wear_leveling_clear_cache();
+ }
+
+ return status;
+}
+
+/**
+ * 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");
+
+ backing_store_lock_status_t lock_status = wear_leveling_unlock();
+ 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_bulk((WEAR_LEVELING_LOGICAL_SIZE), entry.raw16, 4)) {
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+#elif BACKING_STORE_WRITE_SIZE == 4
+ 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), entry.raw64)) {
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+#endif
+ } while (0);
+ }
+
+ if (lock_status == STATUS_SUCCESS) {
+ wear_leveling_lock();
+ }
+ return status;
+}
+
+/**
+ * Forces a write of the current cache.
+ * Erases the backing store, including the write log.
+ * During this operation, there is the potential for data loss if a power loss occurs.
+ */
+static wear_leveling_status_t wear_leveling_consolidate_force(void) {
+ wl_dprintf("Erasing backing store\n");
+
+ // Erase the backing store. Expectation is that any un-written values that are read back after this call come back as zero.
+ bool ok = backing_store_erase();
+ if (!ok) {
+ wl_dprintf("Failed to erase backing store\n");
+ return WEAR_LEVELING_FAILED;
+ }
+
+ // Write the cache to the first section of the backing store.
+ wear_leveling_status_t status = wear_leveling_write_consolidated();
+ if (status == WEAR_LEVELING_FAILED) {
+ wl_dprintf("Failed to write consolidated data\n");
+ }
+
+ // Next write of the log occurs after the consolidated values at the start of the backing store.
+ wear_leveling.write_address = (WEAR_LEVELING_LOGICAL_SIZE) + 8; // +8 due to the FNV1a_64 of the consolidated area
+
+ return status;
+}
+
+/**
+ * Potential write of the current cache to the backing store.
+ * Skipped if the current write log position is not at the end of the backing store.
+ * During this operation, there is the potential for data loss if a power loss occurs.
+ *
+ * @return true if consolidation occurred
+ */
+static wear_leveling_status_t wear_leveling_consolidate_if_needed(void) {
+ if (wear_leveling.write_address >= (WEAR_LEVELING_BACKING_SIZE)) {
+ return wear_leveling_consolidate_force();
+ }
+
+ return WEAR_LEVELING_SUCCESS;
+}
+
+/**
+ * Appends the supplied fixed-width entry to the write log, optionally consolidating if the log is full.
+ *
+ * @return true if consolidation occurred
+ */
+static wear_leveling_status_t wear_leveling_append_raw(backing_store_int_t value) {
+ bool ok = backing_store_write(wear_leveling.write_address, value);
+ if (!ok) {
+ wl_dprintf("Failed to write to backing store\n");
+ return WEAR_LEVELING_FAILED;
+ }
+ wear_leveling.write_address += (BACKING_STORE_WRITE_SIZE);
+ return wear_leveling_consolidate_if_needed();
+}
+
+/**
+ * Handles writing multi_byte-encoded data to the backing store.
+ *
+ * @return true if consolidation occurred
+ */
+static wear_leveling_status_t wear_leveling_write_raw_multibyte(uint32_t address, const void *value, size_t length) {
+ const uint8_t * p = value;
+ write_log_entry_t log = LOG_ENTRY_MAKE_MULTIBYTE(address, length);
+ for (size_t i = 0; i < length; ++i) {
+ log.raw8[3 + i] = p[i];
+ }
+
+ // Write to the backing store. See the multi-byte log format in the documentation header at the top of the file.
+ wear_leveling_status_t status;
+#if BACKING_STORE_WRITE_SIZE == 2
+ status = wear_leveling_append_raw(log.raw16[0]);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ return status;
+ }
+
+ status = wear_leveling_append_raw(log.raw16[1]);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ return status;
+ }
+
+ if (length > 1) {
+ status = wear_leveling_append_raw(log.raw16[2]);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ return status;
+ }
+ }
+
+ if (length > 3) {
+ status = wear_leveling_append_raw(log.raw16[3]);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ return status;
+ }
+ }
+#elif BACKING_STORE_WRITE_SIZE == 4
+ status = wear_leveling_append_raw(log.raw32[0]);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ return status;
+ }
+
+ if (length > 1) {
+ status = wear_leveling_append_raw(log.raw32[1]);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ return status;
+ }
+ }
+#elif BACKING_STORE_WRITE_SIZE == 8
+ status = wear_leveling_append_raw(log.raw64);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ return status;
+ }
+#endif
+ return status;
+}
+
+/**
+ * Handles the actual writing of logical data into the write log section of the backing store.
+ */
+static wear_leveling_status_t wear_leveling_write_raw(uint32_t address, const void *value, size_t length) {
+ const uint8_t * p = value;
+ size_t remaining = length;
+ wear_leveling_status_t status = WEAR_LEVELING_SUCCESS;
+ while (remaining > 0) {
+#if BACKING_STORE_WRITE_SIZE == 2
+ // Small-write optimizations - uint16_t, 0 or 1, address is even, address <16384:
+ if (remaining >= 2 && address % 2 == 0 && address < 16384) {
+ const uint16_t v = ((uint16_t)p[1]) << 8 | p[0]; // don't just dereference a uint16_t here -- if unaligned it generates faults on some MCUs
+ if (v == 0 || v == 1) {
+ const write_log_entry_t log = LOG_ENTRY_MAKE_WORD_01(address, v);
+ status = wear_leveling_append_raw(log.raw16[0]);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ // If consolidation occurred, then the cache has already been written to the consolidated area. No need to continue.
+ // If a failure occurred, pass it on.
+ return status;
+ }
+
+ remaining -= 2;
+ address += 2;
+ p += 2;
+ continue;
+ }
+ }
+
+ // Small-write optimizations - address<64:
+ if (address < 64) {
+ const write_log_entry_t log = LOG_ENTRY_MAKE_OPTIMIZED_64(address, *p);
+ status = wear_leveling_append_raw(log.raw16[0]);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ // If consolidation occurred, then the cache has already been written to the consolidated area. No need to continue.
+ // If a failure occurred, pass it on.
+ return status;
+ }
+
+ remaining--;
+ address++;
+ p++;
+ continue;
+ }
+#endif // BACKING_STORE_WRITE_SIZE == 2
+ const size_t this_length = remaining >= LOG_ENTRY_MULTIBYTE_MAX_BYTES ? LOG_ENTRY_MULTIBYTE_MAX_BYTES : remaining;
+ status = wear_leveling_write_raw_multibyte(address, p, this_length);
+ if (status != WEAR_LEVELING_SUCCESS) {
+ // If consolidation occurred, then the cache has already been written to the consolidated area. No need to continue.
+ // If a failure occurred, pass it on.
+ return status;
+ }
+ remaining -= this_length;
+ address += (uint32_t)this_length;
+ p += this_length;
+ }
+
+ return status;
+}
+
+/**
+ * "Replays" the write log from the backing store, updating the local cache with updated values.
+ */
+static wear_leveling_status_t wear_leveling_playback_log(void) {
+ wl_dprintf("Playback write log\n");
+
+ wear_leveling_status_t status = WEAR_LEVELING_SUCCESS;
+ bool cancel_playback = false;
+ uint32_t address = (WEAR_LEVELING_LOGICAL_SIZE) + 8; // +8 due to the FNV1a_64 of the consolidated area
+ while (!cancel_playback && address < (WEAR_LEVELING_BACKING_SIZE)) {
+ backing_store_int_t value;
+ bool ok = backing_store_read(address, &value);
+ if (!ok) {
+ wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
+ cancel_playback = true;
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+ if (value == 0) {
+ wl_dprintf("Found empty slot, no more log entries\n");
+ cancel_playback = true;
+ break;
+ }
+
+ // If we got a nonzero value, then we need to increment the address to ensure next write occurs at next location
+ address += (BACKING_STORE_WRITE_SIZE);
+
+ // Read from the write log
+ write_log_entry_t log;
+#if BACKING_STORE_WRITE_SIZE == 2
+ log.raw16[0] = value;
+#elif BACKING_STORE_WRITE_SIZE == 4
+ log.raw32[0] = value;
+#elif BACKING_STORE_WRITE_SIZE == 8
+ log.raw64 = value;
+#endif
+
+ switch (LOG_ENTRY_GET_TYPE(log)) {
+ case LOG_ENTRY_TYPE_MULTIBYTE: {
+#if BACKING_STORE_WRITE_SIZE == 2
+ ok = backing_store_read(address, &log.raw16[1]);
+ if (!ok) {
+ wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
+ cancel_playback = true;
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+ address += (BACKING_STORE_WRITE_SIZE);
+#endif // BACKING_STORE_WRITE_SIZE == 2
+ const uint32_t a = LOG_ENTRY_MULTIBYTE_GET_ADDRESS(log);
+ const uint8_t l = LOG_ENTRY_MULTIBYTE_GET_LENGTH(log);
+
+ if (a + l > (WEAR_LEVELING_LOGICAL_SIZE)) {
+ cancel_playback = true;
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+
+#if BACKING_STORE_WRITE_SIZE == 2
+ if (l > 1) {
+ ok = backing_store_read(address, &log.raw16[2]);
+ if (!ok) {
+ wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
+ cancel_playback = true;
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+ address += (BACKING_STORE_WRITE_SIZE);
+ }
+ if (l > 3) {
+ ok = backing_store_read(address, &log.raw16[3]);
+ if (!ok) {
+ wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
+ cancel_playback = true;
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+ address += (BACKING_STORE_WRITE_SIZE);
+ }
+#elif BACKING_STORE_WRITE_SIZE == 4
+ if (l > 1) {
+ ok = backing_store_read(address, &log.raw32[1]);
+ if (!ok) {
+ wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
+ cancel_playback = true;
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+ address += (BACKING_STORE_WRITE_SIZE);
+ }
+#endif
+
+ memcpy(&wear_leveling.cache[a], &log.raw8[3], l);
+ } break;
+#if BACKING_STORE_WRITE_SIZE == 2
+ case LOG_ENTRY_TYPE_OPTIMIZED_64: {
+ const uint32_t a = LOG_ENTRY_OPTIMIZED_64_GET_ADDRESS(log);
+ const uint8_t v = LOG_ENTRY_OPTIMIZED_64_GET_VALUE(log);
+
+ if (a >= (WEAR_LEVELING_LOGICAL_SIZE)) {
+ cancel_playback = true;
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+
+ wear_leveling.cache[a] = v;
+ } break;
+ case LOG_ENTRY_TYPE_WORD_01: {
+ const uint32_t a = LOG_ENTRY_WORD_01_GET_ADDRESS(log);
+ const uint8_t v = LOG_ENTRY_WORD_01_GET_VALUE(log);
+
+ if (a + 1 >= (WEAR_LEVELING_LOGICAL_SIZE)) {
+ cancel_playback = true;
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+
+ wear_leveling.cache[a + 0] = v;
+ wear_leveling.cache[a + 1] = 0;
+ } break;
+#endif // BACKING_STORE_WRITE_SIZE == 2
+ default: {
+ cancel_playback = true;
+ status = WEAR_LEVELING_FAILED;
+ } break;
+ }
+ }
+
+ // We've reached the end of the log, so we're at the new write location
+ wear_leveling.write_address = address;
+
+ if (status == WEAR_LEVELING_FAILED) {
+ // If we had a failure during readback, assume we're corrupted -- force a consolidation with the data we already have
+ status = wear_leveling_consolidate_force();
+ } else {
+ // Consolidate the cache + write log if required
+ status = wear_leveling_consolidate_if_needed();
+ }
+
+ return status;
+}
+
+/**
+ * Wear-leveling initialization
+ */
+wear_leveling_status_t wear_leveling_init(void) {
+ wl_dprintf("Init\n");
+
+ // Reset the cache
+ wear_leveling_clear_cache();
+
+ // Initialise the backing store
+ if (!backing_store_init()) {
+ // If it failed, clear the cache and return with failure
+ wear_leveling_clear_cache();
+ return WEAR_LEVELING_FAILED;
+ }
+
+ // Read the previous consolidated values, then replay the existing write log so that the cache has the "live" values
+ wear_leveling_status_t status = wear_leveling_read_consolidated();
+ if (status == WEAR_LEVELING_FAILED) {
+ // If it failed, clear the cache and return with failure
+ wear_leveling_clear_cache();
+ return status;
+ }
+
+ status = wear_leveling_playback_log();
+ if (status == WEAR_LEVELING_FAILED) {
+ // If it failed, clear the cache and return with failure
+ wear_leveling_clear_cache();
+ return status;
+ }
+
+ return status;
+}
+
+/**
+ * Wear-leveling erase.
+ * Post-condition: any reads from the backing store directly after an erase operation must come back as zero.
+ */
+wear_leveling_status_t wear_leveling_erase(void) {
+ wl_dprintf("Erase\n");
+
+ // Unlock the backing store
+ backing_store_lock_status_t lock_status = wear_leveling_unlock();
+ if (lock_status == STATUS_FAILURE) {
+ wear_leveling_lock();
+ return WEAR_LEVELING_FAILED;
+ }
+
+ // Perform the erase
+ bool ret = backing_store_erase();
+ wear_leveling_clear_cache();
+
+ // Lock the backing store if we acquired the lock successfully
+ if (lock_status == STATUS_SUCCESS) {
+ ret &= (wear_leveling_lock() != STATUS_FAILURE);
+ }
+
+ return ret ? WEAR_LEVELING_SUCCESS : WEAR_LEVELING_FAILED;
+}
+
+/**
+ * Writes logical data into the backing store. Skips writes if there are no changes to values.
+ */
+wear_leveling_status_t wear_leveling_write(const uint32_t address, const void *value, size_t length) {
+ wl_assert(address + length <= (WEAR_LEVELING_LOGICAL_SIZE));
+ if (address + length > (WEAR_LEVELING_LOGICAL_SIZE)) {
+ return WEAR_LEVELING_FAILED;
+ }
+
+ wl_dprintf("Write ");
+ wl_dump(address, value, length);
+
+ // Skip write if there's no change compared to the current cached value
+ if (memcmp(value, &wear_leveling.cache[address], length) == 0) {
+ return true;
+ }
+
+ // Update the cache before writing to the backing store -- if we hit the end of the backing store during writes to the log then we'll force a consolidation in-line
+ memcpy(&wear_leveling.cache[address], value, length);
+
+ // Unlock the backing store
+ backing_store_lock_status_t lock_status = wear_leveling_unlock();
+ if (lock_status == STATUS_FAILURE) {
+ wear_leveling_lock();
+ return WEAR_LEVELING_FAILED;
+ }
+
+ // Perform the actual write
+ wear_leveling_status_t status = wear_leveling_write_raw(address, value, length);
+ switch (status) {
+ case WEAR_LEVELING_CONSOLIDATED:
+ case WEAR_LEVELING_FAILED:
+ // If the write triggered consolidation, or the write failed, then nothing else needs to occur.
+ break;
+
+ case WEAR_LEVELING_SUCCESS:
+ // Consolidate the cache + write log if required
+ status = wear_leveling_consolidate_if_needed();
+ break;
+
+ default:
+ // Unsure how we'd get here...
+ status = WEAR_LEVELING_FAILED;
+ break;
+ }
+
+ if (lock_status == STATUS_SUCCESS) {
+ if (wear_leveling_lock() == STATUS_FAILURE) {
+ status = WEAR_LEVELING_FAILED;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * Reads logical data from the cache.
+ */
+wear_leveling_status_t wear_leveling_read(const uint32_t address, void *value, size_t length) {
+ wl_assert(address + length <= (WEAR_LEVELING_LOGICAL_SIZE));
+ if (address + length > (WEAR_LEVELING_LOGICAL_SIZE)) {
+ return WEAR_LEVELING_FAILED;
+ }
+
+ // Only need to copy from the cache
+ memcpy(value, &wear_leveling.cache[address], length);
+
+ wl_dprintf("Read ");
+ 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;
+}
diff --git a/quantum/wear_leveling/wear_leveling.h b/quantum/wear_leveling/wear_leveling.h
new file mode 100644
index 0000000000..6641bc49b3
--- /dev/null
+++ b/quantum/wear_leveling/wear_leveling.h
@@ -0,0 +1,54 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+#include <stdint.h>
+#include <stdlib.h>
+
+/**
+ * @typedef Status returned from any wear-leveling API.
+ */
+typedef enum wear_leveling_status_t {
+ WEAR_LEVELING_FAILED, //< Invocation failed
+ WEAR_LEVELING_SUCCESS, //< Invocation succeeded
+ WEAR_LEVELING_CONSOLIDATED //< Invocation succeeded, consolidation occurred
+} wear_leveling_status_t;
+
+/**
+ * Wear-leveling initialization
+ *
+ * @return Status of the request
+ */
+wear_leveling_status_t wear_leveling_init(void);
+
+/**
+ * Wear-leveling erasure.
+ *
+ * Clears the wear-leveling area, with the definition that the "reset state" of all data is zero.
+ *
+ * @return Status of the request
+ */
+wear_leveling_status_t wear_leveling_erase(void);
+
+/**
+ * Writes logical data into the backing store.
+ *
+ * Skips writes if there are no changes to written values. The entire written block is considered when attempting to
+ * determine if an overwrite should occur -- if there is any data mismatch the entire block will be written to the log,
+ * not just the changed bytes.
+ *
+ * @param address[in] the logical address to write data
+ * @param value[in] pointer to the source buffer
+ * @param length[in] length of the data
+ * @return Status of the request
+ */
+wear_leveling_status_t wear_leveling_write(uint32_t address, const void* value, size_t length);
+
+/**
+ * Reads logical data from the cache.
+ *
+ * @param address[in] the logical address to read data
+ * @param value[out] pointer to the destination buffer
+ * @param length[in] length of the data
+ * @return Status of the request
+ */
+wear_leveling_status_t wear_leveling_read(uint32_t address, void* value, size_t length);
diff --git a/quantum/wear_leveling/wear_leveling_internal.h b/quantum/wear_leveling/wear_leveling_internal.h
new file mode 100644
index 0000000000..e83f9b22ea
--- /dev/null
+++ b/quantum/wear_leveling/wear_leveling_internal.h
@@ -0,0 +1,151 @@
+// Copyright 2022 Nick Brassel (@tzarc)
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+#ifdef __cplusplus
+# define _Static_assert static_assert
+#endif
+
+#include <stdint.h>
+#include <string.h>
+
+#if BACKING_STORE_WRITE_SIZE == 2
+typedef uint16_t backing_store_int_t;
+#elif BACKING_STORE_WRITE_SIZE == 4
+typedef uint32_t backing_store_int_t;
+#elif BACKING_STORE_WRITE_SIZE == 8
+typedef uint64_t backing_store_int_t;
+#else
+# error Invalid BACKING_STORE_WRITE_SIZE, needs to be 2/4/8.
+#endif
+
+#ifndef WEAR_LEVELING_BACKING_SIZE
+# error WEAR_LEVELING_BACKING_SIZE was not set.
+#endif
+
+#ifndef WEAR_LEVELING_LOGICAL_SIZE
+# error WEAR_LEVELING_LOGICAL_SIZE was not set.
+#endif
+
+#ifdef WEAR_LEVELING_DEBUG_OUTPUT
+# 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 { \
+ 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("\n"); \
+ } while (0)
+#else
+# define wl_dprintf(...) \
+ do { \
+ } while (0)
+# define bs_dprintf(...) \
+ do { \
+ } while (0)
+# define wl_dump(...) \
+ do { \
+ } while (0)
+#endif // WEAR_LEVELING_DEBUG_OUTPUT
+
+#ifdef WEAR_LEVELING_ASSERTS
+# include <assert.h>
+# define wl_assert(...) assert(__VA_ARGS__)
+#else
+# define wl_assert(...) \
+ do { \
+ } while (0)
+#endif // WEAR_LEVELING_ASSERTS
+
+// Compile-time validation of configurable options
+_Static_assert(WEAR_LEVELING_BACKING_SIZE >= (WEAR_LEVELING_LOGICAL_SIZE * 2), "Total backing size must be at least twice the size of the logical size");
+_Static_assert(WEAR_LEVELING_LOGICAL_SIZE % BACKING_STORE_WRITE_SIZE == 0, "Logical size must be a multiple of write size");
+_Static_assert(WEAR_LEVELING_BACKING_SIZE % WEAR_LEVELING_LOGICAL_SIZE == 0, "Backing size must be a multiple of logical size");
+
+// Backing Store API, to be implemented elsewhere by flash driver etc.
+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.
+ */
+typedef union write_log_entry_t {
+ uint64_t raw64;
+ uint32_t raw32[2];
+ uint16_t raw16[4];
+ uint8_t raw8[8];
+} write_log_entry_t;
+
+_Static_assert(sizeof(write_log_entry_t) == 8, "Wear leveling write log entry size was not 8");
+
+/**
+ * Log entry type discriminator.
+ */
+enum {
+ // 0x00 -- Multi-byte storage type
+ LOG_ENTRY_TYPE_MULTIBYTE,
+
+ // 0x01 -- 2-byte backing store write optimization: address < 64
+ LOG_ENTRY_TYPE_OPTIMIZED_64,
+
+ // 0x02 -- 2-byte backing store write optimization: word-encoded 0/1 values
+ LOG_ENTRY_TYPE_WORD_01,
+
+ LOG_ENTRY_TYPES
+};
+
+_Static_assert(LOG_ENTRY_TYPES <= (1 << 2), "Too many log entry types to fit into 2 bits of storage");
+
+#define BITMASK_FOR_BITCOUNT(n) ((1 << (n)) - 1)
+
+#define LOG_ENTRY_GET_TYPE(entry) (((entry).raw8[0] >> 6) & BITMASK_FOR_BITCOUNT(2))
+
+#define LOG_ENTRY_MULTIBYTE_MAX_BYTES 5
+#define LOG_ENTRY_MULTIBYTE_GET_ADDRESS(entry) (((((uint32_t)((entry).raw8[0])) & BITMASK_FOR_BITCOUNT(3)) << 16) | (((uint32_t)((entry).raw8[1])) << 8) | (entry).raw8[2])
+#define LOG_ENTRY_MULTIBYTE_GET_LENGTH(entry) ((uint8_t)(((entry).raw8[0] >> 3) & BITMASK_FOR_BITCOUNT(3)))
+#define LOG_ENTRY_MAKE_MULTIBYTE(address, length) \
+ (write_log_entry_t) { \
+ .raw8 = { \
+ [0] = (((((uint8_t)LOG_ENTRY_TYPE_MULTIBYTE) & BITMASK_FOR_BITCOUNT(2)) << 6) /* type */ \
+ | ((((uint8_t)(length)) & BITMASK_FOR_BITCOUNT(3)) << 3) /* length */ \
+ | ((((uint8_t)((address) >> 16))) & BITMASK_FOR_BITCOUNT(3)) /* address */ \
+ ), \
+ [1] = (((uint8_t)((address) >> 8)) & BITMASK_FOR_BITCOUNT(8)), /* address */ \
+ [2] = (((uint8_t)(address)) & BITMASK_FOR_BITCOUNT(8)), /* address */ \
+ } \
+ }
+
+#define LOG_ENTRY_OPTIMIZED_64_GET_ADDRESS(entry) ((uint32_t)((entry).raw8[0] & BITMASK_FOR_BITCOUNT(6)))
+#define LOG_ENTRY_OPTIMIZED_64_GET_VALUE(entry) ((entry).raw8[1])
+#define LOG_ENTRY_MAKE_OPTIMIZED_64(address, value) \
+ (write_log_entry_t) { \
+ .raw8 = { \
+ [0] = (((((uint8_t)LOG_ENTRY_TYPE_OPTIMIZED_64) & BITMASK_FOR_BITCOUNT(2)) << 6) /* type */ \
+ | ((((uint8_t)(address))) & BITMASK_FOR_BITCOUNT(6)) /* address */ \
+ ), \
+ [1] = ((uint8_t)(value)), /* value */ \
+ } \
+ }
+
+#define LOG_ENTRY_WORD_01_GET_ADDRESS(entry) ((((uint32_t)(((entry).raw8[0]) & BITMASK_FOR_BITCOUNT(5))) << 9) | (((uint32_t)((entry).raw8[1])) << 1))
+#define LOG_ENTRY_WORD_01_GET_VALUE(entry) ((uint8_t)((entry).raw8[0] >> 5) & BITMASK_FOR_BITCOUNT(1))
+#define LOG_ENTRY_MAKE_WORD_01(address, value) \
+ (write_log_entry_t) { \
+ .raw8 = { \
+ [0] = (((((uint8_t)LOG_ENTRY_TYPE_WORD_01) & BITMASK_FOR_BITCOUNT(2)) << 6) /* type */ \
+ | (((((uint8_t)((value) ? 1 : 0))) & BITMASK_FOR_BITCOUNT(1)) << 5) /* value */ \
+ | ((((uint8_t)((address) >> 9))) & BITMASK_FOR_BITCOUNT(5)) /* address */ \
+ ), \
+ [1] = (uint8_t)((address) >> 1), /* address */ \
+ } \
+ }