Browse Source

Merge pull request #3264 from fredizzimo/multimatrix

Add multi-matrix support and make the layer cache more efficient
Jack Humbert 7 năm trước cách đây
mục cha
commit
47c27bb6b9

+ 43 - 56
drivers/qwiic/qwiic_keyboard.c

@@ -37,15 +37,15 @@
 void qwiic_keyboard_write_keymap(uint8_t * pointer);
 void qwiic_keyboard_read_keymap(uint8_t * pointer);
 
-bool qwiic_keyboard_master = false;
-bool qwiic_keyboard_connected = false;
-uint8_t qwiic_keyboard_handshake_message[QWIIC_KEYBOARD_HANDSHAKE_MESSAGE_SIZE] = {0};
-uint8_t qwiic_keyboard_matrix_message[QWIIC_KEYBOARD_ROWS] = {0};
-twi2c_message_received qwiic_keyboard_message_received_ptr = qwiic_keyboard_message_received;
+static bool qwiic_keyboard_master = false;
+static bool qwiic_keyboard_connected = false;
+static uint8_t qwiic_keyboard_handshake_message[QWIIC_KEYBOARD_HANDSHAKE_MESSAGE_SIZE] = {0};
+static uint8_t qwiic_keyboard_matrix_message[QWIIC_KEYBOARD_ROWS] = {0};
+static qwiic_matrix_t matrix_prev[QWIIC_KEYBOARD_ROWS] = {0};
+static twi2c_message_received qwiic_keyboard_message_received_ptr = qwiic_keyboard_message_received;
 
-uint16_t qwiic_keyboard_keymap[QWIIC_KEYBOARD_LAYERS][QWIIC_KEYBOARD_ROWS][QWIIC_KEYBOARD_COLS] = {0};
-uint8_t qwiic_keyboard_listening_address = QWIIC_KEYBOARD_LISTENING_ADDRESS_START;
-uint8_t qwiic_keyboard_processing_slave = false;
+static uint16_t qwiic_keyboard_keymap[QWIIC_KEYBOARD_LAYERS][QWIIC_KEYBOARD_ROWS][QWIIC_KEYBOARD_COLS] = {{{0}}};
+static uint8_t qwiic_keyboard_listening_address = QWIIC_KEYBOARD_LISTENING_ADDRESS_START;
 
 void qwiic_keyboard_init(void) {
   twi2c_init();
@@ -69,50 +69,10 @@ void qwiic_keyboard_task(void) {
   if (qwiic_keyboard_master) {
     if (qwiic_keyboard_connected) {
       // send empty message, expecting matrix info
-      if (MSG_OK == twi2c_transmit_receive(qwiic_keyboard_listening_address,
+      if (MSG_OK != twi2c_transmit_receive(qwiic_keyboard_listening_address,
         command, 1,
         qwiic_keyboard_matrix_message, QWIIC_KEYBOARD_MATRIX_MESSAGE_SIZE
       )) {
-        // majority of this is pulled from keyboard.c:keyboard_task()
-        static qwiic_matrix_t matrix_prev[QWIIC_KEYBOARD_ROWS];
-        qwiic_matrix_t matrix_row = 0;
-        qwiic_matrix_t matrix_change = 0;
-        #ifdef QMK_KEYS_PER_SCAN
-          uint8_t keys_processed = 0;
-        #endif
-        qwiic_keyboard_processing_slave = true;
-        for (uint8_t r = 0; r < QWIIC_KEYBOARD_ROWS; r++) {
-          matrix_row = qwiic_keyboard_matrix_message[r];
-          matrix_change = matrix_row ^ matrix_prev[r];
-          if (matrix_change) {
-            for (uint8_t c = 0; c < QWIIC_KEYBOARD_COLS; c++) {
-              if (matrix_change & ((qwiic_matrix_t)1<<c)) {
-                action_exec((keyevent_t){
-                  .key = (keypos_t){ .row = r, .col = c },
-                  .pressed = (matrix_row & ((qwiic_matrix_t)1<<c)),
-                  .time = (timer_read() | 1) /* time should not be 0 */
-                });
-                // record a processed key
-                matrix_prev[r] ^= ((qwiic_matrix_t)1<<c);
-                #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 QWIIC_MATRIX_LOOP_END;
-              }
-            }
-          }
-        }
-        // 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);
-        QWIIC_MATRIX_LOOP_END:
-        qwiic_keyboard_processing_slave = false;
-      } else {
         // disconnect
         // qwiic_keyboard_connected = false;
       }
@@ -190,13 +150,40 @@ bool is_keyboard_master(void) {
 }
 
 // overwrite the built-in function
-uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t key) {
-  if (qwiic_keyboard_processing_slave) {
-    // trick the built-in handling to accept our replacement keymap
-    return qwiic_keyboard_keymap[(layer)][(key.row)][(key.col)];
-    //return KC_A;
-  } else {
+uint16_t keymap_key_to_keycode(uint8_t layer, keymatrix_t key) {
+  if (key.matrix == 0) {
     // Read entire word (16bits)
-    return pgm_read_word(&keymaps[(layer)][(key.row)][(key.col)]);
+    return pgm_read_word(&keymaps[(layer)][(key.pos.row)][(key.pos.col)]);
+  } else {
+    return qwiic_keyboard_keymap[(layer)][(key.pos.row)][(key.pos.col)];
   }
 }
+
+uint8_t multimatrix_get_num_matrices(void) {
+  return qwiic_keyboard_connected ? 1 : 0;
+}
+
+uint8_t multimatrix_get_num_cols(uint8_t matrix) {
+  return QWIIC_KEYBOARD_COLS;
+}
+
+uint8_t multimatrix_get_num_rows(uint8_t matrix) {
+  return QWIIC_KEYBOARD_ROWS;
+}
+
+uint32_t multimatrix_get_row(uint8_t matrix, uint8_t row) {
+return qwiic_keyboard_matrix_message[row];
+}
+
+uint32_t multimatrix_get_row_cache(uint8_t matrix, uint8_t row) {
+  return matrix_prev[row];
+}
+
+void multimatrix_set_row_cache(uint8_t matrix, uint8_t row, uint32_t value) {
+  matrix_prev[row] = value;
+}
+
+uint8_t* multimatrix_get_source_layers_cache(uint8_t matrix) {
+    static uint8_t source_layers_cache[(QWIIC_KEYBOARD_ROWS * QWIIC_KEYBOARD_COLS * MAX_LAYER_BITS + 7) / 8] = {0};
+    return source_layers_cache;
+}

+ 1 - 1
keyboards/planck/old_keymap_files/keymap_common.c

@@ -18,7 +18,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
 /* translates key to keycode */
-uint8_t keymap_key_to_keycode(uint8_t layer, keypos_t key)
+uint8_t keymap_key_to_keycode(uint8_t layer, keymatrix_t key)
 {
     return pgm_read_byte(&keymaps[(layer)][(key.row)][(key.col)]);
 }

+ 1 - 1
quantum/keymap.h

@@ -44,7 +44,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include "quantum_keycodes.h"
 
 // translates key to keycode
-uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t key);
+uint16_t keymap_key_to_keycode(uint8_t layer, keymatrix_t key);
 
 // translates function id to action
 uint16_t keymap_function_id_to_action( uint16_t function_id );

+ 5 - 3
quantum/keymap_common.c

@@ -38,7 +38,7 @@ extern keymap_config_t keymap_config;
 #include <inttypes.h>
 
 /* converts key to action */
-action_t action_for_key(uint8_t layer, keypos_t key)
+action_t action_for_key(uint8_t layer, keymatrix_t key)
 {
     // 16bit keycodes - important
     uint16_t keycode = keymap_key_to_keycode(layer, key);
@@ -184,10 +184,12 @@ void action_function(keyrecord_t *record, uint8_t id, uint8_t opt)
 
 // translates key to keycode
 __attribute__ ((weak))
-uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t key)
+uint16_t keymap_key_to_keycode(uint8_t layer, keymatrix_t key)
 {
     // Read entire word (16bits)
-    return pgm_read_word(&keymaps[(layer)][(key.row)][(key.col)]);
+    // The default implmention does not have multiple matrix support
+    // and therfore it ignores the matrix field of the key
+    return pgm_read_word(&keymaps[(layer)][(key.pos.row)][(key.pos.col)]);
 }
 
 // translates function id to action

+ 7 - 6
quantum/process_keycode/process_music.c

@@ -198,22 +198,23 @@ bool process_music(uint16_t keycode, keyrecord_t *record) {
       }
 
       uint8_t note = 36;
+      // Note: Multimatrix support is missing from this (it's probablly better to define it using keycodes)
       #ifdef MUSIC_MAP
         if (music_mode == MUSIC_MODE_CHROMATIC) {
-          note = music_starting_note + music_offset + 36 + music_map[record->event.key.row][record->event.key.col];
+          note = music_starting_note + music_offset + 36 + music_map[record->event.key.pos.row][record->event.key.pos.col];
         } else {
-          uint8_t position = music_map[record->event.key.row][record->event.key.col];
+          uint8_t position = music_map[record->event.key.pos.row][record->event.key.pos.col];
           note = music_starting_note + music_offset + 36 + SCALE[position % 12] + (position / 12)*12;
         }
       #else
         if (music_mode == MUSIC_MODE_CHROMATIC)
-          note = (music_starting_note + record->event.key.col + music_offset - 3)+12*(MATRIX_ROWS - record->event.key.row);
+          note = (music_starting_note + record->event.key.pos.col + music_offset - 3)+12*(MATRIX_ROWS - record->event.key.pos.row);
         else if (music_mode == MUSIC_MODE_GUITAR)
-          note = (music_starting_note + record->event.key.col + music_offset + 32)+5*(MATRIX_ROWS - record->event.key.row);
+          note = (music_starting_note + record->event.key.pos.col + music_offset + 32)+5*(MATRIX_ROWS - record->event.key.pos.row);
         else if (music_mode == MUSIC_MODE_VIOLIN)
-          note = (music_starting_note + record->event.key.col + music_offset + 32)+7*(MATRIX_ROWS - record->event.key.row);
+          note = (music_starting_note + record->event.key.pos.col + music_offset + 32)+7*(MATRIX_ROWS - record->event.key.pos.row);
         else if (music_mode == MUSIC_MODE_MAJOR)
-          note = (music_starting_note + SCALE[record->event.key.col + music_offset] - 3)+12*(MATRIX_ROWS - record->event.key.row);
+          note = (music_starting_note + SCALE[record->event.key.pos.col + music_offset] - 3)+12*(MATRIX_ROWS - record->event.key.pos.row);
         else
           note = music_starting_note;
       #endif

+ 1 - 1
quantum/quantum.c

@@ -190,7 +190,7 @@ static bool grave_esc_was_shifted = false;
 bool process_record_quantum(keyrecord_t *record) {
 
   /* This gets the keycode from the key pressed */
-  keypos_t key = record->event.key;
+  keymatrix_t key = record->event.key;
   uint16_t keycode;
 
   #if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS)

+ 2 - 1
quantum/rgb_matrix.c

@@ -119,7 +119,8 @@ void rgb_matrix_set_color_all( uint8_t red, uint8_t green, uint8_t blue ) {
 bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record) {
     if ( record->event.pressed ) {
         uint8_t led[8], led_count;
-        map_row_column_to_led(record->event.key.row, record->event.key.col, led, &led_count);
+        // TODO: Support multi-matrix keyboards
+        map_row_column_to_led(record->event.key.pos.row, record->event.key.pos.col, led, &led_count);
         if (led_count > 0) {
             for (uint8_t i = LED_HITS_TO_REMEMBER; i > 1; i--) {
                 g_last_led_hit[i - 1] = g_last_led_hit[i - 2];

+ 21 - 0
tests/layer_cache/config.h

@@ -0,0 +1,21 @@
+/* Copyright 2018 Fred Sundvik
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#define MATRIX_ROWS 2
+#define MATRIX_COLS 2
+#define PREVENT_STUCK_MODIFIERS

+ 24 - 0
tests/layer_cache/keymap.c

@@ -0,0 +1,24 @@
+/* Copyright 2018 Fred Sundvik
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "quantum.h"
+
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+    [0] = {
+      {KC_A, KC_B},
+      {KC_C, KC_D},
+    }
+};

+ 16 - 0
tests/layer_cache/rules.mk

@@ -0,0 +1,16 @@
+# Copyright 2018 Fred Sundvik
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+CUSTOM_MATRIX=yes

+ 142 - 0
tests/layer_cache/test_layer_cache.cpp

@@ -0,0 +1,142 @@
+/* Copyright 2018 Fred Sundvik
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "test_common.hpp"
+
+#if MAX_LAYER_BITS != 5
+#error "Tese tests assume that the MAX_LAYER_BITS is equal to 5"
+// If this is changed, change the constants below
+#endif
+
+#if MATRIX_COLS != 2 || MATRIX_ROWS !=2
+#error "These tests assume that the second row starts after the second column"
+#endif
+
+namespace
+{
+  constexpr uint8_t max_layer_value = 0b11111;
+  constexpr uint8_t min_layer_value = 0;
+  constexpr uint8_t alternating_starting_with_1 = 0b10101;
+  constexpr uint8_t alternating_starting_with_0 = 0b01010;
+
+
+  uint8_t read_cache(uint8_t col, uint8_t row) {
+    keymatrix_t key;
+    key.pos.col = col;
+    key.pos.row =  row;
+    key.matrix = 0;
+    return read_source_layers_cache(key);
+  }
+
+  void write_cache(uint8_t col, uint8_t row, uint8_t value) {
+    keymatrix_t key;
+    key.pos.col = col;
+    key.pos.row = row;
+    key.matrix = 0;
+    return update_source_layers_cache(key, value);
+  }
+
+  void fill_cache() {
+    for (int i=0; i < MATRIX_ROWS; i++) {
+      for (int j=0; j < MATRIX_COLS; j++) {
+        write_cache(j, i, max_layer_value);
+      }
+    }
+  }
+
+  void clear_cache() {
+    for (int i=0; i < MATRIX_ROWS; i++) {
+      for (int j=0; j < MATRIX_COLS; j++) {
+        write_cache(j, i, min_layer_value);
+      }
+    }
+  }
+}
+
+class LayerCache : public testing::Test
+{
+public:
+  LayerCache()
+  {
+    clear_cache();
+  }
+};
+
+TEST_F(LayerCache, LayerCacheIsInitializedToZero) {
+  for (int i=0; i < MATRIX_ROWS; i++) {
+    for (int j=0; j < MATRIX_COLS; j++) {
+      EXPECT_EQ(read_cache(j, i), min_layer_value);
+    }
+  }
+}
+
+TEST_F(LayerCache, FillAndClearCache) {
+  fill_cache();
+  clear_cache();
+  for (int i=0; i < MATRIX_ROWS; i++) {
+    for (int j=0; j < MATRIX_COLS; j++) {
+      EXPECT_EQ(read_cache(j, i), min_layer_value);
+    }
+  }
+}
+
+TEST_F(LayerCache, WriteAndReadFirstPosMaximumValue) {
+  write_cache(0, 0, max_layer_value);
+  EXPECT_EQ(read_cache(0, 0), max_layer_value);
+  // The second position should not be updated
+  EXPECT_EQ(read_cache(1, 0), min_layer_value);
+  EXPECT_EQ(read_cache(0, 1), min_layer_value);
+}
+
+TEST_F(LayerCache, WriteAndReadSecondPosMaximumValue) {
+  write_cache(1, 0, max_layer_value);
+  EXPECT_EQ(read_cache(1, 0), max_layer_value);
+  // The surrounding positions should not be updated
+  EXPECT_EQ(read_cache(0, 0), min_layer_value);
+  EXPECT_EQ(read_cache(0, 1), min_layer_value);
+}
+
+TEST_F(LayerCache, WriteAndReadFirstPosAlternatingBitsStartingWith1) {
+  write_cache(0, 0, alternating_starting_with_1);
+  EXPECT_EQ(read_cache(0, 0), alternating_starting_with_1);
+  // The second position should not be updated
+  EXPECT_EQ(read_cache(1, 0), min_layer_value);
+  EXPECT_EQ(read_cache(0, 1), min_layer_value);
+}
+
+TEST_F(LayerCache, WriteAndReadSecondPosAlternatingBitsStartingWith1) {
+  write_cache(1, 0, alternating_starting_with_1);
+  EXPECT_EQ(read_cache(1, 0), alternating_starting_with_1);
+  // The surrounding positions should not be updated
+  EXPECT_EQ(read_cache(0, 0), min_layer_value);
+  EXPECT_EQ(read_cache(0, 1), min_layer_value);
+}
+
+TEST_F(LayerCache, WriteAndReadFirstPosAlternatingBitsStartingWith0) {
+  write_cache(0, 0, alternating_starting_with_0);
+  EXPECT_EQ(read_cache(0, 0), alternating_starting_with_0);
+  // The second position should not be updated
+  EXPECT_EQ(read_cache(1, 0), min_layer_value);
+  EXPECT_EQ(read_cache(0, 1), min_layer_value);
+}
+
+TEST_F(LayerCache, WriteAndReadSecondPosAlternatingBitsStartingWith0) {
+  write_cache(1, 0, alternating_starting_with_0);
+  EXPECT_EQ(read_cache(1, 0), alternating_starting_with_0);
+  // The surrounding positions should not be updated
+  EXPECT_EQ(read_cache(0, 0), min_layer_value);
+  EXPECT_EQ(read_cache(0, 1), min_layer_value);
+}

+ 4 - 3
tmk_core/common/action.c

@@ -106,13 +106,14 @@ bool swap_held = false;
 void process_hand_swap(keyevent_t *event) {
     static swap_state_row_t swap_state[MATRIX_ROWS];
 
-    keypos_t pos = event->key;
+    // TODO: Properly support multimatrices, currenty this only works for single-matrix keyboards
+    keypos_t pos = event->key.pos;
     swap_state_row_t col_bit = (swap_state_row_t)1<<pos.col;
     bool do_swap = event->pressed ? swap_hands :
                                     swap_state[pos.row] & (col_bit);
 
     if (do_swap) {
-        event->key = hand_swap_config[pos.row][pos.col];
+        event->key.pos = hand_swap_config[pos.row][pos.col];
         swap_state[pos.row] |= col_bit;
     } else {
         swap_state[pos.row] &= ~(col_bit);
@@ -892,7 +893,7 @@ void clear_keyboard_but_mods(void)
  *
  * FIXME: Needs documentation.
  */
-bool is_tap_key(keypos_t key)
+bool is_tap_key(keymatrix_t key)
 {
     action_t action = layer_switch_get_action(key);
 

+ 2 - 2
tmk_core/common/action.h

@@ -50,7 +50,7 @@ typedef struct {
 void action_exec(keyevent_t event);
 
 /* action for key */
-action_t action_for_key(uint8_t layer, keypos_t key);
+action_t action_for_key(uint8_t layer, keymatrix_t key);
 
 /* macro */
 const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt);
@@ -94,7 +94,7 @@ void unregister_mods(uint8_t mods);
 void clear_keyboard(void);
 void clear_keyboard_but_mods(void);
 void layer_switch(uint8_t new_layer);
-bool is_tap_key(keypos_t key);
+bool is_tap_key(keymatrix_t key);
 
 #ifndef NO_ACTION_TAPPING
 void process_record_tap_hint(keyrecord_t *record);

+ 91 - 26
tmk_core/common/action_layer.c

@@ -220,37 +220,102 @@ void layer_debug(void)
 #endif
 
 #if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS)
-uint8_t source_layers_cache[(MATRIX_ROWS * MATRIX_COLS + 7) / 8][MAX_LAYER_BITS] = {{0}};
+static const uint8_t layer_cache_mask = (1u << MAX_LAYER_BITS) - 1;
 
-void update_source_layers_cache(keypos_t key, uint8_t layer)
+/** \brief Get the pointer to the source layer cache for a connected matrix
+ *
+ * Implement this if you support multiple matrices, see qwiic_keyboard.c for an example
+ * NOTE: The matrix index 0 is the first remote matrix, the function is not called for the master
+ */
+__attribute__((weak))
+uint8_t* multimatrix_get_source_layers_cache(uint8_t matrix) {
+    return 0;
+}
+
+static uint8_t* get_source_layers_cache(keymatrix_t key) {
+  if (key.matrix == 0) {
+    static uint8_t source_layers_cache[(MATRIX_ROWS * MATRIX_COLS * MAX_LAYER_BITS + 7) / 8] = {0};
+    return source_layers_cache;
+  } else {
+    return multimatrix_get_source_layers_cache(key.matrix - 1);
+  }
+}
+
+void update_source_layers_cache(keymatrix_t key, uint8_t layer)
 {
-    const uint8_t key_number = key.col + (key.row * MATRIX_COLS);
-    const uint8_t storage_row = key_number / 8;
-    const uint8_t storage_bit = key_number % 8;
-
-    for (uint8_t bit_number = 0; bit_number < MAX_LAYER_BITS; bit_number++) {
-        source_layers_cache[storage_row][bit_number] ^=
-            (-((layer & (1U << bit_number)) != 0)
-             ^ source_layers_cache[storage_row][bit_number])
-            & (1U << storage_bit);
+  const uint8_t num_cols = keyboard_get_num_cols(key.matrix);
+  const uint8_t num_rows = keyboard_get_num_rows(key.matrix);
+  const uint16_t num_cache_bytes = get_source_layers_cache_size(num_cols, num_rows);
+  uint8_t* cache = get_source_layers_cache(key);
+  const uint16_t key_number = key.pos.col + (key.pos.row * num_cols);
+  const uint32_t bit_number = key_number * MAX_LAYER_BITS;
+  const uint16_t byte_number = bit_number / 8;
+  if (byte_number >= num_cache_bytes) {
+    return;
+  }
+  const uint8_t bit_position = bit_number % 8;
+  int8_t shift = 16 - MAX_LAYER_BITS - bit_position;
+
+  if (shift > 8 ) {
+    // We need to write only one byte
+    shift -= 8;
+    const uint8_t mask = layer_cache_mask << shift;
+    const uint8_t shifted_layer = layer << shift;
+    cache[byte_number] = (shifted_layer & mask) | (cache[byte_number] & (~mask));
+  } else {
+    if (byte_number + 1 >= num_cache_bytes) {
+      return;
     }
+    // We need to write two bytes
+    uint16_t value = layer;
+    uint16_t mask = layer_cache_mask;
+    value <<= shift;
+    mask <<= shift;
+
+    uint16_t masked_value = value & mask;
+    uint16_t inverse_mask = ~mask;
+
+    // This could potentially be done with a single write, but then we have to assume the endian
+    cache[byte_number + 1] = masked_value | (cache[byte_number + 1] & (inverse_mask));
+    masked_value >>= 8;
+    inverse_mask >>= 8;
+    cache[byte_number] = masked_value | (cache[byte_number] & (inverse_mask));
+  }
 }
 
-uint8_t read_source_layers_cache(keypos_t key)
+uint8_t read_source_layers_cache(keymatrix_t key)
 {
-    const uint8_t key_number = key.col + (key.row * MATRIX_COLS);
-    const uint8_t storage_row = key_number / 8;
-    const uint8_t storage_bit = key_number % 8;
-    uint8_t layer = 0;
-
-    for (uint8_t bit_number = 0; bit_number < MAX_LAYER_BITS; bit_number++) {
-        layer |=
-            ((source_layers_cache[storage_row][bit_number]
-              & (1U << storage_bit)) != 0)
-            << bit_number;
+  const uint8_t num_cols = keyboard_get_num_cols(key.matrix);
+  const uint8_t num_rows = keyboard_get_num_rows(key.matrix);
+  const uint16_t num_cache_bytes = get_source_layers_cache_size(num_cols, num_rows);
+  uint8_t* cache = get_source_layers_cache(key);
+  const uint16_t key_number = key.pos.col + (key.pos.row * num_cols);
+  const uint32_t bit_number = key_number * MAX_LAYER_BITS;
+  const uint16_t byte_number = bit_number / 8;
+  if (byte_number >= num_cache_bytes) {
+    return 0;
+  }
+  const uint8_t bit_position = bit_number % 8;
+
+  int8_t shift = 16 - MAX_LAYER_BITS - bit_position;
+
+  if (shift > 8 ) {
+    // We need to read only one byte
+    shift -= 8;
+    return (cache[byte_number] >> shift) & layer_cache_mask;
+  } else {
+    if (byte_number + 1 >= num_cache_bytes) {
+      return 0;
     }
+    // Otherwise read two bytes
+    // This could potentially be done with a single read, but then we have to assume the endian
+    uint16_t value = cache[byte_number] << 8 | cache[byte_number + 1];
+    return (value >> shift) & layer_cache_mask;
+  }
+}
 
-    return layer;
+uint8_t get_source_layers_cache_size(uint8_t num_cols, uint8_t num_rows) {
+  return (num_rows * num_cols * MAX_LAYER_BITS + 7) / 8;
 }
 #endif
 
@@ -261,7 +326,7 @@ uint8_t read_source_layers_cache(keypos_t key)
  * when the layer is switched after the down event but before the up
  * event as they may get stuck otherwise.
  */
-action_t store_or_get_action(bool pressed, keypos_t key)
+action_t store_or_get_action(bool pressed, keymatrix_t key)
 {
 #if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS)
     if (disable_action_cache) {
@@ -288,7 +353,7 @@ action_t store_or_get_action(bool pressed, keypos_t key)
  *
  * FIXME: Needs docs
  */
-int8_t layer_switch_get_layer(keypos_t key)
+int8_t layer_switch_get_layer(keymatrix_t key)
 {
 #ifndef NO_ACTION_LAYER
     action_t action;
@@ -315,7 +380,7 @@ int8_t layer_switch_get_layer(keypos_t key)
  *
  * FIXME: Needs docs
  */
-action_t layer_switch_get_action(keypos_t key)
+action_t layer_switch_get_action(keymatrix_t key)
 {
     return action_for_key(layer_switch_get_layer(key), key);
 }

+ 7 - 5
tmk_core/common/action_layer.h

@@ -91,15 +91,17 @@ uint32_t layer_state_set_kb(uint32_t state);
 #if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS)
 /* The number of bits needed to represent the layer number: log2(32). */
 #define MAX_LAYER_BITS 5
-void update_source_layers_cache(keypos_t key, uint8_t layer);
-uint8_t read_source_layers_cache(keypos_t key);
+void update_source_layers_cache(keymatrix_t key, uint8_t layer);
+uint8_t read_source_layers_cache(keymatrix_t key);
+
+uint8_t get_source_layers_cache_size(uint8_t num_cols, uint8_t num_rows);
 #endif
-action_t store_or_get_action(bool pressed, keypos_t key);
+action_t store_or_get_action(bool pressed, keymatrix_t key);
 
 /* return the topmost non-transparent layer currently associated with key */
-int8_t layer_switch_get_layer(keypos_t key);
+int8_t layer_switch_get_layer(keymatrix_t key);
 
 /* return action depending on current layer status */
-action_t layer_switch_get_action(keypos_t key);
+action_t layer_switch_get_action(keymatrix_t key);
 
 #endif

+ 1 - 1
tmk_core/common/bootmagic.c

@@ -116,7 +116,7 @@ static bool scan_keycode(uint8_t keycode)
         matrix_row_t matrix_row = matrix_get_row(r);
         for (uint8_t c = 0; c < MATRIX_COLS; c++) {
             if (matrix_row & ((matrix_row_t)1<<c)) {
-                if (keycode == keymap_key_to_keycode(0, (keypos_t){ .row = r, .col = c })) {
+                if (keycode == keymap_key_to_keycode(0, (keymatrix_t){ (keypos_t){.row = r, .col = c }, .matrix=0 })) {
                     return true;
                 }
             }

+ 166 - 36
tmk_core/common/keyboard.c

@@ -145,6 +145,132 @@ bool is_keyboard_master(void) {
     return true;
 }
 
+/** \brief Get the number of currently connected matrices
+ *
+ * Implement this if you support multiple matrices, see qwiic_keyboard.c for an example
+ * NOTE: It should return the number of additional matrices, the master is always implied.
+ */
+__attribute__((weak))
+uint8_t multimatrix_get_num_matrices(void) {
+    return 0;
+}
+
+/** \brief Get the number of columns of a connected matrix
+ *
+ * Implement this if you support multiple matrices, see qwiic_keyboard.c for an example
+ * NOTE: The matrix index 0 is the first remote matrix, the function is not called for the master
+ */
+__attribute__((weak))
+uint8_t multimatrix_get_num_cols(uint8_t matrix) {
+    return 0;
+}
+
+/** \brief Get the number of rows of a connected matrix
+ *
+ * Implement this if you support multiple matrices, see qwiic_keyboard.c for an example
+ * NOTE: The matrix index 0 is the first remote matrix, the function is not called for the master
+ */
+__attribute__((weak))
+uint8_t multimatrix_get_num_rows(uint8_t matrix) {
+    return 0;
+}
+
+/** \brief Get the row status of a connected matrix
+ *
+ * Implement this if you support multiple matrices, see qwiic_keyboard.c for an example
+ * NOTE: The matrix index 0 is the first remote matrix, the function is not called for the master
+ */
+__attribute__((weak))
+uint32_t multimatrix_get_row(uint8_t matrix, uint8_t row) {
+    return 0;
+}
+
+/** \brief Get the row cache of a connected matrix
+ *
+ * Implement this if you support multiple matrices, see qwiic_keyboard.c for an example
+ * NOTE: The matrix index 0 is the first remote matrix, the function is not called for the master
+ */
+__attribute__((weak))
+uint32_t multimatrix_get_row_cache(uint8_t matrix, uint8_t row) {
+    return 0;
+}
+
+/** \brief Set the row cache of a connected matrix
+ *
+ * Implement this if you support multiple matrices, see qwiic_keyboard.c for an example
+ * NOTE: The matrix index 0 is the first remote matrix, the function is not called for the master
+ */
+__attribute__((weak))
+void multimatrix_set_row_cache(uint8_t matrix, uint8_t row, uint32_t value) {
+}
+
+/** \brief Get the number of currently connected matrices
+ *
+ * For normal keyboards this usually returns 1, but for multi-matrix keyboards this will
+ * return the total number of connected keyboards/modules including the master
+ */
+uint8_t keyboard_get_num_matrices(void) {
+    return 1 + multimatrix_get_num_matrices();
+}
+
+/** \brief Get the number of columns of a connected matrix
+ *
+ * Specify the matrix index to query connected multi-matrix keyboards/modules
+ * 0 is always the master
+ */
+uint8_t keyboard_get_num_cols(uint8_t matrix) {
+    if (matrix == 0) {
+      return MATRIX_COLS;
+    } else {
+      return multimatrix_get_num_cols(matrix - 1);
+    }
+}
+
+/** \brief Get the number of rows of a connected matrix
+ *
+ * Specify the matrix index to query connected multi-matrix keyboards/modules
+ * 0 is always the master
+ */
+uint8_t keyboard_get_num_rows(uint8_t matrix) {
+    if (matrix == 0) {
+      return MATRIX_ROWS;
+    } else {
+      return multimatrix_get_num_rows(matrix - 1);
+    }
+}
+
+/** \brief Get the row status of a connected matrix
+ *
+ * Specify the matrix index to query connected multi-matrix keyboards/modules
+ * 0 is always the master
+ */
+uint32_t keyboard_get_row(uint8_t matrix, uint8_t row) {
+    if (matrix == 0) {
+      return matrix_get_row(row);
+    } else {
+      return multimatrix_get_row(matrix - 1, row);
+    }
+}
+
+static matrix_row_t matrix_prev[MATRIX_ROWS];
+
+static uint32_t get_row_cache(uint8_t matrix, uint8_t row) {
+    if (matrix == 0) {
+      return matrix_prev[row];
+    } else {
+      return multimatrix_get_row_cache(matrix - 1, row);
+    }
+}
+
+static void set_row_cache(uint8_t matrix, uint8_t row, uint32_t value) {
+    if (matrix == 0) {
+      matrix_prev[row] = value;
+    } else {
+      return multimatrix_set_row_cache(matrix - 1, row, value);
+    }
+}
+
+
 /** \brief keyboard_init
  *
  * FIXME: needs doc
@@ -203,53 +329,57 @@ void keyboard_init(void) {
  */
 void keyboard_task(void)
 {
-    static matrix_row_t matrix_prev[MATRIX_ROWS];
-#ifdef MATRIX_HAS_GHOST
-  //  static matrix_row_t matrix_ghost[MATRIX_ROWS];
-#endif
     static uint8_t led_status = 0;
-    matrix_row_t matrix_row = 0;
-    matrix_row_t matrix_change = 0;
+    uint32_t matrix_row = 0;
+    uint32_t matrix_change = 0;
 #ifdef QMK_KEYS_PER_SCAN
     uint8_t keys_processed = 0;
 #endif
 
     matrix_scan();
     if (is_keyboard_master()) {
-        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) {
+        for (uint8_t m = 0; m < keyboard_get_num_matrices(); m++) {
+            uint8_t num_cols = keyboard_get_num_cols(m);
+            uint8_t num_rows = keyboard_get_num_rows(m);
+            for (uint8_t r = 0; r < num_rows; r++) {
+                matrix_row = keyboard_get_row(m, r);
+                uint32_t row_cache = get_row_cache(m, r);
+                matrix_change = matrix_row ^ row_cache;
+                if (matrix_change) {
 #ifdef MATRIX_HAS_GHOST
-                if (has_ghost_in_row(r, matrix_row)) {
-                    /* Keep track of whether ghosted status has changed for
-                    * debugging. But don't update matrix_prev until un-ghosted, or
-                    * the last key would be lost.
-                    */
-                    //if (debug_matrix && matrix_ghost[r] != matrix_row) {
-                    //    matrix_print();
-                    //}
+                    //NOTE: The we support ghosting only for the main matrix, since it's only useful for old keyboards without diodes
+                    if (has_ghost_in_row(r, matrix_row)) {
+                        /* Keep track of whether ghosted status has changed for
+                        * debugging. But don't update matrix_prev until un-ghosted, or
+                        * the last key would be lost.
+                        */
+                        //if (debug_matrix && matrix_ghost[r] != matrix_row) {
+                        //    matrix_print();
+                        //}
+                        //matrix_ghost[r] = matrix_row;
+                        continue;
+                    }
                     //matrix_ghost[r] = matrix_row;
-                    continue;
-                }
-                //matrix_ghost[r] = matrix_row;
-#endif
-                if (debug_matrix) matrix_print();
-                for (uint8_t c = 0; c < MATRIX_COLS; c++) {
-                    if (matrix_change & ((matrix_row_t)1<<c)) {
-                        action_exec((keyevent_t){
-                            .key = (keypos_t){ .row = r, .col = c },
-                            .pressed = (matrix_row & ((matrix_row_t)1<<c)),
-                            .time = (timer_read() | 1) /* time should not be 0 */
-                        });
-                        // record a processed key
-                        matrix_prev[r] ^= ((matrix_row_t)1<<c);
+#endif
+                    if (debug_matrix) matrix_print();
+                    for (uint8_t c = 0; c < num_cols; c++) {
+                        if (matrix_change & (uint32_t)(1u<<c)) {
+                            action_exec((keyevent_t){
+                                // The main matrix is always 0
+                                .key = (keymatrix_t){.pos = (keypos_t){ .row = r, .col = c }, .matrix = m},
+                                .pressed = (matrix_row & ((uint32_t)1u<<c)),
+                                .time = (timer_read() | 1) /* time should not be 0 */
+                            });
+                            // record a processed key
+                            row_cache ^= (uint32_t)(1u<<c);
+                            set_row_cache(m, r, row_cache);
 #ifdef QMK_KEYS_PER_SCAN
-                        // only jump out if we have processed "enough" keys.
-                        if (++keys_processed >= 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;
+                            // process a key per task call
+                            goto MATRIX_LOOP_END;
+                        }
                     }
                 }
             }

+ 16 - 5
tmk_core/common/keyboard.h

@@ -32,27 +32,33 @@ typedef struct {
     uint8_t row;
 } keypos_t;
 
+/* the key and matrix position */
+typedef struct {
+  keypos_t pos;
+  uint8_t matrix;
+} keymatrix_t;
+
 /* key event */
 typedef struct {
-    keypos_t key;
+    keymatrix_t    key;
     bool     pressed;
     uint16_t time;
 } keyevent_t;
 
-/* equivalent test of keypos_t */
-#define KEYEQ(keya, keyb)       ((keya).row == (keyb).row && (keya).col == (keyb).col)
+/* equivalent test of keymatrix_t */
+#define KEYEQ(keya, keyb)       (keya.pos.row == keyb.pos.row && keya.pos.col == keyb.pos.col && keya.matrix == keyb.matrix)
 
 /* Rules for No Event:
  * 1) (time == 0) to handle (keyevent_t){} as empty event
  * 2) Matrix(255, 255) to make TICK event available
  */
-static inline bool IS_NOEVENT(keyevent_t event) { return event.time == 0 || (event.key.row == 255 && event.key.col == 255); }
+static inline bool IS_NOEVENT(keyevent_t event) { return event.time == 0 || (event.key.pos.row == 255 && event.key.pos.col == 255); }
 static inline bool IS_PRESSED(keyevent_t event) { return (!IS_NOEVENT(event) && event.pressed); }
 static inline bool IS_RELEASED(keyevent_t event) { return (!IS_NOEVENT(event) && !event.pressed); }
 
 /* Tick event */
 #define TICK                    (keyevent_t){           \
-    .key = (keypos_t){ .row = 255, .col = 255 },           \
+    .key = (keymatrix_t){ .pos = (keypos_t){.row = 255, .col = 255}, .matrix = 0 },           \
     .pressed = false,                                   \
     .time = (timer_read() | 1)                          \
 }
@@ -66,6 +72,11 @@ void keyboard_task(void);
 /* it runs when host LED status is updated */
 void keyboard_set_leds(uint8_t leds);
 
+uint8_t keyboard_get_num_matrices(void);
+uint8_t keyboard_get_num_cols(uint8_t matrix);
+uint8_t keyboard_get_num_rows(uint8_t matrix);
+uint32_t keyboard_get_row(uint8_t matrix, uint8_t row);
+
 #ifdef __cplusplus
 }
 #endif